diff options
| author | joshualitt <[email protected]> | 2025-08-06 10:50:02 -0700 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-08-06 17:50:02 +0000 |
| commit | 6133bea388a2de69c71a6be6f1450707f2ce4dfb (patch) | |
| tree | 367de1d618069ea80e47d7e86c4fb8f82ad032a7 /packages/core/src/tools/tools.ts | |
| parent | 882a97aff998b2f19731e9966d135f1db5a59914 (diff) | |
feat(core): Introduce `DeclarativeTool` and `ToolInvocation`. (#5613)
Diffstat (limited to 'packages/core/src/tools/tools.ts')
| -rw-r--r-- | packages/core/src/tools/tools.ts | 255 |
1 files changed, 202 insertions, 53 deletions
diff --git a/packages/core/src/tools/tools.ts b/packages/core/src/tools/tools.ts index 3404093f..79e6f010 100644 --- a/packages/core/src/tools/tools.ts +++ b/packages/core/src/tools/tools.ts @@ -9,101 +9,243 @@ import { ToolErrorType } from './tool-error.js'; import { DiffUpdateResult } from '../ide/ideContext.js'; /** - * Interface representing the base Tool functionality + * Represents a validated and ready-to-execute tool call. + * An instance of this is created by a `ToolBuilder`. */ -export interface Tool< - TParams = unknown, - TResult extends ToolResult = ToolResult, +export interface ToolInvocation< + TParams extends object, + TResult extends ToolResult, > { /** - * The internal name of the tool (used for API calls) + * The validated parameters for this specific invocation. + */ + params: TParams; + + /** + * Gets a pre-execution description of the tool operation. + * @returns A markdown string describing what the tool will do. + */ + getDescription(): string; + + /** + * Determines what file system paths the tool will affect. + * @returns A list of such paths. + */ + toolLocations(): ToolLocation[]; + + /** + * Determines if the tool should prompt for confirmation before execution. + * @returns Confirmation details or false if no confirmation is needed. + */ + shouldConfirmExecute( + abortSignal: AbortSignal, + ): Promise<ToolCallConfirmationDetails | false>; + + /** + * Executes the tool with the validated parameters. + * @param signal AbortSignal for tool cancellation. + * @param updateOutput Optional callback to stream output. + * @returns Result of the tool execution. + */ + execute( + signal: AbortSignal, + updateOutput?: (output: string) => void, + ): Promise<TResult>; +} + +/** + * A type alias for a tool invocation where the specific parameter and result types are not known. + */ +export type AnyToolInvocation = ToolInvocation<object, ToolResult>; + +/** + * An adapter that wraps the legacy `Tool` interface to make it compatible + * with the new `ToolInvocation` pattern. + */ +export class LegacyToolInvocation< + TParams extends object, + TResult extends ToolResult, +> implements ToolInvocation<TParams, TResult> +{ + constructor( + private readonly legacyTool: BaseTool<TParams, TResult>, + readonly params: TParams, + ) {} + + getDescription(): string { + return this.legacyTool.getDescription(this.params); + } + + toolLocations(): ToolLocation[] { + return this.legacyTool.toolLocations(this.params); + } + + shouldConfirmExecute( + abortSignal: AbortSignal, + ): Promise<ToolCallConfirmationDetails | false> { + return this.legacyTool.shouldConfirmExecute(this.params, abortSignal); + } + + execute( + signal: AbortSignal, + updateOutput?: (output: string) => void, + ): Promise<TResult> { + return this.legacyTool.execute(this.params, signal, updateOutput); + } +} + +/** + * Interface for a tool builder that validates parameters and creates invocations. + */ +export interface ToolBuilder< + TParams extends object, + TResult extends ToolResult, +> { + /** + * The internal name of the tool (used for API calls). */ name: string; /** - * The user-friendly display name of the tool + * The user-friendly display name of the tool. */ displayName: string; /** - * Description of what the tool does + * Description of what the tool does. */ description: string; /** - * The icon to display when interacting via ACP + * The icon to display when interacting via ACP. */ icon: Icon; /** - * Function declaration schema from @google/genai + * Function declaration schema from @google/genai. */ schema: FunctionDeclaration; /** - * Whether the tool's output should be rendered as markdown + * Whether the tool's output should be rendered as markdown. */ isOutputMarkdown: boolean; /** - * Whether the tool supports live (streaming) output + * Whether the tool supports live (streaming) output. */ canUpdateOutput: boolean; /** - * Validates the parameters for the tool - * Should be called from both `shouldConfirmExecute` and `execute` - * `shouldConfirmExecute` should return false immediately if invalid - * @param params Parameters to validate - * @returns An error message string if invalid, null otherwise + * Validates raw parameters and builds a ready-to-execute invocation. + * @param params The raw, untrusted parameters from the model. + * @returns A valid `ToolInvocation` if successful. Throws an error if validation fails. */ - validateToolParams(params: TParams): string | null; + build(params: TParams): ToolInvocation<TParams, TResult>; +} - /** - * Gets a pre-execution description of the tool operation - * @param params Parameters for the tool execution - * @returns A markdown string describing what the tool will do - * Optional for backward compatibility - */ - getDescription(params: TParams): string; +/** + * New base class for tools that separates validation from execution. + * New tools should extend this class. + */ +export abstract class DeclarativeTool< + TParams extends object, + TResult extends ToolResult, +> implements ToolBuilder<TParams, TResult> +{ + constructor( + readonly name: string, + readonly displayName: string, + readonly description: string, + readonly icon: Icon, + readonly parameterSchema: Schema, + readonly isOutputMarkdown: boolean = true, + readonly canUpdateOutput: boolean = false, + ) {} + + get schema(): FunctionDeclaration { + return { + name: this.name, + description: this.description, + parameters: this.parameterSchema, + }; + } /** - * Determines what file system paths the tool will affect - * @param params Parameters for the tool execution - * @returns A list of such paths + * Validates the raw tool parameters. + * Subclasses should override this to add custom validation logic + * beyond the JSON schema check. + * @param params The raw parameters from the model. + * @returns An error message string if invalid, null otherwise. */ - toolLocations(params: TParams): ToolLocation[]; + protected validateToolParams(_params: TParams): string | null { + // Base implementation can be extended by subclasses. + return null; + } /** - * Determines if the tool should prompt for confirmation before execution - * @param params Parameters for the tool execution - * @returns Whether execute should be confirmed. + * The core of the new pattern. It validates parameters and, if successful, + * returns a `ToolInvocation` object that encapsulates the logic for the + * specific, validated call. + * @param params The raw, untrusted parameters from the model. + * @returns A `ToolInvocation` instance. */ - shouldConfirmExecute( - params: TParams, - abortSignal: AbortSignal, - ): Promise<ToolCallConfirmationDetails | false>; + abstract build(params: TParams): ToolInvocation<TParams, TResult>; /** - * Executes the tool with the given parameters - * @param params Parameters for the tool execution - * @returns Result of the tool execution + * A convenience method that builds and executes the tool in one step. + * Throws an error if validation fails. + * @param params The raw, untrusted parameters from the model. + * @param signal AbortSignal for tool cancellation. + * @param updateOutput Optional callback to stream output. + * @returns The result of the tool execution. */ - execute( + async buildAndExecute( params: TParams, signal: AbortSignal, updateOutput?: (output: string) => void, - ): Promise<TResult>; + ): Promise<TResult> { + const invocation = this.build(params); + return invocation.execute(signal, updateOutput); + } +} + +/** + * New base class for declarative tools that separates validation from execution. + * New tools should extend this class, which provides a `build` method that + * validates parameters before deferring to a `createInvocation` method for + * the final `ToolInvocation` object instantiation. + */ +export abstract class BaseDeclarativeTool< + TParams extends object, + TResult extends ToolResult, +> extends DeclarativeTool<TParams, TResult> { + build(params: TParams): ToolInvocation<TParams, TResult> { + const validationError = this.validateToolParams(params); + if (validationError) { + throw new Error(validationError); + } + return this.createInvocation(params); + } + + protected abstract createInvocation( + params: TParams, + ): ToolInvocation<TParams, TResult>; } /** + * A type alias for a declarative tool where the specific parameter and result types are not known. + */ +export type AnyDeclarativeTool = DeclarativeTool<object, ToolResult>; + +/** * Base implementation for tools with common functionality + * @deprecated Use `DeclarativeTool` for new tools. */ export abstract class BaseTool< - TParams = unknown, + TParams extends object, TResult extends ToolResult = ToolResult, -> implements Tool<TParams, TResult> -{ +> extends DeclarativeTool<TParams, TResult> { /** * Creates a new instance of BaseTool * @param name Internal name of the tool (used for API calls) @@ -121,17 +263,24 @@ export abstract class BaseTool< readonly parameterSchema: Schema, readonly isOutputMarkdown: boolean = true, readonly canUpdateOutput: boolean = false, - ) {} + ) { + super( + name, + displayName, + description, + icon, + parameterSchema, + isOutputMarkdown, + canUpdateOutput, + ); + } - /** - * Function declaration schema computed from name, description, and parameterSchema - */ - get schema(): FunctionDeclaration { - return { - name: this.name, - description: this.description, - parameters: this.parameterSchema, - }; + build(params: TParams): ToolInvocation<TParams, TResult> { + const validationError = this.validateToolParams(params); + if (validationError) { + throw new Error(validationError); + } + return new LegacyToolInvocation(this, params); } /** |
