Skip to content

Commit f634233

Browse files
committed
vscode extension tool interface
1 parent 9b73965 commit f634233

37 files changed

+1191
-17
lines changed

ToolExtensions.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
## Extension Tool API
2+
3+
Roo now provides an API for VSCode extensions to register custom tools that can be used by Roo. This allows other extensions to extend Roo's capabilities without modifying the Roo codebase.
4+
5+
### Using the Extension Tool API
6+
7+
To use the Extension Tool API, your extension needs to:
8+
9+
1. Add `RooVeterinaryInc.roo-cline` as an extension dependency in your `package.json`.
10+
2. Get the Roo extension API and access the `extensionTools` property.
11+
3. Register your tools using the `registerTool` method.
12+
13+
Here's a simple example:
14+
15+
```typescript
16+
// Access the Roo extension
17+
const rooExtension = vscode.extensions.getExtension<RooAPI>("RooVeterinaryInc.roo-cline")
18+
19+
if (rooExtension && rooExtension.exports.extensionTools) {
20+
// Register a tool
21+
rooExtension.exports.extensionTools.registerTool(context.extension.id, {
22+
name: "my_tool",
23+
description: "Description of what the tool does",
24+
inputSchema: {
25+
// Optional JSON schema for tool arguments
26+
type: "object",
27+
properties: {
28+
myArg: {
29+
type: "string",
30+
description: "Description of the argument",
31+
},
32+
},
33+
},
34+
execute: async (args) => {
35+
// Implement your tool functionality
36+
return {
37+
content: [
38+
{
39+
type: "text",
40+
text: "Result of the tool execution",
41+
},
42+
],
43+
}
44+
},
45+
})
46+
}
47+
```
48+
49+
### Tool Response Format
50+
51+
Tools return responses in the same format as MCP tools:
52+
53+
```typescript
54+
{
55+
content: [
56+
{
57+
type: 'text',
58+
text: 'Text content'
59+
}
60+
// Can also include resources
61+
],
62+
isError?: boolean // Optional flag to indicate if the tool execution failed
63+
}
64+
```
65+
66+
### Example Extension
67+
68+
See the [Roo-NB](https://github.com/RooVeterinaryInc/Roo-NB) extension for a complete example of a tool provider extension.

src/core/Cline.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ import { searchFilesTool } from "./tools/searchFilesTool"
7171
import { browserActionTool } from "./tools/browserActionTool"
7272
import { executeCommandTool } from "./tools/executeCommandTool"
7373
import { useMcpToolTool } from "./tools/useMcpToolTool"
74+
import { useExtToolTool } from "./tools/useExtToolTool"
7475
import { accessMcpResourceTool } from "./tools/accessMcpResourceTool"
7576
import { askFollowupQuestionTool } from "./tools/askFollowupQuestionTool"
7677
import { switchModeTool } from "./tools/switchModeTool"
@@ -980,6 +981,7 @@ export class Cline extends EventEmitter<ClineEvents> {
980981
this.cwd,
981982
(this.api.getModel().info.supportsComputerUse ?? false) && (browserToolEnabled ?? true),
982983
mcpHub,
984+
provider.getExtensionToolManager(),
983985
this.diffStrategy,
984986
browserViewportSize,
985987
mode,
@@ -1241,6 +1243,8 @@ export class Cline extends EventEmitter<ClineEvents> {
12411243
return `[${block.name} for '${block.params.action}']`
12421244
case "use_mcp_tool":
12431245
return `[${block.name} for '${block.params.server_name}']`
1246+
case "use_ext_tool":
1247+
return `[${block.name} for '${block.params.extension_id}']`
12441248
case "access_mcp_resource":
12451249
return `[${block.name} for '${block.params.server_name}']`
12461250
case "ask_followup_question":
@@ -1462,6 +1466,9 @@ export class Cline extends EventEmitter<ClineEvents> {
14621466
case "use_mcp_tool":
14631467
await useMcpToolTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag)
14641468
break
1469+
case "use_ext_tool":
1470+
await useExtToolTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag)
1471+
break
14651472
case "access_mcp_resource":
14661473
await accessMcpResourceTool(
14671474
this,

src/core/prompts/__tests__/custom-system-prompt.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ describe("File-Based Custom System Prompt", () => {
6767
"test/path", // Using a relative path without leading slash
6868
false, // supportsComputerUse
6969
undefined, // mcpHub
70+
undefined, // extensionToolManager
7071
undefined, // diffStrategy
7172
undefined, // browserViewportSize
7273
defaultModeSlug, // mode
@@ -101,6 +102,7 @@ describe("File-Based Custom System Prompt", () => {
101102
"test/path", // Using a relative path without leading slash
102103
false, // supportsComputerUse
103104
undefined, // mcpHub
105+
undefined, // extensionToolManager
104106
undefined, // diffStrategy
105107
undefined, // browserViewportSize
106108
defaultModeSlug, // mode
@@ -144,6 +146,7 @@ describe("File-Based Custom System Prompt", () => {
144146
"test/path", // Using a relative path without leading slash
145147
false, // supportsComputerUse
146148
undefined, // mcpHub
149+
undefined, // extensionToolManager
147150
undefined, // diffStrategy
148151
undefined, // browserViewportSize
149152
defaultModeSlug, // mode

src/core/prompts/__tests__/system.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import "../../../utils/path" // Import path utils to get access to toPosix strin
88
import { addCustomInstructions } from "../sections/custom-instructions"
99
import { EXPERIMENT_IDS } from "../../../shared/experiments"
1010
import { MultiSearchReplaceDiffStrategy } from "../../diff/strategies/multi-search-replace"
11+
import { ExtensionToolManager } from "../../../services/extensions/ExtensionToolManager"
1112

1213
// Mock the sections
1314
jest.mock("../sections/modes", () => ({
@@ -190,6 +191,7 @@ describe("SYSTEM_PROMPT", () => {
190191
"/test/path",
191192
false, // supportsComputerUse
192193
undefined, // mcpHub
194+
undefined, // extensionToolManager
193195
undefined, // diffStrategy
194196
undefined, // browserViewportSize
195197
defaultModeSlug, // mode
@@ -210,6 +212,7 @@ describe("SYSTEM_PROMPT", () => {
210212
"/test/path",
211213
true, // supportsComputerUse
212214
undefined, // mcpHub
215+
undefined, // extensionToolManager
213216
undefined, // diffStrategy
214217
"1280x800", // browserViewportSize
215218
defaultModeSlug, // mode
@@ -232,6 +235,7 @@ describe("SYSTEM_PROMPT", () => {
232235
"/test/path",
233236
false, // supportsComputerUse
234237
mockMcpHub, // mcpHub
238+
undefined, // extensionToolManager
235239
undefined, // diffStrategy
236240
undefined, // browserViewportSize
237241
defaultModeSlug, // mode
@@ -252,6 +256,7 @@ describe("SYSTEM_PROMPT", () => {
252256
"/test/path",
253257
false, // supportsComputerUse
254258
undefined, // explicitly undefined mcpHub
259+
undefined, // extensionToolManager
255260
undefined, // diffStrategy
256261
undefined, // browserViewportSize
257262
defaultModeSlug, // mode
@@ -272,6 +277,7 @@ describe("SYSTEM_PROMPT", () => {
272277
"/test/path",
273278
true, // supportsComputerUse
274279
undefined, // mcpHub
280+
undefined, // extensionToolManager
275281
undefined, // diffStrategy
276282
"900x600", // different viewport size
277283
defaultModeSlug, // mode
@@ -292,6 +298,7 @@ describe("SYSTEM_PROMPT", () => {
292298
"/test/path",
293299
false, // supportsComputerUse
294300
undefined, // mcpHub
301+
undefined, // extensionToolManager
295302
new MultiSearchReplaceDiffStrategy(), // Use actual diff strategy from the codebase
296303
undefined, // browserViewportSize
297304
defaultModeSlug, // mode
@@ -313,6 +320,7 @@ describe("SYSTEM_PROMPT", () => {
313320
"/test/path",
314321
false, // supportsComputerUse
315322
undefined, // mcpHub
323+
undefined, // extensionToolManager
316324
new MultiSearchReplaceDiffStrategy(), // Use actual diff strategy from the codebase
317325
undefined, // browserViewportSize
318326
defaultModeSlug, // mode
@@ -334,6 +342,7 @@ describe("SYSTEM_PROMPT", () => {
334342
"/test/path",
335343
false, // supportsComputerUse
336344
undefined, // mcpHub
345+
undefined, // extensionToolManager
337346
new MultiSearchReplaceDiffStrategy(), // Use actual diff strategy from the codebase
338347
undefined, // browserViewportSize
339348
defaultModeSlug, // mode
@@ -359,6 +368,7 @@ describe("SYSTEM_PROMPT", () => {
359368
"/test/path",
360369
false, // supportsComputerUse
361370
undefined, // mcpHub
371+
undefined, // extensionToolManager
362372
undefined, // diffStrategy
363373
undefined, // browserViewportSize
364374
defaultModeSlug, // mode
@@ -395,6 +405,7 @@ describe("SYSTEM_PROMPT", () => {
395405
"/test/path",
396406
false, // supportsComputerUse
397407
undefined, // mcpHub
408+
undefined, // extensionToolManager
398409
undefined, // diffStrategy
399410
undefined, // browserViewportSize
400411
"custom-mode", // mode
@@ -430,6 +441,7 @@ describe("SYSTEM_PROMPT", () => {
430441
"/test/path",
431442
false, // supportsComputerUse
432443
undefined, // mcpHub
444+
undefined, // extensionToolManager
433445
undefined, // diffStrategy
434446
undefined, // browserViewportSize
435447
defaultModeSlug as Mode, // mode
@@ -460,6 +472,7 @@ describe("SYSTEM_PROMPT", () => {
460472
"/test/path",
461473
false, // supportsComputerUse
462474
undefined, // mcpHub
475+
undefined, // extensionToolManager
463476
undefined, // diffStrategy
464477
undefined, // browserViewportSize
465478
defaultModeSlug as Mode, // mode
@@ -508,6 +521,7 @@ describe("addCustomInstructions", () => {
508521
"/test/path",
509522
false, // supportsComputerUse
510523
undefined, // mcpHub
524+
undefined, // extensionToolManager
511525
undefined, // diffStrategy
512526
undefined, // browserViewportSize
513527
"architect", // mode
@@ -528,6 +542,7 @@ describe("addCustomInstructions", () => {
528542
"/test/path",
529543
false, // supportsComputerUse
530544
undefined, // mcpHub
545+
undefined, // extensionToolManager
531546
undefined, // diffStrategy
532547
undefined, // browserViewportSize
533548
"ask", // mode
@@ -550,6 +565,7 @@ describe("addCustomInstructions", () => {
550565
"/test/path",
551566
false, // supportsComputerUse
552567
mockMcpHub, // mcpHub
568+
undefined, // extensionToolManager
553569
undefined, // diffStrategy
554570
undefined, // browserViewportSize
555571
defaultModeSlug, // mode
@@ -573,6 +589,7 @@ describe("addCustomInstructions", () => {
573589
"/test/path",
574590
false, // supportsComputerUse
575591
mockMcpHub, // mcpHub
592+
undefined, // extensionToolManager
576593
undefined, // diffStrategy
577594
undefined, // browserViewportSize
578595
defaultModeSlug, // mode

src/core/prompts/responses.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ Otherwise, if you have not completed the task and do not need additional informa
7171
invalidMcpToolArgumentError: (serverName: string, toolName: string) =>
7272
`Invalid JSON argument used with ${serverName} for ${toolName}. Please retry with a properly formatted JSON argument.`,
7373

74+
invalidExtToolArgumentError: (extensionId: string, toolName: string) =>
75+
`Invalid JSON argument used with ${extensionId} for ${toolName}. Please retry with a properly formatted JSON argument.`,
76+
7477
toolResult: (
7578
text: string,
7679
images?: string[],
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { ExtensionToolManager } from "../../../services/extensions/ExtensionToolManager"
2+
3+
/**
4+
* Generates the section of the system prompt that describes available extension tools
5+
*/
6+
export async function getExtToolsSection(extensionToolManager?: ExtensionToolManager): Promise<string> {
7+
// If no manager is provided, get the singleton instance
8+
if (!extensionToolManager) {
9+
try {
10+
extensionToolManager = await ExtensionToolManager.getInstance()
11+
} catch (error) {
12+
console.error("Failed to get ExtensionToolManager:", error)
13+
return ""
14+
}
15+
}
16+
17+
// Get all registered tools
18+
const allTools = extensionToolManager.getAllTools()
19+
20+
if (allTools.length === 0) {
21+
return ""
22+
}
23+
24+
// Group tools by extension
25+
const extensionTools: Record<string, string[]> = {}
26+
27+
for (const { extensionId, tool } of allTools) {
28+
if (!extensionTools[extensionId]) {
29+
extensionTools[extensionId] = []
30+
}
31+
32+
extensionTools[extensionId].push(`${tool.name}: ${tool.description}`)
33+
}
34+
35+
let result = "# Available Extension Tools\n\n"
36+
37+
for (const [extensionId, tools] of Object.entries(extensionTools)) {
38+
result += `## Extension: ${extensionId}\n\n`
39+
40+
for (const toolDesc of tools) {
41+
result += `- ${toolDesc}\n`
42+
}
43+
44+
result += "\n"
45+
}
46+
47+
return result
48+
}

src/core/prompts/sections/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export { getObjectiveSection } from "./objective"
44
export { addCustomInstructions } from "./custom-instructions"
55
export { getSharedToolUseSection } from "./tool-use"
66
export { getMcpServersSection } from "./mcp-servers"
7+
export { getExtToolsSection } from "./ext-tools"
78
export { getToolUseGuidelinesSection } from "./tool-use-guidelines"
89
export { getCapabilitiesSection } from "./capabilities"
910
export { getModesSection } from "./modes"

0 commit comments

Comments
 (0)