openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
340 lines (339 loc) • 16.1 kB
JavaScript
import { a as asOptionalRecord } from "../../record-coerce-TIsquoVR.mjs";
import { r as truncateUtf16Safe } from "../../utf16-slice-D_ngcYKd.mjs";
import { n as findNormalizedProviderValue } from "../../provider-id-D1VImdKk.mjs";
import { r as resolveAgentConfig } from "../../agent-scope-config-BEk6-ThS.mjs";
import "../../defaults-Jag53zwP.mjs";
import "../../string-coerce-runtime-BbIJhy7G.mjs";
import "../../text-utility-runtime-BB0hWeEg.mjs";
import { o as buildPassthroughGeminiSanitizingReplayPolicy } from "../../provider-replay-helpers-B0jVS6BC.mjs";
import "../../agent-scope-runtime-OJ73CMLc.mjs";
import "../../provider-model-shared-CFGX5pNa.mjs";
import "../../provider-auth-CjrRHalO.mjs";
import { h as runLiveProviderCatalog } from "../../provider-catalog-live-runtime-BhDUKOgi.mjs";
import { t as defineSingleProviderPluginEntry } from "../../provider-entry-Z3Qh1Ao0.mjs";
import { a as loadOpenRouterModelCapabilities, r as getLoadedOpenRouterModelCapabilities } from "../../provider-stream-BeFr0nEc.mjs";
import "../../provider-stream-family-ClRPt3YW.mjs";
import "../../provider-defaults-ilQX6q3J.mjs";
import { a as resolveOpenRouterApiBaseUrl, i as normalizeOpenRouterBaseUrl, n as buildOpenrouterProvider, r as isOpenRouterProxyReasoningUnsupportedModel, t as buildOpenrouterLiveProvider } from "../../provider-catalog-CI1CSYQf.mjs";
import { t as buildOpenRouterImageGenerationProvider } from "../../image-generation-provider-qy1T_YSH.mjs";
import { t as openrouterMediaUnderstandingProvider } from "../../media-understanding-provider-CPEgWgEe.mjs";
import { i as normalizeOpenRouterModelFamilyId, n as isOpenRouterMistralModelId, r as normalizeOpenRouterApiModelId } from "../../models-CrlfkNW6.mjs";
import { t as buildOpenRouterMusicGenerationProvider } from "../../music-generation-provider-B1s9L4-l.mjs";
import { n as applyOpenrouterConfig, t as OPENROUTER_DEFAULT_MODEL_REF } from "../../onboard-aPmej6ij.mjs";
import { t as createOpenRouterOAuthAuthMethod } from "../../oauth-BKm2rAhF.mjs";
import { t as resolveOpenRouterExtraParamsForTransport } from "../../provider-routing-CgfUYlmZ.mjs";
import { t as buildOpenRouterSpeechProvider } from "../../speech-provider-CTH6KnXP.mjs";
import { t as wrapOpenRouterProviderStream } from "../../stream-Lc5Q9_pR.mjs";
import { t as resolveOpenRouterThinkingProfile } from "../../thinking-policy-PmomRQP7.mjs";
import { n as normalizeOpenRouterToolSchemas, t as inspectOpenRouterToolSchemas } from "../../tool-schemas-CuucG5hz.mjs";
import { t as fetchOpenRouterUsage } from "../../usage-BuVTAXUr.mjs";
import { t as listOpenRouterVideoModelCatalog } from "../../video-model-catalog-Du3peXap.mjs";
import { t as buildOpenRouterVideoGenerationProvider } from "../../video-generation-provider-BxkA5zD5.mjs";
//#region extensions/openrouter/openclaw.plugin.json
var openclaw_plugin_default = {
id: "openrouter",
categories: ["models"],
capabilityCatalogEntry: "./capability-catalog.ts",
activation: { "onStartup": false },
enabledByDefault: true,
providers: ["openrouter"],
modelCatalog: { "discovery": { "openrouter": "runtime" } },
modelIdNormalization: { "providers": { "openrouter": { "prefixWhenBare": "openrouter" } } },
modelPricing: { "providers": { "openrouter": {
"openRouter": { "provider": "openrouter" },
"modelsDev": false,
"liteLLM": false
} } },
providerEndpoints: [{
"endpointClass": "openrouter",
"hostSuffixes": ["openrouter.ai"]
}],
providerRequest: { "providers": { "openrouter": { "family": "openrouter" } } },
setup: { "providers": [{
"id": "openrouter",
"envVars": ["OPENROUTER_API_KEY"]
}] },
providerAuthChoices: [{
"provider": "openrouter",
"method": "api-key",
"choiceId": "openrouter-api-key",
"appGuidedSecret": true,
"choiceLabel": "OpenRouter API key",
"groupId": "openrouter",
"groupLabel": "OpenRouter",
"groupHint": "OAuth or API key",
"onboardingScopes": ["text-inference", "music-generation"],
"optionKey": "openrouterApiKey",
"cliFlag": "--openrouter-api-key",
"cliOption": "--openrouter-api-key <key>",
"cliDescription": "OpenRouter API key"
}, {
"provider": "openrouter",
"method": "oauth",
"choiceId": "openrouter-oauth",
"credentialOnly": true,
"appGuidedAuth": "oauth",
"channelLogin": {},
"choiceLabel": "OpenRouter OAuth",
"choiceHint": "Browser sign-in",
"groupId": "openrouter",
"groupLabel": "OpenRouter",
"groupHint": "OAuth or API key",
"onboardingScopes": ["text-inference", "music-generation"],
"onboardingFeatured": true
}],
contracts: {
"mediaUnderstandingProviders": ["openrouter"],
"imageGenerationProviders": ["openrouter"],
"musicGenerationProviders": ["openrouter"],
"videoGenerationProviders": ["openrouter"],
"speechProviders": ["openrouter"],
"usageProviders": ["openrouter"]
},
mediaUnderstandingProviderMetadata: { "openrouter": {
"capabilities": ["image", "audio"],
"defaultModels": {
"image": "auto",
"audio": "openai/whisper-large-v3-turbo"
},
"autoPriority": { "audio": 35 }
} },
configSchema: {
"type": "object",
"additionalProperties": false,
"properties": {}
}
};
//#endregion
//#region extensions/openrouter/index.ts
const PROVIDER_ID = "openrouter";
const OPENROUTER_DEFAULT_MAX_TOKENS = 8192;
const OPENROUTER_FUSION_MODEL_ID = "openrouter/fusion";
const OPENROUTER_CACHE_TTL_MODEL_FAMILY = /^(?:(?:anthropic|deepseek|moonshot(?:ai)?|x-ai|z-?ai)\/|google\/gemini-(?:2\.5|3))/;
const MAX_PROMPT_MODEL_ID_DISPLAY_CHARS = 256;
function isOpenRouterCatalogRoute(route) {
return (route.api == null || route.api === "openai-completions") && (route.baseUrl == null || route.baseUrl === "https://openrouter.ai/api/v1");
}
function withOpenRouterCatalogThinking(ctx) {
if (ctx.thinkingLevelMap || ctx.compat?.supportsReasoningEffort !== void 0 || ctx.compat?.supportedReasoningEfforts !== void 0 || !isOpenRouterCatalogRoute(ctx)) return ctx;
const capabilities = getLoadedOpenRouterModelCapabilities(normalizeOpenRouterApiModelId(ctx.modelId) ?? ctx.modelId);
if (!capabilities?.compat && !capabilities?.thinkingLevelMap) return ctx;
return {
...ctx,
compat: {
...capabilities.compat,
...ctx.compat
},
...capabilities.thinkingLevelMap ? { thinkingLevelMap: capabilities.thinkingLevelMap } : {}
};
}
function normalizeOpenRouterResolvedModel(model) {
const normalizedBaseUrl = normalizeOpenRouterBaseUrl(model.baseUrl);
const normalizedId = normalizeOpenRouterApiModelId(model.id);
const reasoning = isOpenRouterProxyReasoningUnsupportedModel(model.id) ? false : model.reasoning;
if ((!normalizedBaseUrl || normalizedBaseUrl === model.baseUrl) && (!normalizedId || normalizedId === model.id) && reasoning === model.reasoning) return;
return {
...model,
...normalizedId ? { id: normalizedId } : {},
...normalizedBaseUrl ? { baseUrl: normalizedBaseUrl } : {},
reasoning
};
}
function sanitizePromptModelId(value) {
if (typeof value !== "string") return;
return truncateUtf16Safe(value.replace(/[\p{Cc}\u2028\u2029]/gu, "").trim(), MAX_PROMPT_MODEL_ID_DISPLAY_CHARS) || void 0;
}
function openRouterModelConfigKey(modelId) {
const providerPrefix = `${PROVIDER_ID}/`;
return modelId.trim().toLowerCase().startsWith(providerPrefix) ? modelId : `${PROVIDER_ID}/${modelId}`;
}
function findConfiguredOpenRouterModelParams(ctx) {
const configuredModels = ctx.config?.agents?.defaults?.models;
if (!configuredModels) return;
const normalizedModelId = normalizeOpenRouterApiModelId(ctx.modelId) ?? ctx.modelId;
const directKeys = [
openRouterModelConfigKey(ctx.modelId),
openRouterModelConfigKey(normalizedModelId),
`${PROVIDER_ID}/${ctx.modelId}`,
`${PROVIDER_ID}/${normalizedModelId}`
];
for (const key of directKeys) {
const params = asOptionalRecord(configuredModels[key]?.params);
if (params) return params;
}
for (const [rawKey, entry] of Object.entries(configuredModels)) {
const slashIndex = rawKey.indexOf("/");
if (slashIndex <= 0) continue;
const provider = rawKey.slice(0, slashIndex).trim().toLowerCase();
const modelId = rawKey.slice(slashIndex + 1);
const candidateModelId = normalizeOpenRouterApiModelId(modelId) ?? modelId;
if (provider === PROVIDER_ID && candidateModelId.trim().toLowerCase() === normalizedModelId.trim().toLowerCase()) return asOptionalRecord(entry.params);
}
}
function resolveFusionExtraBody(ctx) {
const params = {
...asOptionalRecord(ctx.config?.agents?.defaults?.params),
...findConfiguredOpenRouterModelParams(ctx),
...ctx.agentId ? asOptionalRecord(resolveAgentConfig(ctx.config ?? {}, ctx.agentId)?.params) : {}
};
if (Object.keys(params).length === 0) return;
return asOptionalRecord(Object.hasOwn(params, "extra_body") ? params.extra_body : params.extraBody);
}
function resolveOpenRouterFusionPromptContribution(ctx) {
if ((normalizeOpenRouterApiModelId(ctx.modelId) ?? ctx.modelId) !== OPENROUTER_FUSION_MODEL_ID) return;
const extraBody = resolveFusionExtraBody(ctx);
const fusionPlugin = Array.isArray(extraBody?.plugins) ? extraBody.plugins.map(asOptionalRecord).find((plugin) => plugin?.id === "fusion") : void 0;
if (!fusionPlugin || fusionPlugin.enabled === false) return;
const analysisModels = Array.isArray(fusionPlugin.analysis_models) ? fusionPlugin.analysis_models.map(sanitizePromptModelId).filter((model) => Boolean(model)) : [];
const finalModel = sanitizePromptModelId(fusionPlugin.model);
const lines = [
"## OpenRouter Fusion Configuration",
"The active OpenRouter Fusion request is configured with these non-secret Fusion plugin fields.",
analysisModels.length > 0 ? `Analysis models: ${analysisModels.join(", ")}.` : void 0,
finalModel ? `Final Fusion model: ${finalModel}.` : void 0
].filter((line) => Boolean(line));
return lines.length > 2 ? { dynamicSuffix: lines.join("\n") } : void 0;
}
var openrouter_default = defineSingleProviderPluginEntry({
id: "openrouter",
name: "OpenRouter Provider",
description: "Bundled OpenRouter provider plugin",
manifest: openclaw_plugin_default,
provider() {
function buildDynamicOpenRouterModel(ctx) {
const apiModelId = normalizeOpenRouterApiModelId(ctx.modelId) ?? ctx.modelId;
const capabilities = getLoadedOpenRouterModelCapabilities(apiModelId);
return {
id: ctx.modelId,
name: capabilities?.name ?? ctx.modelId,
api: "openai-completions",
provider: PROVIDER_ID,
baseUrl: resolveOpenRouterApiBaseUrl(ctx.providerConfig?.baseUrl ?? ctx.config?.models?.providers?.openrouter?.baseUrl),
reasoning: (capabilities?.reasoning ?? false) && !isOpenRouterProxyReasoningUnsupportedModel(ctx.modelId),
input: capabilities?.input ?? ["text"],
...capabilities?.compat || capabilities?.supportsTools !== void 0 ? { compat: {
...capabilities.compat,
...capabilities.supportsTools !== void 0 ? { supportsTools: capabilities.supportsTools } : {}
} } : {},
...capabilities?.thinkingLevelMap ? { thinkingLevelMap: capabilities.thinkingLevelMap } : {},
cost: capabilities?.cost ?? {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0
},
contextWindow: capabilities?.contextWindow ?? 2e5,
maxTokens: capabilities?.maxTokens ?? OPENROUTER_DEFAULT_MAX_TOKENS
};
}
return {
label: "OpenRouter",
docsPath: "/providers/models",
manifestAuth: {
hint: "API key",
defaultModel: OPENROUTER_DEFAULT_MODEL_REF,
applyConfig: applyOpenrouterConfig
},
extraAuth: [createOpenRouterOAuthAuthMethod()],
catalog: {
order: "simple",
run: async (ctx) => {
const auth = ctx.resolveProviderApiKey(PROVIDER_ID);
const apiKey = auth.apiKey;
if (!apiKey) return null;
const providerConfig = ctx.config.models?.providers?.openrouter;
return await runLiveProviderCatalog({
providerId: PROVIDER_ID,
profileId: auth.profileId,
run: async () => ({ provider: await buildOpenrouterLiveProvider({
apiKey,
discoveryApiKey: auth.discoveryApiKey,
baseUrl: providerConfig?.baseUrl,
request: providerConfig?.request
}) })
});
},
staticRun: async (ctx) => {
if (ctx.providerIds?.includes(PROVIDER_ID)) await loadOpenRouterModelCapabilities(OPENROUTER_DEFAULT_MODEL_REF);
return { provider: buildOpenrouterProvider() };
}
},
resolveDynamicModel: buildDynamicOpenRouterModel,
preferRuntimeResolvedModel: (ctx) => {
const configuredProvider = findNormalizedProviderValue(ctx.config?.models?.providers, PROVIDER_ID);
const requestedId = normalizeOpenRouterApiModelId(ctx.modelId) ?? ctx.modelId;
const configuredModel = configuredProvider?.models?.find((model) => (normalizeOpenRouterApiModelId(model.id) ?? model.id) === requestedId);
return configuredModel !== void 0 && isOpenRouterCatalogRoute({
api: configuredModel.api ?? configuredProvider?.api,
baseUrl: configuredModel.baseUrl ?? configuredProvider?.baseUrl
});
},
prepareDynamicModel: async (ctx) => {
await loadOpenRouterModelCapabilities(normalizeOpenRouterApiModelId(ctx.modelId) ?? ctx.modelId);
},
normalizeConfig: ({ providerConfig }) => {
const normalizedBaseUrl = normalizeOpenRouterBaseUrl(providerConfig.baseUrl);
return normalizedBaseUrl && normalizedBaseUrl !== providerConfig.baseUrl ? {
...providerConfig,
baseUrl: normalizedBaseUrl
} : void 0;
},
normalizeResolvedModel: ({ model }) => normalizeOpenRouterResolvedModel(model),
normalizeTransport: ({ api: apiLocal, baseUrl }) => {
const normalizedBaseUrl = normalizeOpenRouterBaseUrl(baseUrl);
return normalizedBaseUrl && normalizedBaseUrl !== baseUrl ? {
api: apiLocal,
baseUrl: normalizedBaseUrl
} : void 0;
},
classifyFailoverReason: ({ provider, errorMessage }) => {
if (provider?.trim().toLowerCase() !== PROVIDER_ID) return;
if (/\b(?:api\s+key\s+budget|key)\s+limit\s*(?:exceeded|reached|hit)\b/i.test(errorMessage)) return "billing";
return /provider returned error/i.test(errorMessage) ? "timeout" : void 0;
},
buildReplayPolicy: ({ modelId }) => ({
...buildPassthroughGeminiSanitizingReplayPolicy(modelId),
...isOpenRouterMistralModelId(modelId) ? {
sanitizeToolCallIds: true,
toolCallIdMode: "strict9"
} : {}
}),
normalizeToolSchemas: normalizeOpenRouterToolSchemas,
inspectToolSchemas: inspectOpenRouterToolSchemas,
resolveReasoningOutputMode: () => "native",
resolveThinkingProfile: (ctx) => resolveOpenRouterThinkingProfile(ctx.modelId, withOpenRouterCatalogThinking(ctx)),
isModernModelRef: () => true,
resolveSystemPromptContribution: resolveOpenRouterFusionPromptContribution,
extraParamsForTransport: resolveOpenRouterExtraParamsForTransport,
wrapStreamFn: wrapOpenRouterProviderStream,
wrapSimpleCompletionStreamFn: wrapOpenRouterProviderStream,
isCacheTtlEligible: ({ modelId }) => OPENROUTER_CACHE_TTL_MODEL_FAMILY.test(normalizeOpenRouterModelFamilyId(modelId) ?? ""),
resolveUsageAuth: async (ctx) => {
const apiKey = ctx.resolveApiKeyFromConfigAndStore({ envDirect: [ctx.env.OPENROUTER_API_KEY] });
return apiKey ? { token: apiKey } : null;
},
fetchUsageSnapshot: async (ctx) => await fetchOpenRouterUsage({
token: ctx.token,
baseUrl: ctx.config.models?.providers?.openrouter?.baseUrl,
request: ctx.config.models?.providers?.openrouter?.request,
timeoutMs: ctx.timeoutMs,
signal: ctx.signal,
fetchFn: ctx.fetchFn
})
};
},
register(api) {
api.registerMediaUnderstandingProvider(openrouterMediaUnderstandingProvider);
api.registerImageGenerationProvider(buildOpenRouterImageGenerationProvider());
api.registerMusicGenerationProvider(buildOpenRouterMusicGenerationProvider());
api.registerVideoGenerationProvider(buildOpenRouterVideoGenerationProvider());
api.registerModelCatalogProvider({
provider: PROVIDER_ID,
kinds: ["video_generation"],
liveCatalog: listOpenRouterVideoModelCatalog
});
api.registerSpeechProvider(buildOpenRouterSpeechProvider());
}
});
//#endregion
export { openrouter_default as default };