test(opencode): migrate tool registry fixtures (#29042)
This commit is contained in:
parent
e2fd4413c8
commit
84eff93bfa
2 changed files with 185 additions and 237 deletions
|
|
@ -55,6 +55,7 @@ import { Reference } from "@/reference/reference"
|
||||||
import { BackgroundJob } from "@/background/job"
|
import { BackgroundJob } from "@/background/job"
|
||||||
import { SessionStatus } from "@/session/status"
|
import { SessionStatus } from "@/session/status"
|
||||||
import { RuntimeFlags } from "@/effect/runtime-flags"
|
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||||
|
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||||
|
|
||||||
const log = Log.create({ service: "tool.registry" })
|
const log = Log.create({ service: "tool.registry" })
|
||||||
|
|
||||||
|
|
@ -81,6 +82,8 @@ export interface Interface {
|
||||||
|
|
||||||
export class Service extends Context.Service<Service, Interface>()("@opencode/ToolRegistry") {}
|
export class Service extends Context.Service<Service, Interface>()("@opencode/ToolRegistry") {}
|
||||||
|
|
||||||
|
export const use = serviceUse(Service)
|
||||||
|
|
||||||
export const layer: Layer.Layer<
|
export const layer: Layer.Layer<
|
||||||
Service,
|
Service,
|
||||||
never,
|
never,
|
||||||
|
|
|
||||||
|
|
@ -95,15 +95,25 @@ const brokenPluginLayer = Layer.succeed(
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
const it = testEffect(Layer.mergeAll(registryLayer(), node, Agent.defaultLayer))
|
const it = testEffect(Layer.mergeAll(registryLayer(), node, Agent.defaultLayer, AppFileSystem.defaultLayer))
|
||||||
const scout = testEffect(
|
const scout = testEffect(
|
||||||
Layer.mergeAll(registryLayer({ flags: { experimentalScout: true } }), node, Agent.defaultLayer),
|
Layer.mergeAll(
|
||||||
|
registryLayer({ flags: { experimentalScout: true } }),
|
||||||
|
node,
|
||||||
|
Agent.defaultLayer,
|
||||||
|
AppFileSystem.defaultLayer,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
const background = testEffect(
|
const background = testEffect(
|
||||||
Layer.mergeAll(registryLayer({ flags: { experimentalBackgroundSubagents: true } }), node, Agent.defaultLayer),
|
Layer.mergeAll(
|
||||||
|
registryLayer({ flags: { experimentalBackgroundSubagents: true } }),
|
||||||
|
node,
|
||||||
|
Agent.defaultLayer,
|
||||||
|
AppFileSystem.defaultLayer,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
const withBrokenPlugin = testEffect(
|
const withBrokenPlugin = testEffect(
|
||||||
Layer.mergeAll(registryLayer({ plugin: brokenPluginLayer }), node, Agent.defaultLayer),
|
Layer.mergeAll(registryLayer({ plugin: brokenPluginLayer }), node, Agent.defaultLayer, AppFileSystem.defaultLayer),
|
||||||
)
|
)
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
|
|
@ -113,8 +123,7 @@ afterEach(async () => {
|
||||||
describe("tool.registry", () => {
|
describe("tool.registry", () => {
|
||||||
it.instance("hides repo research tools unless experimental", () =>
|
it.instance("hides repo research tools unless experimental", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const registry = yield* ToolRegistry.Service
|
const ids = yield* ToolRegistry.use.ids()
|
||||||
const ids = yield* registry.ids()
|
|
||||||
|
|
||||||
expect(ids).not.toContain("repo_clone")
|
expect(ids).not.toContain("repo_clone")
|
||||||
expect(ids).not.toContain("repo_overview")
|
expect(ids).not.toContain("repo_overview")
|
||||||
|
|
@ -123,8 +132,7 @@ describe("tool.registry", () => {
|
||||||
|
|
||||||
scout.instance("shows repo research tools when experimental scout is enabled", () =>
|
scout.instance("shows repo research tools when experimental scout is enabled", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const registry = yield* ToolRegistry.Service
|
const ids = yield* ToolRegistry.use.ids()
|
||||||
const ids = yield* registry.ids()
|
|
||||||
|
|
||||||
expect(ids).toContain("repo_clone")
|
expect(ids).toContain("repo_clone")
|
||||||
expect(ids).toContain("repo_overview")
|
expect(ids).toContain("repo_overview")
|
||||||
|
|
@ -133,8 +141,7 @@ describe("tool.registry", () => {
|
||||||
|
|
||||||
it.instance("hides task_status unless experimental background subagents are enabled", () =>
|
it.instance("hides task_status unless experimental background subagents are enabled", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const registry = yield* ToolRegistry.Service
|
const ids = yield* ToolRegistry.use.ids()
|
||||||
const ids = yield* registry.ids()
|
|
||||||
|
|
||||||
expect(ids).not.toContain("task_status")
|
expect(ids).not.toContain("task_status")
|
||||||
}),
|
}),
|
||||||
|
|
@ -142,11 +149,10 @@ describe("tool.registry", () => {
|
||||||
|
|
||||||
it.instance("hides task background parameter unless experimental background subagents are enabled", () =>
|
it.instance("hides task background parameter unless experimental background subagents are enabled", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const registry = yield* ToolRegistry.Service
|
|
||||||
const agent = yield* Agent.Service
|
const agent = yield* Agent.Service
|
||||||
const build = yield* agent.get("build")
|
const build = yield* agent.get("build")
|
||||||
if (!build) throw new Error("build agent not found")
|
if (!build) throw new Error("build agent not found")
|
||||||
const task = (yield* registry.tools({
|
const task = (yield* ToolRegistry.use.tools({
|
||||||
providerID: ProviderID.opencode,
|
providerID: ProviderID.opencode,
|
||||||
modelID: ModelID.make("test"),
|
modelID: ModelID.make("test"),
|
||||||
agent: build,
|
agent: build,
|
||||||
|
|
@ -159,8 +165,7 @@ describe("tool.registry", () => {
|
||||||
|
|
||||||
background.instance("shows task_status when experimental background subagents are enabled", () =>
|
background.instance("shows task_status when experimental background subagents are enabled", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const registry = yield* ToolRegistry.Service
|
const ids = yield* ToolRegistry.use.ids()
|
||||||
const ids = yield* registry.ids()
|
|
||||||
|
|
||||||
expect(ids).toContain("task_status")
|
expect(ids).toContain("task_status")
|
||||||
}),
|
}),
|
||||||
|
|
@ -169,26 +174,20 @@ describe("tool.registry", () => {
|
||||||
it.instance("loads tools from .opencode/tool (singular)", () =>
|
it.instance("loads tools from .opencode/tool (singular)", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const test = yield* TestInstance
|
const test = yield* TestInstance
|
||||||
const opencode = path.join(test.directory, ".opencode")
|
yield* AppFileSystem.use.writeWithDirs(
|
||||||
const tool = path.join(opencode, "tool")
|
path.join(test.directory, ".opencode", "tool", "hello.ts"),
|
||||||
yield* Effect.promise(() => fs.mkdir(tool, { recursive: true }))
|
[
|
||||||
yield* Effect.promise(() =>
|
"export default {",
|
||||||
Bun.write(
|
" description: 'hello tool',",
|
||||||
path.join(tool, "hello.ts"),
|
" args: {},",
|
||||||
[
|
" execute: async () => {",
|
||||||
"export default {",
|
" return 'hello world'",
|
||||||
" description: 'hello tool',",
|
" },",
|
||||||
" args: {},",
|
"}",
|
||||||
" execute: async () => {",
|
"",
|
||||||
" return 'hello world'",
|
].join("\n"),
|
||||||
" },",
|
|
||||||
"}",
|
|
||||||
"",
|
|
||||||
].join("\n"),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
const registry = yield* ToolRegistry.Service
|
const ids = yield* ToolRegistry.use.ids()
|
||||||
const ids = yield* registry.ids()
|
|
||||||
expect(ids).toContain("hello")
|
expect(ids).toContain("hello")
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
@ -196,25 +195,20 @@ describe("tool.registry", () => {
|
||||||
it.instance("ignores non-tool exports in .opencode/tool files", () =>
|
it.instance("ignores non-tool exports in .opencode/tool files", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const test = yield* TestInstance
|
const test = yield* TestInstance
|
||||||
const tool = path.join(test.directory, ".opencode", "tool")
|
yield* AppFileSystem.use.writeWithDirs(
|
||||||
yield* Effect.promise(() => fs.mkdir(tool, { recursive: true }))
|
path.join(test.directory, ".opencode", "tool", "mixed.ts"),
|
||||||
yield* Effect.promise(() =>
|
[
|
||||||
Bun.write(
|
"export const helper = 'not a tool'",
|
||||||
path.join(tool, "mixed.ts"),
|
"export default {",
|
||||||
[
|
" description: 'mixed tool',",
|
||||||
"export const helper = 'not a tool'",
|
" args: {},",
|
||||||
"export default {",
|
" execute: async () => 'ok',",
|
||||||
" description: 'mixed tool',",
|
"}",
|
||||||
" args: {},",
|
"",
|
||||||
" execute: async () => 'ok',",
|
].join("\n"),
|
||||||
"}",
|
|
||||||
"",
|
|
||||||
].join("\n"),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const registry = yield* ToolRegistry.Service
|
const ids = yield* ToolRegistry.use.ids()
|
||||||
const ids = yield* registry.ids()
|
|
||||||
expect(ids).toContain("mixed")
|
expect(ids).toContain("mixed")
|
||||||
expect(ids).not.toContain("mixed_helper")
|
expect(ids).not.toContain("mixed_helper")
|
||||||
}),
|
}),
|
||||||
|
|
@ -229,28 +223,23 @@ describe("tool.registry", () => {
|
||||||
it.instance("tolerates a custom tool exporting null/undefined args (no-args fallback)", () =>
|
it.instance("tolerates a custom tool exporting null/undefined args (no-args fallback)", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const test = yield* TestInstance
|
const test = yield* TestInstance
|
||||||
const tool = path.join(test.directory, ".opencode", "tool")
|
yield* AppFileSystem.use.writeWithDirs(
|
||||||
yield* Effect.promise(() => fs.mkdir(tool, { recursive: true }))
|
path.join(test.directory, ".opencode", "tool", "noargs.ts"),
|
||||||
yield* Effect.promise(() =>
|
[
|
||||||
Bun.write(
|
"export default {",
|
||||||
path.join(tool, "noargs.ts"),
|
" description: 'tool with no args',",
|
||||||
[
|
" args: undefined,",
|
||||||
"export default {",
|
" execute: async () => 'ok',",
|
||||||
" description: 'tool with no args',",
|
"}",
|
||||||
" args: undefined,",
|
"",
|
||||||
" execute: async () => 'ok',",
|
].join("\n"),
|
||||||
"}",
|
|
||||||
"",
|
|
||||||
].join("\n"),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const registry = yield* ToolRegistry.Service
|
const ids = yield* ToolRegistry.use.ids()
|
||||||
const ids = yield* registry.ids()
|
|
||||||
// Built-in tools must still load — a single malformed custom tool must
|
// Built-in tools must still load — a single malformed custom tool must
|
||||||
// not poison the whole registry.
|
// not poison the whole registry.
|
||||||
expect(ids).toContain("read")
|
expect(ids).toContain("read")
|
||||||
const loaded = (yield* registry.all()).find((t) => t.id === "noargs")
|
const loaded = (yield* ToolRegistry.use.all()).find((t) => t.id === "noargs")
|
||||||
if (!loaded) throw new Error("noargs tool was not loaded")
|
if (!loaded) throw new Error("noargs tool was not loaded")
|
||||||
expect(loaded.jsonSchema).toMatchObject({ type: "object", properties: {} })
|
expect(loaded.jsonSchema).toMatchObject({ type: "object", properties: {} })
|
||||||
}),
|
}),
|
||||||
|
|
@ -264,8 +253,7 @@ describe("tool.registry", () => {
|
||||||
// protection.
|
// protection.
|
||||||
withBrokenPlugin.instance("tolerates a plugin tool registered with null/undefined args", () =>
|
withBrokenPlugin.instance("tolerates a plugin tool registered with null/undefined args", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const registry = yield* ToolRegistry.Service
|
const ids = yield* ToolRegistry.use.ids()
|
||||||
const ids = yield* registry.ids()
|
|
||||||
expect(ids).toContain("read")
|
expect(ids).toContain("read")
|
||||||
expect(ids).toContain("broken_plugin_tool")
|
expect(ids).toContain("broken_plugin_tool")
|
||||||
}),
|
}),
|
||||||
|
|
@ -274,26 +262,20 @@ describe("tool.registry", () => {
|
||||||
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
|
||||||
const opencode = path.join(test.directory, ".opencode")
|
yield* AppFileSystem.use.writeWithDirs(
|
||||||
const tools = path.join(opencode, "tools")
|
path.join(test.directory, ".opencode", "tools", "hello.ts"),
|
||||||
yield* Effect.promise(() => fs.mkdir(tools, { recursive: true }))
|
[
|
||||||
yield* Effect.promise(() =>
|
"export default {",
|
||||||
Bun.write(
|
" description: 'hello tool',",
|
||||||
path.join(tools, "hello.ts"),
|
" args: {},",
|
||||||
[
|
" execute: async () => {",
|
||||||
"export default {",
|
" return 'hello world'",
|
||||||
" description: 'hello tool',",
|
" },",
|
||||||
" args: {},",
|
"}",
|
||||||
" execute: async () => {",
|
"",
|
||||||
" return 'hello world'",
|
].join("\n"),
|
||||||
" },",
|
|
||||||
"}",
|
|
||||||
"",
|
|
||||||
].join("\n"),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
const registry = yield* ToolRegistry.Service
|
const ids = yield* ToolRegistry.use.ids()
|
||||||
const ids = yield* registry.ids()
|
|
||||||
expect(ids).toContain("hello")
|
expect(ids).toContain("hello")
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
@ -301,26 +283,21 @@ describe("tool.registry", () => {
|
||||||
it.instance("loads Zod-schema custom tools with JSON Schema and validation", () =>
|
it.instance("loads Zod-schema custom tools with JSON Schema and validation", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const test = yield* TestInstance
|
const test = yield* TestInstance
|
||||||
const customTools = path.join(test.directory, ".opencode", "tools")
|
|
||||||
const pluginTool = pathToFileURL(path.resolve(import.meta.dir, "../../../plugin/src/tool.ts")).href
|
const pluginTool = pathToFileURL(path.resolve(import.meta.dir, "../../../plugin/src/tool.ts")).href
|
||||||
yield* Effect.promise(() => fs.mkdir(customTools, { recursive: true }))
|
yield* AppFileSystem.use.writeWithDirs(
|
||||||
yield* Effect.promise(() =>
|
path.join(test.directory, ".opencode", "tools", "sql.ts"),
|
||||||
Bun.write(
|
[
|
||||||
path.join(customTools, "sql.ts"),
|
`import { tool } from ${JSON.stringify(pluginTool)}`,
|
||||||
[
|
"export default tool({",
|
||||||
`import { tool } from ${JSON.stringify(pluginTool)}`,
|
" description: 'query database',",
|
||||||
"export default tool({",
|
" args: { query: tool.schema.string().describe('SQL query to execute') },",
|
||||||
" description: 'query database',",
|
" execute: async ({ query }) => query,",
|
||||||
" args: { query: tool.schema.string().describe('SQL query to execute') },",
|
"})",
|
||||||
" execute: async ({ query }) => query,",
|
"",
|
||||||
"})",
|
].join("\n"),
|
||||||
"",
|
|
||||||
].join("\n"),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const registry = yield* ToolRegistry.Service
|
const loaded = (yield* ToolRegistry.use.all()).find((tool) => tool.id === "sql")
|
||||||
const loaded = (yield* registry.all()).find((tool) => tool.id === "sql")
|
|
||||||
if (!loaded) throw new Error("custom sql tool was not loaded")
|
if (!loaded) throw new Error("custom sql tool was not loaded")
|
||||||
expect(loaded?.jsonSchema).toMatchObject({
|
expect(loaded?.jsonSchema).toMatchObject({
|
||||||
type: "object",
|
type: "object",
|
||||||
|
|
@ -333,7 +310,7 @@ describe("tool.registry", () => {
|
||||||
expect(Result.isSuccess(Schema.decodeUnknownResult(loaded.parameters)({}))).toBe(false)
|
expect(Result.isSuccess(Schema.decodeUnknownResult(loaded.parameters)({}))).toBe(false)
|
||||||
|
|
||||||
const agents = yield* Agent.Service
|
const agents = yield* Agent.Service
|
||||||
const promptTools = yield* registry.tools({
|
const promptTools = yield* ToolRegistry.use.tools({
|
||||||
providerID: ProviderID.opencode,
|
providerID: ProviderID.opencode,
|
||||||
modelID: ModelID.make("test"),
|
modelID: ModelID.make("test"),
|
||||||
agent: yield* agents.defaultInfo(),
|
agent: yield* agents.defaultInfo(),
|
||||||
|
|
@ -357,53 +334,44 @@ describe("tool.registry", () => {
|
||||||
const opencode = path.join(test.directory, ".opencode")
|
const opencode = path.join(test.directory, ".opencode")
|
||||||
const customTools = path.join(opencode, "tools")
|
const customTools = path.join(opencode, "tools")
|
||||||
const plugin = path.join(opencode, "node_modules", "@opencode-ai", "plugin")
|
const plugin = path.join(opencode, "node_modules", "@opencode-ai", "plugin")
|
||||||
yield* Effect.promise(() => fs.mkdir(path.join(plugin, "dist"), { recursive: true }))
|
|
||||||
yield* Effect.promise(() => fs.mkdir(customTools, { recursive: true }))
|
|
||||||
yield* Effect.promise(() =>
|
yield* Effect.promise(() =>
|
||||||
fs.cp(path.dirname(fileURLToPath(import.meta.resolve("zod"))), path.join(opencode, "node_modules", "zod"), {
|
fs.cp(path.dirname(fileURLToPath(import.meta.resolve("zod"))), path.join(opencode, "node_modules", "zod"), {
|
||||||
dereference: true,
|
dereference: true,
|
||||||
recursive: true,
|
recursive: true,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
yield* Effect.promise(() =>
|
yield* AppFileSystem.use.writeWithDirs(
|
||||||
Bun.write(
|
path.join(plugin, "package.json"),
|
||||||
path.join(plugin, "package.json"),
|
JSON.stringify({ name: "@opencode-ai/plugin", type: "module", exports: { ".": "./dist/index.js" } }),
|
||||||
JSON.stringify({ name: "@opencode-ai/plugin", type: "module", exports: { ".": "./dist/index.js" } }),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
yield* Effect.promise(() =>
|
yield* AppFileSystem.use.writeWithDirs(
|
||||||
Bun.write(
|
path.join(plugin, "dist", "index.js"),
|
||||||
path.join(plugin, "dist", "index.js"),
|
[
|
||||||
[
|
"import { z } from 'zod'",
|
||||||
"import { z } from 'zod'",
|
"export function tool(input) {",
|
||||||
"export function tool(input) {",
|
" return input",
|
||||||
" return input",
|
"}",
|
||||||
"}",
|
"tool.schema = z",
|
||||||
"tool.schema = z",
|
"",
|
||||||
"",
|
].join("\n"),
|
||||||
].join("\n"),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
yield* Effect.promise(() =>
|
yield* AppFileSystem.use.writeWithDirs(
|
||||||
Bun.write(
|
path.join(customTools, "addition.ts"),
|
||||||
path.join(customTools, "addition.ts"),
|
[
|
||||||
[
|
'import { tool } from "@opencode-ai/plugin"',
|
||||||
'import { tool } from "@opencode-ai/plugin"',
|
"export default tool({",
|
||||||
"export default tool({",
|
" description: 'Use this tool to add two numbers and return their sum.',",
|
||||||
" description: 'Use this tool to add two numbers and return their sum.',",
|
" args: {",
|
||||||
" args: {",
|
" left: tool.schema.number().describe('The first number to add'),",
|
||||||
" left: tool.schema.number().describe('The first number to add'),",
|
" right: tool.schema.number().describe('The second number to add'),",
|
||||||
" right: tool.schema.number().describe('The second number to add'),",
|
" },",
|
||||||
" },",
|
" execute: async (args) => `${args.left} + ${args.right} = ${args.left + args.right}`,",
|
||||||
" execute: async (args) => `${args.left} + ${args.right} = ${args.left + args.right}`,",
|
"})",
|
||||||
"})",
|
"",
|
||||||
"",
|
].join("\n"),
|
||||||
].join("\n"),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const registry = yield* ToolRegistry.Service
|
const loaded = (yield* ToolRegistry.use.all()).find((tool) => tool.id === "addition")
|
||||||
const loaded = (yield* registry.all()).find((tool) => tool.id === "addition")
|
|
||||||
if (!loaded) throw new Error("custom addition tool was not loaded")
|
if (!loaded) throw new Error("custom addition tool was not loaded")
|
||||||
|
|
||||||
expect(ToolJsonSchema.fromTool(loaded)).toMatchObject({
|
expect(ToolJsonSchema.fromTool(loaded)).toMatchObject({
|
||||||
|
|
@ -419,29 +387,24 @@ describe("tool.registry", () => {
|
||||||
it.instance("preserves attachments from structured custom tool results", () =>
|
it.instance("preserves attachments from structured custom tool results", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const test = yield* TestInstance
|
const test = yield* TestInstance
|
||||||
const customTools = path.join(test.directory, ".opencode", "tools")
|
|
||||||
const pluginTool = pathToFileURL(path.resolve(import.meta.dir, "../../../plugin/src/tool.ts")).href
|
const pluginTool = pathToFileURL(path.resolve(import.meta.dir, "../../../plugin/src/tool.ts")).href
|
||||||
yield* Effect.promise(() => fs.mkdir(customTools, { recursive: true }))
|
yield* AppFileSystem.use.writeWithDirs(
|
||||||
yield* Effect.promise(() =>
|
path.join(test.directory, ".opencode", "tools", "image.ts"),
|
||||||
Bun.write(
|
[
|
||||||
path.join(customTools, "image.ts"),
|
`import { tool } from ${JSON.stringify(pluginTool)}`,
|
||||||
[
|
"export default tool({",
|
||||||
`import { tool } from ${JSON.stringify(pluginTool)}`,
|
" description: 'image tool',",
|
||||||
"export default tool({",
|
" args: {},",
|
||||||
" description: 'image tool',",
|
" execute: async () => ({",
|
||||||
" args: {},",
|
" output: 'here is an image',",
|
||||||
" execute: async () => ({",
|
" attachments: [{ type: 'file', mime: 'image/png', filename: 'picture.png', url: 'data:image/png;base64,AAAA' }],",
|
||||||
" output: 'here is an image',",
|
" }),",
|
||||||
" attachments: [{ type: 'file', mime: 'image/png', filename: 'picture.png', url: 'data:image/png;base64,AAAA' }],",
|
"})",
|
||||||
" }),",
|
"",
|
||||||
"})",
|
].join("\n"),
|
||||||
"",
|
|
||||||
].join("\n"),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const registry = yield* ToolRegistry.Service
|
const loaded = (yield* ToolRegistry.use.all()).find((tool) => tool.id === "image")
|
||||||
const loaded = (yield* registry.all()).find((tool) => tool.id === "image")
|
|
||||||
if (!loaded) throw new Error("custom image tool was not loaded")
|
if (!loaded) throw new Error("custom image tool was not loaded")
|
||||||
const agents = yield* Agent.Service
|
const agents = yield* Agent.Service
|
||||||
const result = yield* loaded.execute({}, {
|
const result = yield* loaded.execute({}, {
|
||||||
|
|
@ -464,24 +427,19 @@ describe("tool.registry", () => {
|
||||||
it.instance("loads legacy JSON-schema-shaped custom tools with wire schema", () =>
|
it.instance("loads legacy JSON-schema-shaped custom tools with wire schema", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const test = yield* TestInstance
|
const test = yield* TestInstance
|
||||||
const tools = path.join(test.directory, ".opencode", "tools")
|
yield* AppFileSystem.use.writeWithDirs(
|
||||||
yield* Effect.promise(() => fs.mkdir(tools, { recursive: true }))
|
path.join(test.directory, ".opencode", "tools", "legacy.ts"),
|
||||||
yield* Effect.promise(() =>
|
[
|
||||||
Bun.write(
|
"export default {",
|
||||||
path.join(tools, "legacy.ts"),
|
" description: 'legacy schema tool',",
|
||||||
[
|
" args: { text: { type: 'string', description: 'Text to render' } },",
|
||||||
"export default {",
|
" execute: async ({ text }) => text,",
|
||||||
" description: 'legacy schema tool',",
|
"}",
|
||||||
" args: { text: { type: 'string', description: 'Text to render' } },",
|
"",
|
||||||
" execute: async ({ text }) => text,",
|
].join("\n"),
|
||||||
"}",
|
|
||||||
"",
|
|
||||||
].join("\n"),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const registry = yield* ToolRegistry.Service
|
const loaded = (yield* ToolRegistry.use.all()).find((tool) => tool.id === "legacy")
|
||||||
const loaded = (yield* registry.all()).find((tool) => tool.id === "legacy")
|
|
||||||
if (!loaded) throw new Error("legacy custom tool was not loaded")
|
if (!loaded) throw new Error("legacy custom tool was not loaded")
|
||||||
expect(ToolJsonSchema.fromTool(loaded)).toMatchObject({
|
expect(ToolJsonSchema.fromTool(loaded)).toMatchObject({
|
||||||
type: "object",
|
type: "object",
|
||||||
|
|
@ -498,73 +456,60 @@ describe("tool.registry", () => {
|
||||||
const test = yield* TestInstance
|
const test = yield* TestInstance
|
||||||
const opencode = path.join(test.directory, ".opencode")
|
const opencode = path.join(test.directory, ".opencode")
|
||||||
const tools = path.join(opencode, "tools")
|
const tools = path.join(opencode, "tools")
|
||||||
yield* Effect.promise(() => fs.mkdir(tools, { recursive: true }))
|
yield* AppFileSystem.use.writeWithDirs(
|
||||||
yield* Effect.promise(() =>
|
path.join(opencode, "package.json"),
|
||||||
Bun.write(
|
JSON.stringify({
|
||||||
path.join(opencode, "package.json"),
|
name: "custom-tools",
|
||||||
JSON.stringify({
|
dependencies: {
|
||||||
name: "custom-tools",
|
"@opencode-ai/plugin": "^0.0.0",
|
||||||
dependencies: {
|
cowsay: "^1.6.0",
|
||||||
"@opencode-ai/plugin": "^0.0.0",
|
},
|
||||||
cowsay: "^1.6.0",
|
}),
|
||||||
},
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
yield* Effect.promise(() =>
|
yield* AppFileSystem.use.writeWithDirs(
|
||||||
Bun.write(
|
path.join(opencode, "package-lock.json"),
|
||||||
path.join(opencode, "package-lock.json"),
|
JSON.stringify({
|
||||||
JSON.stringify({
|
name: "custom-tools",
|
||||||
name: "custom-tools",
|
lockfileVersion: 3,
|
||||||
lockfileVersion: 3,
|
packages: {
|
||||||
packages: {
|
"": {
|
||||||
"": {
|
dependencies: {
|
||||||
dependencies: {
|
"@opencode-ai/plugin": "^0.0.0",
|
||||||
"@opencode-ai/plugin": "^0.0.0",
|
cowsay: "^1.6.0",
|
||||||
cowsay: "^1.6.0",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
},
|
||||||
),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
const cowsay = path.join(opencode, "node_modules", "cowsay")
|
const cowsay = path.join(opencode, "node_modules", "cowsay")
|
||||||
yield* Effect.promise(() => fs.mkdir(cowsay, { recursive: true }))
|
yield* AppFileSystem.use.writeWithDirs(
|
||||||
yield* Effect.promise(() =>
|
path.join(cowsay, "package.json"),
|
||||||
Bun.write(
|
JSON.stringify({
|
||||||
path.join(cowsay, "package.json"),
|
name: "cowsay",
|
||||||
JSON.stringify({
|
type: "module",
|
||||||
name: "cowsay",
|
exports: "./index.js",
|
||||||
type: "module",
|
}),
|
||||||
exports: "./index.js",
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
yield* Effect.promise(() =>
|
yield* AppFileSystem.use.writeWithDirs(
|
||||||
Bun.write(
|
path.join(cowsay, "index.js"),
|
||||||
path.join(cowsay, "index.js"),
|
["export function say({ text }) {", " return `moo ${text}`", "}", ""].join("\n"),
|
||||||
["export function say({ text }) {", " return `moo ${text}`", "}", ""].join("\n"),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
yield* Effect.promise(() =>
|
yield* AppFileSystem.use.writeWithDirs(
|
||||||
Bun.write(
|
path.join(tools, "cowsay.ts"),
|
||||||
path.join(tools, "cowsay.ts"),
|
[
|
||||||
[
|
"import { say } from 'cowsay'",
|
||||||
"import { say } from 'cowsay'",
|
"export default {",
|
||||||
"export default {",
|
" description: 'tool that imports cowsay at top level',",
|
||||||
" description: 'tool that imports cowsay at top level',",
|
" args: { text: { type: 'string' } },",
|
||||||
" args: { text: { type: 'string' } },",
|
" execute: async ({ text }: { text: string }) => {",
|
||||||
" execute: async ({ text }: { text: string }) => {",
|
" return say({ text })",
|
||||||
" return say({ text })",
|
" },",
|
||||||
" },",
|
"}",
|
||||||
"}",
|
"",
|
||||||
"",
|
].join("\n"),
|
||||||
].join("\n"),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
const registry = yield* ToolRegistry.Service
|
const ids = yield* ToolRegistry.use.ids()
|
||||||
const ids = yield* registry.ids()
|
|
||||||
expect(ids).toContain("cowsay")
|
expect(ids).toContain("cowsay")
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue