feat(opencode): gate execute tool behind code mode flag (#35185)

This commit is contained in:
Aiden Cline 2026-07-03 13:41:15 -05:00 committed by GitHub
commit ed6dc879be
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 174 additions and 15 deletions

View file

@ -182,7 +182,8 @@ describe("code mode integration (real MCP server)", () => {
expect(description).toContain("// Add two numbers and return the structured sum")
expect(description).not.toContain("$codemode")
expect(description).toContain("## Workflow")
expect(description).toContain("`const res = await tools.<namespace>.<tool>(input)`")
expect(description).toContain("Do not infer or normalize tool names")
expect(description).toContain("bracket notation and quotes are part of the path")
expect(description).not.toContain("total_count")
})

View file

@ -209,7 +209,7 @@ describe("code mode execute", () => {
expect(description).toContain("- zeta (1 tool)\n")
expect(description).toContain("tools.zeta.only_tool(input: { topic: string }): Promise<unknown>")
expect(description).toContain("tools.$codemode.search(")
expect(description).toContain("1. Find a tool (skip when it is already listed below)")
expect(description).toContain("1. If the exact signature is not listed below, first search:")
expect(description).toContain(
'- Browse one namespace: `await tools.$codemode.search({ query: "", namespace: "<name>" })`.',
)

View file

@ -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