Skip to content

Commit a4f26e0

Browse files
committed
vscode extension tool interface
1 parent 1924e10 commit a4f26e0

37 files changed

+1234
-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
@@ -70,6 +70,7 @@ import { searchFilesTool } from "./tools/searchFilesTool"
7070
import { browserActionTool } from "./tools/browserActionTool"
7171
import { executeCommandTool } from "./tools/executeCommandTool"
7272
import { useMcpToolTool } from "./tools/useMcpToolTool"
73+
import { useExtToolTool } from "./tools/useExtToolTool"
7374
import { accessMcpResourceTool } from "./tools/accessMcpResourceTool"
7475
import { askFollowupQuestionTool } from "./tools/askFollowupQuestionTool"
7576
import { switchModeTool } from "./tools/switchModeTool"
@@ -979,6 +980,7 @@ export class Cline extends EventEmitter<ClineEvents> {
979980
this.cwd,
980981
(this.api.getModel().info.supportsComputerUse ?? false) && (browserToolEnabled ?? true),
981982
mcpHub,
983+
provider.getExtensionToolManager(),
982984
this.diffStrategy,
983985
browserViewportSize,
984986
mode,
@@ -1240,6 +1242,8 @@ export class Cline extends EventEmitter<ClineEvents> {
12401242
return `[${block.name} for '${block.params.action}']`
12411243
case "use_mcp_tool":
12421244
return `[${block.name} for '${block.params.server_name}']`
1245+
case "use_ext_tool":
1246+
return `[${block.name} for '${block.params.extension_id}']`
12431247
case "access_mcp_resource":
12441248
return `[${block.name} for '${block.params.server_name}']`
12451249
case "ask_followup_question":
@@ -1461,6 +1465,9 @@ export class Cline extends EventEmitter<ClineEvents> {
14611465
case "use_mcp_tool":
14621466
await useMcpToolTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag)
14631467
break
1468+
case "use_ext_tool":
1469+
await useExtToolTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag)
1470+
break
14641471
case "access_mcp_resource":
14651472
await accessMcpResourceTool(
14661473
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: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ describe("SYSTEM_PROMPT", () => {
180180
"/test/path",
181181
false, // supportsComputerUse
182182
undefined, // mcpHub
183+
undefined, // extensionToolManager
183184
undefined, // diffStrategy
184185
undefined, // browserViewportSize
185186
defaultModeSlug, // mode
@@ -200,6 +201,7 @@ describe("SYSTEM_PROMPT", () => {
200201
"/test/path",
201202
true, // supportsComputerUse
202203
undefined, // mcpHub
204+
undefined, // extensionToolManager
203205
undefined, // diffStrategy
204206
"1280x800", // browserViewportSize
205207
defaultModeSlug, // mode
@@ -222,6 +224,7 @@ describe("SYSTEM_PROMPT", () => {
222224
"/test/path",
223225
false, // supportsComputerUse
224226
mockMcpHub, // mcpHub
227+
undefined, // extensionToolManager
225228
undefined, // diffStrategy
226229
undefined, // browserViewportSize
227230
defaultModeSlug, // mode
@@ -242,6 +245,7 @@ describe("SYSTEM_PROMPT", () => {
242245
"/test/path",
243246
false, // supportsComputerUse
244247
undefined, // explicitly undefined mcpHub
248+
undefined, // extensionToolManager
245249
undefined, // diffStrategy
246250
undefined, // browserViewportSize
247251
defaultModeSlug, // mode
@@ -262,6 +266,7 @@ describe("SYSTEM_PROMPT", () => {
262266
"/test/path",
263267
true, // supportsComputerUse
264268
undefined, // mcpHub
269+
undefined, // extensionToolManager
265270
undefined, // diffStrategy
266271
"900x600", // different viewport size
267272
defaultModeSlug, // mode
@@ -282,6 +287,7 @@ describe("SYSTEM_PROMPT", () => {
282287
"/test/path",
283288
false, // supportsComputerUse
284289
undefined, // mcpHub
290+
undefined, // extensionToolManager
285291
new MultiSearchReplaceDiffStrategy(), // Use actual diff strategy from the codebase
286292
undefined, // browserViewportSize
287293
defaultModeSlug, // mode
@@ -303,6 +309,7 @@ describe("SYSTEM_PROMPT", () => {
303309
"/test/path",
304310
false, // supportsComputerUse
305311
undefined, // mcpHub
312+
undefined, // extensionToolManager
306313
new MultiSearchReplaceDiffStrategy(), // Use actual diff strategy from the codebase
307314
undefined, // browserViewportSize
308315
defaultModeSlug, // mode
@@ -324,6 +331,7 @@ describe("SYSTEM_PROMPT", () => {
324331
"/test/path",
325332
false, // supportsComputerUse
326333
undefined, // mcpHub
334+
undefined, // extensionToolManager
327335
new MultiSearchReplaceDiffStrategy(), // Use actual diff strategy from the codebase
328336
undefined, // browserViewportSize
329337
defaultModeSlug, // mode
@@ -349,6 +357,7 @@ describe("SYSTEM_PROMPT", () => {
349357
"/test/path",
350358
false, // supportsComputerUse
351359
undefined, // mcpHub
360+
undefined, // extensionToolManager
352361
undefined, // diffStrategy
353362
undefined, // browserViewportSize
354363
defaultModeSlug, // mode
@@ -385,6 +394,7 @@ describe("SYSTEM_PROMPT", () => {
385394
"/test/path",
386395
false, // supportsComputerUse
387396
undefined, // mcpHub
397+
undefined, // extensionToolManager
388398
undefined, // diffStrategy
389399
undefined, // browserViewportSize
390400
"custom-mode", // mode
@@ -420,6 +430,7 @@ describe("SYSTEM_PROMPT", () => {
420430
"/test/path",
421431
false, // supportsComputerUse
422432
undefined, // mcpHub
433+
undefined, // extensionToolManager
423434
undefined, // diffStrategy
424435
undefined, // browserViewportSize
425436
defaultModeSlug as Mode, // mode
@@ -450,6 +461,7 @@ describe("SYSTEM_PROMPT", () => {
450461
"/test/path",
451462
false, // supportsComputerUse
452463
undefined, // mcpHub
464+
undefined, // extensionToolManager
453465
undefined, // diffStrategy
454466
undefined, // browserViewportSize
455467
defaultModeSlug as Mode, // mode
@@ -494,6 +506,7 @@ describe("addCustomInstructions", () => {
494506
"/test/path",
495507
false, // supportsComputerUse
496508
undefined, // mcpHub
509+
undefined, // extensionToolManager
497510
undefined, // diffStrategy
498511
undefined, // browserViewportSize
499512
"architect", // mode
@@ -514,6 +527,7 @@ describe("addCustomInstructions", () => {
514527
"/test/path",
515528
false, // supportsComputerUse
516529
undefined, // mcpHub
530+
undefined, // extensionToolManager
517531
undefined, // diffStrategy
518532
undefined, // browserViewportSize
519533
"ask", // mode
@@ -536,6 +550,7 @@ describe("addCustomInstructions", () => {
536550
"/test/path",
537551
false, // supportsComputerUse
538552
mockMcpHub, // mcpHub
553+
undefined, // extensionToolManager
539554
undefined, // diffStrategy
540555
undefined, // browserViewportSize
541556
defaultModeSlug, // mode
@@ -559,6 +574,7 @@ describe("addCustomInstructions", () => {
559574
"/test/path",
560575
false, // supportsComputerUse
561576
mockMcpHub, // mcpHub
577+
undefined, // extensionToolManager
562578
undefined, // diffStrategy
563579
undefined, // browserViewportSize
564580
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"

src/core/prompts/system.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,23 @@ import {
2020
getObjectiveSection,
2121
getSharedToolUseSection,
2222
getMcpServersSection,
23+
getExtToolsSection,
2324
getToolUseGuidelinesSection,
2425
getCapabilitiesSection,
2526
getModesSection,
2627
addCustomInstructions,
2728
} from "./sections"
2829
import { loadSystemPromptFile } from "./sections/custom-system-prompt"
2930
import { formatLanguage } from "../../shared/language"
31+
import { ExtensionToolManager } from "../../exports/extensionToolApi"
3032

3133
async function generatePrompt(
3234
context: vscode.ExtensionContext,
3335
cwd: string,
3436
supportsComputerUse: boolean,
3537
mode: Mode,
3638
mcpHub?: McpHub,
39+
extensionToolManager?: ExtensionToolManager,
3740
diffStrategy?: DiffStrategy,
3841
browserViewportSize?: string,
3942
promptComponent?: PromptComponent,
@@ -56,11 +59,14 @@ async function generatePrompt(
5659
const modeConfig = getModeBySlug(mode, customModeConfigs) || modes.find((m) => m.slug === mode) || modes[0]
5760
const roleDefinition = promptComponent?.roleDefinition || modeConfig.roleDefinition
5861

59-
const [modesSection, mcpServersSection] = await Promise.all([
62+
const [modesSection, mcpServersSection, extToolsSection] = await Promise.all([
6063
getModesSection(context),
6164
modeConfig.groups.some((groupEntry) => getGroupName(groupEntry) === "mcp")
6265
? getMcpServersSection(mcpHub, effectiveDiffStrategy, enableMcpServerCreation)
6366
: Promise.resolve(""),
67+
modeConfig.groups.some((groupEntry) => getGroupName(groupEntry) === "ext")
68+
? getExtToolsSection(extensionToolManager)
69+
: Promise.resolve(""),
6470
])
6571

6672
const basePrompt = `${roleDefinition}
@@ -74,6 +80,7 @@ ${getToolDescriptionsForMode(
7480
effectiveDiffStrategy,
7581
browserViewportSize,
7682
mcpHub,
83+
extensionToolManager,
7784
customModeConfigs,
7885
experiments,
7986
)}
@@ -82,6 +89,8 @@ ${getToolUseGuidelinesSection()}
8289
8390
${mcpServersSection}
8491
92+
${extToolsSection}
93+
8594
${getCapabilitiesSection(cwd, supportsComputerUse, mcpHub, effectiveDiffStrategy)}
8695
8796
${modesSection}
@@ -102,6 +111,7 @@ export const SYSTEM_PROMPT = async (
102111
cwd: string,
103112
supportsComputerUse: boolean,
104113
mcpHub?: McpHub,
114+
extensionToolManager?: ExtensionToolManager,
105115
diffStrategy?: DiffStrategy,
106116
browserViewportSize?: string,
107117
mode: Mode = defaultModeSlug,
@@ -169,6 +179,7 @@ ${customInstructions}`
169179
supportsComputerUse,
170180
currentMode.slug,
171181
mcpHub,
182+
extensionToolManager,
172183
effectiveDiffStrategy,
173184
browserViewportSize,
174185
promptComponent,

0 commit comments

Comments
 (0)