Add OpenCode backend support#5
Conversation
There was a problem hiding this comment.
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.
| 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 | ||
| } |
| 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} |
| 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 | ||
| } |
| // 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"), |
|
Please rebase onto
I verified these APIs with OpenCode 1.18.1: native session creation, message history, asynchronous prompts, token/tool SSE events, and abort all wo |
Summary
opencode run --session, with usher-owned JSONL shadow transcripts instead of reading OpenCode's SQLite storeTest Plan