Adding a New Agent Tool
Follow these steps to add a new tool that the agent can use during autonomous operations.
Step 1: Create the Tool File
Create a new file in src/agent/tools/ (e.g. src/agent/tools/myTool.ts ):
import { Tool } from '../../types/agent';
export const myToolTool: Tool = { name: 'my_tool', description: 'What this tool does - be specific so the LLM knows when to use it', schema: { type: 'object', properties: { param1: { type: 'string', description: 'Param description' } }, required: ['param1'] }, execute: async (params, context) => { // context.workspace is available for file operations // Implementation here return 'Result string shown to LLM'; } };
Argument Flexibility
For tools that accept file paths, accept multiple argument names for robustness (LLMs may use different names). Use the shared resolveWorkspacePath utility from pathUtils.ts :
import { resolveWorkspacePath } from './pathUtils';
const filePath = params.path || params.file || params.filePath;
The existing tools read_file , write_file , and get_diagnostics all follow this pattern.
Step 1b: Register in the Barrel Export
Add the new tool to src/agent/tools/index.ts :
import { myToolTool } from './myTool';
export const builtInTools: Tool[] = [ // ... existing tools ... myToolTool, ];
export { myToolTool };
The ToolRegistry.registerBuiltInTools() iterates over builtInTools and registers them all automatically.
Step 2: Add UI Representation
In src/views/toolUIFormatter.ts , add a case to getToolActionInfo() so the UI shows an appropriate icon and description when the tool runs:
case 'my_tool':
return {
icon: '🔧',
text: Running my tool on ${args.param1},
detail: 'Additional detail shown in collapsed view'
};
Step 3: Understand Execution Routing
Tool execution is handled by the decomposed agent executor in src/services/agent/ . The orchestrator (agentChatExecutor.ts ) delegates tool execution to agentToolRunner.ts , which calls ToolRegistry.execute() for standard tools.
Where to add special execution logic:
-
Standard tools (read/write/search): Create file in src/agent/tools/ and add to index.ts — agentToolRunner.ts calls them automatically via ToolRegistry.execute()
-
Terminal commands: Handled by agentTerminalHandler.ts (approval + execution)
-
File edits: Handled by agentFileEditHandler.ts (sensitivity check + approval)
-
New execution category: Create a new sub-handler in src/services/agent/ — do NOT add logic to agentChatExecutor.ts
See agent-tools.instructions.md → "Agent Executor Architecture" for the full decomposition rules.
Step 3: Add to Settings UI (if toggleable)
If the tool should be individually enable/disable-able, add it to the Tools section in the settings page (src/webview/components/settings/components/ToolsSection.vue ).
Step 4: Write Tests
Add tests in tests/extension/suite/agent/toolRegistry.test.ts :
test('my_tool: basic functionality', async () => { const result = await toolRegistry.execute('my_tool', { param1: 'test-value' }, context); assert.ok(result.includes('expected output')); });
// Test argument name flexibility if applicable test('my_tool: accepts alternative param names', async () => { // ... });
Step 5: Update Instructions
If the tool introduces new conventions or critical rules, update the relevant .github/instructions/*.instructions.md file (likely agent-tools.instructions.md ).
Checklist
-
Tool file created in src/agent/tools/ with clear description and schema
-
Tool added to builtInTools array in src/agent/tools/index.ts
-
UI representation in toolUIFormatter.ts
-
Tests covering happy path and argument flexibility
-
npm run lint:all passes (ESLint + docs + naming)
-
Settings toggle (if applicable)
-
Instructions updated (if new conventions introduced)