diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 1698eeed4..9f8c312a7 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -429,7 +429,7 @@ export namespace Config { temperature: z.number().optional(), top_p: z.number().optional(), prompt: z.string().optional(), - tools: z.record(z.string(), z.boolean()).optional(), + tools: z.record(z.string(), z.boolean()).optional().describe("@deprecated Use 'permission' field instead"), disable: z.boolean().optional(), description: z.string().optional().describe("Description of when to use the agent"), mode: z.enum(["subagent", "primary", "all"]).optional(), @@ -449,6 +449,47 @@ export namespace Config { permission: Permission.optional(), }) .catchall(z.any()) + .transform((agent) => { + const knownKeys = new Set([ + "name", + "model", + "prompt", + "description", + "temperature", + "top_p", + "mode", + "color", + "steps", + "maxSteps", + "options", + "permission", + "disable", + "tools", + ]) + + // Extract unknown properties into options + const options: Record = { ...agent.options } + for (const [key, value] of Object.entries(agent)) { + if (!knownKeys.has(key)) options[key] = value + } + + // Convert legacy tools config to permissions + const permission: Permission = { ...agent.permission } + for (const [tool, enabled] of Object.entries(agent.tools ?? {})) { + const action = enabled ? "allow" : "deny" + // write, edit, patch, multiedit all map to edit permission + if (tool === "write" || tool === "edit" || tool === "patch" || tool === "multiedit") { + permission.edit = action + } else { + permission[tool] = action + } + } + + return { ...agent, options, permission } as typeof agent & { + options: Record + permission: Permission + } + }) .meta({ ref: "AgentConfig", }) diff --git a/packages/opencode/test/agent/agent.test.ts b/packages/opencode/test/agent/agent.test.ts index 784bb1386..4b6149cb3 100644 --- a/packages/opencode/test/agent/agent.test.ts +++ b/packages/opencode/test/agent/agent.test.ts @@ -389,3 +389,47 @@ test("webfetch is allowed by default", async () => { }, }) }) + +test("legacy tools config converts to permissions", async () => { + await using tmp = await tmpdir({ + config: { + agent: { + build: { + tools: { + bash: false, + read: false, + }, + }, + }, + }, + }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const build = await Agent.get("build") + expect(build?.permission.bash?.["*"]).toBe("deny") + expect(build?.permission.read?.["*"]).toBe("deny") + }, + }) +}) + +test("legacy tools config maps write/edit/patch/multiedit to edit permission", async () => { + await using tmp = await tmpdir({ + config: { + agent: { + build: { + tools: { + write: false, + }, + }, + }, + }, + }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const build = await Agent.get("build") + expect(build?.permission.edit?.["*"]).toBe("deny") + }, + }) +}) diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index 2ff8c01cd..ea0737042 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -205,11 +205,13 @@ test("handles agent configuration", async () => { directory: tmp.path, fn: async () => { const config = await Config.get() - expect(config.agent?.["test_agent"]).toEqual({ - model: "test/model", - temperature: 0.7, - description: "test agent", - }) + expect(config.agent?.["test_agent"]).toEqual( + expect.objectContaining({ + model: "test/model", + temperature: 0.7, + description: "test agent", + }), + ) }, }) }) @@ -292,6 +294,8 @@ test("migrates mode field to agent field", async () => { model: "test/model", temperature: 0.5, mode: "primary", + options: {}, + permission: {}, }) }, }) @@ -318,11 +322,13 @@ Test agent prompt`, directory: tmp.path, fn: async () => { const config = await Config.get() - expect(config.agent?.["test"]).toEqual({ - name: "test", - model: "test/model", - prompt: "Test agent prompt", - }) + expect(config.agent?.["test"]).toEqual( + expect.objectContaining({ + name: "test", + model: "test/model", + prompt: "Test agent prompt", + }), + ) }, }) })