UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

783 lines (782 loc) 31.8 kB
import { l as normalizeOptionalString, o as normalizeLowercaseStringOrEmpty } from "./string-coerce-CIXf7egm.js"; import "./defaults-CdX9UGcX.js"; import { c as isNonSecretApiKeyMarker } from "./model-auth-markers-jBKQn38x.js"; import { n as buildManifestModelProviderConfig, o as findCatalogTemplate } from "./provider-catalog-8ARoyQkL.js"; import "./provider-auth-BeZ7NZUU.js"; import { t as createProviderApiKeyAuthMethod } from "./provider-api-key-auth-mNtReYE0.js"; import "./string-coerce-runtime-GQa0ehRA.js"; import { d as normalizeProviderId, h as resolveFamilyForwardCompatModel, m as matchesExactOrPrefix } from "./provider-model-shared-fX-Ywqb3.js"; import "./provider-auth-api-key-DSeBB_JQ.js"; import { c as getCachedLiveProviderModelRows, h as readLiveModelCatalogStringField, m as readLiveModelCatalogPositiveSafeIntegerField, p as readLiveModelCatalogBooleanField, t as LiveModelCatalogHttpError } from "./provider-catalog-live-runtime-EZZSw18v.js"; import "./provider-catalog-shared-B4jeC2Nd.js"; import { c as resolveOpenAIDefaultBaseUrl, i as classifyOpenAIBaseUrl, n as OPENAI_CODEX_RESPONSES_BASE_URL, o as isOpenAICodexBaseUrl, s as isOpenAIHttpsApiBaseUrl } from "./base-url-DVAaAeRo.js"; import { a as OPENAI_DEFAULT_MODEL, c as applyOpenAIConfig, t as OPENAI_CODEX_DEFAULT_MODEL } from "./default-models-Dn67kTkm.js"; import { n as buildOpenAIResponsesProviderHooks, r as buildOpenAISyntheticCatalogEntry, t as OPENAI_DEFAULT_RUNTIME_CONTEXT_TOKENS } from "./shared-BAfroQe-.js"; import { S as resolveOpenAICodexReasoningEfforts, _ as OPENAI_PROVIDER_MODERN_MODEL_IDS, a as OPENAI_GPT_54_MINI_MODEL_ID, b as isOpenAISubscriptionOnlyRouteModelId, c as OPENAI_GPT_54_PRO_MODEL_ID, d as OPENAI_GPT_56_LUNA_MODEL_ID, f as OPENAI_GPT_56_MODEL_ID, g as OPENAI_GPT_6_ASTRA_MODEL_ID, l as OPENAI_GPT_55_MODEL_ID, m as OPENAI_GPT_56_TERRA_MODEL_ID, n as OPENAI_CHAT_LATEST_MODEL_ID, o as OPENAI_GPT_54_MODEL_ID, p as OPENAI_GPT_56_SOL_MODEL_ID, s as OPENAI_GPT_54_NANO_MODEL_ID, u as OPENAI_GPT_55_PRO_MODEL_ID, x as normalizeOpenAIModelRouteId, y as isOpenAIPlatformOnlyRouteModelId } from "./model-route-contract-pNgq17sM.js"; import { n as resolveUnifiedOpenAIThinkingProfile, r as modelCatalog } from "./thinking-policy-C1E_gzmv.js"; import { n as buildOpenAICodexProviderHooks, t as buildOpenAIChatGPTAuthMethodRuns } from "./openai-chatgpt-provider-DMrfR8t_.js"; import { t as createOpenAIProvider } from "./provider-contract-api-iXo0BzTG.js"; import { a as resolveAuthoredOpenAIProviderConfig } from "./provider-policy-api-CAxxTyEq.js"; //#region extensions/openai/openai-provider.ts const PROVIDER_ID = "openai"; function classifyOpenAiFailoverCode(code) { switch (code?.trim().toUpperCase()) { case "SERVER_ERROR": return "server_error"; case "INSUFFICIENT_QUOTA": return "billing"; default: return; } } const OPENAI_MODELS_ENDPOINT = "https://api.openai.com/v1/models"; const OPENAI_CODEX_MODELS_ENDPOINT = `${OPENAI_CODEX_RESPONSES_BASE_URL}/models?client_version=0.153.4`; const OPENAI_MODELS_CACHE_TTL_MS = 6e4; const OPENAI_CODEX_MODELS_CACHE_TTL_MS = 6e4; const OPENAI_CODEX_GPT_56_CONTEXT_WINDOW = 372e3; const OPENAI_GPT_55_PRO_CONTEXT_WINDOW = 105e4; const OPENAI_GPT_54_CONTEXT_TOKENS = 105e4; const OPENAI_GPT_54_PRO_CONTEXT_TOKENS = 105e4; const OPENAI_GPT_54_MINI_CONTEXT_TOKENS = 4e5; const OPENAI_GPT_54_NANO_CONTEXT_TOKENS = 4e5; const OPENAI_GPT_54_MAX_TOKENS = 128e3; const OPENAI_CHAT_LATEST_COST = { input: 5, output: 30, cacheRead: .5, cacheWrite: 0 }; const OPENAI_GPT_54_COST = { input: 2.5, output: 15, cacheRead: .25, cacheWrite: 0 }; const OPENAI_GPT_54_PRO_COST = { input: 30, output: 180, cacheRead: 0, cacheWrite: 0 }; const OPENAI_GPT_54_MINI_COST = { input: .75, output: 4.5, cacheRead: .075, cacheWrite: 0 }; const OPENAI_GPT_54_NANO_COST = { input: .2, output: 1.25, cacheRead: .02, cacheWrite: 0 }; const OPENAI_GPT_55_PRO_TEMPLATE_MODEL_IDS = [OPENAI_GPT_54_PRO_MODEL_ID, OPENAI_GPT_54_MODEL_ID]; const OPENAI_GPT_54_TEMPLATE_MODEL_IDS = [OPENAI_GPT_54_MODEL_ID, OPENAI_GPT_55_MODEL_ID]; const OPENAI_GPT_54_PRO_TEMPLATE_MODEL_IDS = [OPENAI_GPT_54_PRO_MODEL_ID, OPENAI_GPT_55_PRO_MODEL_ID]; const OPENAI_GPT_54_MINI_TEMPLATE_MODEL_IDS = [OPENAI_GPT_54_MINI_MODEL_ID, "gpt-5-mini"]; const OPENAI_GPT_54_NANO_TEMPLATE_MODEL_IDS = [ OPENAI_GPT_54_NANO_MODEL_ID, "gpt-5-nano", "gpt-5-mini" ]; const OPENAI_CHAT_LATEST_TEMPLATE_MODEL_IDS = [OPENAI_GPT_55_MODEL_ID, OPENAI_GPT_54_MODEL_ID]; const OPENAI_GPT_56_TEMPLATE_MODEL_IDS = [OPENAI_GPT_55_MODEL_ID]; const OPENAI_UNKNOWN_MODEL_COST = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }; const OPENAI_MANIFEST_PROVIDER = buildManifestModelProviderConfig({ providerId: PROVIDER_ID, catalog: modelCatalog.providers.openai }); function shouldFetchOpenAILiveModels(baseUrl) { return isOpenAIHttpsApiBaseUrl(baseUrl); } function buildOpenAIManifestModelsForBaseUrl(baseUrl) { return OPENAI_MANIFEST_PROVIDER.models.map((model) => model.api === "openai-chatgpt-responses" || isOpenAICodexBaseUrl(model.baseUrl) ? { ...model } : { ...model, api: model.api ?? OPENAI_MANIFEST_PROVIDER.api ?? "openai-responses", baseUrl }); } function buildOpenAIDiscoverablePlatformModels(baseUrl) { return [ { id: OPENAI_CHAT_LATEST_MODEL_ID, name: "Chat Latest", reasoning: false, cost: OPENAI_CHAT_LATEST_COST, contextWindow: 4e5 }, { id: OPENAI_GPT_54_MODEL_ID, name: "GPT-5.4", reasoning: true, cost: OPENAI_GPT_54_COST, contextWindow: OPENAI_GPT_54_CONTEXT_TOKENS }, { id: OPENAI_GPT_54_PRO_MODEL_ID, name: "GPT-5.4 Pro", reasoning: true, cost: OPENAI_GPT_54_PRO_COST, contextWindow: OPENAI_GPT_54_PRO_CONTEXT_TOKENS }, { id: OPENAI_GPT_54_MINI_MODEL_ID, name: "GPT-5.4 Mini", reasoning: true, cost: OPENAI_GPT_54_MINI_COST, contextWindow: OPENAI_GPT_54_MINI_CONTEXT_TOKENS }, { id: OPENAI_GPT_54_NANO_MODEL_ID, name: "GPT-5.4 Nano", reasoning: true, cost: OPENAI_GPT_54_NANO_COST, contextWindow: OPENAI_GPT_54_NANO_CONTEXT_TOKENS } ].map(({ id, name, reasoning, cost, contextWindow }) => ({ id, name, reasoning, cost, contextWindow, api: "openai-responses", baseUrl, input: ["text", "image"], maxTokens: OPENAI_GPT_54_MAX_TOKENS })); } function scopeOpenAICatalogOutcome(catalog, profileId) { const scopedProfileId = profileId?.trim(); if (!catalog.outcome || !scopedProfileId) return catalog; return { ...catalog, outcome: { ...catalog.outcome, profileId: scopedProfileId } }; } async function buildOpenAILiveProviderConfig(params) { const baseUrl = normalizeOptionalString(params.baseUrl) ?? resolveOpenAIDefaultBaseUrl(params.env); const models = buildOpenAIManifestModelsForBaseUrl(baseUrl); const fallback = { baseUrl, api: "openai-responses", ...params.apiKey ? { apiKey: params.apiKey } : {}, models }; if (!shouldFetchOpenAILiveModels(baseUrl)) return { provider: fallback }; try { const rows = await getCachedLiveProviderModelRows({ providerId: PROVIDER_ID, endpoint: OPENAI_MODELS_ENDPOINT, apiKey: params.apiKey, discoveryApiKey: params.discoveryApiKey, fetchGuard: params.fetchGuard, signal: params.signal, ttlMs: OPENAI_MODELS_CACHE_TTL_MS, auditContext: "openai-model-discovery" }); const discoveredIds = new Set(rows.flatMap((row) => { if (!row || typeof row !== "object" || Array.isArray(row)) return []; const candidate = row; if (candidate.object !== void 0 && candidate.object !== "model") return []; const modelId = typeof candidate.id === "string" ? candidate.id.trim() : ""; return modelId ? [modelId] : []; })); const selectedIds = /* @__PURE__ */ new Set(); return { provider: { ...fallback, models: [...models, ...buildOpenAIDiscoverablePlatformModels(baseUrl)].filter((model) => { if (!discoveredIds.has(model.id) || selectedIds.has(model.id)) return false; selectedIds.add(model.id); return true; }) }, outcome: { provider: PROVIDER_ID, status: "ready" } }; } catch (error) { if (error instanceof LiveModelCatalogHttpError && (error.status === 401 || error.status === 403)) return { provider: { ...fallback, models: [] }, outcome: { provider: PROVIDER_ID, ...params.rejectionScope ? { rejectionScope: "catalog" } : {}, status: "auth-rejected" } }; return { provider: fallback, outcome: { provider: PROVIDER_ID, status: "unavailable" } }; } } function readCodexModelStringArray(row, keys) { if (!row || typeof row !== "object" || Array.isArray(row)) return []; const record = row; for (const key of keys) { const value = record[key]; if (Array.isArray(value)) return value.filter((entry) => typeof entry === "string"); } return []; } function readCodexReasoningLevels(row) { if (!row || typeof row !== "object" || Array.isArray(row)) return; const record = row; const value = record.supported_reasoning_levels ?? record.supportedReasoningLevels; if (!Array.isArray(value)) return; return value.flatMap((entry) => { if (typeof entry === "string" && entry.trim().length > 0) return [entry.trim()]; if (entry && typeof entry === "object" && !Array.isArray(entry)) { const effort = entry.effort; return typeof effort === "string" && effort.trim().length > 0 ? [effort.trim()] : []; } return []; }); } function readCodexModelRows(body) { if (!body || typeof body !== "object" || Array.isArray(body)) throw new Error("OpenAI Codex model discovery response must be { models: [] }"); const models = body.models; if (!Array.isArray(models)) throw new Error("OpenAI Codex model discovery response must be { models: [] }"); return models; } function shouldIncludeCodexModelRow(row) { const visibility = normalizeLowercaseStringOrEmpty(readLiveModelCatalogStringField(row, "visibility") ?? ""); if (visibility && visibility !== "list") return false; return (readLiveModelCatalogBooleanField(row, "show_in_picker") ?? readLiveModelCatalogBooleanField(row, "showInPicker")) !== false; } function resolveCodexModelInput(row, fallback) { const rawModalities = readCodexModelStringArray(row, ["input_modalities", "inputModalities"]); if (rawModalities.length === 0) return fallback?.input ?? ["text", "image"]; const modalities = new Set(rawModalities.map((modality) => normalizeLowercaseStringOrEmpty(modality))); const input = /* @__PURE__ */ new Set(); if (modalities.has("text")) input.add("text"); if (modalities.has("image") || modalities.has("vision")) input.add("image"); if (modalities.has("audio")) input.add("audio"); if (modalities.has("video")) input.add("video"); return input.size > 0 ? [...input] : fallback?.input ?? ["text", "image"]; } function normalizeOpenAICodexCatalogModel(model) { const modelId = normalizeLowercaseStringOrEmpty(model.id); if (modelId === "gpt-5.6-sol" || modelId === "gpt-5.6-terra" || modelId === "gpt-5.6-luna") { const supportedReasoningEfforts = resolveOpenAICodexReasoningEfforts(modelId, model.compat?.supportedReasoningEfforts?.filter((effort) => effort !== "none")); return { ...model, contextWindow: OPENAI_CODEX_GPT_56_CONTEXT_WINDOW, contextTokens: OPENAI_DEFAULT_RUNTIME_CONTEXT_TOKENS, thinkingLevelMap: { ...model.thinkingLevelMap, off: null }, ...model.compat ? { compat: { ...model.compat, ...supportedReasoningEfforts ? { supportedReasoningEfforts } : {} } } : {} }; } return model; } function resolveCodexModelFallback(modelId) { const fallbackModel = OPENAI_MANIFEST_PROVIDER.models.find((candidate) => normalizeLowercaseStringOrEmpty(candidate.id) === normalizeLowercaseStringOrEmpty(modelId)); return fallbackModel ? normalizeOpenAICodexCatalogModel(fallbackModel) : void 0; } function buildOpenAICodexModelFromLiveRow(row) { if (!shouldIncludeCodexModelRow(row)) return; const modelId = readLiveModelCatalogStringField(row, "slug") ?? readLiveModelCatalogStringField(row, "id"); if (!modelId) return; if (isOpenAIPlatformOnlyRouteModelId(modelId)) return; const normalizedModelId = normalizeLowercaseStringOrEmpty(modelId); const fallback = resolveCodexModelFallback(modelId); const reasoningLevels = readCodexReasoningLevels(row); const observedContextTokens = readLiveModelCatalogPositiveSafeIntegerField(row, ["context_window", "contextWindow"]); const contextTokens = matchesExactOrPrefix(normalizedModelId, ["gpt-5.6"]) ? Math.min(observedContextTokens ?? fallback?.contextTokens ?? 272e3, OPENAI_DEFAULT_RUNTIME_CONTEXT_TOKENS) : observedContextTokens; const contextWindow = readLiveModelCatalogPositiveSafeIntegerField(row, ["max_context_window", "maxContextWindow"]) ?? fallback?.contextWindow ?? observedContextTokens ?? 2e5; const maxTokens = readLiveModelCatalogPositiveSafeIntegerField(row, [ "max_output_tokens", "maxOutputTokens", "max_completion_tokens", "maxCompletionTokens" ]) ?? fallback?.maxTokens ?? OPENAI_GPT_54_MAX_TOKENS; const compat = reasoningLevels !== void 0 ? { ...fallback?.compat, supportsReasoningEffort: true, supportedReasoningEfforts: [...reasoningLevels] } : fallback?.compat; const thinkingLevelMap = { ...reasoningLevels === void 0 ? fallback?.thinkingLevelMap : {}, ...normalizedModelId.startsWith("gpt-5.6") ? { off: null } : {}, ...reasoningLevels?.includes("xhigh") ? { xhigh: "xhigh" } : {}, ...reasoningLevels?.includes("max") ? { max: "max" } : {} }; return normalizeOpenAICodexCatalogModel({ id: modelId, name: readLiveModelCatalogStringField(row, "display_name") ?? fallback?.name ?? modelId, api: "openai-chatgpt-responses", baseUrl: OPENAI_CODEX_RESPONSES_BASE_URL, reasoning: (reasoningLevels?.length ?? 0) > 0 || fallback?.reasoning || false, input: resolveCodexModelInput(row, fallback), cost: fallback?.cost ?? OPENAI_UNKNOWN_MODEL_COST, contextWindow, maxTokens, ...contextTokens ?? fallback?.contextTokens ? { contextTokens: contextTokens ?? fallback?.contextTokens } : {}, ...fallback?.mediaInput ? { mediaInput: fallback.mediaInput } : {}, ...compat ? { compat } : {}, ...Object.keys(thinkingLevelMap).length > 0 ? { thinkingLevelMap } : {} }); } function buildOpenAICodexStaticProviderConfig() { return { baseUrl: OPENAI_CODEX_RESPONSES_BASE_URL, api: "openai-chatgpt-responses", auth: "oauth", models: OPENAI_MANIFEST_PROVIDER.models.flatMap((model) => { const modelId = normalizeLowercaseStringOrEmpty(model.id); if (isOpenAIPlatformOnlyRouteModelId(modelId)) return []; if (modelId === "gpt-6-astra" || modelId.startsWith("gpt-5.6") && modelId !== "gpt-5.6-sol") return []; return [normalizeOpenAICodexCatalogModel(model)]; }) }; } async function buildOpenAICodexLiveProviderConfig(params) { try { const models = (await getCachedLiveProviderModelRows({ providerId: PROVIDER_ID, endpoint: OPENAI_CODEX_MODELS_ENDPOINT, discoveryApiKey: params.discoveryApiKey, fetchGuard: params.fetchGuard, signal: params.signal, ttlMs: OPENAI_CODEX_MODELS_CACHE_TTL_MS, auditContext: "openai-model-discovery", readRows: readCodexModelRows, buildRequestHeaders: ({ discoveryApiKey }) => ({ Accept: "application/json", ...discoveryApiKey ? { Authorization: `Bearer ${discoveryApiKey}` } : {}, ...params.accountId ? { "ChatGPT-Account-ID": params.accountId } : {} }), cacheKeyParts: [ PROVIDER_ID, "codex-model-rows", OPENAI_CODEX_MODELS_ENDPOINT, params.discoveryApiKey, params.accountId ?? "" ] })).map(buildOpenAICodexModelFromLiveRow).filter((model) => Boolean(model)); return { provider: { baseUrl: OPENAI_CODEX_RESPONSES_BASE_URL, api: "openai-chatgpt-responses", auth: "oauth", models }, outcome: { provider: PROVIDER_ID, status: "ready" } }; } catch (error) { if (error instanceof LiveModelCatalogHttpError && (error.status === 401 || error.status === 403)) return { provider: { ...buildOpenAICodexStaticProviderConfig(), models: [] }, outcome: { provider: PROVIDER_ID, status: "auth-rejected" } }; } return { provider: buildOpenAICodexStaticProviderConfig(), outcome: { provider: PROVIDER_ID, status: "unavailable" } }; } function isCodexCatalogAuthMode(mode) { return mode === "oauth" || mode === "token"; } function resolveOpenAICatalogRejectionScope(auth) { return auth.apiKey && !auth.discoveryApiKey && isNonSecretApiKeyMarker(auth.apiKey) ? "catalog" : void 0; } function resolveOpenAICatalogBaseUrl(ctx) { const configuredProvider = Object.entries(ctx.config?.models?.providers ?? {}).find(([providerId]) => normalizeProviderId(providerId) === PROVIDER_ID)?.[1]; return normalizeOptionalString(configuredProvider?.baseUrl) ?? resolveOpenAIDefaultBaseUrl(ctx.env ?? process.env); } function shouldUseOpenAIResponsesTransport(params) { if (params.api !== "openai-completions") return false; const isOwnerProvider = normalizeProviderId(params.provider) === PROVIDER_ID; const isPlatformEndpoint = typeof params.baseUrl === "string" && classifyOpenAIBaseUrl(params.baseUrl) === "platform"; if (isOwnerProvider) { if (resolveAuthoredOpenAICompletionsRoute(params)) return false; return !params.baseUrl || isPlatformEndpoint; } return isPlatformEndpoint; } /** Resolves the effective authored OpenAI config route for one model. */ function resolveAuthoredOpenAIConfigRoute(params) { const providerConfig = resolveAuthoredOpenAIProviderConfig(params); if (!providerConfig) return; const modelId = normalizeOpenAIModelRouteId(params.modelId); let modelConfig; for (const model of providerConfig.models ?? []) { if (normalizeOpenAIModelRouteId(model.id) !== modelId) continue; modelConfig = modelConfig ? { ...model, ...modelConfig } : model; } return { ...modelConfig ? { configuredModel: modelConfig } : {}, configuredProvider: providerConfig }; } /** Authored Completions is a current transport contract; only catalog defaults are upgraded. */ function resolveAuthoredOpenAICompletionsRoute(params) { const configuredRoute = resolveAuthoredOpenAIConfigRoute(params); if (!configuredRoute) return; if ((normalizeOptionalString(configuredRoute.configuredModel?.api) ?? normalizeOptionalString(configuredRoute.configuredProvider.api)) !== "openai-completions") return; return { api: "openai-completions", baseUrl: normalizeOptionalString(configuredRoute.configuredModel?.baseUrl) ?? normalizeOptionalString(configuredRoute.configuredProvider.baseUrl) ?? resolveOpenAIDefaultBaseUrl(process.env) }; } function isOpenAIProvider(provider) { return normalizeProviderId(provider ?? "") === PROVIDER_ID; } function normalizeOpenAITransport(model, context) { if (!shouldUseOpenAIResponsesTransport({ provider: model.provider, modelId: context?.modelId, api: model.api, baseUrl: model.baseUrl, config: context?.config })) return model; return { ...model, api: "openai-responses" }; } function shouldUseCodexResponsesHooks(params) { if (params.api === "openai-chatgpt-responses") return true; return typeof params.baseUrl === "string" && isOpenAICodexBaseUrl(params.baseUrl); } function resolveConfiguredProviderAuthTransport(providerConfig) { const authMode = providerConfig?.auth; if (authMode === "oauth" || authMode === "token") return "codex"; if (authMode === "api-key") return "responses"; } function shouldResolveDynamicModelThroughCodex(ctx) { if (shouldUseCodexResponsesHooks({ provider: ctx.provider, api: ctx.providerConfig?.api, baseUrl: ctx.providerConfig?.baseUrl })) return true; if (ctx.providerConfig?.api === "openai-responses" || ctx.providerConfig?.api === "openai-completions" || ctx.providerConfig?.baseUrl && !isOpenAICodexBaseUrl(ctx.providerConfig.baseUrl)) return false; if (isOpenAIPlatformOnlyRouteModelId(ctx.modelId)) return false; if (isOpenAISubscriptionOnlyRouteModelId(ctx.modelId)) return true; return ctx.agentRuntimeId === "codex"; } function buildOpenAIUnknownModelHint(modelId) { if (normalizeLowercaseStringOrEmpty(modelId) !== "gpt-5.3-codex-spark") return; return "gpt-5.3-codex-spark is available only through ChatGPT/Codex OAuth. Run `openclaw models auth login --provider openai` and use openai/gpt-5.3-codex-spark with that OAuth profile; OpenAI API-key auth cannot use this model."; } const OPENAI_GPT_FORWARD_COMPAT_CASES = [ { match: [OPENAI_GPT_6_ASTRA_MODEL_ID], templateIds: [OPENAI_GPT_56_SOL_MODEL_ID, OPENAI_GPT_55_MODEL_ID] }, { match: [OPENAI_CHAT_LATEST_MODEL_ID], templateIds: OPENAI_CHAT_LATEST_TEMPLATE_MODEL_IDS, patch: { reasoning: false, cost: OPENAI_CHAT_LATEST_COST, contextWindow: 4e5 } }, { match: [ OPENAI_GPT_56_MODEL_ID, OPENAI_GPT_56_SOL_MODEL_ID, OPENAI_GPT_56_TERRA_MODEL_ID, OPENAI_GPT_56_LUNA_MODEL_ID ], templateIds: OPENAI_GPT_56_TEMPLATE_MODEL_IDS }, { match: [OPENAI_GPT_55_MODEL_ID], templateIds: [OPENAI_GPT_55_MODEL_ID, OPENAI_GPT_54_MODEL_ID] }, { match: [OPENAI_GPT_55_PRO_MODEL_ID], templateIds: OPENAI_GPT_55_PRO_TEMPLATE_MODEL_IDS }, { match: [OPENAI_GPT_54_MODEL_ID], templateIds: OPENAI_GPT_54_TEMPLATE_MODEL_IDS }, { match: [OPENAI_GPT_54_PRO_MODEL_ID], templateIds: OPENAI_GPT_54_PRO_TEMPLATE_MODEL_IDS }, { match: [OPENAI_GPT_54_MINI_MODEL_ID], templateIds: OPENAI_GPT_54_MINI_TEMPLATE_MODEL_IDS }, { match: [OPENAI_GPT_54_NANO_MODEL_ID], templateIds: OPENAI_GPT_54_NANO_TEMPLATE_MODEL_IDS } ]; function resolveOpenAIGptForwardCompatModel(ctx) { const trimmedModelId = ctx.modelId.trim(); const modelId = normalizeLowercaseStringOrEmpty(trimmedModelId); const exactModel = ctx.modelRegistry.find(PROVIDER_ID, trimmedModelId); if (modelId === "gpt-6-astra" || modelId === "gpt-5.6-sol" || modelId === "gpt-5.6-terra" || modelId === "gpt-5.6-luna") { if (exactModel) return exactModel; } const catalogModel = OPENAI_MANIFEST_PROVIDER.models.find(({ id }) => id === (modelId === "gpt-5.6" ? OPENAI_GPT_56_SOL_MODEL_ID : modelId)); return resolveFamilyForwardCompatModel({ providerId: PROVIDER_ID, ctx, cases: OPENAI_GPT_FORWARD_COMPAT_CASES, patch: { reasoning: true, maxTokens: OPENAI_GPT_54_MAX_TOKENS, ...catalogModel, id: trimmedModelId, name: trimmedModelId, api: "openai-responses", provider: PROVIDER_ID, baseUrl: resolveOpenAIDefaultBaseUrl(), input: catalogModel ? catalogModel.input.filter((item) => item === "text" || item === "image") : ["text", "image"], compat: exactModel ? exactModel.compat : catalogModel?.compat }, synthesize: true }); } function buildOpenAIProvider() { const providerDefinition = createOpenAIProvider(); const apiKeyDefinition = providerDefinition.auth.find((method) => method.id === "api-key"); if (!apiKeyDefinition) throw new Error("OpenAI provider contract is missing API-key auth"); const chatGPTAuthRuns = buildOpenAIChatGPTAuthMethodRuns(); const apiKeyRuntime = createProviderApiKeyAuthMethod({ providerId: PROVIDER_ID, methodId: apiKeyDefinition.id, label: apiKeyDefinition.label, hint: apiKeyDefinition.hint, optionKey: "openaiApiKey", flagName: "--openai-api-key", envVar: "OPENAI_API_KEY", promptMessage: "Enter OpenAI API key", profileId: "openai:api-key", defaultModel: OPENAI_DEFAULT_MODEL, preserveExistingPrimary: true, expectedProviders: [PROVIDER_ID], applyConfig: (cfg) => applyOpenAIConfig(cfg), wizard: apiKeyDefinition.wizard }); for (const method of providerDefinition.auth) { if (method.id === "oauth" || method.id === "device-code") { method.run = chatGPTAuthRuns[method.id]; continue; } if (method.id !== "api-key") throw new Error(`OpenAI provider contract has unknown auth method: ${method.id}`); method.starterModel = apiKeyRuntime.starterModel; method.run = apiKeyRuntime.run; method.runNonInteractive = apiKeyRuntime.runNonInteractive; method.validateNonInteractive = apiKeyRuntime.validateNonInteractive; } const codexHooks = buildOpenAICodexProviderHooks(); const nativeResponsesHooks = buildOpenAIResponsesProviderHooks(); const responsesHooks = buildOpenAIResponsesProviderHooks({ transport: "sse" }); return { ...providerDefinition, catalog: { order: "simple", run: async (ctx) => { if (ctx.providerIds && !ctx.providerIds.includes(PROVIDER_ID)) return null; const auth = ctx.resolveProviderAuth(PROVIDER_ID); try { const { resolveApiKeyForProvider, resolveProviderAuthProfileMetadata } = await import("./plugin-sdk/provider-auth-runtime.js"); const runtimeAuth = await resolveApiKeyForProvider({ provider: PROVIDER_ID, cfg: ctx.config, ...ctx.agentDir ? { agentDir: ctx.agentDir } : {}, ...ctx.workspaceDir ? { workspaceDir: ctx.workspaceDir } : {}, ...auth.profileId ? { profileId: auth.profileId, lockedProfile: true } : {} }); if (runtimeAuth && isCodexCatalogAuthMode(runtimeAuth.mode) && runtimeAuth.apiKey) { const metadata = resolveProviderAuthProfileMetadata({ provider: PROVIDER_ID, cfg: ctx.config, ...ctx.agentDir ? { agentDir: ctx.agentDir } : {}, ...runtimeAuth.profileId ?? auth.profileId ? { profileId: runtimeAuth.profileId ?? auth.profileId } : {} }); const catalog = scopeOpenAICatalogOutcome(await buildOpenAICodexLiveProviderConfig({ discoveryApiKey: runtimeAuth.apiKey, accountId: metadata.accountId }), runtimeAuth.profileId ?? auth.profileId); return { providers: { [PROVIDER_ID]: catalog.provider }, ...catalog.outcome ? { outcomes: [catalog.outcome] } : {} }; } } catch {} if (auth.mode === "api_key" && auth.apiKey) { const catalog = scopeOpenAICatalogOutcome(await buildOpenAILiveProviderConfig({ apiKey: auth.apiKey, baseUrl: resolveOpenAICatalogBaseUrl(ctx), discoveryApiKey: auth.discoveryApiKey, rejectionScope: resolveOpenAICatalogRejectionScope(auth) }), auth.profileId); return { providers: { [PROVIDER_ID]: catalog.provider }, ...catalog.outcome ? { outcomes: [catalog.outcome] } : {} }; } const apiKey = ctx.resolveProviderApiKey(PROVIDER_ID); if (!apiKey.apiKey) return null; const catalog = await buildOpenAILiveProviderConfig({ apiKey: apiKey.apiKey, baseUrl: resolveOpenAICatalogBaseUrl(ctx), discoveryApiKey: apiKey.discoveryApiKey, rejectionScope: resolveOpenAICatalogRejectionScope(apiKey) }); return { providers: { [PROVIDER_ID]: catalog.provider }, ...catalog.outcome ? { outcomes: [catalog.outcome] } : {} }; } }, staticCatalog: { order: "simple", run: async () => ({ providers: { [PROVIDER_ID]: OPENAI_MANIFEST_PROVIDER } }) }, resolveDynamicModel: (ctx) => shouldResolveDynamicModelThroughCodex(ctx) ? codexHooks.resolveDynamicModel?.(ctx) : resolveOpenAIGptForwardCompatModel(ctx), preferRuntimeResolvedModel: (ctx) => codexHooks.preferRuntimeResolvedModel?.(ctx) ?? false, normalizeResolvedModel: (ctx) => { if (!isOpenAIProvider(ctx.provider)) return; const authoredCompletionsRoute = resolveAuthoredOpenAICompletionsRoute(ctx); if (authoredCompletionsRoute) return { ...ctx.model, ...authoredCompletionsRoute }; if (shouldUseCodexResponsesHooks({ provider: ctx.provider, api: ctx.model.api, baseUrl: ctx.model.baseUrl })) return codexHooks.normalizeResolvedModel?.(ctx); return normalizeOpenAITransport(ctx.model, ctx); }, normalizeTransport: (ctx) => { const authoredCompletionsRoute = resolveAuthoredOpenAICompletionsRoute(ctx); if (authoredCompletionsRoute) return ctx.api === authoredCompletionsRoute.api && ctx.baseUrl === authoredCompletionsRoute.baseUrl ? void 0 : authoredCompletionsRoute; if (shouldUseCodexResponsesHooks(ctx)) return codexHooks.normalizeTransport?.(ctx); return shouldUseOpenAIResponsesTransport(ctx) ? { api: "openai-responses", baseUrl: ctx.baseUrl } : void 0; }, ...responsesHooks, prepareExtraParams: (ctx) => { const providerConfig = ctx.config?.models?.providers?.[PROVIDER_ID]; return (shouldUseCodexResponsesHooks({ provider: ctx.provider, api: ctx.model?.api, baseUrl: ctx.model?.baseUrl }) || normalizeProviderId(ctx.provider) === PROVIDER_ID && (!providerConfig?.baseUrl || isOpenAIHttpsApiBaseUrl(providerConfig.baseUrl)) && resolveConfiguredProviderAuthTransport(providerConfig) === "codex" ? nativeResponsesHooks : responsesHooks).prepareExtraParams?.(ctx); }, resolveUsageAuth: codexHooks.resolveUsageAuth, fetchUsageSnapshot: codexHooks.fetchUsageSnapshot, refreshOAuth: codexHooks.refreshOAuth, buildUnknownModelHint: ({ modelId }) => buildOpenAIUnknownModelHint(modelId), buildMissingAuthMessage: (ctx) => { if (normalizeProviderId(ctx.provider) !== PROVIDER_ID) return; if (ctx.listProfileIds(PROVIDER_ID).length === 0) return; return `No API key found for provider "openai". You are authenticated with OpenAI ChatGPT/Codex OAuth. Use ${OPENAI_CODEX_DEFAULT_MODEL} with the ChatGPT/Codex OAuth profile, or set OPENAI_API_KEY for direct OpenAI API access.`; }, matchesContextOverflowError: ({ errorMessage }) => /content_filter.*(?:prompt|input).*(?:too long|exceed)/i.test(errorMessage), classifyFailoverReason: ({ code }) => classifyOpenAiFailoverCode(code), resolveReasoningOutputMode: () => "native", resolveThinkingProfile: ({ provider, modelId, agentRuntime, api, compat }) => normalizeProviderId(provider) === PROVIDER_ID ? resolveUnifiedOpenAIThinkingProfile(modelId, agentRuntime, compat, api) : null, isModernModelRef: ({ modelId }) => matchesExactOrPrefix(modelId, OPENAI_PROVIDER_MODERN_MODEL_IDS), augmentModelCatalog: (ctx) => { const openAiGpt55ProTemplate = findCatalogTemplate({ entries: ctx.entries, providerId: PROVIDER_ID, templateIds: OPENAI_GPT_55_PRO_TEMPLATE_MODEL_IDS }); const openAiGpt54Template = findCatalogTemplate({ entries: ctx.entries, providerId: PROVIDER_ID, templateIds: OPENAI_GPT_54_TEMPLATE_MODEL_IDS }); const openAiGpt54ProTemplate = findCatalogTemplate({ entries: ctx.entries, providerId: PROVIDER_ID, templateIds: OPENAI_GPT_54_PRO_TEMPLATE_MODEL_IDS }); const openAiGpt54MiniTemplate = findCatalogTemplate({ entries: ctx.entries, providerId: PROVIDER_ID, templateIds: OPENAI_GPT_54_MINI_TEMPLATE_MODEL_IDS }); const openAiGpt54NanoTemplate = findCatalogTemplate({ entries: ctx.entries, providerId: PROVIDER_ID, templateIds: OPENAI_GPT_54_NANO_TEMPLATE_MODEL_IDS }); return [ buildOpenAISyntheticCatalogEntry(openAiGpt55ProTemplate, { id: OPENAI_GPT_55_PRO_MODEL_ID, reasoning: true, input: ["text", "image"], contextWindow: OPENAI_GPT_55_PRO_CONTEXT_WINDOW, contextTokens: OPENAI_DEFAULT_RUNTIME_CONTEXT_TOKENS }), buildOpenAISyntheticCatalogEntry(openAiGpt54Template, { id: OPENAI_GPT_54_MODEL_ID, reasoning: true, input: ["text", "image"], contextWindow: OPENAI_GPT_54_CONTEXT_TOKENS }), buildOpenAISyntheticCatalogEntry(openAiGpt54ProTemplate, { id: OPENAI_GPT_54_PRO_MODEL_ID, reasoning: true, input: ["text", "image"], contextWindow: OPENAI_GPT_54_PRO_CONTEXT_TOKENS }), buildOpenAISyntheticCatalogEntry(openAiGpt54MiniTemplate, { id: OPENAI_GPT_54_MINI_MODEL_ID, reasoning: true, input: ["text", "image"], contextWindow: OPENAI_GPT_54_MINI_CONTEXT_TOKENS }), buildOpenAISyntheticCatalogEntry(openAiGpt54NanoTemplate, { id: OPENAI_GPT_54_NANO_MODEL_ID, reasoning: true, input: ["text", "image"], contextWindow: OPENAI_GPT_54_NANO_CONTEXT_TOKENS }) ].filter((entry) => entry !== void 0); } }; } //#endregion export { buildOpenAIProvider as t };