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
4 changes: 2 additions & 2 deletions src/providers/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.";
Expand Down
7 changes: 3 additions & 4 deletions src/providers/proxies/claude-proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
17 changes: 16 additions & 1 deletion tests/providers/proxy-contract.test.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -12,6 +13,7 @@ import {
COPILOT_VISION_HEADER,
} from "../../src/providers/constants";
import type {
ClaudeAccountMetadata,
CodexAccountMetadata,
CopilotAccountMetadata,
} from "../../src/providers/metadata";
Expand Down Expand Up @@ -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
) =>
Expand Down Expand Up @@ -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 {
Expand All @@ -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(",")
);
Expand Down