|
|
@@ -19,6 +19,8 @@ import { MessageID, SessionID } from "@/session/schema"
|
|
|
import { RuntimeFlags } from "@/effect/runtime-flags"
|
|
|
import { ProviderV2 } from "@opencode-ai/core/provider"
|
|
|
import { ModelV2 } from "@opencode-ai/core/model"
|
|
|
+import { MCP } from "@/mcp"
|
|
|
+import type { Tool as MCPToolDef } from "@modelcontextprotocol/sdk/types.js"
|
|
|
|
|
|
const configLayer = TestConfig.layer({
|
|
|
directories: () => InstanceState.directory.pipe(Effect.map((dir) => [path.join(dir, ".opencode")])),
|
|
|
@@ -55,6 +57,42 @@ const replacements = [
|
|
|
] as const
|
|
|
|
|
|
const it = testEffect(LayerNode.compile(root, replacements))
|
|
|
+const withCodeMode = testEffect(
|
|
|
+ LayerNode.compile(root, [
|
|
|
+ [Config.node, configLayer],
|
|
|
+ [RuntimeFlags.node, RuntimeFlags.layer({ experimentalCodeMode: true })],
|
|
|
+ [
|
|
|
+ MCP.node,
|
|
|
+ Layer.mock(MCP.Service, {
|
|
|
+ tools: () =>
|
|
|
+ Effect.succeed({
|
|
|
+ weather_current: {
|
|
|
+ def: {
|
|
|
+ name: "current",
|
|
|
+ description: "current weather",
|
|
|
+ inputSchema: { type: "object", properties: { city: { type: "string" } }, required: ["city"] },
|
|
|
+ } as MCPToolDef,
|
|
|
+ client: {} as MCP.McpTool["client"],
|
|
|
+ },
|
|
|
+ }),
|
|
|
+ clients: () => Effect.succeed({ weather: {} as any }),
|
|
|
+ }),
|
|
|
+ ],
|
|
|
+ ]),
|
|
|
+)
|
|
|
+const withEmptyCodeMode = testEffect(
|
|
|
+ LayerNode.compile(root, [
|
|
|
+ [Config.node, configLayer],
|
|
|
+ [RuntimeFlags.node, RuntimeFlags.layer({ experimentalCodeMode: true })],
|
|
|
+ [
|
|
|
+ MCP.node,
|
|
|
+ Layer.mock(MCP.Service, {
|
|
|
+ tools: () => Effect.succeed({}),
|
|
|
+ clients: () => Effect.succeed({}),
|
|
|
+ }),
|
|
|
+ ],
|
|
|
+ ]),
|
|
|
+)
|
|
|
const withBrokenPlugin = testEffect(LayerNode.compile(root, [...replacements, [Plugin.node, brokenPluginLayer]]))
|
|
|
|
|
|
afterEach(async () => {
|
|
|
@@ -71,6 +109,47 @@ describe("tool.registry", () => {
|
|
|
}),
|
|
|
)
|
|
|
|
|
|
+ it.instance("does not expose execute unless code mode is enabled", () =>
|
|
|
+ Effect.gen(function* () {
|
|
|
+ const registry = yield* ToolRegistry.Service
|
|
|
+ const ids = yield* registry.ids()
|
|
|
+
|
|
|
+ expect(ids).not.toContain("execute")
|
|
|
+ }),
|
|
|
+ )
|
|
|
+
|
|
|
+ withCodeMode.instance("exposes execute when code mode is enabled", () =>
|
|
|
+ Effect.gen(function* () {
|
|
|
+ const registry = yield* ToolRegistry.Service
|
|
|
+ const agents = yield* Agent.Service
|
|
|
+ const ids = yield* registry.ids()
|
|
|
+ const tools = yield* registry.tools({
|
|
|
+ providerID: ProviderV2.ID.opencode,
|
|
|
+ modelID: ModelV2.ID.make("test"),
|
|
|
+ agent: yield* agents.defaultInfo(),
|
|
|
+ })
|
|
|
+ const execute = tools.find((tool) => tool.id === "execute")
|
|
|
+
|
|
|
+ expect(ids).toContain("execute")
|
|
|
+ expect(tools.map((tool) => tool.id)).toContain("execute")
|
|
|
+ expect(execute?.description).toContain("tools.weather.current(input: { city: string })")
|
|
|
+ }),
|
|
|
+ )
|
|
|
+
|
|
|
+ withEmptyCodeMode.instance("does not expose execute when code mode has no visible tools", () =>
|
|
|
+ Effect.gen(function* () {
|
|
|
+ const registry = yield* ToolRegistry.Service
|
|
|
+ const agents = yield* Agent.Service
|
|
|
+ const tools = yield* registry.tools({
|
|
|
+ providerID: ProviderV2.ID.opencode,
|
|
|
+ modelID: ModelV2.ID.make("test"),
|
|
|
+ agent: yield* agents.defaultInfo(),
|
|
|
+ })
|
|
|
+
|
|
|
+ expect(tools.map((tool) => tool.id)).not.toContain("execute")
|
|
|
+ }),
|
|
|
+ )
|
|
|
+
|
|
|
it.instance("hides task background parameter unless experimental background subagents are enabled", () =>
|
|
|
Effect.gen(function* () {
|
|
|
const registry = yield* ToolRegistry.Service
|