diff options
Diffstat (limited to 'packages/core/src/tools')
| -rw-r--r-- | packages/core/src/tools/tool-error.ts | 1 | ||||
| -rw-r--r-- | packages/core/src/tools/tools.test.ts | 115 | ||||
| -rw-r--r-- | packages/core/src/tools/tools.ts | 58 |
3 files changed, 172 insertions, 2 deletions
diff --git a/packages/core/src/tools/tool-error.ts b/packages/core/src/tools/tool-error.ts index 2702de02..fc19fc72 100644 --- a/packages/core/src/tools/tool-error.ts +++ b/packages/core/src/tools/tool-error.ts @@ -13,6 +13,7 @@ export enum ToolErrorType { UNKNOWN = 'unknown', UNHANDLED_EXCEPTION = 'unhandled_exception', TOOL_NOT_REGISTERED = 'tool_not_registered', + EXECUTION_FAILED = 'execution_failed', // File System Errors FILE_NOT_FOUND = 'file_not_found', diff --git a/packages/core/src/tools/tools.test.ts b/packages/core/src/tools/tools.test.ts index 9942d3a9..a98d1121 100644 --- a/packages/core/src/tools/tools.test.ts +++ b/packages/core/src/tools/tools.test.ts @@ -4,8 +4,119 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { describe, it, expect } from 'vitest'; -import { hasCycleInSchema } from './tools.js'; // Added getStringifiedResultForDisplay +import { describe, it, expect, vi } from 'vitest'; +import { + DeclarativeTool, + hasCycleInSchema, + Kind, + ToolInvocation, + ToolResult, +} from './tools.js'; +import { ToolErrorType } from './tool-error.js'; + +class TestToolInvocation implements ToolInvocation<object, ToolResult> { + constructor( + readonly params: object, + private readonly executeFn: () => Promise<ToolResult>, + ) {} + + getDescription(): string { + return 'A test invocation'; + } + + toolLocations() { + return []; + } + + shouldConfirmExecute(): Promise<false> { + return Promise.resolve(false); + } + + execute(): Promise<ToolResult> { + return this.executeFn(); + } +} + +class TestTool extends DeclarativeTool<object, ToolResult> { + private readonly buildFn: (params: object) => TestToolInvocation; + + constructor(buildFn: (params: object) => TestToolInvocation) { + super('test-tool', 'Test Tool', 'A tool for testing', Kind.Other, {}); + this.buildFn = buildFn; + } + + build(params: object): ToolInvocation<object, ToolResult> { + return this.buildFn(params); + } +} + +describe('DeclarativeTool', () => { + describe('validateBuildAndExecute', () => { + const abortSignal = new AbortController().signal; + + it('should return INVALID_TOOL_PARAMS error if build fails', async () => { + const buildError = new Error('Invalid build parameters'); + const buildFn = vi.fn().mockImplementation(() => { + throw buildError; + }); + const tool = new TestTool(buildFn); + const params = { foo: 'bar' }; + + const result = await tool.validateBuildAndExecute(params, abortSignal); + + expect(buildFn).toHaveBeenCalledWith(params); + expect(result).toEqual({ + llmContent: `Error: Invalid parameters provided. Reason: ${buildError.message}`, + returnDisplay: buildError.message, + error: { + message: buildError.message, + type: ToolErrorType.INVALID_TOOL_PARAMS, + }, + }); + }); + + it('should return EXECUTION_FAILED error if execute fails', async () => { + const executeError = new Error('Execution failed'); + const executeFn = vi.fn().mockRejectedValue(executeError); + const invocation = new TestToolInvocation({}, executeFn); + const buildFn = vi.fn().mockReturnValue(invocation); + const tool = new TestTool(buildFn); + const params = { foo: 'bar' }; + + const result = await tool.validateBuildAndExecute(params, abortSignal); + + expect(buildFn).toHaveBeenCalledWith(params); + expect(executeFn).toHaveBeenCalled(); + expect(result).toEqual({ + llmContent: `Error: Tool call execution failed. Reason: ${executeError.message}`, + returnDisplay: executeError.message, + error: { + message: executeError.message, + type: ToolErrorType.EXECUTION_FAILED, + }, + }); + }); + + it('should return the result of execute on success', async () => { + const successResult: ToolResult = { + llmContent: 'Success!', + returnDisplay: 'Success!', + summary: 'Tool executed successfully', + }; + const executeFn = vi.fn().mockResolvedValue(successResult); + const invocation = new TestToolInvocation({}, executeFn); + const buildFn = vi.fn().mockReturnValue(invocation); + const tool = new TestTool(buildFn); + const params = { foo: 'bar' }; + + const result = await tool.validateBuildAndExecute(params, abortSignal); + + expect(buildFn).toHaveBeenCalledWith(params); + expect(executeFn).toHaveBeenCalled(); + expect(result).toEqual(successResult); + }); + }); +}); describe('hasCycleInSchema', () => { it('should detect a simple direct cycle', () => { diff --git a/packages/core/src/tools/tools.ts b/packages/core/src/tools/tools.ts index 68ea00af..deb54cf6 100644 --- a/packages/core/src/tools/tools.ts +++ b/packages/core/src/tools/tools.ts @@ -200,6 +200,64 @@ export abstract class DeclarativeTool< const invocation = this.build(params); return invocation.execute(signal, updateOutput); } + + /** + * Similar to `build` but never throws. + * @param params The raw, untrusted parameters from the model. + * @returns A `ToolInvocation` instance. + */ + private silentBuild( + params: TParams, + ): ToolInvocation<TParams, TResult> | Error { + try { + return this.build(params); + } catch (e) { + if (e instanceof Error) { + return e; + } + return new Error(String(e)); + } + } + + /** + * A convenience method that builds and executes the tool in one step. + * Never throws. + * @param params The raw, untrusted parameters from the model. + * @params abortSignal a signal to abort. + * @returns The result of the tool execution. + */ + async validateBuildAndExecute( + params: TParams, + abortSignal: AbortSignal, + ): Promise<ToolResult> { + const invocationOrError = this.silentBuild(params); + if (invocationOrError instanceof Error) { + const errorMessage = invocationOrError.message; + return { + llmContent: `Error: Invalid parameters provided. Reason: ${errorMessage}`, + returnDisplay: errorMessage, + error: { + message: errorMessage, + type: ToolErrorType.INVALID_TOOL_PARAMS, + }, + }; + } + + try { + return await invocationOrError.execute(abortSignal); + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : String(error); + return { + llmContent: `Error: Tool call execution failed. Reason: ${errorMessage}`, + returnDisplay: errorMessage, + error: { + message: errorMessage, + type: ToolErrorType.EXECUTION_FAILED, + }, + }; + } + } } /** |
