mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 04:12: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
47 lines
1.4 KiB
TypeScript
47 lines
1.4 KiB
TypeScript
/**
|
|
* Claude Code fingerprint computation.
|
|
*
|
|
* The billing header includes a 3-char fingerprint derived from:
|
|
* SHA256(SALT + msg[4] + msg[7] + msg[20] + version)[:3]
|
|
*
|
|
* This fingerprint is computed from the first user message text and
|
|
* included in cc_version=VERSION.FINGERPRINT in the billing header.
|
|
*/
|
|
|
|
import { createHash } from "node:crypto";
|
|
|
|
const FINGERPRINT_SALT = "59cf53e54c78";
|
|
|
|
export function computeFingerprint(firstUserMessageText: string, version: string): string {
|
|
const indices = [4, 7, 20];
|
|
const chars = indices.map((i) => firstUserMessageText[i] || "0").join("");
|
|
const input = `${FINGERPRINT_SALT}${chars}${version}`;
|
|
const hash = createHash("sha256").update(input).digest("hex");
|
|
return hash.slice(0, 3);
|
|
}
|
|
|
|
export function extractFirstUserMessageText(
|
|
messages: Array<{ role?: string; content?: unknown }> | undefined
|
|
): string {
|
|
if (!Array.isArray(messages)) return "";
|
|
for (const msg of messages) {
|
|
if (String(msg?.role).toLowerCase() !== "user") continue;
|
|
const content = msg?.content;
|
|
if (typeof content === "string") return content;
|
|
if (Array.isArray(content)) {
|
|
for (const block of content) {
|
|
if (
|
|
block &&
|
|
typeof block === "object" &&
|
|
"text" in block &&
|
|
typeof (block as Record<string, unknown>).text === "string"
|
|
) {
|
|
return (block as Record<string, unknown>).text as string;
|
|
}
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
return "";
|
|
}
|