openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
308 lines (307 loc) • 15.9 kB
JavaScript
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 };