Skip to content

Add OpenCode backend support#5

Open
srstack wants to merge 1 commit into
nexustar:mainfrom
srstack:add-opencode-backend
Open

Add OpenCode backend support#5
srstack wants to merge 1 commit into
nexustar:mainfrom
srstack:add-opencode-backend

Conversation

@srstack

@srstack srstack commented Jul 15, 2026

Copy link
Copy Markdown

Summary

  • add an OpenCode backend driven by opencode run --session, with usher-owned JSONL shadow transcripts instead of reading OpenCode's SQLite store
  • wire OpenCode into discovery, routing, new-session creation, backend marks, and the model picker
  • document the OpenCode scope and add unit coverage for discovery and sender behavior

Test Plan

  • go test ./...

Copilot AI review requested due to automatic review settings July 15, 2026 08:30

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an OpenCode backend to usher, integrating it into session discovery, routing, new-session creation, backend marks, and the model picker, while keeping usher’s file-derived session model by writing usher-owned JSONL “shadow” transcripts instead of relying on OpenCode’s SQLite store.

Changes:

  • Add an OpenCode sender backend driven by opencode run --session, writing Claude-shaped JSONL shadow transcripts for discovery/rendering.
  • Wire OpenCode into routing/model selection (UI + server allowlist + backend mapping) and trigger discovery upserts after session creation.
  • Add unit tests for OpenCode sender behavior and discovery source detection.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
README.md Documents OpenCode support, enabling conditions, flags, and scope (shadow JSONL).
internal/web/static/style.css Adds OpenCode accent color and UI styling hooks for backend mark/model picker.
internal/web/static/render.js Adds an OpenCode backend mark SVG and backendMark mapping for opencode.
internal/web/static/detail.js Adds OpenCode model option group and conditional removal when backend isn’t available.
internal/web/server.go Allows create-session model value opencode (sentinel for OpenCode default).
internal/sender/sender.go Adds OpenCode sender plumbing and exposes Sender.Path() for post-create discovery upsert.
internal/sender/opencode.go Implements the OpenCode backend runner and shadow JSONL writer.
internal/sender/opencode_test.go Adds unit test coverage for OpenCode sender streaming + shadow log writing.
internal/router/router.go Maps model opencode to the OpenCode backend and upserts created transcript paths.
internal/router/router_test.go Extends BackendForModel test coverage for opencode.
internal/discovery/sources.go Adds OpenCode discovery source for usher-owned shadow JSONL transcripts.
internal/discovery/sources_test.go Adds unit test verifying discovery picks up OpenCode shadow JSONL logs.
cmd/usher/main.go Adds CLI flags and backend enablement logic for OpenCode, plus a helper to detect the binary.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +183 to +190
func (b *openCodeBackend) args(id, prompt, cwd, model string) []string {
args := []string{"run", "--session", id, "--dir", cwd}
if model != "" && model != "default" {
args = append(args, "--model", model)
}
args = append(args, prompt)
return args
}
Comment on lines +36 to +41
func NewOpenCode(openCodeCmd, sessionsDir string, maxLive int, logger *slog.Logger) *Sender {
if logger == nil {
logger = slog.Default()
}
_ = maxLive // opencode run is one process per turn; no warm pool yet.
t := timing{confirm: 8 * time.Second, poll: 150 * time.Millisecond}
Comment thread cmd/usher/main.go
Comment on lines +385 to +392
func commandExists(cmd string) bool {
if strings.ContainsRune(cmd, os.PathSeparator) {
info, err := os.Stat(cmd)
return err == nil && !info.IsDir() && info.Mode().Perm()&0o111 != 0
}
_, err := exec.LookPath(cmd)
return err == nil
}
Comment thread internal/router/router.go
Comment on lines +204 to 211
// to an explicit backend); gpt-*/o-series/codex are Codex, "opencode" is
// OpenCode, everything else (claude-*, opus, sonnet, haiku, fable) is Claude.
func backendForModel(model string) string {
m := strings.ToLower(strings.TrimSpace(model))
switch {
case m == "opencode":
return "opencode"
case strings.HasPrefix(m, "gpt"), strings.HasPrefix(m, "o1"),
@nexustar

Copy link
Copy Markdown
Owner

Please rebase onto 720d375 refactor: abstract agent backends and rebuild the OpenCode integration around the new backend interfaces.

  • Implement OpenCode as a standalone backend.Runtime rather than adding OpenCode branches to sender.Sender.
  • Match the Codex lifecycle model: run one isolated opencode serve worker per live root session, managed as sessionID → worker.
  • To create a session, start an unregistered worker, call POST /session, obtain the native OpenCode session ID, and register the worker after creation succeeds. OpenCode does not support caller-assigned session IDs.
  • To resume a session, start a worker for its native ID and continue it through the OpenCode API.
  • Follow the Codex manager design for maxLiveSessions, busy checks, LRU eviction, concurrent-start protection, process isolation, and restart recovery.
  • Use the native REST API for lifecycle operations:
    • POST /session
    • GET /session/:id/message
    • POST /session/:id/prompt_async
    • POST /session/:id/abort
  • Consume each worker’s /event SSE stream and translate native text, reasoning, tool, usage, busy/idle, and error events into usher’s backend.Event vocabulary.
  • Remove the shadow JSONL implementation, opencode-sessions-dir, and line-by-line wrapping of formatted stdout. OpenCode already exposes complete sessions, messages, and live events, so maintaining a second transcript is unnecessary and risks divergence.
  • Treat "opencode" only as the backend identifier. It must not become --model opencode. Omit the model when using OpenCode’s default; a native provider-backed ModelProvider can be added separately.
  • The existing backend.Runtime, backend-assigned Start, and ModelProvider abstractions are sufficient. The remaining architectural gap is that Transcript.ReadTurns(path, ...) and discovery are still file-path based. Please extend them to support an API-backed session source instead of working around that limitation with shadow files.
  • Add tests for native ID creation, resume, REST history, SSE text/reasoning/tool events, abort, worker LRU, concurrency limits, process exit, and restart recovery.

I verified these APIs with OpenCode 1.18.1: native session creation, message history, asynchronous prompts, token/tool SSE events, and abort all wo

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.

3 participants