refactor(opencode): align output with current permissions

Stop emitting deprecated tools config for OpenCode permission modes. Update the OpenCode schema notes and add a regression that the current CE plugin outputs skills plus subagents, not command files.
This commit is contained in:
Trevin Chow
2026-04-19 17:32:51 -07:00
parent e60a6237f8
commit 326e4e8615
4 changed files with 51 additions and 13 deletions
+11 -5
View File
@@ -26,10 +26,11 @@ https://opencode.ai/config.json
## Core config keys
- `model` and `small_model` set the primary and lightweight models; `provider` configures provider options.
- `tools` is still supported but deprecated; permissions are now the canonical control surface.
- `tools` is still supported but deprecated as of OpenCode v1.1.1; permissions are now the canonical control surface.
- `permission` controls tool approvals and can be configured globally or per tool, including pattern-based rules.
- `mcp`, `instructions`, `disabled_providers`, `enabled_providers`, and `plugin` are supported config sections.
- `plugin` can list npm packages to load at startup.
- `skills.paths` and `skills.urls` can add extra skill discovery locations, but CE should not depend on them until the layout is smoke-tested locally with OpenCode.
## Tools
@@ -45,27 +46,31 @@ https://opencode.ai/config.json
## Agents
- Agents can be configured in `opencode.json` or as markdown files in `~/.config/opencode/agents/` or `.opencode/agents/`.
- Agent config supports `mode`, `model`, `temperature`, `tools`, and `permission`, and agent configs override global settings.
- Agent config supports `mode`, `model`, `variant`, `temperature`, `top_p`, `hidden`, `steps`, `options`, `permission`, and other schema fields. `tools` still exists but is deprecated.
- `mode` can be `primary`, `subagent`, or `all`; omitted mode defaults to `all`.
- `hidden: true` hides subagents from the `@` autocomplete menu.
- `permission.task` controls which subagents an agent may invoke.
- Model IDs use the `provider/model-id` format.
## Skills
- Skills are reusable `SKILL.md` definitions loaded on demand through OpenCode's native `skill` tool.
- OpenCode searches direct child skill directories only:
- OpenCode searches direct child skill directories in its built-in roots:
- `.opencode/skills/<name>/SKILL.md`
- `~/.config/opencode/skills/<name>/SKILL.md`
- `.claude/skills/<name>/SKILL.md`
- `~/.claude/skills/<name>/SKILL.md`
- `.agents/skills/<name>/SKILL.md`
- `~/.agents/skills/<name>/SKILL.md`
- The config schema also exposes `skills.paths` and `skills.urls` for extra skill sources. Do not switch CE to those until tested against a local OpenCode install; direct `~/.config/opencode/skills/<name>/SKILL.md` remains the stable writer shape.
- Skill frontmatter recognizes `name`, `description`, `license`, `compatibility`, and `metadata`; unknown fields are ignored.
- Skill names must be lowercase alphanumeric with single hyphen separators and must match the directory name.
## Commands
- Commands can be configured in `opencode.json` or as Markdown files in `~/.config/opencode/commands/` or `.opencode/commands/`.
- Markdown command frontmatter can include fields such as `description`, `agent`, and `model`; the body becomes the prompt template.
- Markdown command frontmatter can include fields such as `description`, `agent`, `model`, and `subtask`; the body becomes the prompt template.
- If a command targets an agent whose mode is `subagent`, OpenCode invokes it as a subagent by default. `subtask: true` can force subagent invocation.
## Plugins and events
@@ -79,8 +84,9 @@ https://opencode.ai/config.json
- The current CE writer shape is still appropriate in April 2026:
- `~/.config/opencode/opencode.json`
- `~/.config/opencode/agents/*.md`
- `~/.config/opencode/commands/*.md`
- `~/.config/opencode/commands/*.md` only when a source plugin ships commands
- `~/.config/opencode/plugins/*.ts`
- `~/.config/opencode/skills/*/SKILL.md`
- OpenCode's plugin system is useful for JS/TS hooks and custom tools, but current docs do not describe a native marketplace command that consumes CE's `.claude-plugin/marketplace.json` and installs the full skills/agents/commands payload.
- Keep the custom Bun writer until OpenCode documents a native distribution path for packaged skills and agents.
- The `compound-engineering` plugin currently emits skills and subagent Markdown files for OpenCode. It should not emit deprecated `tools` config; permission config is enough for non-default permission modes.
-6
View File
@@ -362,11 +362,6 @@ function applyPermissions(
}
const permission: Record<string, "allow" | "deny" | Record<string, "allow" | "deny">> = {}
const tools: Record<string, boolean> = {}
for (const tool of sourceTools) {
tools[tool] = mode === "broad" ? true : enabled.has(tool)
}
if (mode === "broad") {
for (const tool of sourceTools) {
@@ -415,7 +410,6 @@ function applyPermissions(
}
config.permission = permission
config.tools = tools
}
function normalizeTool(raw: string): string | null {
+20 -2
View File
@@ -4,19 +4,37 @@ export type OpenCodeConfig = {
$schema?: string
model?: string
default_agent?: string
/** @deprecated OpenCode v1.1.1+ uses permission as the canonical control surface. */
tools?: Record<string, boolean>
permission?: Record<string, OpenCodePermission | Record<string, OpenCodePermission>>
agent?: Record<string, OpenCodeAgentConfig>
mcp?: Record<string, OpenCodeMcpServer>
skills?: OpenCodeSkillsConfig
}
export type OpenCodeAgentConfig = {
description?: string
mode?: "primary" | "subagent"
mode?: "primary" | "subagent" | "all"
model?: string
variant?: string
temperature?: number
top_p?: number
prompt?: string
disable?: boolean
hidden?: boolean
color?: string
steps?: number
/** @deprecated Use steps instead. */
maxSteps?: number
options?: Record<string, unknown>
/** @deprecated OpenCode v1.1.1+ uses permission as the canonical control surface. */
tools?: Record<string, boolean>
permission?: Record<string, OpenCodePermission>
permission?: Record<string, OpenCodePermission | Record<string, OpenCodePermission>>
}
export type OpenCodeSkillsConfig = {
paths?: string[]
urls?: string[]
}
export type OpenCodeMcpServer = {
+20
View File
@@ -15,6 +15,24 @@ const compoundEngineeringRoot = path.join(
)
describe("convertClaudeToOpenCode", () => {
test("current compound-engineering output is skills and subagents, not commands", async () => {
const plugin = await loadClaudePlugin(compoundEngineeringRoot)
const bundle = convertClaudeToOpenCode(plugin, {
agentMode: "subagent",
inferTemperature: true,
permissions: "none",
})
expect(bundle.agents.length).toBeGreaterThan(0)
expect(bundle.skillDirs.length).toBeGreaterThan(0)
expect(bundle.commandFiles).toHaveLength(0)
expect(bundle.plugins).toHaveLength(0)
expect(bundle.config.tools).toBeUndefined()
const parsedAgents = bundle.agents.map((agent) => parseFrontmatter(agent.content))
expect(parsedAgents.every((agent) => agent.data.mode === "subagent")).toBe(true)
})
test("from-command mode: map allowedTools to global permission block", async () => {
const plugin = await loadClaudePlugin(fixtureRoot)
const bundle = convertClaudeToOpenCode(plugin, {
@@ -24,6 +42,7 @@ describe("convertClaudeToOpenCode", () => {
})
expect(bundle.config.command).toBeUndefined()
expect(bundle.config.tools).toBeUndefined()
expect(bundle.commandFiles.find((f) => f.name === "workflows:review")).toBeDefined()
expect(bundle.commandFiles.find((f) => f.name === "plan_review")).toBeDefined()
@@ -275,6 +294,7 @@ describe("convertClaudeToOpenCode", () => {
inferTemperature: false,
permissions: "broad",
})
expect(broadBundle.config.tools).toBeUndefined()
expect(broadBundle.config.permission).toEqual({
read: "allow",
write: "allow",