diff --git a/src/agents/sessions/tools/tool-definition-wrapper.test.ts b/src/agents/sessions/tools/tool-definition-wrapper.test.ts new file mode 100644 index 000000000000..8d0e1bed42ff --- /dev/null +++ b/src/agents/sessions/tools/tool-definition-wrapper.test.ts @@ -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); + }); +}); diff --git a/src/agents/sessions/tools/tool-definition-wrapper.ts b/src/agents/sessions/tools/tool-definition-wrapper.ts index ad5f02670ebd..00e0309cce6e 100644 --- a/src/agents/sessions/tools/tool-definition-wrapper.ts +++ b/src/agents/sessions/tools/tool-definition-wrapper.ts @@ -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( + 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, ctxFactory?: () => ExtensionContext, ): AgentTool { - 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; + 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; }