fix(tool): ignore invalid custom tool exports
This commit is contained in:
@@ -201,7 +201,8 @@ export const layer: Layer.Layer<
|
|||||||
// `match` is an absolute filesystem path from `Glob.scanSync(..., { absolute: true })`.
|
// `match` is an absolute filesystem path from `Glob.scanSync(..., { absolute: true })`.
|
||||||
// Import it as `file://` so Node on Windows accepts the dynamic import.
|
// Import it as `file://` so Node on Windows accepts the dynamic import.
|
||||||
const mod = yield* Effect.promise(() => import(pathToFileURL(match).href))
|
const mod = yield* Effect.promise(() => import(pathToFileURL(match).href))
|
||||||
for (const [id, def] of Object.entries<ToolDefinition>(mod)) {
|
for (const [id, def] of Object.entries(mod)) {
|
||||||
|
if (!isPluginTool(def)) continue
|
||||||
custom.push(fromPlugin(id === "default" ? namespace : `${namespace}_${id}`, def))
|
custom.push(fromPlugin(id === "default" ? namespace : `${namespace}_${id}`, def))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -396,6 +397,10 @@ function isZodType(value: unknown): value is z.ZodType {
|
|||||||
return typeof value === "object" && value !== null && "_zod" in value
|
return typeof value === "object" && value !== null && "_zod" in value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isPluginTool(value: unknown): value is ToolDefinition {
|
||||||
|
return typeof value === "object" && value !== null && "args" in value && "description" in value && "execute" in value
|
||||||
|
}
|
||||||
|
|
||||||
function isJsonSchemaDefinition(value: unknown): value is JSONSchema7Definition {
|
function isJsonSchemaDefinition(value: unknown): value is JSONSchema7Definition {
|
||||||
return typeof value === "boolean" || (typeof value === "object" && value !== null && !Array.isArray(value))
|
return typeof value === "boolean" || (typeof value === "object" && value !== null && !Array.isArray(value))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -158,6 +158,33 @@ describe("tool.registry", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
it.instance("ignores non-tool exports in .opencode/tool files", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const test = yield* TestInstance
|
||||||
|
const tool = path.join(test.directory, ".opencode", "tool")
|
||||||
|
yield* Effect.promise(() => fs.mkdir(tool, { recursive: true }))
|
||||||
|
yield* Effect.promise(() =>
|
||||||
|
Bun.write(
|
||||||
|
path.join(tool, "mixed.ts"),
|
||||||
|
[
|
||||||
|
"export const helper = 'not a tool'",
|
||||||
|
"export default {",
|
||||||
|
" description: 'mixed tool',",
|
||||||
|
" args: {},",
|
||||||
|
" execute: async () => 'ok',",
|
||||||
|
"}",
|
||||||
|
"",
|
||||||
|
].join("\n"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
const registry = yield* ToolRegistry.Service
|
||||||
|
const ids = yield* registry.ids()
|
||||||
|
expect(ids).toContain("mixed")
|
||||||
|
expect(ids).not.toContain("mixed_helper")
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
it.instance("loads tools from .opencode/tools (plural)", () =>
|
it.instance("loads tools from .opencode/tools (plural)", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const test = yield* TestInstance
|
const test = yield* TestInstance
|
||||||
|
|||||||
Reference in New Issue
Block a user