mirror of
https://github.com/decolua/9router.git
synced 2026-05-08 12:01:28 +00:00
Fix: Codex image support - convert image_url to input_image format (#236)
Cursor sends images as Chat Completions format:
{ type: "image_url", image_url: { url: "data:...", detail: "auto" } }
But Codex Responses API requires:
{ type: "input_image", image_url: "data:..." }
- openai-responses.js: bidirectional conversion image_url <-> input_image
- responsesApiHelper.js: input_image -> image_url in Responses->Chat path
- codex.js: safety net conversion in executor before sending to Codex API
Note: Cursor has a known bug where images bypass the Override OpenAI Base URL
and are sent directly to api.openai.com. This fix is effective for other clients
(curl, Codex CLI, Claude Code) that route through the proxy correctly.
Made-with: Cursor
This commit is contained in:
committed by
GitHub
parent
7195fee2f6
commit
40a53fbd33
@@ -34,6 +34,21 @@ export class CodexExecutor extends BaseExecutor {
|
||||
body.input = [{ type: "message", role: "user", content: [{ type: "input_text", text: "..." }] }];
|
||||
}
|
||||
|
||||
// Normalize image content: image_url → input_image (Responses API format)
|
||||
if (Array.isArray(body.input)) {
|
||||
for (const item of body.input) {
|
||||
if (Array.isArray(item.content)) {
|
||||
item.content = item.content.map(c => {
|
||||
if (c.type === "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 c;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure streaming is enabled (Codex API requires it)
|
||||
body.stream = true;
|
||||
|
||||
|
||||
@@ -56,11 +56,15 @@ export function convertResponsesApiFormat(body) {
|
||||
pendingToolResults = [];
|
||||
}
|
||||
|
||||
// Convert content: input_text → text, output_text → text
|
||||
// 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") {
|
||||
const url = c.image_url || c.file_id || "";
|
||||
return { type: "image_url", image_url: { url, detail: c.detail || "auto" } };
|
||||
}
|
||||
return c;
|
||||
})
|
||||
: item.content;
|
||||
|
||||
@@ -48,11 +48,15 @@ export function openaiResponsesToOpenAIRequest(model, body, stream, credentials)
|
||||
pendingToolResults = [];
|
||||
}
|
||||
|
||||
// Convert content: input_text → text, output_text → text
|
||||
// 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") {
|
||||
const url = c.image_url || c.file_id || "";
|
||||
return { type: "image_url", image_url: { url, detail: c.detail || "auto" } };
|
||||
}
|
||||
return c;
|
||||
})
|
||||
: item.content;
|
||||
@@ -186,7 +190,14 @@ export function openaiToOpenAIResponsesRequest(model, body, stream, credentials)
|
||||
: Array.isArray(msg.content)
|
||||
? msg.content.map(c => {
|
||||
if (c.type === "text") return { type: contentType, text: c.text };
|
||||
if (c.type === "image_url") return { type: "image_url", image_url: c.image_url };
|
||||
// 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") {
|
||||
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" };
|
||||
}
|
||||
if (c.type === "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) };
|
||||
|
||||
Reference in New Issue
Block a user