| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134 |
- import { PermissionV1 } from "@kirincode-ai/core/v1/permission"
- import { CrossSpawnSpawner } from "@kirincode-ai/core/cross-spawn-spawner"
- import { LayerNode } from "@kirincode-ai/core/effect/layer-node"
- import { Ripgrep } from "@kirincode-ai/core/ripgrep"
- import { Cause, Effect, Exit, Layer } from "effect"
- import { afterEach, describe, expect } from "bun:test"
- import path from "path"
- import type { Permission } from "../../src/permission"
- import type { Tool } from "@/tool/tool"
- import { SkillTool } from "../../src/tool/skill"
- import { ToolRegistry } from "@/tool/registry"
- import { disposeAllInstances, TestInstance } from "../fixture/fixture"
- import { SessionID, MessageID } from "../../src/session/schema"
- import { testEffect } from "../lib/effect"
- const baseCtx: Omit<Tool.Context, "ask"> = {
- sessionID: SessionID.make("ses_test"),
- messageID: MessageID.make("msg_test"),
- callID: "",
- agent: "build",
- abort: AbortSignal.any([]),
- messages: [],
- metadata: () => Effect.void,
- }
- afterEach(async () => {
- await disposeAllInstances()
- })
- const it = testEffect(LayerNode.compile(LayerNode.group([ToolRegistry.node, CrossSpawnSpawner.node, Ripgrep.node])))
- describe("tool.skill", () => {
- it.instance("execute returns skill content block with files", () =>
- Effect.gen(function* () {
- const dir = (yield* TestInstance).directory
- const skill = path.join(dir, ".kirincode", "skill", "tool-skill")
- yield* Effect.promise(() =>
- Bun.write(
- path.join(skill, "SKILL.md"),
- `---
- name: tool-skill
- description: Skill for tool tests.
- ---
- # Tool Skill
- Use this skill.
- `,
- ),
- )
- yield* Effect.promise(() => Bun.write(path.join(skill, "scripts", "demo.txt"), "demo"))
- const home = process.env.KIRINCODE_TEST_HOME
- process.env.KIRINCODE_TEST_HOME = dir
- yield* Effect.addFinalizer(() =>
- Effect.sync(() => {
- process.env.KIRINCODE_TEST_HOME = home
- }),
- )
- const registry = yield* ToolRegistry.Service
- const agent = { name: "build", mode: "primary" as const, permission: [], options: {} }
- const tool = (yield* registry.tools({
- providerID: "kirincode" as any,
- modelID: "gpt-5" as any,
- agent,
- })).find((tool) => tool.id === SkillTool.id)
- if (!tool) throw new Error("Skill tool not found")
- expect(tool.description).not.toContain("tool-skill")
- expect(tool.description).not.toContain("Skill for tool tests.")
- const requests: Array<Omit<PermissionV1.Request, "id" | "sessionID" | "tool">> = []
- const ctx: Tool.Context = {
- ...baseCtx,
- ask: (req) =>
- Effect.sync(() => {
- requests.push(req)
- }),
- }
- const result = yield* tool.execute({ name: "tool-skill" }, ctx)
- const file = path.resolve(skill, "scripts", "demo.txt")
- expect(requests.length).toBe(1)
- expect(requests[0].permission).toBe("skill")
- expect(requests[0].patterns).toContain("tool-skill")
- expect(requests[0].always).toContain("tool-skill")
- expect(result.metadata.dir).toBe(skill)
- expect(result.output).toContain(`<skill_content name="tool-skill">`)
- expect(result.output).toContain(`Base directory for this skill: ${skill}`)
- expect(result.output).toContain(`<file>${file}</file>`)
- }),
- )
- it.instance("execute preserves not found message", () =>
- Effect.gen(function* () {
- const dir = (yield* TestInstance).directory
- const home = process.env.KIRINCODE_TEST_HOME
- process.env.KIRINCODE_TEST_HOME = dir
- yield* Effect.addFinalizer(() =>
- Effect.sync(() => {
- process.env.KIRINCODE_TEST_HOME = home
- }),
- )
- const registry = yield* ToolRegistry.Service
- const agent = { name: "build", mode: "primary" as const, permission: [], options: {} }
- const tool = (yield* registry.tools({
- providerID: "kirincode" as any,
- modelID: "gpt-5" as any,
- agent,
- })).find((tool) => tool.id === SkillTool.id)
- if (!tool) throw new Error("Skill tool not found")
- const exit = yield* tool
- .execute(
- { name: "missing-skill" },
- {
- ...baseCtx,
- ask: () => Effect.void,
- },
- )
- .pipe(Effect.exit)
- expect(Exit.isFailure(exit)).toBe(true)
- if (Exit.isFailure(exit)) {
- const error = Cause.squash(exit.cause)
- expect(error).toBeInstanceOf(Error)
- if (error instanceof Error) expect(error.message).toContain('Skill "missing-skill" not found.')
- }
- }),
- )
- })
|