UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

205 lines (204 loc) • 10 kB
import { t as stableStringify } from "./stable-stringify-CkQ0IEj1.mjs"; import { l as normalizeToolPolicyName } from "./tool-policy-shared-DSNSYBF4.mjs"; import "./tool-policy-C57wduz9.mjs"; import { r as getPluginToolMeta } from "./tool-metadata--ndgGyn8.mjs"; import { o as DirectoryCache } from "./target-resolver-zB2E88Fj.mjs"; import { r as hasSessionControlAuthority } from "./openclaw-tools-CdfCVvgd.mjs"; import { r as loadPairedComputerUseAvailabilityForSurface } from "./computer-tool-Ugq_ziKB.mjs"; import { n as listCoreToolFactoryDescriptors, t as isCoreCodingSurfaceToolName } from "./core-tool-factory-descriptors-DYpD_U2M.mjs"; import { t as applyEmbeddedAttemptToolsAllow } from "./attempt-tool-construction-plan-BizPvV2W.mjs"; import { n as readMcpLoopbackToolName, t as buildMcpToolSchema } from "./mcp-http.schema-Dx29nlJU.mjs"; import { t as loadNodeExecAvailability } from "./node-exec-availability-BA8GFQRT.mjs"; import { t as resolveGatewayScopedTools } from "./tool-resolution-D1wI-8N8.mjs"; //#region src/gateway/mcp-http.runtime.ts const TOOL_CACHE_TTL_MS = 3e4; const TOOL_CACHE_MAX_ENTRIES = 256; const NATIVE_TOOL_EXCLUDE = new Set(listCoreToolFactoryDescriptors().map(({ name }) => name).filter(isCoreCodingSurfaceToolName)); function resolveMediatedNativeTools(toolsAllow, mode) { if (mode === "exact") return new Set((toolsAllow ?? []).map((name) => normalizeToolPolicyName(name)).filter((name) => NATIVE_TOOL_EXCLUDE.has(name))); if (toolsAllow === void 0 || toolsAllow.some((toolName) => normalizeToolPolicyName(toolName) === "*")) return /* @__PURE__ */ new Set(); return new Set(applyEmbeddedAttemptToolsAllow(Array.from(NATIVE_TOOL_EXCLUDE, (name) => ({ name })), toolsAllow).map((tool) => tool.name)); } async function resolveNodeExecScope(params, mode) { if (!(!params.rootedExecution && params.context.nodeExecAllowed === true && resolveMediatedNativeTools(params.context.toolsAllow, mode).size === 0)) return params; return { ...params, nodeExecAvailability: await loadNodeExecAvailability(params.signal) }; } function isComputerAllowedByMcpScope(params, mode) { const { toolsAllow } = params.context; if (mode === "exact") return toolsAllow === void 0 || toolsAllow.some((name) => normalizeToolPolicyName(name) === "computer"); return applyEmbeddedAttemptToolsAllow([{ name: "computer" }], toolsAllow).length === 1; } async function resolveNodeScope(input, mode) { return resolvePairedComputerNodeScope(await resolveNodeExecScope(input, mode), mode); } async function resolvePairedComputerNodeScope(params, mode) { if (!(isComputerAllowedByMcpScope(params, mode) && params.context.modelHasVision !== false && !params.rootedExecution)) return { params }; const policyResolved = resolveMcpLoopbackTools(params, mode); const computerAllowed = policyResolved.tools.some((tool) => readMcpLoopbackToolName(tool) === "computer"); const pairedComputerUseAvailability = await loadPairedComputerUseAvailabilityForSurface({ computerAllowed, modelHasVision: params.context.modelHasVision, computerTransport: params.rootedExecution ? null : void 0, signal: params.signal }); if (!pairedComputerUseAvailability) return { params, policyResolved }; const resolvedParams = { params: { ...params, pairedComputerUseAvailability } }; return pairedComputerUseAvailability.prepared ? resolvedParams : { ...resolvedParams, policyResolved }; } function resolveMcpLoopbackTools(params, mode) { params.signal?.throwIfAborted(); const { toolsAllow, webSearchDisabled, ...context } = params.context; const excludeToolNames = new Set(NATIVE_TOOL_EXCLUDE); if (webSearchDisabled) excludeToolNames.add("web_search"); const mediatedNativeTools = params.rootedExecution ? new Set(NATIVE_TOOL_EXCLUDE) : resolveMediatedNativeTools(toolsAllow, mode); for (const toolName of mediatedNativeTools) excludeToolNames.delete(toolName); const includeNodeExecTool = context.nodeExecAllowed === true && mediatedNativeTools.size === 0; if (includeNodeExecTool) excludeToolNames.delete("exec"); const skillWorkshop = context.skillWorkshop || params.skillLibraryAuthoring ? { ...context.skillWorkshop, libraryAuthoring: params.skillLibraryAuthoring } : void 0; const scoped = resolveGatewayScopedTools({ ...context, rootedExecution: params.rootedExecution, messageActionTurnCapability: params.messageActionTurnCapability, cfg: params.cfg, authProfileStore: params.authProfileStore, onYield: params.onYield, skillWorkshop, nativeCronCreatorToolAllowlist: context.nativeCronCreatorToolAllowlist ?? void 0, agentDir: params.authProfileStoreAgentDir, conversationReadOrigin: "delegated", surface: "loopback", sessionControlAuthority: params.sessionControlAuthority, isGrantCurrent: params.isGrantCurrent, excludeToolNames, mediatedToolNames: mediatedNativeTools, includeNodeExecTool, nodeExecAvailable: params.nodeExecAvailability?.isAvailable, pairedNodeComputerUse: params.pairedComputerUseAvailability?.prepared }); const tools = mode === "exact" ? applyGrantToolsAllow(scoped.tools, toolsAllow) : applyPolicyToolsAllow(scoped.tools, toolsAllow); const toolSchema = buildMcpToolSchema(tools); scoped.captureFinalCronCreatorTools?.(new Set(toolSchema.map((tool) => tool.name))); return { agentId: scoped.agentId, workspaceDir: scoped.workspaceDir, tools, toolSchema }; } /** Resolves loopback-visible tools from the exact names carried by a minted grant. */ async function resolveMcpLoopbackScopedTools(params) { const resolved = await resolveNodeScope(params, "exact"); return resolved.policyResolved ?? resolveMcpLoopbackTools(resolved.params, "exact"); } /** Materializes runtime policy expressions against the concrete loopback catalog. */ async function resolveMcpLoopbackPolicyTools(params) { const resolved = await resolveNodeScope(params, "policy"); return resolved.policyResolved ?? resolveMcpLoopbackTools(resolved.params, "policy"); } /** * Hard-enforces a per-run grant allowlist on the loopback surface. Both * tools/list and tools/call consume this list, so a tool outside the * allowlist can be neither discovered nor executed even when the CLI runs * with a bypass permission mode. An empty allowlist fails closed. */ function applyGrantToolsAllow(tools, toolsAllow) { if (!toolsAllow) return tools; const allowed = new Set(toolsAllow.map((name) => normalizeToolPolicyName(name)).filter(Boolean)); return tools.filter((tool) => { const name = readMcpLoopbackToolName(tool); return name !== void 0 && allowed.has(normalizeToolPolicyName(name)); }); } function applyPolicyToolsAllow(tools, toolsAllow) { if (!toolsAllow) return tools; const candidates = tools.flatMap((tool) => { const name = readMcpLoopbackToolName(tool); return name ? [{ name, tool }] : []; }); return applyEmbeddedAttemptToolsAllow(candidates, toolsAllow, { toolMeta: (candidate) => getPluginToolMeta(candidate.tool) }).map((candidate) => candidate.tool); } function buildMcpLoopbackToolCacheKey(params) { const { context } = params; return `${params.grantToken ?? ""}\u0000${stableStringify({ context: { ...context, clientCaps: [...new Set(context.clientCaps ?? [])].toSorted(), toolsAllow: context.toolsAllow ? [...new Set(context.toolsAllow)].toSorted() : void 0, modelHasVision: context.modelHasVision, pinnedWidgetAuthoring: context.pinnedWidgetAuthoring === true, currentInboundAudio: context.currentInboundAudio === true, sourceReplyOnly: context.sourceReplyOnly === true, requireExplicitMessageTarget: context.requireExplicitMessageTarget === true, nodeExecAllowed: context.nodeExecAllowed === true, delegationCapability: context.delegationCapability === "report_only" ? "report_only" : void 0 }, sessionControlsAllowed: hasSessionControlAuthority(params.sessionControlAuthority), authProfileStoreAgentDir: params.authProfileStoreAgentDir, yieldContextCacheKey: params.yieldContextCacheKey, nodeExecAvailability: params.nodeExecAvailability?.cacheKey, pairedComputerUseAvailability: params.pairedComputerUseAvailability?.cacheKey })}`; } /** Short-lived cache for loopback tool lists keyed by session/channel context. */ var McpLoopbackToolCache = class { #entries = new DirectoryCache(TOOL_CACHE_TTL_MS, TOOL_CACHE_MAX_ENTRIES); #grantConfigScopes = /* @__PURE__ */ new Map(); #epoch = 0; async resolve(input) { const epoch = this.#epoch; const nodeExecParams = await resolveNodeExecScope(input, "exact"); input.signal?.throwIfAborted(); const preDiscoveryCacheKey = buildMcpLoopbackToolCacheKey(nodeExecParams); const preDiscoveryCached = this.#entries.get(preDiscoveryCacheKey, nodeExecParams.cfg); if (preDiscoveryCached) return preDiscoveryCached; const resolved = await resolvePairedComputerNodeScope(nodeExecParams, "exact"); input.signal?.throwIfAborted(); const { params } = resolved; const cacheKey = buildMcpLoopbackToolCacheKey(params); const cached = this.#entries.get(cacheKey, params.cfg); if (cached) return cached; const nextEntry = resolved.policyResolved ?? resolveMcpLoopbackTools(params, "exact"); if (epoch !== this.#epoch) return nextEntry; this.#entries.set(cacheKey, nextEntry, params.cfg); if (params.grantToken) { const scopes = this.#grantConfigScopes.get(params.grantToken) ?? /* @__PURE__ */ new Set(); scopes.add(params.cfg); this.#grantConfigScopes.set(params.grantToken, scopes); } return nextEntry; } evictGrant(token) { this.#epoch += 1; const scopes = this.#grantConfigScopes.get(token); if (!scopes) return false; const cacheKeyPrefix = `${token}\u0000`; for (const cfg of scopes) this.#entries.clearMatching((cacheKey) => cacheKey.startsWith(cacheKeyPrefix), cfg); this.#grantConfigScopes.delete(token); return true; } clear() { this.#epoch += 1; this.#entries.clear(); this.#grantConfigScopes.clear(); } }; //#endregion export { resolveMcpLoopbackPolicyTools as n, resolveMcpLoopbackScopedTools as r, McpLoopbackToolCache as t };