From aff5da63cbbcc194fa53f18e37c887cb90d4a113 Mon Sep 17 00:00:00 2001 From: Aishanee Shah Date: Tue, 17 Feb 2026 05:11:04 +0000 Subject: [PATCH] feat: refactor bulky tools to improve token efficiency --- packages/core/src/config/config.ts | 2 +- packages/core/src/prompts/promptProvider.ts | 12 ++++++++++++ packages/core/src/prompts/snippets.legacy.ts | 12 ++++++++++++ packages/core/src/prompts/snippets.ts | 12 ++++++++++++ .../coreToolsModelSnapshots.test.ts.snap | 16 ++++++++-------- .../core/src/tools/definitions/coreTools.ts | 8 ++++---- .../core/src/tools/definitions/resolver.ts | 2 +- packages/core/src/tools/definitions/types.ts | 6 ++++-- packages/core/src/tools/edit.ts | 12 ++++++++++-- packages/core/src/tools/tools.ts | 1 + packages/core/src/tools/write-todos.ts | 18 +++++++++++++++--- 11 files changed, 80 insertions(+), 21 deletions(-) diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index 6dfc62f322..81c659607d 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -2440,7 +2440,7 @@ export class Config { ); if (this.getUseWriteTodos()) { maybeRegister(WriteTodosTool, () => - registry.registerTool(new WriteTodosTool(this.messageBus)), + registry.registerTool(new WriteTodosTool(this, this.messageBus)), ); } if (this.isPlanEnabled()) { diff --git a/packages/core/src/prompts/promptProvider.ts b/packages/core/src/prompts/promptProvider.ts index 2b7b7854eb..d19b3e6683 100644 --- a/packages/core/src/prompts/promptProvider.ts +++ b/packages/core/src/prompts/promptProvider.ts @@ -63,6 +63,16 @@ export class PromptProvider { const activeSnippets = isModernModel ? snippets : legacySnippets; const contextFilenames = getAllGeminiMdFilenames(); + const registry = + typeof config.getToolRegistry === 'function' + ? config.getToolRegistry() + : undefined; + const allTools = + typeof registry?.getAllTools === 'function' ? registry.getAllTools() : []; + const toolInstructions = allTools + .map((t) => t.instructions) + .filter((i): i is string => !!i); + // --- Context Gathering --- let planModeToolsList = PLAN_MODE_TOOLS.filter((t) => enabledToolNames.has(t), @@ -201,6 +211,8 @@ export class PromptProvider { : this.withSection('finalReminder', () => ({ readFileToolName: READ_FILE_TOOL_NAME, })), + toolInstructions: + toolInstructions.length > 0 ? toolInstructions : undefined, } as snippets.SystemPromptOptions; // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion diff --git a/packages/core/src/prompts/snippets.legacy.ts b/packages/core/src/prompts/snippets.legacy.ts index 8d46fd6a1a..a872dc874a 100644 --- a/packages/core/src/prompts/snippets.legacy.ts +++ b/packages/core/src/prompts/snippets.legacy.ts @@ -35,6 +35,7 @@ export interface SystemPromptOptions { interactiveYoloMode?: boolean; gitRepo?: GitRepoOptions; finalReminder?: FinalReminderOptions; + toolInstructions?: string[]; } export interface PreambleOptions { @@ -113,6 +114,8 @@ ${ : renderPrimaryWorkflows(options.primaryWorkflows) } +${renderToolInstructions(options.toolInstructions)} + ${renderOperationalGuidelines(options.operationalGuidelines)} ${renderInteractiveYoloMode(options.interactiveYoloMode)} @@ -244,6 +247,15 @@ ${newApplicationSteps(options)} `.trim(); } +export function renderToolInstructions(instructions?: string[]): string { + if (!instructions || instructions.length === 0) return ''; + return ` +# Detailed Tool Reference + +${instructions.join('\n\n')} +`.trim(); +} + export function renderOperationalGuidelines( options?: OperationalGuidelinesOptions, ): string { diff --git a/packages/core/src/prompts/snippets.ts b/packages/core/src/prompts/snippets.ts index a556a1b42d..d1190cb2f4 100644 --- a/packages/core/src/prompts/snippets.ts +++ b/packages/core/src/prompts/snippets.ts @@ -35,6 +35,7 @@ export interface SystemPromptOptions { sandbox?: SandboxMode; interactiveYoloMode?: boolean; gitRepo?: GitRepoOptions; + toolInstructions?: string[]; } export interface PreambleOptions { @@ -110,6 +111,8 @@ ${ : renderPrimaryWorkflows(options.primaryWorkflows) } +${renderToolInstructions(options.toolInstructions)} + ${renderOperationalGuidelines(options.operationalGuidelines)} ${renderInteractiveYoloMode(options.interactiveYoloMode)} @@ -268,6 +271,15 @@ ${newApplicationSteps(options)} `.trim(); } +export function renderToolInstructions(instructions?: string[]): string { + if (!instructions || instructions.length === 0) return ''; + return ` +# Detailed Tool Reference + +${instructions.join('\n\n')} +`.trim(); +} + export function renderOperationalGuidelines( options?: OperationalGuidelinesOptions, ): string { diff --git a/packages/core/src/tools/definitions/__snapshots__/coreToolsModelSnapshots.test.ts.snap b/packages/core/src/tools/definitions/__snapshots__/coreToolsModelSnapshots.test.ts.snap index 99180cc735..b954732919 100644 --- a/packages/core/src/tools/definitions/__snapshots__/coreToolsModelSnapshots.test.ts.snap +++ b/packages/core/src/tools/definitions/__snapshots__/coreToolsModelSnapshots.test.ts.snap @@ -506,8 +506,8 @@ exports[`coreTools snapshots for specific models > Model: gemini-2.5-pro > snaps { "description": "Replaces text within a file. By default, replaces a single occurrence, but can replace multiple occurrences when \`expected_replacements\` is specified. This tool requires providing significant context around the change to ensure precise targeting. Always use the read_file tool to examine the file's current content before attempting a text replacement. - The user has the ability to modify the \`new_string\` content. If modified, this will be stated in the response. - + The user has the ability to modify the \`new_string\` content. If modified, this will be stated in the response.", + "instructions": " Expectation for required parameters: 1. \`old_string\` MUST be the exact literal text to replace (including all whitespace, indentation, newlines, and surrounding code etc.). 2. \`new_string\` MUST be the exact literal text to replace \`old_string\` with (also including all whitespace, indentation, newlines, and surrounding code etc.). Ensure the resulting code is correct and idiomatic and that \`old_string\` and \`new_string\` are different. @@ -684,8 +684,8 @@ exports[`coreTools snapshots for specific models > Model: gemini-2.5-pro > snaps exports[`coreTools snapshots for specific models > Model: gemini-2.5-pro > snapshot for tool: write_todos 1`] = ` { - "description": "This tool can help you list out the current subtasks that are required to be completed for a given user request. The list of subtasks helps you keep track of the current task, organize complex queries and help ensure that you don't miss any steps. With this list, the user can also see the current progress you are making in executing a given task. - + "description": "This tool can help you list out the current subtasks that are required to be completed for a given user request. The list of subtasks helps you keep track of the current task, organize complex queries and help ensure that you don't miss any steps. With this list, the user can also see the current progress you are making in executing a given task.", + "instructions": " Depending on the task complexity, you should first divide a given task into subtasks and then use this tool to list out the subtasks that are required to be completed for a given user request. Each of the subtasks should be clear and distinct. @@ -1295,8 +1295,8 @@ exports[`coreTools snapshots for specific models > Model: gemini-3-pro-preview > { "description": "Replaces text within a file. By default, replaces a single occurrence, but can replace multiple occurrences when \`expected_replacements\` is specified. This tool requires providing significant context around the change to ensure precise targeting. Always use the read_file tool to examine the file's current content before attempting a text replacement. - The user has the ability to modify the \`new_string\` content. If modified, this will be stated in the response. - + The user has the ability to modify the \`new_string\` content. If modified, this will be stated in the response.", + "instructions": " Expectation for required parameters: 1. \`old_string\` MUST be the exact literal text to replace (including all whitespace, indentation, newlines, and surrounding code etc.). 2. \`new_string\` MUST be the exact literal text to replace \`old_string\` with (also including all whitespace, indentation, newlines, and surrounding code etc.). Ensure the resulting code is correct and idiomatic and that \`old_string\` and \`new_string\` are different. @@ -1473,8 +1473,8 @@ exports[`coreTools snapshots for specific models > Model: gemini-3-pro-preview > exports[`coreTools snapshots for specific models > Model: gemini-3-pro-preview > snapshot for tool: write_todos 1`] = ` { - "description": "This tool can help you list out the current subtasks that are required to be completed for a given user request. The list of subtasks helps you keep track of the current task, organize complex queries and help ensure that you don't miss any steps. With this list, the user can also see the current progress you are making in executing a given task. - + "description": "This tool can help you list out the current subtasks that are required to be completed for a given user request. The list of subtasks helps you keep track of the current task, organize complex queries and help ensure that you don't miss any steps. With this list, the user can also see the current progress you are making in executing a given task.", + "instructions": " Depending on the task complexity, you should first divide a given task into subtasks and then use this tool to list out the subtasks that are required to be completed for a given user request. Each of the subtasks should be clear and distinct. diff --git a/packages/core/src/tools/definitions/coreTools.ts b/packages/core/src/tools/definitions/coreTools.ts index c1f5976eaa..9a4bcfc664 100644 --- a/packages/core/src/tools/definitions/coreTools.ts +++ b/packages/core/src/tools/definitions/coreTools.ts @@ -269,8 +269,8 @@ export const EDIT_DEFINITION: ToolDefinition = { name: EDIT_TOOL_NAME, description: `Replaces text within a file. By default, replaces a single occurrence, but can replace multiple occurrences when \`expected_replacements\` is specified. This tool requires providing significant context around the change to ensure precise targeting. Always use the ${READ_FILE_TOOL_NAME} tool to examine the file's current content before attempting a text replacement. - The user has the ability to modify the \`new_string\` content. If modified, this will be stated in the response. - + The user has the ability to modify the \`new_string\` content. If modified, this will be stated in the response.`, + instructions: ` Expectation for required parameters: 1. \`old_string\` MUST be the exact literal text to replace (including all whitespace, indentation, newlines, and surrounding code etc.). 2. \`new_string\` MUST be the exact literal text to replace \`old_string\` with (also including all whitespace, indentation, newlines, and surrounding code etc.). Ensure the resulting code is correct and idiomatic and that \`old_string\` and \`new_string\` are different. @@ -646,8 +646,8 @@ NEVER save workspace-specific context, local paths, or commands (e.g. "The entry export const WRITE_TODOS_DEFINITION: ToolDefinition = { base: { name: WRITE_TODOS_TOOL_NAME, - description: `This tool can help you list out the current subtasks that are required to be completed for a given user request. The list of subtasks helps you keep track of the current task, organize complex queries and help ensure that you don't miss any steps. With this list, the user can also see the current progress you are making in executing a given task. - + description: `This tool can help you list out the current subtasks that are required to be completed for a given user request. The list of subtasks helps you keep track of the current task, organize complex queries and help ensure that you don't miss any steps. With this list, the user can also see the current progress you are making in executing a given task.`, + instructions: ` Depending on the task complexity, you should first divide a given task into subtasks and then use this tool to list out the subtasks that are required to be completed for a given user request. Each of the subtasks should be clear and distinct. diff --git a/packages/core/src/tools/definitions/resolver.ts b/packages/core/src/tools/definitions/resolver.ts index 06ec9210f4..081f63ee75 100644 --- a/packages/core/src/tools/definitions/resolver.ts +++ b/packages/core/src/tools/definitions/resolver.ts @@ -17,7 +17,7 @@ import type { ToolDefinition } from './types.js'; export function resolveToolDeclaration( definition: ToolDefinition, modelId?: string, -): FunctionDeclaration { +): FunctionDeclaration & { instructions?: string } { if (!modelId || !definition.overrides) { return definition.base; } diff --git a/packages/core/src/tools/definitions/types.ts b/packages/core/src/tools/definitions/types.ts index d7e1a3ceda..b3080ba978 100644 --- a/packages/core/src/tools/definitions/types.ts +++ b/packages/core/src/tools/definitions/types.ts @@ -11,10 +11,12 @@ import { type FunctionDeclaration } from '@google/genai'; */ export interface ToolDefinition { /** The base declaration for the tool. */ - base: FunctionDeclaration; + base: FunctionDeclaration & { instructions?: string }; /** * Optional overrides for specific model families or versions. */ - overrides?: (modelId: string) => Partial | undefined; + overrides?: ( + modelId: string, + ) => (Partial & { instructions?: string }) | undefined; } diff --git a/packages/core/src/tools/edit.ts b/packages/core/src/tools/edit.ts index 41f895f5cd..3504261fe3 100644 --- a/packages/core/src/tools/edit.ts +++ b/packages/core/src/tools/edit.ts @@ -910,15 +910,23 @@ export class EditTool private readonly config: Config, messageBus: MessageBus, ) { + const modelId = + typeof config.getActiveModel === 'function' + ? config.getActiveModel() + : undefined; + const resolved = resolveToolDeclaration(EDIT_DEFINITION, modelId); super( EditTool.Name, EDIT_DISPLAY_NAME, - EDIT_DEFINITION.base.description!, + resolved.description!, Kind.Edit, - EDIT_DEFINITION.base.parametersJsonSchema, + resolved.parametersJsonSchema, messageBus, true, // isOutputMarkdown false, // canUpdateOutput + undefined, // extensionName + undefined, // extensionId + resolved.instructions, ); } diff --git a/packages/core/src/tools/tools.ts b/packages/core/src/tools/tools.ts index 3d90e80699..36cc9c61fb 100644 --- a/packages/core/src/tools/tools.ts +++ b/packages/core/src/tools/tools.ts @@ -361,6 +361,7 @@ export abstract class DeclarativeTool< readonly canUpdateOutput: boolean = false, readonly extensionName?: string, readonly extensionId?: string, + readonly instructions?: string, ) {} getSchema(_modelId?: string): FunctionDeclaration { diff --git a/packages/core/src/tools/write-todos.ts b/packages/core/src/tools/write-todos.ts index 38aef4f309..560abe9cfd 100644 --- a/packages/core/src/tools/write-todos.ts +++ b/packages/core/src/tools/write-todos.ts @@ -12,6 +12,7 @@ import { type Todo, type ToolResult, } from './tools.js'; +import type { Config } from '../config/config.js'; import type { MessageBus } from '../confirmation-bus/message-bus.js'; import { WRITE_TODOS_TOOL_NAME } from './tool-names.js'; import { WRITE_TODOS_DEFINITION } from './definitions/coreTools.js'; @@ -81,16 +82,27 @@ export class WriteTodosTool extends BaseDeclarativeTool< > { static readonly Name = WRITE_TODOS_TOOL_NAME; - constructor(messageBus: MessageBus) { + constructor( + private readonly config: Config, + messageBus: MessageBus, + ) { + const modelId = + typeof config.getActiveModel === 'function' + ? config.getActiveModel() + : undefined; + const resolved = resolveToolDeclaration(WRITE_TODOS_DEFINITION, modelId); super( WriteTodosTool.Name, 'WriteTodos', - WRITE_TODOS_DEFINITION.base.description!, + resolved.description!, Kind.Other, - WRITE_TODOS_DEFINITION.base.parametersJsonSchema, + resolved.parametersJsonSchema, messageBus, true, // isOutputMarkdown false, // canUpdateOutput + undefined, // extensionName + undefined, // extensionId + resolved.instructions, ); }