mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-02 21:32:10 +03:00
Squash merge of feat/claude-code-native-parity into release/v3.6.5. ## Wiring 1. base.ts: CCH xxHash64 body signing for anthropic-compatible-cc-* providers, applied after CLI fingerprint ordering so the hash covers the final bytes sent upstream. 2. chatCore.ts: After buildClaudeCodeCompatibleRequest(), applies synchronous parity pipeline steps: - remapToolNamesInRequest() — TitleCase tool name mapping (14 tools) - enforceThinkingTemperature() — temperature=1 when thinking active - disableThinkingIfToolChoiceForced() — remove thinking on forced tool_choice Cache-control limit enforcement intentionally omitted from chatCore because the billing-header system block counts toward the 4-block cap and would strip legitimate client cache markers. 3. xxhash-wasm: installed (was declared in package.json by PR but not installed). ## Test updates - tests/unit/claude-code-parity.test.mjs: 25 new tests covering CCH signing, fingerprint computation, tool remapping, and API constraints. - tests/unit/cc-compatible-provider.test.mjs: fixed pre-existing assertion that expected no cache_control on system blocks (billing header now carries ephemeral per PR #1188 design). Closes #1188
128 lines
3.6 KiB
TypeScript
128 lines
3.6 KiB
TypeScript
/**
|
|
* Claude Code API constraints.
|
|
*
|
|
* Enforces Anthropic API requirements that real Claude Code handles:
|
|
* 1. temperature=1 when thinking is enabled
|
|
* 2. Disable thinking when tool_choice forces a specific tool
|
|
* 3. Enforce max 4 cache_control breakpoints
|
|
* 4. Normalize cache_control TTL ordering
|
|
*/
|
|
|
|
export function enforceThinkingTemperature(body: Record<string, unknown>): void {
|
|
const thinking = body.thinking as Record<string, unknown> | undefined;
|
|
if (thinking?.type === "enabled" || thinking?.type === "adaptive") {
|
|
body.temperature = 1;
|
|
}
|
|
}
|
|
|
|
export function disableThinkingIfToolChoiceForced(body: Record<string, unknown>): void {
|
|
const toolChoice = body.tool_choice as Record<string, unknown> | string | undefined;
|
|
if (!toolChoice) return;
|
|
|
|
const isForced =
|
|
toolChoice === "any" ||
|
|
(typeof toolChoice === "object" && (toolChoice.type === "any" || toolChoice.type === "tool"));
|
|
|
|
if (isForced && body.thinking) {
|
|
delete body.thinking;
|
|
}
|
|
}
|
|
|
|
const MAX_CACHE_CONTROL_BLOCKS = 4;
|
|
|
|
export function enforceCacheControlLimit(body: Record<string, unknown>): void {
|
|
let count = 0;
|
|
|
|
// Count in system blocks
|
|
const system = body.system as Array<Record<string, unknown>> | undefined;
|
|
if (Array.isArray(system)) {
|
|
for (const block of system) {
|
|
if (block.cache_control) count++;
|
|
}
|
|
}
|
|
|
|
// Count in messages
|
|
const messages = body.messages as Array<Record<string, unknown>> | undefined;
|
|
if (Array.isArray(messages)) {
|
|
for (const msg of messages) {
|
|
const content = msg.content as Array<Record<string, unknown>> | undefined;
|
|
if (!Array.isArray(content)) continue;
|
|
for (const block of content) {
|
|
if (block.cache_control) count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Count in tools
|
|
const tools = body.tools as Array<Record<string, unknown>> | undefined;
|
|
if (Array.isArray(tools)) {
|
|
for (const tool of tools) {
|
|
if (tool.cache_control) count++;
|
|
}
|
|
}
|
|
|
|
if (count <= MAX_CACHE_CONTROL_BLOCKS) return;
|
|
|
|
// Strip excess cache_control blocks from the end (keep first 4)
|
|
let remaining = MAX_CACHE_CONTROL_BLOCKS;
|
|
|
|
if (Array.isArray(system)) {
|
|
for (const block of system) {
|
|
if (block.cache_control) {
|
|
if (remaining > 0) {
|
|
remaining--;
|
|
} else {
|
|
delete block.cache_control;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Array.isArray(messages)) {
|
|
for (const msg of messages) {
|
|
const content = msg.content as Array<Record<string, unknown>> | undefined;
|
|
if (!Array.isArray(content)) continue;
|
|
for (const block of content) {
|
|
if (block.cache_control) {
|
|
if (remaining > 0) {
|
|
remaining--;
|
|
} else {
|
|
delete block.cache_control;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Array.isArray(tools)) {
|
|
for (const tool of tools) {
|
|
if (tool.cache_control) {
|
|
if (remaining > 0) {
|
|
remaining--;
|
|
} else {
|
|
delete tool.cache_control;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
export function ensureCacheControlOnLastUserMessage(body: Record<string, unknown>): void {
|
|
const messages = body.messages as Array<Record<string, unknown>> | undefined;
|
|
if (!Array.isArray(messages) || messages.length === 0) return;
|
|
|
|
// Find the last user message
|
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
if (String(messages[i].role) === "user") {
|
|
const content = messages[i].content;
|
|
if (Array.isArray(content) && content.length > 0) {
|
|
const lastBlock = content[content.length - 1] as Record<string, unknown>;
|
|
if (!lastBlock.cache_control) {
|
|
lastBlock.cache_control = { type: "ephemeral" };
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|