mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
fix(agents): guard tool definition schemas
This commit is contained in:
@@ -43,6 +43,24 @@ async function executeTool(tool: AgentTool, callId: string) {
|
||||
}
|
||||
|
||||
describe("agent tool definition adapter", () => {
|
||||
it("uses an empty object schema when an agent tool parameter getter throws", () => {
|
||||
const tool = {
|
||||
name: "hostile_schema",
|
||||
label: "Hostile Schema",
|
||||
description: "throws while reading parameters",
|
||||
execute: async () => ({ content: [] }),
|
||||
} as unknown as AgentTool;
|
||||
Object.defineProperty(tool, "parameters", {
|
||||
get() {
|
||||
throw new Error("schema unavailable");
|
||||
},
|
||||
});
|
||||
|
||||
const [def] = toToolDefinitions([tool]);
|
||||
|
||||
expect(def?.parameters).toEqual({ type: "object", properties: {} });
|
||||
});
|
||||
|
||||
it("wraps tool errors into a tool result", async () => {
|
||||
const result = await executeThrowingTool("boom", "call1");
|
||||
|
||||
@@ -140,6 +158,22 @@ async function executeClientTool(params: unknown): Promise<{
|
||||
}
|
||||
|
||||
describe("toClientToolDefinitions – param coercion", () => {
|
||||
it("uses an empty object schema when a client tool parameter getter throws", () => {
|
||||
const func = {
|
||||
name: "client_schema",
|
||||
description: "throws while reading parameters",
|
||||
} as ClientToolDefinition["function"];
|
||||
Object.defineProperty(func, "parameters", {
|
||||
get() {
|
||||
throw new Error("schema unavailable");
|
||||
},
|
||||
});
|
||||
|
||||
const [def] = toClientToolDefinitions([{ type: "function", function: func }]);
|
||||
|
||||
expect(def?.parameters).toEqual({ type: "object", properties: {} });
|
||||
});
|
||||
|
||||
it("returns terminal pending results for each client tool in a batch", async () => {
|
||||
const completed: Array<{ id: string; name: string; params: Record<string, unknown> }> = [];
|
||||
const defs = toClientToolDefinitions([makeClientTool("search"), makeClientTool("lookup")], {
|
||||
|
||||
@@ -53,6 +53,7 @@ const TOOL_ERROR_PARAM_PREVIEW_MAX_CHARS = 600;
|
||||
const TOOL_ERROR_EXEC_COMMAND_HASH_CHARS = 16;
|
||||
const SENSITIVE_EXEC_ENV_VALUE = "[omitted exec env value]";
|
||||
const EXEC_COMMAND_PARAM_KEYS = new Set(["command", "cmd"]);
|
||||
const EMPTY_OBJECT_TOOL_PARAMETERS = { type: "object", properties: {} } as ToolDefinition["parameters"];
|
||||
|
||||
export type ClientToolCallRecorder =
|
||||
| ((toolName: string, params: Record<string, unknown>) => void)
|
||||
@@ -135,6 +136,32 @@ function kindForLog(value: unknown): string {
|
||||
return typeof value;
|
||||
}
|
||||
|
||||
function resolveAgentToolParameters(tool: AnyAgentTool): ToolDefinition["parameters"] {
|
||||
try {
|
||||
return tool.parameters;
|
||||
} catch (err) {
|
||||
const described = describeToolExecutionError(err);
|
||||
logDebug(
|
||||
`tools: ${tool.name || "tool"} has unreadable parameter schema; using empty object schema: ${described.message}`,
|
||||
);
|
||||
return EMPTY_OBJECT_TOOL_PARAMETERS;
|
||||
}
|
||||
}
|
||||
|
||||
function resolveClientToolParameters(
|
||||
func: ClientToolDefinition["function"],
|
||||
): ToolDefinition["parameters"] {
|
||||
try {
|
||||
return func.parameters as ToolDefinition["parameters"];
|
||||
} catch (err) {
|
||||
const described = describeToolExecutionError(err);
|
||||
logDebug(
|
||||
`tools: ${func.name || "client_tool"} has unreadable client parameter schema; using empty object schema: ${described.message}`,
|
||||
);
|
||||
return EMPTY_OBJECT_TOOL_PARAMETERS;
|
||||
}
|
||||
}
|
||||
|
||||
function summarizeSensitiveValueForLog(params: {
|
||||
value: unknown;
|
||||
reason: string;
|
||||
@@ -358,7 +385,7 @@ export function toToolDefinitions(
|
||||
name,
|
||||
label: tool.label ?? name,
|
||||
description: tool.description ?? "",
|
||||
parameters: tool.parameters,
|
||||
parameters: resolveAgentToolParameters(tool),
|
||||
execute: async (...args: ToolExecuteArgs): Promise<AgentToolResult<unknown>> => {
|
||||
const { toolCallId, params, onUpdate, signal } = splitToolExecuteArgs(args);
|
||||
let executeParams = params;
|
||||
@@ -490,7 +517,7 @@ export function toClientToolDefinitions(
|
||||
name: func.name,
|
||||
label: func.name,
|
||||
description: func.description ?? "",
|
||||
parameters: func.parameters as ToolDefinition["parameters"],
|
||||
parameters: resolveClientToolParameters(func),
|
||||
execute: async (...args: ToolExecuteArgs): Promise<AgentToolResult<unknown>> => {
|
||||
const { toolCallId, params } = splitToolExecuteArgs(args);
|
||||
if (onClientToolCall && typeof onClientToolCall !== "function") {
|
||||
|
||||
Reference in New Issue
Block a user