Files
openclaw/src/gateway/server-methods/tools-invoke.ts
NVIDIAN ef0eb12615 feat(gateway): add SDK-facing tools.invoke RPC
Adds the SDK-facing tools.invoke Gateway RPC for #74705.

Reuses the /tools/invoke policy path for tool policy, deny-list, owner filtering, before-tool-call hooks, session/agent scoping, and plugin approval handling. Returns typed SDK approval/refusal/success results while preserving HTTP compatibility and uses idempotencyKey as the stable tool-call id.

Includes protocol schema exports, method scope/list registration, SDK helper/types, docs, generated Swift models, tests, and changelog credit.
2026-05-01 03:16:53 -05:00

87 lines
2.4 KiB
TypeScript

import { normalizeOptionalString } from "../../shared/string-coerce.js";
import { ADMIN_SCOPE } from "../method-scopes.js";
import {
ErrorCodes,
errorShape,
formatValidationErrors,
validateToolsInvokeParams,
type ToolsInvokeResult,
} from "../protocol/index.js";
import { invokeGatewayTool } from "../tools-invoke-shared.js";
import type { GatewayRequestHandlers } from "./types.js";
function resolveRpcErrorCode(params: {
type: "invalid_request" | "not_found" | "tool_call_blocked" | "tool_error";
requiresApproval?: boolean;
}): string {
if (params.requiresApproval) {
return "requires_approval";
}
switch (params.type) {
case "invalid_request":
return "validation_error";
case "not_found":
return "not_found";
case "tool_call_blocked":
return "forbidden";
case "tool_error":
return "internal_error";
}
return "internal_error";
}
export const toolsInvokeHandlers: GatewayRequestHandlers = {
"tools.invoke": async ({ params, client, respond, context }) => {
if (!validateToolsInvokeParams(params)) {
respond(
false,
undefined,
errorShape(
ErrorCodes.INVALID_REQUEST,
`invalid tools.invoke params: ${formatValidationErrors(validateToolsInvokeParams.errors)}`,
),
);
return;
}
const requestedToolName = normalizeOptionalString(params.name);
if (!requestedToolName) {
respond(
false,
undefined,
errorShape(ErrorCodes.INVALID_REQUEST, "invalid tools.invoke params: name required"),
);
return;
}
const outcome = await invokeGatewayTool({
cfg: context.getRuntimeConfig(),
input: params,
senderIsOwner: Boolean(client?.connect.scopes?.includes(ADMIN_SCOPE)),
toolCallIdPrefix: "rpc",
approvalMode: params.confirm === true ? "request" : "report",
});
if (outcome.ok) {
const payload: ToolsInvokeResult = {
ok: true,
toolName: outcome.toolName,
output: outcome.result,
source: outcome.source,
};
respond(true, payload, undefined);
return;
}
const payload: ToolsInvokeResult = {
ok: false,
toolName: outcome.toolName || requestedToolName,
...(outcome.error.requiresApproval ? { requiresApproval: true } : {}),
error: {
code: resolveRpcErrorCode(outcome.error),
message: outcome.error.message,
},
};
respond(true, payload, undefined);
},
};