mirror of
https://github.com/Nezumi-2711/9router.git
synced 2026-09-22 13:38:31 +00:00
fix(codex): await image URL fetches before sending to upstream (closes #575)
Remote HTTP(S) image URLs are fetched and inlined as base64 data URIs in a new prefetchImages() step run before super.execute(), so the body sent to Codex contains resolved image bytes instead of URLs the backend cannot access. Scope is limited to the Codex executor — base executor and other providers are untouched. Co-authored-by: anuragg-saxenaa <anuragg.saxenaa@gmail.com> Made-with: Cursor
This commit is contained in:
+31
-16
@@ -3,6 +3,7 @@ import { BaseExecutor } from "./base.js";
|
||||
import { CODEX_DEFAULT_INSTRUCTIONS } from "../config/codexInstructions.js";
|
||||
import { PROVIDERS } from "../config/providers.js";
|
||||
import { normalizeResponsesInput } from "../translator/helpers/responsesApiHelper.js";
|
||||
import { fetchImageAsBase64 } from "../translator/helpers/imageHelper.js";
|
||||
import { getConsistentMachineId } from "../../src/shared/utils/machineId.js";
|
||||
|
||||
// In-memory map: hash(machineId + first assistant content) → { sessionId, lastUsed }
|
||||
@@ -93,7 +94,36 @@ export class CodexExecutor extends BaseExecutor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform request before sending - inject default instructions if missing
|
||||
* Prefetch remote image URLs and inline them as base64 data URIs.
|
||||
* Runs before execute() because Codex backend cannot fetch remote images.
|
||||
* Mutates body.input in place.
|
||||
*/
|
||||
async prefetchImages(body) {
|
||||
if (!Array.isArray(body?.input)) return;
|
||||
for (const item of body.input) {
|
||||
if (!Array.isArray(item.content)) continue;
|
||||
const pending = item.content.map(async (c) => {
|
||||
if (c.type !== "image_url") return c;
|
||||
const url = typeof c.image_url === "string" ? c.image_url : c.image_url?.url;
|
||||
const detail = c.image_url?.detail || "auto";
|
||||
if (!url) return c;
|
||||
if (url.startsWith("data:")) return { type: "input_image", image_url: url, detail };
|
||||
const fetched = await fetchImageAsBase64(url, { timeoutMs: 15000 });
|
||||
return { type: "input_image", image_url: fetched?.url || url, detail };
|
||||
});
|
||||
item.content = await Promise.all(pending);
|
||||
}
|
||||
}
|
||||
|
||||
async execute(args) {
|
||||
// Fetch remote images before the synchronous transform/execute pipeline
|
||||
await this.prefetchImages(args.body);
|
||||
return super.execute(args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform request before sending - inject default instructions if missing.
|
||||
* Image fetching is handled separately in prefetchImages() so this stays sync.
|
||||
*/
|
||||
transformRequest(model, body, stream, credentials) {
|
||||
this._isCompact = !!body._compact;
|
||||
@@ -109,21 +139,6 @@ 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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user