Skip to content

feat: add OpenCode runner support#1263

Open
JappyMondo wants to merge 60 commits into
cyrusagents:mainfrom
jappyjan:opencode-cli-runner-support
Open

feat: add OpenCode runner support#1263
JappyMondo wants to merge 60 commits into
cyrusagents:mainfrom
jappyjan:opencode-cli-runner-support

Conversation

@JappyMondo

@JappyMondo JappyMondo commented May 28, 2026

Copy link
Copy Markdown

Summary

  • Add an OpenCode runner package that launches opencode run, parses JSONL events, preserves appended system prompts, and maps lifecycle/output events into Cyrus runner messages.
  • Wire OpenCode into runner selection through the opencode label and [agent=opencode] selector, including model/default config handling and chat/session continuation safeguards.
  • Translate Cyrus MCP and tool permissions into OpenCode runtime config, including configured MCP servers, OAuth metadata, state-scope options, and deny-by-default tool grants.
  • Add OpenCode activity formatting, session metadata, tests, guarded live-probe coverage, and F1 validation notes.

Test plan

  • pnpm build
  • pnpm typecheck
  • pnpm test:packages:run
  • pnpm audit
  • pnpm lint (warning-only Biome suggestions; no errors)

@JappyMondo JappyMondo marked this pull request as draft May 28, 2026 09:17
@JappyMondo JappyMondo force-pushed the opencode-cli-runner-support branch from 1cc6d89 to 1df5f63 Compare May 28, 2026 09:35
@JappyMondo JappyMondo marked this pull request as ready for review May 28, 2026 11:55
@JappyMondo JappyMondo force-pushed the opencode-cli-runner-support branch from 1071830 to 8874763 Compare May 29, 2026 08:11
@jappyjan jappyjan force-pushed the opencode-cli-runner-support branch from 9424f3f to 50c844e Compare June 10, 2026 21:27
@Connoropolous

Copy link
Copy Markdown
Contributor

@JappyMondo Great work here!

We had to clear a big codex update first, but now ready to get on with merging this in.

Can you bring it up to speed with 'main' branch?

@JappyMondo

Copy link
Copy Markdown
Author

hi @Connoropolous thanks for the feedback, yes i will address all these :)
Since we use this at our company, i am essentially paid to do this :D

regarding the model selection labels, for the opencode runner you need a 3 part selector, runner + provider + model
e.g. opencode/openai/gpt-5.5
for other runners like claude it would be simpler, e.g. cursor/claude-fable-5

@JappyMondo

Copy link
Copy Markdown
Author

@Connoropolous i fixed/addressed all your comments and will now test it for the next couple days, feel free to already give it a look :)
worked fine for me for the last hours.

@Connoropolous

Copy link
Copy Markdown
Contributor

awesome!

we will be running it in a new test

…-support

# Conflicts:
#	CHANGELOG.internal.md
#	CHANGELOG.md
#	packages/core/src/index.ts
#	pnpm-lock.yaml
@JappyMondo

JappyMondo commented Jun 22, 2026

Copy link
Copy Markdown
Author

Just pushed an update to bring this branch back in sync with main

The branch is now mergable again the only remaining blocker is the required review approval.

Could you let us know where this stands on your side and what's needed to move it forward? Happy to address any feedback or make further changes.

@Connoropolous

Connoropolous commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Good stuff.

I need to get clear if this error out case is more on the side of opencode or openrouter...
Screenshot 2026-06-22 at 5 43 51 PM

Screenshot 2026-06-22 at 5 45 12 PM

its using kimi k2.7

Screenshot 2026-06-22 at 2 57 44 PM

I'm also still failing to understand why these are such a frequent occurrence. Do you not see these?

@Connoropolous

Copy link
Copy Markdown
Contributor

@cyrusagent can you report to me on this PR handles the translation of the 'allowed tools' into opencode compatibility, or otherwise approaches that? (compared to claude runner, cursor runner, and codex runner)

@cyrusagent

cyrusagent Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Short version: this PR does add an OpenCode-specific translation layer, but it is not strict parity with Claude/Cursor.

OpenCode path:

  • EdgeWorker resolves allowedTools/disallowedTools once and passes the same arrays into the selected runner (packages/edge-worker/src/RunnerConfigBuilder.ts:431).
  • OpenCode then builds OPENCODE_CONFIG_CONTENT with a generated permission block (packages/opencode-runner/src/config.ts:467).
  • It starts deny-by-default via { "*": "deny" }, maps supported Cyrus/Claude-style patterns, then applies disallowedTools after allowedTools (packages/opencode-runner/src/config.ts:380, packages/opencode-runner/src/config.ts:385, packages/opencode-runner/src/config.ts:421).
  • Supported mappings include file tools, bash/shell, web fetch/search, task tools, skills, todo, ask/question, and MCP patterns (mcp__server__tool -> server_tool, mcp__server -> server_*) (packages/opencode-runner/src/config.ts:141).
  • Tests cover the translation and config override behavior (packages/opencode-runner/test/config.test.ts:135, packages/opencode-runner/test/config.test.ts:395).

Comparison:

  • Claude: mostly native pass-through. It expands allowedDirectories into Read(...) grants, adds generated home-directory denies, then passes allowedTools/disallowedTools directly to the Claude SDK with strict MCP config (packages/claude-runner/src/ClaudeRunner.ts:526, packages/claude-runner/src/ClaudeRunner.ts:550, packages/claude-runner/src/ClaudeRunner.ts:697).
  • Cursor: translates Cyrus patterns into Cursor hook vocabulary and installs .cursor permission artifacts/hooks. This is a real enforcement layer with allow/deny matching (packages/cursor-runner/src/permissions.ts:91, packages/cursor-runner/src/permissions.ts:289, packages/cursor-runner/src/CursorRunner.ts:768).
  • Codex: only really translates MCP allowedTools into Codex MCP enabled_tools / approval config. It does not translate general Read/Edit/Bash allowlists the way OpenCode/Cursor do; filesystem control is handled separately through sandbox permissions (packages/codex-runner/src/config/CodexConfigBuilder.ts:134, packages/codex-runner/src/config/mcpConfigTranslator.ts:265, packages/codex-runner/src/config/sandboxPolicy.ts:46).

Main caveat: OpenCode currently auto-allows every enabled MCP server present in runtime config via addConfiguredMcpPermissions, then relies on specific disallowedTools to deny individual tools (packages/opencode-runner/src/config.ts:263, packages/opencode-runner/src/config.ts:420). Since McpConfigService intentionally builds MCP servers regardless of allowed-tools and expects allowed-tools to gate calls (packages/edge-worker/src/McpConfigService.ts:53), OpenCode is broader than Claude/Cursor here unless the allowlist already includes only desired MCP servers or explicit denies are configured.

So: good compatibility layer for common built-in tool classes, but not exact allowed-tools parity. The biggest follow-up I’d want before calling it strict is to stop auto-allowing all configured MCP servers, or filter that server/tool permission set from allowedTools instead. I made no code changes.

@Connoropolous

Copy link
Copy Markdown
Contributor

@cyrusagent " The biggest follow-up I’d want before calling it strict is to stop auto-allowing all configured MCP servers, or filter that server/tool permission set from allowedTools instead. " Is it clear what would be done to enable this? Don't actually do it, just discuss.

@cyrusagents cyrusagents deleted a comment from cyrusagent Bot Jun 23, 2026
@Connoropolous

Copy link
Copy Markdown
Contributor

@cyrusagent can you try again to answer? I missed that

@cyrusagent

Copy link
Copy Markdown
Contributor

Yes — it's a small, well-scoped change. Here's what's actually going on and what would be done.

Why it auto-allows today. In buildOpenCodeConfig the order is: start { "*": "deny" }, apply allowedTools, then call addConfiguredMcpPermissions(permission, runtimeConfig.mcp) (packages/opencode-runner/src/config.ts:420), then apply disallowedTools. That helper (config.ts:263) loops every enabled configured MCP server and writes server_*: allow for it — regardless of whether the allowlist asked for it. That single step is what makes OpenCode broader than Claude/Cursor.

Key point: the allowlist path for MCP already exists. addOpenCodePermissionparseMcpPattern already translates mcp__server__toolserver_tool (allow) and mcp__serverserver_* (allow) when those patterns are in allowedTools (config.ts:147). So MCP tools are already grantable the normal way. The auto-allow loop is redundant with that — it just additionally grants every configured server even when the allowlist didn't.

Option A (recommended — strict parity). Delete the addConfiguredMcpPermissions call at config.ts:420. MCP tools then behave like every other tool class: configured/connectable, but deny-by-default until an allowedTools entry grants them, with disallowedTools still applying on top. This matches Claude (passes allowedTools straight to the SDK) and Cursor (translates patterns into its permission layer).

Option B (filter instead of remove). Keep the loop but intersect it with the allowlist — only auto-allow a server that allowedTools already references (and isn't denied). This lands in the same place as A but with more moving parts, so A is cleaner unless we want the loop for another reason.

The diligence that matters more than the code. The auto-allow exists because McpConfigService intentionally builds every MCP server regardless of allowed-tools and expects allowed-tools to gate the calls (packages/edge-worker/src/McpConfigService.ts:53). For Claude that gating happens natively in the SDK; OpenCode currently undoes it. So before removing the loop we'd want to confirm the allowedTools arrays reaching the runner actually enumerate the MCP entries we expect on by default (the cyrus-tools defaults in packages/core/src/allowed-tools-defaults.ts do this). Any server that today "just works" purely by being configured — never appearing in an allowlist — would start being denied and would need an explicit allowlist/default entry. That's the real decision, not the deletion.

Test coverage to add: in packages/opencode-runner/test/config.test.ts, assert that a configured-but-not-allowlisted MCP server resolves to deny, and an allowlisted one resolves to allow.

Net: one-line removal plus verifying the allowed-tools defaults carry the intended MCP entries. I made no code changes.

@cyrusagent

cyrusagent Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

The 2 uncommitted changes (EdgeWorker.ts, GitLabCommentService.ts) were already present at session start and are unrelated to this task — this was a discussion-only question with no intended code changes. I posted my answer to the PR and made no edits of my own, so there's nothing for me to commit. Stopping.

@jappyjan

Copy link
Copy Markdown

Hey @Connoropolous

i am using this daily and i havent seen the error you experience even once... Not sure what it might be...

I also saw your conversation here with cyrusagent, do you want me to adapt some things?
Sounds like there is a discrepancy between how my solution handles permissions and how you would expect them to work?

@jappyjan

Copy link
Copy Markdown

@Connoropolous ping :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants