Skip to content

feat(setup): add an OpenCode adapter for the setup agent (1/6) - #894

Open
atornsii wants to merge 5 commits into
atorns/go-1.26-upgradefrom
atorns/agent-1-opencode-adapter
Open

atornsii wants to merge 5 commits into
atorns/go-1.26-upgradefrom
atorns/agent-1-opencode-adapter

Conversation

@atornsii

@atornsii atornsii commented Oct 20, 2026 •

Copy link
Copy Markdown

This adds the internal/setup/agent package, which runs one OpenCode session for a setup task. Nothing uses it yet. PR 2 adds the wizard step.

What it does

  • Starts opencode serve in the project folder on 127.0.0.1, with a random password for each run (OPENCODE_SERVER_PASSWORD) and without any LD_ environment variable.
  • Creates a session, sends the task, and reads the event stream. It turns the events into text, tool calls, changed files, token usage, permission requests, and questions.
  • Answers permissions (POST /permission/{id}/reply) and questions (POST /question/{id}/reply or /reject), and stops OpenCode when the run ends.
  • Checks that OpenCode 1.19.0 or later is installed.

The agent runs shell commands without approvals. OpenCode's rules (in OPENCODE_CONFIG_CONTENT) deny files outside the project, web access, sub-agents, git commit, git push, git reset, sudo, and rm -rf on absolute paths. The agent asks the user only with the question tool. The task tells the agent to read the SDK credential from an environment variable, so no key goes into a prompt.

Testing

Unit tests cover the event decoder, the stream reader, the prompt, the environment filter, the permission rules, and the version check. A fake OpenCode HTTP server tests the run: the task, the events, permission replies, question answers, and a stream that ends early. The event and request formats come from the OpenCode v1.19.3 SDK types, and the adapter ran end to end against OpenCode 1.19.3 in local tests.

Diagram

flowchart TD
  caller["cmd/setup in #895<br/>runner.Check, runner.Start"]:::earlier
  agent["agent.go<br/>Task, Config, Event<br/>Runner, Run interfaces"]:::new
  oc["opencode.go<br/>OpenCode.Check, Start<br/>startRun, startSession<br/>pump, send, Close"]:::new
  prompt["prompt.go<br/>DefaultInstructions<br/>BuildPrompt, CredentialEnvVar"]:::new
  cfg["opencode_config.go<br/>openCodeConfig<br/>orderedRules, childEnv"]:::new
  srv["server.go<br/>startServer<br/>waitReady, stop"]:::new
  cli["client.go<br/>createSession, promptAsync<br/>replyPermission, answerQuestion<br/>subscribe"]:::new
  ev["events.go<br/>readSSE, decoder.decode"]:::new
  proc[["opencode serve<br/>HTTP API and /event SSE"]]:::ext
  caller -->|Check, Start| oc
  oc -.->|implements Runner| agent
  oc -->|BuildPrompt| prompt
  oc -->|openCodeConfig| cfg
  oc -->|startServer| srv
  oc -->|session calls| cli
  oc -->|pump: readSSE| ev
  srv -->|childEnv| cfg
  srv -->|exec| proc
  cli -->|HTTP| proc
  proc -.->|SSE stream| ev
  classDef new fill:#e6f0fa,stroke:#1d6fb8,stroke-width:2px,color:#19202b
  classDef chg fill:#fbf1e1,stroke:#a86a12,stroke-width:2px,color:#19202b
  classDef earlier fill:#ffffff,stroke:#7a8494,stroke-dasharray:5 4,color:#19202b
  classDef ext fill:#eceefa,stroke:#4e5a9c,stroke-width:2px,color:#19202b
Loading

Blue boxes are new files, orange boxes are changed files, dashed boxes are files from an earlier PR, and purple boxes are outside ldcli. A solid arrow goes from the caller to the code that it calls. A dashed arrow shows data that moves without a direct call.

agent.go is the contract that the wizard uses: Task, Config, Event, and the Runner and Run interfaces. opencode.go implements Runner and connects the other files. When Start runs, it gets the permission rules from opencode_config.go and starts opencode serve with server.go. server.go uses childEnv to remove every LD_ variable from the child environment. Then opencode.go creates a session, subscribes to events, and sends the task from prompt.go through client.go. events.go reads the SSE stream and turns it into Event values for the run's channel.

This PR adds only new files. Nothing calls the package until #895.

Stack

This is one of six PRs for the ldcli setup agent. Review and merge them in order, after #877 (Go 1.26):

  1. OpenCode adapter
  2. Wizard agent step
  3. macOS sandbox
  4. AI SDK and relay
  5. OpenCode handler
  6. Run fixes

They replace #876 and #878.

🤖 Generated with Claude Code


Devin Review


Note

Overview
Adds internal/setup/agent, a new OpenCode-backed runner for ldcli setup that is not wired into the CLI yet (follow-up PRs will add the wizard step).

The package defines Runner / Run APIs (Task, Event, permissions, questions) and an OpenCode implementation that spawns opencode serve on loopback with a per-run password, strips LD_* env vars from the child process, and injects permission rules via OPENCODE_CONFIG_CONTENT (allow read/edit/bash with denials for git push/commit, sudo, destructive rm, web, and paths outside the project). It creates a session, sends a built prompt that tells the agent to read SDK credentials from env vars (never ldcli tokens), streams SSE into typed events, and supports replying to permissions/questions.

Tests cover prompt/credential mapping, env filtering, config rules, version checks, SSE/event decoding, and HTTP integration with a fake OpenCode server.

Reviewed by Cursor Bugbot for commit a6fafd5. Bugbot is set up for automated code reviews on this repo. Configure here.

The new internal/setup/agent package runs one OpenCode session for a
setup task. It starts `opencode serve` in the project folder on the
loopback interface, with a random password for each run, and without
any LD_ environment variable. It creates the session, sends the task,
reads the event stream, and turns the events into text, tool calls,
changed files, token usage, permission requests, and questions. It
answers permissions and questions, and it stops OpenCode when the run
ends.

The agent runs shell commands without approvals. OpenCode's rules deny
files outside the project, web access, sub-agents, and a few commands
such as git push. The agent asks the user only with the question tool.

Nothing uses the package yet. The wizard step comes in the next change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
devin-ai-integration[bot]

This comment was marked as resolved.

cursor[bot]

This comment was marked as resolved.

atornsii and others added 2 commits October 20, 2026 11:22
…d map every client SDK

Close now sends a final error event and closes Events, if the run did not finish. A reader of Events no longer waits forever.

The child environment now sets OPENCODE_SERVER_USERNAME, so a value in the user's shell does not break the basic auth.

CredentialEnvVar now covers every client-side and mobile SDK ID and alias that the installer and the templates accept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
devin-ai-integration[bot]

This comment was marked as resolved.

The prompt for flutter-client-sdk now also names LAUNCHDARKLY_CLIENT_SIDE_ID for an app that runs on the web.

The error of a stopped run now uses active voice, and two comments now follow the sentence limits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

1 participant