import { describe, expect, test } from "bun:test" import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { Effect, Layer } from "effect" import type { Agent } from "../../src/agent/agent" import { NamedError } from "@opencode-ai/core/util/error" import { Skill } from "../../src/skill" import { Permission } from "../../src/permission" import type { Provider } from "../../src/provider/provider" import { SystemPrompt } from "../../src/session/system" import { MCP } from "../../src/mcp" import { testEffect } from "../lib/effect" const skills: Skill.Info[] = [ { name: "zeta-skill", description: "Zeta skill.", location: "/tmp/zeta-skill/SKILL.md", content: "# zeta-skill", }, { name: "alpha-skill", description: "Alpha skill.", location: "/tmp/alpha-skill/SKILL.md", content: "# alpha-skill", }, { name: "middle-skill", description: "Middle skill.", location: "/tmp/middle-skill/SKILL.md", content: "# middle-skill", }, { name: "manual-skill", location: "/tmp/manual-skill/SKILL.md", content: "# manual-skill", }, ] const build: Agent.Info = { name: "build", mode: "primary", permission: Permission.fromConfig({ "*": "allow" }), options: {}, } const it = testEffect( LayerNode.compile(SystemPrompt.node, [ [ MCP.node, Layer.mock(MCP.Service, { instructions: () => Effect.succeed([ { name: "guide-server", instructions: "Use lookup before mutate.", tools: [], }, { name: "tool-server", instructions: "Prefer search before update.", tools: ["tool-server_search", "tool-server_update"], }, ]), }), ], [ Skill.node, Layer.succeed( Skill.Service, Skill.Service.of({ get: (name) => Effect.succeed(skills.find((skill) => skill.name === name)), require: (name) => { const info = skills.find((skill) => skill.name === name) if (info) return Effect.succeed(info) return Effect.fail(new Skill.NotFoundError({ name, available: skills.map((skill) => skill.name) })) }, all: () => Effect.succeed(skills), dirs: () => Effect.succeed([]), available: () => Effect.succeed(skills), }), ), ], ]), ) describe("session.system", () => { test("selects the Meta prompt for Muse Spark model IDs", () => { expect(SystemPrompt.provider({ api: { id: "meta/muse-spark-preview" } } as Provider.Model)[0]).toContain( "Meta Muse Spark", ) }) it.effect("skills output is sorted by name and stable across calls", () => Effect.gen(function* () { const prompt = yield* SystemPrompt.Service const first = yield* prompt.skills(build) const second = yield* prompt.skills(build) const output = first ?? (yield* Effect.fail(new NamedError.Unknown({ message: "missing skills output" }))) expect(first).toBe(second) const alpha = output.indexOf("alpha-skill") const middle = output.indexOf("middle-skill") const zeta = output.indexOf("zeta-skill") expect(alpha).toBeGreaterThan(-1) expect(middle).toBeGreaterThan(alpha) expect(zeta).toBeGreaterThan(middle) expect(output).not.toContain("manual-skill") }), ) it.effect("MCP output includes connected server instructions", () => Effect.gen(function* () { const prompt = yield* SystemPrompt.Service const output = yield* prompt.mcp(build) expect(output).toBe( [ "", ' ', " Use lookup before mutate.", " ", ' ', " Prefer search before update.", " ", "", ].join("\n"), ) }), ) it.effect("MCP output omits servers when all advertised tools are denied", () => Effect.gen(function* () { const prompt = yield* SystemPrompt.Service const output = yield* prompt.mcp(build, Permission.fromConfig({ "tool-server_*": "deny" })) expect(output).toBe( [ "", ' ', " Use lookup before mutate.", " ", "", ].join("\n"), ) }), ) })