UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

608 lines (607 loc) • 25.5 kB
import { j as resolveIntegerOption } from "./number-coercion-CuVhdCBs.mjs"; import { s as filterStringRecord } from "./record-coerce-TIsquoVR.mjs"; import { r as truncateUtf16Safe } from "./utf16-slice-D_ngcYKd.mjs"; import { o as resolveAgentModelFallbackValues, s as resolveAgentModelPrimaryValue } from "./model-input-C9K4a_6k.mjs"; import { r as extensionForMime } from "./mime-CGDYrRzL.mjs"; import "./string-coerce-runtime-50qKeWeL.mjs"; import "./text-utility-runtime-BDs7x8Fk.mjs"; import { t as bufferToBlobPart } from "./blob-runtime-CBGGTQ96.mjs"; import "./number-runtime-CgyXj1A5.mjs"; import "./provider-onboard-CF3Sw4qH.mjs"; import { i as isPrivateNetworkOptInEnabled } from "./ssrf-policy-CWFvb6PK.mjs"; import "./media-mime-DLFR6fqC.mjs"; import { n as OPENAI_CODEX_RESPONSES_BASE_URL, o as isOpenAICodexBaseUrl, r as canonicalizeCodexResponsesBaseUrl } from "./base-url-BUR_8zW8.mjs"; import { i as OPENAI_DEFAULT_IMAGE_MODEL, t as OPENAI_CODEX_DEFAULT_MODEL } from "./default-models-CYEf1QuY.mjs"; import { t as resolveModelAuthPolicy } from "./model-auth-policy-CDWi63wg.mjs"; import "./provider-policy-api-DaHQqwNU.mjs"; import { r as resolveConfiguredOpenAIBaseUrl } from "./shared-C588ZwAA.mjs"; import path from "node:path"; //#region extensions/openai/image-generation-provider.ts const DEFAULT_OPENAI_IMAGE_BASE_URL = "https://api.openai.com/v1"; const DEFAULT_OPENAI_CODEX_IMAGE_BASE_URL = OPENAI_CODEX_RESPONSES_BASE_URL; const OPENAI_MODEL_REF_PREFIX = "openai/"; const DEFAULT_OPENAI_CODEX_IMAGE_RESPONSES_MODEL = OPENAI_CODEX_DEFAULT_MODEL.slice(7); const OPENAI_CODEX_IMAGE_INSTRUCTIONS = "You are an image generation assistant."; const OPENAI_TRANSPARENT_BACKGROUND_IMAGE_MODEL = "gpt-image-1.5"; const DEFAULT_OPENAI_IMAGE_TIMEOUT_MS = 18e4; const DEFAULT_AZURE_OPENAI_IMAGE_TIMEOUT_MS = 6e5; const DEFAULT_OUTPUT_MIME = "image/png"; const DEFAULT_SIZE = "1024x1024"; const OPENAI_SUPPORTED_SIZES = [ "1024x1024", "1536x1024", "1024x1536", "2048x2048", "2048x1152", "3840x2160", "2160x3840" ]; const OPENAI_LEGACY_IMAGE_SIZES = [ "1024x1024", "1536x1024", "1024x1536" ]; const OPENAI_MAX_INPUT_IMAGES = 5; const OPENAI_MAX_IMAGE_RESULTS = 4; const LOG_VALUE_MAX_CHARS = 256; const MOCK_OPENAI_PROVIDER_ID = "mock-openai"; const OPENAI_OUTPUT_FORMATS = [ "png", "jpeg", "webp" ]; const OPENAI_BACKGROUNDS = [ "transparent", "opaque", "auto" ]; const OPENAI_QUALITIES = [ "low", "medium", "high", "auto" ]; const OPENAI_IMAGE_25_MODELS = ["gpt-image-2.5-flare", "gpt-image-2.5-sunburst"]; const OPENAI_IMAGE_25_QUALITIES = [ "low", "medium", "high", "xhigh", "max", "auto" ]; const OPENAI_IMAGE_MODELS = [ OPENAI_DEFAULT_IMAGE_MODEL, ...OPENAI_IMAGE_25_MODELS, OPENAI_TRANSPARENT_BACKGROUND_IMAGE_MODEL, "gpt-image-1", "gpt-image-1-mini" ]; const OPENAI_FLEXIBLE_IMAGE_MODELS = [ OPENAI_DEFAULT_IMAGE_MODEL, ...OPENAI_IMAGE_25_MODELS, "gpt-image-2-2026-04-21" ]; const AZURE_HOSTNAME_SUFFIXES = [ ".openai.azure.com", ".services.ai.azure.com", ".cognitiveservices.azure.com" ]; const DEFAULT_AZURE_OPENAI_API_VERSION = "2024-12-01-preview"; function sanitizeLogValue(value) { const cleaned = (typeof value === "string" ? value : typeof value === "number" || typeof value === "boolean" ? String(value) : "").replace(/[\r\n\u2028\u2029]+/g, " ").replace(/[\u200e\u200f\u202a-\u202e\u2066-\u2069]/gi, "").replace(/\p{Cc}+/gu, " ").replace(/\s+/g, " ").trim(); if (!cleaned) return "unknown"; return cleaned.length > LOG_VALUE_MAX_CHARS ? `${truncateUtf16Safe(cleaned, LOG_VALUE_MAX_CHARS)}...` : cleaned; } function resolveOpenAIImageTimeoutMs(timeoutMs, options) { return timeoutMs ?? (options?.isAzure ? DEFAULT_AZURE_OPENAI_IMAGE_TIMEOUT_MS : DEFAULT_OPENAI_IMAGE_TIMEOUT_MS); } function isPublicOpenAIImageBaseUrl(baseUrl) { const parsed = URL.parse(baseUrl.trim()); if (!parsed) return false; const pathName = parsed.pathname.replace(/\/+$/, ""); return parsed.protocol === "https:" && parsed.hostname.toLowerCase() === "api.openai.com" && parsed.port === "" && parsed.username === "" && parsed.password === "" && parsed.search === "" && parsed.hash === "" && pathName === "/v1"; } function isAzureOpenAIBaseUrl(baseUrl) { const hostname = URL.parse(baseUrl?.trim() ?? "")?.hostname.toLowerCase(); return hostname !== void 0 && AZURE_HOSTNAME_SUFFIXES.some((suffix) => hostname.endsWith(suffix)); } function resolveAzureApiVersion() { return process.env.AZURE_OPENAI_API_VERSION?.trim() || DEFAULT_AZURE_OPENAI_API_VERSION; } function buildAzureImageUrl(rawBaseUrl, model, action) { return `${rawBaseUrl.replace(/\/+$/, "").replace(/\/openai\/v1$/, "").replace(/\/v1$/, "")}/openai/deployments/${model}/images/${action}?api-version=${resolveAzureApiVersion()}`; } function resolveOutputMime(outputFormat) { switch (outputFormat) { case "jpeg": return { mimeType: "image/jpeg", extension: "jpg" }; case "webp": return { mimeType: "image/webp", extension: "webp" }; default: return { mimeType: DEFAULT_OUTPUT_MIME, extension: "png" }; } } function resolveOpenAIImageOptions(req) { const openai = req.providerOptions?.openai; const background = openai?.background ?? req.background; const outputCompression = req.outputFormat === "jpeg" || req.outputFormat === "webp" ? openai?.outputCompression : void 0; return { ...req.quality !== void 0 ? { quality: req.quality } : {}, ...req.outputFormat !== void 0 ? { output_format: req.outputFormat } : {}, ...background !== void 0 ? { background } : {}, ...openai?.moderation !== void 0 ? { moderation: openai.moderation } : {}, ...outputCompression !== void 0 ? { output_compression: outputCompression } : {} }; } function resolveOpenAIImageRequestModel(req, options) { const model = req.model || "gpt-image-2"; if (options?.allowTransparentDefaultReroute === true && model === "gpt-image-2" && (req.providerOptions?.openai?.background ?? req.background) === "transparent") return OPENAI_TRANSPARENT_BACKGROUND_IMAGE_MODEL; return model; } function resolveNativeOpenAIImageSizesForModel(model) { switch (model) { case "gpt-image-1": case "gpt-image-1-mini": return OPENAI_LEGACY_IMAGE_SIZES; default: return OPENAI_SUPPORTED_SIZES; } } function isValidFlexibleOpenAIImageSize(model, size) { if (!OPENAI_FLEXIBLE_IMAGE_MODELS.some((candidate) => candidate === model)) return false; if (size === "auto") return OPENAI_IMAGE_25_MODELS.some((candidate) => candidate === model); const dimensions = /^(\d+)x(\d+)$/.exec(size ?? ""); if (!dimensions) return false; const width = Number(dimensions[1]); const height = Number(dimensions[2]); const pixels = width * height; return width > 0 && height > 0 && width % 16 === 0 && height % 16 === 0 && Math.max(width, height) <= 3840 && pixels >= 655360 && pixels <= 8294400 && width <= height * 3 && height <= width * 3; } function resolveConfiguredOpenAIImageBaseUrl(cfg, model) { const modelId = model.trim().replace(/^openai\//u, ""); return cfg?.models?.providers?.openai?.models?.find((candidate) => candidate.id.trim().replace(/^openai\//u, "") === modelId)?.baseUrl?.trim() || resolveConfiguredOpenAIBaseUrl(cfg); } function resolveOpenAIImageRequestSize(params, resolveSize) { const requestedSize = params.requestedSize ?? DEFAULT_SIZE; if (!params.applyNativeLimits) return { size: requestedSize }; const size = resolveSize({ requestedSize, supportedSizes: resolveNativeOpenAIImageSizesForModel(params.model) }) ?? DEFAULT_SIZE; if (size === requestedSize) return { size }; return { size, metadata: { requestedSize, normalizedSize: size } }; } function shouldAllowPrivateImageEndpoint(req) { if (req.provider === MOCK_OPENAI_PROVIDER_ID) return true; if (isPrivateNetworkOptInEnabled(req.cfg?.browser?.ssrfPolicy)) return true; const baseUrl = resolveConfiguredOpenAIImageBaseUrl(req.cfg, req.model); if (!baseUrl.startsWith("http://127.0.0.1:") && !baseUrl.startsWith("http://localhost:")) return false; return process.env.OPENCLAW_QA_ALLOW_LOCAL_IMAGE_PROVIDER === "1"; } function resolveRequestAuthStore(req, modelAuth) { if (req.authStore) return req.authStore; const agentDir = req.agentDir?.trim(); if (!agentDir) return; return modelAuth.ensureAuthProfileStore(agentDir, { allowKeychainPrompt: false }); } function hasDirectOpenAIImageApiKeyAuth(params, modelAuth) { if (hasExplicitOpenAIImageApiKeyConfig(params.cfg)) return true; if (process.env.OPENAI_API_KEY?.trim()) return true; const store = params.agentDir ? modelAuth.ensureAuthProfileStore(params.agentDir, { allowKeychainPrompt: false }) : void 0; if (!store) return false; const profileIds = modelAuth.listProfilesForProvider(store, "openai"); if (profileIds.length === 0) return false; return profileIds.some((profileId) => store.profiles[profileId]?.type === "api_key"); } function hasCodexResponseTransportProfileConfigured(req, modelAuth) { const store = resolveRequestAuthStore(req, modelAuth); if (!store) return false; return modelAuth.listProfilesForProvider(store, "openai").some((profileId) => { const credential = store.profiles[profileId]; return (credential?.type === "oauth" || credential?.type === "token") && resolveModelAuthPolicy({ provider: "openai", mode: credential.type, authFlow: credential.type === "oauth" ? credential.authFlow : void 0, capability: "image-generation" })?.compatible === true; }); } function hasExplicitOpenAIImageApiKeyConfig(cfg) { const providerConfig = cfg?.models?.providers?.openai; return providerConfig?.apiKey !== void 0 || providerConfig?.auth === "api-key"; } function hasExplicitDirectOpenAIImageConfig(cfg) { const providerConfig = cfg?.models?.providers?.openai; if (!providerConfig) return false; return hasExplicitOpenAIImageApiKeyConfig(cfg) || providerConfig.headers !== void 0 || providerConfig.authHeader !== void 0 || providerConfig.request !== void 0 || providerConfig.api !== void 0 && providerConfig.api !== "openai-chatgpt-responses"; } function hasChatGPTImageRouteConfig(cfg) { const providerConfig = cfg?.models?.providers?.openai; return isOpenAICodexBaseUrl(resolveConfiguredOpenAIBaseUrl(cfg)) || providerConfig?.api === "openai-chatgpt-responses"; } function forceOpenAIImageApiKeyAuth(cfg) { if (!hasExplicitOpenAIImageApiKeyConfig(cfg)) return cfg; const providerConfig = cfg?.models?.providers?.openai; if (!providerConfig) return cfg; return { ...cfg, models: { ...cfg?.models, providers: { ...cfg?.models?.providers, openai: { ...providerConfig, auth: "api-key" } } } }; } function isCodexSubscriptionAuthMode(mode) { return mode === "oauth" || mode === "token"; } function inferImageUploadFileName(params) { const fileName = params.fileName?.trim(); if (fileName) return path.basename(fileName); const mimeType = params.mimeType?.trim().toLowerCase() || DEFAULT_OUTPUT_MIME; const ext = extensionForMime(mimeType)?.slice(1) ?? "png"; return `image-${params.index + 1}.${ext}`; } async function resolveOptionalApiKeyForProvider(params) { const { resolveApiKeyForProvider } = await import("./plugin-sdk/provider-auth-runtime.js"); try { return await resolveApiKeyForProvider(params); } catch (error) { const provider = params?.provider ?? ""; if (!(error instanceof Error ? error.message : "").startsWith(`No API key found for provider "${provider}".`)) throw error; return null; } } function resolveCodexImageResponsesModels(cfg) { const agentModel = cfg?.agents?.defaults?.model; const retryModels = /* @__PURE__ */ new Set(); for (const ref of [resolveAgentModelPrimaryValue(agentModel), ...resolveAgentModelFallbackValues(agentModel)]) { const modelRef = ref?.trim(); const modelId = modelRef?.startsWith(OPENAI_MODEL_REF_PREFIX) ? modelRef.slice(7) : void 0; if (modelId && modelId !== DEFAULT_OPENAI_CODEX_IMAGE_RESPONSES_MODEL) retryModels.add(modelId); } return [DEFAULT_OPENAI_CODEX_IMAGE_RESPONSES_MODEL, ...retryModels]; } async function logCodexImageAuthSelected(params) { const { createSubsystemLogger } = await import("./plugin-sdk/logging-core.js"); const log = createSubsystemLogger("image-generation/openai"); const model = resolveOpenAIImageRequestModel(params.req, { allowTransparentDefaultReroute: true }); log.info(`image auth selected: provider=openai mode=${sanitizeLogValue(params.authMode)} transport=codex-responses requestedModel=${sanitizeLogValue(model)} responsesModel=${DEFAULT_OPENAI_CODEX_IMAGE_RESPONSES_MODEL} timeoutMs=${params.timeoutMs}`); } function isCodexModelUnavailableBody(body, model) { if (!body) return false; try { const payload = JSON.parse(body); return typeof payload === "object" && payload !== null && "detail" in payload && payload.detail === `The '${model}' model is not supported when using Codex with a ChatGPT account.`; } catch { return false; } } async function generateOpenAICodexImage(params) { const [{ ProviderHttpError, assertOkOrThrowHttpError, postJsonRequest, resolveProviderHttpRequestConfig, sanitizeConfiguredModelProviderRequest }, { toImageDataUrl }, { resolveClosestSize }, { readCodexImageGenerationResponse }] = await Promise.all([ import("./plugin-sdk/provider-http.js"), import("./plugin-sdk/image-generation.js"), import("./plugin-sdk/media-generation-runtime.js"), import("./extensions/openai/image-generation-codex-response.js") ]); const { req, apiKey } = params; const inputImages = req.inputImages ?? []; const openAIProviderConfig = req.cfg?.models?.providers?.openai; const codexProviderConfig = openAIProviderConfig?.api === "openai-chatgpt-responses" ? openAIProviderConfig : void 0; const { baseUrl, allowPrivateNetwork, headers, dispatcherPolicy } = resolveProviderHttpRequestConfig({ baseUrl: canonicalizeCodexResponsesBaseUrl(codexProviderConfig?.baseUrl), defaultBaseUrl: DEFAULT_OPENAI_CODEX_IMAGE_BASE_URL, defaultHeaders: { Authorization: `Bearer ${apiKey}`, Accept: "text/event-stream" }, request: sanitizeConfiguredModelProviderRequest(codexProviderConfig?.request), provider: "openai", api: "openai-chatgpt-responses", capability: "image", transport: "http" }); const model = resolveOpenAIImageRequestModel(req, { allowTransparentDefaultReroute: true }); const count = resolveIntegerOption(req.count, 1, { min: 1, max: OPENAI_MAX_IMAGE_RESULTS }); const sizeResolution = resolveOpenAIImageRequestSize({ model, requestedSize: req.size, applyNativeLimits: true }, resolveClosestSize); const size = sizeResolution.size; const timeoutMs = resolveOpenAIImageTimeoutMs(req.timeoutMs); headers.set("Content-Type", "application/json"); const content = [{ type: "input_text", text: req.prompt }, ...inputImages.map((image) => ({ type: "input_image", image_url: toImageDataUrl({ buffer: image.buffer, mimeType: image.mimeType }), detail: "auto" }))]; const requestImage = async (responsesModel) => { const { response, release } = await postJsonRequest({ url: `${baseUrl}/responses`, headers, body: { model: responsesModel, input: [{ role: "user", content }], instructions: OPENAI_CODEX_IMAGE_INSTRUCTIONS, tools: [{ type: "image_generation", model, size, ...resolveOpenAIImageOptions(req) }], tool_choice: { type: "image_generation" }, stream: true, store: false }, timeoutMs, fetchFn: fetch, allowPrivateNetwork, ssrfPolicy: req.ssrfPolicy, dispatcherPolicy }); try { await assertOkOrThrowHttpError(response, "OpenAI Codex image generation failed"); return await readCodexImageGenerationResponse(response, { model, ...resolveOutputMime(req.outputFormat) }); } finally { await release(); } }; const [defaultResponsesModel, ...retryResponsesModels] = resolveCodexImageResponsesModels(req.cfg); let responsesModel = defaultResponsesModel; const results = []; for (let index = 0; index < count; index += 1) for (;;) try { results.push(await requestImage(responsesModel)); break; } catch (error) { const nextResponsesModel = retryResponsesModels.shift(); if (!nextResponsesModel || !(error instanceof ProviderHttpError) || error.status !== 400 || !isCodexModelUnavailableBody(error.errorBody, responsesModel)) throw error; const { createSubsystemLogger } = await import("./plugin-sdk/logging-core.js"); createSubsystemLogger("image-generation/openai").info(`codex image responses model unavailable: responsesModel=${sanitizeLogValue(responsesModel)} retryResponsesModel=${sanitizeLogValue(nextResponsesModel)}`); responsesModel = nextResponsesModel; } const images = results.flatMap((result) => result.images); const output = resolveOutputMime(req.outputFormat); return { images: images.map((image, index) => Object.assign({}, image, { fileName: `image-${index + 1}.${output.extension}` })), model, metadata: { ...sizeResolution.metadata, responses: results.map((result) => result.metadata).filter(Boolean) } }; } function buildOpenAIImageGenerationProvider(modelAuth) { return { id: "openai", label: "OpenAI", defaultModel: OPENAI_DEFAULT_IMAGE_MODEL, models: [...OPENAI_IMAGE_MODELS], capabilities: { generate: { maxCount: 4, supportsSize: true, supportsAspectRatio: false, supportsResolution: false }, edit: { enabled: true, maxCount: 4, maxInputImages: OPENAI_MAX_INPUT_IMAGES, supportsSize: true, supportsAspectRatio: false, supportsResolution: false }, geometry: { sizes: [...OPENAI_SUPPORTED_SIZES], sizesByModel: Object.fromEntries(OPENAI_FLEXIBLE_IMAGE_MODELS.map((model) => [model, []])) }, output: { formats: [...OPENAI_OUTPUT_FORMATS], qualities: [...OPENAI_QUALITIES], qualitiesByModel: Object.fromEntries(OPENAI_IMAGE_25_MODELS.map((model) => [model, [...OPENAI_IMAGE_25_QUALITIES]])), backgrounds: [...OPENAI_BACKGROUNDS] } }, isConfigured: ({ cfg, agentDir }) => modelAuth.isProviderApiKeyConfigured({ provider: "openai", agentDir, cfg, capability: "image-generation" }) && (isPublicOpenAIImageBaseUrl(resolveConfiguredOpenAIBaseUrl(cfg)) || hasDirectOpenAIImageApiKeyAuth({ cfg, agentDir }, modelAuth) || hasChatGPTImageRouteConfig(cfg) && hasCodexResponseTransportProfileConfigured({ agentDir }, modelAuth)), async generateImage(req) { const inputImages = req.inputImages ?? []; const isEdit = inputImages.length > 0; const rawBaseUrl = resolveConfiguredOpenAIImageBaseUrl(req.cfg, req.model); const publicOpenAIBaseUrl = isPublicOpenAIImageBaseUrl(rawBaseUrl); const chatGPTBaseUrl = isOpenAICodexBaseUrl(rawBaseUrl); const codexResponsesConfigured = req.cfg?.models?.providers?.openai?.api === "openai-chatgpt-responses"; const explicitOpenAIApiKeyConfig = hasExplicitOpenAIImageApiKeyConfig(req.cfg); const explicitDirectOpenAIConfig = !chatGPTBaseUrl && !codexResponsesConfigured && hasExplicitDirectOpenAIImageConfig(req.cfg); const useCodexResponseTransportRoute = (publicOpenAIBaseUrl || chatGPTBaseUrl || codexResponsesConfigured) && !explicitDirectOpenAIConfig && hasCodexResponseTransportProfileConfigured(req, modelAuth); let preResolvedImageAuth; if (explicitOpenAIApiKeyConfig) { const directAuth = await resolveOptionalApiKeyForProvider({ provider: "openai", capability: "image-generation", cfg: forceOpenAIImageApiKeyAuth(req.cfg), agentDir: req.agentDir, store: req.authStore, credentialPrecedence: "env-first" }); preResolvedImageAuth = directAuth?.apiKey && (directAuth.mode === void 0 || directAuth.mode === "api-key") ? directAuth : null; } if (useCodexResponseTransportRoute) { const codexAuth = await resolveOptionalApiKeyForProvider({ provider: "openai", capability: "image-generation", cfg: req.cfg, agentDir: req.agentDir, store: req.authStore }); if (!codexAuth?.apiKey) throw new Error("OpenAI Codex OAuth missing"); if (codexAuth.mode === "api-key") preResolvedImageAuth = codexAuth; else { const timeoutMs = resolveOpenAIImageTimeoutMs(req.timeoutMs); await logCodexImageAuthSelected({ req, authMode: codexAuth.mode, timeoutMs }); return generateOpenAICodexImage({ req, apiKey: codexAuth.apiKey }); } } let imageAuth = preResolvedImageAuth !== void 0 ? preResolvedImageAuth : await resolveOptionalApiKeyForProvider({ provider: "openai", capability: "image-generation", cfg: req.cfg, agentDir: req.agentDir, store: req.authStore }); if (!explicitDirectOpenAIConfig && imageAuth?.apiKey && isCodexSubscriptionAuthMode(imageAuth.mode)) { if (publicOpenAIBaseUrl) { const timeoutMs = resolveOpenAIImageTimeoutMs(req.timeoutMs); await logCodexImageAuthSelected({ req, authMode: imageAuth.mode, timeoutMs }); return generateOpenAICodexImage({ req, apiKey: imageAuth.apiKey }); } imageAuth = void 0; } if (!imageAuth?.apiKey) { if (!publicOpenAIBaseUrl) throw new Error("OpenAI API key missing"); throw new Error("OpenAI API key or Codex OAuth missing"); } const [{ assertOkOrThrowHttpError, postJsonRequest, postMultipartRequest, readProviderJsonResponse, resolveProviderHttpRequestConfig, sanitizeConfiguredModelProviderRequest }, { parseOpenAiCompatibleImageResponse, resolveInlineImageJsonResponseMaxBytes }, { resolveClosestSize, resolveGeneratedMediaMaxBytes }] = await Promise.all([ import("./plugin-sdk/provider-http.js"), import("./plugin-sdk/image-generation.js"), import("./plugin-sdk/media-generation-runtime.js") ]); const isAzure = isAzureOpenAIBaseUrl(rawBaseUrl); const openAIProviderConfig = req.cfg?.models?.providers?.openai; const { baseUrl, allowPrivateNetwork, headers, dispatcherPolicy } = resolveProviderHttpRequestConfig({ baseUrl: rawBaseUrl, defaultBaseUrl: DEFAULT_OPENAI_IMAGE_BASE_URL, allowPrivateNetwork: shouldAllowPrivateImageEndpoint(req), headers: filterStringRecord(openAIProviderConfig?.headers), request: sanitizeConfiguredModelProviderRequest(openAIProviderConfig?.request), api: openAIProviderConfig?.api, defaultHeaders: isAzure ? { "api-key": imageAuth.apiKey } : { Authorization: `Bearer ${imageAuth.apiKey}` }, provider: "openai", capability: "image", transport: "http" }); const model = resolveOpenAIImageRequestModel(req, { allowTransparentDefaultReroute: publicOpenAIBaseUrl }); const count = resolveIntegerOption(req.count, 1, { min: 1, max: OPENAI_MAX_IMAGE_RESULTS }); const timeoutMs = resolveOpenAIImageTimeoutMs(req.timeoutMs, { isAzure }); const sizeResolution = isValidFlexibleOpenAIImageSize(model, req.size) ? { size: req.size } : resolveOpenAIImageRequestSize({ model, requestedSize: req.size, applyNativeLimits: publicOpenAIBaseUrl || isAzure }, resolveClosestSize); const size = sizeResolution.size; const url = isAzure ? buildAzureImageUrl(rawBaseUrl, model, isEdit ? "edits" : "generations") : `${baseUrl}/images/${isEdit ? "edits" : "generations"}`; const body = { ...!isAzure ? { model } : {}, prompt: req.prompt, n: count, size, ...resolveOpenAIImageOptions(req), ...req.providerOptions?.openai?.user !== void 0 ? { user: req.providerOptions.openai.user } : {} }; const requestOptions = { url, headers: new Headers(headers), timeoutMs, fetchFn: fetch, allowPrivateNetwork, ssrfPolicy: req.ssrfPolicy, dispatcherPolicy }; const form = isEdit ? new FormData() : void 0; if (form) { for (const [key, value] of Object.entries(body)) form.set(key, String(value)); for (const [index, image] of inputImages.entries()) { const mimeType = image.mimeType?.trim() || DEFAULT_OUTPUT_MIME; form.append("image[]", new Blob([bufferToBlobPart(image.buffer)], { type: mimeType }), inferImageUploadFileName({ fileName: image.fileName, mimeType, index })); } requestOptions.headers.delete("Content-Type"); } else requestOptions.headers.set("Content-Type", "application/json"); const { response, release } = form ? await postMultipartRequest({ ...requestOptions, body: form }) : await postJsonRequest({ ...requestOptions, body }); try { await assertOkOrThrowHttpError(response, isEdit ? "OpenAI image edit failed" : "OpenAI image generation failed"); const data = await readProviderJsonResponse(response, "openai.image-generation", { maxBytes: resolveInlineImageJsonResponseMaxBytes(count, resolveGeneratedMediaMaxBytes(req.cfg, "image")) }); const output = resolveOutputMime(req.outputFormat); const images = parseOpenAiCompatibleImageResponse(data, { defaultMimeType: output.mimeType, malformedResponseError: isEdit ? "OpenAI image edit response malformed" : "OpenAI image generation response malformed" }).map((image, index) => Object.assign(image, { fileName: `image-${index + 1}.${output.extension}` })); if (images.length === 0) throw new Error(isEdit ? "OpenAI image edit response missing image data" : "OpenAI image generation response missing image data"); return { images, model, ...sizeResolution.metadata ? { metadata: sizeResolution.metadata } : {} }; } finally { await release(); } } }; } //#endregion export { buildOpenAIImageGenerationProvider as t };