openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
351 lines (350 loc) • 16.7 kB
JavaScript
import { l as normalizeOptionalString } from "./string-coerce-CIXf7egm.js";
import "./string-coerce-runtime-GQa0ehRA.js";
import { i as classifyOpenAIBaseUrl, n as OPENAI_CODEX_RESPONSES_BASE_URL, o as isOpenAICodexBaseUrl, t as OPENAI_API_BASE_URL } from "./base-url-DVAaAeRo.js";
import { b as isOpenAISubscriptionOnlyRouteModelId, v as isOpenAIDualRouteModelId, x as normalizeOpenAIModelRouteId, y as isOpenAIPlatformOnlyRouteModelId } from "./model-route-contract-pNgq17sM.js";
import { n as resolveUnifiedOpenAIThinkingProfile } from "./thinking-policy-C1E_gzmv.js";
//#region extensions/openai/provider-policy-api.ts
const OPENAI_RESPONSES_API = "openai-responses";
const OPENAI_COMPLETIONS_API = "openai-completions";
const OPENAI_CHATGPT_RESPONSES_API = "openai-chatgpt-responses";
const OPENAI_PROVIDER_ID = "openai";
const OPENAI_AGENT_RUNTIME_ID = "openclaw";
const CODEX_AGENT_RUNTIME_ID = "codex";
const OPENCLAW_RUNTIME_COMPATIBLE_IDS = [OPENAI_AGENT_RUNTIME_ID];
const CODEX_RUNTIME_COMPATIBLE_IDS = [OPENAI_AGENT_RUNTIME_ID, CODEX_AGENT_RUNTIME_ID];
function normalizeOptionalRouteApi(value) {
return normalizeOptionalString(value);
}
/** Canonical logical id for OpenAI catalog projection. */
function normalizeModelCatalogId(params) {
return params.provider.trim().toLowerCase() === OPENAI_PROVIDER_ID ? normalizeOpenAIModelRouteId(params.modelId) : null;
}
function isResponseModelEquivalent(params) {
return params.provider.trim().toLowerCase() === OPENAI_PROVIDER_ID && params.requestedModelId === "gpt-5.6" && params.responseModelId === "gpt-5.6-sol";
}
/** Resolves authored OpenAI provider config without activating the runtime plugin. */
function resolveAuthoredOpenAIProviderConfig(params) {
if (params.provider.trim().toLowerCase() !== OPENAI_PROVIDER_ID) return;
const providers = Object.entries(params.config?.models?.providers ?? {});
const requestedProvider = params.provider.trim();
const providerKey = providers.find(([providerId]) => providerId.trim() === requestedProvider)?.[0].trim() ?? providers.find(([providerId]) => providerId.trim().toLowerCase() === OPENAI_PROVIDER_ID)?.[0].trim();
let providerConfig;
for (const [providerId, candidate] of providers) {
if (providerId.trim() !== providerKey || !candidate) continue;
providerConfig = providerConfig ? {
...providerConfig,
...candidate,
models: candidate.models ?? providerConfig.models
} : candidate;
}
return providerConfig;
}
/**
* Skips full runtime loading only when OpenAI normalization is provably a no-op.
* Transport-sensitive routes and legacy model aliases still use the runtime hook.
*/
function projectConfiguredModelRow(ctx) {
if (ctx.provider.trim().toLowerCase() !== OPENAI_PROVIDER_ID) return;
const configuredApi = normalizeOptionalRouteApi(resolveAuthoredOpenAIProviderConfig(ctx)?.api);
const modelId = ctx.model.id;
const canonicalModelId = normalizeOpenAIModelRouteId(modelId);
const canonicalRouteId = normalizeOpenAIModelRouteId(ctx.modelId);
if (configuredApi !== void 0 && configuredApi !== OPENAI_RESPONSES_API || ctx.model.api !== OPENAI_RESPONSES_API || isOpenAICodexBaseUrl(ctx.model.baseUrl) || canonicalModelId !== modelId || canonicalRouteId !== ctx.modelId) return;
return null;
}
function firstRouteBaseUrl(...values) {
for (const value of values) {
if (typeof value === "string") {
if (value.trim()) return value.trim();
continue;
}
if (value !== void 0 && value !== null) return value;
}
}
function concreteBaseUrl(value, fallback) {
return normalizeOptionalString(value) ?? fallback;
}
function resolveOpenAIEnvironmentBaseUrl(context) {
return (context.env ?? process.env).OPENAI_BASE_URL;
}
function isHttpBaseUrl(baseUrl) {
if (typeof baseUrl !== "string") return false;
try {
return new URL(baseUrl.trim()).protocol === "http:";
} catch {
return false;
}
}
function codexCanReproduceRoute(candidate, sourceBaseUrl = candidate.baseUrl) {
if (isHttpBaseUrl(sourceBaseUrl) || candidate.requestTransportOverrides === "present") return false;
const endpointKind = classifyOpenAIBaseUrl(candidate.baseUrl);
return candidate.api === OPENAI_RESPONSES_API && endpointKind === "platform" || candidate.api === OPENAI_CHATGPT_RESPONSES_API && endpointKind === "chatgpt";
}
function withRuntimePolicy(candidate, sourceBaseUrl = candidate.baseUrl) {
return {
...candidate,
runtimePolicy: { compatibleIds: codexCanReproduceRoute(candidate, sourceBaseUrl) ? CODEX_RUNTIME_COMPATIBLE_IDS : OPENCLAW_RUNTIME_COMPATIBLE_IDS }
};
}
function defaultRuntimeIdForRoute(candidate, sourceBaseUrl = candidate.baseUrl) {
return codexCanReproduceRoute(candidate, sourceBaseUrl) ? CODEX_AGENT_RUNTIME_ID : OPENAI_AGENT_RUNTIME_ID;
}
function route(candidate, sourceBaseUrl) {
const compatibleCandidate = withRuntimePolicy(candidate, sourceBaseUrl);
return {
kind: "routes",
routes: [compatibleCandidate],
defaultRuntimeId: defaultRuntimeIdForRoute(compatibleCandidate, sourceBaseUrl)
};
}
/**
* Resolves OpenAI transport policy in provider-default order.
*
* Candidate order is not credential order. Callers must honor a locked profile,
* provider auth, then auth.order before choosing a compatible candidate. Unknown
* models without route facts remain indeterminate until a catalog row is observed.
*/
function resolveSingleObservedModelRoute(context) {
if (context.provider.trim().toLowerCase() !== "openai") return {
kind: "incompatible",
code: "openai-route-provider-mismatch",
message: `OpenAI route policy cannot resolve provider ${context.provider || "(empty)"}.`
};
const modelApi = normalizeOptionalRouteApi(context.configuredModel?.api);
const requestTransportOverrides = context.requestTransportOverrides ?? "none";
const providerApi = normalizeOptionalRouteApi(context.configuredProvider?.api);
const modelBaseUrl = firstRouteBaseUrl(context.configuredModel?.baseUrl);
const providerBaseUrl = firstRouteBaseUrl(context.configuredProvider?.baseUrl);
const environmentBaseUrl = firstRouteBaseUrl(resolveOpenAIEnvironmentBaseUrl(context));
const observedApi = normalizeOptionalRouteApi(context.observed?.api);
const observedBaseUrl = firstRouteBaseUrl(context.observed?.baseUrl);
const hasObservedRoute = observedApi !== void 0 || observedBaseUrl !== void 0;
let effectiveApi;
let effectiveBaseUrl;
let configuredRoute = false;
let customDefaultApi = OPENAI_COMPLETIONS_API;
if (modelApi !== void 0 || modelBaseUrl !== void 0) {
configuredRoute = true;
effectiveApi = modelApi ?? providerApi;
effectiveBaseUrl = modelBaseUrl;
if (modelBaseUrl === void 0) {
const lowerBaseUrl = providerBaseUrl ?? environmentBaseUrl;
const lowerEndpointKind = classifyOpenAIBaseUrl(lowerBaseUrl);
effectiveBaseUrl = lowerEndpointKind === "custom" || lowerEndpointKind === "invalid" ? lowerBaseUrl : void 0;
}
} else if (providerApi !== void 0 || providerBaseUrl !== void 0) {
configuredRoute = true;
effectiveApi = providerApi;
effectiveBaseUrl = providerBaseUrl;
if (providerBaseUrl === void 0) {
const environmentEndpointKind = classifyOpenAIBaseUrl(environmentBaseUrl);
if (environmentEndpointKind === "custom" || environmentEndpointKind === "invalid") effectiveBaseUrl = environmentBaseUrl;
}
} else if (environmentBaseUrl !== void 0) {
configuredRoute = true;
effectiveBaseUrl = environmentBaseUrl;
customDefaultApi = OPENAI_RESPONSES_API;
} else {
effectiveApi = observedApi;
effectiveBaseUrl = observedBaseUrl;
}
const endpointKind = classifyOpenAIBaseUrl(effectiveBaseUrl);
if (endpointKind === "invalid") return {
kind: "incompatible",
code: "invalid-openai-base-url",
message: "OpenAI model route baseUrl must be a non-empty URL string."
};
const chatGPTApi = effectiveApi?.toLowerCase() === OPENAI_CHATGPT_RESPONSES_API;
const authoredChatGPTApi = modelApi?.toLowerCase() === OPENAI_CHATGPT_RESPONSES_API || providerApi?.toLowerCase() === OPENAI_CHATGPT_RESPONSES_API;
if (endpointKind === "custom") {
if (chatGPTApi && !authoredChatGPTApi) return {
kind: "incompatible",
code: "custom-chatgpt-relay-requires-configuration",
message: "Custom ChatGPT relays require an explicitly configured ChatGPT adapter."
};
const customApi = effectiveApi ?? (observedApi === OPENAI_RESPONSES_API || observedApi === OPENAI_COMPLETIONS_API ? observedApi : void 0) ?? customDefaultApi;
if (customApi !== OPENAI_RESPONSES_API && customApi !== OPENAI_COMPLETIONS_API && customApi !== OPENAI_CHATGPT_RESPONSES_API) return {
kind: "incompatible",
code: "unsupported-custom-openai-api",
message: `${customApi} is not an OpenAI-compatible model adapter.`
};
const customAuthRequirement = customApi.toLowerCase() === OPENAI_CHATGPT_RESPONSES_API ? "subscription" : "api-key";
return route({
api: customApi,
baseUrl: concreteBaseUrl(effectiveBaseUrl, OPENAI_API_BASE_URL),
authRequirement: customAuthRequirement,
requestTransportOverrides
}, effectiveBaseUrl);
}
if (endpointKind === "platform" && chatGPTApi || endpointKind === "chatgpt" && effectiveApi !== void 0 && !chatGPTApi) return {
kind: "incompatible",
code: "conflicting-official-openai-route",
message: "OpenAI model API and baseUrl select different official transports."
};
if (effectiveApi !== void 0 && effectiveApi !== OPENAI_RESPONSES_API && effectiveApi !== OPENAI_COMPLETIONS_API && effectiveApi !== OPENAI_CHATGPT_RESPONSES_API) return {
kind: "incompatible",
code: "unsupported-official-openai-api",
message: `${effectiveApi} is not an OpenAI Platform model adapter.`
};
const modelId = normalizeOpenAIModelRouteId(context.modelId);
const sourceBaseUrl = effectiveBaseUrl;
const platformRoute = withRuntimePolicy({
api: configuredRoute && effectiveApi === OPENAI_COMPLETIONS_API ? OPENAI_COMPLETIONS_API : OPENAI_RESPONSES_API,
baseUrl: classifyOpenAIBaseUrl(sourceBaseUrl) === "platform" && isHttpBaseUrl(sourceBaseUrl) ? concreteBaseUrl(sourceBaseUrl, OPENAI_API_BASE_URL) : OPENAI_API_BASE_URL,
authRequirement: "api-key",
requestTransportOverrides
}, sourceBaseUrl);
const chatGPTRoute = withRuntimePolicy({
api: OPENAI_CHATGPT_RESPONSES_API,
baseUrl: OPENAI_CODEX_RESPONSES_BASE_URL,
authRequirement: "subscription",
requestTransportOverrides
}, sourceBaseUrl);
const platformOnly = isOpenAIPlatformOnlyRouteModelId(modelId);
const subscriptionOnly = isOpenAISubscriptionOnlyRouteModelId(modelId);
const dualRoute = isOpenAIDualRouteModelId(modelId);
if (!configuredRoute) {
if (subscriptionOnly) return route(chatGPTRoute, sourceBaseUrl);
if (platformOnly) return route(platformRoute, sourceBaseUrl);
if (dualRoute) return {
kind: "routes",
defaultRuntimeId: defaultRuntimeIdForRoute(platformRoute, sourceBaseUrl),
routes: [platformRoute, chatGPTRoute]
};
}
if (endpointKind === "chatgpt" || chatGPTApi) {
if (platformOnly) return {
kind: "incompatible",
code: "platform-only-model-on-chatgpt",
message: `${modelId} is available only through OpenAI Platform API-key authentication.`
};
return route(chatGPTRoute, sourceBaseUrl);
}
if (subscriptionOnly) return {
kind: "incompatible",
code: "subscription-only-model-on-platform",
message: `${modelId} is available only through ChatGPT subscription authentication.`
};
if (!configuredRoute && !hasObservedRoute) return {
kind: "indeterminate",
defaultRuntimeId: requestTransportOverrides === "present" ? OPENAI_AGENT_RUNTIME_ID : CODEX_AGENT_RUNTIME_ID
};
return route(platformRoute, sourceBaseUrl);
}
function hasAuthoredRouteFacts(context) {
return normalizeOptionalRouteApi(context.configuredModel?.api) !== void 0 || firstRouteBaseUrl(context.configuredModel?.baseUrl) !== void 0 || normalizeOptionalRouteApi(context.configuredProvider?.api) !== void 0 || firstRouteBaseUrl(context.configuredProvider?.baseUrl) !== void 0 || firstRouteBaseUrl(resolveOpenAIEnvironmentBaseUrl(context)) !== void 0;
}
function authoredRouteNeedsObservedPlatformApi(context) {
if (normalizeOptionalRouteApi(context.configuredModel?.api) !== void 0 || normalizeOptionalRouteApi(context.configuredProvider?.api) !== void 0) return false;
const authoredBaseUrl = firstRouteBaseUrl(context.configuredModel?.baseUrl, context.configuredProvider?.baseUrl, resolveOpenAIEnvironmentBaseUrl(context));
return classifyOpenAIBaseUrl(authoredBaseUrl) === "custom";
}
function canonicalRouteCandidateBaseUrl(baseUrl) {
try {
const url = new URL(baseUrl);
url.pathname = url.pathname.replace(/\/+$/u, "") || "/";
return url.toString();
} catch {
return baseUrl;
}
}
function routeCandidateKey(candidate) {
return [
candidate.api,
canonicalRouteCandidateBaseUrl(candidate.baseUrl),
candidate.authRequirement,
candidate.requestTransportOverrides,
...candidate.runtimePolicy?.compatibleIds ?? []
].join("\0");
}
function compareRouteCandidates(a, b) {
const authOrder = (candidate) => candidate.authRequirement === "api-key" ? 0 : 1;
return authOrder(a) - authOrder(b) || a.api.localeCompare(b.api) || a.baseUrl.localeCompare(b.baseUrl);
}
function ambiguousObservedRouteGroup(message) {
return {
kind: "incompatible",
code: "ambiguous-openai-route-group",
message
};
}
function resolveAuthoredObservedFallback(observedRoutes) {
const platformApis = /* @__PURE__ */ new Set();
for (const observed of observedRoutes) {
const api = normalizeOptionalRouteApi(observed.api);
if (!api || api === OPENAI_CHATGPT_RESPONSES_API) continue;
if (api !== OPENAI_RESPONSES_API && api !== OPENAI_COMPLETIONS_API) return {
kind: "incompatible",
resolution: {
kind: "incompatible",
code: "unsupported-custom-openai-api",
message: `${api} is not an OpenAI-compatible model adapter.`
}
};
platformApis.add(api);
}
if (platformApis.size > 1) return {
kind: "incompatible",
resolution: ambiguousObservedRouteGroup("Observed OpenAI routes disagree on the Platform adapter for an authored endpoint.")
};
const api = [...platformApis][0];
return {
kind: "observed",
...api ? { route: { api } } : {}
};
}
/** Resolves every physical row for one logical OpenAI model in provider order. */
function resolveModelRoutes(context) {
const observedRoutes = (context.observedRoutes ?? []).filter((observed) => observed.api != null || observed.baseUrl != null);
if (hasAuthoredRouteFacts(context)) {
if (authoredRouteNeedsObservedPlatformApi(context)) {
const fallback = resolveAuthoredObservedFallback(observedRoutes);
if (fallback.kind === "incompatible") return fallback.resolution;
return resolveSingleObservedModelRoute({
...context,
observed: fallback.route
});
}
return resolveSingleObservedModelRoute(context);
}
if (observedRoutes.length <= 1) return resolveSingleObservedModelRoute({
...context,
observed: observedRoutes[0]
});
const resolutions = observedRoutes.map((observed) => resolveSingleObservedModelRoute({
...context,
observed
}));
const incompatible = resolutions.filter((resolution) => resolution.kind === "incompatible").toSorted((a, b) => a.code.localeCompare(b.code) || a.message.localeCompare(b.message))[0];
if (incompatible) return incompatible;
const routesByKey = /* @__PURE__ */ new Map();
for (const resolution of resolutions) {
if (resolution.kind !== "routes") continue;
for (const candidate of resolution.routes) {
const key = routeCandidateKey(candidate);
const existing = routesByKey.get(key);
if (!existing || candidate.baseUrl.localeCompare(existing.baseUrl) < 0) routesByKey.set(key, candidate);
}
}
const routes = [...routesByKey.values()].toSorted(compareRouteCandidates);
const authRequirements = new Set(routes.map((candidate) => candidate.authRequirement));
if (routes.length > authRequirements.size) return ambiguousObservedRouteGroup("Observed OpenAI routes contain multiple endpoints for the same authentication class.");
const firstRoute = routes[0];
if (!firstRoute) return resolveSingleObservedModelRoute(context);
return {
kind: "routes",
routes,
defaultRuntimeId: resolutions.some((resolution) => resolution.kind === "routes" && resolution.defaultRuntimeId === "openclaw") ? OPENAI_AGENT_RUNTIME_ID : defaultRuntimeIdForRoute(firstRoute)
};
}
function normalizeConfig(params) {
return params.providerConfig;
}
function resolveThinkingProfile(params) {
switch (params.provider.trim().toLowerCase()) {
case "openai": return resolveUnifiedOpenAIThinkingProfile(params.modelId, params.agentRuntime, params.compat, params.api);
default: return null;
}
}
//#endregion
export { resolveAuthoredOpenAIProviderConfig as a, projectConfiguredModelRow as i, normalizeConfig as n, resolveModelRoutes as o, normalizeModelCatalogId as r, resolveThinkingProfile as s, isResponseModelEquivalent as t };