|
|
@@ -1,157 +1,154 @@
|
|
|
-import { afterEach, describe, expect, test } from "bun:test"
|
|
|
+import { NodeChildProcessSpawner, NodeFileSystem, NodePath } from "@effect/platform-node"
|
|
|
+import { afterEach, describe, expect } from "bun:test"
|
|
|
import path from "path"
|
|
|
import fs from "fs/promises"
|
|
|
-import { tmpdir } from "../fixture/fixture"
|
|
|
+import { Effect, Layer } from "effect"
|
|
|
import { Instance } from "../../src/project/instance"
|
|
|
import { ToolRegistry } from "../../src/tool/registry"
|
|
|
+import { provideTmpdirInstance } from "../fixture/fixture"
|
|
|
+import { testEffect } from "../lib/effect"
|
|
|
+
|
|
|
+const node = NodeChildProcessSpawner.layer.pipe(
|
|
|
+ Layer.provideMerge(Layer.mergeAll(NodeFileSystem.layer, NodePath.layer)),
|
|
|
+)
|
|
|
+
|
|
|
+const it = testEffect(Layer.mergeAll(ToolRegistry.defaultLayer, node))
|
|
|
|
|
|
afterEach(async () => {
|
|
|
await Instance.disposeAll()
|
|
|
})
|
|
|
|
|
|
describe("tool.registry", () => {
|
|
|
- test("loads tools from .opencode/tool (singular)", async () => {
|
|
|
- await using tmp = await tmpdir({
|
|
|
- init: async (dir) => {
|
|
|
- const opencodeDir = path.join(dir, ".opencode")
|
|
|
- await fs.mkdir(opencodeDir, { recursive: true })
|
|
|
-
|
|
|
- const toolDir = path.join(opencodeDir, "tool")
|
|
|
- await fs.mkdir(toolDir, { recursive: true })
|
|
|
-
|
|
|
- await Bun.write(
|
|
|
- path.join(toolDir, "hello.ts"),
|
|
|
- [
|
|
|
- "export default {",
|
|
|
- " description: 'hello tool',",
|
|
|
- " args: {},",
|
|
|
- " execute: async () => {",
|
|
|
- " return 'hello world'",
|
|
|
- " },",
|
|
|
- "}",
|
|
|
- "",
|
|
|
- ].join("\n"),
|
|
|
+ it.live("loads tools from .opencode/tool (singular)", () =>
|
|
|
+ provideTmpdirInstance((dir) =>
|
|
|
+ Effect.gen(function* () {
|
|
|
+ const opencode = path.join(dir, ".opencode")
|
|
|
+ const tool = path.join(opencode, "tool")
|
|
|
+ yield* Effect.promise(() => fs.mkdir(tool, { recursive: true }))
|
|
|
+ yield* Effect.promise(() =>
|
|
|
+ Bun.write(
|
|
|
+ path.join(tool, "hello.ts"),
|
|
|
+ [
|
|
|
+ "export default {",
|
|
|
+ " description: 'hello tool',",
|
|
|
+ " args: {},",
|
|
|
+ " execute: async () => {",
|
|
|
+ " return 'hello world'",
|
|
|
+ " },",
|
|
|
+ "}",
|
|
|
+ "",
|
|
|
+ ].join("\n"),
|
|
|
+ ),
|
|
|
)
|
|
|
- },
|
|
|
- })
|
|
|
-
|
|
|
- await Instance.provide({
|
|
|
- directory: tmp.path,
|
|
|
- fn: async () => {
|
|
|
- const ids = await ToolRegistry.ids()
|
|
|
+ const registry = yield* ToolRegistry.Service
|
|
|
+ const ids = yield* registry.ids()
|
|
|
expect(ids).toContain("hello")
|
|
|
- },
|
|
|
- })
|
|
|
- })
|
|
|
-
|
|
|
- test("loads tools from .opencode/tools (plural)", async () => {
|
|
|
- await using tmp = await tmpdir({
|
|
|
- init: async (dir) => {
|
|
|
- const opencodeDir = path.join(dir, ".opencode")
|
|
|
- await fs.mkdir(opencodeDir, { recursive: true })
|
|
|
-
|
|
|
- const toolsDir = path.join(opencodeDir, "tools")
|
|
|
- await fs.mkdir(toolsDir, { recursive: true })
|
|
|
-
|
|
|
- await Bun.write(
|
|
|
- path.join(toolsDir, "hello.ts"),
|
|
|
- [
|
|
|
- "export default {",
|
|
|
- " description: 'hello tool',",
|
|
|
- " args: {},",
|
|
|
- " execute: async () => {",
|
|
|
- " return 'hello world'",
|
|
|
- " },",
|
|
|
- "}",
|
|
|
- "",
|
|
|
- ].join("\n"),
|
|
|
+ }),
|
|
|
+ ),
|
|
|
+ )
|
|
|
+
|
|
|
+ it.live("loads tools from .opencode/tools (plural)", () =>
|
|
|
+ provideTmpdirInstance((dir) =>
|
|
|
+ Effect.gen(function* () {
|
|
|
+ const opencode = path.join(dir, ".opencode")
|
|
|
+ const tools = path.join(opencode, "tools")
|
|
|
+ yield* Effect.promise(() => fs.mkdir(tools, { recursive: true }))
|
|
|
+ yield* Effect.promise(() =>
|
|
|
+ Bun.write(
|
|
|
+ path.join(tools, "hello.ts"),
|
|
|
+ [
|
|
|
+ "export default {",
|
|
|
+ " description: 'hello tool',",
|
|
|
+ " args: {},",
|
|
|
+ " execute: async () => {",
|
|
|
+ " return 'hello world'",
|
|
|
+ " },",
|
|
|
+ "}",
|
|
|
+ "",
|
|
|
+ ].join("\n"),
|
|
|
+ ),
|
|
|
)
|
|
|
- },
|
|
|
- })
|
|
|
-
|
|
|
- await Instance.provide({
|
|
|
- directory: tmp.path,
|
|
|
- fn: async () => {
|
|
|
- const ids = await ToolRegistry.ids()
|
|
|
+ const registry = yield* ToolRegistry.Service
|
|
|
+ const ids = yield* registry.ids()
|
|
|
expect(ids).toContain("hello")
|
|
|
- },
|
|
|
- })
|
|
|
- })
|
|
|
-
|
|
|
- test("loads tools with external dependencies without crashing", async () => {
|
|
|
- await using tmp = await tmpdir({
|
|
|
- init: async (dir) => {
|
|
|
- const opencodeDir = path.join(dir, ".opencode")
|
|
|
- await fs.mkdir(opencodeDir, { recursive: true })
|
|
|
-
|
|
|
- const toolsDir = path.join(opencodeDir, "tools")
|
|
|
- await fs.mkdir(toolsDir, { recursive: true })
|
|
|
-
|
|
|
- await Bun.write(
|
|
|
- path.join(opencodeDir, "package.json"),
|
|
|
- JSON.stringify({
|
|
|
- name: "custom-tools",
|
|
|
- dependencies: {
|
|
|
- "@opencode-ai/plugin": "^0.0.0",
|
|
|
- cowsay: "^1.6.0",
|
|
|
- },
|
|
|
- }),
|
|
|
+ }),
|
|
|
+ ),
|
|
|
+ )
|
|
|
+
|
|
|
+ it.live("loads tools with external dependencies without crashing", () =>
|
|
|
+ provideTmpdirInstance((dir) =>
|
|
|
+ Effect.gen(function* () {
|
|
|
+ const opencode = path.join(dir, ".opencode")
|
|
|
+ const tools = path.join(opencode, "tools")
|
|
|
+ yield* Effect.promise(() => fs.mkdir(tools, { recursive: true }))
|
|
|
+ yield* Effect.promise(() =>
|
|
|
+ Bun.write(
|
|
|
+ path.join(opencode, "package.json"),
|
|
|
+ JSON.stringify({
|
|
|
+ name: "custom-tools",
|
|
|
+ dependencies: {
|
|
|
+ "@opencode-ai/plugin": "^0.0.0",
|
|
|
+ cowsay: "^1.6.0",
|
|
|
+ },
|
|
|
+ }),
|
|
|
+ ),
|
|
|
)
|
|
|
-
|
|
|
- await Bun.write(
|
|
|
- path.join(opencodeDir, "package-lock.json"),
|
|
|
- JSON.stringify({
|
|
|
- name: "custom-tools",
|
|
|
- lockfileVersion: 3,
|
|
|
- packages: {
|
|
|
- "": {
|
|
|
- dependencies: {
|
|
|
- "@opencode-ai/plugin": "^0.0.0",
|
|
|
- cowsay: "^1.6.0",
|
|
|
+ yield* Effect.promise(() =>
|
|
|
+ Bun.write(
|
|
|
+ path.join(opencode, "package-lock.json"),
|
|
|
+ JSON.stringify({
|
|
|
+ name: "custom-tools",
|
|
|
+ lockfileVersion: 3,
|
|
|
+ packages: {
|
|
|
+ "": {
|
|
|
+ dependencies: {
|
|
|
+ "@opencode-ai/plugin": "^0.0.0",
|
|
|
+ cowsay: "^1.6.0",
|
|
|
+ },
|
|
|
},
|
|
|
},
|
|
|
- },
|
|
|
- }),
|
|
|
+ }),
|
|
|
+ ),
|
|
|
)
|
|
|
|
|
|
- const cowsayDir = path.join(opencodeDir, "node_modules", "cowsay")
|
|
|
- await fs.mkdir(cowsayDir, { recursive: true })
|
|
|
- await Bun.write(
|
|
|
- path.join(cowsayDir, "package.json"),
|
|
|
- JSON.stringify({
|
|
|
- name: "cowsay",
|
|
|
- type: "module",
|
|
|
- exports: "./index.js",
|
|
|
- }),
|
|
|
+ const cowsay = path.join(opencode, "node_modules", "cowsay")
|
|
|
+ yield* Effect.promise(() => fs.mkdir(cowsay, { recursive: true }))
|
|
|
+ yield* Effect.promise(() =>
|
|
|
+ Bun.write(
|
|
|
+ path.join(cowsay, "package.json"),
|
|
|
+ JSON.stringify({
|
|
|
+ name: "cowsay",
|
|
|
+ type: "module",
|
|
|
+ exports: "./index.js",
|
|
|
+ }),
|
|
|
+ ),
|
|
|
)
|
|
|
- await Bun.write(
|
|
|
- path.join(cowsayDir, "index.js"),
|
|
|
- ["export function say({ text }) {", " return `moo ${text}`", "}", ""].join("\n"),
|
|
|
+ yield* Effect.promise(() =>
|
|
|
+ Bun.write(
|
|
|
+ path.join(cowsay, "index.js"),
|
|
|
+ ["export function say({ text }) {", " return `moo ${text}`", "}", ""].join("\n"),
|
|
|
+ ),
|
|
|
)
|
|
|
-
|
|
|
- await Bun.write(
|
|
|
- path.join(toolsDir, "cowsay.ts"),
|
|
|
- [
|
|
|
- "import { say } from 'cowsay'",
|
|
|
- "export default {",
|
|
|
- " description: 'tool that imports cowsay at top level',",
|
|
|
- " args: { text: { type: 'string' } },",
|
|
|
- " execute: async ({ text }: { text: string }) => {",
|
|
|
- " return say({ text })",
|
|
|
- " },",
|
|
|
- "}",
|
|
|
- "",
|
|
|
- ].join("\n"),
|
|
|
+ yield* Effect.promise(() =>
|
|
|
+ Bun.write(
|
|
|
+ path.join(tools, "cowsay.ts"),
|
|
|
+ [
|
|
|
+ "import { say } from 'cowsay'",
|
|
|
+ "export default {",
|
|
|
+ " description: 'tool that imports cowsay at top level',",
|
|
|
+ " args: { text: { type: 'string' } },",
|
|
|
+ " execute: async ({ text }: { text: string }) => {",
|
|
|
+ " return say({ text })",
|
|
|
+ " },",
|
|
|
+ "}",
|
|
|
+ "",
|
|
|
+ ].join("\n"),
|
|
|
+ ),
|
|
|
)
|
|
|
- },
|
|
|
- })
|
|
|
-
|
|
|
- await Instance.provide({
|
|
|
- directory: tmp.path,
|
|
|
- fn: async () => {
|
|
|
- const ids = await ToolRegistry.ids()
|
|
|
+ const registry = yield* ToolRegistry.Service
|
|
|
+ const ids = yield* registry.ids()
|
|
|
expect(ids).toContain("cowsay")
|
|
|
- },
|
|
|
- })
|
|
|
- })
|
|
|
+ }),
|
|
|
+ ),
|
|
|
+ )
|
|
|
})
|