mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-15 21:05:51 +00:00
- 搬入 OpenAI 消息转换(convertMessages)、工具转换(convertTools)、流适配(streamAdapter) - 搬入 OpenAI 和 Grok 模型映射(resolveOpenAIModel、resolveGrokModel) - 主项目文件改为 thin re-export proxy Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
124 lines
4.1 KiB
TypeScript
124 lines
4.1 KiB
TypeScript
import type { BetaToolUnion } from '@anthropic-ai/sdk/resources/beta/messages/messages.mjs'
|
|
import type { ChatCompletionTool } from 'openai/resources/chat/completions/completions.mjs'
|
|
|
|
/**
|
|
* Convert Anthropic tool schemas to OpenAI function calling format.
|
|
*
|
|
* Anthropic: { name, description, input_schema }
|
|
* OpenAI: { type: "function", function: { name, description, parameters } }
|
|
*
|
|
* Anthropic-specific fields (cache_control, defer_loading, etc.) are stripped.
|
|
*/
|
|
export function anthropicToolsToOpenAI(
|
|
tools: BetaToolUnion[],
|
|
): ChatCompletionTool[] {
|
|
return tools
|
|
.filter(tool => {
|
|
// Only convert standard tools (skip server tools like computer_use, etc.)
|
|
const toolType = (tool as unknown as { type?: string }).type
|
|
return tool.type === 'custom' || !('type' in tool) || toolType !== 'server'
|
|
})
|
|
.map(tool => {
|
|
// Handle the various tool shapes from Anthropic SDK
|
|
const anyTool = tool as unknown as Record<string, unknown>
|
|
const name = (anyTool.name as string) || ''
|
|
const description = (anyTool.description as string) || ''
|
|
const inputSchema = anyTool.input_schema as Record<string, unknown> | undefined
|
|
|
|
return {
|
|
type: 'function' as const,
|
|
function: {
|
|
name,
|
|
description,
|
|
parameters: sanitizeJsonSchema(inputSchema || { type: 'object', properties: {} }),
|
|
},
|
|
} satisfies ChatCompletionTool
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Recursively sanitize a JSON Schema for OpenAI-compatible providers.
|
|
*
|
|
* Many OpenAI-compatible endpoints (Ollama, DeepSeek, vLLM, etc.) do not
|
|
* support the `const` keyword in JSON Schema. Convert it to `enum` with a
|
|
* single-element array, which is semantically equivalent.
|
|
*/
|
|
function sanitizeJsonSchema(schema: Record<string, unknown>): Record<string, unknown> {
|
|
if (!schema || typeof schema !== 'object') return schema
|
|
|
|
const result = { ...schema }
|
|
|
|
// Convert `const` → `enum: [value]`
|
|
if ('const' in result) {
|
|
result.enum = [result.const]
|
|
delete result.const
|
|
}
|
|
|
|
// Recursively process nested schemas
|
|
const objectKeys = ['properties', 'definitions', '$defs', 'patternProperties'] as const
|
|
for (const key of objectKeys) {
|
|
const nested = result[key]
|
|
if (nested && typeof nested === 'object') {
|
|
const sanitized: Record<string, unknown> = {}
|
|
for (const [k, v] of Object.entries(nested as Record<string, unknown>)) {
|
|
sanitized[k] = v && typeof v === 'object' ? sanitizeJsonSchema(v as Record<string, unknown>) : v
|
|
}
|
|
result[key] = sanitized
|
|
}
|
|
}
|
|
|
|
// Recursively process single-schema keys
|
|
const singleKeys = ['items', 'additionalProperties', 'not', 'if', 'then', 'else', 'contains', 'propertyNames'] as const
|
|
for (const key of singleKeys) {
|
|
const nested = result[key]
|
|
if (nested && typeof nested === 'object' && !Array.isArray(nested)) {
|
|
result[key] = sanitizeJsonSchema(nested as Record<string, unknown>)
|
|
}
|
|
}
|
|
|
|
// Recursively process array-of-schemas keys
|
|
const arrayKeys = ['anyOf', 'oneOf', 'allOf'] as const
|
|
for (const key of arrayKeys) {
|
|
const nested = result[key]
|
|
if (Array.isArray(nested)) {
|
|
result[key] = nested.map(item =>
|
|
item && typeof item === 'object' ? sanitizeJsonSchema(item as Record<string, unknown>) : item
|
|
)
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
/**
|
|
* Map Anthropic tool_choice to OpenAI tool_choice format.
|
|
*
|
|
* Anthropic → OpenAI:
|
|
* - { type: "auto" } → "auto"
|
|
* - { type: "any" } → "required"
|
|
* - { type: "tool", name } → { type: "function", function: { name } }
|
|
* - undefined → undefined (use provider default)
|
|
*/
|
|
export function anthropicToolChoiceToOpenAI(
|
|
toolChoice: unknown,
|
|
): string | { type: 'function'; function: { name: string } } | undefined {
|
|
if (!toolChoice || typeof toolChoice !== 'object') return undefined
|
|
|
|
const tc = toolChoice as Record<string, unknown>
|
|
const type = tc.type as string
|
|
|
|
switch (type) {
|
|
case 'auto':
|
|
return 'auto'
|
|
case 'any':
|
|
return 'required'
|
|
case 'tool':
|
|
return {
|
|
type: 'function',
|
|
function: { name: tc.name as string },
|
|
}
|
|
default:
|
|
return undefined
|
|
}
|
|
}
|