From 647c93b9ccea6d49ee27de6ee261164e2d96622f Mon Sep 17 00:00:00 2001 From: notlikejuice Date: Tue, 25 Mar 2025 19:37:00 +0100 Subject: [PATCH] feat: Introduce new tools and refactor existing ones - Added `FileChangePlanTool` for managing file change plans with user approval. - Introduced `RejectFileChangesTool` to handle rejection of file changes with reasons. - Implemented `WebSearchTool` for performing web searches and returning results. - Created `WriteToFileTool` for writing content to files with user confirmation. - Added `ReadFileTool` for reading file content with structured responses. - Refactored existing tools to use a unified `definitions` module for parameter schemas. - Enhanced `ToolExecutor` to include new tools and manage their execution. - Developed utility functions for generating prompts and documentation for tools. - Updated `ToolParser` to support both standard and legacy XML formats for tool calls. - Improved error handling and user feedback mechanisms across tools. --- codemcp.toml | 3 + .../agent/v1/tools/definitions/MIGRATION.md | 160 +++++++++++++ .../agent/v1/tools/definitions/NEXT_STEPS.md | 82 +++++++ .../src/agent/v1/tools/definitions/README.md | 78 ++++++ .../definitions/add-interested-file.tool.ts | 90 +++++++ .../definitions/ask-followup-question.tool.ts | 78 ++++++ .../definitions/attempt-completion.tool.ts | 85 +++++++ .../tools/definitions/execute-command.tool.ts | 79 ++++++ .../v1/tools/definitions/exit-agent.tool.ts | 85 +++++++ .../definitions/explore-repo-folder.tool.ts | 78 ++++++ .../definitions/file-change-plan.tool.ts | 90 +++++++ .../v1/tools/definitions/file-editor.tool.ts | 193 +++++++++++++++ .../src/agent/v1/tools/definitions/index.ts | 101 ++++++++ .../v1/tools/definitions/list-files.tool.ts | 87 +++++++ .../v1/tools/definitions/read-file.tool.ts | 75 ++++++ .../definitions/reject-file-changes.tool.ts | 81 +++++++ .../v1/tools/definitions/search-files.tool.ts | 95 ++++++++ .../tools/definitions/search-symbol.tool.ts | 80 +++++++ .../tools/definitions/server-runner.tool.ts | 120 ++++++++++ .../v1/tools/definitions/spawn-agent.tool.ts | 100 ++++++++ .../tools/definitions/submit-review.tool.ts | 78 ++++++ .../tools/definitions/tool-call-template.ts | 63 +++++ .../tools/definitions/url-screenshot.tool.ts | 75 ++++++ .../v1/tools/definitions/web-search.tool.ts | 128 ++++++++++ .../tools/definitions/write-to-file.tool.ts | 108 +++++++++ .../tools/runners/add-interested-file.tool.ts | 175 ++++++++++++++ .../tools/runners/explore-repo-folder.tool.ts | 2 +- .../v1/tools/runners/file-change-plan.tool.ts | 224 ++++++++++++++++++ .../agent/v1/tools/runners/list-files.tool.ts | 2 +- .../tools/runners/read-file/read-file.tool.ts | 2 +- .../runners/read-file/read-file.tool.ts.new | 119 ++++++++++ .../tools/runners/reject-file-changes.tool.ts | 123 ++++++++++ .../v1/tools/runners/search-files.tool.ts | 2 +- .../v1/tools/runners/submit-review.tool.ts | 2 +- .../agent/v1/tools/runners/web-search.tool.ts | 137 +++++++++++ .../v1/tools/runners/write-to-file.tool.ts | 211 +++++++++++++++++ extension/src/agent/v1/tools/tool-executor.ts | 17 +- .../agent/v1/tools/tool-parser/tool-parser.ts | 38 ++- .../agent/v1/tools/utils/generate-prompts.ts | 29 +++ .../v1/tools/utils/system-prompt-generator.ts | 114 +++++++++ .../editor/code-diff-formatter.ts | 2 +- .../src/providers/openrouter-provider.ts | 49 ++++ 42 files changed, 3525 insertions(+), 15 deletions(-) create mode 100644 codemcp.toml create mode 100644 extension/src/agent/v1/tools/definitions/MIGRATION.md create mode 100644 extension/src/agent/v1/tools/definitions/NEXT_STEPS.md create mode 100644 extension/src/agent/v1/tools/definitions/README.md create mode 100644 extension/src/agent/v1/tools/definitions/add-interested-file.tool.ts create mode 100644 extension/src/agent/v1/tools/definitions/ask-followup-question.tool.ts create mode 100644 extension/src/agent/v1/tools/definitions/attempt-completion.tool.ts create mode 100644 extension/src/agent/v1/tools/definitions/execute-command.tool.ts create mode 100644 extension/src/agent/v1/tools/definitions/exit-agent.tool.ts create mode 100644 extension/src/agent/v1/tools/definitions/explore-repo-folder.tool.ts create mode 100644 extension/src/agent/v1/tools/definitions/file-change-plan.tool.ts create mode 100644 extension/src/agent/v1/tools/definitions/file-editor.tool.ts create mode 100644 extension/src/agent/v1/tools/definitions/index.ts create mode 100644 extension/src/agent/v1/tools/definitions/list-files.tool.ts create mode 100644 extension/src/agent/v1/tools/definitions/read-file.tool.ts create mode 100644 extension/src/agent/v1/tools/definitions/reject-file-changes.tool.ts create mode 100644 extension/src/agent/v1/tools/definitions/search-files.tool.ts create mode 100644 extension/src/agent/v1/tools/definitions/search-symbol.tool.ts create mode 100644 extension/src/agent/v1/tools/definitions/server-runner.tool.ts create mode 100644 extension/src/agent/v1/tools/definitions/spawn-agent.tool.ts create mode 100644 extension/src/agent/v1/tools/definitions/submit-review.tool.ts create mode 100644 extension/src/agent/v1/tools/definitions/tool-call-template.ts create mode 100644 extension/src/agent/v1/tools/definitions/url-screenshot.tool.ts create mode 100644 extension/src/agent/v1/tools/definitions/web-search.tool.ts create mode 100644 extension/src/agent/v1/tools/definitions/write-to-file.tool.ts create mode 100644 extension/src/agent/v1/tools/runners/add-interested-file.tool.ts create mode 100644 extension/src/agent/v1/tools/runners/file-change-plan.tool.ts create mode 100644 extension/src/agent/v1/tools/runners/read-file/read-file.tool.ts.new create mode 100644 extension/src/agent/v1/tools/runners/reject-file-changes.tool.ts create mode 100644 extension/src/agent/v1/tools/runners/web-search.tool.ts create mode 100644 extension/src/agent/v1/tools/runners/write-to-file.tool.ts create mode 100644 extension/src/agent/v1/tools/utils/generate-prompts.ts create mode 100644 extension/src/agent/v1/tools/utils/system-prompt-generator.ts create mode 100644 extension/src/providers/openrouter-provider.ts diff --git a/codemcp.toml b/codemcp.toml new file mode 100644 index 00000000..4698ce16 --- /dev/null +++ b/codemcp.toml @@ -0,0 +1,3 @@ +[commands] +format = ["./run_format.sh"] +test = ["./run_test.sh"] diff --git a/extension/src/agent/v1/tools/definitions/MIGRATION.md b/extension/src/agent/v1/tools/definitions/MIGRATION.md new file mode 100644 index 00000000..070cdef5 --- /dev/null +++ b/extension/src/agent/v1/tools/definitions/MIGRATION.md @@ -0,0 +1,160 @@ +# Migration Plan: Unified Tool Definitions + +This document outlines the steps to migrate from the existing separate tool definitions in `/schema` and `/prompts/tools` to the new unified architecture in `/definitions`. + +## Overview + +The migration involves: + +1. Replacing schema imports with unified definition imports +2. Updating the ToolExecutor to use the new definitions +3. Modifying the ToolParser to work with the unified format +4. Updating tool runners to reference the new definitions +5. Integrating the system prompt generator + +## Detailed Steps + +### 1. Update Tool Executor + +The `tool-executor.ts` file needs to be modified to use the new unified definitions: + +```typescript +// Replace this import +import { tools, writeToFileTool } from "./schema" + +// With this import +import { toolDefinitions, writeToFileTool } from "./definitions" + +// Update the ToolParser initialization +this.toolParser = new ToolParser( + toolDefinitions.map((tool) => ({ name: tool.name, schema: tool.schema })), + { + onToolUpdate: this.handleToolUpdate.bind(this), + onToolEnd: this.handleToolEnd.bind(this), + onToolError: this.handleToolError.bind(this), + } +) + +// Make the createTool method more dynamic +private createTool(params: FullToolParams) { + const toolName = params.name as ToolName; + + // Define the mapping of tool names to their implementation classes + const toolImplementations = { + read_file: ReadFileTool, + search_files: SearchFilesTool, + execute_command: ExecuteCommandTool, + list_files: ListFilesTool, + file_editor: FileEditorTool, + ask_followup_question: AskFollowupQuestionTool, + search_symbol: SearchSymbolTool, + url_screenshot: UrlScreenshotTool, + attempt_completion: AttemptCompletionTool, + explore_repo_folder: ExploreRepoFolderTool, + spawn_agent: SpawnAgentTool, + exit_agent: ExitAgentTool, + server_runner: ServerRunnerTool, + web_search: WebSearchTool, + write_to_file: WriteToFileTool, + add_interested_file: AddInterestedFileTool, + file_changes_plan: FileChangePlanTool, + submit_review: SubmitReviewTool, + reject_file_changes: RejectFileChangesTool, + } as const; + + const ToolClass = toolImplementations[toolName]; + if (!ToolClass) { + throw new Error(`Unknown tool: ${params.name}`); + } + + return new ToolClass(params, this.options); +} +``` + +### 2. Update Tool Runners + +Each tool runner should be updated to import its types from the new unified definitions: + +```typescript +// Replace this import +import { ReadFileToolParams } from "../schema/read_file"; + +// With this import +import { ReadFileToolParams } from "../definitions"; +``` + +### 3. Integrate System Prompt Generator + +Update the agent initialization to use the new system prompt generator: + +```typescript +// Import the generator +import { generateToolsSystemPrompt } from "../tools/utils/system-prompt-generator"; + +// Use it in the system prompt +const systemPrompt = ` +${baseSystemPrompt} + +${generateToolsSystemPrompt()} + +${additionalInstructions} +`; +``` + +### 4. Update Tool Parser + +Enhance the ToolParser to support both the standard and legacy XML formats: + +```typescript +// Add support for both formats in the pattern matching +const standardPattern = /([\s\S]*?)<\/tool>/g; +const legacyPattern = /<([^>]+)>([\s\S]*?)<\/\1>/g; + +// Check both patterns when parsing +function parseToolUse(text: string) { + let match; + + // Try standard format first + match = standardPattern.exec(text); + if (match) { + const [fullMatch, toolName, paramsText] = match; + // Process params and return + return { toolName, params, fullMatch }; + } + + // Try legacy format as fallback + match = legacyPattern.exec(text); + if (match) { + const [fullMatch, toolName, paramsText] = match; + // Process params and return + return { toolName, params, fullMatch }; + } + + return null; +} +``` + +### 5. Phase Out Old Definitions + +Once the new system is in place and tested: + +1. Create a deprecation notice in the `/schema` and `/prompts/tools` directories +2. Mark the old imports as deprecated in the codebase +3. Establish a timeline for removing the old files entirely + +## Testing Strategy + +1. Create unit tests for the new unified definitions +2. Test each tool with both standard and legacy XML formats +3. Perform integration tests with the ToolExecutor +4. Verify the system prompt generation includes all tools correctly +5. Conduct end-to-end tests with the Claude agent using the new formats + +## Rollout Plan + +1. Implement the changes in a separate branch +2. Review and test thoroughly +3. Merge to development branch and test in the full system +4. Schedule the production deployment +5. Monitor for any issues after deployment +6. After successful rollout, create a cleanup ticket to remove the deprecated code \ No newline at end of file diff --git a/extension/src/agent/v1/tools/definitions/NEXT_STEPS.md b/extension/src/agent/v1/tools/definitions/NEXT_STEPS.md new file mode 100644 index 00000000..7d1fe428 --- /dev/null +++ b/extension/src/agent/v1/tools/definitions/NEXT_STEPS.md @@ -0,0 +1,82 @@ +# Next Steps for Tool Definition Unification + +## Completed Work + +1. ✅ Created a unified tool call template with consistent XML format +2. ✅ Implemented 19 tool definitions using the new unified format: + - read_file + - search_files + - execute_command + - list_files + - file_editor + - ask_followup_question + - search_symbol + - url_screenshot + - attempt_completion + - explore_repo_folder + - spawn_agent + - exit_agent + - server_runner + - web_search + - write_to_file + - add_interested_file + - file_changes_plan + - submit_review + - reject_file_changes +3. ✅ Created helper functions to manage tool definitions +4. ✅ Developed system prompt generator for consistent LLM instructions +5. ✅ Documented the new architecture and migration strategy + +## Pending Tasks + +1. 🔲 Update the ToolExecutor to use unified definitions + - Modify imports to use new definitions + - Update ToolParser initialization + - Make createTool method more dynamic + +2. 🔲 Update tool runners to reference unified definitions + - Replace schema imports with definition imports + - Ensure type compatibility + +3. 🔲 Integrate the system prompt generator + - Connect to agent initialization + - Test generation with all tools + +4. 🔲 Enhance the ToolParser + - Add support for both standard and legacy XML formats + - Ensure parameter extraction works with new format + +5. 🔲 Testing + - Create unit tests for the unified definitions + - Test LLM understanding of the new format + - End-to-end testing with all tools + +6. 🔲 Documentation + - Update developer documentation to reflect the new architecture + - Add examples of how to create new tool definitions + +## Benefits of the New Architecture + +1. **Single Source of Truth**: All information about a tool is in one place +2. **Type Safety**: TypeScript types are derived directly from Zod schemas +3. **Consistent Calling Format**: All tools use the same XML format +4. **Better LLM Understanding**: Standardized examples improve Claude's tool usage +5. **Easier Maintenance**: Adding or modifying tools requires changes in fewer places +6. **Automated Documentation**: System prompts can be generated from definitions +7. **Future Compatibility**: The format supports both current and future Claude models + +## Implementation Priority + +1. Start with the ToolExecutor updates, as this is the central integration point +2. Update the most commonly used tool runners first +3. Integrate the system prompt generator to improve LLM understanding +4. Add testing throughout the process +5. Continue with less frequently used tools +6. Complete documentation updates last + +## Timeline Estimate + +- **Week 1**: Implement ToolExecutor changes and update common runners +- **Week 2**: Update remaining runners and integrate system prompt generator +- **Week 3**: Testing, bug fixing, and documentation +- **Week 4**: Phase out old definitions and monitor for issues \ No newline at end of file diff --git a/extension/src/agent/v1/tools/definitions/README.md b/extension/src/agent/v1/tools/definitions/README.md new file mode 100644 index 00000000..e5b15fc2 --- /dev/null +++ b/extension/src/agent/v1/tools/definitions/README.md @@ -0,0 +1,78 @@ +# Unified Tool Definitions + +This directory contains unified definitions for all tools used in the Claude Dev agent system. Each tool definition combines: + +1. **Schema** - Zod schema for validation +2. **Types** - TypeScript types derived from the schema +3. **Prompt Information** - Descriptions, capabilities, and examples for LLM consumption +4. **Calling Format** - Standardized XML format for tool invocation + +## Architecture + +The unified tool definitions provide a single source of truth for each tool, ensuring consistency between: +- Runtime validation (Zod schema) +- Type safety (TypeScript types) +- LLM instructions (prompt definitions) +- Tool invocation format (XML template) + +### Key Components + +- **tool-call-template.ts** - Defines the XML template format and helpers +- **index.ts** - Registry of all tool definitions with helper functions +- **{tool-name}.tool.ts** - Individual tool definitions + +## XML Tool Calling Format + +All tools use a consistent XML format: + +```xml + + value1 + value2 + +``` + +This provides Claude with a standardized way to invoke tools. + +## Usage + +1. **Adding a new tool**: + - Create a new file `{tool-name}.tool.ts` + - Define the schema, types, and prompt information + - Register it in `index.ts` + +2. **Fetching tool information**: + - `getToolDefinition(name)` - Get a specific tool definition + - `getToolCallFormat(name)` - Get XML format for a specific tool + - `getAllToolExamples()` - Get examples for system prompts + +## Tool List + +The system currently provides definitions for the following tools: + +- `read_file` - Read file contents +- `search_files` - Search for files with a pattern +- `execute_command` - Execute a shell command +- `list_files` - List files in a directory +- `file_editor` - Edit file contents +- `ask_followup_question` - Ask a clarifying question +- `search_symbol` - Search for code symbols +- `url_screenshot` - Take a screenshot of a URL +- `attempt_completion` - Attempt to complete a task +- `explore_repo_folder` - Explore a repository folder +- `spawn_agent` - Create a new agent +- `exit_agent` - Exit the current agent +- `server_runner` - Run a development server +- `web_search` - Search the web for information +- `write_to_file` - Write content to a file +- `add_interested_file` - Track files relevant to the task +- `file_changes_plan` - Plan file changes +- `submit_review` - Submit a review of progress +- `reject_file_changes` - Reject proposed file changes + +## Integration + +This unified architecture integrates with: +- Tool execution logic in `/agent/v1/tools/runners/` +- Tool schema validation in `/agent/v1/tools/schema/` +- System prompt generation in `/agent/v1/prompts/tools/` \ No newline at end of file diff --git a/extension/src/agent/v1/tools/definitions/add-interested-file.tool.ts b/extension/src/agent/v1/tools/definitions/add-interested-file.tool.ts new file mode 100644 index 00000000..acb57f57 --- /dev/null +++ b/extension/src/agent/v1/tools/definitions/add-interested-file.tool.ts @@ -0,0 +1,90 @@ +import { z } from "zod" +import { defineToolCallFormat, generateToolCallExamples } from "./tool-call-template" + +/** + * Unified definition for add_interested_file tool + * Combines schema, prompt, and type definitions in one place + * Uses consistent calling format + */ + +/** + * ZOD Schema Definition + */ +export const schema = z.object({ + path: z.string().describe("The path of the file to track (relative to the current working directory)"), + why: z.string().describe("Explanation of why this file is relevant to the current task"), +}) + +/** + * Type definitions derived from schema + */ +export type AddInterestedFileInput = z.infer + +export type AddInterestedFileToolParams = { + name: "add_interested_file" + input: AddInterestedFileInput +} + +/** + * Example parameter values for tool calls + */ +const exampleParams = [ + { + path: "/src/services/auth.ts", + why: "Contains authentication logic that needs to be modified for the new feature" + }, + { + path: "/src/types/user.ts", + why: "Defines user interface that will be extended with new properties" + }, + { + path: "/src/utils/validation.ts", + why: "Contains validation helpers that will be reused in the new feature" + } +] + +/** + * Generate consistent example tool calls + */ +const exampleCalls = generateToolCallExamples("add_interested_file", exampleParams) + +/** + * Prompt definition for LLM consumption + */ +export const promptDefinition = { + name: "add_interested_file", + description: "Track files that are relevant to the current task. This tool helps maintain context by tracking file dependencies and documenting why files are important.", + parameters: { + path: { + type: "string", + description: "The path of the file to track (relative to the current working directory)", + required: true, + }, + why: { + type: "string", + description: "Explanation of why this file is relevant to the current task", + required: true, + }, + }, + capabilities: [ + "You can use add_interested_file tool to mark files as important for the current task.", + "This helps maintain context by documenting why certain files are relevant.", + "Always provide a clear explanation of why the file is important to the task." + ], + examples: exampleCalls.map((call, i) => ({ + description: `Track ${exampleParams[i].path} as important`, + output: call + })), + ...defineToolCallFormat("add_interested_file") +} + +/** + * Full tool definition - exports everything needed in one place + */ +export const addInterestedFileTool = { + name: "add_interested_file", + schema, + prompt: promptDefinition, + examples: promptDefinition.examples, + callFormat: promptDefinition.callFormat +} \ No newline at end of file diff --git a/extension/src/agent/v1/tools/definitions/ask-followup-question.tool.ts b/extension/src/agent/v1/tools/definitions/ask-followup-question.tool.ts new file mode 100644 index 00000000..878d0909 --- /dev/null +++ b/extension/src/agent/v1/tools/definitions/ask-followup-question.tool.ts @@ -0,0 +1,78 @@ +import { z } from "zod" +import { defineToolCallFormat, generateToolCallExamples } from "./tool-call-template" + +/** + * Unified definition for ask_followup_question tool + * Combines schema, prompt, and type definitions in one place + * Uses consistent calling format + */ + +/** + * ZOD Schema Definition + */ +export const schema = z.object({ + question: z + .string() + .describe( + "The question to ask the user. This should be a clear, specific question that addresses the information you need." + ), +}) + +/** + * Type definitions derived from schema + */ +export type AskFollowupQuestionInput = z.infer + +export type AskFollowupQuestionToolParams = { + name: "ask_followup_question" + input: AskFollowupQuestionInput +} + +/** + * Example parameter values for tool calls + */ +const exampleParams = [ + { question: "Could you please provide more details about the desired functionality?" }, + { question: "What is the deadline for this task?" }, + { question: "Do you have any preferred programming languages for this project?" } +] + +/** + * Generate consistent example tool calls + */ +const exampleCalls = generateToolCallExamples("ask_followup_question", exampleParams) + +/** + * Prompt definition for LLM consumption + * Based on prompts/tools/ask-followup-question.ts but with unified calling format + */ +export const promptDefinition = { + name: "ask_followup_question", + description: "Ask the user a question to gather additional information needed to complete the task. This tool should be used when you encounter ambiguities, need clarification, or require more details to proceed effectively. It allows for interactive problem-solving by enabling direct communication with the user. Use this tool judiciously to maintain a balance between gathering necessary information and avoiding excessive back-and-forth.", + parameters: { + question: { + type: "string", + description: "The question to ask the user. This should be a clear, specific question that addresses the information you need.", + required: true, + }, + }, + capabilities: [ + "You can use ask_followup_question tool to ask the user a question to gather additional information needed to complete the task. This tool should be used when you encounter ambiguities, need clarification, or require more details to proceed effectively, this is meant to enable direct communication with the user but should be used only when absolutely necessary or when the user directly asks for it." + ], + examples: exampleCalls.map((call, i) => ({ + description: `Ask: ${exampleParams[i].question}`, + output: call + })), + ...defineToolCallFormat("ask_followup_question") +} + +/** + * Full tool definition - exports everything needed in one place + */ +export const askFollowupQuestionTool = { + name: "ask_followup_question", + schema, + prompt: promptDefinition, + examples: promptDefinition.examples, + callFormat: promptDefinition.callFormat +} \ No newline at end of file diff --git a/extension/src/agent/v1/tools/definitions/attempt-completion.tool.ts b/extension/src/agent/v1/tools/definitions/attempt-completion.tool.ts new file mode 100644 index 00000000..a87a4441 --- /dev/null +++ b/extension/src/agent/v1/tools/definitions/attempt-completion.tool.ts @@ -0,0 +1,85 @@ +import { z } from "zod" +import { defineToolCallFormat, generateToolCallExamples } from "./tool-call-template" + +/** + * Unified definition for attempt_completion tool + * Combines schema, prompt, and type definitions in one place + * Uses consistent calling format + */ + +/** + * ZOD Schema Definition + */ +export const schema = z.object({ + result: z + .string() + .describe( + "The result of the task. Formulate this result in a way that is final and does not require further input from the user. Don't end your result with questions or offers for further assistance." + ), + // Commented out as in original implementation + // command: z + // .string() + // .optional() + // .describe( + // 'The CLI command to execute to show a live demo of the result to the user. For example, use "open index.html" to display a created website. This should be valid for the current operating system. Ensure the command is properly formatted and does not contain any harmful instructions.' + // ), +}) + +/** + * Type definitions derived from schema + */ +export type AttemptCompletionInput = z.infer + +export type AttemptCompletionToolParams = { + name: "attempt_completion" + input: AttemptCompletionInput +} + +/** + * Example parameter values for tool calls + */ +const exampleParams = [ + { result: "The requested feature has been implemented successfully." }, + { result: "The website is ready for review." }, + { result: "The data analysis is complete. Please find the report attached." } +] + +/** + * Generate consistent example tool calls + */ +const exampleCalls = generateToolCallExamples("attempt_completion", exampleParams) + +/** + * Prompt definition for LLM consumption + * Based on prompts/tools/attempt-complete.ts but with unified calling format + */ +export const promptDefinition = { + name: "attempt_completion", + description: "After each tool use, the user will respond with the result of that tool use, i.e. if it succeeded or failed, along with any reasons for failure. Once you've received the results of tool uses and can confirm that the task is complete, use this tool to present the result of your work to the user. The user may respond with feedback if they are not satisfied with the result, which you can use to make improvements and try again.", + parameters: { + result: { + type: "string", + description: "The result of the task. Formulate this result in a way that is final and does not require further input from the user. Don't end your result with questions or offers for further assistance.", + required: true, + }, + }, + capabilities: [ + "You can use attempt_completion tool to present the result of your work to the user, this tool is used after you've received the results of tool uses and can confirm that the task is complete, the user may respond with feedback if they are not satisfied with the result, which you can use to make improvements and try again." + ], + examples: exampleCalls.map((call, i) => ({ + description: `Complete task with result: ${exampleParams[i].result.substring(0, 30)}${exampleParams[i].result.length > 30 ? '...' : ''}`, + output: call + })), + ...defineToolCallFormat("attempt_completion") +} + +/** + * Full tool definition - exports everything needed in one place + */ +export const attemptCompletionTool = { + name: "attempt_completion", + schema, + prompt: promptDefinition, + examples: promptDefinition.examples, + callFormat: promptDefinition.callFormat +} \ No newline at end of file diff --git a/extension/src/agent/v1/tools/definitions/execute-command.tool.ts b/extension/src/agent/v1/tools/definitions/execute-command.tool.ts new file mode 100644 index 00000000..9e0f435e --- /dev/null +++ b/extension/src/agent/v1/tools/definitions/execute-command.tool.ts @@ -0,0 +1,79 @@ +import { z } from "zod" +import { defineToolCallFormat, generateToolCallExamples } from "./tool-call-template" + +/** + * Unified definition for execute_command tool + * Combines schema, prompt, and type definitions in one place + * Uses consistent calling format + */ + +/** + * ZOD Schema Definition + */ +export const schema = z.object({ + command: z + .string() + .describe( + "The CLI command to execute. This should be valid for the current operating system. Ensure the command is properly formatted and does not contain any harmful instructions." + ), +}) + +/** + * Type definitions derived from schema + */ +export type ExecuteCommandInput = z.infer + +export type ExecuteCommandToolParams = { + name: "execute_command" + input: ExecuteCommandInput +} + +/** + * Example parameter values for tool calls + */ +const exampleParams = [ + { command: "ls -la" }, + { command: "mkdir new_folder && cd new_folder" }, + { command: "echo 'Hello World' > hello.txt" }, + { command: "npm install express" } +] + +/** + * Generate consistent example tool calls + */ +const exampleCalls = generateToolCallExamples("execute_command", exampleParams) + +/** + * Prompt definition for LLM consumption + * Based on prompts/tools/execute-command.ts but with unified calling format + */ +export const promptDefinition = { + name: "execute_command", + description: "Request to execute a CLI command on the system. Use this when you need to perform system operations or run specific commands to accomplish any step in the user's task. You must tailor your command to the user's system and provide a clear explanation of what the command does. Prefer to execute complex CLI commands over creating executable scripts, as they are more flexible and easier to run. Commands will be executed in the current working directory.", + parameters: { + command: { + type: "string", + description: "The CLI command to execute. This should be valid for the current operating system. Ensure the command is properly formatted and does not contain any harmful instructions.\nCOMMAND CANNOT RUN SOMETHING like 'npm start', 'yarn start', 'python -m http.server', etc. (if you want to start a server, you must use the server_runner tool.)", + required: true, + }, + }, + capabilities: [ + "You can use execute_command tool to execute a CLI command on the system, this tool is useful when you need to perform system operations or run specific commands to accomplish any step in the user's task, you must tailor your command to the user's system and provide a clear explanation of what the command does, prefer to execute complex CLI commands over creating executable scripts, as they are more flexible and easier to run. for example, you can use this tool to install a package using npm, run a build command, etc or for example remove a file, create a directory, copy a file, etc." + ], + examples: exampleCalls.map((call, i) => ({ + description: `Execute: ${exampleParams[i].command}`, + output: call + })), + ...defineToolCallFormat("execute_command") +} + +/** + * Full tool definition - exports everything needed in one place + */ +export const executeCommandTool = { + name: "execute_command", + schema, + prompt: promptDefinition, + examples: promptDefinition.examples, + callFormat: promptDefinition.callFormat +} \ No newline at end of file diff --git a/extension/src/agent/v1/tools/definitions/exit-agent.tool.ts b/extension/src/agent/v1/tools/definitions/exit-agent.tool.ts new file mode 100644 index 00000000..3606beb4 --- /dev/null +++ b/extension/src/agent/v1/tools/definitions/exit-agent.tool.ts @@ -0,0 +1,85 @@ +import { z } from "zod" +import { defineToolCallFormat, generateToolCallExamples } from "./tool-call-template" + +/** + * Unified definition for exit_agent tool + * Combines schema, prompt, and type definitions in one place + * Uses consistent calling format + */ + +/** + * ZOD Schema Definition + */ +export const schema = z.object({ + result: z.string().describe("The result of the sub-agent operation"), +}) + +/** + * Type definitions derived from schema + */ +export type ExitAgentInput = z.infer + +export type ExitAgentToolParams = { + name: "exit_agent" + input: ExitAgentInput +} + +/** + * Example parameter values for tool calls + */ +const exampleParams = [ + { + result: `I've installed the following dependencies: +- Jest +- Enzyme +- Axios +- React Testing Library + +Here is the unit test output: +Test Suites: 3 passed,1 failed, 4 total +PASS src/components/App.test.js +PASS src/components/Header.test.js +PASS src/components/Footer.test.js +FAIL src/components/Profile.test.js - Expected 1, received 0 (I think this is related to the API call)` + } +] + +/** + * Generate consistent example tool calls + */ +const exampleCalls = generateToolCallExamples("exit_agent", exampleParams) + +/** + * Prompt definition for LLM consumption + * Based on prompts/tools/exit-agent.ts but with unified calling format + */ +export const promptDefinition = { + name: "exit_agent", + description: "Exit the current task and return the final result of the task, the result must be detailed and to the point, this result will be passed back to the user for further processing or task completion.", + parameters: { + result: { + type: "string", + description: "The final result or output of the agent operation. This should be a string describing what was accomplished or any relevant output that should be passed back to the user.", + required: true, + }, + }, + capabilities: [ + "Once you finish and finalized the task, you can use exit_agent tool to exit the current task and return the final result of the task, the result must be detailed and to the point, this result will be passed back to the user for further processing or task completion, this tool is used to let the user know that the task is completed and the final result is ready for review." + ], + examples: exampleCalls.map((call, i) => ({ + description: `Exit agent with results from task completion`, + output: call + })), + ...defineToolCallFormat("exit_agent") +} + +/** + * Full tool definition - exports everything needed in one place + */ +export const exitAgentTool = { + name: "exit_agent", + schema, + prompt: promptDefinition, + examples: promptDefinition.examples, + callFormat: promptDefinition.callFormat +} \ No newline at end of file diff --git a/extension/src/agent/v1/tools/definitions/explore-repo-folder.tool.ts b/extension/src/agent/v1/tools/definitions/explore-repo-folder.tool.ts new file mode 100644 index 00000000..88fe7f0f --- /dev/null +++ b/extension/src/agent/v1/tools/definitions/explore-repo-folder.tool.ts @@ -0,0 +1,78 @@ +import { z } from "zod" +import { defineToolCallFormat, generateToolCallExamples } from "./tool-call-template" + +/** + * Unified definition for explore_repo_folder tool + * Combines schema, prompt, and type definitions in one place + * Uses consistent calling format + */ + +/** + * ZOD Schema Definition + */ +export const schema = z.object({ + path: z + .string() + .describe( + "The path of the directory (relative to the current working directory) to list top-level source code definitions for." + ), +}) + +/** + * Type definitions derived from schema + */ +export type ExploreRepoFolderInput = z.infer + +export type ExploreRepoFolderToolParams = { + name: "explore_repo_folder" + input: ExploreRepoFolderInput +} + +/** + * Example parameter values for tool calls + */ +const exampleParams = [ + { path: "/src" }, + { path: "/lib" }, + { path: "/components" } +] + +/** + * Generate consistent example tool calls + */ +const exampleCalls = generateToolCallExamples("explore_repo_folder", exampleParams) + +/** + * Prompt definition for LLM consumption + * Based on prompts/tools/explore-repo-folder.ts but with unified calling format + */ +export const promptDefinition = { + name: "explore_repo_folder", + description: "Request to list definition names (classes, functions, methods, etc.) used in source code files at the top level of the specified directory. This tool provides insights into the codebase structure and important constructs, encapsulating high-level concepts and relationships that are crucial for understanding the overall architecture.", + parameters: { + path: { + type: "string", + description: "The path of the directory (relative to the current working directory) to list top level source code definitions for.", + required: true, + }, + }, + capabilities: [ + "You can use explore_repo_folder tool to list definition names (classes, functions, methods, etc.) used in source code files at the top level of the specified directory. This tool provides insights into the codebase structure and important constructs, encapsulating high-level concepts and relationships that are crucial for understanding the overall architecture." + ], + examples: exampleCalls.map((call, i) => ({ + description: `Explore directory: ${exampleParams[i].path}`, + output: call + })), + ...defineToolCallFormat("explore_repo_folder") +} + +/** + * Full tool definition - exports everything needed in one place + */ +export const exploreRepoFolderTool = { + name: "explore_repo_folder", + schema, + prompt: promptDefinition, + examples: promptDefinition.examples, + callFormat: promptDefinition.callFormat +} \ No newline at end of file diff --git a/extension/src/agent/v1/tools/definitions/file-change-plan.tool.ts b/extension/src/agent/v1/tools/definitions/file-change-plan.tool.ts new file mode 100644 index 00000000..835a81f1 --- /dev/null +++ b/extension/src/agent/v1/tools/definitions/file-change-plan.tool.ts @@ -0,0 +1,90 @@ +import { z } from "zod" +import { defineToolCallFormat, generateToolCallExamples } from "./tool-call-template" + +/** + * Unified definition for file_changes_plan tool + * Combines schema, prompt, and type definitions in one place + * Uses consistent calling format + */ + +/** + * ZOD Schema Definition + */ +export const schema = z.object({ + path: z.string().describe("The path of the file you want to change."), + what_to_accomplish: z.string().describe("What you want to accomplish with this file change."), +}) + +/** + * Type definitions derived from schema + */ +export type FileChangePlanInput = z.infer + +export type FileChangePlanToolParams = { + name: "file_changes_plan" + input: FileChangePlanInput +} + +/** + * Example parameter values for tool calls + */ +const exampleParams = [ + { + path: "/src", + what_to_accomplish: "Implement a new authentication flow for user login" + }, + { + path: "/lib", + what_to_accomplish: "Refactor utility functions to improve performance" + }, + { + path: "/components", + what_to_accomplish: "Add responsive design to UI components" + } +] + +/** + * Generate consistent example tool calls + */ +const exampleCalls = generateToolCallExamples("file_changes_plan", exampleParams) + +/** + * Prompt definition for LLM consumption + */ +export const promptDefinition = { + name: "file_changes_plan", + description: "Create a plan for changing a file or directory. This tool helps with planning modifications by documenting what needs to be accomplished.", + parameters: { + path: { + type: "string", + description: "The path of the file or directory you want to change.", + required: true, + }, + what_to_accomplish: { + type: "string", + description: "What you want to accomplish with this file change.", + required: true, + }, + }, + capabilities: [ + "You can use file_changes_plan to document what changes you intend to make to a file or directory.", + "This tool helps with organized planning before making code modifications.", + "Clearly articulate what you want to accomplish with the changes." + ], + examples: exampleCalls.map((call, i) => ({ + description: `Plan changes for ${exampleParams[i].path}`, + output: call + })), + ...defineToolCallFormat("file_changes_plan") +} + +/** + * Full tool definition - exports everything needed in one place + */ +export const fileChangePlanTool = { + name: "file_changes_plan", + schema, + prompt: promptDefinition, + examples: promptDefinition.examples, + callFormat: promptDefinition.callFormat +} \ No newline at end of file diff --git a/extension/src/agent/v1/tools/definitions/file-editor.tool.ts b/extension/src/agent/v1/tools/definitions/file-editor.tool.ts new file mode 100644 index 00000000..ec7db215 --- /dev/null +++ b/extension/src/agent/v1/tools/definitions/file-editor.tool.ts @@ -0,0 +1,193 @@ +import { z } from "zod" +import dedent from "dedent" +import { defineToolCallFormat, generateToolCallExamples } from "./tool-call-template" + +/** + * Unified definition for file_editor tool + * Combines schema, prompt, and type definitions in one place + * Uses consistent calling format + */ + +/** + * File editor modes + */ +export const FileEditorModes = ["edit", "whole_write", "rollback"] as const + +/** + * ZOD Schema Definition + */ +export const schema = z.object({ + path: z.string().describe("The path of the file to write to (relative to the current working directory)."), + mode: z.preprocess((val) => { + // Ensure that val is a string before passing it to the enum validator + if (typeof val === "string") { + return val + } + return undefined // This will fail the enum check if not a string + }, z.enum(FileEditorModes).describe("The mode of the file editor tool.")), + commit_message: z.string().optional().describe("The commit message to use when committing changes to the file."), + kodu_content: z + .string() + .describe( + "The full content to write to the file when creating a new file. Always provide the complete content without any truncation." + ) + .optional(), + kodu_diff: z + .string() + .describe( + "The `SEARCH/REPLACE` blocks representing the changes to be made to an existing file. These blocks must be formatted correctly, matching exact existing content for `SEARCH` and precise modifications for `REPLACE`." + ) + .optional(), +}) + +/** + * Type definitions derived from schema + */ +export type FileEditorInput = z.infer + +export type FileEditorToolParams = { + name: "file_editor" + input: FileEditorInput +} + +/** + * Example parameter values for tool calls + */ +const exampleParams = [ + { + path: "myapp/utility.py", + mode: "edit", + commit_message: "feat(utility): add import and rename function", + kodu_diff: dedent` + <<<<<<< HEAD + (3 lines of exact context) + ======= + (3 lines of exact context + new import + updated function name) + >>>>>>> updated + <<<<<<< HEAD + (3 lines of exact context for second edit) + ======= + (3 lines of exact context for second edit with replaced lines) + >>>>>>> updated + ` + }, + { + path: "src/components/UserProfile.tsx", + mode: "whole_write", + commit_message: "feat(components): create UserProfile component", + kodu_content: "// Full file content here..." + } +] + +/** + * Generate consistent example tool calls + */ +const exampleCalls = generateToolCallExamples("file_editor", exampleParams) + +/** + * Prompt definition for LLM consumption + * Based on prompts/tools/file-editor.ts but with unified calling format + */ +export const promptDefinition = { + name: "file_editor", + description: "Requests to create or edit a specific file. Edit mode for precise changes, whole_write mode for full content replacement", + parameters: { + mode: { + type: "string", + description: dedent` + The operation mode of the file_editor tool: + - "whole_write": create or completely rewrite a file. + - "edit": make precise edits to an existing file.`, + required: true, + }, + path: { + type: "string", + description: "The relative path of the file to edit, create, or roll back.", + required: true, + }, + commit_message: { + type: "string", + description: dedent` + A short, concise commit message describing the change. + Required if "mode" is "whole_write" or "edit". + Should follow conventional commits standards (e.g., "feat:", "fix:", etc.). + `, + required: 'Required for "whole_write" or "edit" mode', + }, + kodu_diff: { + type: "string", + description: dedent`it is required to make a precise kodu_diff if "mode" is "edit". + Must use standard Git conflict merge format as follows: + <<<<<<< HEAD + (the exact lines from the file, including 3 lines of context before and 3 lines of context after the replaced lines) + ======= + (the new lines that will replace the old ones; must be final, no placeholders) + >>>>>>> updated + You may include up to 5 such blocks if multiple edits are needed, ordered from the top of the file to the bottom of the file (line numbers). Each block must have at least 3 lines of unchanged context before the snippet you want to replace. + The content between <<<<<<< HEAD and ======= must exactly match the file's current content, including whitespace and indentation. The content between ======= and >>>>>>> updated is the fully updated version. + `, + required: 'Required for "edit" mode', + }, + kodu_content: { + type: "string", + description: dedent` + Required if "mode" is "whole_write". This must be the complete, final content of the file with no placeholders or omissions. It overwrites the file if it already exists or creates a new one if it does not.`, + required: 'Required for "whole_write" mode', + }, + }, + capabilities: [ + "Use 'whole_write' to replace the entire file content or create a new file.", + "Use 'edit' with kodu_diff, it will allow you to make precise changes to the file using Git conflict format (up to 5 blocks per call)." + ], + extraDescriptions: dedent` + ### Key Principles When Using file_editor + + 1. **Gather all changes first**: Apply them in a single transaction to reduce file writes. + 2. **Think carefully before calling the tool**: Plan your edits to confirm exactly what lines to change. + 3. **No placeholders** in the final snippets for "edit" or "whole_write" modes. Provide exact text. + 4. **Think carefully about the file content and file type**: You should think carefully about the type of file we are editing and match the editing accordingly. + For example for typescript files, you should be careful about the syntax and indentation and typing. + For python files, you should be careful about the syntax and indentation, python is very sensitive to indentation and whitespace and can break if misaligned or misused. + For react files, you should be careful about the syntax and indentation and the JSX syntax, remember to close all tags and use the correct syntax, don't forget the best practices and the react hooks. + You should follow this idea for any programming language or file type you are editing, first think about the file type and the syntax and the best practices and then start editing. + + ### Key Principles per Mode + + **'whole_write' Mode**: + - Provide the file's full content in 'kodu_content'. This overwrites an existing file entirely or creates a new file. + - Must include a valid commit_message. + + **'edit' Mode**: + - Provide the precise changes via 'kodu_diff' using standard Git conflict markers, up to 5 blocks per call, in top-to-bottom file order while maintaining indentation and whitespace. + - Each block must have at least 3 lines of exact context before (and ideally after) the snippet being replaced. + - The content in <<<<<<< HEAD ... ======= must exactly match the file's current lines. + - The content in ======= ... >>>>>>> updated is your fully updated replacement with no placeholders. + - If multiple snippets need editing, combine them into one 'kodu_diff' string with multiple blocks, in top-to-bottom file order. + - Must include a valid commit_message. + - Must use precise changes and find the code block boundaries and edit only the needed lines. + + **CRITICAL RULES FOR 'edit' MODE**: + 1. You must have read the file (latest version) before editing. No guesswork; the HEAD section must match character-for-character. + 2. Maintain indentation and whitespace exactly. Python or similarly sensitive files can break if misaligned. + 3. Provide at least 3 lines of unchanged context around each replaced snippet. + 4. Write the changes from top to bottom, ensuring the blocks appear in the same order as they do in the file. + 5. Escape special characters (\t, \n, etc.) properly if needed. + 6. You should try to aim for a precise edit while using only the necessary lines to be changed, find the code block boundaries and edit only the needed lines. + `, + examples: exampleCalls.map((call, i) => ({ + description: i === 0 ? "Editing a file with precise changes" : "Creating a new file or overwriting an existing file", + output: call + })), + ...defineToolCallFormat("file_editor") +} + +/** + * Full tool definition - exports everything needed in one place + */ +export const fileEditorTool = { + name: "file_editor", + schema, + prompt: promptDefinition, + examples: promptDefinition.examples, + callFormat: promptDefinition.callFormat +} \ No newline at end of file diff --git a/extension/src/agent/v1/tools/definitions/index.ts b/extension/src/agent/v1/tools/definitions/index.ts new file mode 100644 index 00000000..c220307f --- /dev/null +++ b/extension/src/agent/v1/tools/definitions/index.ts @@ -0,0 +1,101 @@ +/** + * Central registry for all unified tool definitions + * Each tool definition combines schema, prompt, and type information + * with a consistent calling format + */ + +// Import all tool definitions +import { readFileTool } from './read-file.tool' +import { searchFilesTool } from './search-files.tool' +import { executeCommandTool } from './execute-command.tool' +import { listFilesTool } from './list-files.tool' +import { fileEditorTool } from './file-editor.tool' +import { askFollowupQuestionTool } from './ask-followup-question.tool' +import { searchSymbolTool } from './search-symbol.tool' +import { urlScreenshotTool } from './url-screenshot.tool' +import { attemptCompletionTool } from './attempt-completion.tool' +import { exploreRepoFolderTool } from './explore-repo-folder.tool' +import { spawnAgentTool } from './spawn-agent.tool' +import { exitAgentTool } from './exit-agent.tool' +import { serverRunnerTool } from './server-runner.tool' +import { webSearchTool } from './web-search.tool' +import { writeToFileTool } from './write-to-file.tool' +import { addInterestedFileTool } from './add-interested-file.tool' +import { fileChangePlanTool } from './file-change-plan.tool' +import { submitReviewTool } from './submit-review.tool' +import { rejectFileChangesTool } from './reject-file-changes.tool' + +// Export individual tool definitions +export * from './tool-call-template' +export * from './read-file.tool' +export * from './search-files.tool' +export * from './execute-command.tool' +export * from './list-files.tool' +export * from './file-editor.tool' +export * from './ask-followup-question.tool' +export * from './search-symbol.tool' +export * from './url-screenshot.tool' +export * from './attempt-completion.tool' +export * from './explore-repo-folder.tool' +export * from './spawn-agent.tool' +export * from './exit-agent.tool' +export * from './server-runner.tool' +export * from './web-search.tool' +export * from './write-to-file.tool' +export * from './add-interested-file.tool' +export * from './file-change-plan.tool' +export * from './submit-review.tool' +export * from './reject-file-changes.tool' + +// Export a registry of all tools +export const toolDefinitions = [ + readFileTool, + searchFilesTool, + executeCommandTool, + listFilesTool, + fileEditorTool, + askFollowupQuestionTool, + searchSymbolTool, + urlScreenshotTool, + attemptCompletionTool, + exploreRepoFolderTool, + spawnAgentTool, + exitAgentTool, + serverRunnerTool, + webSearchTool, + writeToFileTool, + addInterestedFileTool, + fileChangePlanTool, + submitReviewTool, + rejectFileChangesTool +] + +// Tool registry type +export type ToolName = typeof toolDefinitions[number]['name'] + +// Helper to get a tool definition by name +export function getToolDefinition(name: string) { + return toolDefinitions.find(tool => tool.name === name) +} + +/** + * Get the XML calling format for a specific tool + * This provides a consistent way for LLMs to call tools + */ +export function getToolCallFormat(toolName: string) { + const tool = getToolDefinition(toolName) + return tool?.callFormat || null +} + +/** + * Extract all tool examples for use in the system prompt + */ +export function getAllToolExamples() { + return toolDefinitions.flatMap(tool => + tool.examples.map(example => ({ + toolName: tool.name, + description: example.description, + format: example.output + })) + ) +} \ No newline at end of file diff --git a/extension/src/agent/v1/tools/definitions/list-files.tool.ts b/extension/src/agent/v1/tools/definitions/list-files.tool.ts new file mode 100644 index 00000000..d4f856e3 --- /dev/null +++ b/extension/src/agent/v1/tools/definitions/list-files.tool.ts @@ -0,0 +1,87 @@ +import { z } from "zod" +import { defineToolCallFormat, generateToolCallExamples } from "./tool-call-template" + +/** + * Unified definition for list_files tool + * Combines schema, prompt, and type definitions in one place + * Uses consistent calling format + */ + +/** + * ZOD Schema Definition + */ +export const schema = z.object({ + path: z + .string() + .describe("The path of the directory to list contents for (relative to the current working directory)."), + recursive: z + .enum(["true", "false"]) + .optional() + .describe( + "Whether to list files recursively. Use 'true' for recursive listing, 'false' or omit for top-level only." + ), +}) + +/** + * Type definitions derived from schema + */ +export type ListFilesInput = z.infer + +export type ListFilesToolParams = { + name: "list_files" + input: ListFilesInput +} + +/** + * Example parameter values for tool calls + */ +const exampleParams = [ + { path: "/documents" }, + { path: "/projects", recursive: "true" }, + { path: "." } +] + +/** + * Generate consistent example tool calls + */ +const exampleCalls = generateToolCallExamples("list_files", exampleParams) + +/** + * Prompt definition for LLM consumption + * Based on prompts/tools/list-files.ts but with unified calling format + */ +export const promptDefinition = { + name: "list_files", + description: "Request to list files and directories within the specified directory. If recursive is true, it will list all files and directories recursively. If recursive is false or not provided, it will only list the top-level contents. Do not use this tool to confirm the existence of files you may have created, as the user will let you know if the files were created successfully or not.", + parameters: { + path: { + type: "string", + description: "The path of the directory to list contents for (relative to the current working directory)", + required: true, + }, + recursive: { + type: "string", + description: "Whether to list files recursively. Use true for recursive listing, false or omit for top-level only.", + required: false, + }, + }, + capabilities: [ + "You can use list_files tool to list files and directories within the specified directory. This tool is useful for understanding the contents of a directory, verifying the presence of files, or identifying the structure of a project." + ], + examples: exampleCalls.map((call, i) => ({ + description: `List files in ${exampleParams[i].path}${exampleParams[i].recursive ? ' recursively' : ''}`, + output: call + })), + ...defineToolCallFormat("list_files") +} + +/** + * Full tool definition - exports everything needed in one place + */ +export const listFilesTool = { + name: "list_files", + schema, + prompt: promptDefinition, + examples: promptDefinition.examples, + callFormat: promptDefinition.callFormat +} \ No newline at end of file diff --git a/extension/src/agent/v1/tools/definitions/read-file.tool.ts b/extension/src/agent/v1/tools/definitions/read-file.tool.ts new file mode 100644 index 00000000..a5981dbc --- /dev/null +++ b/extension/src/agent/v1/tools/definitions/read-file.tool.ts @@ -0,0 +1,75 @@ +import { z } from "zod" +import { defineToolCallFormat, generateToolCallExamples } from "./tool-call-template" + +/** + * Unified definition for read_file tool + * Combines schema, prompt, and type definitions in one place + * Uses consistent calling format + */ + +/** + * ZOD Schema Definition + */ +export const schema = z.object({ + path: z.string().describe("The path of the file to read (relative to the current working directory)."), +}) + +/** + * Type definitions derived from schema + */ +export type ReadFileInput = z.infer + +export type ReadFileToolParams = { + name: "read_file" + input: ReadFileInput +} + +/** + * Example parameter values for tool calls + */ +const exampleParams = [ + { path: "/src/index.js" }, + { path: "/config/settings.json" }, + { path: "/documents/report.docx" } +] + +/** + * Generate consistent example tool calls + */ +const exampleCalls = generateToolCallExamples("read_file", exampleParams) + +/** + * Prompt definition for LLM consumption + * Based on prompts/tools/read-file.ts but with unified calling format + */ +export const promptDefinition = { + name: "read_file", + description: "Request to read the contents of a file at the specified path. Use this when you need to examine the contents of an existing file you do not know the contents of, for example to analyze code, review text files, or extract information from configuration files. Automatically extracts raw text from PDF and DOCX files. May not be suitable for other types of binary files, as it returns the raw content as a string.", + parameters: { + path: { + type: "string", + description: "The path of the file to read (relative to the current working directory)", + required: true, + }, + }, + capabilities: [ + "You can use read_file tool to read the contents of a file at the specified path and time, this tool is useful when you need to examine the contents of an existing file you do not know the contents of, for example to analyze code, review text files, or extract information from configuration files.", + "When you use read_file tool, it will automatically extracts raw text from PDF and DOCX files, but may not be suitable for other types of binary files, as it returns the raw content as a string.", + ], + examples: exampleCalls.map((call, i) => ({ + description: `Read ${exampleParams[i].path}`, + output: call + })), + ...defineToolCallFormat("read_file") +} + +/** + * Full tool definition - exports everything needed in one place + */ +export const readFileTool = { + name: "read_file", + schema, + prompt: promptDefinition, + examples: promptDefinition.examples, + callFormat: promptDefinition.callFormat +} \ No newline at end of file diff --git a/extension/src/agent/v1/tools/definitions/reject-file-changes.tool.ts b/extension/src/agent/v1/tools/definitions/reject-file-changes.tool.ts new file mode 100644 index 00000000..7b48f4b2 --- /dev/null +++ b/extension/src/agent/v1/tools/definitions/reject-file-changes.tool.ts @@ -0,0 +1,81 @@ +import { z } from "zod" +import { defineToolCallFormat, generateToolCallExamples } from "./tool-call-template" + +/** + * Unified definition for reject_file_changes tool + * Combines schema, prompt, and type definitions in one place + * Uses consistent calling format + */ + +/** + * ZOD Schema Definition + */ +export const schema = z.object({ + reason: z.string().describe("The reason for rejecting the file changes."), +}) + +/** + * Type definitions derived from schema + */ +export type RejectFileChangesInput = z.infer + +export type RejectFileChangesToolParams = { + name: "reject_file_changes" + input: RejectFileChangesInput +} + +/** + * Example parameter values for tool calls + */ +const exampleParams = [ + { + reason: "The proposed changes would break existing functionality in the authentication system." + }, + { + reason: "The code style doesn't match the project's coding standards." + }, + { + reason: "The implementation doesn't handle edge cases properly." + } +] + +/** + * Generate consistent example tool calls + */ +const exampleCalls = generateToolCallExamples("reject_file_changes", exampleParams) + +/** + * Prompt definition for LLM consumption + */ +export const promptDefinition = { + name: "reject_file_changes", + description: "Reject proposed file changes and provide a reason for the rejection. This tool helps with quality control by documenting why changes were not accepted.", + parameters: { + reason: { + type: "string", + description: "The reason for rejecting the file changes.", + required: true, + }, + }, + capabilities: [ + "You can use reject_file_changes to document why proposed changes to files are not acceptable.", + "Provide a clear explanation for why the changes are being rejected.", + "This helps maintain code quality by documenting rejection reasons." + ], + examples: exampleCalls.map((call, i) => ({ + description: "Reject file changes", + output: call + })), + ...defineToolCallFormat("reject_file_changes") +} + +/** + * Full tool definition - exports everything needed in one place + */ +export const rejectFileChangesTool = { + name: "reject_file_changes", + schema, + prompt: promptDefinition, + examples: promptDefinition.examples, + callFormat: promptDefinition.callFormat +} \ No newline at end of file diff --git a/extension/src/agent/v1/tools/definitions/search-files.tool.ts b/extension/src/agent/v1/tools/definitions/search-files.tool.ts new file mode 100644 index 00000000..7c253eb1 --- /dev/null +++ b/extension/src/agent/v1/tools/definitions/search-files.tool.ts @@ -0,0 +1,95 @@ +import { z } from "zod" +import { defineToolCallFormat, generateToolCallExamples } from "./tool-call-template" + +/** + * Unified definition for search_files tool + * Combines schema, prompt, and type definitions in one place + * Uses consistent calling format + */ + +/** + * ZOD Schema Definition + */ +export const schema = z.object({ + path: z + .string() + .describe( + "The path of the directory to search in (relative to the current working directory). This directory will be recursively searched." + ), + regex: z.string().describe("The regular expression pattern to search for. Uses Rust regex syntax."), + filePattern: z + .string() + .optional() + .describe( + "Optional glob pattern to filter files (e.g., '*.ts' for TypeScript files). If not provided, it will search all files (*)." + ), +}) + +/** + * Type definitions derived from schema + */ +export type SearchFilesInput = z.infer + +export type SearchFilesToolParams = { + name: "search_files" + input: SearchFilesInput +} + +/** + * Example parameter values for tool calls + */ +const exampleParams = [ + { path: "/logs", regex: "Error.*" }, + { path: "/src", regex: "function\\s+\\w+", filePattern: "*.js" }, + { path: "/documents", regex: "TODO" } +] + +/** + * Generate consistent example tool calls + */ +const exampleCalls = generateToolCallExamples("search_files", exampleParams) + +/** + * Prompt definition for LLM consumption + * Based on prompts/tools/search-files.ts but with unified calling format + */ +export const promptDefinition = { + name: "search_files", + description: "Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context.", + parameters: { + path: { + type: "string", + description: "The path of the directory to search in (relative to the current working directory). This directory will be recursively searched.", + required: true, + }, + regex: { + type: "string", + description: "The regular expression pattern to search for. Uses Rust regex syntax.", + required: true, + }, + filePattern: { + type: "string", + description: "Glob pattern to filter files (e.g., '*.ts' for TypeScript files). If not provided, it will search all files (*).", + required: false, + }, + }, + capabilities: [ + "You can use search_files tool to perform regex searches across files in a specified directory, outputting context-rich results that include surrounding lines. This is particularly useful for understanding code patterns, finding specific implementations, or identifying areas that need refactoring." + ], + examples: exampleCalls.map((call, i) => ({ + description: `Search for ${exampleParams[i].regex} in ${exampleParams[i].path}`, + output: call + })), + ...defineToolCallFormat("search_files") +} + +/** + * Full tool definition - exports everything needed in one place + */ +export const searchFilesTool = { + name: "search_files", + schema, + prompt: promptDefinition, + examples: promptDefinition.examples, + callFormat: promptDefinition.callFormat +} \ No newline at end of file diff --git a/extension/src/agent/v1/tools/definitions/search-symbol.tool.ts b/extension/src/agent/v1/tools/definitions/search-symbol.tool.ts new file mode 100644 index 00000000..f9306a2b --- /dev/null +++ b/extension/src/agent/v1/tools/definitions/search-symbol.tool.ts @@ -0,0 +1,80 @@ +import { z } from "zod" +import { defineToolCallFormat, generateToolCallExamples } from "./tool-call-template" + +/** + * Unified definition for search_symbol tool + * Combines schema, prompt, and type definitions in one place + * Uses consistent calling format + */ + +/** + * ZOD Schema Definition + */ +export const schema = z.object({ + symbolName: z.string().describe("The name of the symbol to search for (e.g., function name, class name)"), + path: z.string().describe("The path to search in (relative to the current working directory)"), +}) + +/** + * Type definitions derived from schema + */ +export type SearchSymbolInput = z.infer + +export type SearchSymbolToolParams = { + name: "search_symbol" + input: SearchSymbolInput +} + +/** + * Example parameter values for tool calls + */ +const exampleParams = [ + { symbolName: "handleRequest", path: "src" }, + { symbolName: "UserService", path: "src/services" }, + { symbolName: "processData", path: "lib/utils" } +] + +/** + * Generate consistent example tool calls + */ +const exampleCalls = generateToolCallExamples("search_symbol", exampleParams) + +/** + * Prompt definition for LLM consumption + * Based on prompts/tools/search-symbol.ts but with unified calling format + */ +export const promptDefinition = { + name: "search_symbol", + description: "Request to find and understand code symbol (function, classe, method) in source files. This tool helps navigate and understand code structure by finding symbol definitions and their context. It's particularly useful for:\n- Understanding function implementations\n- Finding class definitions\n- Tracing method usage\n- Building mental models of code", + parameters: { + symbolName: { + type: "string", + description: "The name of the symbol to search for (e.g., function name, class name)", + required: true, + }, + path: { + type: "string", + description: "The path to search in (relative to the current working directory)", + required: true, + }, + }, + capabilities: [ + "You can use search_symbol tool to understand how a specific function, class, or method is implemented in the codebase it can help you map potential changes, relationships, and dependencies between different parts of the codebase." + ], + examples: exampleCalls.map((call, i) => ({ + description: `Search for symbol '${exampleParams[i].symbolName}' in ${exampleParams[i].path}`, + output: call + })), + ...defineToolCallFormat("search_symbol") +} + +/** + * Full tool definition - exports everything needed in one place + */ +export const searchSymbolTool = { + name: "search_symbol", + schema, + prompt: promptDefinition, + examples: promptDefinition.examples, + callFormat: promptDefinition.callFormat +} \ No newline at end of file diff --git a/extension/src/agent/v1/tools/definitions/server-runner.tool.ts b/extension/src/agent/v1/tools/definitions/server-runner.tool.ts new file mode 100644 index 00000000..d7e36478 --- /dev/null +++ b/extension/src/agent/v1/tools/definitions/server-runner.tool.ts @@ -0,0 +1,120 @@ +import { z } from "zod" +import { defineToolCallFormat, generateToolCallExamples } from "./tool-call-template" + +/** + * Unified definition for server_runner tool + * Combines schema, prompt, and type definitions in one place + * Uses consistent calling format + */ + +/** + * ZOD Schema Definition + */ +export const schema = z.object({ + commandType: z + .enum(["start", "stop", "restart", "getLogs"]) + .optional() + .describe( + "The type of operation to perform on the dev server. 'start' begins the server, 'stop' terminates it, 'restart' stops then starts the server, and 'getLogs' retrieves the server logs." + ), + serverName: z.string().optional().describe("The name of the terminal to use for the operation."), + commandToRun: z + .string() + .optional() + .describe( + "The specific command to execute for the operation. For 'start' and 'restart', this is typically the command to start your dev server (e.g., 'npm run dev'). For 'stop', it's the command to stop the server. For 'getLogs', this can be left empty." + ), + lines: z.string().default("-1").optional().describe("The number of lines to retrieve from the logs."), +}) + +/** + * Type definitions derived from schema + */ +export type ServerRunnerInput = z.infer + +export type ServerRunnerToolParams = { + name: "server_runner" + input: ServerRunnerInput +} + +/** + * Example parameter values for tool calls + */ +const exampleParams = [ + { + commandType: "start", + commandToRun: "cd frontend && npm run dev", + serverName: "frontend" + }, + { + commandType: "getLogs", + serverName: "frontend", + lines: "50" + }, + { + commandType: "restart", + commandToRun: "npm run dev", + serverName: "backend" + }, + { + commandType: "stop", + serverName: "frontend" + } +] + +/** + * Generate consistent example tool calls + */ +const exampleCalls = generateToolCallExamples("server_runner", exampleParams) + +/** + * Prompt definition for LLM consumption + * Based on prompts/tools/server-runner.ts but with unified calling format + */ +export const promptDefinition = { + name: "server_runner", + description: "start a server / development server. This tool is used to run web applications locally, backend server, or anytype of server. this is tool allow you to start, stop, restart, or get logs from a server instance and keep it in memory.\nTHIS IS THE ONLY TOOL THAT IS CAPABLE OF STARTING A SERVER, DO NOT USE THE execute_command TOOL TO START A SERVER, I REPEAT, DO NOT USE THE execute_command TOOL TO START A SERVER.\nYOU MUST GIVE A NAME FOR EACH SERVER INSTANCE YOU START, SO YOU CAN KEEP TRACK OF THEM.\nYou must always provide all the parameters for this tool.", + parameters: { + commandToRun: { + type: "string", + description: "The CLI command to start the server. This should be valid for the current operating system. Ensure the command is properly formatted and has the correct path to the directory you want to serve (relative to the current working directory).", + required: false, + }, + commandType: { + type: "string", + description: "The type of command to run. Use 'start' to start the server, 'stop' to stop it, 'restart' to restart it, or 'getLogs' to retrieve logs from the server.", + required: true, + }, + serverName: { + type: "string", + description: "The name of the terminal to use for the operation. This is used to identify the terminal instance where the server is running.", + required: true, + }, + lines: { + type: "string", + description: "The number of lines to retrieve from the server logs. This is only required when the commandType is 'getLogs'.", + required: "Required when commandType is 'getLogs'", + }, + }, + capabilities: [ + "You can use server_runner tool to start, stop, restart, or get logs from a server instance while keeping it in memory for future use, it's extremely useful for running web applications locally, backend server, or any type of server instance." + ], + examples: exampleCalls.map((call, i) => { + if (i === 0) {return { description: "Start a development server", output: call };} + if (i === 1) {return { description: "Get logs from a server", output: call };} + if (i === 2) {return { description: "Restart a server", output: call };} + return { description: "Stop a server", output: call }; + }), + ...defineToolCallFormat("server_runner") +} + +/** + * Full tool definition - exports everything needed in one place + */ +export const serverRunnerTool = { + name: "server_runner", + schema, + prompt: promptDefinition, + examples: promptDefinition.examples, + callFormat: promptDefinition.callFormat +} \ No newline at end of file diff --git a/extension/src/agent/v1/tools/definitions/spawn-agent.tool.ts b/extension/src/agent/v1/tools/definitions/spawn-agent.tool.ts new file mode 100644 index 00000000..1a1f4f5e --- /dev/null +++ b/extension/src/agent/v1/tools/definitions/spawn-agent.tool.ts @@ -0,0 +1,100 @@ +import { z } from "zod" +import { defineToolCallFormat, generateToolCallExamples } from "./tool-call-template" + +/** + * Unified definition for spawn_agent tool + * Combines schema, prompt, and type definitions in one place + * Uses consistent calling format + */ + +/** + * Agent type options + */ +export const SpawnAgentOptions = ["coder", "planner", "sub_task"] as const +export type SpawnAgentOptions = (typeof SpawnAgentOptions)[number] + +/** + * ZOD Schema Definition + */ +export const schema = z.object({ + agentName: z.enum(SpawnAgentOptions).describe("Name of the sub-agent for identification"), + instructions: z.string().describe("Instructions for the sub-agent"), + files: z.string().optional().describe("Files to be processed by the sub-agent"), +}) + +/** + * Type definitions derived from schema + */ +export type SpawnAgentInput = z.infer + +export type SpawnAgentToolParams = { + name: "spawn_agent" + input: SpawnAgentInput +} + +/** + * Example parameter values for tool calls + */ +const exampleParams = [ + { + agentName: "sub_task", + instructions: "Take a look at the project files and install the dependencies. Run the unit tests and report back the results with any failures.", + files: "package.json,README.md" + }, + { + agentName: "planner", + instructions: "Create a detailed plan for implementing a new user dashboard feature. Break down the requirements into manageable sub-tasks and identify dependencies." + } +] + +/** + * Generate consistent example tool calls + */ +const exampleCalls = generateToolCallExamples("spawn_agent", exampleParams) + +/** + * Prompt definition for LLM consumption + * Based on prompts/tools/spawn-agent.ts but with unified calling format + */ +export const promptDefinition = { + name: "spawn_agent", + description: "Request to spawn a sub task agent with specific instructions and capabilities. This tool allows you to create specialized agents for specific sub tasks like planning, installing dependencies and running unit tests or even exploring the repo and reporting back. The tool requires user approval before creating the agent.", + parameters: { + agentName: { + type: "string", + description: "The type of agent to spawn. Must be one of: 'sub_task'. Each type is specialized for different tasks:\n- sub_task: For handling specific sub-components of a larger task", + required: true, + }, + instructions: { + type: "string", + description: "Detailed instructions for the sub-agent, describing its task and objectives, this is will be the meta prompt for the sub-agent. give few shots examples if possible", + required: true, + }, + files: { + type: "string", + description: "Comma-separated list of files that the sub-agent should focus on or work with. no spaces between files just comma separated values", + required: false, + }, + }, + capabilities: [ + "You can use spawn_agent tool to create specialized sub-agents for specific tasks like handling sub-tasks, each agent type has its own specialized capabilities and focus areas, the tool requires user approval before creating the agent and allows you to specify which files the agent should work with, ensuring proper context and state management throughout the agent's lifecycle.", + "Spawnning a sub-agent is a great way to break down a large task into smaller, more manageable sub-tasks. This allows you to focus on one task at a time, ensuring that each sub-task is completed successfully before moving on to the next one.", + "By creating specialized sub-agents, you can ensure that each agent is focused on a specific task or set of tasks, allowing for more efficient and effective task completion. This can help streamline your workflow and improve overall productivity." + ], + examples: exampleCalls.map((call, i) => ({ + description: i === 0 ? "Spawn an agent to install the dependencies and run the unit tests" : "Spawn a planner agent to break down a task", + output: call + })), + ...defineToolCallFormat("spawn_agent") +} + +/** + * Full tool definition - exports everything needed in one place + */ +export const spawnAgentTool = { + name: "spawn_agent", + schema, + prompt: promptDefinition, + examples: promptDefinition.examples, + callFormat: promptDefinition.callFormat +} \ No newline at end of file diff --git a/extension/src/agent/v1/tools/definitions/submit-review.tool.ts b/extension/src/agent/v1/tools/definitions/submit-review.tool.ts new file mode 100644 index 00000000..48af2553 --- /dev/null +++ b/extension/src/agent/v1/tools/definitions/submit-review.tool.ts @@ -0,0 +1,78 @@ +import { z } from "zod" +import { defineToolCallFormat, generateToolCallExamples } from "./tool-call-template" + +/** + * Unified definition for submit_review tool + * Combines schema, prompt, and type definitions in one place + * Uses consistent calling format + */ + +/** + * ZOD Schema Definition + */ +export const schema = z.object({ + review: z.string().describe("A formatted XML string containing the progress summary, questions, and next steps"), +}) + +/** + * Type definitions derived from schema + */ +export type SubmitReviewInput = z.infer + +export type SubmitReviewToolParams = { + name: "submit_review" + input: SubmitReviewInput +} + +/** + * Example parameter values for tool calls + */ +const exampleParams = [ + { + review: "Implemented basic authentication flow with login/signup endpoints\n\n- Should we add rate limiting to these endpoints?\n- Is the current token expiration time of 24h appropriate?\n\nWill implement password reset flow after review" + }, + { + review: "Created responsive UI components for the dashboard\n\n- Is the current layout suitable for mobile devices?\n- Should we add more interactive elements?\n\nWill implement dark mode theme next" + } +] + +/** + * Generate consistent example tool calls + */ +const exampleCalls = generateToolCallExamples("submit_review", exampleParams) + +/** + * Prompt definition for LLM consumption + */ +export const promptDefinition = { + name: "submit_review", + description: "Submit a review of your progress on the current task. This includes a summary of what has been accomplished, questions that need answers, and planned next steps.", + parameters: { + review: { + type: "string", + description: "A formatted XML string containing the progress summary, questions, and next steps", + required: true, + }, + }, + capabilities: [ + "You can use submit_review to provide a structured update on your progress.", + "Include what you've accomplished, questions you have, and what you plan to do next.", + "Format your review with XML tags for progress_summary, questions, and next_steps." + ], + examples: exampleCalls.map((call, i) => ({ + description: "Submit a progress review", + output: call + })), + ...defineToolCallFormat("submit_review") +} + +/** + * Full tool definition - exports everything needed in one place + */ +export const submitReviewTool = { + name: "submit_review", + schema, + prompt: promptDefinition, + examples: promptDefinition.examples, + callFormat: promptDefinition.callFormat +} \ No newline at end of file diff --git a/extension/src/agent/v1/tools/definitions/tool-call-template.ts b/extension/src/agent/v1/tools/definitions/tool-call-template.ts new file mode 100644 index 00000000..ee4b87c8 --- /dev/null +++ b/extension/src/agent/v1/tools/definitions/tool-call-template.ts @@ -0,0 +1,63 @@ +/** + * Unified tool calling format template + * This provides a consistent way to define how tools should be called by LLMs + */ + +/** + * XML format tool call template + * All tools should use this same format for consistency + */ +export const xmlToolCallTemplate = { + // Format for Claude 3.5 Sonnet + standard: '\n{parameters}\n', + + // Format for Claude 3 Opus/Haiku + legacy: '<{toolName}>\n{parameters}\n', + + // Format parameter (single) + parameter: ' <{paramName}>{paramValue}', + + // Format for nested parameters + nestedParameter: ' <{paramName}>\n{nestedParameters}\n ', + + // Format nested parameter item + nestedParameterItem: ' <{paramName}>{paramValue}' +} + +/** + * Generates tool call examples in both formats + * @param toolName The name of the tool + * @param examples Array of parameter examples + */ +export function generateToolCallExamples( + toolName: string, + examples: Array> +): string[] { + return examples.map(example => { + const params = Object.entries(example) + .map(([key, value]) => xmlToolCallTemplate.parameter + .replace('{paramName}', key) + .replace('{paramValue}', String(value)) + ) + .join('\n') + + return xmlToolCallTemplate.standard + .replace('{toolName}', toolName) + .replace('{parameters}', params) + }) +} + +/** + * Generates the unified tool call format for tool definitions + */ +export function defineToolCallFormat(toolName: string) { + return { + name: toolName, + callFormat: { + xml: { + standard: xmlToolCallTemplate.standard.replace('{toolName}', toolName), + legacy: xmlToolCallTemplate.legacy.replace('{toolName}', toolName) + } + } + } +} \ No newline at end of file diff --git a/extension/src/agent/v1/tools/definitions/url-screenshot.tool.ts b/extension/src/agent/v1/tools/definitions/url-screenshot.tool.ts new file mode 100644 index 00000000..28b4c741 --- /dev/null +++ b/extension/src/agent/v1/tools/definitions/url-screenshot.tool.ts @@ -0,0 +1,75 @@ +import { z } from "zod" +import { defineToolCallFormat, generateToolCallExamples } from "./tool-call-template" + +/** + * Unified definition for url_screenshot tool + * Combines schema, prompt, and type definitions in one place + * Uses consistent calling format + */ + +/** + * ZOD Schema Definition + */ +export const schema = z.object({ + url: z.string().describe("The URL provided by the user."), +}) + +/** + * Type definitions derived from schema + */ +export type UrlScreenshotInput = z.infer + +export type UrlScreenshotToolParams = { + name: "url_screenshot" + input: UrlScreenshotInput +} + +/** + * Example parameter values for tool calls + */ +const exampleParams = [ + { url: "https://www.example.com" }, + { url: "https://www.companysite.com/about" }, + { url: "https://www.designinspiration.com/portfolio" } +] + +/** + * Generate consistent example tool calls + */ +const exampleCalls = generateToolCallExamples("url_screenshot", exampleParams) + +/** + * Prompt definition for LLM consumption + * Based on prompts/tools/url-screenshot.ts but with unified calling format + */ +export const promptDefinition = { + name: "url_screenshot", + description: "Request to capture a screenshot and console logs of the initial state of a website. This tool navigates to the specified URL, takes a screenshot of the entire page as it appears immediately after loading, and collects any console logs or errors that occur during page load. It does not interact with the page or capture any state changes after the initial load.", + parameters: { + url: { + type: "string", + description: "The URL of the site to inspect. This should be a valid URL including the protocol (e.g. http://localhost:3000/page, file:///path/to/file.html, etc.)", + required: true, + }, + }, + capabilities: [ + "You can use the url_screenshot tool to capture a screenshot and console logs of the initial state of a website (including html files and locally running development servers) when you feel it is necessary in accomplishing the user's task. This tool may be useful at key stages of web development tasks-such as after implementing new features, making substantial changes, when troubleshooting issues, or to verify the result of your work. You can analyze the provided screenshot to ensure correct rendering or identify errors, and review console logs for runtime issues.\n - For example, if asked to add a component to a react website, you might create the necessary files, run the site locally, then use url_screenshot to verify there are no runtime errors on page load." + ], + examples: exampleCalls.map((call, i) => ({ + description: `Screenshot website: ${exampleParams[i].url}`, + output: call + })), + requiresFeatures: ["vision"], + ...defineToolCallFormat("url_screenshot") +} + +/** + * Full tool definition - exports everything needed in one place + */ +export const urlScreenshotTool = { + name: "url_screenshot", + schema, + prompt: promptDefinition, + examples: promptDefinition.examples, + callFormat: promptDefinition.callFormat +} \ No newline at end of file diff --git a/extension/src/agent/v1/tools/definitions/web-search.tool.ts b/extension/src/agent/v1/tools/definitions/web-search.tool.ts new file mode 100644 index 00000000..7b65a6b1 --- /dev/null +++ b/extension/src/agent/v1/tools/definitions/web-search.tool.ts @@ -0,0 +1,128 @@ +import { z } from "zod" +import { defineToolCallFormat, generateToolCallExamples } from "./tool-call-template" + +/** + * Unified definition for web_search tool + * Combines schema, prompt, and type definitions in one place + * Uses consistent calling format + */ + +/** + * ZOD Schema Definition + */ +export const schema = z.object({ + searchQuery: z.string().describe("The question you want to search for on the web."), + baseLink: z + .string() + .optional() + .describe("The base link provided by the user. If it is provided, you can start your search from here."), + browserModel: z + .enum(["smart", "fast"]) + .default("fast") + .optional() + .describe( + "The browser model to use for the search. Use 'smart' for slower but smarter search, use 'fast' for faster but less smart search." + ), + browserMode: z + .enum(["api_docs", "generic"]) + .default("generic") + .optional() + .describe( + "The browser mode to use for the search. Use 'generic' to search the web. Use 'api_docs' when you want to search API docs." + ), +}) + +/** + * Type definitions derived from schema + */ +export type WebSearchInput = z.infer + +export type WebSearchToolParams = { + name: "web_search" + input: WebSearchInput +} + +/** + * Example parameter values for tool calls + */ +const exampleParams = [ + { + searchQuery: "Latest advancements in AI technology", + browserModel: "smart", + browserMode: "generic" + }, + { + searchQuery: "How to optimize React applications?", + baseLink: "https://reactjs.org/docs/optimizing-performance.html", + browserModel: "smart", + browserMode: "generic" + }, + { + searchQuery: "Zustand state management API setter function", + browserMode: "api_docs" + }, + { + searchQuery: "Fixing type error in my code", + browserModel: "fast" + } +] + +/** + * Generate consistent example tool calls + */ +const exampleCalls = generateToolCallExamples("web_search", exampleParams) + +/** + * Prompt definition for LLM consumption + */ +export const promptDefinition = { + name: "web_search", + description: "Lets you ask a question about links and generate a short summary of information regarding a question. You can provide a link to access directly or a search query. At both stages, you are required to provide a general question about this web search.", + parameters: { + searchQuery: { + type: "string", + description: "The question you want to search for on the web.", + required: true, + }, + baseLink: { + type: "string", + description: "Optional base link provided by the user.", + required: false, + }, + browserModel: { + type: "string", + description: "The browser model to use for the search. Use 'smart' for slower but smarter search, use 'fast' for faster but less smart search.", + required: false, + enum: ["smart", "fast"], + default: "fast" + }, + browserMode: { + type: "string", + description: "The browser mode to use for the search. Use 'generic' to search the web. Use 'api_docs' when you want to search API docs.", + required: false, + enum: ["api_docs", "generic"], + default: "generic" + }, + }, + capabilities: [ + "You can use the web_search tool to search the web for information about a specific topic or question.", + "You can optionally provide a base link to start your search from a specific page.", + "You can specify the browser model (smart/fast) and mode (api_docs/generic) for different search scenarios." + ], + examples: exampleCalls.map((call, i) => ({ + description: `Search for ${exampleParams[i].searchQuery}`, + output: call + })), + ...defineToolCallFormat("web_search") +} + +/** + * Full tool definition - exports everything needed in one place + */ +export const webSearchTool = { + name: "web_search", + schema, + prompt: promptDefinition, + examples: promptDefinition.examples, + callFormat: promptDefinition.callFormat +} \ No newline at end of file diff --git a/extension/src/agent/v1/tools/definitions/write-to-file.tool.ts b/extension/src/agent/v1/tools/definitions/write-to-file.tool.ts new file mode 100644 index 00000000..bf93f78d --- /dev/null +++ b/extension/src/agent/v1/tools/definitions/write-to-file.tool.ts @@ -0,0 +1,108 @@ +import { z } from "zod" +import { defineToolCallFormat, generateToolCallExamples } from "./tool-call-template" + +/** + * Unified definition for write_to_file tool + * Combines schema, prompt, and type definitions in one place + * Uses consistent calling format + */ + +/** + * ZOD Schema Definition + */ +export const schema = z.object({ + path: z.string().describe("The path of the file to write to (relative to the current working directory)."), + kodu_content: z + .string() + .describe( + "The full content to write to the file when creating a new file. Always provide the complete content without any truncation." + ) + .optional(), + kodu_diff: z + .string() + .describe( + "The `SEARCH/REPLACE` blocks representing the changes to be made to an existing file. These blocks must be formatted correctly, matching exact existing content for `SEARCH` and precise modifications for `REPLACE`." + ) + .optional(), +}) + +/** + * Type definitions derived from schema + */ +export type WriteToFileInput = z.infer + +export type WriteToFileToolParams = { + name: "write_to_file" + input: WriteToFileInput +} + +export type EditFileBlocksToolParams = { + name: "edit_file_blocks" + input: WriteToFileInput +} + +/** + * Example parameter values for tool calls + */ +const exampleParams = [ + { + path: "/scripts/setup.sh", + kodu_content: "\n\nSEARCH\necho \"Setting up environment\"\n=======\nREPLACE\necho \"Initializing environment\"\n" + }, + { + path: "/hello.py", + content: "def hello():\n \"print a greeting\"\n\n print(\"hello\")" + } +] + +/** + * Generate consistent example tool calls + */ +const exampleCalls = generateToolCallExamples("write_to_file", exampleParams) + +/** + * Prompt definition for LLM consumption + */ +export const promptDefinition = { + name: "write_to_file", + description: "Write content to a file at the specified path. This tool has two modes of operation: 1) Creating a New File: Provide the full intended content using the `content` parameter. The file will be created if it does not exist. 2) Modifying an Existing File: Provide changes using `SEARCH/REPLACE` blocks to precisely describe modifications to existing files. If the file exists, use the `diff` parameter to describe the changes. If the file doesn't exist, use the `content` parameter to create it with the provided content. Always provide the full content or accurate changes using `SEARCH/REPLACE` blocks. Never truncate content or use placeholders.", + parameters: { + path: { + type: "string", + description: "The path of the file to write to (relative to the current working directory).", + required: true, + }, + kodu_content: { + type: "string", + description: "The full content to write to the file when creating a new file. Always provide the complete content without any truncation.", + required: false, + }, + kodu_diff: { + type: "string", + description: "The `SEARCH/REPLACE` blocks representing the changes to be made to an existing file. These blocks must be formatted correctly, matching exact existing content for `SEARCH` and precise modifications for `REPLACE`.", + required: false, + }, + }, + capabilities: [ + "You can use write_to_file tool to create new files with specific content.", + "You can modify existing files by providing precise SEARCH/REPLACE blocks that identify the exact content to change.", + "When creating a new file, always provide the complete content without truncation.", + "When modifying an existing file, format your SEARCH/REPLACE blocks correctly, matching existing content exactly." + ], + examples: exampleCalls.map((call, i) => ({ + description: i === 0 ? `Modify ${exampleParams[i].path}` : `Create ${exampleParams[i].path}`, + output: call + })), + ...defineToolCallFormat("write_to_file") +} + +/** + * Full tool definition - exports everything needed in one place + */ +export const writeToFileTool = { + name: "write_to_file", + schema, + prompt: promptDefinition, + examples: promptDefinition.examples, + callFormat: promptDefinition.callFormat +} \ No newline at end of file diff --git a/extension/src/agent/v1/tools/runners/add-interested-file.tool.ts b/extension/src/agent/v1/tools/runners/add-interested-file.tool.ts new file mode 100644 index 00000000..5a286af2 --- /dev/null +++ b/extension/src/agent/v1/tools/runners/add-interested-file.tool.ts @@ -0,0 +1,175 @@ +import path from "path" +import { BaseAgentTool } from "../base-agent.tool" +import { AddInterestedFileToolParams } from "../definitions" + +export class AddInterestedFileTool extends BaseAgentTool { + async execute() { + const { input, say } = this.params + const { file_path, reason } = input + + if (!file_path) { + await say( + "error", + "Kodu tried to add an interested file without specifying a file path. Retrying..." + ) + return this.toolResponse("error", ` + + + error + add_interested_file + ${new Date().toISOString()} + + + missing_parameter + Missing required parameter 'file_path' + + + add_interested_file + + path/to/file.txt + Why this file is important for the task + + + + + + `) + } + + try { + // Update UI to show operation is in progress + await this.params.updateAsk( + "tool", + { + tool: { + tool: "add_interested_file", + file_path, + reason: reason || "No reason provided", + approvalState: "loading", + ts: this.ts + }, + }, + this.ts + ) + + // Normalize the file path + const normalizedPath = path.isAbsolute(file_path) + ? file_path + : path.resolve(this.options.cwd, file_path) + + // Add file to interested files list (assuming there's a method in koduDev for this) + // This is a placeholder - actual implementation will depend on how interested files are tracked + const isAdded = await this.addToInterestedFiles(normalizedPath, reason) + + if (!isAdded) { + await this.params.updateAsk( + "tool", + { + tool: { + tool: "add_interested_file", + file_path, + reason: reason || "No reason provided", + approvalState: "error", + error: "Failed to add file to interested files list", + ts: this.ts + }, + }, + this.ts + ) + + return this.toolResponse("error", ` + + + error + add_interested_file + ${new Date().toISOString()} + + + operation_failed + Failed to add file to interested files list + ${file_path} + + + `) + } + + // Update the tool status to completed + await this.params.updateAsk( + "tool", + { + tool: { + tool: "add_interested_file", + file_path, + reason: reason || "No reason provided", + approvalState: "approved", + ts: this.ts + }, + }, + this.ts + ) + + return this.toolResponse( + "success", + ` + + success + add_interested_file + ${new Date().toISOString()} + + + ${file_path} + ${reason || "No reason provided"} + + ` + ) + } catch (error) { + await say( + "error", + `Error adding interested file: ${error instanceof Error ? error.message : String(error)}` + ) + + await this.params.updateAsk( + "tool", + { + tool: { + tool: "add_interested_file", + file_path, + reason: reason || "No reason provided", + approvalState: "error", + error: error instanceof Error ? error.message : String(error), + ts: this.ts + }, + }, + this.ts + ) + + return this.toolResponse("error", ` + + + error + add_interested_file + ${new Date().toISOString()} + + + operation_error + ${error instanceof Error ? error.message : String(error)} + ${file_path} + + + `) + } + } + + // Placeholder method - actual implementation would involve the state manager + private async addToInterestedFiles(filePath: string, reason?: string): Promise { + try { + // In a real implementation, this would use the state manager to track interested files + // For now, we'll simulate success + console.log(`Adding ${filePath} to interested files with reason: ${reason || 'No reason provided'}`) + return true + } catch (error) { + console.error("Error adding file to interested files:", error) + return false + } + } +} \ No newline at end of file diff --git a/extension/src/agent/v1/tools/runners/explore-repo-folder.tool.ts b/extension/src/agent/v1/tools/runners/explore-repo-folder.tool.ts index fb134a12..4177d70e 100644 --- a/extension/src/agent/v1/tools/runners/explore-repo-folder.tool.ts +++ b/extension/src/agent/v1/tools/runners/explore-repo-folder.tool.ts @@ -2,7 +2,7 @@ import * as path from "path" import { serializeError } from "serialize-error" import { parseSourceCodeForDefinitionsTopLevel } from "../../../../parse-source-code" import { BaseAgentTool } from "../base-agent.tool" -import { ExploreRepoFolderToolParams } from "../schema/explore-repo-folder.schema" +import { ExploreRepoFolderToolParams } from "../definitions" import { getReadablePath } from "../../utils" import { exploreRepoFolderPrompt } from "../../prompts/tools/explore-repo-folder" diff --git a/extension/src/agent/v1/tools/runners/file-change-plan.tool.ts b/extension/src/agent/v1/tools/runners/file-change-plan.tool.ts new file mode 100644 index 00000000..09c18559 --- /dev/null +++ b/extension/src/agent/v1/tools/runners/file-change-plan.tool.ts @@ -0,0 +1,224 @@ +import { BaseAgentTool } from "../base-agent.tool" +import { FileChangePlanToolParams } from "../definitions" + +export class FileChangePlanTool extends BaseAgentTool { + async execute() { + const { input, ask, say } = this.params + const { plan, files } = input + + if (!plan) { + await say( + "error", + "Kodu tried to submit a file change plan without specifying the plan. Retrying..." + ) + return this.toolResponse("error", ` + + + error + file_change_plan + ${new Date().toISOString()} + + + missing_parameter + Missing required parameter 'plan' + + + file_change_plan + + Detailed description of the changes to be made + + path/to/file1.js + path/to/file2.js + + + + + + + `) + } + + if (!files || !Array.isArray(files) || files.length === 0) { + await say( + "error", + "Kodu tried to submit a file change plan without specifying the affected files. Retrying..." + ) + return this.toolResponse("error", ` + + + error + file_change_plan + ${new Date().toISOString()} + + + missing_parameter + Missing or empty required parameter 'files' + + + file_change_plan + + Detailed description of the changes to be made + + path/to/file1.js + path/to/file2.js + + + + + + + `) + } + + try { + // Update UI to show plan submission is in progress + await this.params.updateAsk( + "tool", + { + tool: { + tool: "file_change_plan", + plan, + files, + approvalState: "pending", + ts: this.ts + }, + }, + this.ts + ) + + // Get user approval for the plan + const { text } = await ask( + "tool", + { + tool: { + tool: "file_change_plan", + plan, + files, + approvalState: "pending", + ts: this.ts + }, + }, + this.ts + ) + + // Check if user approved + if (text && text.toLowerCase().includes("reject")) { + await this.params.updateAsk( + "tool", + { + tool: { + tool: "file_change_plan", + plan, + files, + approvalState: "rejected", + ts: this.ts + }, + }, + this.ts + ) + + await say( + "user_feedback", + "The file change plan was rejected. Please refine your plan or propose an alternative approach." + ) + + return this.toolResponse("error", ` + + + error + file_change_plan + ${new Date().toISOString()} + + + user_rejection + User rejected the file change plan + + + `) + } + + // Record the plan (assuming there's a method in koduDev for this) + // This is a placeholder - actual implementation will depend on how plans are tracked + await this.recordChangePlan(plan, files) + + // Update the tool status to approved + await this.params.updateAsk( + "tool", + { + tool: { + tool: "file_change_plan", + plan, + files, + approvalState: "approved", + ts: this.ts + }, + }, + this.ts + ) + + await say( + "user_feedback", + "The file change plan has been approved. You can proceed with implementing the changes." + ) + + return this.toolResponse( + "success", + ` + + success + file_change_plan + ${new Date().toISOString()} + + + ${plan} + + ${files.map(file => `${file}`).join('\n ')} + + approved + + ` + ) + } catch (error) { + await say( + "error", + `Error submitting file change plan: ${error instanceof Error ? error.message : String(error)}` + ) + + await this.params.updateAsk( + "tool", + { + tool: { + tool: "file_change_plan", + plan, + files, + approvalState: "error", + error: error instanceof Error ? error.message : String(error), + ts: this.ts + }, + }, + this.ts + ) + + return this.toolResponse("error", ` + + + error + file_change_plan + ${new Date().toISOString()} + + + operation_error + ${error instanceof Error ? error.message : String(error)} + + + `) + } + } + + // Placeholder method - actual implementation would involve the state manager + private async recordChangePlan(plan: string, files: string[]): Promise { + // In a real implementation, this would use the state manager to track change plans + console.log("Recording file change plan:", plan) + console.log("Affected files:", files.join(", ")) + } +} \ No newline at end of file diff --git a/extension/src/agent/v1/tools/runners/list-files.tool.ts b/extension/src/agent/v1/tools/runners/list-files.tool.ts index f05b246d..e52b80a0 100644 --- a/extension/src/agent/v1/tools/runners/list-files.tool.ts +++ b/extension/src/agent/v1/tools/runners/list-files.tool.ts @@ -3,7 +3,7 @@ import { serializeError } from "serialize-error" import { LIST_FILES_LIMIT, listFiles } from "../../../../parse-source-code" import { formatFilesList, getReadablePath } from "../../utils" import { BaseAgentTool } from "../base-agent.tool" -import { ListFilesToolParams } from "../schema/list_files" +import { ListFilesToolParams } from "../definitions" import { listFilesPrompt } from "../../prompts/tools/list-files" export class ListFilesTool extends BaseAgentTool { diff --git a/extension/src/agent/v1/tools/runners/read-file/read-file.tool.ts b/extension/src/agent/v1/tools/runners/read-file/read-file.tool.ts index 66906196..1ec9df34 100644 --- a/extension/src/agent/v1/tools/runners/read-file/read-file.tool.ts +++ b/extension/src/agent/v1/tools/runners/read-file/read-file.tool.ts @@ -6,7 +6,7 @@ import { z } from "zod" import { getReadablePath } from "../../../utils" import { BaseAgentTool } from "../../base-agent.tool" import { extractTextFromFile, formatFileToLines } from "./utils" -import { ReadFileToolParams } from "../../schema/read_file" +import { ReadFileToolParams } from "../../definitions" import { readFilePrompt } from "../../../prompts/tools/read-file" export class ReadFileTool extends BaseAgentTool { diff --git a/extension/src/agent/v1/tools/runners/read-file/read-file.tool.ts.new b/extension/src/agent/v1/tools/runners/read-file/read-file.tool.ts.new new file mode 100644 index 00000000..932f65d2 --- /dev/null +++ b/extension/src/agent/v1/tools/runners/read-file/read-file.tool.ts.new @@ -0,0 +1,119 @@ +import * as path from "path" +import { serializeError } from "serialize-error" +import dedent from "dedent" + +import { getReadablePath } from "../../../utils" +import { BaseAgentTool } from "../../base-agent.tool" +import { extractTextFromFile, formatFileToLines } from "./utils" +import { ReadFileToolParams, promptDefinition } from "../../definitions/read-file.tool" + +/** + * Implementation of the read_file tool + * Using the unified tool definition from definitions/read-file.tool.ts + */ +export class ReadFileTool extends BaseAgentTool { + async execute() { + const { input, ask, say } = this.params + + const { path: relPath } = input + + try { + const absolutePath = path.resolve(this.cwd, relPath) + const content = await extractTextFromFile(absolutePath) + + const { response, text, images } = await ask( + "tool", + { + tool: { + tool: "read_file", + path: getReadablePath(relPath, this.cwd), + approvalState: "pending", + content, + ts: this.ts, + }, + }, + this.ts + ) + + if (response !== "yesButtonTapped") { + await this.params.updateAsk( + "tool", + { + tool: { + tool: "read_file", + path: getReadablePath(relPath, this.cwd), + approvalState: "rejected", + content, + userFeedback: text, + ts: this.ts, + }, + }, + this.ts + ) + + if (response === "messageResponse") { + await this.params.say("user_feedback", text ?? "The user denied this operation.", images) + return this.toolResponse("feedback", text, images) + } + + return this.toolResponse("error", "Read operation cancelled by user.") + } + + await this.params.updateAsk( + "tool", + { + tool: { + tool: "read_file", + path: getReadablePath(relPath, this.cwd), + approvalState: "approved", + content, + ts: this.ts, + }, + }, + this.ts + ) + + if (content.trim().length === 0) { + return this.toolResponse( + "success", + dedent`successfile_read${new Date().toISOString()}${relPath}empty` + ) + } + + // Format content into lines + const lines = formatFileToLines(content) + + const now = new Date().toISOString() + return this.toolResponse( + "success", + `successfile_read${now}Here is the latest file content as of (${now}):\n${content}` + ) + } catch (error) { + await this.params.updateAsk( + "tool", + { + tool: { + tool: "read_file", + path: getReadablePath(relPath, this.cwd), + content: "Cannot read content", + approvalState: "error", + ts: this.ts, + }, + }, + this.ts + ) + const errorString = dedent`errorfile_read${new Date().toISOString()}Error reading file: ${JSON.stringify( + serializeError(error) + )}${relPath}${ + promptDefinition.examples[0].output + }Please provide a valid file path. File reading operations require a valid path parameter.` + + await say( + "error", + `Error reading file:\n${(error as Error).message ?? JSON.stringify(serializeError(error), null, 2)}` + ) + + return this.toolResponse("error", errorString) + } + } +} \ No newline at end of file diff --git a/extension/src/agent/v1/tools/runners/reject-file-changes.tool.ts b/extension/src/agent/v1/tools/runners/reject-file-changes.tool.ts new file mode 100644 index 00000000..dbc0b522 --- /dev/null +++ b/extension/src/agent/v1/tools/runners/reject-file-changes.tool.ts @@ -0,0 +1,123 @@ +import { BaseAgentTool } from "../base-agent.tool" +import { RejectFileChangesToolParams } from "../definitions" + +export class RejectFileChangesTool extends BaseAgentTool { + async execute() { + const { input, say } = this.params + const { reason } = input + + if (!reason) { + await say( + "error", + "Kodu tried to reject file changes without specifying a reason. Retrying..." + ) + return this.toolResponse("error", ` + + + error + reject_file_changes + ${new Date().toISOString()} + + + missing_parameter + Missing required parameter 'reason' + + + reject_file_changes + + Detailed explanation of why the changes are being rejected + + + + + + `) + } + + try { + // Update UI to show rejection is in progress + await this.params.updateAsk( + "tool", + { + tool: { + tool: "reject_file_changes", + reason, + approvalState: "pending", + ts: this.ts + }, + }, + this.ts + ) + + // Process the rejection + console.log("Processing file changes rejection with reason:", reason) + + // Update the tool status to completed + await this.params.updateAsk( + "tool", + { + tool: { + tool: "reject_file_changes", + reason, + approvalState: "approved", + ts: this.ts + }, + }, + this.ts + ) + + await say( + "user_feedback", + `The file changes have been rejected. Reason: ${reason}` + ) + + return this.toolResponse( + "success", + ` + + success + reject_file_changes + ${new Date().toISOString()} + +
+ ${reason} + completed +
+
` + ) + } catch (error) { + await say( + "error", + `Error rejecting file changes: ${error instanceof Error ? error.message : String(error)}` + ) + + await this.params.updateAsk( + "tool", + { + tool: { + tool: "reject_file_changes", + reason, + approvalState: "error", + error: error instanceof Error ? error.message : String(error), + ts: this.ts + }, + }, + this.ts + ) + + return this.toolResponse("error", ` + + + error + reject_file_changes + ${new Date().toISOString()} + + + operation_error + ${error instanceof Error ? error.message : String(error)} + + + `) + } + } +} \ No newline at end of file diff --git a/extension/src/agent/v1/tools/runners/search-files.tool.ts b/extension/src/agent/v1/tools/runners/search-files.tool.ts index 1710390f..c0c5c4d5 100644 --- a/extension/src/agent/v1/tools/runners/search-files.tool.ts +++ b/extension/src/agent/v1/tools/runners/search-files.tool.ts @@ -3,7 +3,7 @@ import { serializeError } from "serialize-error" import { getReadablePath } from "../../utils" import { regexSearchFiles } from "../../../../utils/ripgrep" import { BaseAgentTool } from "../base-agent.tool" -import { SearchFilesToolParams } from "../schema/search_files" +import { SearchFilesToolParams } from "../definitions" import { searchFilesPrompt } from "../../prompts/tools/search-files" export class SearchFilesTool extends BaseAgentTool { diff --git a/extension/src/agent/v1/tools/runners/submit-review.tool.ts b/extension/src/agent/v1/tools/runners/submit-review.tool.ts index be906a3f..14e43558 100644 --- a/extension/src/agent/v1/tools/runners/submit-review.tool.ts +++ b/extension/src/agent/v1/tools/runners/submit-review.tool.ts @@ -1,5 +1,5 @@ import { BaseAgentTool } from "../base-agent.tool" -import { SubmitReviewToolParams } from "../schema/submit_review" +import { SubmitReviewToolParams } from "../definitions" export class SubmitReviewTool extends BaseAgentTool { async execute() { diff --git a/extension/src/agent/v1/tools/runners/web-search.tool.ts b/extension/src/agent/v1/tools/runners/web-search.tool.ts new file mode 100644 index 00000000..cb56d2a2 --- /dev/null +++ b/extension/src/agent/v1/tools/runners/web-search.tool.ts @@ -0,0 +1,137 @@ +import { BaseAgentTool } from "../base-agent.tool" +import { WebSearchToolParams } from "../definitions" + +export class WebSearchTool extends BaseAgentTool { + async execute() { + const { input, say } = this.params + const { query } = input + + if (!query || query.trim() === "") { + await say( + "error", + "Kodu tried to use web_search without a valid query. Retrying..." + ) + return this.toolResponse("error", ` + + + error + web_search + ${new Date().toISOString()} + + + missing_parameter + Missing or empty required parameter 'query' + + + web_search + + your search query + + + + + + `) + } + + try { + // Update UI to show search is in progress + await this.params.updateAsk( + "tool", + { + tool: { + tool: "web_search", + query, + approvalState: "loading", + ts: this.ts + }, + }, + this.ts + ) + + // Mock search results for now - in a real implementation this would call a search API + const searchResults = await this.performWebSearch(query) + + // Update the tool status to completed + await this.params.updateAsk( + "tool", + { + tool: { + tool: "web_search", + query, + approvalState: "approved", + ts: this.ts + }, + }, + this.ts + ) + + return this.toolResponse( + "success", + ` + + success + web_search + ${new Date().toISOString()} + + ${query} + + ${searchResults} + + ` + ) + } catch (error) { + await say( + "error", + `Error performing web search: ${error instanceof Error ? error.message : String(error)}` + ) + return this.toolResponse("error", ` + + + error + web_search + ${new Date().toISOString()} + + + search_error + ${error instanceof Error ? error.message : String(error)} + + + `) + } + } + + private async performWebSearch(query: string): Promise { + // In a real implementation, this would make an API call to a search service + // For now, we'll return a simulated response + + // Simulate network delay + await new Promise(resolve => setTimeout(resolve, 1000)) + + return ` + Search results for: ${query} + + This is a placeholder for search results. In a production environment, + this would contain actual search results from a search API. + + web_search_simulation + + + Result 1 for ${query} + https://example.com/result1 + This is a snippet from the first search result... + + + Result 2 for ${query} + https://example.com/result2 + This is a snippet from the second search result... + + + Result 3 for ${query} + https://example.com/result3 + This is a snippet from the third search result... + + + ` + } +} \ No newline at end of file diff --git a/extension/src/agent/v1/tools/runners/write-to-file.tool.ts b/extension/src/agent/v1/tools/runners/write-to-file.tool.ts new file mode 100644 index 00000000..dbbcc1d8 --- /dev/null +++ b/extension/src/agent/v1/tools/runners/write-to-file.tool.ts @@ -0,0 +1,211 @@ +import fs from "fs" +import path from "path" +import { BaseAgentTool } from "../base-agent.tool" +import { WriteToFileToolParams } from "../definitions" +import { formatString } from "../format-content" + +export class WriteToFileTool extends BaseAgentTool { + async execute() { + const { input, ask, say } = this.params + const { path: filePath, content } = input + + if (!filePath) { + await say( + "error", + "Kodu tried to use write_to_file without specifying a file path. Retrying..." + ) + return this.toolResponse("error", ` + + + error + write_to_file + ${new Date().toISOString()} + + + missing_parameter + Missing required parameter 'path' + + + write_to_file + + path/to/file.txt + File content here + + + + + + `) + } + + if (content === undefined) { + await say( + "error", + "Kodu tried to use write_to_file without specifying content. Retrying..." + ) + return this.toolResponse("error", ` + + + error + write_to_file + ${new Date().toISOString()} + + + missing_parameter + Missing required parameter 'content' + + + write_to_file + + path/to/file.txt + File content here + + + + + + `) + } + + try { + // Update UI to show write operation is in progress + await this.params.updateAsk( + "tool", + { + tool: { + tool: "write_to_file", + path: filePath, + content: formatString(content), + approvalState: "pending", + ts: this.ts + }, + }, + this.ts + ) + + // Get user approval for the write operation + const { text } = await ask( + "tool", + { + tool: { + tool: "write_to_file", + path: filePath, + content: formatString(content), + approvalState: "pending", + ts: this.ts + }, + }, + this.ts + ) + + // Check if user approved + if (text && text.toLowerCase().includes("reject")) { + await this.params.updateAsk( + "tool", + { + tool: { + tool: "write_to_file", + path: filePath, + content: formatString(content), + approvalState: "rejected", + ts: this.ts + }, + }, + this.ts + ) + + return this.toolResponse("error", ` + + + error + write_to_file + ${new Date().toISOString()} + + + user_rejection + User rejected the file write operation + + + `) + } + + // Perform the write operation + const absolutePath = path.isAbsolute(filePath) + ? filePath + : path.resolve(this.options.cwd, filePath) + + // Ensure directory exists + const dirPath = path.dirname(absolutePath) + await fs.promises.mkdir(dirPath, { recursive: true }) + + // Write the file + await fs.promises.writeFile(absolutePath, content) + + // Update the tool status to completed + await this.params.updateAsk( + "tool", + { + tool: { + tool: "write_to_file", + path: filePath, + content: formatString(content), + approvalState: "approved", + ts: this.ts + }, + }, + this.ts + ) + + return this.toolResponse( + "success", + ` + + success + write_to_file + ${new Date().toISOString()} + + + ${filePath} + ${absolutePath} + ${content.length} + + ` + ) + } catch (error) { + await say( + "error", + `Error writing to file: ${error instanceof Error ? error.message : String(error)}` + ) + + await this.params.updateAsk( + "tool", + { + tool: { + tool: "write_to_file", + path: filePath, + content: formatString(content), + approvalState: "error", + error: error instanceof Error ? error.message : String(error), + ts: this.ts + }, + }, + this.ts + ) + + return this.toolResponse("error", ` + + + error + write_to_file + ${new Date().toISOString()} + + + file_system_error + ${error instanceof Error ? error.message : String(error)} + ${filePath} + + + `) + } + } +} \ No newline at end of file diff --git a/extension/src/agent/v1/tools/tool-executor.ts b/extension/src/agent/v1/tools/tool-executor.ts index de7a4a2e..59bc6455 100644 --- a/extension/src/agent/v1/tools/tool-executor.ts +++ b/extension/src/agent/v1/tools/tool-executor.ts @@ -21,7 +21,7 @@ import { import { SearchSymbolsTool } from "./runners/search-symbols.tool" import { BaseAgentTool, FullToolParams } from "./base-agent.tool" import ToolParser from "./tool-parser/tool-parser" -import { tools, writeToFileTool } from "./schema" +import { toolDefinitions } from "./definitions" import pWaitFor from "p-wait-for" import PQueue from "p-queue" import { DevServerTool } from "./runners/dev-server.tool" @@ -29,6 +29,13 @@ import { SpawnAgentTool } from "./runners/agents/spawn-agent.tool" import { ExitAgentTool } from "./runners/agents/exit-agent.tool" import { SubmitReviewTool } from "./runners/submit-review.tool" +// Import additional tool implementations +import { WebSearchTool } from "./runners/web-search.tool" +import { WriteToFileTool } from "./runners/write-to-file.tool" +import { AddInterestedFileTool } from "./runners/add-interested-file.tool" +import { FileChangePlanTool } from "./runners/file-change-plan.tool" +import { RejectFileChangesTool } from "./runners/reject-file-changes.tool" + /** * Represents the context and state of a tool during its lifecycle * @interface ToolContext @@ -77,7 +84,7 @@ export class ToolExecutor { this.queue = new PQueue({ concurrency: 1 }) this.toolParser = new ToolParser( - tools.map((tool) => tool.schema), + toolDefinitions.map((tool) => ({ name: tool.name, schema: tool.schema })), { onToolUpdate: this.handleToolUpdate.bind(this), onToolEnd: this.handleToolEnd.bind(this), @@ -130,6 +137,12 @@ export class ToolExecutor { file_editor: FileEditorTool, spawn_agent: SpawnAgentTool, exit_agent: ExitAgentTool, + web_search: WebSearchTool, + write_to_file: WriteToFileTool, + add_interested_file: AddInterestedFileTool, + file_change_plan: FileChangePlanTool, + submit_review: SubmitReviewTool, + reject_file_changes: RejectFileChangesTool, } as const const ToolClass = toolMap[params.name as keyof typeof toolMap] diff --git a/extension/src/agent/v1/tools/tool-parser/tool-parser.ts b/extension/src/agent/v1/tools/tool-parser/tool-parser.ts index b91d070f..972a79a3 100644 --- a/extension/src/agent/v1/tools/tool-parser/tool-parser.ts +++ b/extension/src/agent/v1/tools/tool-parser/tool-parser.ts @@ -1,6 +1,5 @@ import { z } from "zod" import { nanoid } from "nanoid" -import { tools } from "../schema" type ToolSchema = { name: string @@ -230,15 +229,21 @@ export class ToolParser { } private checkForToolStart(tag: string): void { - const tagName = tag.slice(1, -1).split(" ")[0] - if (this.toolSchemas.some((schema) => schema.name === tagName)) { + // Check for both standard and legacy XML formats + const standardMatch = tag.match(//) + const legacyMatch = tag.match(/<([^>\s]+)/) + + // Extract the tool name based on the format + const toolName = standardMatch ? standardMatch[1] : legacyMatch ? legacyMatch[1] : null + + if (toolName && this.toolSchemas.some((schema) => schema.name === toolName)) { this.isInTool = true const id = this.isMock ? "mocked-nanoid" : nanoid() const ts = Date.now() this.currentContext = { id, ts, - toolName: tagName, + toolName: toolName, params: {}, currentParam: "", content: "", @@ -248,7 +253,7 @@ export class ToolParser { lastUpdateLength: {}, } this.lastFlushTime = Date.now() // reset flush timer on new tool - this.onToolUpdate?.(id, tagName, {}, ts) + this.onToolUpdate?.(id, toolName, {}, ts) } else { this.nonToolBuffer += tag } @@ -312,8 +317,27 @@ export class ToolParser { } const isClosingTag = tag.startsWith("/) + const legacyMatch = tag.match(/<\/([^>]+)>/) + + // For standard format , use the current context's tool name + // For legacy format , extract the tool name + tagName = standardMatch ? this.currentContext.toolName : legacyMatch ? legacyMatch[1] : "" + } else { + // For opening tags + const standardMatch = tag.match(//) + const legacyMatch = tag.match(/<([^>\s]+)/) + const tagContent = tag.slice(1, -1) + + // Extract the tag name, supporting both formats + tagName = standardMatch ? standardMatch[1] : legacyMatch ? legacyMatch[1] : tagContent.split(" ")[0] + } if (isClosingTag) { this.handleClosingTag(tagName) diff --git a/extension/src/agent/v1/tools/utils/generate-prompts.ts b/extension/src/agent/v1/tools/utils/generate-prompts.ts new file mode 100644 index 00000000..dff62e3b --- /dev/null +++ b/extension/src/agent/v1/tools/utils/generate-prompts.ts @@ -0,0 +1,29 @@ +/** + * Utility to generate tool prompts from unified tool definitions + * This allows prompts to be generated directly from the source of truth + */ + +import { toolDefinitions } from '../definitions' +import { ToolPromptSchema } from '../../prompts/utils/utils' + +/** + * Generate prompt definitions for all tools + * These can be imported in the prompts directory + */ +export function generateToolPrompts(): Record { + const prompts: Record = {} + + for (const tool of toolDefinitions) { + prompts[tool.name] = tool.prompt as ToolPromptSchema + } + + return prompts +} + +/** + * Get a specific tool prompt definition + */ +export function getToolPrompt(name: string): ToolPromptSchema | undefined { + const tool = toolDefinitions.find(t => t.name === name) + return tool?.prompt as ToolPromptSchema +} \ No newline at end of file diff --git a/extension/src/agent/v1/tools/utils/system-prompt-generator.ts b/extension/src/agent/v1/tools/utils/system-prompt-generator.ts new file mode 100644 index 00000000..fd471777 --- /dev/null +++ b/extension/src/agent/v1/tools/utils/system-prompt-generator.ts @@ -0,0 +1,114 @@ +import { + toolDefinitions, + getAllToolExamples, + getToolDefinition, + getToolCallFormat +} from '../definitions' + +/** + * Generates tool documentation for the system prompt + * This creates a formatted string that describes all available tools + * with their parameters, examples, and capabilities + */ +export function generateToolDocumentation(): string { + return toolDefinitions + .map(tool => { + const { name, prompt } = tool + const format = getToolCallFormat(name) + + // Basic tool description + let doc = `## ${name}\n\n${prompt.description}\n\n` + + // Add parameters section + doc += `**Parameters:**\n` + Object.entries(prompt.parameters).forEach(([paramName, param]) => { + const required = param.required ? '(required)' : '(optional)' + doc += `- \`${paramName}\`: ${param.description} ${required}\n` + + // Add enum values if available + if (param.enum) { + doc += ` - Allowed values: ${param.enum.map(v => `\`${v}\``).join(', ')}\n` + } + + // Add default value if available + if (param.default !== undefined) { + doc += ` - Default: \`${param.default}\`\n` + } + }) + + // Add capabilities section + if (prompt.capabilities && prompt.capabilities.length > 0) { + doc += `\n**Capabilities:**\n` + prompt.capabilities.forEach(capability => { + doc += `- ${capability}\n` + }) + } + + // Add XML format examples + doc += `\n**Format:**\n\`\`\`xml\n${format?.xml.standard.replace('{parameters}', ' value')}\n\`\`\`\n\n` + + // Add example usage + if (prompt.examples && prompt.examples.length > 0) { + doc += `**Example:**\n\`\`\`xml\n${prompt.examples[0].output}\n\`\`\`\n\n` + } + + return doc + }) + .join('\n---\n\n') +} + +/** + * Generates a section of example tool calls for the system prompt + */ +export function generateToolExamples(): string { + const examples = getAllToolExamples() + + // Only include a subset of examples to keep the prompt size reasonable + const selectedExamples = examples + .filter((_, index) => index % 2 === 0) // Only include every other example + .slice(0, 10) // Maximum 10 examples + + return selectedExamples + .map(example => { + return `### ${example.description}\n\`\`\`xml\n${example.format}\n\`\`\`\n\n` + }) + .join('') +} + +/** + * Generates the complete tools section for the system prompt + * This includes an overview, general format, and all tool documentation + */ +export function generateToolsSystemPrompt(): string { + return ` +# Available Tools + +You have access to the following tools to help you complete tasks. Use these tools to interact with the environment, read files, execute commands, and more. + +## General Tool Format + +Tools are called using a standardized XML format: + +\`\`\`xml + + value1 + value2 + +\`\`\` + +## Tool Usage Guidelines + +1. **Use the appropriate tool** for each task +2. **Provide all required parameters** in the correct format +3. **Wait for tool execution** to complete before proceeding +4. **Handle errors gracefully** if a tool fails + +## Tool Examples + +${generateToolExamples()} + +## Tool Reference + +${generateToolDocumentation()} +` +} \ No newline at end of file diff --git a/extension/src/integrations/editor/code-diff-formatter.ts b/extension/src/integrations/editor/code-diff-formatter.ts index 31154a03..f4077ebc 100644 --- a/extension/src/integrations/editor/code-diff-formatter.ts +++ b/extension/src/integrations/editor/code-diff-formatter.ts @@ -301,7 +301,7 @@ export class CodeDiffFormatter { private mergeOverlappingRegions( regions: Array<{ start: number; end: number }> ): Array<{ start: number; end: number }> { - if (regions.length <= 1) return regions + if (regions.length <= 1) {return regions} // Sort regions by start position regions.sort((a, b) => a.start - b.start) diff --git a/extension/src/providers/openrouter-provider.ts b/extension/src/providers/openrouter-provider.ts new file mode 100644 index 00000000..1a0b681b --- /dev/null +++ b/extension/src/providers/openrouter-provider.ts @@ -0,0 +1,49 @@ +// ...istniejący kod... + +// openrouter-provider.ts + +/** + * Klasa OpenRouterProvider obsługuje komunikację z OpenRouterem. + * @class OpenRouterProvider + */ +class OpenRouterProvider { + private apiKey: string; + + /** + * Inicjalizuje dostawcę z kluczem API. + * @param {string} apiKey - Klucz API OpenRoutera. + */ + constructor(apiKey: string) { + this.apiKey = apiKey; + } + + /** + * Wyślij zapytanie do OpenRoutera. + * @param {string} prompt - Prompt do wysłania. + * @returns {Promise} - Odpowiedź z OpenRoutera. + */ + async sendRequest(prompt: string): Promise { + const response = await fetch('https://api.openrouter.ai/v1/chat/completions', { + method: 'POST', + headers: { + 'Authorization': `Bearer ${this.apiKey}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + model: 'openrouter/auto', + messages: [{ role: 'user', content: prompt }] + }) + }); + + if (!response.ok) { + throw new Error(`Błąd podczas komunikacji z OpenRouterem: ${response.statusText}`); + } + + const data = await response.json(); + return data.choices[0].message.content; + } +} + +export default OpenRouterProvider; + +// ...istniejący kod...