feat: pool the OpenCode server runtime - #184
Conversation
📝 WalkthroughWalkthroughAdds OpenCode local-agent support with shared runtime management, session execution, model and permission mappings, response parsing, health handling, provider wiring, and integration tests. ChangesOpenCode integration
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant LocalAgentDrivers
participant OpencodeLocalAgentDriver
participant OpencodeRuntime
participant OpenCodeServer
participant OpenCodeSession
LocalAgentDrivers->>OpencodeLocalAgentDriver: select "opencode"
OpencodeLocalAgentDriver->>OpencodeRuntime: obtain shared runtime
OpencodeRuntime->>OpenCodeServer: check health
OpencodeRuntime->>OpenCodeSession: create or resume session
OpencodeRuntime->>OpenCodeSession: submit prompt and wait
OpencodeRuntime->>OpenCodeSession: read messages
OpencodeRuntime-->>OpencodeLocalAgentDriver: return response and session ID
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Greptile SummaryThe PR introduces a pooled OpenCode runtime that shares one server across logical sessions while preserving durable provider session IDs and integrating runtime health and shutdown behavior.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| src/local-agent-opencode.ts | Implements the pooled OpenCode runtime, durable-session continuation, per-turn authority settings, response extraction, health invalidation, and idempotent shutdown; the previously reported cold-continuation defect is addressed. |
| src/local-agent-opencode.test.ts | Covers server reuse, session creation and continuation, thinking overrides, permission mapping, health-failure eviction, recreation, and single shutdown. |
| src/local-agent-adapters.ts | Registers OpenCode with the pooled driver path while leaving other providers on their existing implementations. |
Sequence Diagram
sequenceDiagram
participant M as Local Agent Manager
participant P as Runtime Pool
participant R as OpenCode Runtime
participant S as OpenCode Server
M->>P: run(context, input)
P->>R: reuse or create runtime
R->>S: health.get()
alt New session
R->>S: session.create(model, agent, workspace)
else Durable continuation
R->>S: session.get(sessionId)
R->>S: switchModel(model + thinking variant)
end
R->>S: switchAgent(write-mode agent)
R->>S: prompt(sessionId, prompt)
R->>S: wait(sessionId)
R->>S: messages(sessionId)
R-->>M: session ID and final response
P->>R: close on lifecycle expiry
R->>S: close server
Reviews (2): Last reviewed commit: "fix: enforce OpenCode authority and heal..." | Re-trigger Greptile
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3ccc712d54
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| } | ||
| } | ||
|
|
||
| export class OpencodeLocalAgentDriver implements LocalAgentDriver { |
There was a problem hiding this comment.
Wire the pooled drivers into the manager
Repo-wide search only finds OpencodeLocalAgentDriver and CodexLocalAgentDriver in these new files and their tests, while the server constructs subagents through createLocalAgentDrivers() from local-agent-adapters; as a result this pooled OpenCode runtime is unreachable in production, so OpenCode runs still won't share a server runtime. Please register the new driver(s) on the production driver factory path.
Useful? React with 👍 / 👎.
| return client.session.prompt({ | ||
| sessionID: sessionId, | ||
| prompt: { text: input.prompt }, | ||
| }, { throwOnError: true }); |
There was a problem hiding this comment.
Send OpenCode prompts in the v2 shape
When this runtime is used with the real @opencode-ai/sdk/v2, prompts are sent with a nested prompt object, but OpenCode's own v2 client call sends top-level sessionID, model/variant, and parts for session.prompt; with this payload the user text/model override is not part of the request the SDK expects, so OpenCode turns can be rejected or run without the requested prompt. Use the v2 parts: [{ type: "text", text: input.prompt }]/top-level fields shape here instead.
Useful? React with 👍 / 👎.
3ccc712 to
2cdb266
Compare
2cdb266 to
962f356
Compare
962f356 to
b6d06a0
Compare
2353b53 to
30dd494
Compare
30dd494 to
0929a57
Compare
0929a57 to
6ecc540
Compare
6ecc540 to
47713ab
Compare
738779f to
b2dd430
Compare
b2dd430 to
b0c15bf
Compare
526c3d2 to
8e0d52d
Compare
8e0d52d to
d1e388a
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/local-agent-opencode.ts`:
- Around line 108-114: Update the three agent configurations in createOpencode
for devspace_read_only, devspace_allowed, and devspace_full_access to explicitly
set mode to "primary" and task to "deny" alongside their existing permission
values. Add tests covering these fields for all three agent configurations.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: da3b45ec-e4af-415a-b163-be4e62bbd3be
📒 Files selected for processing (3)
src/local-agent-adapters.tssrc/local-agent-opencode.test.tssrc/local-agent-opencode.ts
| return createOpencode({ config: { | ||
| agent: { | ||
| devspace_read_only: { permission: opencodePermissionFor("read_only") }, | ||
| devspace_allowed: { permission: opencodePermissionFor("allowed") }, | ||
| devspace_full_access: { permission: opencodePermissionFor("full_access") }, | ||
| }, | ||
| } }); |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
For @opencode-ai/sdkversion 1.17.13, which agent configuration fields supportmode: "primary"andpermission.task: "deny"?
💡 Result:
In @opencode-ai/sdk version 1.17.13, the agent configuration fields mode: primary and permission.task: deny are defined within the agent's configuration object [1][2]. The mode: primary setting is a property of an agent's configuration that designates it as a primary agent, allowing it to interact directly with the user and perform main conversation tasks [1][3][4]. This is configured under the agent's key in the configuration: { "agent": { "your-agent-name": { "mode": "primary",... } } } The permission.task: deny setting is used within the permission configuration of an agent to restrict its ability to invoke subagents via the Task tool [1][3][5]. When set to deny, the specified subagent (or all subagents if using a wildcard) is removed from the Task tool's description, preventing the model from attempting to invoke it [1][4][6]. It is typically structured as follows: { "agent": { "your-agent-name": { "permission": { "task": { "": "deny", "specific-subagent-name": "allow" } } } } } These settings are part of the broader agent configuration schema defined in the SDK's types [2]. Note that OpenCode has migrated to a V2 configuration format, where agent permissions are handled via a more explicit permissions array (e.g., { "action": "task", "resource": "", "effect": "deny" }), though the SDK maintains compatibility with legacy frontmatter and configuration structures [7].
Citations:
- 1: https://opencode.ai/docs/agents/
- 2: https://github.com/sst/opencode/blob/9ad6588f/packages/sdk/js/src/v2/gen/types.gen.ts
- 3: https://www.agenticcodingweekly.com/p/opencode-cli-cheat-sheet
- 4: https://github.com/eXodes/caveopen/blob/main/SPEC.md
- 5: feat(task): Add subagent-to-subagent delegation with budgets, persistent sessions, and hierarchical session navigation anomalyco/opencode#7756
- 6: https://regolo.ai/opencode-brick-for-multi-agent-coding-and-optimize-costs-up-to-80/
- 7: https://opencode.ai/v2/docs/migrate-v1
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- target file ---'
cat -n src/local-agent-opencode.ts | sed -n '1,190p'
printf '%s\n' '--- dependency declarations and lock entries ---'
rg -n -C 3 '`@opencode-ai/sdk`|opencode' package.json package-lock.json pnpm-lock.yaml yarn.lock bun.lockb 2>/dev/null || true
printf '%s\n' '--- related tests and call sites ---'
rg -n -C 4 'devspace_read_only|devspace_allowed|devspace_full_access|opencodePermissionFor|defaultOpencodeFactory|permission.*task|mode:.*primary' . \
-g '!node_modules' -g '!dist' -g '!build' || true
printf '%s\n' '--- tracked files near the target ---'
git ls-files | rg '(^|/)(test|tests|spec|__tests__)/|opencode|package.json|lock' | head -200Repository: Waishnav/devspace
Length of output: 15258
🏁 Script executed:
#!/bin/bash
set -eu
tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT
curl -fsSL 'https://registry.npmjs.org/@opencode-ai/sdk/-/sdk-1.17.13.tgz' -o "$tmpdir/sdk.tgz"
tar -xzf "$tmpdir/sdk.tgz" -C "$tmpdir"
printf '%s\n' '--- SDK package files relevant to V2 config ---'
find "$tmpdir/package" -type f | rg 'types|config|gen|index' | head -100
printf '%s\n' '--- V2 agent/config/permission declarations ---'
rg -n -C 5 'PermissionConfig|AgentConfig|permission|mode|task' "$tmpdir/package" \
-g '*.d.ts' -g '*.ts' -g '*.js' | head -300
printf '%s\n' '--- local tests ---'
cat -n src/local-agent-opencode.test.ts | sed -n '1,155p'
printf '%s\n' '--- runtime input contract ---'
rg -n -C 5 'interface LocalAgentRunInput|type LocalAgentRunInput|workspace|writeMode|task|profile|result' src/local-agent-runtime.ts src -g '*.ts' | head -300Repository: Waishnav/devspace
Length of output: 50373
🏁 Script executed:
#!/bin/bash
set -eu
tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT
curl -fsSL 'https://registry.npmjs.org/@opencode-ai/sdk/-/sdk-1.17.13.tgz' -o "$tmpdir/sdk.tgz"
tar -xzf "$tmpdir/sdk.tgz" -C "$tmpdir"
printf '%s\n' '--- legacy AgentConfig permission fields ---'
sed -n '835,880p' "$tmpdir/package/dist/gen/types.gen.d.ts"
printf '%s\n' '--- V2 AgentConfig and Config declarations ---'
rg -n 'export type (AgentConfig|Config|PermissionConfig)|interface (AgentConfig|Config|PermissionConfig)|agent\?:|permission\?:|task\?:|mode\?:' \
"$tmpdir/package/dist/v2" -g '*.d.ts' -g '*.ts'
sed -n '1,220p' "$tmpdir/package/dist/v2/gen/types.gen.d.ts" | rg -n -C 8 'AgentConfig|Config|PermissionConfig|task|mode|permission'
printf '%s\n' '--- createOpencode config handling ---'
rg -n -C 8 'function createOpencode|createOpencode|config' "$tmpdir/package/dist/v2" -g '*.js' -g '*.d.ts' | head -180Repository: Waishnav/devspace
Length of output: 23268
🏁 Script executed:
#!/bin/bash
set -eu
tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT
curl -fsSL 'https://registry.npmjs.org/@opencode-ai/sdk/-/sdk-1.17.13.tgz' -o "$tmpdir/sdk.tgz"
tar -xzf "$tmpdir/sdk.tgz" -C "$tmpdir"
types="$tmpdir/package/dist/v2/gen/types.gen.d.ts"
printf '%s\n' '--- exact V2 permission and agent types ---'
sed -n '1315,1380p' "$types"
sed -n '1505,1630p' "$types"
printf '%s\n' '--- task/mode references in the SDK package ---'
rg -n -C 4 '"task"|task\?:|mode\?:|PermissionConfig|PermissionRuleConfig' "$tmpdir/package" \
-g '*.d.ts' -g '*.js' -g '*.md' | head -260
printf '%s\n' '--- repository configuration usage and OpenCode docs ---'
rg -n -C 4 'OPENCODE_CONFIG|PermissionConfig|permission|mode|task|subagent|primary' src examples README.md package.json \
-g '*.ts' -g '*.md' -g '*.json' | head -300Repository: Waishnav/devspace
Length of output: 37339
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- local-agent ownership and subagent execution path ---'
rg -n -C 6 'subagents|LocalAgentRunInput|LocalAgentRuntimeContext|profile|prompt|workspace|agentId' \
src/local-agent-*.ts src/local-agent-*.test.ts \
-g '*.ts' | head -500
printf '%s\n' '--- OpenCode agent/task documentation and implementation references ---'
for url in \
'https://raw.githubusercontent.com/anomalyco/opencode/v1.17.13/packages/web/src/content/docs/agents.mdx' \
'https://raw.githubusercontent.com/anomalyco/opencode/dev/packages/web/src/content/docs/agents.mdx' \
'https://api.github.com/repos/anomalyco/opencode/contents/packages' \
'https://api.github.com/repos/anomalyco/opencode/commits?path=packages/opencode/src/tool/task.ts&per_page=5'; do
printf '\nURL: %s\n' "$url"
curl -L --max-time 15 -fsS "$url" 2>/dev/null | rg -n -C 4 'mode|task|subagent|permission|default' | head -160 || true
doneRepository: Waishnav/devspace
Length of output: 43817
🏁 Script executed:
#!/bin/bash
set -eu
tree="$(mktemp)"
trap 'rm -f "$tree"' EXIT
curl -fsSL --max-time 20 \
'https://api.github.com/repos/anomalyco/opencode/git/trees/v1.17.13?recursive=1' > "$tree"
printf '%s\n' '--- source paths for agent and permission defaults ---'
python3 - "$tree" <<'PY'
import json, sys
data=json.load(open(sys.argv[1]))
for item in data.get("tree", []):
p=item.get("path","")
if any(term in p.lower() for term in ("agent", "permission", "task")) and p.endswith((".ts",".tsx",".mdx")):
print(p)
PY
printf '%s\n' '--- selected source references ---'
for path in \
'packages/opencode/src/agent/agent.ts' \
'packages/opencode/src/permission/permission.ts' \
'packages/opencode/src/tool/task.ts' \
'packages/opencode/src/config/config.ts'; do
printf '\nPATH: %s\n' "$path"
curl -L --max-time 15 -fsS \
"https://raw.githubusercontent.com/anomalyco/opencode/v1.17.13/$path" 2>/dev/null \
| rg -n -C 6 'mode|task|permission|primary|subagent|all|default' | head -220 || true
doneRepository: Waishnav/devspace
Length of output: 206
Make the OpenCode agent policy explicit.
Set each devspace_* agent to mode: "primary" and add task: "deny" in opencodePermissionFor. Without these fields, OpenCode controls the agent mode and child-agent delegation. Add a test for all three agent configurations.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/local-agent-opencode.ts` around lines 108 - 114, Update the three agent
configurations in createOpencode for devspace_read_only, devspace_allowed, and
devspace_full_access to explicitly set mode to "primary" and task to "deny"
alongside their existing permission values. Add tests covering these fields for
all three agent configurations.
Source: Coding guidelines
d1e388a to
5a7d15a
Compare
OpenCode is naturally split between a server and client, but DevSpace previously recreated that pair for each turn. This stacked change adds the reusable OpenCode runtime that keeps one server alive for multiple logical sessions, preserves provider session ids for cold continuation, and closes the server through the shared runtime lifecycle.
Summary by CodeRabbit