openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
181 lines (180 loc) • 7.84 kB
JavaScript
import { d as asPositiveSafeInteger } from "./number-coercion-CLj0HTDM.js";
import { a as asOptionalRecord } from "./record-coerce-DItp3I4t.js";
import { l as normalizeOptionalString } from "./string-coerce-CIXf7egm.js";
import { n as filterStringEntries } from "./string-normalization-DsCfAx8q.js";
import { i as normalizeOpenRouterModelPricing } from "./model-catalog-pricing-icO2BZQe.js";
import { b as ssrfPolicyFromHttpBaseUrlAllowedOrigin } from "./ssrf-0QyXWOVG.js";
import { i as fetchWithSsrFGuard } from "./fetch-guard-BMdGQhbb.js";
import { l as normalizeBaseUrl, p as sanitizeConfiguredModelProviderRequest } from "./provider-request-config-DIOYidiO.js";
import { p as resolveProviderHttpRequestConfig } from "./shared-DCDpYrmy.js";
import "./string-coerce-runtime-GQa0ehRA.js";
import "./model-catalog-pricing-Sbg3LUp0.js";
import "./ssrf-runtime-Bum5C6NN.js";
import "./provider-http-k9RMI7iG.js";
import { n as buildLiveModelProviderConfig } from "./provider-catalog-live-runtime-EZZSw18v.js";
import { t as OPENROUTER_BASE_URL } from "./provider-defaults-ilQX6q3J.js";
//#region extensions/openrouter/provider-catalog.ts
const OPENROUTER_LEGACY_BASE_URL = "https://openrouter.ai/v1";
const OPENROUTER_MODELS_CACHE_TTL_MS = 6e4;
const OPENROUTER_DEFAULT_MODEL_ID = "openrouter/auto";
const OPENROUTER_DEFAULT_CONTEXT_WINDOW = 2e5;
const OPENROUTER_DEFAULT_MAX_TOKENS = 8192;
const OPENROUTER_DEFAULT_COST = {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0
};
const OPENROUTER_PROXY_REASONING_UNSUPPORTED_MODEL_IDS = /* @__PURE__ */ new Set(["openrouter/hunter-alpha"]);
const OPENROUTER_KIMI_K2_6_COST = {
input: .8,
output: 3.5,
cacheRead: .2,
cacheWrite: 0
};
const OPENROUTER_KIMI_K2_5_COST = {
input: .44,
output: 2,
cacheRead: .22,
cacheWrite: 0
};
function normalizeOpenRouterBaseUrl(baseUrl) {
const normalized = baseUrl?.trim().replace(/\/+$/, "");
if (!normalized) return;
if (normalized === "https://openrouter.ai/api/v1" || normalized === OPENROUTER_LEGACY_BASE_URL) return OPENROUTER_BASE_URL;
}
function resolveOpenRouterApiBaseUrl(baseUrl) {
const normalized = normalizeOpenRouterBaseUrl(baseUrl) ?? normalizeBaseUrl(baseUrl, "https://openrouter.ai/api/v1");
const parsed = URL.canParse(normalized) ? new URL(normalized) : void 0;
if (!parsed || parsed.protocol !== "http:" && parsed.protocol !== "https:" || parsed.username || parsed.password || parsed.search || parsed.hash) throw new Error("Invalid OpenRouter API base URL");
return normalized;
}
function resolveOpenRouterSsrfPolicy(requestConfig, request) {
return requestConfig.allowPrivateNetwork ? { allowPrivateNetwork: true } : request?.allowPrivateNetwork === false ? {} : ssrfPolicyFromHttpBaseUrlAllowedOrigin(requestConfig.baseUrl);
}
function isOpenRouterProxyReasoningUnsupportedModel(modelId) {
const normalized = (modelId ?? "").trim().toLowerCase();
if (!normalized) return false;
return OPENROUTER_PROXY_REASONING_UNSUPPORTED_MODEL_IDS.has(normalized) || normalized.startsWith("openrouter/hunter-alpha:");
}
function buildOpenrouterProvider() {
return {
baseUrl: OPENROUTER_BASE_URL,
api: "openai-completions",
models: [
{
id: OPENROUTER_DEFAULT_MODEL_ID,
name: "OpenRouter Auto",
reasoning: false,
input: ["text", "image"],
cost: OPENROUTER_DEFAULT_COST,
contextWindow: OPENROUTER_DEFAULT_CONTEXT_WINDOW,
maxTokens: OPENROUTER_DEFAULT_MAX_TOKENS
},
{
id: "moonshotai/kimi-k2.6",
name: "MoonshotAI: Kimi K2.6",
reasoning: true,
input: ["text", "image"],
cost: OPENROUTER_KIMI_K2_6_COST,
contextWindow: 262144,
maxTokens: 262144
},
{
id: "moonshotai/kimi-k2.5",
name: "MoonshotAI: Kimi K2.5",
reasoning: true,
input: ["text", "image"],
cost: OPENROUTER_KIMI_K2_5_COST,
contextWindow: 262144,
maxTokens: 262144
}
]
};
}
function readStringArray(record, key) {
return filterStringEntries(record?.[key]);
}
function readOpenRouterModalities(architecture, direction) {
const explicit = readStringArray(architecture, `${direction}_modalities`);
if (explicit.length > 0) return explicit;
const modality = normalizeOptionalString(architecture?.modality);
if (!modality) return [];
const [input = "", output = ""] = modality.split("->", 2);
return (direction === "input" ? input : output).split("+").filter(Boolean);
}
function buildOpenRouterLiveModel(row) {
const record = asOptionalRecord(row);
const id = normalizeOptionalString(record?.id);
const architecture = asOptionalRecord(record?.architecture);
const outputModalities = readOpenRouterModalities(architecture, "output");
if (!id || outputModalities.length > 0 && !outputModalities.includes("text")) return;
const inputModalities = readOpenRouterModalities(architecture, "input");
const supportedParameters = readStringArray(record, "supported_parameters");
const topProvider = asOptionalRecord(record?.top_provider);
return {
id,
name: normalizeOptionalString(record?.name) ?? id,
reasoning: supportedParameters.includes("reasoning") || supportedParameters.includes("include_reasoning"),
input: inputModalities.includes("image") ? ["text", "image"] : ["text"],
cost: normalizeOpenRouterModelPricing(record?.pricing) ?? { ...OPENROUTER_DEFAULT_COST },
contextWindow: asPositiveSafeInteger(topProvider?.context_length) ?? asPositiveSafeInteger(record?.context_length) ?? OPENROUTER_DEFAULT_CONTEXT_WINDOW,
maxTokens: asPositiveSafeInteger(topProvider?.max_completion_tokens) ?? asPositiveSafeInteger(record?.max_completion_tokens) ?? asPositiveSafeInteger(record?.max_output_tokens) ?? OPENROUTER_DEFAULT_MAX_TOKENS
};
}
async function buildOpenrouterLiveProvider(params) {
const fallback = buildOpenrouterProvider();
const baseUrl = resolveOpenRouterApiBaseUrl(params.baseUrl);
const request = sanitizeConfiguredModelProviderRequest(params.request);
const resolveRequest = (apiKey) => resolveProviderHttpRequestConfig({
provider: "openrouter",
capability: "llm",
baseUrl,
defaultBaseUrl: OPENROUTER_BASE_URL,
defaultHeaders: {
Accept: "application/json",
...apiKey ? { Authorization: `Bearer ${apiKey}` } : {}
},
request
});
const requestConfig = resolveRequest();
const endpoint = `${requestConfig.baseUrl}/models`;
return await buildLiveModelProviderConfig({
providerId: "openrouter",
endpoint,
providerConfig: {
baseUrl: requestConfig.baseUrl,
api: fallback.api,
...params.request ? { request: params.request } : {}
},
models: fallback.models,
apiKey: params.apiKey,
discoveryApiKey: params.discoveryApiKey,
fetchGuard: async (fetchParams) => await (params.fetchGuard ?? fetchWithSsrFGuard)({
...fetchParams,
...requestConfig.dispatcherPolicy ? { dispatcherPolicy: requestConfig.dispatcherPolicy } : {}
}),
signal: params.signal,
ttlMs: OPENROUTER_MODELS_CACHE_TTL_MS,
auditContext: "openrouter-model-discovery",
policy: resolveOpenRouterSsrfPolicy(requestConfig, params.request),
cacheKeyParts: [
"openrouter",
"model-rows",
endpoint,
params.discoveryApiKey ?? params.apiKey,
request ?? null
],
buildRequestHeaders: ({ apiKey, discoveryApiKey }) => resolveRequest(discoveryApiKey ?? apiKey).headers,
projectRows: (rows, fallbackProvider) => {
const liveModels = rows.flatMap((row) => {
const model = buildOpenRouterLiveModel(row);
return model ? [model] : [];
});
if (liveModels.length === 0) return [];
return [...new Map([...fallbackProvider.models, ...liveModels].map((model) => [model.id, model])).values()].toSorted((a, b) => a.id.localeCompare(b.id));
}
});
}
//#endregion
export { resolveOpenRouterApiBaseUrl as a, normalizeOpenRouterBaseUrl as i, buildOpenrouterProvider as n, resolveOpenRouterSsrfPolicy as o, isOpenRouterProxyReasoningUnsupportedModel as r, buildOpenrouterLiveProvider as t };