mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-01 12:52:11 +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
81 lines
2.5 KiB
TypeScript
81 lines
2.5 KiB
TypeScript
/**
|
|
* Claude Code tool name remapping.
|
|
*
|
|
* Anthropic uses tool name fingerprinting to detect third-party clients.
|
|
* Real Claude Code uses TitleCase tool names (Bash, Read, Write, etc.)
|
|
* while third-party clients like OpenCode use lowercase.
|
|
*
|
|
* This module remaps tool names in both directions:
|
|
* - Request path: lowercase → TitleCase (before sending to Anthropic)
|
|
* - Response path: TitleCase → lowercase (for clients expecting lowercase)
|
|
*/
|
|
|
|
const TOOL_RENAME_MAP: Record<string, string> = {
|
|
bash: "Bash",
|
|
read: "Read",
|
|
write: "Write",
|
|
edit: "Edit",
|
|
glob: "Glob",
|
|
grep: "Grep",
|
|
task: "Task",
|
|
webfetch: "WebFetch",
|
|
todowrite: "TodoWrite",
|
|
todoread: "TodoRead",
|
|
question: "Question",
|
|
skill: "Skill",
|
|
multiedit: "MultiEdit",
|
|
notebook: "Notebook",
|
|
};
|
|
|
|
const REVERSE_MAP: Record<string, string> = {};
|
|
for (const [k, v] of Object.entries(TOOL_RENAME_MAP)) {
|
|
REVERSE_MAP[v] = k;
|
|
}
|
|
|
|
export function remapToolNamesInRequest(body: Record<string, unknown>): void {
|
|
// Remap tool definitions
|
|
const tools = body.tools as Array<Record<string, unknown>> | undefined;
|
|
if (Array.isArray(tools)) {
|
|
for (const tool of tools) {
|
|
const name = String(tool.name || "");
|
|
if (TOOL_RENAME_MAP[name]) {
|
|
tool.name = TOOL_RENAME_MAP[name];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remap tool_result references 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.type === "tool_use" && typeof block.name === "string") {
|
|
const mapped = TOOL_RENAME_MAP[block.name];
|
|
if (mapped) block.name = mapped;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remap tool_choice
|
|
const toolChoice = body.tool_choice as Record<string, unknown> | undefined;
|
|
if (toolChoice?.type === "tool" && typeof toolChoice.name === "string") {
|
|
const mapped = TOOL_RENAME_MAP[toolChoice.name];
|
|
if (mapped) toolChoice.name = mapped;
|
|
}
|
|
}
|
|
|
|
export function remapToolNamesInResponse(text: string): string {
|
|
// Replace TitleCase tool names back to lowercase in SSE chunks
|
|
for (const [titleCase, lower] of Object.entries(REVERSE_MAP)) {
|
|
// Match in "name":"ToolName" patterns
|
|
text = text.replaceAll(`"name":"${titleCase}"`, `"name":"${lower}"`);
|
|
text = text.replaceAll(`"name": "${titleCase}"`, `"name": "${lower}"`);
|
|
}
|
|
return text;
|
|
}
|
|
|
|
export { TOOL_RENAME_MAP, REVERSE_MAP };
|