UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

308 lines (307 loc) 15.9 kB
import { c as normalizeOptionalString } from "./string-coerce-mnp54Vah.js"; import { _ as parseStrictFiniteNumber } from "./number-coercion-CJQ8TR--.js"; import "./parse-finite-number-Z7n6tXLk.js"; import { i as formatErrorMessage } from "./errors-BXgSefBE.js"; import { t as createSubsystemLogger } from "./subsystem-BzXSmsuh.js"; import { t as createCorePluginStateSyncKeyedStore } from "./plugin-state-store-jd1pQXxt.js"; import { i as streamSimple } from "./stream-4H1GSjKe.js"; import { b as createToolStreamWrapper, i as createGoogleThinkingPayloadWrapper } from "./provider-stream-shared-BIV_zuwB.js"; import { C as resolveOpenAIServiceTier, S as resolveOpenAIFastMode, _ as createOpenAIResponsesContextManagementWrapper, a as createMinimaxFastModeWrapper, b as createOpenAITextVerbosityWrapper, d as createOpenAIAttributionHeadersWrapper, g as createOpenAIReasoningCompatibilityWrapper, h as createOpenAIFastModeWrapper, i as isProxyReasoningUnsupported, r as createOpenRouterWrapper, t as createKilocodeWrapper, u as createCodexNativeWebSearchWrapper, v as createOpenAIServiceTierWrapper, w as resolveOpenAITextVerbosity, x as createOpenAIThinkingLevelWrapper, y as createOpenAIStringContentWrapper } from "./proxy-2a5P4QqJ.js"; import { n as resolveMoonshotThinkingKeep, r as resolveMoonshotThinkingType, t as createMoonshotThinkingWrapper } from "./moonshot-thinking-RT-IFIsx.js"; import { i as resolveProxyFetchFromEnv } from "./proxy-fetch-Ii-XBOip.js"; //#region src/llm/providers/stream-wrappers/anthropic-family-tool-payload-compat.ts function hasOpenAiAnthropicToolPayloadCompatFlag(model) { if (!model.compat || typeof model.compat !== "object" || Array.isArray(model.compat)) return false; return model.compat.requiresOpenAiAnthropicToolPayload === true; } function requiresAnthropicToolPayloadCompatibilityForModel(model, options) { if (model.api !== "anthropic-messages") return false; return Boolean(options?.toolSchemaMode || options?.toolChoiceMode) || hasOpenAiAnthropicToolPayloadCompatFlag(model); } function usesOpenAiFunctionAnthropicToolSchemaForModel(model, options) { return options?.toolSchemaMode === "openai-functions" || hasOpenAiAnthropicToolPayloadCompatFlag(model); } function usesOpenAiStringModeAnthropicToolChoiceForModel(model, options) { return options?.toolChoiceMode === "openai-string-modes" || hasOpenAiAnthropicToolPayloadCompatFlag(model); } function normalizeOpenAiFunctionAnthropicToolDefinition(tool) { if (!tool || typeof tool !== "object" || Array.isArray(tool)) return; const toolObj = tool; if (toolObj.function && typeof toolObj.function === "object") return toolObj; const rawName = normalizeOptionalString(toolObj.name) ?? ""; if (!rawName) return toolObj; const functionSpec = { name: rawName, parameters: toolObj.input_schema && typeof toolObj.input_schema === "object" ? toolObj.input_schema : toolObj.parameters && typeof toolObj.parameters === "object" ? toolObj.parameters : { type: "object", properties: {} } }; if (typeof toolObj.description === "string" && toolObj.description.trim()) functionSpec.description = toolObj.description; if (typeof toolObj.strict === "boolean") functionSpec.strict = toolObj.strict; return { type: "function", function: functionSpec }; } function normalizeOpenAiStringModeAnthropicToolChoice(toolChoice) { if (!toolChoice || typeof toolChoice !== "object" || Array.isArray(toolChoice)) return toolChoice; const choice = toolChoice; if (choice.type === "auto") return "auto"; if (choice.type === "none") return "none"; if (choice.type === "required" || choice.type === "any") return "required"; if (choice.type === "tool" && typeof choice.name === "string" && choice.name.trim()) return { type: "function", function: { name: choice.name.trim() } }; return toolChoice; } /** @deprecated Anthropic-family provider stream helper; do not use from third-party plugins. */ function createAnthropicToolPayloadCompatibilityWrapper(baseStreamFn, options) { const underlying = baseStreamFn ?? streamSimple; return (model, context, streamOptions) => { const originalOnPayload = streamOptions?.onPayload; return underlying(model, context, { ...streamOptions, onPayload: (payload) => { if (payload && typeof payload === "object" && requiresAnthropicToolPayloadCompatibilityForModel(model, options)) { const payloadObj = payload; if (Array.isArray(payloadObj.tools) && usesOpenAiFunctionAnthropicToolSchemaForModel(model, options)) payloadObj.tools = payloadObj.tools.map((tool) => normalizeOpenAiFunctionAnthropicToolDefinition(tool)).filter((tool) => Boolean(tool)); if (usesOpenAiStringModeAnthropicToolChoiceForModel(model, options)) payloadObj.tool_choice = normalizeOpenAiStringModeAnthropicToolChoice(payloadObj.tool_choice); } return originalOnPayload?.(payload, model); } }); }; } /** @deprecated Anthropic-family provider stream helper; do not use from third-party plugins. */ function createOpenAIAnthropicToolPayloadCompatibilityWrapper(baseStreamFn) { return createAnthropicToolPayloadCompatibilityWrapper(baseStreamFn, { toolSchemaMode: "openai-functions", toolChoiceMode: "openai-string-modes" }); } //#endregion //#region src/agents/embedded-agent-runner/openrouter-model-capabilities.ts /** * Runtime OpenRouter model capability detection. * * When an OpenRouter model is not in the built-in static list, we look up its * actual capabilities from a cached copy of the OpenRouter model catalog. * * Cache layers (checked in order): * 1. In-memory Map (instant, cleared on process restart) * 2. Shared SQLite state cache * 3. OpenRouter API fetch (populates both layers) * * Model capabilities are assumed stable — the cache has no TTL expiry. * A background refresh is triggered only when a model is not found in * the cache (i.e. a newly added model on OpenRouter). * * Sync callers can read whatever is already cached. Async callers can await a * one-time fetch so the first unknown-model lookup resolves with real * capabilities instead of the text-only fallback. */ const log = createSubsystemLogger("openrouter-model-capabilities"); const OPENROUTER_MODELS_URL = "https://openrouter.ai/api/v1/models"; const FETCH_TIMEOUT_MS = 1e4; const SQLITE_CACHE_OWNER_ID = "core:openrouter-model-capabilities"; const SQLITE_CACHE_NAMESPACE = "models.v3"; const SQLITE_CACHE_MAX_ENTRIES = 1e4; function isValidCapabilities(value) { if (!value || typeof value !== "object") return false; const record = value; return typeof record.name === "string" && Array.isArray(record.input) && typeof record.reasoning === "boolean" && typeof record.contextWindow === "number" && typeof record.maxTokens === "number"; } function openSqliteCacheStore() { return createCorePluginStateSyncKeyedStore({ ownerId: SQLITE_CACHE_OWNER_ID, namespace: SQLITE_CACHE_NAMESPACE, maxEntries: SQLITE_CACHE_MAX_ENTRIES }); } function writeSqliteCache(map) { try { const store = openSqliteCacheStore(); store.clear(); for (const [id, capabilities] of map) store.register(id, capabilities); } catch (err) { const message = formatErrorMessage(err); log.debug(`Failed to write OpenRouter SQLite cache: ${message}`); } } function readSqliteCache() { try { const entries = openSqliteCacheStore().entries(); if (entries.length === 0) return; const map = /* @__PURE__ */ new Map(); for (const { key, value } of entries) if (isValidCapabilities(value)) map.set(key, value); return map.size > 0 ? map : void 0; } catch (err) { const message = formatErrorMessage(err); log.debug(`Failed to read OpenRouter SQLite cache: ${message}`); return; } } let cache; let fetchInFlight; const skipNextMissRefresh = /* @__PURE__ */ new Set(); function parseModel(model) { const input = ["text"]; if (((model.architecture?.modality ?? model.modality ?? "").split("->")[0] ?? "").includes("image")) input.push("image"); const supportedParameters = Array.isArray(model.supported_parameters) ? model.supported_parameters : void 0; return { name: model.name || model.id, input, reasoning: supportedParameters?.includes("reasoning") ?? false, ...supportedParameters ? { supportsTools: supportedParameters.includes("tools") } : {}, contextWindow: model.top_provider?.context_length ?? model.context_length ?? 128e3, maxTokens: model.top_provider?.max_completion_tokens ?? model.max_completion_tokens ?? model.max_output_tokens ?? 8192, cost: { input: (parseStrictFiniteNumber(model.pricing?.prompt) ?? 0) * 1e6, output: (parseStrictFiniteNumber(model.pricing?.completion) ?? 0) * 1e6, cacheRead: (parseStrictFiniteNumber(model.pricing?.input_cache_read) ?? 0) * 1e6, cacheWrite: (parseStrictFiniteNumber(model.pricing?.input_cache_write) ?? 0) * 1e6 } }; } async function doFetch() { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS); try { const response = await (resolveProxyFetchFromEnv() ?? globalThis.fetch)(OPENROUTER_MODELS_URL, { signal: controller.signal }); if (!response.ok) { log.warn(`OpenRouter models API returned ${response.status}`); return; } const models = (await response.json()).data ?? []; const map = /* @__PURE__ */ new Map(); for (const model of models) { if (!model.id) continue; map.set(model.id, parseModel(model)); } cache = map; writeSqliteCache(map); log.debug(`Cached ${map.size} OpenRouter models from API`); } catch (err) { const message = formatErrorMessage(err); log.warn(`Failed to fetch OpenRouter models: ${message}`); } finally { clearTimeout(timeout); } } function triggerFetch() { if (fetchInFlight) return; fetchInFlight = doFetch().finally(() => { fetchInFlight = void 0; }); } /** * Ensure the cache is populated. Checks in-memory first, then SQLite, then * triggers a background API fetch as a last resort. * Does not block — returns immediately. */ function ensureOpenRouterModelCache() { if (cache) return; const stored = readSqliteCache(); if (stored) { cache = stored; log.debug(`Loaded ${stored.size} OpenRouter models from SQLite cache`); return; } triggerFetch(); } /** * Ensure capabilities for a specific model are available before first use. * * Known cached entries return immediately. Unknown entries wait for at most * one catalog fetch, then leave sync resolution to read from the populated * cache on the same request. * * @deprecated OpenRouter provider-owned catalog helper; do not use from third-party plugins. */ async function loadOpenRouterModelCapabilities(modelId) { ensureOpenRouterModelCache(); if (cache?.has(modelId)) return; let fetchPromise = fetchInFlight; if (!fetchPromise) { triggerFetch(); fetchPromise = fetchInFlight; } await fetchPromise; if (!cache?.has(modelId)) skipNextMissRefresh.add(modelId); } /** * Synchronously look up model capabilities from the cache. * * If a model is not found but the cache exists, a background refresh is * triggered in case it's a newly added model not yet in the cache. * * @deprecated OpenRouter provider-owned catalog helper; do not use from third-party plugins. */ function getOpenRouterModelCapabilities(modelId) { ensureOpenRouterModelCache(); const result = cache?.get(modelId); if (!result && skipNextMissRefresh.delete(modelId)) return; if (!result && cache && !fetchInFlight) triggerFetch(); return result; } //#endregion //#region src/plugin-sdk/provider-stream.ts /** Builds provider hook objects for one supported stream-wrapper family. */ function buildProviderStreamFamilyHooks(family) { switch (family) { case "google-thinking": return { wrapStreamFn: (ctx) => createGoogleThinkingPayloadWrapper(ctx.streamFn, ctx.thinkingLevel) }; case "moonshot-thinking": return { wrapStreamFn: (ctx) => { const thinkingType = resolveMoonshotThinkingType({ configuredThinking: ctx.extraParams?.thinking, thinkingLevel: ctx.thinkingLevel }); const thinkingKeep = resolveMoonshotThinkingKeep({ configuredThinking: ctx.extraParams?.thinking }); return createMoonshotThinkingWrapper(ctx.streamFn, thinkingType, thinkingKeep); } }; case "kilocode-thinking": return { wrapStreamFn: (ctx) => { const thinkingLevel = ctx.modelId === "kilo/auto" || isProxyReasoningUnsupported(ctx.modelId) ? void 0 : ctx.thinkingLevel; return createKilocodeWrapper(ctx.streamFn, thinkingLevel); } }; case "minimax-fast-mode": return { wrapStreamFn: (ctx) => createMinimaxFastModeWrapper(ctx.streamFn, ctx.extraParams?.fastMode === true) }; case "openai-responses-defaults": return { wrapStreamFn: (ctx) => { let nextStreamFn = createOpenAIAttributionHeadersWrapper(ctx.streamFn); if (resolveOpenAIFastMode(ctx.extraParams)) nextStreamFn = createOpenAIFastModeWrapper(nextStreamFn); const serviceTier = resolveOpenAIServiceTier(ctx.extraParams); if (serviceTier) nextStreamFn = createOpenAIServiceTierWrapper(nextStreamFn, serviceTier); const textVerbosity = resolveOpenAITextVerbosity(ctx.extraParams); if (textVerbosity) nextStreamFn = createOpenAITextVerbosityWrapper(nextStreamFn, textVerbosity); nextStreamFn = createCodexNativeWebSearchWrapper(nextStreamFn, { config: ctx.config, agentDir: ctx.agentDir, agentId: ctx.agentId, nativeWebSearchAllowedByToolPolicy: ctx.nativeWebSearchAllowedByToolPolicy }); nextStreamFn = createOpenAIStringContentWrapper(nextStreamFn); return createOpenAIResponsesContextManagementWrapper(createOpenAIReasoningCompatibilityWrapper(createOpenAIThinkingLevelWrapper(nextStreamFn, ctx.thinkingLevel)), ctx.extraParams); } }; case "openrouter-thinking": return { wrapStreamFn: (ctx) => { const thinkingLevel = ctx.modelId === "auto" || isProxyReasoningUnsupported(ctx.modelId) ? void 0 : ctx.thinkingLevel; return createOpenRouterWrapper(ctx.streamFn, thinkingLevel, ctx.extraParams); } }; case "tool-stream-default-on": return { wrapStreamFn: (ctx) => createToolStreamWrapper(ctx.streamFn, ctx.extraParams?.tool_stream !== false) }; } throw new Error("Unsupported provider stream family"); } /** @deprecated Google provider-owned stream hook shortcut; use local provider hooks instead. */ const GOOGLE_THINKING_STREAM_HOOKS = buildProviderStreamFamilyHooks("google-thinking"); /** @deprecated Kilocode provider-owned stream hook shortcut; use local provider hooks instead. */ const KILOCODE_THINKING_STREAM_HOOKS = buildProviderStreamFamilyHooks("kilocode-thinking"); /** @deprecated Moonshot provider-owned stream hook shortcut; use local provider hooks instead. */ const MOONSHOT_THINKING_STREAM_HOOKS = buildProviderStreamFamilyHooks("moonshot-thinking"); /** @deprecated MiniMax provider-owned stream hook shortcut; use local provider hooks instead. */ const MINIMAX_FAST_MODE_STREAM_HOOKS = buildProviderStreamFamilyHooks("minimax-fast-mode"); /** @deprecated OpenAI provider-owned stream hook shortcut; use local provider hooks instead. */ const OPENAI_RESPONSES_STREAM_HOOKS = buildProviderStreamFamilyHooks("openai-responses-defaults"); /** @deprecated OpenRouter provider-owned stream hook shortcut; use local provider hooks instead. */ const OPENROUTER_THINKING_STREAM_HOOKS = buildProviderStreamFamilyHooks("openrouter-thinking"); /** @deprecated Provider-owned stream hook shortcut; use local provider hooks instead. */ const TOOL_STREAM_DEFAULT_ON_HOOKS = buildProviderStreamFamilyHooks("tool-stream-default-on"); //#endregion export { OPENAI_RESPONSES_STREAM_HOOKS as a, buildProviderStreamFamilyHooks as c, createAnthropicToolPayloadCompatibilityWrapper as d, createOpenAIAnthropicToolPayloadCompatibilityWrapper as f, MOONSHOT_THINKING_STREAM_HOOKS as i, getOpenRouterModelCapabilities as l, KILOCODE_THINKING_STREAM_HOOKS as n, OPENROUTER_THINKING_STREAM_HOOKS as o, MINIMAX_FAST_MODE_STREAM_HOOKS as r, TOOL_STREAM_DEFAULT_ON_HOOKS as s, GOOGLE_THINKING_STREAM_HOOKS as t, loadOpenRouterModelCapabilities as u };