/** * Normalize Responses API input to array format. * Accepts string or array, returns array of message items. * @param {string|Array} input - raw input from Responses API body * @returns {Array|null} normalized array or null if invalid */ export function normalizeResponsesInput(input) { if (typeof input === "string") { const text = input.trim() === "" ? "..." : input; return [{ type: "message", role: "user", content: [{ type: "input_text", text }] }]; } if (Array.isArray(input)) return input; return null; } /** * Convert OpenAI Responses API format to standard chat completions format * Responses API uses: { input: [...], instructions: "..." } * Chat API uses: { messages: [...] } */ export function convertResponsesApiFormat(body) { if (!body.input) return body; const result = { ...body }; result.messages = []; // Convert instructions to system message if (body.instructions) { result.messages.push({ role: "system", content: body.instructions }); } // Group items by conversation turn let currentAssistantMsg = null; let pendingToolCalls = []; let pendingToolResults = []; const inputItems = normalizeResponsesInput(body.input); if (!inputItems) return body; for (const item of inputItems) { // Determine item type - Droid CLI sends role-based items without 'type' field // Fallback: if no type but has role property, treat as message const itemType = item.type || (item.role ? "message" : null); if (itemType === "message") { // Flush any pending assistant message with tool calls if (currentAssistantMsg) { result.messages.push(currentAssistantMsg); currentAssistantMsg = null; } // Flush pending tool results if (pendingToolResults.length > 0) { for (const tr of pendingToolResults) { result.messages.push(tr); } pendingToolResults = []; } // Convert content: input_text → text, output_text → text const content = Array.isArray(item.content) ? item.content.map(c => { if (c.type === "input_text") return { type: "text", text: c.text }; if (c.type === "output_text") return { type: "text", text: c.text }; return c; }) : item.content; result.messages.push({ role: item.role, content }); } else if (itemType === "function_call") { // Start or append to assistant message with tool_calls if (!currentAssistantMsg) { currentAssistantMsg = { role: "assistant", content: null, tool_calls: [] }; } currentAssistantMsg.tool_calls.push({ id: item.call_id, type: "function", function: { name: item.name, arguments: item.arguments } }); } else if (itemType === "function_call_output") { // Flush assistant message first if exists if (currentAssistantMsg) { result.messages.push(currentAssistantMsg); currentAssistantMsg = null; } // Add tool result pendingToolResults.push({ role: "tool", tool_call_id: item.call_id, content: typeof item.output === "string" ? item.output : JSON.stringify(item.output) }); } else if (itemType === "reasoning") { // Skip reasoning items - they are for display only continue; } } // Flush remaining if (currentAssistantMsg) { result.messages.push(currentAssistantMsg); } if (pendingToolResults.length > 0) { for (const tr of pendingToolResults) { result.messages.push(tr); } } // Cleanup Responses API specific fields delete result.input; delete result.instructions; delete result.include; delete result.prompt_cache_key; delete result.store; delete result.reasoning; return result; }