diff --git a/src/converters/strategies/OpenAIConverter.js b/src/converters/strategies/OpenAIConverter.js index c337896..9bdd145 100644 --- a/src/converters/strategies/OpenAIConverter.js +++ b/src/converters/strategies/OpenAIConverter.js @@ -161,7 +161,7 @@ export class OpenAIConverter extends BaseConverter { } content.push({ type: 'tool_result', - tool_use_id: message.tool_call_id, + tool_use_id: message.tool_call_id || message.tool_use_id, content: toolContent }); claudeMessages.push({ role: 'user', content: content }); @@ -219,6 +219,33 @@ export class OpenAIConverter extends BaseConverter { content.push({ type: 'text', text: `[Audio: ${audioUrl}]` }); } break; + case 'input_audio': + // OpenAI 官方 input_audio 格式 + if (item.input_audio) { + // Claude 不直接支持音频输入,转换为文本描述 + content.push({ type: 'text', text: `[Audio Input: ${item.input_audio.format || 'audio'}]` }); + } + break; + case 'tool_use': + content.push({ + type: 'tool_use', + id: item.id, + name: item.name, + input: typeof item.input === 'string' ? safeParseJSON(item.input) : (item.input || {}) + }); + break; + case 'tool_result': { + let resultContent = item.content; + if (typeof resultContent === 'object' && resultContent !== null) { + resultContent = JSON.stringify(resultContent); + } + content.push({ + type: 'tool_result', + tool_use_id: item.tool_use_id || item.id, + content: resultContent + }); + break; + } } }); } @@ -276,12 +303,25 @@ export class OpenAIConverter extends BaseConverter { } if (openaiRequest.tools?.length) { - claudeRequest.tools = openaiRequest.tools.map(t => ({ - name: t.function.name, - description: t.function.description || '', - input_schema: t.function.parameters || { type: 'object', properties: {} } - })); - claudeRequest.tool_choice = this.buildClaudeToolChoice(openaiRequest.tool_choice); + claudeRequest.tools = openaiRequest.tools + .filter(t => t && ((t.function && t.function.name) || t.name)) + .map(t => { + if (t.function) { + return { + name: t.function.name, + description: t.function.description || '', + input_schema: t.function.parameters || { type: 'object', properties: {} } + }; + } + return { + name: t.name, + description: t.description || '', + input_schema: t.input_schema || { type: 'object', properties: {} } + }; + }); + if (claudeRequest.tools.length > 0) { + claudeRequest.tool_choice = this.buildClaudeToolChoice(openaiRequest.tool_choice); + } } // Optional passthrough: request-side "thinking" controls for Claude/Kiro. @@ -596,8 +636,15 @@ export class OpenAIConverter extends BaseConverter { const mapping = { auto: 'auto', none: 'none', required: 'any' }; return { type: mapping[toolChoice] }; } - if (typeof toolChoice === 'object' && toolChoice.function) { - return { type: 'tool', name: toolChoice.function.name }; + if (typeof toolChoice === 'object') { + // Claude 原生格式:{ type, name } + if (toolChoice.type && toolChoice.name) { + return { type: toolChoice.type, name: toolChoice.name }; + } + // OpenAI 格式:{ function: { name } } + if (toolChoice.function) { + return { type: 'tool', name: toolChoice.function.name }; + } } return undefined; } @@ -625,6 +672,14 @@ export class OpenAIConverter extends BaseConverter { } } } + // Claude 格式:content 数组中的 tool_use + if (message.role === 'assistant' && Array.isArray(message.content)) { + for (const item of message.content) { + if (item && item.type === 'tool_use' && item.id && item.name) { + tcID2Name[item.id] = item.name; + } + } + } } // 构建 tool_call_id -> response 映射 @@ -633,6 +688,14 @@ export class OpenAIConverter extends BaseConverter { if (message.role === 'tool' && message.tool_call_id) { toolResponses[message.tool_call_id] = message.content; } + // Claude 格式:user content 数组中的 tool_result + if (message.role === 'user' && Array.isArray(message.content)) { + for (const item of message.content) { + if (item && item.type === 'tool_result' && item.tool_use_id) { + toolResponses[item.tool_use_id] = item.content; + } + } + } } const processedMessages = []; @@ -643,7 +706,7 @@ export class OpenAIConverter extends BaseConverter { const role = message.role; const content = message.content; - if (role === 'system') { + if (role === 'system' || role === 'developer') { // system -> system_instruction if (messages.length > 1) { if (typeof content === 'string') { @@ -790,6 +853,7 @@ export class OpenAIConverter extends BaseConverter { const node = { role: 'model', parts: [] }; // 处理文本内容 + const functionCallIds = []; if (typeof content === 'string' && content) { node.parts.push({ text: content }); } else if (Array.isArray(content)) { @@ -797,6 +861,19 @@ export class OpenAIConverter extends BaseConverter { if (!item) continue; if (item.type === 'text' && item.text) { node.parts.push({ text: item.text }); + } else if (item.type === 'tool_use') { + // Claude 格式 tool_use -> Gemini functionCall + const fid = item.id || ''; + const fname = item.name || ''; + const argsObj = typeof item.input === 'string' ? (() => { try { return JSON.parse(item.input); } catch(e) { return {}; } })() : (item.input || {}); + node.parts.push({ + functionCall: { + name: fname, + args: argsObj + }, + thoughtSignature: OpenAIConverter.GEMINI_OPENAI_THOUGHT_SIGNATURE + }); + if (fid) functionCallIds.push(fid); } else if (item.type === 'image_url' && item.image_url) { const imageUrl = typeof item.image_url === 'string' ? item.image_url @@ -823,22 +900,21 @@ export class OpenAIConverter extends BaseConverter { } } - // 处理 tool_calls -> functionCall + // 处理 OpenAI 格式 tool_calls -> functionCall if (message.tool_calls && Array.isArray(message.tool_calls)) { - const functionCallIds = []; for (const tc of message.tool_calls) { if (tc.type !== 'function') continue; const fid = tc.id || ''; const fname = tc.function?.name || ''; const fargs = tc.function?.arguments || '{}'; - + let argsObj; try { argsObj = typeof fargs === 'string' ? JSON.parse(fargs) : fargs; } catch (e) { argsObj = {}; } - + node.parts.push({ functionCall: { name: fname, @@ -846,50 +922,72 @@ export class OpenAIConverter extends BaseConverter { }, thoughtSignature: OpenAIConverter.GEMINI_OPENAI_THOUGHT_SIGNATURE }); - + if (fid) { functionCallIds.push(fid); } } - - // 添加 model 消息 - if (node.parts.length > 0) { - processedMessages.push(node); + } + + // 添加 model 消息 + if (node.parts.length > 0) { + processedMessages.push(node); + } + + // 添加对应的 functionResponse(作为 user 消息) + if (functionCallIds.length > 0) { + const toolNode = { role: 'user', parts: [] }; + for (const fid of functionCallIds) { + const name = tcID2Name[fid]; + if (name) { + let resp = toolResponses[fid] || '{}'; + if (typeof resp !== 'string') { + resp = JSON.stringify(resp); + } + toolNode.parts.push({ + functionResponse: { + name: name, + response: { + result: resp + } + } + }); + } + } + if (toolNode.parts.length > 0) { + processedMessages.push(toolNode); + } + } + } else if (role === 'tool') { + // 处理独立的 tool role 消息(OpenAI 格式) + // 转换为 Gemini 的 functionResponse 格式 + const toolNode = { role: 'user', parts: [] }; + + // 从 tool_call_id 查找对应的函数名 + const toolCallId = message.tool_call_id; + const functionName = tcID2Name[toolCallId]; + + if (functionName) { + let responseContent = message.content; + if (typeof responseContent !== 'string') { + responseContent = JSON.stringify(responseContent); } - // 添加对应的 functionResponse(作为 user 消息) - if (functionCallIds.length > 0) { - const toolNode = { role: 'user', parts: [] }; - for (const fid of functionCallIds) { - const name = tcID2Name[fid]; - if (name) { - let resp = toolResponses[fid] || '{}'; - // 确保 resp 是字符串 - if (typeof resp !== 'string') { - resp = JSON.stringify(resp); - } - toolNode.parts.push({ - functionResponse: { - name: name, - response: { - result: resp - } - } - }); + toolNode.parts.push({ + functionResponse: { + name: functionName, + response: { + result: responseContent } } - if (toolNode.parts.length > 0) { - processedMessages.push(toolNode); - } - } - } else { - // 没有 tool_calls,直接添加 - if (node.parts.length > 0) { - processedMessages.push(node); + }); + + if (toolNode.parts.length > 0) { + processedMessages.push(toolNode); } } } - // tool 消息已经在 assistant 的 tool_calls 处理中合并了,这里跳过 + // 其他 role 类型跳过 } // 构建 Gemini 请求 @@ -1013,7 +1111,7 @@ export class OpenAIConverter extends BaseConverter { name: String(func.name || ''), description: String(func.description || '') }; - + // 处理 parameters -> parametersJsonSchema if (func.parameters) { fnDecl.parametersJsonSchema = cleanJsonSchema(func.parameters); @@ -1023,8 +1121,14 @@ export class OpenAIConverter extends BaseConverter { properties: {} }; } - + functionDeclarations.push(fnDecl); + } else if (t.name) { + functionDeclarations.push({ + name: String(t.name), + description: String(t.description || ''), + parametersJsonSchema: cleanJsonSchema(t.input_schema || { type: 'object', properties: {} }) + }); } // 处理 google_search 工具 @@ -1334,6 +1438,8 @@ export class OpenAIConverter extends BaseConverter { } }] }; + + return result; } // =========================================================================