diff options
Diffstat (limited to 'packages/core/src')
21 files changed, 126 insertions, 18 deletions
diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index 59f1e1ba..9d47fb08 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -145,6 +145,7 @@ export interface ConfigParameters { model: string; extensionContextFilePaths?: string[]; maxSessionTurns?: number; + experimentalAcp?: boolean; listExtensions?: boolean; activeExtensions?: ActiveExtension[]; noBrowser?: boolean; @@ -199,6 +200,7 @@ export class Config { private readonly summarizeToolOutput: | Record<string, SummarizeToolOutputSettings> | undefined; + private readonly experimentalAcp: boolean = false; constructor(params: ConfigParameters) { this.sessionId = params.sessionId; @@ -241,6 +243,7 @@ export class Config { this.model = params.model; this.extensionContextFilePaths = params.extensionContextFilePaths ?? []; this.maxSessionTurns = params.maxSessionTurns ?? -1; + this.experimentalAcp = params.experimentalAcp ?? false; this.listExtensions = params.listExtensions ?? false; this._activeExtensions = params.activeExtensions ?? []; this.noBrowser = params.noBrowser ?? false; @@ -494,6 +497,10 @@ export class Config { return this.extensionContextFilePaths; } + getExperimentalAcp(): boolean { + return this.experimentalAcp; + } + getListExtensions(): boolean { return this.listExtensions; } diff --git a/packages/core/src/core/client.ts b/packages/core/src/core/client.ts index 1cffd6a3..7bb8cea4 100644 --- a/packages/core/src/core/client.ts +++ b/packages/core/src/core/client.ts @@ -221,7 +221,7 @@ export class GeminiClient { return initialParts; } - private async startChat(extraHistory?: Content[]): Promise<GeminiChat> { + async startChat(extraHistory?: Content[]): Promise<GeminiChat> { const envParts = await this.getEnvironment(); const toolRegistry = await this.config.getToolRegistry(); const toolDeclarations = toolRegistry.getFunctionDeclarations(); diff --git a/packages/core/src/core/coreToolScheduler.test.ts b/packages/core/src/core/coreToolScheduler.test.ts index 0b2c5124..94d4f7c1 100644 --- a/packages/core/src/core/coreToolScheduler.test.ts +++ b/packages/core/src/core/coreToolScheduler.test.ts @@ -19,6 +19,7 @@ import { ToolConfirmationPayload, ToolResult, Config, + Icon, } from '../index.js'; import { Part, PartListUnion } from '@google/genai'; @@ -29,7 +30,7 @@ class MockTool extends BaseTool<Record<string, unknown>, ToolResult> { executeFn = vi.fn(); constructor(name = 'mockTool') { - super(name, name, 'A mock tool', {}); + super(name, name, 'A mock tool', Icon.Hammer, {}); } async shouldConfirmExecute( @@ -91,6 +92,8 @@ class MockModifiableTool title: 'Confirm Mock Tool', fileName: 'test.txt', fileDiff: 'diff', + originalContent: 'originalContent', + newContent: 'newContent', onConfirm: async () => {}, }; } diff --git a/packages/core/src/core/nonInteractiveToolExecutor.test.ts b/packages/core/src/core/nonInteractiveToolExecutor.test.ts index 14b048b4..d52efb06 100644 --- a/packages/core/src/core/nonInteractiveToolExecutor.test.ts +++ b/packages/core/src/core/nonInteractiveToolExecutor.test.ts @@ -13,6 +13,7 @@ import { Tool, ToolCallConfirmationDetails, Config, + Icon, } from '../index.js'; import { Part, Type } from '@google/genai'; @@ -32,6 +33,7 @@ describe('executeToolCall', () => { name: 'testTool', displayName: 'Test Tool', description: 'A tool for testing', + icon: Icon.Hammer, schema: { name: 'testTool', description: 'A tool for testing', @@ -51,6 +53,7 @@ describe('executeToolCall', () => { isOutputMarkdown: false, canUpdateOutput: false, getDescription: vi.fn(), + toolLocations: vi.fn(() => []), }; mockToolRegistry = { diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 5f1dc3e7..ffc06866 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -33,6 +33,8 @@ export * from './utils/memoryDiscovery.js'; export * from './utils/gitIgnoreParser.js'; export * from './utils/editor.js'; export * from './utils/quotaErrorDetection.js'; +export * from './utils/fileUtils.js'; +export * from './utils/retry.js'; // Export services export * from './services/fileDiscoveryService.js'; diff --git a/packages/core/src/tools/edit.ts b/packages/core/src/tools/edit.ts index 8d8753d4..ccba3d72 100644 --- a/packages/core/src/tools/edit.ts +++ b/packages/core/src/tools/edit.ts @@ -9,9 +9,11 @@ import * as path from 'path'; import * as Diff from 'diff'; import { BaseTool, + Icon, ToolCallConfirmationDetails, ToolConfirmationOutcome, ToolEditConfirmationDetails, + ToolLocation, ToolResult, ToolResultDisplay, } from './tools.js'; @@ -89,6 +91,7 @@ Expectation for required parameters: 4. NEVER escape \`old_string\` or \`new_string\`, that would break the exact literal text requirement. **Important:** If ANY of the above are not satisfied, the tool will fail. CRITICAL for \`old_string\`: Must uniquely identify the single instance to change. Include at least 3 lines of context BEFORE and AFTER the target text, matching whitespace and indentation precisely. If this string matches multiple locations, or does not match exactly, the tool will fail. **Multiple replacements:** Set \`expected_replacements\` to the number of occurrences you want to replace. The tool will replace ALL occurrences that match \`old_string\` exactly. Ensure the number of replacements matches your expectation.`, + Icon.Pencil, { properties: { file_path: { @@ -141,6 +144,15 @@ Expectation for required parameters: return null; } + /** + * Determines any file locations affected by the tool execution + * @param params Parameters for the tool execution + * @returns A list of such paths + */ + toolLocations(params: EditToolParams): ToolLocation[] { + return [{ path: params.file_path }]; + } + private _applyReplacement( currentContent: string | null, oldString: string, @@ -306,6 +318,8 @@ Expectation for required parameters: title: `Confirm Edit: ${shortenPath(makeRelative(params.file_path, this.config.getTargetDir()))}`, fileName, fileDiff, + originalContent: editData.currentContent, + newContent: editData.newContent, onConfirm: async (outcome: ToolConfirmationOutcome) => { if (outcome === ToolConfirmationOutcome.ProceedAlways) { this.config.setApprovalMode(ApprovalMode.AUTO_EDIT); @@ -394,7 +408,12 @@ Expectation for required parameters: 'Proposed', DEFAULT_DIFF_OPTIONS, ); - displayResult = { fileDiff, fileName }; + displayResult = { + fileDiff, + fileName, + originalContent: editData.currentContent, + newContent: editData.newContent, + }; } const llmSuccessMessageParts = [ diff --git a/packages/core/src/tools/glob.ts b/packages/core/src/tools/glob.ts index 9381894e..417495fe 100644 --- a/packages/core/src/tools/glob.ts +++ b/packages/core/src/tools/glob.ts @@ -8,7 +8,7 @@ import fs from 'fs'; import path from 'path'; import { glob } from 'glob'; import { SchemaValidator } from '../utils/schemaValidator.js'; -import { BaseTool, ToolResult } from './tools.js'; +import { BaseTool, Icon, ToolResult } from './tools.js'; import { Type } from '@google/genai'; import { shortenPath, makeRelative } from '../utils/paths.js'; import { isWithinRoot } from '../utils/fileUtils.js'; @@ -86,6 +86,7 @@ export class GlobTool extends BaseTool<GlobToolParams, ToolResult> { GlobTool.Name, 'FindFiles', 'Efficiently finds files matching specific glob patterns (e.g., `src/**/*.ts`, `**/*.md`), returning absolute paths sorted by modification time (newest first). Ideal for quickly locating files based on their name or path structure, especially in large codebases.', + Icon.FileSearch, { properties: { pattern: { diff --git a/packages/core/src/tools/grep.ts b/packages/core/src/tools/grep.ts index afe83050..177bd1aa 100644 --- a/packages/core/src/tools/grep.ts +++ b/packages/core/src/tools/grep.ts @@ -10,7 +10,7 @@ import path from 'path'; import { EOL } from 'os'; import { spawn } from 'child_process'; import { globStream } from 'glob'; -import { BaseTool, ToolResult } from './tools.js'; +import { BaseTool, Icon, ToolResult } from './tools.js'; import { Type } from '@google/genai'; import { SchemaValidator } from '../utils/schemaValidator.js'; import { makeRelative, shortenPath } from '../utils/paths.js'; @@ -62,6 +62,7 @@ export class GrepTool extends BaseTool<GrepToolParams, ToolResult> { GrepTool.Name, 'SearchText', 'Searches for a regular expression pattern within the content of files in a specified directory (or current working directory). Can filter files by a glob pattern. Returns the lines containing matches, along with their file paths and line numbers.', + Icon.Regex, { properties: { pattern: { diff --git a/packages/core/src/tools/ls.ts b/packages/core/src/tools/ls.ts index 9fb60072..fc4f06dd 100644 --- a/packages/core/src/tools/ls.ts +++ b/packages/core/src/tools/ls.ts @@ -6,7 +6,7 @@ import fs from 'fs'; import path from 'path'; -import { BaseTool, ToolResult } from './tools.js'; +import { BaseTool, Icon, ToolResult } from './tools.js'; import { Type } from '@google/genai'; import { SchemaValidator } from '../utils/schemaValidator.js'; import { makeRelative, shortenPath } from '../utils/paths.js'; @@ -74,6 +74,7 @@ export class LSTool extends BaseTool<LSToolParams, ToolResult> { LSTool.Name, 'ReadFolder', 'Lists the names of files and subdirectories directly within a specified directory path. Can optionally ignore entries matching provided glob patterns.', + Icon.Folder, { properties: { path: { diff --git a/packages/core/src/tools/mcp-tool.ts b/packages/core/src/tools/mcp-tool.ts index 663ec6ee..aadc484a 100644 --- a/packages/core/src/tools/mcp-tool.ts +++ b/packages/core/src/tools/mcp-tool.ts @@ -10,6 +10,7 @@ import { ToolCallConfirmationDetails, ToolConfirmationOutcome, ToolMcpConfirmationDetails, + Icon, } from './tools.js'; import { CallableTool, @@ -38,6 +39,7 @@ export class DiscoveredMCPTool extends BaseTool<ToolParams, ToolResult> { name, `${serverToolName} (${serverName} MCP Server)`, description, + Icon.Hammer, { type: Type.OBJECT }, // this is a dummy Schema for MCP, will be not be used to construct the FunctionDeclaration true, // isOutputMarkdown false, // canUpdateOutput diff --git a/packages/core/src/tools/memoryTool.ts b/packages/core/src/tools/memoryTool.ts index b4a671b0..f0f1e16b 100644 --- a/packages/core/src/tools/memoryTool.ts +++ b/packages/core/src/tools/memoryTool.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { BaseTool, ToolResult } from './tools.js'; +import { BaseTool, Icon, ToolResult } from './tools.js'; import { FunctionDeclaration, Type } from '@google/genai'; import * as fs from 'fs/promises'; import * as path from 'path'; @@ -105,6 +105,7 @@ export class MemoryTool extends BaseTool<SaveMemoryParams, ToolResult> { MemoryTool.Name, 'Save Memory', memoryToolDescription, + Icon.LightBulb, memoryToolSchemaData.parameters as Record<string, unknown>, ); } diff --git a/packages/core/src/tools/read-file.ts b/packages/core/src/tools/read-file.ts index a2ff89c1..9ba80672 100644 --- a/packages/core/src/tools/read-file.ts +++ b/packages/core/src/tools/read-file.ts @@ -7,7 +7,7 @@ import path from 'path'; import { SchemaValidator } from '../utils/schemaValidator.js'; import { makeRelative, shortenPath } from '../utils/paths.js'; -import { BaseTool, ToolResult } from './tools.js'; +import { BaseTool, Icon, ToolLocation, ToolResult } from './tools.js'; import { Type } from '@google/genai'; import { isWithinRoot, @@ -51,6 +51,7 @@ export class ReadFileTool extends BaseTool<ReadFileToolParams, ToolResult> { ReadFileTool.Name, 'ReadFile', 'Reads and returns the content of a specified file from the local filesystem. Handles text, images (PNG, JPG, GIF, WEBP, SVG, BMP), and PDF files. For text files, it can read specific line ranges.', + Icon.FileSearch, { properties: { absolute_path: { @@ -118,6 +119,10 @@ export class ReadFileTool extends BaseTool<ReadFileToolParams, ToolResult> { return shortenPath(relativePath); } + toolLocations(params: ReadFileToolParams): ToolLocation[] { + return [{ path: params.absolute_path, line: params.offset }]; + } + async execute( params: ReadFileToolParams, _signal: AbortSignal, diff --git a/packages/core/src/tools/read-many-files.ts b/packages/core/src/tools/read-many-files.ts index c43841b5..1c01ee9f 100644 --- a/packages/core/src/tools/read-many-files.ts +++ b/packages/core/src/tools/read-many-files.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { BaseTool, ToolResult } from './tools.js'; +import { BaseTool, Icon, ToolResult } from './tools.js'; import { SchemaValidator } from '../utils/schemaValidator.js'; import { getErrorMessage } from '../utils/errors.js'; import * as path from 'path'; @@ -196,6 +196,7 @@ This tool is useful when you need to understand or analyze a collection of files - When the user asks to "read all files in X directory" or "show me the content of all Y files". Use this tool when the user's query implies needing the content of several files simultaneously for context, analysis, or summarization. For text files, it uses default UTF-8 encoding and a '--- {filePath} ---' separator between file contents. Ensure paths are relative to the target directory. Glob patterns like 'src/**/*.js' are supported. Avoid using for single files if a more specific single-file reading tool is available, unless the user specifically requests to process a list containing just one file via this tool. Other binary files (not explicitly requested as image/PDF) are generally skipped. Default excludes apply to common non-text files (except for explicitly requested images/PDFs) and large dependency directories unless 'useDefaultExcludes' is false.`, + Icon.FileSearch, parameterSchema, ); this.geminiIgnorePatterns = config diff --git a/packages/core/src/tools/shell.ts b/packages/core/src/tools/shell.ts index 3dc3d0a6..af514546 100644 --- a/packages/core/src/tools/shell.ts +++ b/packages/core/src/tools/shell.ts @@ -15,6 +15,7 @@ import { ToolCallConfirmationDetails, ToolExecuteConfirmationDetails, ToolConfirmationOutcome, + Icon, } from './tools.js'; import { Type } from '@google/genai'; import { SchemaValidator } from '../utils/schemaValidator.js'; @@ -52,6 +53,7 @@ Exit Code: Exit code or \`(none)\` if terminated by signal. Signal: Signal number or \`(none)\` if no signal was received. Background PIDs: List of background processes started or \`(none)\`. Process Group PGID: Process group started or \`(none)\``, + Icon.Terminal, { type: Type.OBJECT, properties: { diff --git a/packages/core/src/tools/tool-registry.test.ts b/packages/core/src/tools/tool-registry.test.ts index 38b058ea..ec709a44 100644 --- a/packages/core/src/tools/tool-registry.test.ts +++ b/packages/core/src/tools/tool-registry.test.ts @@ -14,14 +14,14 @@ import { afterEach, Mocked, } from 'vitest'; +import { Config, ConfigParameters, ApprovalMode } from '../config/config.js'; import { ToolRegistry, DiscoveredTool, sanitizeParameters, } from './tool-registry.js'; import { DiscoveredMCPTool } from './mcp-tool.js'; -import { Config, ConfigParameters, ApprovalMode } from '../config/config.js'; -import { BaseTool, ToolResult } from './tools.js'; +import { BaseTool, Icon, ToolResult } from './tools.js'; import { FunctionDeclaration, CallableTool, @@ -109,7 +109,7 @@ class MockTool extends BaseTool<{ param: string }, ToolResult> { displayName = 'A mock tool', description = 'A mock tool description', ) { - super(name, displayName, description, { + super(name, displayName, description, Icon.Hammer, { type: Type.OBJECT, properties: { param: { type: Type.STRING }, diff --git a/packages/core/src/tools/tool-registry.ts b/packages/core/src/tools/tool-registry.ts index d2303fc9..e6a4121d 100644 --- a/packages/core/src/tools/tool-registry.ts +++ b/packages/core/src/tools/tool-registry.ts @@ -5,7 +5,7 @@ */ import { FunctionDeclaration, Schema, Type } from '@google/genai'; -import { Tool, ToolResult, BaseTool } from './tools.js'; +import { Tool, ToolResult, BaseTool, Icon } from './tools.js'; import { Config } from '../config/config.js'; import { spawn } from 'node:child_process'; import { StringDecoder } from 'node:string_decoder'; @@ -44,6 +44,7 @@ Signal: Signal number or \`(none)\` if no signal was received. name, name, description, + Icon.Hammer, parameterSchema, false, // isOutputMarkdown false, // canUpdateOutput diff --git a/packages/core/src/tools/tools.ts b/packages/core/src/tools/tools.ts index 6f6d3f58..0d7b402a 100644 --- a/packages/core/src/tools/tools.ts +++ b/packages/core/src/tools/tools.ts @@ -29,6 +29,11 @@ export interface Tool< description: string; /** + * The icon to display when interacting via ACP + */ + icon: Icon; + + /** * Function declaration schema from @google/genai */ schema: FunctionDeclaration; @@ -61,6 +66,13 @@ export interface Tool< getDescription(params: TParams): string; /** + * Determines what file system paths the tool will affect + * @param params Parameters for the tool execution + * @returns A list of such paths + */ + toolLocations(params: TParams): ToolLocation[]; + + /** * Determines if the tool should prompt for confirmation before execution * @param params Parameters for the tool execution * @returns Whether execute should be confirmed. @@ -103,6 +115,7 @@ export abstract class BaseTool< readonly name: string, readonly displayName: string, readonly description: string, + readonly icon: Icon, readonly parameterSchema: Schema, readonly isOutputMarkdown: boolean = true, readonly canUpdateOutput: boolean = false, @@ -159,6 +172,18 @@ export abstract class BaseTool< } /** + * Determines what file system paths the tool will affect + * @param params Parameters for the tool execution + * @returns A list of such paths + */ + toolLocations( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + params: TParams, + ): ToolLocation[] { + return []; + } + + /** * Abstract method to execute the tool with the given parameters * Must be implemented by derived classes * @param params Parameters for the tool execution @@ -199,6 +224,8 @@ export type ToolResultDisplay = string | FileDiff; export interface FileDiff { fileDiff: string; fileName: string; + originalContent: string | null; + newContent: string; } export interface ToolEditConfirmationDetails { @@ -210,6 +237,8 @@ export interface ToolEditConfirmationDetails { ) => Promise<void>; fileName: string; fileDiff: string; + originalContent: string | null; + newContent: string; isModifying?: boolean; } @@ -258,3 +287,21 @@ export enum ToolConfirmationOutcome { ModifyWithEditor = 'modify_with_editor', Cancel = 'cancel', } + +export enum Icon { + FileSearch = 'fileSearch', + Folder = 'folder', + Globe = 'globe', + Hammer = 'hammer', + LightBulb = 'lightBulb', + Pencil = 'pencil', + Regex = 'regex', + Terminal = 'terminal', +} + +export interface ToolLocation { + // Absolute path to the file + path: string; + // Which line (if known) + line?: number; +} diff --git a/packages/core/src/tools/web-fetch.ts b/packages/core/src/tools/web-fetch.ts index ee06880e..c96cae6c 100644 --- a/packages/core/src/tools/web-fetch.ts +++ b/packages/core/src/tools/web-fetch.ts @@ -10,6 +10,7 @@ import { ToolResult, ToolCallConfirmationDetails, ToolConfirmationOutcome, + Icon, } from './tools.js'; import { Type } from '@google/genai'; import { getErrorMessage } from '../utils/errors.js'; @@ -70,6 +71,7 @@ export class WebFetchTool extends BaseTool<WebFetchToolParams, ToolResult> { WebFetchTool.Name, 'WebFetch', "Processes content from URL(s), including local and private network addresses (e.g., localhost), embedded in a prompt. Include up to 20 URLs and instructions (e.g., summarize, extract specific data) directly in the 'prompt' parameter.", + Icon.Globe, { properties: { prompt: { diff --git a/packages/core/src/tools/web-search.ts b/packages/core/src/tools/web-search.ts index 98be1f30..480cc7e7 100644 --- a/packages/core/src/tools/web-search.ts +++ b/packages/core/src/tools/web-search.ts @@ -5,7 +5,7 @@ */ import { GroundingMetadata } from '@google/genai'; -import { BaseTool, ToolResult } from './tools.js'; +import { BaseTool, Icon, ToolResult } from './tools.js'; import { Type } from '@google/genai'; import { SchemaValidator } from '../utils/schemaValidator.js'; @@ -69,6 +69,7 @@ export class WebSearchTool extends BaseTool< WebSearchTool.Name, 'GoogleSearch', 'Performs a web search using Google Search (via the Gemini API) and returns the results. This tool is useful for finding information on the internet based on a query.', + Icon.Globe, { type: Type.OBJECT, properties: { diff --git a/packages/core/src/tools/write-file.ts b/packages/core/src/tools/write-file.ts index a3756c69..ae37ca8a 100644 --- a/packages/core/src/tools/write-file.ts +++ b/packages/core/src/tools/write-file.ts @@ -15,6 +15,7 @@ import { ToolEditConfirmationDetails, ToolConfirmationOutcome, ToolCallConfirmationDetails, + Icon, } from './tools.js'; import { Type } from '@google/genai'; import { SchemaValidator } from '../utils/schemaValidator.js'; @@ -72,9 +73,10 @@ export class WriteFileTool super( WriteFileTool.Name, 'WriteFile', - `Writes content to a specified file in the local filesystem. - + `Writes content to a specified file in the local filesystem. + The user has the ability to modify \`content\`. If modified, this will be stated in the response.`, + Icon.Pencil, { properties: { file_path: { @@ -184,6 +186,8 @@ export class WriteFileTool title: `Confirm Write: ${shortenPath(relativePath)}`, fileName, fileDiff, + originalContent, + newContent: correctedContent, onConfirm: async (outcome: ToolConfirmationOutcome) => { if (outcome === ToolConfirmationOutcome.ProceedAlways) { this.config.setApprovalMode(ApprovalMode.AUTO_EDIT); @@ -269,7 +273,12 @@ export class WriteFileTool ); } - const displayResult: FileDiff = { fileDiff, fileName }; + const displayResult: FileDiff = { + fileDiff, + fileName, + originalContent: correctedContentResult.originalContent, + newContent: correctedContentResult.correctedContent, + }; const lines = fileContent.split('\n').length; const mimetype = getSpecificMimeType(params.file_path); diff --git a/packages/core/src/utils/retry.ts b/packages/core/src/utils/retry.ts index e5d65751..bf4532bc 100644 --- a/packages/core/src/utils/retry.ts +++ b/packages/core/src/utils/retry.ts @@ -216,7 +216,7 @@ export async function retryWithBackoff<T>( * @param error The error object. * @returns The HTTP status code, or undefined if not found. */ -function getErrorStatus(error: unknown): number | undefined { +export function getErrorStatus(error: unknown): number | undefined { if (typeof error === 'object' && error !== null) { if ('status' in error && typeof error.status === 'number') { return error.status; |
