refactor(open-sse): translator DRY + schema enums, bug fixes, dead code cleanup
- Bug B1-B7: media UI m.kind||m.type, serviceKinds, gemini mediaPriority, schema kind, models/info lookup by kind - Dead code D1-D6: safeParseJSON, drop PROVIDER_ENDPOINTS, orphan fetcher, GITHUB_CONFIG derive, getProviderConfig internal, legacy kiro file - Translator concerns: toOpenAIUsage, toOpenAIFinish (gemini/kiro/ollama + fix kiro tool finish), thinking effort maps - Reorg helpers/ → concerns/ (logic) + formats/ (per-format) + schema/ (pure enums: roles/blocks/finishReasons/defaults) - Wire ~280 hardcoded role/block/finish/default literals to schema enums across 20+ files - collapseTextParts + extractTextContent dedup - Normalize translator fn names to openaiToXRequest / xToOpenAIResponse - Golden tests lock behavior; 0 regression (byte-for-byte providers/alias, 26=26 known fails) Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -6,7 +6,8 @@
|
||||
*/
|
||||
import { register } from "../index.js";
|
||||
import { FORMATS } from "../formats.js";
|
||||
import { normalizeResponsesInput } from "../helpers/responsesApiHelper.js";
|
||||
import { normalizeResponsesInput } from "../formats/responsesApi.js";
|
||||
import { ROLE, OPENAI_BLOCK, RESPONSES_ITEM } from "../schema/index.js";
|
||||
|
||||
// Responses API enforces max 64 chars on call_id (#393)
|
||||
const MAX_CALL_ID_LEN = 64;
|
||||
@@ -23,7 +24,7 @@ export function openaiResponsesToOpenAIRequest(model, body, stream, credentials)
|
||||
|
||||
// Convert instructions to system message
|
||||
if (body.instructions) {
|
||||
result.messages.push({ role: "system", content: body.instructions });
|
||||
result.messages.push({ role: ROLE.SYSTEM, content: body.instructions });
|
||||
}
|
||||
|
||||
// Group items by conversation turn
|
||||
@@ -50,9 +51,9 @@ export function openaiResponsesToOpenAIRequest(model, body, stream, credentials)
|
||||
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);
|
||||
const itemType = item.type || (item.role ? RESPONSES_ITEM.MESSAGE : null);
|
||||
|
||||
if (itemType === "message") {
|
||||
if (itemType === RESPONSES_ITEM.MESSAGE) {
|
||||
// Flush any pending assistant message with tool calls
|
||||
if (currentAssistantMsg) {
|
||||
result.messages.push(currentAssistantMsg);
|
||||
@@ -69,28 +70,28 @@ export function openaiResponsesToOpenAIRequest(model, body, stream, credentials)
|
||||
// Convert content: input_text → text, output_text → text, input_image → image_url
|
||||
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 };
|
||||
if (c.type === "input_image") {
|
||||
if (c.type === RESPONSES_ITEM.INPUT_TEXT) return { type: OPENAI_BLOCK.TEXT, text: c.text };
|
||||
if (c.type === RESPONSES_ITEM.OUTPUT_TEXT) return { type: OPENAI_BLOCK.TEXT, text: c.text };
|
||||
if (c.type === RESPONSES_ITEM.INPUT_IMAGE) {
|
||||
const url = c.image_url || c.file_id || "";
|
||||
return { type: "image_url", image_url: { url, detail: c.detail || "auto" } };
|
||||
return { type: OPENAI_BLOCK.IMAGE_URL, image_url: { url, detail: c.detail || "auto" } };
|
||||
}
|
||||
return c;
|
||||
})
|
||||
: item.content;
|
||||
const msg = { role: item.role, content };
|
||||
// Attach buffered reasoning to assistant turn (required by xiaomi-mimo thinking mode)
|
||||
if (item.role === "assistant" && pendingReasoning) {
|
||||
if (item.role === ROLE.ASSISTANT && pendingReasoning) {
|
||||
msg.reasoning_content = pendingReasoning;
|
||||
}
|
||||
pendingReasoning = "";
|
||||
result.messages.push(msg);
|
||||
}
|
||||
else if (itemType === "function_call") {
|
||||
else if (itemType === RESPONSES_ITEM.FUNCTION_CALL) {
|
||||
// Start or append to assistant message with tool_calls
|
||||
if (!currentAssistantMsg) {
|
||||
currentAssistantMsg = {
|
||||
role: "assistant",
|
||||
role: ROLE.ASSISTANT,
|
||||
content: null,
|
||||
tool_calls: []
|
||||
};
|
||||
@@ -103,14 +104,14 @@ export function openaiResponsesToOpenAIRequest(model, body, stream, credentials)
|
||||
if (!item.name || typeof item.name !== "string" || item.name.trim() === "") continue;
|
||||
currentAssistantMsg.tool_calls.push({
|
||||
id: item.call_id,
|
||||
type: "function",
|
||||
type: OPENAI_BLOCK.FUNCTION,
|
||||
function: {
|
||||
name: item.name,
|
||||
arguments: item.arguments
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (itemType === "function_call_output") {
|
||||
else if (itemType === RESPONSES_ITEM.FUNCTION_CALL_OUTPUT) {
|
||||
// Flush assistant message first if exists
|
||||
if (currentAssistantMsg) {
|
||||
result.messages.push(currentAssistantMsg);
|
||||
@@ -125,12 +126,12 @@ export function openaiResponsesToOpenAIRequest(model, body, stream, credentials)
|
||||
}
|
||||
// Add tool result immediately
|
||||
result.messages.push({
|
||||
role: "tool",
|
||||
role: ROLE.TOOL,
|
||||
tool_call_id: item.call_id,
|
||||
content: typeof item.output === "string" ? item.output : JSON.stringify(item.output)
|
||||
});
|
||||
}
|
||||
else if (itemType === "reasoning") {
|
||||
else if (itemType === RESPONSES_ITEM.REASONING) {
|
||||
// Buffer reasoning text; attached to next assistant message/function_call
|
||||
const txt = extractReasoningText(item);
|
||||
if (txt) pendingReasoning = pendingReasoning ? `${pendingReasoning}\n${txt}` : txt;
|
||||
@@ -163,7 +164,7 @@ export function openaiResponsesToOpenAIRequest(model, body, stream, credentials)
|
||||
const name = tool.name;
|
||||
if (!name || typeof name !== "string" || name.trim() === "") return null;
|
||||
return {
|
||||
type: "function",
|
||||
type: OPENAI_BLOCK.FUNCTION,
|
||||
function: {
|
||||
name,
|
||||
description: String(tool.description || ""),
|
||||
@@ -214,7 +215,7 @@ export function openaiToOpenAIResponsesRequest(model, body, stream, credentials)
|
||||
const messages = body.messages || [];
|
||||
|
||||
for (const msg of messages) {
|
||||
if (msg.role === "system") {
|
||||
if (msg.role === ROLE.SYSTEM) {
|
||||
// Use first system message as instructions
|
||||
if (!hasSystemMessage) {
|
||||
result.instructions = typeof msg.content === "string" ? msg.content : "";
|
||||
@@ -224,21 +225,21 @@ export function openaiToOpenAIResponsesRequest(model, body, stream, credentials)
|
||||
}
|
||||
|
||||
// Convert user/assistant messages to input items
|
||||
if (msg.role === "user" || msg.role === "assistant") {
|
||||
const contentType = msg.role === "user" ? "input_text" : "output_text";
|
||||
if (msg.role === ROLE.USER || msg.role === ROLE.ASSISTANT) {
|
||||
const contentType = msg.role === ROLE.USER ? RESPONSES_ITEM.INPUT_TEXT : RESPONSES_ITEM.OUTPUT_TEXT;
|
||||
const content = typeof msg.content === "string"
|
||||
? [{ type: contentType, text: msg.content }]
|
||||
: Array.isArray(msg.content)
|
||||
? msg.content.map(c => {
|
||||
if (c.type === "text") return { type: contentType, text: c.text };
|
||||
if (c.type === OPENAI_BLOCK.TEXT) return { type: contentType, text: c.text };
|
||||
// Convert Chat Completions image_url → Responses API input_image
|
||||
// Responses API expects: { type: "input_image", image_url: "<url string>" }
|
||||
// Chat Completions sends: { type: "image_url", image_url: { url: "...", detail: "..." } }
|
||||
if (c.type === "image_url") {
|
||||
if (c.type === OPENAI_BLOCK.IMAGE_URL) {
|
||||
const url = typeof c.image_url === "string" ? c.image_url : c.image_url?.url;
|
||||
return { type: "input_image", image_url: url, detail: c.image_url?.detail || "auto" };
|
||||
return { type: RESPONSES_ITEM.INPUT_IMAGE, image_url: url, detail: c.image_url?.detail || "auto" };
|
||||
}
|
||||
if (c.type === "input_image") return c;
|
||||
if (c.type === RESPONSES_ITEM.INPUT_IMAGE) return c;
|
||||
// Serialize any unknown type (tool_use, tool_result, thinking, etc.) as text
|
||||
const text = c.text || c.content || JSON.stringify(c);
|
||||
return { type: contentType, text: typeof text === "string" ? text : JSON.stringify(text) };
|
||||
@@ -250,7 +251,7 @@ export function openaiToOpenAIResponsesRequest(model, body, stream, credentials)
|
||||
// message block in that case; the tool_calls are pushed separately below.
|
||||
if (content.length > 0) {
|
||||
result.input.push({
|
||||
type: "message",
|
||||
type: RESPONSES_ITEM.MESSAGE,
|
||||
role: msg.role,
|
||||
content
|
||||
});
|
||||
@@ -258,10 +259,10 @@ export function openaiToOpenAIResponsesRequest(model, body, stream, credentials)
|
||||
}
|
||||
|
||||
// Convert tool calls
|
||||
if (msg.role === "assistant" && msg.tool_calls) {
|
||||
if (msg.role === ROLE.ASSISTANT && msg.tool_calls) {
|
||||
for (const tc of msg.tool_calls) {
|
||||
result.input.push({
|
||||
type: "function_call",
|
||||
type: RESPONSES_ITEM.FUNCTION_CALL,
|
||||
call_id: clampCallId(tc.id),
|
||||
name: tc.function?.name || "_unknown",
|
||||
arguments: tc.function?.arguments || "{}"
|
||||
@@ -270,14 +271,14 @@ export function openaiToOpenAIResponsesRequest(model, body, stream, credentials)
|
||||
}
|
||||
|
||||
// Convert tool results - output must be a string for Responses API
|
||||
if (msg.role === "tool") {
|
||||
if (msg.role === ROLE.TOOL) {
|
||||
const output = typeof msg.content === "string"
|
||||
? msg.content
|
||||
: Array.isArray(msg.content)
|
||||
? msg.content.map(c => c.text || JSON.stringify(c)).join("")
|
||||
: JSON.stringify(msg.content);
|
||||
result.input.push({
|
||||
type: "function_call_output",
|
||||
type: RESPONSES_ITEM.FUNCTION_CALL_OUTPUT,
|
||||
call_id: clampCallId(msg.tool_call_id),
|
||||
output
|
||||
});
|
||||
@@ -292,9 +293,9 @@ export function openaiToOpenAIResponsesRequest(model, body, stream, credentials)
|
||||
// Convert tools format
|
||||
if (body.tools && Array.isArray(body.tools)) {
|
||||
result.tools = body.tools.map(tool => {
|
||||
if (tool.type === "function") {
|
||||
if (tool.type === OPENAI_BLOCK.FUNCTION) {
|
||||
return {
|
||||
type: "function",
|
||||
type: OPENAI_BLOCK.FUNCTION,
|
||||
name: tool.function.name,
|
||||
description: String(tool.function.description || ""),
|
||||
parameters: normalizeToolParameters(tool.function.parameters),
|
||||
|
||||
Reference in New Issue
Block a user