diff --git a/CHANGELOG.md b/CHANGELOG.md index df662b9b..0cca4ec6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ ### Fixed +- Claude Code 2.1.84 计费头 strip 行为新增 rotation 变体回归覆盖:实测 Claude Code 把 `x-anthropic-billing-header: cc_version=...; cc_entrypoint=cli; cch=<5hex>;` 作为 `system[0]` 独立块下发,`cc_version` 后缀和 `cch` 每请求 reroll;新增 `it.each` 覆盖 5 个真实 `cc_version` 后缀(c8e / 76b / f51 / 5b4 / 4f3)与"两次不同 cch 产出同一份 `instructions`"的不变性断言,防止后续改 strip(如改 `startsWith` → 正则、或加 inline 清洗)意外让 `cch` 漏进 `instructions` 污染上游 prompt cache(`tests/unit/translation/anthropic-to-codex.test.ts`) - Dashboard Errors tab now has a real clear action: `DELETE /admin/error-logs` removes current + backup JSONL files and the read cursor so repeated `StreamUpstreamPrematureClose` groups can be cleared from the page instead of only marked read. Anthropic setup defaults now map Opus 4.7 → `gpt-5.5` and Sonnet 4.6 → `gpt-5.4`, and the built-in Anthropic API-key catalog lists Claude Opus 4.7 (`src/logs/error-log.ts`, `src/routes/admin/error-logs.ts`, `shared/hooks/use-error-logs.ts`, `web/src/pages/ErrorsPage.tsx`, `web/src/components/AnthropicSetup.tsx`, `src/auth/api-key-catalog.ts`). - Claude Code 的 `Read` 工具参数里如果 `pages` 传成空字符串或空白字符串,会在 Codex → Anthropic 转换时被自动剔除,避免 GPT-5.5 反复触发 `Read tool validation error: Invalid pages parameter: ""` 并重试隔离工作树;对应单测覆盖流式与非流式两条路径,以及非空 PDF 页码范围保留(`src/translation/codex-to-anthropic.ts`、`tests/unit/translation/codex-to-anthropic-read-pages.test.ts`)。 - Dashboard egress log details now include Codex request `reasoning` and optional `service_tier`, so `/admin/logs` can show the actual reasoning effort sent upstream instead of only `model` / `stream` / `useWebSocket` (`src/routes/shared/proxy-egress-log.ts`, `tests/unit/routes/shared/proxy-egress-log.test.ts`). diff --git a/tests/unit/translation/anthropic-to-codex.test.ts b/tests/unit/translation/anthropic-to-codex.test.ts index bf94d380..1aeb1a1e 100644 --- a/tests/unit/translation/anthropic-to-codex.test.ts +++ b/tests/unit/translation/anthropic-to-codex.test.ts @@ -106,6 +106,66 @@ describe("translateAnthropicToCodexRequest", () => { expect(result.instructions).toBe("Keep answers short."); }); + // Real Claude Code 2.1.84 emits the billing header as a standalone block[0] + // with per-request rotating cc_version + cch. Tests must prove the strip is + // invariant across that rotation, otherwise the cache-buster leaks into + // `instructions` and tanks upstream prompt cache. + it.each([ + "x-anthropic-billing-header: cc_version=2.1.84.c8e; cc_entrypoint=cli; cch=da09b;", + "x-anthropic-billing-header: cc_version=2.1.84.76b; cc_entrypoint=cli; cch=46d1d;", + "x-anthropic-billing-header: cc_version=2.1.84.f51; cc_entrypoint=cli; cch=3c1ed;", + "x-anthropic-billing-header: cc_version=2.1.84.5b4; cc_entrypoint=cli; cch=8f29c;", + "x-anthropic-billing-header: cc_version=2.1.84.4f3; cc_entrypoint=cli; cch=d1658;", + ])("strips Claude Code billing header variant: %s", (billingText) => { + const result = translateAnthropicToCodexRequest( + makeRequest({ + system: [ + { type: "text" as const, text: billingText }, + { + type: "text" as const, + text: "You are Claude Code, Anthropic's official CLI for Claude.", + cache_control: { type: "ephemeral" }, + }, + { + type: "text" as const, + text: "\nYou are an interactive agent that helps users with software engineering tasks.", + cache_control: { type: "ephemeral" }, + }, + ], + }), + ); + expect(result.instructions).toBe( + "You are Claude Code, Anthropic's official CLI for Claude.\n\nYou are an interactive agent that helps users with software engineering tasks.", + ); + expect(result.instructions).not.toMatch(/cch=|cc_version=|x-anthropic-billing/); + }); + + it("produces identical instructions across rotating cc_version + cch values", () => { + const baseSystem = (billingText: string) => [ + { type: "text" as const, text: billingText }, + { + type: "text" as const, + text: "You are Claude Code, Anthropic's official CLI for Claude.", + cache_control: { type: "ephemeral" as const }, + }, + ]; + const a = translateAnthropicToCodexRequest( + makeRequest({ + system: baseSystem( + "x-anthropic-billing-header: cc_version=2.1.84.c8e; cc_entrypoint=cli; cch=da09b;", + ), + }), + ); + const b = translateAnthropicToCodexRequest( + makeRequest({ + system: baseSystem( + "x-anthropic-billing-header: cc_version=2.1.84.4f3; cc_entrypoint=cli; cch=d1658;", + ), + }), + ); + expect(a.instructions).toBe(b.instructions); + }); + it("falls back to default instructions when no system provided", () => { const result = translateAnthropicToCodexRequest(makeRequest()); expect(result.instructions).toBe("You are a helpful assistant.");