diff --git a/src/providers/constants.ts b/src/providers/constants.ts index 0e58641..749f978 100644 --- a/src/providers/constants.ts +++ b/src/providers/constants.ts @@ -30,8 +30,8 @@ export const CLAUDE_REQUIRED_BETA_HEADERS = [ CLAUDE_INTERLEAVED_THINKING_BETA_HEADER, CLAUDE_FINE_GRAINED_TOOL_STREAMING_BETA_HEADER, ] as const; -// https://github.com/badlogic/pi-mono/blob/5c0ec26c28c918c5301f218e8c13fcc540d8e3a4/packages/ai/src/providers/anthropic.ts#L537 -export const CLAUDE_CLI_USER_AGENT = "claude-cli/2.1.2 (external, cli)"; +// Claude Fable 5.1 requires Claude Code 2.1.251 or newer. +export const CLAUDE_CLI_USER_AGENT = "claude-cli/2.1.251 (external, cli)"; // https://github.com/badlogic/pi-mono/blob/5c0ec26c28c918c5301f218e8c13fcc540d8e3a4/packages/ai/src/providers/anthropic.ts#L581-L586 export const CLAUDE_SYSTEM_IDENTITY = "You are Claude Code, Anthropic's official CLI for Claude."; diff --git a/src/providers/proxies/claude-proxy.ts b/src/providers/proxies/claude-proxy.ts index 80152c9..d8302d1 100644 --- a/src/providers/proxies/claude-proxy.ts +++ b/src/providers/proxies/claude-proxy.ts @@ -655,10 +655,9 @@ export const prepareClaudeProxyRequest = ( // https://github.com/badlogic/pi-mono/blob/5c0ec26c28c918c5301f218e8c13fcc540d8e3a4/packages/ai/src/providers/anthropic.ts#L525-L538 input.headers.set("authorization", `Bearer ${input.accessToken}`); input.headers.set("anthropic-beta", mergedBetas); - input.headers.set( - "user-agent", - input.metadata?.userAgent ?? CLAUDE_CLI_USER_AGENT - ); + // Keep existing accounts on the minimum supported Claude Code version even + // when their persisted metadata still contains an older user agent. + input.headers.set("user-agent", CLAUDE_CLI_USER_AGENT); input.headers.set("x-app", "cli"); const transformedPayload = transformClaudeRequestPayload( diff --git a/tests/providers/proxy-contract.test.ts b/tests/providers/proxy-contract.test.ts index 5377140..5ee1d1f 100644 --- a/tests/providers/proxy-contract.test.ts +++ b/tests/providers/proxy-contract.test.ts @@ -1,6 +1,7 @@ import { afterEach, describe, expect, test } from "bun:test"; import { + CLAUDE_CLI_USER_AGENT, CLAUDE_REQUIRED_BETA_HEADERS, CLAUDE_SYSTEM_IDENTITY, CODEX_ACCOUNT_ID_HEADER, @@ -12,6 +13,7 @@ import { COPILOT_VISION_HEADER, } from "../../src/providers/constants"; import type { + ClaudeAccountMetadata, CodexAccountMetadata, CopilotAccountMetadata, } from "../../src/providers/metadata"; @@ -2388,6 +2390,18 @@ describe("proxy contract: copilot", () => { }); describe("proxy contract: claude", () => { + const legacyClaudeMetadata: ClaudeAccountMetadata = { + provider: "claude", + tokenType: null, + scope: null, + oauthMode: "max", + oauthHost: "claude.ai", + betaHeaders: [...CLAUDE_REQUIRED_BETA_HEADERS], + userAgent: "claude-cli/2.1.2 (external, cli)", + systemIdentity: CLAUDE_SYSTEM_IDENTITY, + toolPrefix: "mcp_", + }; + const prepareClaudeUsageRequest = ( onTokenUsage?: ((usage: TokenUsage) => void) | null ) => @@ -2434,7 +2448,7 @@ describe("proxy contract: claude", () => { bodyText: JSON.stringify(requestBody), bodyJson: requestBody, accessToken: "claude-token", - metadata: null, + metadata: legacyClaudeMetadata, }); const transformed = JSON.parse(result.bodyText) as { @@ -2445,6 +2459,7 @@ describe("proxy contract: claude", () => { }; expect(headers.get("authorization")).toBe("Bearer claude-token"); + expect(headers.get("user-agent")).toBe(CLAUDE_CLI_USER_AGENT); expect(headers.get("anthropic-beta")).toBe( [...CLAUDE_REQUIRED_BETA_HEADERS, "custom-beta"].join(",") );