import { register } from "../index.js"; import { FORMATS } from "../formats.js"; import { adjustMaxTokens } from "../helpers/maxTokensHelper.js"; // Convert Claude request to OpenAI format function claudeToOpenAI(model, body, stream) { const result = { model: model, messages: [], stream: stream }; // Max tokens if (body.max_tokens) { result.max_tokens = adjustMaxTokens(body); } // Temperature if (body.temperature !== undefined) { result.temperature = body.temperature; } // System message if (body.system) { const systemContent = Array.isArray(body.system) ? body.system.map(s => s.text || "").join("\n") : body.system; if (systemContent) { result.messages.push({ role: "system", content: systemContent }); } } // Convert messages if (body.messages && Array.isArray(body.messages)) { for (let i = 0; i < body.messages.length; i++) { const msg = body.messages[i]; const converted = convertClaudeMessage(msg); if (converted) { // Handle array of messages (multiple tool results) if (Array.isArray(converted)) { result.messages.push(...converted); } else { result.messages.push(converted); } } } } // Fix missing tool responses - OpenAI requires every tool_call to have a response fixMissingToolResponses(result.messages); // Tools if (body.tools && Array.isArray(body.tools)) { result.tools = body.tools.map(tool => ({ type: "function", function: { name: tool.name, description: tool.description, parameters: tool.input_schema || { type: "object", properties: {} } } })); } // Tool choice if (body.tool_choice) { result.tool_choice = convertToolChoice(body.tool_choice); } return result; } // Fix missing tool responses - add empty responses for tool_calls without responses function fixMissingToolResponses(messages) { for (let i = 0; i < messages.length; i++) { const msg = messages[i]; if (msg.role === "assistant" && msg.tool_calls && msg.tool_calls.length > 0) { const toolCallIds = msg.tool_calls.map(tc => tc.id); // Collect all tool response IDs that IMMEDIATELY follow this assistant message // Stop at any non-tool message (user or assistant) const respondedIds = new Set(); let insertPosition = i + 1; for (let j = i + 1; j < messages.length; j++) { const nextMsg = messages[j]; if (nextMsg.role === "tool" && nextMsg.tool_call_id) { respondedIds.add(nextMsg.tool_call_id); insertPosition = j + 1; } else { // Stop at any non-tool message (user or assistant) break; } } // Find missing responses and insert them const missingIds = toolCallIds.filter(id => !respondedIds.has(id)); if (missingIds.length > 0) { const missingResponses = missingIds.map(id => ({ role: "tool", tool_call_id: id, content: "[No response received]" })); // Insert missing responses at the correct position messages.splice(insertPosition, 0, ...missingResponses); // Adjust index to skip inserted messages i = insertPosition + missingResponses.length - 1; } } } } // Convert single Claude message - returns single message or array of messages function convertClaudeMessage(msg) { const role = msg.role === "user" || msg.role === "tool" ? "user" : "assistant"; // Simple string content if (typeof msg.content === "string") { return { role, content: msg.content }; } // Array content if (Array.isArray(msg.content)) { const parts = []; const toolCalls = []; const toolResults = []; for (const block of msg.content) { switch (block.type) { case "text": parts.push({ type: "text", text: block.text }); break; case "image": if (block.source?.type === "base64") { parts.push({ type: "image_url", image_url: { url: `data:${block.source.media_type};base64,${block.source.data}` } }); } break; case "tool_use": toolCalls.push({ id: block.id, type: "function", function: { name: block.name, arguments: JSON.stringify(block.input || {}) } }); break; case "tool_result": // Extract actual content from tool_result let resultContent = ""; if (typeof block.content === "string") { resultContent = block.content; } else if (Array.isArray(block.content)) { // Claude tool_result content can be array of text blocks resultContent = block.content .filter(c => c.type === "text") .map(c => c.text) .join("\n") || JSON.stringify(block.content); } else if (block.content) { resultContent = JSON.stringify(block.content); } toolResults.push({ role: "tool", tool_call_id: block.tool_use_id, content: resultContent }); break; } } // If has tool results, return array of tool messages if (toolResults.length > 0) { // Also include text parts as user message if any if (parts.length > 0) { const textContent = parts.length === 1 && parts[0].type === "text" ? parts[0].text : parts; return [...toolResults, { role: "user", content: textContent }]; } return toolResults; } // If has tool calls, return assistant message with tool_calls if (toolCalls.length > 0) { const result = { role: "assistant" }; if (parts.length > 0) { result.content = parts.length === 1 && parts[0].type === "text" ? parts[0].text : parts; } result.tool_calls = toolCalls; return result; } // Return content if (parts.length > 0) { return { role, content: parts.length === 1 && parts[0].type === "text" ? parts[0].text : parts }; } // Empty content array - return empty string content to keep message in conversation if (msg.content.length === 0) { return { role, content: "" }; } } return null; } // Convert tool choice function convertToolChoice(choice) { if (!choice) return "auto"; if (typeof choice === "string") return choice; switch (choice.type) { case "auto": return "auto"; case "any": return "required"; case "tool": return { type: "function", function: { name: choice.name } }; default: return "auto"; } } // Register register(FORMATS.CLAUDE, FORMATS.OPENAI, claudeToOpenAI, null);