summaryrefslogtreecommitdiff
path: root/packages/core/src/tools/tools.ts
diff options
context:
space:
mode:
authorjoshualitt <[email protected]>2025-08-06 10:50:02 -0700
committerGitHub <[email protected]>2025-08-06 17:50:02 +0000
commit6133bea388a2de69c71a6be6f1450707f2ce4dfb (patch)
tree367de1d618069ea80e47d7e86c4fb8f82ad032a7 /packages/core/src/tools/tools.ts
parent882a97aff998b2f19731e9966d135f1db5a59914 (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.ts255
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);
}
/**