Download src/server/proxy/transform/anthropicToOpenaiChat.ts from chenbhao/codev: direct link, hf CLI and curl.
- Browser
- Download file 6.06 kB
-
https://huggingface.co/chenbhao/codev/resolve/main/src/server/proxy/transform/anthropicToOpenaiChat.ts
- Command line
-
hf download hf://chenbhao/codev/src/server/proxy/transform/anthropicToOpenaiChat.ts
-
curl -L -o anthropicToOpenaiChat.ts https://huggingface.co/chenbhao/codev/resolve/main/src/server/proxy/transform/anthropicToOpenaiChat.ts
6.06 kB
| /** | |
| * Request transformation: Anthropic Messages → OpenAI Chat Completions | |
| * Derived from cc-switch (https://github.com/farion1231/cc-switch) | |
| * Original work by Jason Young, MIT License | |
| */ | |
| import type { | |
| AnthropicRequest, | |
| AnthropicContentBlock, | |
| AnthropicMessage, | |
| OpenAIChatRequest, | |
| OpenAIChatMessage, | |
| OpenAIChatContentPart, | |
| OpenAIToolCall, | |
| OpenAITool, | |
| } from './types.js' | |
| /** | |
| * Convert Anthropic Messages request to OpenAI Chat Completions request. | |
| */ | |
| export function anthropicToOpenaiChat( | |
| body: AnthropicRequest, | |
| options: { roundTripReasoningContent?: boolean; passThinkingToggle?: boolean } = {}, | |
| ): OpenAIChatRequest { | |
| const messages: OpenAIChatMessage[] = [] | |
| // Convert system prompt | |
| if (body.system) { | |
| if (typeof body.system === 'string') { | |
| messages.push({ role: 'system', content: body.system }) | |
| } else if (Array.isArray(body.system)) { | |
| const text = body.system.map((b) => b.text).join('\n') | |
| messages.push({ role: 'system', content: text }) | |
| } | |
| } | |
| // Convert messages | |
| for (const msg of body.messages) { | |
| convertMessage(msg, messages, options) | |
| } | |
| // Build request | |
| const result: OpenAIChatRequest = { | |
| model: body.model, | |
| messages, | |
| stream: body.stream, | |
| } | |
| // max_tokens — omit to let upstream provider use its own default/max. | |
| // Claude Code sends very large values (e.g. 128K) that exceed many | |
| // providers' limits (DeepSeek: 8192, etc.). | |
| // temperature & top_p | |
| if (body.temperature !== undefined) result.temperature = body.temperature | |
| if (body.top_p !== undefined) result.top_p = body.top_p | |
| // stop_sequences → stop | |
| if (body.stop_sequences && body.stop_sequences.length > 0) { | |
| result.stop = body.stop_sequences | |
| } | |
| // tools | |
| if (body.tools && body.tools.length > 0) { | |
| result.tools = body.tools | |
| .filter((t) => t.name !== 'BatchTool') | |
| .map((t): OpenAITool => ({ | |
| type: 'function', | |
| function: { | |
| name: t.name, | |
| description: t.description, | |
| parameters: t.input_schema, | |
| }, | |
| })) | |
| } | |
| // tool_choice | |
| if (body.tool_choice !== undefined) { | |
| result.tool_choice = convertToolChoice(body.tool_choice) | |
| } | |
| // thinking → reasoning_effort | |
| if (body.thinking) { | |
| const budget = body.thinking.budget_tokens | |
| if (budget !== undefined) { | |
| if (budget <= 1024) result.reasoning_effort = 'low' | |
| else if (budget <= 8192) result.reasoning_effort = 'medium' | |
| else result.reasoning_effort = 'high' | |
| } else if (body.thinking.type === 'enabled') { | |
| result.reasoning_effort = 'high' | |
| } | |
| if (options.passThinkingToggle) { | |
| result.thinking = { type: body.thinking.type } | |
| } | |
| } | |
| return result | |
| } | |
| function convertMessage( | |
| msg: AnthropicMessage, | |
| output: OpenAIChatMessage[], | |
| options: { roundTripReasoningContent?: boolean }, | |
| ): void { | |
| const content = msg.content | |
| // Simple string content | |
| if (typeof content === 'string') { | |
| output.push({ role: msg.role, content }) | |
| return | |
| } | |
| // Array content blocks | |
| if (!Array.isArray(content) || content.length === 0) { | |
| output.push({ role: msg.role, content: '' }) | |
| return | |
| } | |
| if (msg.role === 'user') { | |
| convertUserMessage(content, output) | |
| } else { | |
| convertAssistantMessage(content, output, options) | |
| } | |
| } | |
| function convertUserMessage(blocks: AnthropicContentBlock[], output: OpenAIChatMessage[]): void { | |
| // Separate tool_result blocks from other content | |
| const contentParts: OpenAIChatContentPart[] = [] | |
| for (const block of blocks) { | |
| if (block.type === 'text') { | |
| contentParts.push({ type: 'text', text: block.text }) | |
| } else if (block.type === 'image') { | |
| const url = `data:${block.source.media_type};base64,${block.source.data}` | |
| contentParts.push({ type: 'image_url', image_url: { url } }) | |
| } else if (block.type === 'tool_result') { | |
| // tool_result → separate tool message | |
| const resultContent = typeof block.content === 'string' | |
| ? block.content | |
| : Array.isArray(block.content) | |
| ? block.content.filter((b): b is Extract<AnthropicContentBlock, { type: 'text' }> => b.type === 'text').map((b) => b.text).join('\n') | |
| : '' | |
| output.push({ | |
| role: 'tool', | |
| tool_call_id: block.tool_use_id, | |
| content: resultContent, | |
| }) | |
| } | |
| } | |
| if (contentParts.length > 0) { | |
| output.push({ | |
| role: 'user', | |
| content: contentParts.length === 1 && contentParts[0].type === 'text' | |
| ? contentParts[0].text | |
| : contentParts, | |
| }) | |
| } | |
| } | |
| function convertAssistantMessage( | |
| blocks: AnthropicContentBlock[], | |
| output: OpenAIChatMessage[], | |
| options: { roundTripReasoningContent?: boolean }, | |
| ): void { | |
| let textContent = '' | |
| let reasoningContent = '' | |
| const toolCalls: OpenAIToolCall[] = [] | |
| for (const block of blocks) { | |
| if (block.type === 'text') { | |
| textContent += block.text | |
| } else if (block.type === 'thinking' && options.roundTripReasoningContent) { | |
| reasoningContent += block.thinking | |
| } else if (block.type === 'tool_use') { | |
| toolCalls.push({ | |
| id: block.id, | |
| type: 'function', | |
| function: { | |
| name: block.name, | |
| arguments: typeof block.input === 'string' ? block.input : JSON.stringify(block.input), | |
| }, | |
| }) | |
| } | |
| } | |
| const msg: OpenAIChatMessage = { | |
| role: 'assistant', | |
| content: textContent || null, | |
| } | |
| if (toolCalls.length > 0) { | |
| msg.tool_calls = toolCalls | |
| } | |
| if (reasoningContent) { | |
| msg.reasoning_content = reasoningContent | |
| } | |
| output.push(msg) | |
| } | |
| function convertToolChoice(choice: unknown): unknown { | |
| if (typeof choice === 'string') return choice | |
| if (typeof choice === 'object' && choice !== null) { | |
| const c = choice as Record<string, unknown> | |
| if (c.type === 'auto') return 'auto' | |
| if (c.type === 'any') return 'required' | |
| if (c.type === 'none') return 'none' | |
| if (c.type === 'tool' && typeof c.name === 'string') { | |
| return { type: 'function', function: { name: c.name } } | |
| } | |
| } | |
| return 'auto' | |
| } | |