fix(anthropic): restore subscription OAuth billing routing

This commit is contained in:
Vincent Koc
2026-07-06 06:18:45 -07:00
parent 9344bb8b48
commit 709be93ca8
4 changed files with 46 additions and 0 deletions
@@ -149,6 +149,37 @@ describe("Anthropic provider", () => {
expect(config.authToken).toBeNull();
});
it("puts Claude subscription billing identity first for OAuth requests", async () => {
let capturedPayload: unknown;
const stream = streamSimpleAnthropic(
makeAnthropicModel(),
{
messages: [{ role: "user", content: "hello", timestamp: 1 }],
},
{
apiKey: "sk-ant-oat01-test-token",
onPayload: (payload) => {
capturedPayload = payload;
},
},
);
const result = await stream.result();
expect(result.stopReason).toBe("error");
expect((capturedPayload as { system?: unknown }).system).toEqual([
{
type: "text",
text: "x-anthropic-billing-header: cc_version=2.1.75; cc_entrypoint=sdk-cli;",
},
{
type: "text",
text: "You are Claude Code, Anthropic's official CLI for Claude.",
cache_control: { type: "ephemeral" },
},
]);
});
it("keeps aggregate cache billing buckets out of the context total", async () => {
const client = {
messages: {
+6
View File
@@ -109,6 +109,7 @@ function getCacheControl(
// Stealth mode: Mimic Claude Code's tool naming exactly
const claudeCodeVersion = "2.1.75";
const claudeCodeBillingSystemBlock = `x-anthropic-billing-header: cc_version=${claudeCodeVersion}; cc_entrypoint=sdk-cli;`;
// Claude Code 2.x tool names (canonical casing)
// Source: https://cchistory.mariozechner.at/data/prompts-2.1.11.md
@@ -1564,6 +1565,11 @@ function buildAnthropicSystemBlocks(
): TextBlockParam[] | undefined {
const blocks: TextBlockParam[] = [];
if (isOAuthTokenResult) {
// Anthropic uses this first system block to route Claude subscription OAuth billing.
blocks.push({
type: "text",
text: claudeCodeBillingSystemBlock,
});
blocks.push({
type: "text",
text: "You are Claude Code, Anthropic's official CLI for Claude.",
@@ -1618,6 +1618,9 @@ describe("anthropic transport stream", () => {
expect(headers.get("user-agent")).toContain("claude-cli/");
const firstCallParams = latestAnthropicRequest().payload;
const system = requireArray(firstCallParams.system, "system");
expect(requireRecord(system[0], "billing system item").text).toBe(
"x-anthropic-billing-header: cc_version=2.1.75; cc_entrypoint=sdk-cli;",
);
expect(
system.some(
(item) =>
+6
View File
@@ -82,6 +82,7 @@ import {
import type { ContextUsage } from "./usage.js";
const CLAUDE_CODE_VERSION = "2.1.75";
const CLAUDE_CODE_BILLING_SYSTEM_BLOCK = `x-anthropic-billing-header: cc_version=${CLAUDE_CODE_VERSION}; cc_entrypoint=sdk-cli;`;
const ANTHROPIC_MESSAGES_ERROR_BODY_MAX_BYTES = 8 * 1024;
const ANTHROPIC_MESSAGES_ERROR_BODY_MAX_CHARS = 400;
const ANTHROPIC_MESSAGES_ERROR_BODY_READ_IDLE_TIMEOUT_MS = 10_000;
@@ -1006,6 +1007,11 @@ function buildAnthropicParams(
}
if (isOAuthToken) {
params.system = [
// Anthropic requires this first block to route Claude subscription OAuth billing.
{
type: "text",
text: CLAUDE_CODE_BILLING_SYSTEM_BLOCK,
},
{
type: "text",
text: "You are Claude Code, Anthropic's official CLI for Claude.",