diff --git a/packages/core/src/tools/activate-skill.ts b/packages/core/src/tools/activate-skill.ts index cc9ba3048d..cf6a33f3e6 100644 --- a/packages/core/src/tools/activate-skill.ts +++ b/packages/core/src/tools/activate-skill.ts @@ -4,8 +4,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { z } from 'zod'; -import { zodToJsonSchema } from 'zod-to-json-schema'; import * as path from 'node:path'; import { getFolderStructure } from '../utils/getFolderStructure.js'; import type { MessageBus } from '../confirmation-bus/message-bus.js'; @@ -19,6 +17,8 @@ import { BaseDeclarativeTool, BaseToolInvocation, Kind } from './tools.js'; import type { Config } from '../config/config.js'; import { ACTIVATE_SKILL_TOOL_NAME } from './tool-names.js'; import { ToolErrorType } from './tool-error.js'; +import { getActivateSkillDefinition } from './definitions/coreTools.js'; +import { resolveToolDeclaration } from './definitions/resolver.js'; /** * Parameters for the ActivateSkill tool @@ -166,32 +166,14 @@ export class ActivateSkillTool extends BaseDeclarativeTool< ) { const skills = config.getSkillManager().getSkills(); const skillNames = skills.map((s) => s.name); - - let schema: z.ZodTypeAny; - if (skillNames.length === 0) { - schema = z.object({ - name: z.string().describe('No skills are currently available.'), - }); - } else { - schema = z.object({ - name: z - // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion - .enum(skillNames as [string, ...string[]]) - .describe('The name of the skill to activate.'), - }); - } - - const availableSkillsHint = - skillNames.length > 0 - ? ` (Available: ${skillNames.map((n) => `'${n}'`).join(', ')})` - : ''; + const definition = getActivateSkillDefinition(skillNames); super( ActivateSkillTool.Name, 'Activate Skill', - `Activates a specialized agent skill by name${availableSkillsHint}. Returns the skill's instructions wrapped in \`\` tags. These provide specialized guidance for the current task. Use this when you identify a task that matches a skill's description. ONLY use names exactly as they appear in the \`\` section.`, + definition.base.description!, Kind.Other, - zodToJsonSchema(schema), + definition.base.parametersJsonSchema, messageBus, true, false, @@ -212,4 +194,13 @@ export class ActivateSkillTool extends BaseDeclarativeTool< _toolDisplayName ?? 'Activate Skill', ); } + + override getSchema(modelId?: string) { + const skills = this.config.getSkillManager().getSkills(); + const skillNames = skills.map((s) => s.name); + return resolveToolDeclaration( + getActivateSkillDefinition(skillNames), + modelId, + ); + } } diff --git a/packages/core/src/tools/ask-user.ts b/packages/core/src/tools/ask-user.ts index 78e518afb1..6dbec43dda 100644 --- a/packages/core/src/tools/ask-user.ts +++ b/packages/core/src/tools/ask-user.ts @@ -17,6 +17,8 @@ import { ToolErrorType } from './tool-error.js'; import type { MessageBus } from '../confirmation-bus/message-bus.js'; import { QuestionType, type Question } from '../confirmation-bus/types.js'; import { ASK_USER_TOOL_NAME, ASK_USER_DISPLAY_NAME } from './tool-names.js'; +import { ASK_USER_DEFINITION } from './definitions/coreTools.js'; +import { resolveToolDeclaration } from './definitions/resolver.js'; export interface AskUserParams { questions: Question[]; @@ -30,74 +32,9 @@ export class AskUserTool extends BaseDeclarativeTool< super( ASK_USER_TOOL_NAME, ASK_USER_DISPLAY_NAME, - 'Ask the user one or more questions to gather preferences, clarify requirements, or make decisions.', + ASK_USER_DEFINITION.base.description!, Kind.Communicate, - { - type: 'object', - required: ['questions'], - properties: { - questions: { - type: 'array', - minItems: 1, - maxItems: 4, - items: { - type: 'object', - required: ['question', 'header', 'type'], - properties: { - question: { - type: 'string', - description: - 'The complete question to ask the user. Should be clear, specific, and end with a question mark.', - }, - header: { - type: 'string', - maxLength: 16, - description: - 'MUST be 16 characters or fewer or the call will fail. Very short label displayed as a chip/tag. Use abbreviations: "Auth" not "Authentication", "Config" not "Configuration". Examples: "Auth method", "Library", "Approach", "Database".', - }, - type: { - type: 'string', - enum: ['choice', 'text', 'yesno'], - default: 'choice', - description: - "Question type: 'choice' (default) for multiple-choice with options, 'text' for free-form input, 'yesno' for Yes/No confirmation.", - }, - options: { - type: 'array', - description: - "The selectable choices for 'choice' type questions. Provide 2-4 options. An 'Other' option is automatically added. Not needed for 'text' or 'yesno' types.", - items: { - type: 'object', - required: ['label', 'description'], - properties: { - label: { - type: 'string', - description: - 'The display text for this option (1-5 words). Example: "OAuth 2.0"', - }, - description: { - type: 'string', - description: - 'Brief explanation of this option. Example: "Industry standard, supports SSO"', - }, - }, - }, - }, - multiSelect: { - type: 'boolean', - description: - "Only applies when type='choice'. Set to true to allow selecting multiple options.", - }, - placeholder: { - type: 'string', - description: - "Hint text shown in the input field. For type='text', shown in the main input. For type='choice', shown in the 'Other' custom input.", - }, - }, - }, - }, - }, - }, + ASK_USER_DEFINITION.base.parametersJsonSchema, messageBus, ); } @@ -172,6 +109,10 @@ export class AskUserTool extends BaseDeclarativeTool< } return result; } + + override getSchema(modelId?: string) { + return resolveToolDeclaration(ASK_USER_DEFINITION, modelId); + } } export class AskUserInvocation extends BaseToolInvocation< 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 5ec7724a98..99180cc735 100644 --- a/packages/core/src/tools/definitions/__snapshots__/coreToolsModelSnapshots.test.ts.snap +++ b/packages/core/src/tools/definitions/__snapshots__/coreToolsModelSnapshots.test.ts.snap @@ -1,5 +1,204 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html +exports[`coreTools snapshots for specific models > Model: gemini-2.5-pro > snapshot for tool: activate_skill 1`] = ` +{ + "description": "Activates a specialized agent skill by name (Available: 'skill1', 'skill2'). Returns the skill's instructions wrapped in \`\` tags. These provide specialized guidance for the current task. Use this when you identify a task that matches a skill's description. ONLY use names exactly as they appear in the \`\` section.", + "name": "activate_skill", + "parametersJsonSchema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "name": { + "description": "The name of the skill to activate.", + "enum": [ + "skill1", + "skill2", + ], + "type": "string", + }, + }, + "required": [ + "name", + ], + "type": "object", + }, +} +`; + +exports[`coreTools snapshots for specific models > Model: gemini-2.5-pro > snapshot for tool: activate_skill_empty 1`] = ` +{ + "description": "Activates a specialized agent skill by name. Returns the skill's instructions wrapped in \`\` tags. These provide specialized guidance for the current task. Use this when you identify a task that matches a skill's description. ONLY use names exactly as they appear in the \`\` section.", + "name": "activate_skill", + "parametersJsonSchema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "name": { + "description": "No skills are currently available.", + "type": "string", + }, + }, + "required": [ + "name", + ], + "type": "object", + }, +} +`; + +exports[`coreTools snapshots for specific models > Model: gemini-2.5-pro > snapshot for tool: activate_skill_single 1`] = ` +{ + "description": "Activates a specialized agent skill by name (Available: 'skill1'). Returns the skill's instructions wrapped in \`\` tags. These provide specialized guidance for the current task. Use this when you identify a task that matches a skill's description. ONLY use names exactly as they appear in the \`\` section.", + "name": "activate_skill", + "parametersJsonSchema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "name": { + "description": "The name of the skill to activate.", + "enum": [ + "skill1", + ], + "type": "string", + }, + }, + "required": [ + "name", + ], + "type": "object", + }, +} +`; + +exports[`coreTools snapshots for specific models > Model: gemini-2.5-pro > snapshot for tool: ask_user 1`] = ` +{ + "description": "Ask the user one or more questions to gather preferences, clarify requirements, or make decisions.", + "name": "ask_user", + "parametersJsonSchema": { + "properties": { + "questions": { + "items": { + "properties": { + "header": { + "description": "MUST be 16 characters or fewer or the call will fail. Very short label displayed as a chip/tag. Use abbreviations: "Auth" not "Authentication", "Config" not "Configuration". Examples: "Auth method", "Library", "Approach", "Database".", + "maxLength": 16, + "type": "string", + }, + "multiSelect": { + "description": "Only applies when type='choice'. Set to true to allow selecting multiple options.", + "type": "boolean", + }, + "options": { + "description": "The selectable choices for 'choice' type questions. Provide 2-4 options. An 'Other' option is automatically added. Not needed for 'text' or 'yesno' types.", + "items": { + "properties": { + "description": { + "description": "Brief explanation of this option. Example: "Industry standard, supports SSO"", + "type": "string", + }, + "label": { + "description": "The display text for this option (1-5 words). Example: "OAuth 2.0"", + "type": "string", + }, + }, + "required": [ + "label", + "description", + ], + "type": "object", + }, + "type": "array", + }, + "placeholder": { + "description": "Hint text shown in the input field. For type='text', shown in the main input. For type='choice', shown in the 'Other' custom input.", + "type": "string", + }, + "question": { + "description": "The complete question to ask the user. Should be clear, specific, and end with a question mark.", + "type": "string", + }, + "type": { + "default": "choice", + "description": "Question type: 'choice' (default) for multiple-choice with options, 'text' for free-form input, 'yesno' for Yes/No confirmation.", + "enum": [ + "choice", + "text", + "yesno", + ], + "type": "string", + }, + }, + "required": [ + "question", + "header", + "type", + ], + "type": "object", + }, + "maxItems": 4, + "minItems": 1, + "type": "array", + }, + }, + "required": [ + "questions", + ], + "type": "object", + }, +} +`; + +exports[`coreTools snapshots for specific models > Model: gemini-2.5-pro > snapshot for tool: enter_plan_mode 1`] = ` +{ + "description": "Switch to Plan Mode to safely research, design, and plan complex changes using read-only tools.", + "name": "enter_plan_mode", + "parametersJsonSchema": { + "properties": { + "reason": { + "description": "Short reason explaining why you are entering plan mode.", + "type": "string", + }, + }, + "type": "object", + }, +} +`; + +exports[`coreTools snapshots for specific models > Model: gemini-2.5-pro > snapshot for tool: exit_plan_mode 1`] = ` +{ + "description": "Signals that the planning phase is complete and requests user approval to start implementation.", + "name": "exit_plan_mode", + "parametersJsonSchema": { + "properties": { + "plan_path": { + "description": "The file path to the finalized plan (e.g., "/mock/plans/feature-x.md"). This path MUST be within the designated plans directory: /mock/plans/", + "type": "string", + }, + }, + "required": [ + "plan_path", + ], + "type": "object", + }, +} +`; + +exports[`coreTools snapshots for specific models > Model: gemini-2.5-pro > snapshot for tool: get_internal_docs 1`] = ` +{ + "description": "Returns the content of Gemini CLI internal documentation files. If no path is provided, returns a list of all available documentation paths.", + "name": "get_internal_docs", + "parametersJsonSchema": { + "properties": { + "path": { + "description": "The relative path to the documentation file (e.g., 'cli/commands.md'). If omitted, lists all available documentation.", + "type": "string", + }, + }, + "type": "object", + }, +} +`; + exports[`coreTools snapshots for specific models > Model: gemini-2.5-pro > snapshot for tool: glob 1`] = ` { "description": "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.", @@ -237,6 +436,72 @@ 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: read_many_files 1`] = ` +{ + "description": "Reads content from multiple files specified by glob patterns within a configured target directory. For text files, it concatenates their content into a single string. It is primarily designed for text-based files. However, it can also process image (e.g., .png, .jpg), audio (e.g., .mp3, .wav), and PDF (.pdf) files if their file names or extensions are explicitly included in the 'include' argument. For these explicitly requested non-text files, their data is read and included in a format suitable for model consumption (e.g., base64 encoded). + +This tool is useful when you need to understand or analyze a collection of files, such as: +- Getting an overview of a codebase or parts of it (e.g., all TypeScript files in the 'src' directory). +- Finding where specific functionality is implemented if the user asks broad questions about code. +- Reviewing documentation files (e.g., all Markdown files in the 'docs' directory). +- Gathering context from multiple configuration 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. The tool inserts a '--- End of content ---' after the last file. Ensure glob patterns 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/audio/PDF) are generally skipped. Default excludes apply to common non-text files (except for explicitly requested images/audio/PDFs) and large dependency directories unless 'useDefaultExcludes' is false.", + "name": "read_many_files", + "parametersJsonSchema": { + "properties": { + "exclude": { + "default": [], + "description": "Optional. Glob patterns for files/directories to exclude. Added to default excludes if useDefaultExcludes is true. Example: "**/*.log", "temp/"", + "items": { + "minLength": 1, + "type": "string", + }, + "type": "array", + }, + "file_filtering_options": { + "description": "Whether to respect ignore patterns from .gitignore or .geminiignore", + "properties": { + "respect_gemini_ignore": { + "description": "Optional: Whether to respect .geminiignore patterns when listing files. Defaults to true.", + "type": "boolean", + }, + "respect_git_ignore": { + "description": "Optional: Whether to respect .gitignore patterns when listing files. Only available in git repositories. Defaults to true.", + "type": "boolean", + }, + }, + "type": "object", + }, + "include": { + "description": "An array of glob patterns or paths. Examples: ["src/**/*.ts"], ["README.md", "docs/"]", + "items": { + "minLength": 1, + "type": "string", + }, + "minItems": 1, + "type": "array", + }, + "recursive": { + "default": true, + "description": "Optional. Whether to search recursively (primarily controlled by \`**\` in glob patterns). Defaults to true.", + "type": "boolean", + }, + "useDefaultExcludes": { + "default": true, + "description": "Optional. Whether to apply a list of default exclusion patterns (e.g., node_modules, .git, binary files). Defaults to true.", + "type": "boolean", + }, + }, + "required": [ + "include", + ], + "type": "object", + }, +} +`; + exports[`coreTools snapshots for specific models > Model: gemini-2.5-pro > snapshot for tool: replace 1`] = ` { "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. @@ -345,6 +610,52 @@ 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: save_memory 1`] = ` +{ + "description": " +Saves concise global user context (preferences, facts) for use across ALL workspaces. + +### CRITICAL: GLOBAL CONTEXT ONLY +NEVER save workspace-specific context, local paths, or commands (e.g. "The entry point is src/index.js", "The test command is npm test"). These are local to the current workspace and must NOT be saved globally. EXCLUSIVELY for context relevant across ALL workspaces. + +- Use for "Remember X" or clear personal facts. +- Do NOT use for session context.", + "name": "save_memory", + "parametersJsonSchema": { + "additionalProperties": false, + "properties": { + "fact": { + "description": "The specific fact or piece of information to remember. Should be a clear, self-contained statement.", + "type": "string", + }, + }, + "required": [ + "fact", + ], + "type": "object", + }, +} +`; + +exports[`coreTools snapshots for specific models > Model: gemini-2.5-pro > snapshot for tool: web_fetch 1`] = ` +{ + "description": "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.", + "name": "web_fetch", + "parametersJsonSchema": { + "properties": { + "prompt": { + "description": "A comprehensive prompt that includes the URL(s) (up to 20) to fetch and specific instructions on how to process their content (e.g., "Summarize https://example.com/article and extract key points from https://another.com/data"). All URLs to be fetched must be valid and complete, starting with "http://" or "https://", and be fully-formed with a valid hostname (e.g., a domain name like "example.com" or an IP address). For example, "https://example.com" is valid, but "example.com" is not.", + "type": "string", + }, + }, + "required": [ + "prompt", + ], + "type": "object", + }, +} +`; + exports[`coreTools snapshots for specific models > Model: gemini-2.5-pro > snapshot for tool: write_file 1`] = ` { "description": "Writes content to a specified file in the local filesystem. @@ -371,6 +682,312 @@ 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. + +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. + +Use this tool for complex queries that require multiple steps. If you find that the request is actually complex after you have started executing the user task, create a todo list and use it. If execution of the user task requires multiple steps, planning and generally is higher complexity than a simple Q&A, use this tool. + +DO NOT use this tool for simple tasks that can be completed in less than 2 steps. If the user query is simple and straightforward, do not use the tool. If you can respond with an answer in a single turn then this tool is not required. + +## Task state definitions + +- pending: Work has not begun on a given subtask. +- in_progress: Marked just prior to beginning work on a given subtask. You should only have one subtask as in_progress at a time. +- completed: Subtask was successfully completed with no errors or issues. If the subtask required more steps to complete, update the todo list with the subtasks. All steps should be identified as completed only when they are completed. +- cancelled: As you update the todo list, some tasks are not required anymore due to the dynamic nature of the task. In this case, mark the subtasks as cancelled. + + +## Methodology for using this tool +1. Use this todo list as soon as you receive a user request based on the complexity of the task. +2. Keep track of every subtask that you update the list with. +3. Mark a subtask as in_progress before you begin working on it. You should only have one subtask as in_progress at a time. +4. Update the subtask list as you proceed in executing the task. The subtask list is not static and should reflect your progress and current plans, which may evolve as you acquire new information. +5. Mark a subtask as completed when you have completed it. +6. Mark a subtask as cancelled if the subtask is no longer needed. +7. You must update the todo list as soon as you start, stop or cancel a subtask. Don't batch or wait to update the todo list. + + +## Examples of When to Use the Todo List + + +User request: Create a website with a React for creating fancy logos using gemini-2.5-flash-image + +ToDo list created by the agent: +1. Initialize a new React project environment (e.g., using Vite). +2. Design and build the core UI components: a text input (prompt field) for the logo description, selection controls for style parameters (if the API supports them), and an image preview area. +3. Implement state management (e.g., React Context or Zustand) to manage the user's input prompt, the API loading status (pending, success, error), and the resulting image data. +4. Create an API service module within the React app (using "fetch" or "axios") to securely format and send the prompt data via an HTTP POST request to the specified "gemini-2.5-flash-image" (Gemini model) endpoint. +5. Implement asynchronous logic to handle the API call: show a loading indicator while the request is pending, retrieve the generated image (e.g., as a URL or base64 string) upon success, and display any errors. +6. Display the returned "fancy logo" from the API response in the preview area component. +7. Add functionality (e.g., a "Download" button) to allow the user to save the generated image file. +8. Deploy the application to a web server or hosting platform. + + +The agent used the todo list to break the task into distinct, manageable steps: +1. Building an entire interactive web application from scratch is a highly complex, multi-stage process involving setup, UI development, logic integration, and deployment. +2. The agent inferred the core functionality required for a "logo creator," such as UI controls for customization (Task 3) and an export feature (Task 7), which must be tracked as distinct goals. +3. The agent rightly inferred the requirement of an API service model for interacting with the image model endpoint. + + + + +## Examples of When NOT to Use the Todo List + + +User request: Ensure that the test passes. + +Agent: + + + +The agent did not use the todo list because this task could be completed by a tight loop of execute test->edit->execute test. + +", + "name": "write_todos", + "parametersJsonSchema": { + "additionalProperties": false, + "properties": { + "todos": { + "description": "The complete list of todo items. This will replace the existing list.", + "items": { + "additionalProperties": false, + "description": "A single todo item.", + "properties": { + "description": { + "description": "The description of the task.", + "type": "string", + }, + "status": { + "description": "The current status of the task.", + "enum": [ + "pending", + "in_progress", + "completed", + "cancelled", + ], + "type": "string", + }, + }, + "required": [ + "description", + "status", + ], + "type": "object", + }, + "type": "array", + }, + }, + "required": [ + "todos", + ], + "type": "object", + }, +} +`; + +exports[`coreTools snapshots for specific models > Model: gemini-3-pro-preview > snapshot for tool: activate_skill 1`] = ` +{ + "description": "Activates a specialized agent skill by name (Available: 'skill1', 'skill2'). Returns the skill's instructions wrapped in \`\` tags. These provide specialized guidance for the current task. Use this when you identify a task that matches a skill's description. ONLY use names exactly as they appear in the \`\` section.", + "name": "activate_skill", + "parametersJsonSchema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "name": { + "description": "The name of the skill to activate.", + "enum": [ + "skill1", + "skill2", + ], + "type": "string", + }, + }, + "required": [ + "name", + ], + "type": "object", + }, +} +`; + +exports[`coreTools snapshots for specific models > Model: gemini-3-pro-preview > snapshot for tool: activate_skill_empty 1`] = ` +{ + "description": "Activates a specialized agent skill by name. Returns the skill's instructions wrapped in \`\` tags. These provide specialized guidance for the current task. Use this when you identify a task that matches a skill's description. ONLY use names exactly as they appear in the \`\` section.", + "name": "activate_skill", + "parametersJsonSchema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "name": { + "description": "No skills are currently available.", + "type": "string", + }, + }, + "required": [ + "name", + ], + "type": "object", + }, +} +`; + +exports[`coreTools snapshots for specific models > Model: gemini-3-pro-preview > snapshot for tool: activate_skill_single 1`] = ` +{ + "description": "Activates a specialized agent skill by name (Available: 'skill1'). Returns the skill's instructions wrapped in \`\` tags. These provide specialized guidance for the current task. Use this when you identify a task that matches a skill's description. ONLY use names exactly as they appear in the \`\` section.", + "name": "activate_skill", + "parametersJsonSchema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "name": { + "description": "The name of the skill to activate.", + "enum": [ + "skill1", + ], + "type": "string", + }, + }, + "required": [ + "name", + ], + "type": "object", + }, +} +`; + +exports[`coreTools snapshots for specific models > Model: gemini-3-pro-preview > snapshot for tool: ask_user 1`] = ` +{ + "description": "Ask the user one or more questions to gather preferences, clarify requirements, or make decisions.", + "name": "ask_user", + "parametersJsonSchema": { + "properties": { + "questions": { + "items": { + "properties": { + "header": { + "description": "MUST be 16 characters or fewer or the call will fail. Very short label displayed as a chip/tag. Use abbreviations: "Auth" not "Authentication", "Config" not "Configuration". Examples: "Auth method", "Library", "Approach", "Database".", + "maxLength": 16, + "type": "string", + }, + "multiSelect": { + "description": "Only applies when type='choice'. Set to true to allow selecting multiple options.", + "type": "boolean", + }, + "options": { + "description": "The selectable choices for 'choice' type questions. Provide 2-4 options. An 'Other' option is automatically added. Not needed for 'text' or 'yesno' types.", + "items": { + "properties": { + "description": { + "description": "Brief explanation of this option. Example: "Industry standard, supports SSO"", + "type": "string", + }, + "label": { + "description": "The display text for this option (1-5 words). Example: "OAuth 2.0"", + "type": "string", + }, + }, + "required": [ + "label", + "description", + ], + "type": "object", + }, + "type": "array", + }, + "placeholder": { + "description": "Hint text shown in the input field. For type='text', shown in the main input. For type='choice', shown in the 'Other' custom input.", + "type": "string", + }, + "question": { + "description": "The complete question to ask the user. Should be clear, specific, and end with a question mark.", + "type": "string", + }, + "type": { + "default": "choice", + "description": "Question type: 'choice' (default) for multiple-choice with options, 'text' for free-form input, 'yesno' for Yes/No confirmation.", + "enum": [ + "choice", + "text", + "yesno", + ], + "type": "string", + }, + }, + "required": [ + "question", + "header", + "type", + ], + "type": "object", + }, + "maxItems": 4, + "minItems": 1, + "type": "array", + }, + }, + "required": [ + "questions", + ], + "type": "object", + }, +} +`; + +exports[`coreTools snapshots for specific models > Model: gemini-3-pro-preview > snapshot for tool: enter_plan_mode 1`] = ` +{ + "description": "Switch to Plan Mode to safely research, design, and plan complex changes using read-only tools.", + "name": "enter_plan_mode", + "parametersJsonSchema": { + "properties": { + "reason": { + "description": "Short reason explaining why you are entering plan mode.", + "type": "string", + }, + }, + "type": "object", + }, +} +`; + +exports[`coreTools snapshots for specific models > Model: gemini-3-pro-preview > snapshot for tool: exit_plan_mode 1`] = ` +{ + "description": "Signals that the planning phase is complete and requests user approval to start implementation.", + "name": "exit_plan_mode", + "parametersJsonSchema": { + "properties": { + "plan_path": { + "description": "The file path to the finalized plan (e.g., "/mock/plans/feature-x.md"). This path MUST be within the designated plans directory: /mock/plans/", + "type": "string", + }, + }, + "required": [ + "plan_path", + ], + "type": "object", + }, +} +`; + +exports[`coreTools snapshots for specific models > Model: gemini-3-pro-preview > snapshot for tool: get_internal_docs 1`] = ` +{ + "description": "Returns the content of Gemini CLI internal documentation files. If no path is provided, returns a list of all available documentation paths.", + "name": "get_internal_docs", + "parametersJsonSchema": { + "properties": { + "path": { + "description": "The relative path to the documentation file (e.g., 'cli/commands.md'). If omitted, lists all available documentation.", + "type": "string", + }, + }, + "type": "object", + }, +} +`; + exports[`coreTools snapshots for specific models > Model: gemini-3-pro-preview > snapshot for tool: glob 1`] = ` { "description": "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.", @@ -608,6 +1225,72 @@ 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: read_many_files 1`] = ` +{ + "description": "Reads content from multiple files specified by glob patterns within a configured target directory. For text files, it concatenates their content into a single string. It is primarily designed for text-based files. However, it can also process image (e.g., .png, .jpg), audio (e.g., .mp3, .wav), and PDF (.pdf) files if their file names or extensions are explicitly included in the 'include' argument. For these explicitly requested non-text files, their data is read and included in a format suitable for model consumption (e.g., base64 encoded). + +This tool is useful when you need to understand or analyze a collection of files, such as: +- Getting an overview of a codebase or parts of it (e.g., all TypeScript files in the 'src' directory). +- Finding where specific functionality is implemented if the user asks broad questions about code. +- Reviewing documentation files (e.g., all Markdown files in the 'docs' directory). +- Gathering context from multiple configuration 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. The tool inserts a '--- End of content ---' after the last file. Ensure glob patterns 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/audio/PDF) are generally skipped. Default excludes apply to common non-text files (except for explicitly requested images/audio/PDFs) and large dependency directories unless 'useDefaultExcludes' is false.", + "name": "read_many_files", + "parametersJsonSchema": { + "properties": { + "exclude": { + "default": [], + "description": "Optional. Glob patterns for files/directories to exclude. Added to default excludes if useDefaultExcludes is true. Example: "**/*.log", "temp/"", + "items": { + "minLength": 1, + "type": "string", + }, + "type": "array", + }, + "file_filtering_options": { + "description": "Whether to respect ignore patterns from .gitignore or .geminiignore", + "properties": { + "respect_gemini_ignore": { + "description": "Optional: Whether to respect .geminiignore patterns when listing files. Defaults to true.", + "type": "boolean", + }, + "respect_git_ignore": { + "description": "Optional: Whether to respect .gitignore patterns when listing files. Only available in git repositories. Defaults to true.", + "type": "boolean", + }, + }, + "type": "object", + }, + "include": { + "description": "An array of glob patterns or paths. Examples: ["src/**/*.ts"], ["README.md", "docs/"]", + "items": { + "minLength": 1, + "type": "string", + }, + "minItems": 1, + "type": "array", + }, + "recursive": { + "default": true, + "description": "Optional. Whether to search recursively (primarily controlled by \`**\` in glob patterns). Defaults to true.", + "type": "boolean", + }, + "useDefaultExcludes": { + "default": true, + "description": "Optional. Whether to apply a list of default exclusion patterns (e.g., node_modules, .git, binary files). Defaults to true.", + "type": "boolean", + }, + }, + "required": [ + "include", + ], + "type": "object", + }, +} +`; + exports[`coreTools snapshots for specific models > Model: gemini-3-pro-preview > snapshot for tool: replace 1`] = ` { "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. @@ -716,6 +1399,52 @@ 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: save_memory 1`] = ` +{ + "description": " +Saves concise global user context (preferences, facts) for use across ALL workspaces. + +### CRITICAL: GLOBAL CONTEXT ONLY +NEVER save workspace-specific context, local paths, or commands (e.g. "The entry point is src/index.js", "The test command is npm test"). These are local to the current workspace and must NOT be saved globally. EXCLUSIVELY for context relevant across ALL workspaces. + +- Use for "Remember X" or clear personal facts. +- Do NOT use for session context.", + "name": "save_memory", + "parametersJsonSchema": { + "additionalProperties": false, + "properties": { + "fact": { + "description": "The specific fact or piece of information to remember. Should be a clear, self-contained statement.", + "type": "string", + }, + }, + "required": [ + "fact", + ], + "type": "object", + }, +} +`; + +exports[`coreTools snapshots for specific models > Model: gemini-3-pro-preview > snapshot for tool: web_fetch 1`] = ` +{ + "description": "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.", + "name": "web_fetch", + "parametersJsonSchema": { + "properties": { + "prompt": { + "description": "A comprehensive prompt that includes the URL(s) (up to 20) to fetch and specific instructions on how to process their content (e.g., "Summarize https://example.com/article and extract key points from https://another.com/data"). All URLs to be fetched must be valid and complete, starting with "http://" or "https://", and be fully-formed with a valid hostname (e.g., a domain name like "example.com" or an IP address). For example, "https://example.com" is valid, but "example.com" is not.", + "type": "string", + }, + }, + "required": [ + "prompt", + ], + "type": "object", + }, +} +`; + exports[`coreTools snapshots for specific models > Model: gemini-3-pro-preview > snapshot for tool: write_file 1`] = ` { "description": "Writes content to a specified file in the local filesystem. @@ -741,3 +1470,110 @@ 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. + +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. + +Use this tool for complex queries that require multiple steps. If you find that the request is actually complex after you have started executing the user task, create a todo list and use it. If execution of the user task requires multiple steps, planning and generally is higher complexity than a simple Q&A, use this tool. + +DO NOT use this tool for simple tasks that can be completed in less than 2 steps. If the user query is simple and straightforward, do not use the tool. If you can respond with an answer in a single turn then this tool is not required. + +## Task state definitions + +- pending: Work has not begun on a given subtask. +- in_progress: Marked just prior to beginning work on a given subtask. You should only have one subtask as in_progress at a time. +- completed: Subtask was successfully completed with no errors or issues. If the subtask required more steps to complete, update the todo list with the subtasks. All steps should be identified as completed only when they are completed. +- cancelled: As you update the todo list, some tasks are not required anymore due to the dynamic nature of the task. In this case, mark the subtasks as cancelled. + + +## Methodology for using this tool +1. Use this todo list as soon as you receive a user request based on the complexity of the task. +2. Keep track of every subtask that you update the list with. +3. Mark a subtask as in_progress before you begin working on it. You should only have one subtask as in_progress at a time. +4. Update the subtask list as you proceed in executing the task. The subtask list is not static and should reflect your progress and current plans, which may evolve as you acquire new information. +5. Mark a subtask as completed when you have completed it. +6. Mark a subtask as cancelled if the subtask is no longer needed. +7. You must update the todo list as soon as you start, stop or cancel a subtask. Don't batch or wait to update the todo list. + + +## Examples of When to Use the Todo List + + +User request: Create a website with a React for creating fancy logos using gemini-2.5-flash-image + +ToDo list created by the agent: +1. Initialize a new React project environment (e.g., using Vite). +2. Design and build the core UI components: a text input (prompt field) for the logo description, selection controls for style parameters (if the API supports them), and an image preview area. +3. Implement state management (e.g., React Context or Zustand) to manage the user's input prompt, the API loading status (pending, success, error), and the resulting image data. +4. Create an API service module within the React app (using "fetch" or "axios") to securely format and send the prompt data via an HTTP POST request to the specified "gemini-2.5-flash-image" (Gemini model) endpoint. +5. Implement asynchronous logic to handle the API call: show a loading indicator while the request is pending, retrieve the generated image (e.g., as a URL or base64 string) upon success, and display any errors. +6. Display the returned "fancy logo" from the API response in the preview area component. +7. Add functionality (e.g., a "Download" button) to allow the user to save the generated image file. +8. Deploy the application to a web server or hosting platform. + + +The agent used the todo list to break the task into distinct, manageable steps: +1. Building an entire interactive web application from scratch is a highly complex, multi-stage process involving setup, UI development, logic integration, and deployment. +2. The agent inferred the core functionality required for a "logo creator," such as UI controls for customization (Task 3) and an export feature (Task 7), which must be tracked as distinct goals. +3. The agent rightly inferred the requirement of an API service model for interacting with the image model endpoint. + + + + +## Examples of When NOT to Use the Todo List + + +User request: Ensure that the test passes. + +Agent: + + + +The agent did not use the todo list because this task could be completed by a tight loop of execute test->edit->execute test. + +", + "name": "write_todos", + "parametersJsonSchema": { + "additionalProperties": false, + "properties": { + "todos": { + "description": "The complete list of todo items. This will replace the existing list.", + "items": { + "additionalProperties": false, + "description": "A single todo item.", + "properties": { + "description": { + "description": "The description of the task.", + "type": "string", + }, + "status": { + "description": "The current status of the task.", + "enum": [ + "pending", + "in_progress", + "completed", + "cancelled", + ], + "type": "string", + }, + }, + "required": [ + "description", + "status", + ], + "type": "object", + }, + "type": "array", + }, + }, + "required": [ + "todos", + ], + "type": "object", + }, +} +`; diff --git a/packages/core/src/tools/definitions/coreTools.ts b/packages/core/src/tools/definitions/coreTools.ts index 908127f203..c1f5976eaa 100644 --- a/packages/core/src/tools/definitions/coreTools.ts +++ b/packages/core/src/tools/definitions/coreTools.ts @@ -6,8 +6,21 @@ import type { ToolDefinition } from './types.js'; import * as os from 'node:os'; +import { z } from 'zod'; +import { zodToJsonSchema } from 'zod-to-json-schema'; + +// This file serves as the source of truth for all core tool definitions across +// models. Tool implementation files should not contain any hardcoded +// definitions. + +// Each tool has it's section, and a base ToolDefinition defined for that tool. +// For different model_ids, definition can be overridden to make the tool +// description or schema model_id specific. + +// ============================================================================ +// TOOL NAMES +// ============================================================================ -// Centralized tool names to avoid circular dependencies export const GLOB_TOOL_NAME = 'glob'; export const GREP_TOOL_NAME = 'grep_search'; export const LS_TOOL_NAME = 'list_directory'; @@ -17,6 +30,17 @@ export const WRITE_FILE_TOOL_NAME = 'write_file'; export const EDIT_TOOL_NAME = 'replace'; export const WEB_SEARCH_TOOL_NAME = 'google_web_search'; +export const WRITE_TODOS_TOOL_NAME = 'write_todos'; +export const WEB_FETCH_TOOL_NAME = 'web_fetch'; +export const READ_MANY_FILES_TOOL_NAME = 'read_many_files'; + +export const MEMORY_TOOL_NAME = 'save_memory'; +export const GET_INTERNAL_DOCS_TOOL_NAME = 'get_internal_docs'; +export const ACTIVATE_SKILL_TOOL_NAME = 'activate_skill'; +export const ASK_USER_TOOL_NAME = 'ask_user'; +export const EXIT_PLAN_MODE_TOOL_NAME = 'exit_plan_mode'; +export const ENTER_PLAN_MODE_TOOL_NAME = 'enter_plan_mode'; + // ============================================================================ // READ_FILE TOOL // ============================================================================ @@ -486,3 +510,416 @@ export function getShellDefinition( }, }; } + +// ============================================================================ +// WEB_FETCH TOOL +// ============================================================================ + +export const WEB_FETCH_DEFINITION: ToolDefinition = { + base: { + name: WEB_FETCH_TOOL_NAME, + description: + "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.", + parametersJsonSchema: { + type: 'object', + properties: { + prompt: { + description: + 'A comprehensive prompt that includes the URL(s) (up to 20) to fetch and specific instructions on how to process their content (e.g., "Summarize https://example.com/article and extract key points from https://another.com/data"). All URLs to be fetched must be valid and complete, starting with "http://" or "https://", and be fully-formed with a valid hostname (e.g., a domain name like "example.com" or an IP address). For example, "https://example.com" is valid, but "example.com" is not.', + type: 'string', + }, + }, + required: ['prompt'], + }, + }, +}; + +// ============================================================================ +// READ_MANY_FILES TOOL +// ============================================================================ + +export const READ_MANY_FILES_DEFINITION: ToolDefinition = { + base: { + name: READ_MANY_FILES_TOOL_NAME, + description: `Reads content from multiple files specified by glob patterns within a configured target directory. For text files, it concatenates their content into a single string. It is primarily designed for text-based files. However, it can also process image (e.g., .png, .jpg), audio (e.g., .mp3, .wav), and PDF (.pdf) files if their file names or extensions are explicitly included in the 'include' argument. For these explicitly requested non-text files, their data is read and included in a format suitable for model consumption (e.g., base64 encoded). + +This tool is useful when you need to understand or analyze a collection of files, such as: +- Getting an overview of a codebase or parts of it (e.g., all TypeScript files in the 'src' directory). +- Finding where specific functionality is implemented if the user asks broad questions about code. +- Reviewing documentation files (e.g., all Markdown files in the 'docs' directory). +- Gathering context from multiple configuration 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. The tool inserts a '--- End of content ---' after the last file. Ensure glob patterns 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/audio/PDF) are generally skipped. Default excludes apply to common non-text files (except for explicitly requested images/audio/PDFs) and large dependency directories unless 'useDefaultExcludes' is false.`, + parametersJsonSchema: { + type: 'object', + properties: { + include: { + type: 'array', + items: { + type: 'string', + minLength: 1, + }, + minItems: 1, + description: + 'An array of glob patterns or paths. Examples: ["src/**/*.ts"], ["README.md", "docs/"]', + }, + exclude: { + type: 'array', + items: { + type: 'string', + minLength: 1, + }, + description: + 'Optional. Glob patterns for files/directories to exclude. Added to default excludes if useDefaultExcludes is true. Example: "**/*.log", "temp/"', + default: [], + }, + recursive: { + type: 'boolean', + description: + 'Optional. Whether to search recursively (primarily controlled by `**` in glob patterns). Defaults to true.', + default: true, + }, + useDefaultExcludes: { + type: 'boolean', + description: + 'Optional. Whether to apply a list of default exclusion patterns (e.g., node_modules, .git, binary files). Defaults to true.', + default: true, + }, + file_filtering_options: { + description: + 'Whether to respect ignore patterns from .gitignore or .geminiignore', + type: 'object', + properties: { + respect_git_ignore: { + description: + 'Optional: Whether to respect .gitignore patterns when listing files. Only available in git repositories. Defaults to true.', + type: 'boolean', + }, + respect_gemini_ignore: { + description: + 'Optional: Whether to respect .geminiignore patterns when listing files. Defaults to true.', + type: 'boolean', + }, + }, + }, + }, + required: ['include'], + }, + }, +}; + +// ============================================================================ +// MEMORY TOOL +// ============================================================================ + +export const MEMORY_DEFINITION: ToolDefinition = { + base: { + name: MEMORY_TOOL_NAME, + description: ` +Saves concise global user context (preferences, facts) for use across ALL workspaces. + +### CRITICAL: GLOBAL CONTEXT ONLY +NEVER save workspace-specific context, local paths, or commands (e.g. "The entry point is src/index.js", "The test command is npm test"). These are local to the current workspace and must NOT be saved globally. EXCLUSIVELY for context relevant across ALL workspaces. + +- Use for "Remember X" or clear personal facts. +- Do NOT use for session context.`, + parametersJsonSchema: { + type: 'object', + properties: { + fact: { + type: 'string', + description: + 'The specific fact or piece of information to remember. Should be a clear, self-contained statement.', + }, + }, + required: ['fact'], + additionalProperties: false, + }, + }, +}; + +// ============================================================================ +// WRITE_TODOS TOOL +// ============================================================================ + +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. + +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. + +Use this tool for complex queries that require multiple steps. If you find that the request is actually complex after you have started executing the user task, create a todo list and use it. If execution of the user task requires multiple steps, planning and generally is higher complexity than a simple Q&A, use this tool. + +DO NOT use this tool for simple tasks that can be completed in less than 2 steps. If the user query is simple and straightforward, do not use the tool. If you can respond with an answer in a single turn then this tool is not required. + +## Task state definitions + +- pending: Work has not begun on a given subtask. +- in_progress: Marked just prior to beginning work on a given subtask. You should only have one subtask as in_progress at a time. +- completed: Subtask was successfully completed with no errors or issues. If the subtask required more steps to complete, update the todo list with the subtasks. All steps should be identified as completed only when they are completed. +- cancelled: As you update the todo list, some tasks are not required anymore due to the dynamic nature of the task. In this case, mark the subtasks as cancelled. + + +## Methodology for using this tool +1. Use this todo list as soon as you receive a user request based on the complexity of the task. +2. Keep track of every subtask that you update the list with. +3. Mark a subtask as in_progress before you begin working on it. You should only have one subtask as in_progress at a time. +4. Update the subtask list as you proceed in executing the task. The subtask list is not static and should reflect your progress and current plans, which may evolve as you acquire new information. +5. Mark a subtask as completed when you have completed it. +6. Mark a subtask as cancelled if the subtask is no longer needed. +7. You must update the todo list as soon as you start, stop or cancel a subtask. Don't batch or wait to update the todo list. + + +## Examples of When to Use the Todo List + + +User request: Create a website with a React for creating fancy logos using gemini-2.5-flash-image + +ToDo list created by the agent: +1. Initialize a new React project environment (e.g., using Vite). +2. Design and build the core UI components: a text input (prompt field) for the logo description, selection controls for style parameters (if the API supports them), and an image preview area. +3. Implement state management (e.g., React Context or Zustand) to manage the user's input prompt, the API loading status (pending, success, error), and the resulting image data. +4. Create an API service module within the React app (using "fetch" or "axios") to securely format and send the prompt data via an HTTP POST request to the specified "gemini-2.5-flash-image" (Gemini model) endpoint. +5. Implement asynchronous logic to handle the API call: show a loading indicator while the request is pending, retrieve the generated image (e.g., as a URL or base64 string) upon success, and display any errors. +6. Display the returned "fancy logo" from the API response in the preview area component. +7. Add functionality (e.g., a "Download" button) to allow the user to save the generated image file. +8. Deploy the application to a web server or hosting platform. + + +The agent used the todo list to break the task into distinct, manageable steps: +1. Building an entire interactive web application from scratch is a highly complex, multi-stage process involving setup, UI development, logic integration, and deployment. +2. The agent inferred the core functionality required for a "logo creator," such as UI controls for customization (Task 3) and an export feature (Task 7), which must be tracked as distinct goals. +3. The agent rightly inferred the requirement of an API service model for interacting with the image model endpoint. + + + + +## Examples of When NOT to Use the Todo List + + +User request: Ensure that the test passes. + +Agent: + + + +The agent did not use the todo list because this task could be completed by a tight loop of execute test->edit->execute test. + +`, + parametersJsonSchema: { + type: 'object', + properties: { + todos: { + type: 'array', + description: + 'The complete list of todo items. This will replace the existing list.', + items: { + type: 'object', + description: 'A single todo item.', + properties: { + description: { + type: 'string', + description: 'The description of the task.', + }, + status: { + type: 'string', + description: 'The current status of the task.', + enum: ['pending', 'in_progress', 'completed', 'cancelled'], + }, + }, + required: ['description', 'status'], + additionalProperties: false, + }, + }, + }, + required: ['todos'], + additionalProperties: false, + }, + }, +}; + +// ============================================================================ +// GET_INTERNAL_DOCS TOOL +// ============================================================================ + +export const GET_INTERNAL_DOCS_DEFINITION: ToolDefinition = { + base: { + name: GET_INTERNAL_DOCS_TOOL_NAME, + description: + 'Returns the content of Gemini CLI internal documentation files. If no path is provided, returns a list of all available documentation paths.', + parametersJsonSchema: { + type: 'object', + properties: { + path: { + description: + "The relative path to the documentation file (e.g., 'cli/commands.md'). If omitted, lists all available documentation.", + type: 'string', + }, + }, + }, + }, +}; + +// ============================================================================ +// ASK_USER TOOL +// ============================================================================ + +export const ASK_USER_DEFINITION: ToolDefinition = { + base: { + name: ASK_USER_TOOL_NAME, + description: + 'Ask the user one or more questions to gather preferences, clarify requirements, or make decisions.', + parametersJsonSchema: { + type: 'object', + required: ['questions'], + properties: { + questions: { + type: 'array', + minItems: 1, + maxItems: 4, + items: { + type: 'object', + required: ['question', 'header', 'type'], + properties: { + question: { + type: 'string', + description: + 'The complete question to ask the user. Should be clear, specific, and end with a question mark.', + }, + header: { + type: 'string', + maxLength: 16, + description: + 'MUST be 16 characters or fewer or the call will fail. Very short label displayed as a chip/tag. Use abbreviations: "Auth" not "Authentication", "Config" not "Configuration". Examples: "Auth method", "Library", "Approach", "Database".', + }, + type: { + type: 'string', + enum: ['choice', 'text', 'yesno'], + default: 'choice', + description: + "Question type: 'choice' (default) for multiple-choice with options, 'text' for free-form input, 'yesno' for Yes/No confirmation.", + }, + options: { + type: 'array', + description: + "The selectable choices for 'choice' type questions. Provide 2-4 options. An 'Other' option is automatically added. Not needed for 'text' or 'yesno' types.", + items: { + type: 'object', + required: ['label', 'description'], + properties: { + label: { + type: 'string', + description: + 'The display text for this option (1-5 words). Example: "OAuth 2.0"', + }, + description: { + type: 'string', + description: + 'Brief explanation of this option. Example: "Industry standard, supports SSO"', + }, + }, + }, + }, + multiSelect: { + type: 'boolean', + description: + "Only applies when type='choice'. Set to true to allow selecting multiple options.", + }, + placeholder: { + type: 'string', + description: + "Hint text shown in the input field. For type='text', shown in the main input. For type='choice', shown in the 'Other' custom input.", + }, + }, + }, + }, + }, + }, + }, +}; + +// ============================================================================ +// PLAN_MODE TOOLS +// ============================================================================ + +export const ENTER_PLAN_MODE_DEFINITION: ToolDefinition = { + base: { + name: ENTER_PLAN_MODE_TOOL_NAME, + description: + 'Switch to Plan Mode to safely research, design, and plan complex changes using read-only tools.', + parametersJsonSchema: { + type: 'object', + properties: { + reason: { + type: 'string', + description: + 'Short reason explaining why you are entering plan mode.', + }, + }, + }, + }, +}; + +/** + * Returns the tool definition for exiting plan mode. + */ +export function getExitPlanModeDefinition(plansDir: string): ToolDefinition { + return { + base: { + name: EXIT_PLAN_MODE_TOOL_NAME, + description: + 'Signals that the planning phase is complete and requests user approval to start implementation.', + parametersJsonSchema: { + type: 'object', + required: ['plan_path'], + properties: { + plan_path: { + type: 'string', + description: `The file path to the finalized plan (e.g., "${plansDir}/feature-x.md"). This path MUST be within the designated plans directory: ${plansDir}/`, + }, + }, + }, + }, + }; +} + +// ============================================================================ +// ACTIVATE_SKILL TOOL +// ============================================================================ + +/** + * Returns the tool definition for activating a skill. + */ +export function getActivateSkillDefinition( + skillNames: string[], +): ToolDefinition { + const availableSkillsHint = + skillNames.length > 0 + ? ` (Available: ${skillNames.map((n) => `'${n}'`).join(', ')})` + : ''; + + let schema: z.ZodTypeAny; + if (skillNames.length === 0) { + schema = z.object({ + name: z.string().describe('No skills are currently available.'), + }); + } else { + schema = z.object({ + name: z + // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion + .enum(skillNames as [string, ...string[]]) + .describe('The name of the skill to activate.'), + }); + } + + return { + base: { + name: ACTIVATE_SKILL_TOOL_NAME, + description: `Activates a specialized agent skill by name${availableSkillsHint}. Returns the skill's instructions wrapped in \`\` tags. These provide specialized guidance for the current task. Use this when you identify a task that matches a skill's description. ONLY use names exactly as they appear in the \`\` section.`, + parametersJsonSchema: zodToJsonSchema(schema), + }, + }; +} diff --git a/packages/core/src/tools/definitions/coreToolsModelSnapshots.test.ts b/packages/core/src/tools/definitions/coreToolsModelSnapshots.test.ts index f3f362d244..c80350808e 100644 --- a/packages/core/src/tools/definitions/coreToolsModelSnapshots.test.ts +++ b/packages/core/src/tools/definitions/coreToolsModelSnapshots.test.ts @@ -26,6 +26,15 @@ import { getShellDefinition, EDIT_DEFINITION, WEB_SEARCH_DEFINITION, + WEB_FETCH_DEFINITION, + READ_MANY_FILES_DEFINITION, + MEMORY_DEFINITION, + WRITE_TODOS_DEFINITION, + GET_INTERNAL_DOCS_DEFINITION, + ASK_USER_DEFINITION, + ENTER_PLAN_MODE_DEFINITION, + getExitPlanModeDefinition, + getActivateSkillDefinition, } from './coreTools.js'; describe('coreTools snapshots for specific models', () => { @@ -64,6 +73,29 @@ describe('coreTools snapshots for specific models', () => { }, { name: 'replace', definition: EDIT_DEFINITION }, { name: 'google_web_search', definition: WEB_SEARCH_DEFINITION }, + { name: 'web_fetch', definition: WEB_FETCH_DEFINITION }, + { name: 'read_many_files', definition: READ_MANY_FILES_DEFINITION }, + { name: 'save_memory', definition: MEMORY_DEFINITION }, + { name: 'write_todos', definition: WRITE_TODOS_DEFINITION }, + { name: 'get_internal_docs', definition: GET_INTERNAL_DOCS_DEFINITION }, + { name: 'ask_user', definition: ASK_USER_DEFINITION }, + { name: 'enter_plan_mode', definition: ENTER_PLAN_MODE_DEFINITION }, + { + name: 'exit_plan_mode', + definition: getExitPlanModeDefinition('/mock/plans'), + }, + { + name: 'activate_skill', + definition: getActivateSkillDefinition(['skill1', 'skill2']), + }, + { + name: 'activate_skill_empty', + definition: getActivateSkillDefinition([]), + }, + { + name: 'activate_skill_single', + definition: getActivateSkillDefinition(['skill1']), + }, ]; for (const modelId of modelIds) { diff --git a/packages/core/src/tools/enter-plan-mode.ts b/packages/core/src/tools/enter-plan-mode.ts index 89fe0cbf2f..feccb81089 100644 --- a/packages/core/src/tools/enter-plan-mode.ts +++ b/packages/core/src/tools/enter-plan-mode.ts @@ -16,6 +16,8 @@ import type { MessageBus } from '../confirmation-bus/message-bus.js'; import type { Config } from '../config/config.js'; import { ENTER_PLAN_MODE_TOOL_NAME } from './tool-names.js'; import { ApprovalMode } from '../policy/types.js'; +import { ENTER_PLAN_MODE_DEFINITION } from './definitions/coreTools.js'; +import { resolveToolDeclaration } from './definitions/resolver.js'; export interface EnterPlanModeParams { reason?: string; @@ -32,18 +34,9 @@ export class EnterPlanModeTool extends BaseDeclarativeTool< super( ENTER_PLAN_MODE_TOOL_NAME, 'Enter Plan Mode', - 'Switch to Plan Mode to safely research, design, and plan complex changes using read-only tools.', + ENTER_PLAN_MODE_DEFINITION.base.description!, Kind.Plan, - { - type: 'object', - properties: { - reason: { - type: 'string', - description: - 'Short reason explaining why you are entering plan mode.', - }, - }, - }, + ENTER_PLAN_MODE_DEFINITION.base.parametersJsonSchema, messageBus, ); } @@ -62,6 +55,10 @@ export class EnterPlanModeTool extends BaseDeclarativeTool< this.config, ); } + + override getSchema(modelId?: string) { + return resolveToolDeclaration(ENTER_PLAN_MODE_DEFINITION, modelId); + } } export class EnterPlanModeInvocation extends BaseToolInvocation< diff --git a/packages/core/src/tools/exit-plan-mode.ts b/packages/core/src/tools/exit-plan-mode.ts index ff2310bab0..a0540b11e3 100644 --- a/packages/core/src/tools/exit-plan-mode.ts +++ b/packages/core/src/tools/exit-plan-mode.ts @@ -24,6 +24,8 @@ import { checkExhaustive } from '../utils/checks.js'; import { resolveToRealPath, isSubpath } from '../utils/paths.js'; import { logPlanExecution } from '../telemetry/loggers.js'; import { PlanExecutionEvent } from '../telemetry/types.js'; +import { getExitPlanModeDefinition } from './definitions/coreTools.js'; +import { resolveToolDeclaration } from './definitions/resolver.js'; /** * Returns a human-readable description for an approval mode. @@ -56,21 +58,13 @@ export class ExitPlanModeTool extends BaseDeclarativeTool< messageBus: MessageBus, ) { const plansDir = config.storage.getProjectTempPlansDir(); + const definition = getExitPlanModeDefinition(plansDir); super( EXIT_PLAN_MODE_TOOL_NAME, 'Exit Plan Mode', - 'Signals that the planning phase is complete and requests user approval to start implementation.', + definition.base.description!, Kind.Plan, - { - type: 'object', - required: ['plan_path'], - properties: { - plan_path: { - type: 'string', - description: `The file path to the finalized plan (e.g., "${plansDir}/feature-x.md"). This path MUST be within the designated plans directory: ${plansDir}/`, - }, - }, - }, + definition.base.parametersJsonSchema, messageBus, ); } @@ -115,6 +109,11 @@ export class ExitPlanModeTool extends BaseDeclarativeTool< this.config, ); } + + override getSchema(modelId?: string) { + const plansDir = this.config.storage.getProjectTempPlansDir(); + return resolveToolDeclaration(getExitPlanModeDefinition(plansDir), modelId); + } } export class ExitPlanModeInvocation extends BaseToolInvocation< diff --git a/packages/core/src/tools/get-internal-docs.ts b/packages/core/src/tools/get-internal-docs.ts index a02bcf9fc6..23bda8f4dd 100644 --- a/packages/core/src/tools/get-internal-docs.ts +++ b/packages/core/src/tools/get-internal-docs.ts @@ -19,6 +19,8 @@ import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { glob } from 'glob'; import { ToolErrorType } from './tool-error.js'; +import { GET_INTERNAL_DOCS_DEFINITION } from './definitions/coreTools.js'; +import { resolveToolDeclaration } from './definitions/resolver.js'; /** * Parameters for the GetInternalDocs tool. @@ -157,18 +159,9 @@ export class GetInternalDocsTool extends BaseDeclarativeTool< super( GetInternalDocsTool.Name, 'GetInternalDocs', - 'Returns the content of Gemini CLI internal documentation files. If no path is provided, returns a list of all available documentation paths.', + GET_INTERNAL_DOCS_DEFINITION.base.description!, Kind.Think, - { - type: 'object', - properties: { - path: { - description: - "The relative path to the documentation file (e.g., 'cli/commands.md'). If omitted, lists all available documentation.", - type: 'string', - }, - }, - }, + GET_INTERNAL_DOCS_DEFINITION.base.parametersJsonSchema, messageBus, /* isOutputMarkdown */ true, /* canUpdateOutput */ false, @@ -188,4 +181,8 @@ export class GetInternalDocsTool extends BaseDeclarativeTool< _toolDisplayName, ); } + + override getSchema(modelId?: string) { + return resolveToolDeclaration(GET_INTERNAL_DOCS_DEFINITION, modelId); + } } diff --git a/packages/core/src/tools/memoryTool.ts b/packages/core/src/tools/memoryTool.ts index 032d012850..31cc35077d 100644 --- a/packages/core/src/tools/memoryTool.ts +++ b/packages/core/src/tools/memoryTool.ts @@ -24,15 +24,8 @@ import type { import { ToolErrorType } from './tool-error.js'; import { MEMORY_TOOL_NAME } from './tool-names.js'; import type { MessageBus } from '../confirmation-bus/message-bus.js'; - -const memoryToolDescription = ` -Saves concise global user context (preferences, facts) for use across ALL workspaces. - -### CRITICAL: GLOBAL CONTEXT ONLY -NEVER save workspace-specific context, local paths, or commands (e.g. "The entry point is src/index.js", "The test command is npm test"). These are local to the current workspace and must NOT be saved globally. EXCLUSIVELY for context relevant across ALL workspaces. - -- Use for "Remember X" or clear personal facts. -- Do NOT use for session context.`; +import { MEMORY_DEFINITION } from './definitions/coreTools.js'; +import { resolveToolDeclaration } from './definitions/resolver.js'; export const DEFAULT_CONTEXT_FILENAME = 'GEMINI.md'; export const MEMORY_SECTION_HEADER = '## Gemini Added Memories'; @@ -286,21 +279,9 @@ export class MemoryTool super( MemoryTool.Name, 'SaveMemory', - memoryToolDescription + - ' Examples: "Always lint after building", "Never run sudo commands", "Remember my address".', + MEMORY_DEFINITION.base.description!, Kind.Think, - { - type: 'object', - properties: { - fact: { - type: 'string', - description: - 'The specific fact or piece of information to remember. Should be a clear, self-contained statement.', - }, - }, - required: ['fact'], - additionalProperties: false, - }, + MEMORY_DEFINITION.base.parametersJsonSchema, messageBus, true, false, @@ -331,6 +312,10 @@ export class MemoryTool ); } + override getSchema(modelId?: string) { + return resolveToolDeclaration(MEMORY_DEFINITION, modelId); + } + getModifyContext(_abortSignal: AbortSignal): ModifyContext { return { getFilePath: (_params: SaveMemoryParams) => getGlobalMemoryFilePath(), diff --git a/packages/core/src/tools/read-many-files.ts b/packages/core/src/tools/read-many-files.ts index 89919dc2cb..0a5d68a6ba 100644 --- a/packages/core/src/tools/read-many-files.ts +++ b/packages/core/src/tools/read-many-files.ts @@ -29,6 +29,8 @@ import { logFileOperation } from '../telemetry/loggers.js'; import { FileOperationEvent } from '../telemetry/types.js'; import { ToolErrorType } from './tool-error.js'; import { READ_MANY_FILES_TOOL_NAME } from './tool-names.js'; +import { READ_MANY_FILES_DEFINITION } from './definitions/coreTools.js'; +import { resolveToolDeclaration } from './definitions/resolver.js'; import { REFERENCE_CONTENT_END } from '../utils/constants.js'; @@ -463,77 +465,12 @@ export class ReadManyFilesTool extends BaseDeclarativeTool< private config: Config, messageBus: MessageBus, ) { - const parameterSchema = { - type: 'object', - properties: { - include: { - type: 'array', - items: { - type: 'string', - minLength: 1, - }, - minItems: 1, - description: - 'An array of glob patterns or paths. Examples: ["src/**/*.ts"], ["README.md", "docs/"]', - }, - exclude: { - type: 'array', - items: { - type: 'string', - minLength: 1, - }, - description: - 'Optional. Glob patterns for files/directories to exclude. Added to default excludes if useDefaultExcludes is true. Example: "**/*.log", "temp/"', - default: [], - }, - recursive: { - type: 'boolean', - description: - 'Optional. Whether to search recursively (primarily controlled by `**` in glob patterns). Defaults to true.', - default: true, - }, - useDefaultExcludes: { - type: 'boolean', - description: - 'Optional. Whether to apply a list of default exclusion patterns (e.g., node_modules, .git, binary files). Defaults to true.', - default: true, - }, - file_filtering_options: { - description: - 'Whether to respect ignore patterns from .gitignore or .geminiignore', - type: 'object', - properties: { - respect_git_ignore: { - description: - 'Optional: Whether to respect .gitignore patterns when listing files. Only available in git repositories. Defaults to true.', - type: 'boolean', - }, - respect_gemini_ignore: { - description: - 'Optional: Whether to respect .geminiignore patterns when listing files. Defaults to true.', - type: 'boolean', - }, - }, - }, - }, - required: ['include'], - }; - super( ReadManyFilesTool.Name, 'ReadManyFiles', - `Reads content from multiple files specified by glob patterns within a configured target directory. For text files, it concatenates their content into a single string. It is primarily designed for text-based files. However, it can also process image (e.g., .png, .jpg), audio (e.g., .mp3, .wav), and PDF (.pdf) files if their file names or extensions are explicitly included in the 'include' argument. For these explicitly requested non-text files, their data is read and included in a format suitable for model consumption (e.g., base64 encoded). - -This tool is useful when you need to understand or analyze a collection of files, such as: -- Getting an overview of a codebase or parts of it (e.g., all TypeScript files in the 'src' directory). -- Finding where specific functionality is implemented if the user asks broad questions about code. -- Reviewing documentation files (e.g., all Markdown files in the 'docs' directory). -- Gathering context from multiple configuration 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. The tool inserts a '${REFERENCE_CONTENT_END}' after the last file. Ensure glob patterns 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/audio/PDF) are generally skipped. Default excludes apply to common non-text files (except for explicitly requested images/audio/PDFs) and large dependency directories unless 'useDefaultExcludes' is false.`, + READ_MANY_FILES_DEFINITION.base.description!, Kind.Read, - parameterSchema, + READ_MANY_FILES_DEFINITION.base.parametersJsonSchema, messageBus, true, // isOutputMarkdown false, // canUpdateOutput @@ -554,4 +491,8 @@ Use this tool when the user's query implies needing the content of several files _toolDisplayName, ); } + + override getSchema(modelId?: string) { + return resolveToolDeclaration(READ_MANY_FILES_DEFINITION, modelId); + } } diff --git a/packages/core/src/tools/tool-names.ts b/packages/core/src/tools/tool-names.ts index 191d667ea3..5cc1dc6e3a 100644 --- a/packages/core/src/tools/tool-names.ts +++ b/packages/core/src/tools/tool-names.ts @@ -5,43 +5,48 @@ */ import { - EDIT_TOOL_NAME, GLOB_TOOL_NAME, GREP_TOOL_NAME, LS_TOOL_NAME, READ_FILE_TOOL_NAME, SHELL_TOOL_NAME, - WEB_SEARCH_TOOL_NAME, WRITE_FILE_TOOL_NAME, + EDIT_TOOL_NAME, + WEB_SEARCH_TOOL_NAME, + WRITE_TODOS_TOOL_NAME, + WEB_FETCH_TOOL_NAME, + READ_MANY_FILES_TOOL_NAME, + MEMORY_TOOL_NAME, + GET_INTERNAL_DOCS_TOOL_NAME, + ACTIVATE_SKILL_TOOL_NAME, + ASK_USER_TOOL_NAME, + EXIT_PLAN_MODE_TOOL_NAME, + ENTER_PLAN_MODE_TOOL_NAME, } from './definitions/coreTools.js'; -// Centralized constants for tool names. -// This prevents circular dependencies that can occur when other modules (like agents) -// need to reference a tool's name without importing the tool's implementation. - export { - EDIT_TOOL_NAME, GLOB_TOOL_NAME, GREP_TOOL_NAME, LS_TOOL_NAME, READ_FILE_TOOL_NAME, SHELL_TOOL_NAME, - WEB_SEARCH_TOOL_NAME, WRITE_FILE_TOOL_NAME, + EDIT_TOOL_NAME, + WEB_SEARCH_TOOL_NAME, + WRITE_TODOS_TOOL_NAME, + WEB_FETCH_TOOL_NAME, + READ_MANY_FILES_TOOL_NAME, + MEMORY_TOOL_NAME, + GET_INTERNAL_DOCS_TOOL_NAME, + ACTIVATE_SKILL_TOOL_NAME, + ASK_USER_TOOL_NAME, + EXIT_PLAN_MODE_TOOL_NAME, + ENTER_PLAN_MODE_TOOL_NAME, }; -export const WRITE_TODOS_TOOL_NAME = 'write_todos'; -export const WEB_FETCH_TOOL_NAME = 'web_fetch'; -export const READ_MANY_FILES_TOOL_NAME = 'read_many_files'; export const LS_TOOL_NAME_LEGACY = 'list_directory'; // Just to be safe if anything used the old exported name directly -export const MEMORY_TOOL_NAME = 'save_memory'; -export const GET_INTERNAL_DOCS_TOOL_NAME = 'get_internal_docs'; -export const ACTIVATE_SKILL_TOOL_NAME = 'activate_skill'; export const EDIT_TOOL_NAMES = new Set([EDIT_TOOL_NAME, WRITE_FILE_TOOL_NAME]); -export const ASK_USER_TOOL_NAME = 'ask_user'; -export const EXIT_PLAN_MODE_TOOL_NAME = 'exit_plan_mode'; -export const ENTER_PLAN_MODE_TOOL_NAME = 'enter_plan_mode'; // Tool Display Names export const WRITE_FILE_DISPLAY_NAME = 'WriteFile'; @@ -102,6 +107,9 @@ export const ALL_BUILTIN_TOOL_NAMES = [ MEMORY_TOOL_NAME, ACTIVATE_SKILL_TOOL_NAME, ASK_USER_TOOL_NAME, + GET_INTERNAL_DOCS_TOOL_NAME, + ENTER_PLAN_MODE_TOOL_NAME, + EXIT_PLAN_MODE_TOOL_NAME, ] as const; /** diff --git a/packages/core/src/tools/web-fetch.ts b/packages/core/src/tools/web-fetch.ts index 254a90aa7b..396b99a6de 100644 --- a/packages/core/src/tools/web-fetch.ts +++ b/packages/core/src/tools/web-fetch.ts @@ -30,6 +30,8 @@ import { import { WEB_FETCH_TOOL_NAME } from './tool-names.js'; import { debugLogger } from '../utils/debugLogger.js'; import { retryWithBackoff } from '../utils/retry.js'; +import { WEB_FETCH_DEFINITION } from './definitions/coreTools.js'; +import { resolveToolDeclaration } from './definitions/resolver.js'; const URL_FETCH_TIMEOUT_MS = 10000; const MAX_CONTENT_LENGTH = 100000; @@ -414,19 +416,9 @@ export class WebFetchTool extends BaseDeclarativeTool< super( 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.", + WEB_FETCH_DEFINITION.base.description!, Kind.Fetch, - { - properties: { - prompt: { - description: - 'A comprehensive prompt that includes the URL(s) (up to 20) to fetch and specific instructions on how to process their content (e.g., "Summarize https://example.com/article and extract key points from https://another.com/data"). All URLs to be fetched must be valid and complete, starting with "http://" or "https://", and be fully-formed with a valid hostname (e.g., a domain name like "example.com" or an IP address). For example, "https://example.com" is valid, but "example.com" is not.', - type: 'string', - }, - }, - required: ['prompt'], - type: 'object', - }, + WEB_FETCH_DEFINITION.base.parametersJsonSchema, messageBus, true, // isOutputMarkdown false, // canUpdateOutput @@ -467,4 +459,8 @@ export class WebFetchTool extends BaseDeclarativeTool< _toolDisplayName, ); } + + override getSchema(modelId?: string) { + return resolveToolDeclaration(WEB_FETCH_DEFINITION, modelId); + } } diff --git a/packages/core/src/tools/write-todos.ts b/packages/core/src/tools/write-todos.ts index 6f12574107..38aef4f309 100644 --- a/packages/core/src/tools/write-todos.ts +++ b/packages/core/src/tools/write-todos.ts @@ -14,6 +14,8 @@ import { } from './tools.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'; +import { resolveToolDeclaration } from './definitions/resolver.js'; const TODO_STATUSES = [ 'pending', @@ -22,72 +24,6 @@ const TODO_STATUSES = [ 'cancelled', ] as const; -// Inspired by langchain/deepagents. -export const WRITE_TODOS_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. - -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. - -Use this tool for complex queries that require multiple steps. If you find that the request is actually complex after you have started executing the user task, create a todo list and use it. If execution of the user task requires multiple steps, planning and generally is higher complexity than a simple Q&A, use this tool. - -DO NOT use this tool for simple tasks that can be completed in less than 2 steps. If the user query is simple and straightforward, do not use the tool. If you can respond with an answer in a single turn then this tool is not required. - -## Task state definitions - -- pending: Work has not begun on a given subtask. -- in_progress: Marked just prior to beginning work on a given subtask. You should only have one subtask as in_progress at a time. -- completed: Subtask was successfully completed with no errors or issues. If the subtask required more steps to complete, update the todo list with the subtasks. All steps should be identified as completed only when they are completed. -- cancelled: As you update the todo list, some tasks are not required anymore due to the dynamic nature of the task. In this case, mark the subtasks as cancelled. - - -## Methodology for using this tool -1. Use this todo list as soon as you receive a user request based on the complexity of the task. -2. Keep track of every subtask that you update the list with. -3. Mark a subtask as in_progress before you begin working on it. You should only have one subtask as in_progress at a time. -4. Update the subtask list as you proceed in executing the task. The subtask list is not static and should reflect your progress and current plans, which may evolve as you acquire new information. -5. Mark a subtask as completed when you have completed it. -6. Mark a subtask as cancelled if the subtask is no longer needed. -7. You must update the todo list as soon as you start, stop or cancel a subtask. Don't batch or wait to update the todo list. - - -## Examples of When to Use the Todo List - - -User request: Create a website with a React for creating fancy logos using gemini-2.5-flash-image - -ToDo list created by the agent: -1. Initialize a new React project environment (e.g., using Vite). -2. Design and build the core UI components: a text input (prompt field) for the logo description, selection controls for style parameters (if the API supports them), and an image preview area. -3. Implement state management (e.g., React Context or Zustand) to manage the user's input prompt, the API loading status (pending, success, error), and the resulting image data. -4. Create an API service module within the React app (using "fetch" or "axios") to securely format and send the prompt data via an HTTP POST request to the specified "gemini-2.5-flash-image" (Gemini model) endpoint. -5. Implement asynchronous logic to handle the API call: show a loading indicator while the request is pending, retrieve the generated image (e.g., as a URL or base64 string) upon success, and display any errors. -6. Display the returned "fancy logo" from the API response in the preview area component. -7. Add functionality (e.g., a "Download" button) to allow the user to save the generated image file. -8. Deploy the application to a web server or hosting platform. - - -The agent used the todo list to break the task into distinct, manageable steps: -1. Building an entire interactive web application from scratch is a highly complex, multi-stage process involving setup, UI development, logic integration, and deployment. -2. The agent inferred the core functionality required for a "logo creator," such as UI controls for customization (Task 3) and an export feature (Task 7), which must be tracked as distinct goals. -3. The agent rightly inferred the requirement of an API service model for interacting with the image model endpoint. - - - - -## Examples of When NOT to Use the Todo List - - -User request: Ensure that the test passes. - -Agent: - - - -The agent did not use the todo list because this task could be completed by a tight loop of execute test->edit->execute test. - - -`; - export interface WriteTodosToolParams { /** * The full list of todos. This will overwrite any existing list. @@ -149,73 +85,17 @@ export class WriteTodosTool extends BaseDeclarativeTool< super( WriteTodosTool.Name, 'WriteTodos', - WRITE_TODOS_DESCRIPTION, + WRITE_TODOS_DEFINITION.base.description!, Kind.Other, - { - type: 'object', - properties: { - todos: { - type: 'array', - description: - 'The complete list of todo items. This will replace the existing list.', - items: { - type: 'object', - description: 'A single todo item.', - properties: { - description: { - type: 'string', - description: 'The description of the task.', - }, - status: { - type: 'string', - description: 'The current status of the task.', - enum: TODO_STATUSES, - }, - }, - required: ['description', 'status'], - additionalProperties: false, - }, - }, - }, - required: ['todos'], - additionalProperties: false, - }, + WRITE_TODOS_DEFINITION.base.parametersJsonSchema, messageBus, true, // isOutputMarkdown false, // canUpdateOutput ); } - override get schema() { - return { - name: this.name, - description: this.description, - parametersJsonSchema: this.parameterSchema, - responseJsonSchema: { - type: 'object', - properties: { - todos: { - type: 'array', - items: { - type: 'object', - properties: { - description: { - type: 'string', - }, - status: { - type: 'string', - enum: TODO_STATUSES, - }, - }, - required: ['description', 'status'], - additionalProperties: false, - }, - }, - }, - required: ['todos'], - additionalProperties: false, - }, - }; + override getSchema(modelId?: string) { + return resolveToolDeclaration(WRITE_TODOS_DEFINITION, modelId); } protected override validateToolParamValues(