| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208 |
- import { afterEach, describe, expect, test } from "bun:test"
- import path from "path"
- import fs from "fs/promises"
- import { Effect, Layer } from "effect"
- import { Instance } from "../../src/project/instance"
- import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner"
- import { ToolRegistry } from "../../src/tool/registry"
- import { provideTmpdirInstance, tmpdir } from "../fixture/fixture"
- import { testEffect } from "../lib/effect"
- const node = CrossSpawnSpawner.defaultLayer
- const it = testEffect(Layer.mergeAll(ToolRegistry.defaultLayer, node))
- afterEach(async () => {
- await Instance.disposeAll()
- })
- describe("tool.registry", () => {
- // kilocode_change start - plan_exit is always registered
- it.live("plan_exit is always registered regardless of client", () =>
- Effect.gen(function* () {
- const original = process.env["KILO_CLIENT"]
- try {
- for (const client of ["cli", "vscode", "desktop", "app"]) {
- process.env["KILO_CLIENT"] = client
- yield* provideTmpdirInstance(
- () =>
- Effect.gen(function* () {
- const registry = yield* ToolRegistry.Service
- const ids = yield* registry.ids()
- expect(ids).toContain("plan_exit")
- }),
- { git: true },
- )
- }
- } finally {
- if (original === undefined) delete process.env["KILO_CLIENT"]
- else process.env["KILO_CLIENT"] = original
- }
- }),
- )
- // kilocode_change end
- // kilocode_change start
- test("suggest is registered for cli and vscode only", async () => {
- const original = process.env["KILO_CLIENT"]
- const originalQuestion = process.env["KILO_ENABLE_QUESTION_TOOL"]
- const originalConfig = process.env["KILO_CONFIG_DIR"]
- try {
- for (const client of ["cli", "vscode", "desktop", "app"]) {
- process.env["KILO_CLIENT"] = client
- process.env["KILO_ENABLE_QUESTION_TOOL"] = client === "vscode" ? "true" : "false"
- await using tmp = await tmpdir({ git: true })
- process.env["KILO_CONFIG_DIR"] = tmp.path
- await Instance.provide({
- directory: tmp.path,
- fn: async () => {
- const ids = await ToolRegistry.ids()
- if (client === "cli" || client === "vscode") expect(ids).toContain("suggest")
- else expect(ids).not.toContain("suggest")
- },
- })
- }
- } finally {
- if (original === undefined) delete process.env["KILO_CLIENT"]
- else process.env["KILO_CLIENT"] = original
- if (originalQuestion === undefined) delete process.env["KILO_ENABLE_QUESTION_TOOL"]
- else process.env["KILO_ENABLE_QUESTION_TOOL"] = originalQuestion
- if (originalConfig === undefined) delete process.env["KILO_CONFIG_DIR"]
- else process.env["KILO_CONFIG_DIR"] = originalConfig
- }
- })
- // kilocode_change end
- 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"),
- ),
- )
- const registry = yield* ToolRegistry.Service
- const ids = yield* registry.ids()
- expect(ids).toContain("hello")
- }),
- ),
- )
- 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"),
- ),
- )
- const registry = yield* ToolRegistry.Service
- const ids = yield* registry.ids()
- expect(ids).toContain("hello")
- }),
- ),
- )
- 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: {
- "@kilocode/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: {
- "@kilocode/plugin": "^0.0.0",
- cowsay: "^1.6.0",
- },
- },
- },
- }),
- ),
- )
- 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",
- }),
- ),
- )
- yield* Effect.promise(() =>
- Bun.write(
- path.join(cowsay, "index.js"),
- ["export function say({ text }) {", " return `moo ${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"),
- ),
- )
- const registry = yield* ToolRegistry.Service
- const ids = yield* registry.ids()
- expect(ids).toContain("cowsay")
- }),
- ),
- )
- })
|