- Remove forced stream=true from responsesHandler - Add stream-to-JSON converter for non-streaming clients (Codex) - Accept string input in Responses API (normalize to array) - Codex SSE header fallback for missing Content-Type - Refactor: extract shared normalizeResponsesInput() Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com>
125 lines
3.8 KiB
JavaScript
125 lines
3.8 KiB
JavaScript
/**
|
|
* 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;
|
|
}
|