Skip to content
Closed
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
11 changes: 11 additions & 0 deletions src/providers/proxies/claude-proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -653,8 +653,19 @@ export const prepareClaudeProxyRequest = (

// OAuth sessions require Claude Code identity headers.
// https://github.com/badlogic/pi-mono/blob/5c0ec26c28c918c5301f218e8c13fcc540d8e3a4/packages/ai/src/providers/anthropic.ts#L525-L538
// Claude Code's Messages client uses the 2023-06-01 API version and accepts
// JSON even when `stream: true` requests an SSE response.
input.headers.delete("x-api-key");
input.headers.delete("cookie");
input.headers.delete("proxy-authorization");
input.headers.delete("content-encoding");
input.headers.delete("content-length");
input.headers.delete("host");
input.headers.set("authorization", `Bearer ${input.accessToken}`);
input.headers.set("anthropic-version", "2023-06-01");
input.headers.set("anthropic-beta", mergedBetas);
input.headers.set("accept", "application/json");
input.headers.set("content-type", "application/json");
// 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);
Expand Down
39 changes: 39 additions & 0 deletions tests/providers/proxy-contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3024,6 +3024,16 @@ describe("proxy contract: claude", () => {
test("merges beta headers and rewrites payload tool names", () => {
const headers = new Headers({
"anthropic-beta": `custom-beta,${CLAUDE_REQUIRED_BETA_HEADERS[0]}`,
"anthropic-version": "2020-01-01",
accept: "text/plain",
"content-type": "text/plain",
"content-encoding": "gzip",
"content-length": "1234",
authorization: "Bearer caller-token",
"x-api-key": "caller-key",
cookie: "session=caller-cookie",
"proxy-authorization": "Basic caller-secret",
host: "caller.invalid",
});
const requestBody = {
system:
Expand Down Expand Up @@ -3065,6 +3075,19 @@ describe("proxy contract: claude", () => {
};

expect(headers.get("authorization")).toBe("Bearer claude-token");
expect(headers.get("anthropic-version")).toBe("2023-06-01");
expect(headers.get("accept")).toBe("application/json");
expect(headers.get("content-type")).toBe("application/json");
for (const name of [
"x-api-key",
"cookie",
"proxy-authorization",
"content-encoding",
"content-length",
"host",
]) {
expect(headers.has(name)).toBe(false);
}
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 Expand Up @@ -3101,6 +3124,22 @@ describe("proxy contract: claude", () => {
expect(transformed.messages[0]?.content[0]?.name).toBe("mcp_Shell");
});

test("keeps Claude Code JSON Accept for streaming Messages requests", () => {
const headers = new Headers({ accept: "text/plain" });
prepareClaudeProxyRequest({
requestUrl: new URL("https://kleis.local/v1/messages"),
headers,
bodyText: '{"stream":true}',
bodyJson: { stream: true },
accessToken: "claude-token",
metadata: null,
});

expect(headers.get("accept")).toBe("application/json");
expect(headers.get("content-type")).toBe("application/json");
expect(headers.get("anthropic-version")).toBe("2023-06-01");
});

test("rewrites repo path and directories in non-OpenCode Claude system prompts", () => {
const requestBody = {
system:
Expand Down
Loading