Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`).
Expand Down
60 changes: 60 additions & 0 deletions tests/unit/translation/anthropic-to-codex.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.");
Expand Down
Loading