openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
121 lines (120 loc) • 5.55 kB
JavaScript
import { a as asOptionalRecord } from "./record-coerce-DItp3I4t.js";
import { l as normalizeOptionalString } from "./string-coerce-CIXf7egm.js";
import { t as createSubsystemLogger } from "./subsystem-Dy2tqXOS.js";
import "./node-commands-BC8PhxqU.js";
//#region src/gateway/node-plugin-tool-snapshot.ts
/** Connected node-hosted plugin tools available to agent tool resolution. */
const toolsByNodeId = /* @__PURE__ */ new Map();
const NODE_PLUGIN_TOOL_NAME_RE = /^[A-Za-z][A-Za-z0-9_-]{0,63}$/;
const NODE_PLUGIN_TOOL_DESCRIPTION_MAX_LENGTH = 1024;
const NODE_PLUGIN_TOOL_MAX_DESCRIPTORS = 128;
const log = createSubsystemLogger("gateway/node-plugin-tools");
let snapshotVersion = 0;
function bumpSnapshotVersion() {
snapshotVersion += 1;
}
function defaultParameters() {
return {
type: "object",
properties: {},
additionalProperties: true
};
}
function isProviderSafeToolName(value) {
return NODE_PLUGIN_TOOL_NAME_RE.test(value);
}
function createRegisteredNodePluginToolDescriptorMap(commands) {
const descriptors = /* @__PURE__ */ new Map();
for (const entry of commands ?? []) {
const agentTool = entry.command.agentTool;
const name = normalizeOptionalString(agentTool?.name) ?? "";
const description = normalizeOptionalString(agentTool?.description) ?? "";
const command = normalizeOptionalString(entry.command.command) ?? "";
if (!isProviderSafeToolName(name) || !description || !command) continue;
const mcpServer = normalizeOptionalString(agentTool?.mcp?.server) ?? "";
const mcpTool = normalizeOptionalString(agentTool?.mcp?.tool) ?? "";
descriptors.set(`${entry.pluginId}\0${name}\0${command}`, {
pluginId: entry.pluginId,
name,
description,
parameters: asOptionalRecord(agentTool?.parameters) ?? defaultParameters(),
command,
...mcpServer && mcpTool ? { mcp: {
server: mcpServer,
tool: mcpTool
} } : {}
});
}
return descriptors;
}
function normalizeNodePluginToolDescriptors(params) {
if (params.enabled === false) return [];
const allowedCommands = new Set(params.allowedCommands);
const normalized = [];
for (const tool of params.tools ?? []) {
const pluginId = normalizeOptionalString(tool.pluginId) ?? "";
const name = normalizeOptionalString(tool.name) ?? "";
const description = (normalizeOptionalString(tool.description) ?? "").slice(0, NODE_PLUGIN_TOOL_DESCRIPTION_MAX_LENGTH);
const command = normalizeOptionalString(tool.command) ?? "";
if (!pluginId || !isProviderSafeToolName(name) || !description || !command || !allowedCommands.has(command)) continue;
if (pluginId === "node-mcp" && (command !== "mcp.tools.call.v1" || !tool.mcp?.server || !tool.mcp?.tool)) {
log.warn(`node ${params.nodeId} published non-MCP descriptor under reserved node-mcp id`);
continue;
}
const registeredDescriptor = params.registeredDescriptors.get(`${pluginId}\0${name}\0${command}`);
const descriptor = registeredDescriptor ?? tool;
const descriptorDescription = (normalizeOptionalString(descriptor.description) ?? "").slice(0, NODE_PLUGIN_TOOL_DESCRIPTION_MAX_LENGTH);
const mcpServer = normalizeOptionalString(descriptor.mcp?.server) ?? "";
const mcpTool = normalizeOptionalString(descriptor.mcp?.tool) ?? "";
normalized.push({
descriptor: {
pluginId,
name,
description: descriptorDescription,
parameters: asOptionalRecord(descriptor.parameters) ?? defaultParameters(),
command,
...mcpServer && mcpTool ? { mcp: {
server: mcpServer,
tool: mcpTool
} } : {}
},
registered: Boolean(registeredDescriptor)
});
}
normalized.sort((left, right) => left.descriptor.pluginId.localeCompare(right.descriptor.pluginId) || left.descriptor.name.localeCompare(right.descriptor.name) || (left.descriptor.command ?? "").localeCompare(right.descriptor.command ?? ""));
const byKey = /* @__PURE__ */ new Map();
for (const entry of normalized) {
const key = `${entry.descriptor.pluginId}\0${entry.descriptor.name}`;
if (!byKey.has(key)) byKey.set(key, entry);
}
const entries = [...byKey.values()];
const droppedCount = entries.length - NODE_PLUGIN_TOOL_MAX_DESCRIPTORS;
if (droppedCount > 0) log.warn(`node ${params.nodeId} published ${entries.length} plugin tool descriptors; dropped ${droppedCount} beyond the ${NODE_PLUGIN_TOOL_MAX_DESCRIPTORS} descriptor limit`);
return entries.slice(0, NODE_PLUGIN_TOOL_MAX_DESCRIPTORS);
}
function replaceConnectedNodePluginTools(params) {
if (params.tools.length === 0) {
if (toolsByNodeId.delete(params.nodeId)) bumpSnapshotVersion();
return;
}
toolsByNodeId.set(params.nodeId, params.tools.map((entry) => ({
nodeId: params.nodeId,
displayName: params.displayName,
platform: params.platform,
remoteIp: params.remoteIp,
descriptor: entry.descriptor,
registered: entry.registered
})));
bumpSnapshotVersion();
}
function removeConnectedNodePluginTools(nodeId) {
if (toolsByNodeId.delete(nodeId)) bumpSnapshotVersion();
}
function listConnectedNodePluginTools() {
return [...toolsByNodeId.values()].flat().toSorted((left, right) => left.descriptor.pluginId.localeCompare(right.descriptor.pluginId) || left.descriptor.name.localeCompare(right.descriptor.name) || left.nodeId.localeCompare(right.nodeId));
}
function getConnectedNodePluginToolsVersion() {
return snapshotVersion;
}
//#endregion
export { removeConnectedNodePluginTools as a, normalizeNodePluginToolDescriptors as i, getConnectedNodePluginToolsVersion as n, replaceConnectedNodePluginTools as o, listConnectedNodePluginTools as r, createRegisteredNodePluginToolDescriptorMap as t };