mirror of
https://github.com/anomalyco/opencode.git
synced 2026-07-21 18:26:09 +00:00
test(core): cover mcp instruction producer (#37303)
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
import { describe, expect } from "bun:test"
|
||||
import { Effect, Layer } from "effect"
|
||||
import { AgentV2 } from "@opencode-ai/core/agent"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { MCP } from "@opencode-ai/core/mcp/index"
|
||||
import { McpInstructions } from "@opencode-ai/core/mcp/instructions"
|
||||
import { PermissionV2 } from "@opencode-ai/core/permission"
|
||||
import { McpTool } from "@opencode-ai/core/tool/mcp"
|
||||
import { it } from "./lib/effect"
|
||||
import { readInitial, readUpdate } from "./lib/instructions"
|
||||
|
||||
const build = AgentV2.ID.make("build")
|
||||
|
||||
const selection = (permissions: PermissionV2.Ruleset = []) => {
|
||||
const info = AgentV2.Info.make({ ...AgentV2.Info.empty(build), permissions })
|
||||
return { id: info.id, info }
|
||||
}
|
||||
|
||||
const instructions = (server: string, text: string) =>
|
||||
new MCP.ServerInstructions({ server: MCP.ServerName.make(server), instructions: text })
|
||||
|
||||
const tool = (server: string, name = "search") => new MCP.Tool({ server: MCP.ServerName.make(server), name })
|
||||
|
||||
const layer = (catalog: () => MCP.ServerInstructions[], tools: () => MCP.Tool[]) =>
|
||||
AppNodeBuilder.build(McpInstructions.node, [
|
||||
[
|
||||
MCP.node,
|
||||
Layer.mock(MCP.Service, {
|
||||
instructions: () => Effect.succeed(catalog()),
|
||||
tools: () => Effect.succeed(tools()),
|
||||
}),
|
||||
],
|
||||
])
|
||||
|
||||
describe("McpInstructions", () => {
|
||||
it.effect("renders instructions for servers with at least one permitted tool", () =>
|
||||
Effect.gen(function* () {
|
||||
const service = yield* McpInstructions.Service
|
||||
const generation = yield* service
|
||||
.load(
|
||||
selection([
|
||||
{ action: McpTool.name("alpha", "restricted"), resource: "*", effect: "deny" },
|
||||
{ action: McpTool.name("hidden", "search"), resource: "*", effect: "deny" },
|
||||
]),
|
||||
)
|
||||
.pipe(Effect.flatMap(readInitial))
|
||||
|
||||
expect(generation.text).toBe(
|
||||
[
|
||||
"<mcp_instructions>",
|
||||
' <server name="alpha">',
|
||||
' Use tools from this server through `execute` under `tools["alpha"]`.',
|
||||
" Alpha line one",
|
||||
" Alpha line two",
|
||||
" </server>",
|
||||
' <server name="beta">',
|
||||
' Use tools from this server through `execute` under `tools["beta"]`.',
|
||||
" Beta instructions",
|
||||
" </server>",
|
||||
"</mcp_instructions>",
|
||||
].join("\n"),
|
||||
)
|
||||
}).pipe(
|
||||
Effect.provide(
|
||||
layer(
|
||||
() => [
|
||||
instructions("beta", "Beta instructions"),
|
||||
instructions("unused", "No tools"),
|
||||
instructions("hidden", "Denied tool"),
|
||||
instructions("alpha", "Alpha line one\nAlpha line two"),
|
||||
],
|
||||
() => [tool("alpha"), tool("alpha", "restricted"), tool("beta"), tool("hidden")],
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
it.effect("omits instructions when the agent cannot use execute", () =>
|
||||
Effect.gen(function* () {
|
||||
const service = yield* McpInstructions.Service
|
||||
const generation = yield* service
|
||||
.load(selection([{ action: "execute", resource: "*", effect: "deny" }]))
|
||||
.pipe(Effect.flatMap(readInitial))
|
||||
|
||||
expect(generation.text).toBe("")
|
||||
}).pipe(
|
||||
Effect.provide(
|
||||
layer(
|
||||
() => [instructions("alpha", "Alpha instructions")],
|
||||
() => [tool("alpha")],
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
it.effect("renders additions, changes, and removal", () => {
|
||||
let catalog = [instructions("alpha", "Alpha instructions")]
|
||||
const tools = [tool("alpha"), tool("beta")]
|
||||
return Effect.gen(function* () {
|
||||
const service = yield* McpInstructions.Service
|
||||
const initialized = yield* service.load(selection()).pipe(Effect.flatMap(readInitial))
|
||||
|
||||
catalog = [instructions("alpha", "Alpha instructions"), instructions("beta", "Beta instructions")]
|
||||
const added = yield* readUpdate(yield* service.load(selection()), initialized)
|
||||
expect(added.text).toBe(
|
||||
[
|
||||
"New MCP server instructions are available in addition to those previously listed:",
|
||||
' <server name="beta">',
|
||||
' Use tools from this server through `execute` under `tools["beta"]`.',
|
||||
" Beta instructions",
|
||||
" </server>",
|
||||
].join("\n"),
|
||||
)
|
||||
|
||||
catalog = [instructions("alpha", "Updated alpha"), instructions("beta", "Beta instructions")]
|
||||
const changed = yield* readUpdate(yield* service.load(selection()), added)
|
||||
expect(changed.text).toBe(
|
||||
[
|
||||
"The available MCP server instructions have changed. This list supersedes the previous one.",
|
||||
"<mcp_instructions>",
|
||||
' <server name="alpha">',
|
||||
' Use tools from this server through `execute` under `tools["alpha"]`.',
|
||||
" Updated alpha",
|
||||
" </server>",
|
||||
' <server name="beta">',
|
||||
' Use tools from this server through `execute` under `tools["beta"]`.',
|
||||
" Beta instructions",
|
||||
" </server>",
|
||||
"</mcp_instructions>",
|
||||
].join("\n"),
|
||||
)
|
||||
|
||||
catalog = [instructions("beta", "Beta instructions")]
|
||||
const removed = yield* readUpdate(yield* service.load(selection()), changed)
|
||||
expect(removed.text).toBe("Instructions for the following MCP servers are no longer available: alpha.")
|
||||
|
||||
catalog = []
|
||||
expect((yield* readUpdate(yield* service.load(selection()), removed)).text).toBe(
|
||||
"MCP server instructions are no longer available.",
|
||||
)
|
||||
}).pipe(
|
||||
Effect.provide(
|
||||
layer(
|
||||
() => catalog,
|
||||
() => tools,
|
||||
),
|
||||
),
|
||||
)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user