fix(agents): preserve unreadable wrapped tool schemas

This commit is contained in:
Vincent Koc
2026-06-04 11:24:21 +02:00
parent 05289f1aa0
commit 4b92f1df75
2 changed files with 78 additions and 6 deletions

View File

@@ -0,0 +1,57 @@
import { describe, expect, it, vi } from "vitest";
import type { AgentTool } from "../../runtime/index.js";
import type { ToolDefinition } from "../extensions/types.js";
import {
createToolDefinitionFromAgentTool,
wrapToolDefinition,
} from "./tool-definition-wrapper.js";
describe("tool definition wrapper", () => {
it("preserves an unreadable extension tool definition schema without crashing the wrapper", async () => {
const execute = vi.fn(async () => ({ content: [] }));
const prepareArguments = vi.fn((params: unknown) => params);
const definition = {
name: "hostile_definition",
label: "Hostile Definition",
description: "throws while reading parameters",
prepareArguments,
execute,
} as unknown as ToolDefinition;
Object.defineProperty(definition, "parameters", {
get() {
throw new Error("definition parameters exploded");
},
});
const tool = wrapToolDefinition(definition, () => ({}) as never);
expect(() => tool.parameters).toThrow("definition parameters exploded");
expect(tool.prepareArguments).toBe(prepareArguments);
await tool.execute("call-1", {}, undefined, undefined);
expect(execute).toHaveBeenCalledTimes(1);
});
it("preserves an unreadable agent tool schema without crashing definition synthesis", async () => {
const execute = vi.fn(async () => ({ content: [] }));
const prepareArguments = vi.fn((params: unknown) => params);
const tool = {
name: "hostile_agent_tool",
label: "Hostile Agent Tool",
description: "throws while reading parameters",
prepareArguments,
execute,
} as unknown as AgentTool;
Object.defineProperty(tool, "parameters", {
get() {
throw new Error("agent parameters exploded");
},
});
const definition = createToolDefinitionFromAgentTool(tool);
expect(() => definition.parameters).toThrow("agent parameters exploded");
expect(definition.prepareArguments).toBe(prepareArguments);
await definition.execute("call-1", {}, undefined, undefined, {} as never);
expect(execute).toHaveBeenCalledTimes(1);
});
});

View File

@@ -2,6 +2,19 @@ import type { TSchema } from "typebox";
import type { AgentTool } from "../../runtime/index.js";
import type { ExtensionContext, ToolDefinition } from "../extensions/types.js";
function defineForwardedParameters<TParams extends TSchema>(
target: { parameters?: TParams },
source: { parameters: TParams },
): void {
Object.defineProperty(target, "parameters", {
enumerable: true,
configurable: true,
get() {
return source.parameters;
},
});
}
/** Wrap a ToolDefinition into an AgentTool for the core runtime. */
export function wrapToolDefinition<
TParams extends TSchema = TSchema,
@@ -11,16 +24,17 @@ export function wrapToolDefinition<
definition: ToolDefinition<TParams, TDetails, TState>,
ctxFactory?: () => ExtensionContext,
): AgentTool<TParams, TDetails> {
return {
const tool = {
name: definition.name,
label: definition.label,
description: definition.description,
parameters: definition.parameters,
prepareArguments: definition.prepareArguments,
executionMode: definition.executionMode,
execute: (toolCallId, params, signal, onUpdate) =>
definition.execute(toolCallId, params, signal, onUpdate, ctxFactory?.() as ExtensionContext),
};
} as AgentTool<TParams, TDetails>;
defineForwardedParameters(tool, definition);
return tool;
}
/** Wrap multiple ToolDefinitions into AgentTools for the core runtime. */
@@ -38,14 +52,15 @@ export function wrapToolDefinitions(
* provides plain AgentTool overrides that do not include prompt metadata or renderers.
*/
export function createToolDefinitionFromAgentTool(tool: AgentTool): ToolDefinition {
return {
const definition = {
name: tool.name,
label: tool.label,
description: tool.description,
parameters: tool.parameters,
prepareArguments: tool.prepareArguments,
executionMode: tool.executionMode,
execute: async (toolCallId, params, signal, onUpdate) =>
tool.execute(toolCallId, params, signal, onUpdate),
};
} as ToolDefinition;
defineForwardedParameters(definition, tool);
return definition;
}