UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

522 lines (521 loc) 23.2 kB
import { c as asFiniteNumberInRange, s as asFiniteNumber } from "./number-coercion-CLj0HTDM.js"; import "./src-vebZIeLe.js"; import { l as normalizeOptionalString } from "./string-coerce-CIXf7egm.js"; import { n as normalizeAgentId } from "./agent-id-CeT3w4ap.js"; import "./session-key-BnWWjqNc.js"; import { a as getChildLogger } from "./logger-DK-iouVT.js"; import { I as markHostPluginUsageDiagnosticEvent, f as isDiagnosticsEnabled, o as emitTrustedDiagnosticEvent } from "./diagnostic-events-Cwe92uV3.js"; import { l as normalizePluginsConfig } from "./config-state-BkU1frVq.js"; import { s as normalizeModelRef } from "./model-ref-shared-Dz7QU0Lx.js"; import { t as modelKey } from "./model-key-CMdQNkZf.js"; import { r as buildConfiguredModelCatalog } from "./model-selection-shared-BlLyx1r2.js"; import { t as splitTrailingAuthProfile } from "./model-ref-profile-BIKs-96s.js"; import { a as getPluginRuntimeGatewayRequestScope } from "./gateway-request-scope-BCMYlsDI.js"; import "./logging-DB4bVraC.js"; import { c as normalizeUsage } from "./usage-CtmV8Xxq.js"; import { c as resolveThinkingProfile } from "./thinking-DterdXhr.js"; import { o as resolveEffectiveAgentRuntime } from "./thinking-runtime-BiFbTVdI.js"; import { a as resolveModelCostConfig, n as estimateUsageCost, t as estimateAggregateUsageCost } from "./usage-format-DhUP1uYp.js"; import { t as compileModelAllowlist } from "./model-allowlist-Vk4n4FlI.js"; //#region src/plugins/runtime/runtime-llm-error.ts function createLlmCompleteError(code, message, cause) { return Object.assign(new Error(message, cause === void 0 ? void 0 : { cause }), { name: "LlmCompleteError", code }); } //#endregion //#region src/plugins/runtime/runtime-llm-isolated.ts const MAX_TIMER_DELAY_MS = 2147483647; function requireIsolatedUserPrompt(params) { if (params.execution?.mode !== "isolated-agent-runtime" || !Array.isArray(params.messages) || params.messages.length !== 1 || params.messages[0]?.role !== "user" || typeof params.messages[0].content !== "string") throw createLlmCompleteError("LLM_ISOLATED_INPUT_REJECTED", "Isolated agent-runtime completion requires exactly one user message; pass system instructions through systemPrompt."); return params.messages[0].content; } function isIsolatedAgentRuntimeRequest(params) { return params.execution?.mode === "isolated-agent-runtime"; } function assertSupportedExecutionMode(params) { const execution = params.execution; if (execution === void 0) return; if (!execution || typeof execution !== "object" || Array.isArray(execution) || execution.mode !== "isolated-agent-runtime") throw createLlmCompleteError("LLM_ISOLATED_INPUT_REJECTED", "Plugin LLM completion execution.mode must be \"isolated-agent-runtime\" when execution is provided."); } function resolveIsolatedTimeoutMs(value) { if (value === void 0) return 3e4; const timeoutMs = asFiniteNumber(value); if (timeoutMs === void 0 || !Number.isSafeInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > MAX_TIMER_DELAY_MS) throw createLlmCompleteError("LLM_ISOLATED_INPUT_REJECTED", `Isolated agent-runtime completion timeoutMs must be an integer from 1 through ${MAX_TIMER_DELAY_MS}.`); return timeoutMs; } function assertIsolatedReasoningSupported(params) { if (params.reasoning === void 0) return; const catalog = buildConfiguredModelCatalog({ cfg: params.cfg }); const profile = resolveThinkingProfile({ provider: params.provider, model: params.model, agentRuntime: resolveEffectiveAgentRuntime({ cfg: params.cfg, agentId: params.agentId, provider: params.provider, modelId: params.model }), ...catalog.length > 0 ? { catalog } : {} }); if (profile.levels.some((level) => level.id === params.reasoning)) return; throw createLlmCompleteError("LLM_ISOLATED_INPUT_REJECTED", `Thinking level "${params.reasoning}" is not supported for ${params.provider}/${params.model}. Use one of: ${profile.levels.map((level) => level.label).join(", ")}.`); } async function runIsolatedAgentRuntimeCompletion(params) { const prompt = requireIsolatedUserPrompt(params.request); const timeoutMs = resolveIsolatedTimeoutMs(params.request.execution.timeoutMs); assertIsolatedReasoningSupported({ cfg: params.cfg, agentId: params.agentId, provider: params.provider, model: params.model, reasoning: params.request.reasoning }); const controller = new AbortController(); let timedOut = false; const abortFromCaller = () => controller.abort(params.request.signal?.reason); if (params.request.signal?.aborted) throw createLlmCompleteError("LLM_COMPLETION_ABORTED", "Plugin LLM completion was aborted."); params.request.signal?.addEventListener("abort", abortFromCaller, { once: true }); const timer = setTimeout(() => { timedOut = true; controller.abort(/* @__PURE__ */ new Error(`Isolated completion timed out after ${timeoutMs}ms.`)); }, timeoutMs); timer.unref?.(); let rejectOnAbort; const abortPromise = new Promise((_resolve, reject) => { rejectOnAbort = () => { const reason = controller.signal.reason; reject(reason instanceof Error ? reason : /* @__PURE__ */ new Error("Isolated completion was aborted.")); }; controller.signal.addEventListener("abort", rejectOnAbort, { once: true }); }); try { const operation = (async () => { const { runIsolatedCompletion } = await import("./isolated-completion-BGUOyVW_.js"); return await runIsolatedCompletion({ config: params.cfg, provider: params.provider, model: params.model, authProfileId: params.authProfileId, agentId: params.agentId, systemPrompt: params.request.systemPrompt ?? "", prompt, timeoutMs, abortSignal: controller.signal, thinkLevel: params.request.reasoning, streamParams: { maxTokens: asFiniteNumber(params.request.maxTokens), temperature: asFiniteNumber(params.request.temperature) } }); })(); return await Promise.race([operation, abortPromise]); } catch (error) { if (timedOut) throw createLlmCompleteError("LLM_COMPLETION_TIMEOUT", `Plugin LLM completion timed out after ${timeoutMs}ms.`, error); if (params.request.signal?.aborted) throw createLlmCompleteError("LLM_COMPLETION_ABORTED", "Plugin LLM completion was aborted.", error); const isolatedError = error; if (isolatedError.code === "unsupported") throw createLlmCompleteError("LLM_ISOLATED_UNSUPPORTED", typeof isolatedError.message === "string" ? isolatedError.message : "Configured agent runtime does not support isolated completion.", error); if (isolatedError.code === "runtime-unavailable") throw createLlmCompleteError("LLM_RUNTIME_UNAVAILABLE", typeof isolatedError.message === "string" ? isolatedError.message : "Configured agent runtime is unavailable.", error); if (isolatedError.code === "input-rejected") throw createLlmCompleteError("LLM_ISOLATED_INPUT_REJECTED", typeof isolatedError.message === "string" ? isolatedError.message : "Isolated completion input was rejected.", error); if (isolatedError.code === "output-rejected") throw createLlmCompleteError("LLM_COMPLETION_OUTPUT_REJECTED", typeof isolatedError.message === "string" ? isolatedError.message : "Isolated completion output was rejected.", error); throw createLlmCompleteError("LLM_COMPLETION_FAILED", "Plugin LLM completion failed.", error); } finally { clearTimeout(timer); if (rejectOnAbort) controller.signal.removeEventListener("abort", rejectOnAbort); params.request.signal?.removeEventListener("abort", abortFromCaller); } } //#endregion //#region src/plugins/runtime/runtime-llm.runtime.ts const defaultLogger = getChildLogger({ capability: "runtime.llm" }); function toRuntimeLogger(logger) { return { debug: (message, meta) => logger.debug?.(meta, message), info: (message, meta) => logger.info(meta, message), warn: (message, meta) => logger.warn(meta, message), error: (message, meta) => logger.error(meta, message) }; } function normalizeCaller(caller, fallback) { const source = caller ?? fallback; if (!source) return { kind: "unknown" }; return { kind: source.kind, ...normalizeOptionalString(source.id) ? { id: source.id.trim() } : {}, ...normalizeOptionalString(source.name) ? { name: source.name.trim() } : {} }; } function resolveTrustedCaller(authority) { if (authority?.caller?.kind === "context-engine") return normalizeCaller(authority.caller); const scope = getPluginRuntimeGatewayRequestScope(); const scopedPluginId = normalizeOptionalString(scope?.pluginId); if (scopedPluginId) return { kind: "plugin", id: scopedPluginId }; return normalizeCaller(authority?.caller); } function resolveRuntimeConfig(options) { const cfg = options.getConfig?.(); if (!cfg) throw new Error("Plugin LLM completion requires an injected runtime config scope."); return cfg; } async function resolveAgentId(params) { const authorityAgentIdRaw = normalizeOptionalString(params.authority?.agentId); const requestedAgentIdRaw = normalizeOptionalString(params.request.agentId); const authorityAgentId = authorityAgentIdRaw ? normalizeAgentId(authorityAgentIdRaw) : void 0; const requestedAgentId = requestedAgentIdRaw ? normalizeAgentId(requestedAgentIdRaw) : void 0; if (params.authority?.requiresBoundAgent && !authorityAgentId) throw createLlmCompleteError("LLM_COMPLETION_NOT_AUTHORIZED", "Plugin LLM completion is not bound to an active session agent."); if (authorityAgentId) { if (requestedAgentId && requestedAgentId !== authorityAgentId && !params.allowAgentIdOverride) throw createLlmCompleteError("LLM_COMPLETION_NOT_AUTHORIZED", "Plugin LLM completion cannot override the active session agent."); return authorityAgentId; } if (requestedAgentId) { if (!params.allowAgentIdOverride) throw createLlmCompleteError("LLM_COMPLETION_NOT_AUTHORIZED", "Plugin LLM completion cannot override the target agent."); return requestedAgentId; } const { resolveAmbientOwnerAgentId } = await import("./agent-scope-BHLMs19p.js"); return resolveAmbientOwnerAgentId(params.cfg); } function buildSystemPrompt(params) { const segments = [normalizeOptionalString(params.systemPrompt), ...params.messages.filter((message) => message.role === "system").map((message) => normalizeOptionalString(message.content))].filter((segment) => Boolean(segment)); return segments.length > 0 ? segments.join("\n\n") : void 0; } function buildMessages(params) { const now = Date.now(); return params.request.messages.filter((message) => message.role !== "system").map((message) => message.role === "user" ? { role: "user", content: message.content, timestamp: now } : { role: "assistant", content: [{ type: "text", text: message.content }], api: params.api, provider: params.provider, model: params.model, usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } }, stopReason: "stop", timestamp: now }); } function readFiniteNonNegativeNumber(value) { return asFiniteNumberInRange(value, { min: 0 }); } function readExplicitCostUsd(raw) { if (!raw || typeof raw !== "object" || Array.isArray(raw)) return; const cost = raw.cost; if (typeof cost === "number") return readFiniteNonNegativeNumber(cost); if (!cost || typeof cost !== "object" || Array.isArray(cost)) return; return readFiniteNonNegativeNumber(cost.totalUsd) ?? readFiniteNonNegativeNumber(cost.total); } function finalizePluginLlmCompletion(params) { const normalized = normalizeUsage(params.rawUsage); const costConfig = resolveModelCostConfig({ provider: params.result.provider, model: params.result.model, config: params.cfg }); const estimateCost = params.result.execution.mode === "direct-provider" ? estimateUsageCost : estimateAggregateUsageCost; const costUsd = readExplicitCostUsd(params.rawUsage) ?? estimateCost({ usage: normalized, cost: costConfig }); const usage = { ...normalized?.input !== void 0 ? { inputTokens: normalized.input } : {}, ...normalized?.output !== void 0 ? { outputTokens: normalized.output } : {}, ...normalized?.cacheRead !== void 0 ? { cacheReadTokens: normalized.cacheRead } : {}, ...normalized?.cacheWrite !== void 0 ? { cacheWriteTokens: normalized.cacheWrite } : {}, ...normalized?.total !== void 0 ? { totalTokens: normalized.total } : {}, ...costUsd !== void 0 ? { costUsd } : {} }; (params.logger ?? toRuntimeLogger(defaultLogger)).info("plugin llm completion", { caller: params.result.audit.caller, purpose: params.result.audit.purpose, sessionKey: params.result.audit.sessionKey, agentId: params.result.agentId, provider: params.result.provider, model: params.result.model, executionMode: params.result.execution.mode, executionOwner: params.result.execution.owner, usage }); const input = normalized?.input ?? 0; const output = normalized?.output ?? 0; const cacheRead = normalized?.cacheRead ?? 0; const cacheWrite = normalized?.cacheWrite ?? 0; const promptTokens = input + cacheRead + cacheWrite; const total = normalized?.total ?? promptTokens + output; const hasPositiveUsage = [ input, output, cacheRead, cacheWrite, total, usage.costUsd ].some((value) => typeof value === "number" && Number.isFinite(value) && value > 0); if (params.suppressUsage !== true && isDiagnosticsEnabled(params.cfg) && hasPositiveUsage) emitTrustedDiagnosticEvent(markHostPluginUsageDiagnosticEvent({ type: "model.usage", ...params.result.audit.sessionKey ? { sessionKey: params.result.audit.sessionKey } : {}, agentId: params.result.agentId, provider: params.result.provider, model: params.result.model, usage: { input, output, cacheRead, cacheWrite, promptTokens, total }, ...usage.costUsd !== void 0 ? { costUsd: usage.costUsd } : {} }, params.hostPluginId)); return { ...params.result, usage }; } function buildPolicyFromEntry(entry) { return { allowAgentIdOverride: entry.allowAgentIdOverride === true, allowModelOverride: entry.allowModelOverride === true, allowAuthProfileOverride: entry.allowAuthProfileOverride === true, overrideModels: compileModelAllowlist({ configured: entry.hasAllowedModelsConfig === true, values: entry.allowedModels, formatKey: modelKey }), completionModels: compileModelAllowlist({ configured: entry.hasAllowedCompletionModelsConfig === true, values: entry.allowedCompletionModels, formatKey: modelKey }) }; } function resolvePluginPolicyId(authority, caller) { const authorityPluginId = normalizeOptionalString(authority?.pluginIdForPolicy); if (authorityPluginId) return authorityPluginId; if (caller.kind !== "plugin") return; return normalizeOptionalString(caller.id); } function resolvePluginLlmPolicy(cfg, pluginId) { if (!pluginId) return; const entry = normalizePluginsConfig(cfg.plugins).entries[pluginId]?.llm; return entry ? buildPolicyFromEntry(entry) : void 0; } function resolveAuthorityModelPolicy(authority) { if (authority?.allowAgentIdOverride !== true && authority?.allowModelOverride !== true && authority?.allowAuthProfileOverride !== true && authority?.allowedModels === void 0 && authority?.allowedCompletionModels === void 0) return; return buildPolicyFromEntry({ allowAgentIdOverride: authority.allowAgentIdOverride, allowModelOverride: authority.allowModelOverride, allowAuthProfileOverride: authority.allowAuthProfileOverride, hasAllowedModelsConfig: authority.allowedModels !== void 0, allowedModels: authority.allowedModels, hasAllowedCompletionModelsConfig: authority.allowedCompletionModels !== void 0, allowedCompletionModels: authority.allowedCompletionModels }); } function assertAllowedAuthProfileOverride(params) { if (!params.authProfileId) return; if (params.authorityPolicy?.allowAuthProfileOverride === true || params.pluginPolicy?.allowAuthProfileOverride === true) return; throw createLlmCompleteError("LLM_COMPLETION_NOT_AUTHORIZED", "Plugin LLM completion cannot override the auth profile. Enable plugins.entries.<id>.llm.allowAuthProfileOverride to authorize it."); } function assertModelAllowed(params) { const allowlist = params.kind === "override" ? params.policy?.overrideModels : params.policy?.completionModels; if (!allowlist?.configured || allowlist.allowAny) return; const target = params.kind === "override" ? "model override" : "model"; if (allowlist.models.size === 0) throw createLlmCompleteError("LLM_COMPLETION_NOT_AUTHORIZED", `Plugin LLM completion ${target} allowlist has no valid models.`); if (!params.resolvedModelRef) throw createLlmCompleteError("LLM_COMPLETION_NOT_AUTHORIZED", `Plugin LLM completion ${target} allowlist requires a resolvable provider/model target.`); if (!allowlist.models.has(params.resolvedModelRef)) { const owner = params.policyOwnerPluginId ? ` for plugin "${params.policyOwnerPluginId}"` : ""; const usage = params.kind === "completion" ? " for completions" : ""; throw createLlmCompleteError("LLM_COMPLETION_NOT_AUTHORIZED", `Plugin LLM completion ${target} "${params.resolvedModelRef}" is not allowlisted${usage}${owner}.`); } } function assertAllowedModelOverride(params) { if (params.authorityPolicy?.allowModelOverride !== true && params.pluginPolicy?.allowModelOverride !== true) throw createLlmCompleteError("LLM_COMPLETION_NOT_AUTHORIZED", "Plugin LLM completion cannot override the target model."); assertModelAllowed({ kind: "override", resolvedModelRef: params.resolvedModelRef, policy: params.authorityPolicy }); assertModelAllowed({ kind: "override", resolvedModelRef: params.resolvedModelRef, policy: params.pluginPolicy, policyOwnerPluginId: params.pluginPolicyId }); } /** * Create the host-owned generic LLM completion runtime for trusted plugin callers. */ function createRuntimeLlm(options = {}) { const logger = options.logger ?? toRuntimeLogger(defaultLogger); return { complete: async (params) => { const caller = resolveTrustedCaller(options.authority); if (options.authority?.allowComplete === false) { const reason = options.authority.denyReason ?? "capability denied"; logger.warn("plugin llm completion denied", { caller, purpose: params.purpose, reason }); throw createLlmCompleteError("LLM_COMPLETION_NOT_AUTHORIZED", `Plugin LLM completion denied: ${reason}`); } assertSupportedExecutionMode(params); const [{ prepareSimpleCompletionModelForAgent, completeWithPreparedSimpleCompletionModel, resolveSimpleCompletionSelectionForAgent }, cfg] = await Promise.all([import("./simple-completion-runtime-CYTFMIFR.js"), Promise.resolve(resolveRuntimeConfig(options))]); const pluginPolicyId = resolvePluginPolicyId(options.authority, caller); const pluginPolicy = resolvePluginLlmPolicy(cfg, pluginPolicyId); const authorityPolicy = resolveAuthorityModelPolicy(options.authority); const preferredProfile = normalizeOptionalString(options.authority?.preferredProfile); const audit = { caller, ...params.purpose ? { purpose: params.purpose } : {}, ...options.authority?.sessionKey ? { sessionKey: options.authority.sessionKey } : {} }; const agentId = await resolveAgentId({ request: params, cfg, authority: options.authority, allowAgentIdOverride: options.authority?.allowAgentIdOverride === false ? false : authorityPolicy?.allowAgentIdOverride === true || pluginPolicy?.allowAgentIdOverride === true }); const requestedModel = normalizeOptionalString(params.model); const requestedModelProfile = requestedModel ? normalizeOptionalString(splitTrailingAuthProfile(requestedModel).profile) : void 0; const selection = resolveSimpleCompletionSelectionForAgent({ cfg, agentId, modelRef: requestedModel }); if (!selection) throw createLlmCompleteError("LLM_COMPLETION_FAILED", `No model configured for agent ${agentId}.`); const normalizedSelection = normalizeModelRef(selection.provider, selection.modelId); const resolvedModelRef = modelKey(normalizedSelection.provider, normalizedSelection.model); assertModelAllowed({ kind: "completion", resolvedModelRef, policy: authorityPolicy }); assertModelAllowed({ kind: "completion", resolvedModelRef, policy: pluginPolicy, policyOwnerPluginId: pluginPolicyId }); if (requestedModel) assertAllowedModelOverride({ resolvedModelRef, pluginPolicyId, authorityPolicy, pluginPolicy }); const isolatedRequest = isIsolatedAgentRuntimeRequest(params); const executionProfile = isolatedRequest ? normalizeOptionalString(params.execution.authProfileId) : void 0; const modelProfile = normalizeOptionalString(selection.profileId); if (executionProfile && requestedModelProfile && executionProfile !== requestedModelProfile) throw createLlmCompleteError("LLM_ISOLATED_INPUT_REJECTED", "Isolated completion received conflicting auth profiles in model and execution.authProfileId."); if (isolatedRequest) { assertAllowedAuthProfileOverride({ authProfileId: executionProfile ?? requestedModelProfile, authorityPolicy, pluginPolicy }); const result = await runIsolatedAgentRuntimeCompletion({ request: params, cfg, agentId, provider: selection.provider, model: selection.modelId, authProfileId: executionProfile ?? requestedModelProfile ?? preferredProfile ?? modelProfile }); return finalizePluginLlmCompletion({ cfg, hostPluginId: pluginPolicyId, rawUsage: result.usage, logger, result: { text: result.text, provider: result.provider, model: result.model, agentId, execution: { mode: params.execution.mode, owner: result.owner }, audit } }); } const prepared = await prepareSimpleCompletionModelForAgent({ cfg, agentId, modelRef: params.model, preferredProfile, allowBundledStaticCatalogFallback: true, allowMissingApiKeyModes: ["aws-sdk"], skipAgentDiscovery: true }); if ("error" in prepared) throw new Error(`Plugin LLM completion failed: ${prepared.error}`); const context = { systemPrompt: buildSystemPrompt(params), messages: buildMessages({ request: params, provider: prepared.model.provider, model: prepared.model.id, api: prepared.model.api }) }; const result = await completeWithPreparedSimpleCompletionModel({ model: prepared.model, auth: prepared.auth, cfg, context, options: { maxTokens: asFiniteNumber(params.maxTokens), temperature: asFiniteNumber(params.temperature), ...params.reasoning !== void 0 ? { reasoning: params.reasoning } : {}, signal: params.signal } }); const text = result.content.filter((c) => c.type === "text").map((c) => c.text).join(""); return finalizePluginLlmCompletion({ cfg, hostPluginId: pluginPolicyId, suppressUsage: !text.trim() || ![ "stop", "length", "toolUse" ].includes(result.stopReason), rawUsage: result.usage, logger, result: { text, provider: prepared.selection.provider, model: prepared.selection.modelId, agentId, execution: { mode: "direct-provider", owner: { kind: "provider", id: prepared.selection.provider } }, audit } }); } }; } //#endregion export { createRuntimeLlm, finalizePluginLlmCompletion };