UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

214 lines (213 loc) 9.07 kB
import { s as normalizeOptionalLowercaseString } from "./string-coerce-mnp54Vah.js"; import { t as callGatewayTool } from "./gateway-Dq9H8-iz.js"; import { n as parsePairingList, t as parseNodeList } from "./node-list-parse-BSOtZszw.js"; import { n as resolveNodeIdFromNodeList, t as resolveNodeFromNodeList } from "./node-resolve-BawtWMwa.js"; //#region src/agents/agent-tools.params.ts const RETRY_GUIDANCE_SUFFIX = " Supply correct parameters before retrying."; const XML_ARG_VALUE_SUFFIX_RE = /<\/arg_value>>+$/; const XML_ARG_VALUE_PATH_PARAM_KEYS = new Set(["path"]); function parameterValidationError(message) { return /* @__PURE__ */ new Error(`${message}.${RETRY_GUIDANCE_SUFFIX}`); } function describeReceivedParamValue(value, allowEmpty = false) { if (value === void 0 || value === null) return; if (typeof value === "string") { if (allowEmpty || value.trim().length > 0) return; return "<empty-string>"; } if (Array.isArray(value)) return "<array>"; return `<${typeof value}>`; } function formatReceivedParamHint(record, groups) { const allowEmptyKeys = /* @__PURE__ */ new Set(); for (const group of groups) if (group.allowEmpty) for (const key of group.keys) allowEmptyKeys.add(key); const received = []; for (const key of Object.keys(record)) { const detail = describeReceivedParamValue(record[key], allowEmptyKeys.has(key)); if (record[key] === void 0 || record[key] === null) continue; received.push(detail ? `${key}=${detail}` : key); } return received.length > 0 ? ` (received: ${received.join(", ")})` : ""; } function isValidEditReplacement(value) { if (!value || typeof value !== "object") return false; const record = value; return typeof record.oldText === "string" && record.oldText.trim().length > 0 && typeof record.newText === "string"; } function hasValidEditReplacements(record) { const edits = record.edits; return Array.isArray(edits) && edits.length > 0 && edits.every((entry) => isValidEditReplacement(entry)); } /** Required parameter groups for file-style tools that need retry guidance. */ const REQUIRED_PARAM_GROUPS = { read: [{ keys: ["path"], label: "path" }], write: [{ keys: ["path"], label: "path" }, { keys: ["content"], label: "content" }], edit: [{ keys: ["path"], label: "path" }, { keys: ["edits"], label: "edits", validator: hasValidEditReplacements }] }; /** Return a record view of model-supplied tool params when possible. */ function getToolParamsRecord(params) { return params && typeof params === "object" ? params : void 0; } /** Strip extra closing markers sometimes produced in XML arg_value path params. */ function stripMalformedXmlArgValueSuffix(value) { return value.includes("</arg_value>") ? value.replace(XML_ARG_VALUE_SUFFIX_RE, "") : value; } /** Strip malformed XML suffixes from selected string fields without mutating input. */ function stripMalformedXmlArgValueSuffixFromKeys(record, keys) { let normalized; for (const key of keys) { const value = record[key]; if (typeof value !== "string") continue; const stripped = stripMalformedXmlArgValueSuffix(value); if (stripped !== value) { normalized ??= { ...record }; normalized[key] = stripped; } } return normalized ?? record; } function resolveMalformedXmlArgValuePathKeys(groups) { const keys = /* @__PURE__ */ new Set(); for (const group of groups ?? []) for (const key of group.keys) if (XML_ARG_VALUE_PATH_PARAM_KEYS.has(key)) keys.add(key); return [...keys]; } /** Throw actionable retry guidance when required tool params are missing. */ function assertRequiredParams(record, groups, toolName) { if (!record || typeof record !== "object") throw parameterValidationError(`Missing parameters for ${toolName}`); const missingLabels = []; for (const group of groups) if (!(group.validator?.(record) ?? group.keys.some((key) => { if (!(key in record)) return false; const value = record[key]; if (typeof value !== "string") return false; if (group.allowEmpty) return true; return value.trim().length > 0; }))) { const label = group.label ?? group.keys.join(" or "); missingLabels.push(label); } if (missingLabels.length > 0) { const joined = missingLabels.join(", "); throw parameterValidationError(`Missing required ${missingLabels.length === 1 ? "parameter" : "parameters"}: ${joined}${formatReceivedParamHint(record, groups)}`); } } /** Wrap a tool execute function with required-parameter validation. */ function wrapToolParamValidation(tool, requiredParamGroups) { return { ...tool, execute: async (toolCallId, params, signal, onUpdate) => { const record = getToolParamsRecord(params); const pathKeys = resolveMalformedXmlArgValuePathKeys(requiredParamGroups); const normalizedParams = record && pathKeys.length > 0 ? stripMalformedXmlArgValueSuffixFromKeys(record, pathKeys) : params; if (requiredParamGroups?.length) assertRequiredParams(getToolParamsRecord(normalizedParams), requiredParamGroups, tool.name); return tool.execute(toolCallId, normalizedParams, signal, onUpdate); } }; } //#endregion //#region src/agents/tools/nodes-utils.ts /** * Nodes lookup helpers. * * Loads paired nodes from Gateway and resolves requested/default nodes with legacy pair-list fallback. */ function messageFromError(error) { if (error instanceof Error) return error.message; if (typeof error === "string") return error; if (typeof error === "object" && error !== null && "message" in error && typeof error.message === "string") return error.message; if (typeof error === "object" && error !== null) try { return JSON.stringify(error); } catch { return ""; } return ""; } function shouldFallbackToPairList(error) { const message = normalizeOptionalLowercaseString(messageFromError(error)) ?? ""; if (!message.includes("node.list")) return false; return message.includes("unknown method") || message.includes("method not found") || message.includes("not implemented") || message.includes("unsupported"); } async function loadNodes(opts) { try { return parseNodeList(await callGatewayTool("node.list", opts, {})); } catch (error) { if (!shouldFallbackToPairList(error)) throw error; const { paired } = parsePairingList(await callGatewayTool("node.pair.list", opts, {})); return paired.map((n) => ({ nodeId: n.nodeId, displayName: n.displayName, platform: n.platform, remoteIp: n.remoteIp })); } } function isLocalMacNode(node) { return normalizeOptionalLowercaseString(node.platform)?.startsWith("mac") === true && typeof node.nodeId === "string" && node.nodeId.startsWith("mac-"); } function compareDefaultNodeOrder(a, b) { const aConnectedAt = Number.isFinite(a.connectedAtMs) ? a.connectedAtMs ?? 0 : -1; const bConnectedAt = Number.isFinite(b.connectedAtMs) ? b.connectedAtMs ?? 0 : -1; if (aConnectedAt !== bConnectedAt) return bConnectedAt - aConnectedAt; return a.nodeId.localeCompare(b.nodeId); } /** Selects the implicit node target when a tool call omits an explicit node query. */ function selectDefaultNodeFromList(nodes, options = {}) { const capability = options.capability?.trim(); const withCapability = capability ? nodes.filter((n) => Array.isArray(n.caps) ? n.caps.includes(capability) : true) : nodes; if (withCapability.length === 0) return null; const connected = withCapability.filter((n) => n.connected); const candidates = connected.length > 0 ? connected : withCapability; if (candidates.length === 1) return candidates[0]; if (options.preferLocalMac ?? true) { const local = candidates.filter(isLocalMacNode); if (local.length === 1) return local[0]; } if ((options.fallback ?? "none") === "none") return null; return [...candidates].toSorted(compareDefaultNodeOrder)[0] ?? null; } function pickDefaultNode(nodes) { return selectDefaultNodeFromList(nodes, { capability: "canvas", fallback: "first", preferLocalMac: true }); } /** Lists Gateway nodes, falling back to paired-node records for older Gateway versions. */ async function listNodes(opts) { return loadNodes(opts); } /** Resolves a node id from an already-loaded node list using shared node matching rules. */ function resolveNodeIdFromList(nodes, query, allowDefault = false) { return resolveNodeIdFromNodeList(nodes, query, { allowDefault, pickDefaultNode }); } /** Loads nodes from the Gateway and resolves the requested or default node id. */ async function resolveNodeId(opts, query, allowDefault = false) { return (await resolveNode(opts, query, allowDefault)).nodeId; } /** Loads nodes from the Gateway and returns the requested or default node record. */ async function resolveNode(opts, query, allowDefault = false) { return resolveNodeFromNodeList(await loadNodes(opts), query, { allowDefault, pickDefaultNode }); } //#endregion export { selectDefaultNodeFromList as a, getToolParamsRecord as c, wrapToolParamValidation as d, resolveNodeIdFromList as i, stripMalformedXmlArgValueSuffix as l, resolveNode as n, REQUIRED_PARAM_GROUPS as o, resolveNodeId as r, assertRequiredParams as s, listNodes as t, stripMalformedXmlArgValueSuffixFromKeys as u };