From 2152d6891b711d51ca3bbd74a5f15b773fc867fc Mon Sep 17 00:00:00 2001 From: i1hwan Date: Sat, 11 Apr 2026 21:18:08 +0900 Subject: [PATCH] fix(proxy): rewrite Claude Code compatible requests Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- open-sse/services/claudeCodeCompatible.ts | 19 ++++++++-- .../claude-code-compatible-request.test.mjs | 35 ++++++++++++++++--- 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/open-sse/services/claudeCodeCompatible.ts b/open-sse/services/claudeCodeCompatible.ts index 0294bf319913..2a41ec2b5345 100644 --- a/open-sse/services/claudeCodeCompatible.ts +++ b/open-sse/services/claudeCodeCompatible.ts @@ -1,6 +1,9 @@ import { createHash, randomUUID } from "node:crypto"; -import { prepareClaudeRequest } from "../translator/helpers/claudeHelper.ts"; +import { + applyClaudeOAuthLexicalRewrite, + prepareClaudeRequest, +} from "../translator/helpers/claudeHelper.ts"; export const CLAUDE_CODE_COMPATIBLE_PREFIX = "anthropic-compatible-cc-"; export const CLAUDE_CODE_COMPATIBLE_DEFAULT_CHAT_PATH = "/v1/messages?beta=true"; @@ -180,11 +183,13 @@ export function buildClaudeCodeCompatibleRequest({ const toolChoice = tools.length > 0 ? buildClaudeCodeCompatibleToolChoice( - normalizedBody?.["tool_choice"] ?? sourceBody?.["tool_choice"] + preparedClaudeBody?.tool_choice ?? + normalizedBody?.["tool_choice"] ?? + sourceBody?.["tool_choice"] ) : undefined; - return { + const requestBody = { model, messages, system, @@ -217,6 +222,13 @@ export function buildClaudeCodeCompatibleRequest({ ...(toolChoice ? { tool_choice: toolChoice } : {}), ...(stream ? { stream: true } : {}), }; + + const lexicalRewrite = applyClaudeOAuthLexicalRewrite(requestBody); + if (lexicalRewrite.toolNameMap) { + lexicalRewrite.body._toolNameMap = lexicalRewrite.toolNameMap; + } + + return lexicalRewrite.body; } export function resolveClaudeCodeCompatibleEffort( @@ -539,6 +551,7 @@ function prepareClaudeCodeCompatibleBody( system: normalizeClaudeSystemInput(claudeBody.system), messages: normalizeClaudeMessageInput(claudeBody.messages), tools: normalizeClaudeToolInput(claudeBody.tools), + tool_choice: readRecord(claudeBody.tool_choice) || claudeBody.tool_choice, thinking: readRecord(claudeBody.thinking) || claudeBody.thinking, }, CLAUDE_CODE_COMPATIBLE_PREFIX, diff --git a/tests/unit/claude-code-compatible-request.test.mjs b/tests/unit/claude-code-compatible-request.test.mjs index 88dd186bd9bb..335fbdb9c156 100644 --- a/tests/unit/claude-code-compatible-request.test.mjs +++ b/tests/unit/claude-code-compatible-request.test.mjs @@ -109,16 +109,29 @@ test("buildClaudeCodeCompatibleRequest covers normalized OpenAI-style messages, test("buildClaudeCodeCompatibleRequest covers Claude-native bodies and cache-control stripping", () => { const stripped = buildClaudeCodeCompatibleRequest({ claudeBody: { - system: [{ type: "text", text: "sys", cache_control: { type: "ephemeral" } }], + system: [ + { + type: "text", + text: "sys src/", + cache_control: { type: "ephemeral" }, + }, + ], messages: [ { role: "assistant", - content: [{ type: "text", text: "prefill", cache_control: { type: "ephemeral" } }], + content: [ + { type: "text", text: "prefill", cache_control: { type: "ephemeral" } }, + { type: "tool_use", id: "call_1", name: "background_output", input: {} }, + ], }, { role: "user", content: [ - { type: "text", text: "ask", cache_control: { type: "ephemeral" } }, + { + type: "text", + text: "ask background_cancel", + cache_control: { type: "ephemeral" }, + }, { type: "image", source: { type: "base64", media_type: "image/png", data: "abc" }, @@ -132,8 +145,14 @@ test("buildClaudeCodeCompatibleRequest covers Claude-native bodies and cache-con }, ], tools: [ - { name: "toolA", input_schema: { type: "object" }, cache_control: { type: "ephemeral" } }, + { + name: "background_cancel", + description: "Cancel background_output work", + input_schema: { type: "object" }, + cache_control: { type: "ephemeral" }, + }, ], + tool_choice: { type: "tool", name: "background_cancel" }, thinking: { type: "enabled", budget_tokens: 12 }, }, model: "claude-sonnet-4-6", @@ -171,8 +190,16 @@ test("buildClaudeCodeCompatibleRequest covers Claude-native bodies and cache-con assert.equal(JSON.parse(stripped.metadata.user_id).session_id, "explicit-session"); assert.equal(stripped.messages.at(-1).role, "user"); assert.equal(stripped.messages[0].content[0].cache_control, undefined); + assert.equal(stripped.messages[0].content[1].name, "background_result"); + assert.equal(stripped.messages[1].content[0].text, "ask background_stop"); assert.equal(stripped.system.at(-1).cache_control, undefined); + assert.equal(stripped.system.at(-1).text, "sys directories:\nsrc/"); assert.equal(stripped.tools[0].cache_control, undefined); + assert.equal(stripped.tools[0].name, "background_stop"); + assert.equal(stripped.tools[0].description, "Cancel background_result work"); + assert.deepEqual(stripped.tool_choice, { type: "tool", name: "background_stop" }); + assert.equal(stripped._toolNameMap.get("background_result"), "background_output"); + assert.equal(stripped._toolNameMap.get("background_stop"), "background_cancel"); assert.equal(preserved.messages[0].content[0].cache_control.type, "ephemeral"); assert.equal(preserved.system.at(-1).cache_control.type, "ephemeral"); assert.equal(preserved.tools[0].cache_control.type, "ephemeral");