Skip to content

Commit 2212a3a

Browse files
committed
Minor cleanup
1 parent f436e7c commit 2212a3a

File tree

2 files changed

+15
-131
lines changed

2 files changed

+15
-131
lines changed

packages/core/src/custom-tools/__tests__/custom-tool-registry.spec.ts

Lines changed: 2 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,14 @@
11
import path from "path"
22
import { fileURLToPath } from "url"
33

4-
import {
5-
type CustomToolDefinition,
6-
type CustomToolContext,
7-
type TaskLike,
8-
parametersSchema as z,
9-
} from "@roo-code/types"
4+
import { type CustomToolDefinition, parametersSchema as z } from "@roo-code/types"
105

116
import { CustomToolRegistry } from "../custom-tool-registry.js"
127

138
const __dirname = path.dirname(fileURLToPath(import.meta.url))
149

1510
const TEST_FIXTURES_DIR = path.join(__dirname, "fixtures")
1611

17-
const testContext: CustomToolContext = {
18-
mode: "code",
19-
task: { taskId: "test-task-id" } as unknown as TaskLike,
20-
}
21-
2212
describe("CustomToolRegistry", () => {
2313
let registry: CustomToolRegistry
2414

@@ -214,65 +204,6 @@ describe("CustomToolRegistry", () => {
214204
})
215205
})
216206

217-
describe("execute", () => {
218-
it("should execute a tool with arguments", async () => {
219-
registry.register({
220-
name: "greeter",
221-
description: "Greets someone",
222-
parameters: z.object({ name: z.string() }),
223-
execute: async (args) => `Hello, ${(args as { name: string }).name}!`,
224-
})
225-
226-
const result = await registry.execute("greeter", { name: "World" }, testContext)
227-
228-
expect(result).toBe("Hello, World!")
229-
})
230-
231-
it("should throw for non-existent tool", async () => {
232-
await expect(registry.execute("nonexistent", {}, testContext)).rejects.toThrow(
233-
"Tool not found: nonexistent",
234-
)
235-
})
236-
237-
it("should validate arguments against Zod schema", async () => {
238-
registry.register({
239-
name: "typed_tool",
240-
description: "Tool with validation",
241-
parameters: z.object({
242-
count: z.number().min(0),
243-
}),
244-
execute: async (args) => `Count: ${(args as { count: number }).count}`,
245-
})
246-
247-
// Valid args.
248-
const result = await registry.execute("typed_tool", { count: 5 }, testContext)
249-
expect(result).toBe("Count: 5")
250-
251-
// Invalid args - negative number.
252-
await expect(registry.execute("typed_tool", { count: -1 }, testContext)).rejects.toThrow()
253-
254-
// Invalid args - wrong type.
255-
await expect(registry.execute("typed_tool", { count: "five" }, testContext)).rejects.toThrow()
256-
})
257-
258-
it("should pass context to execute function", async () => {
259-
let receivedContext: CustomToolContext | null = null
260-
261-
registry.register({
262-
name: "context_checker",
263-
description: "Checks context",
264-
execute: async (_args, ctx) => {
265-
receivedContext = ctx
266-
return "done"
267-
},
268-
})
269-
270-
await registry.execute("context_checker", {}, testContext)
271-
272-
expect(receivedContext).toEqual(testContext)
273-
})
274-
})
275-
276207
describe("clear", () => {
277208
it("should remove all registered tools", () => {
278209
registry.register({ name: "tool1", description: "1", execute: async () => "1" })
@@ -321,7 +252,7 @@ describe("CustomToolRegistry", () => {
321252
const result = await registry.loadFromDirectory(TEST_FIXTURES_DIR)
322253

323254
expect(result.loaded).toContain("mixed_validTool")
324-
// The non-tool exports should not appear in loaded or failed
255+
// The non-tool exports should not appear in loaded or failed.
325256
expect(result.loaded).not.toContain("mixed_someString")
326257
expect(result.loaded).not.toContain("mixed_someNumber")
327258
expect(result.loaded).not.toContain("mixed_someObject")

packages/core/src/custom-tools/custom-tool-registry.ts

Lines changed: 13 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -15,29 +15,10 @@ import os from "os"
1515

1616
import { build } from "esbuild"
1717

18-
import type {
19-
CustomToolDefinition,
20-
SerializedCustomToolDefinition,
21-
CustomToolParametersSchema,
22-
CustomToolContext,
23-
} from "@roo-code/types"
18+
import type { CustomToolDefinition, SerializedCustomToolDefinition, CustomToolParametersSchema } from "@roo-code/types"
2419

2520
import { serializeCustomTool } from "./serialize.js"
2621

27-
/**
28-
* Default subdirectory name for custom tools within a .roo directory.
29-
* Tools placed in `{rooDir}/tools/` will be automatically discovered and loaded.
30-
*
31-
* @example
32-
* ```ts
33-
* // Typical usage with getRooDirectoriesForCwd from roo-config:
34-
* for (const rooDir of getRooDirectoriesForCwd(cwd)) {
35-
* await registry.loadFromDirectory(path.join(rooDir, TOOLS_DIR_NAME))
36-
* }
37-
* ```
38-
*/
39-
export const TOOLS_DIR_NAME = "tools"
40-
4122
export interface LoadResult {
4223
loaded: string[]
4324
failed: Array<{ file: string; error: string }>
@@ -69,18 +50,6 @@ export class CustomToolRegistry {
6950
*
7051
* @param toolDir - Absolute path to the tools directory
7152
* @returns LoadResult with lists of loaded and failed tools
72-
*
73-
* @example
74-
* ```ts
75-
* // Load tools from multiple .roo directories (global and project):
76-
* import { getRooDirectoriesForCwd } from "../services/roo-config"
77-
* import { CustomToolRegistry, TOOLS_DIR_NAME } from "@roo-code/core"
78-
*
79-
* const registry = new CustomToolRegistry()
80-
* for (const rooDir of getRooDirectoriesForCwd(cwd)) {
81-
* await registry.loadFromDirectory(path.join(rooDir, TOOLS_DIR_NAME))
82-
* }
83-
* ```
8453
*/
8554
async loadFromDirectory(toolDir: string): Promise<LoadResult> {
8655
const result: LoadResult = { loaded: [], failed: [] }
@@ -97,10 +66,10 @@ export class CustomToolRegistry {
9766

9867
try {
9968
console.log(`[CustomToolRegistry] importing tool from ${filePath}`)
100-
const mod = await this.importTypeScript(filePath)
69+
const mod = await this.import(filePath)
10170

10271
for (const [exportName, value] of Object.entries(mod)) {
103-
const def = this.validateToolDefinition(exportName, value)
72+
const def = this.validate(exportName, value)
10473

10574
if (!def) {
10675
continue
@@ -112,7 +81,7 @@ export class CustomToolRegistry {
11281
}
11382
} catch (error) {
11483
const message = error instanceof Error ? error.message : String(error)
115-
console.error(`[CustomToolRegistry] importTypeScript(${filePath}) failed: ${message}`)
84+
console.error(`[CustomToolRegistry] import(${filePath}) failed: ${message}`)
11685
result.failed.push({ file, error: message })
11786
}
11887
}
@@ -125,7 +94,6 @@ export class CustomToolRegistry {
12594
}
12695

12796
async loadFromDirectoryIfStale(toolDir: string): Promise<LoadResult> {
128-
// Return empty result if directory doesn't exist
12997
if (!fs.existsSync(toolDir)) {
13098
return { loaded: [], failed: [] }
13199
}
@@ -147,7 +115,7 @@ export class CustomToolRegistry {
147115
*/
148116
register(definition: CustomToolDefinition): void {
149117
const { name: id } = definition
150-
const validated = this.validateToolDefinition(id, definition)
118+
const validated = this.validate(id, definition)
151119

152120
if (!validated) {
153121
throw new Error(`Invalid tool definition for '${id}'`)
@@ -191,6 +159,9 @@ export class CustomToolRegistry {
191159
return Array.from(this.tools.values())
192160
}
193161

162+
/**
163+
* Get all registered tools in the serialized format.
164+
*/
194165
getAllSerialized(): SerializedCustomToolDefinition[] {
195166
return this.getAll().map(serializeCustomTool)
196167
}
@@ -202,24 +173,6 @@ export class CustomToolRegistry {
202173
return this.tools.size
203174
}
204175

205-
/**
206-
* Execute a tool with given arguments.
207-
*/
208-
async execute(toolId: string, args: unknown, context: CustomToolContext): Promise<string> {
209-
const tool = this.tools.get(toolId)
210-
211-
if (!tool) {
212-
throw new Error(`Tool not found: ${toolId}`)
213-
}
214-
215-
// Validate args against schema if available.
216-
if (tool.parameters && "parse" in tool.parameters) {
217-
;(tool.parameters as { parse: (args: unknown) => void }).parse(args)
218-
}
219-
220-
return tool.execute(args, context)
221-
}
222-
223176
/**
224177
* Clear all registered tools.
225178
*/
@@ -233,7 +186,6 @@ export class CustomToolRegistry {
233186
clearCache(): void {
234187
this.tsCache.clear()
235188

236-
// Also remove compiled files from disk
237189
if (fs.existsSync(this.cacheDir)) {
238190
try {
239191
const files = fs.readdirSync(this.cacheDir)
@@ -243,8 +195,9 @@ export class CustomToolRegistry {
243195
}
244196
}
245197
} catch (error) {
246-
// Silently ignore cleanup errors
247-
console.error(`[CustomToolRegistry] clearCache failed to clean disk cache: ${error}`)
198+
console.error(
199+
`[CustomToolRegistry] clearCache failed to clean disk cache: ${error instanceof Error ? error.message : String(error)}`,
200+
)
248201
}
249202
}
250203
}
@@ -253,7 +206,7 @@ export class CustomToolRegistry {
253206
* Dynamically import a TypeScript or JavaScript file.
254207
* TypeScript files are transpiled on-the-fly using esbuild.
255208
*/
256-
private async importTypeScript(filePath: string): Promise<Record<string, CustomToolDefinition>> {
209+
private async import(filePath: string): Promise<Record<string, CustomToolDefinition>> {
257210
const absolutePath = path.resolve(filePath)
258211
const ext = path.extname(absolutePath)
259212

@@ -311,7 +264,7 @@ export class CustomToolRegistry {
311264
* Validate a tool definition and return a typed result.
312265
* Returns null for non-tool exports, throws for invalid tools.
313266
*/
314-
private validateToolDefinition(exportName: string, value: unknown): CustomToolDefinition | null {
267+
private validate(exportName: string, value: unknown): CustomToolDefinition | null {
315268
// Quick pre-check to filter out non-objects.
316269
if (!value || typeof value !== "object") {
317270
return null

0 commit comments

Comments
 (0)