Skip to content

OpenCode Architecture Index

OpenCode Architecture Index (packages/opencode/src)

Section titled “OpenCode Architecture Index (packages/opencode/src)”
  • Repository: references/opencode
  • Commit SHA: 7ed449974864361bad2c1f1405769fd2c2fcdf42
  • Indexed scope: packages/opencode/src

OpenCode is a Bun + TypeScript AI agent runtime with:

  • A CLI entrypoint (src/index.ts) that registers all commands and initializes logging + DB migration.
  • A headless HTTP/SSE/WebSocket server (src/server/server.ts) used by TUI, web, and attached clients.
  • A session engine (src/session/*) that manages message state, tool execution, retries, snapshots, compaction, and summaries.
  • A dynamic provider layer (src/provider/*) that maps config/auth/models to AI SDK language models.
  • First-party + plugin + MCP tool orchestration (src/tool/*, src/mcp/*).

This repo is client-server in shape, but the core logic is mostly reusable as local modules.

  • acp/: Agent Client Protocol bridge — maps OpenCode sessions/tools/permissions to external ACP clients.
  • agent/: agent definitions, defaults, permissions, generation prompt.
  • bun/: Bun runtime integration — package registry, dynamic SDK installs, process spawning.
  • bus/: typed event pub/sub system with Zod schemas, instance-scoped + global cross-process bus.
  • cli/: command implementations (run, serve, attach, etc.) and TUI boot.
  • config/: layered config loading + schema + plugin/agent/command discovery.
  • control/: OAuth account/credential management for OpenCode Control (enterprise portal).
  • file/: file abstraction layer — git integration, ignore rules, file watching, ripgrep search, content handling.
  • flag/: feature flags and environment variable registry (30+ flags).
  • global/: singleton paths (XDG-compliant data/config/cache/state directories).
  • id/: monotonic ID generation with ascending/descending timestamp-based IDs.
  • lsp/: language server registry, client lifecycle, diagnostics, symbol/definition operations.
  • mcp/: MCP local/remote clients, OAuth flow, tool/prompt/resource bridge.
  • plugin/: plugin system — codex and copilot plugins for provider-specific behavior.
  • project/: project identity, git/worktree discovery, per-instance scoping.
  • provider/: provider/model inventory, SDK loader, auth/env/config adaptation.
  • pty/: managed pseudo-terminal sessions + websocket attachment.
  • server/: Hono API, SSE event stream, auth, CORS, proxy fallback, route modules.
  • session/: chat/message persistence, prompt loop, stream processor, compaction/summary/retry/status.
  • share/: session sharing — export/import, shareable URLs, SQL persistence.
  • shell/: shell detection (bash/zsh/cmd), platform-specific process tree management.
  • snapshot/: lightweight git snapshot sandbox used for patch/diff/revert tracking.
  • storage/: dual-layer persistence — legacy JSON file storage + SQLite via Drizzle ORM.
  • tool/: core tool definitions and tool registry/gating.
  • util/: shared utilities — context DI, git wrapper, token estimation, async queue, locks, filesystem helpers.
  • worktree/: git worktree management — create/remove/reset isolated sandboxes.

Flow:

  1. Register unhandled rejection/exception loggers.
  2. Build yargs CLI and global options (--print-logs, --log-level).
  3. Middleware initializes log subsystem and sets AGENT=1/OPENCODE=1 env markers.
  4. One-time DB migration gate: if opencode.db marker missing, run JsonMigration.run with progress bar.
  5. Register commands (run, serve, attach, agent, mcp, db, etc.).
  6. Parse CLI and route failures through FormatError + UI.error.

Full command registry: RunCommand, GenerateCommand, AuthCommand, AgentCommand, UpgradeCommand, UninstallCommand, ServeCommand, AttachCommand, TuiThreadCommand, McpCommand, GithubCommand, ExportCommand, ImportCommand, PrCommand, SessionCommand, DbCommand, DebugCommand, WebCommand, StatsCommand, ModelsCommand.

2) Instance bootstrap (src/project/bootstrap.ts)

Section titled “2) Instance bootstrap (src/project/bootstrap.ts)”

When a request/session is bound to a project instance:

  • Initialize plugin runtime.
  • Initialize share subsystem, formatter, LSP, file watcher, file bus, VCS watcher, snapshot cleaner, truncation cleaner.
  • Subscribe command execution events (/init command marks project initialized).

All commands use a bootstrap() wrapper that calls Instance.provide() + Instance.dispose() to ensure project/directory context is set up and torn down properly.

Instance context (src/project/instance.ts)

Section titled “Instance context (src/project/instance.ts)”

Instance.provide({ directory, fn }) creates a scoped context with:

  • directory: current working directory for this request/session.
  • worktree: VCS sandbox boundary (git worktree root or / fallback).
  • project: persisted project identity.

Critical behavior:

  • Instance.state(init, dispose) gives per-directory cached state stores for modules (LSP, MCP, session trackers, etc.).
  • containsPath(filepath) treats paths inside directory or worktree as in-bound for permission logic.
  • Instance.disposeAll() tears down all cached state when config changes.

Project identification (src/project/project.ts)

Section titled “Project identification (src/project/project.ts)”

For git repos:

  • Walk up to .git.
  • Derive stable project id from root commit list (git rev-list --max-parents=0 --all).
  • Resolve top-level + common-dir for worktree semantics. Non-git fallback:
  • Project id global, worktree /.

Result: project identity is stable across nested directories and worktrees.

The bus is a typed event pub/sub system using Zod for runtime schema validation. It’s instance-scoped (per project directory) and provides type-safe event handling.

Exports:

  • Bus.publish(def, properties) — publish typed events to local subscriptions AND GlobalBus.
  • Bus.subscribe(def, callback) — subscribe to specific event type; returns unsubscribe function.
  • Bus.subscribeAll(callback) — wildcard subscription to all events.
  • Bus.once(def, callback) — one-time subscription; callback returns "done" to unsubscribe.
  • Bus.InstanceDisposed — predefined event when instance is disposed.

Internal state per instance: Map<string, Subscription[]> keyed by event type or "*" for wildcard.

Event flow:

  1. Code calls Bus.publish(SomeEvent.def, { prop: value }).
  2. Bus creates payload { type, properties }.
  3. Calls all subscriptions matching type or "*".
  4. Emits to GlobalBus for inter-process distribution.

Event type registry (src/bus/bus-event.ts)

Section titled “Event type registry (src/bus/bus-event.ts)”
  • BusEvent.define(type, propertiesSchema) — registers event with Zod schema in global registry.
  • BusEvent.payloads() — returns z.discriminatedUnion("type", [...all events]) for validation.
  • Global registry = Map<string, Definition>() tracks all event types.
  • GlobalBus is a Node.js EventEmitter for cross-process events.
  • Shape: { directory?: string, payload: { type, properties } }.
  • Allows instances in different processes to communicate; each instance publishes to both local subs and GlobalBus.

JSON files (legacy) migrating to SQLite. Uses migrations, file-based locks, and lazy-loaded state.

JSON file storage (src/storage/storage.ts)

Section titled “JSON file storage (src/storage/storage.ts)”

API:

  • Storage.read<T>(key: string[]) — key arrays map to file paths: ["session", "abc123"]$DATA/storage/session/abc123.json.
  • Storage.write<T>(key, content) — write JSON to computed path.
  • Storage.update<T>(key, fn) — read-modify-write with draft function.
  • Storage.remove(key) — delete file.
  • Storage.list(prefix) — list all keys under prefix.
  • Uses Lock.read() and Lock.write() for concurrent access control.

Storage hierarchy:

~/.opencode/data/
├── storage/
│ ├── project/*.json
│ ├── session/PROJECT_ID/*.json
│ ├── message/SESSION_ID/*.json
│ ├── message_diff/SESSION_ID.json
│ ├── part/MESSAGE_ID/*.json
│ ├── todo/SESSION_ID.json
│ ├── permission/PROJECT_ID.json
│ ├── session_share/SESSION_ID.json
│ └── migration # migration marker file
└── opencode.db # SQLite database (new)

Uses Drizzle ORM over bun:sqlite.

API:

  • Database.Client — lazy-initialized Drizzle ORM instance.
  • Database.use(callback) — provides DB client or transaction context.
  • Database.transaction(callback) — wraps in SQLite transaction, runs effects after commit.
  • Database.effect(fn) — queues function to run after transaction commits or immediately if outside transaction.

SQLite pragmas applied:

  • journal_mode = WAL (Write-Ahead Logging for concurrency).
  • synchronous = NORMAL.
  • busy_timeout = 5000 (5s wait for locks).
  • cache_size = -64000 (64MB cache).
  • foreign_keys = ON.
  • wal_checkpoint(PASSIVE).

JSON to SQLite migration (src/storage/json-migration.ts)

Section titled “JSON to SQLite migration (src/storage/json-migration.ts)”

JsonMigration.run(sqlite, options) migrates all JSON files to SQLite in a single transaction:

  1. Scans all JSON files via Bun.Glob patterns.
  2. Projects first (no FK deps) → sessions → messages → parts → todos, permissions, shares.
  3. Batch processing: 1000 items per batch to avoid OOM.
  4. Orphan handling: skips records without valid parent IDs, logs warnings.
  5. Error resilience: Promise.allSettled() for individual file reads.
  6. Progress callback for TUI progress bar.

Re-exports all table definitions: ControlAccountTable, SessionTable, MessageTable, PartTable, TodoTable, PermissionTable, SessionShareTable, ProjectTable.

Shared Timestamps columns: time_created (auto-set on insert), time_updated (auto-set on update).

Layered, mergeable config with precedence order (low → high):

  1. Remote .well-known/opencode (org defaults).
  2. Global ~/.config/opencode/opencode.json{,c}.
  3. Custom config path ($OPENCODE_CONFIG).
  4. Project config (opencode.json{,c} in worktree up to root).
  5. .opencode/ directories (project & home).
  6. Inline config ($OPENCODE_CONFIG_CONTENT).
  7. Managed config (/etc/opencode/opencode.json{,c} — highest priority).

Merging: custom merge() concatenates arrays (plugins, instructions accumulate across layers) instead of replacing.

File interpolation: {env:VAR} and {file:/path} substitution before parsing. JSONC support with comments and trailing commas.

{
theme?: string
keybinds?: Keybinds // 50+ fields with vim-like defaults
tui?: { scroll_speed, scroll_acceleration, diff_style }
server?: { port, hostname, mdns, mdnsDomain, cors }
command?: Record<string, Command>
skills?: { paths, urls }
plugin?: string[] // npm packages or file:// URLs
snapshot?: boolean
share?: "manual" | "auto" | "disabled"
model?: string // provider/model format
small_model?: string
default_agent?: string
agent?: Record<string, Agent>
provider?: Record<string, Provider>
mcp?: Record<string, Mcp>
formatter?: false | Record<string, FormatterConfig>
lsp?: false | Record<string, LSPConfig>
instructions?: string[]
permission?: Permission // Record<string, "ask"|"allow"|"deny">
compaction?: { auto, prune, reserved }
experimental?: { batch_tool, openTelemetry, primary_tools, continue_loop_on_deny, mcp_timeout }
}
  • loadCommand(dir) — scans {command,commands}/**/*.md, parses YAML frontmatter via ConfigMarkdown.parse().
  • loadAgent(dir) — scans {agent,agents}/**/*.md.
  • loadMode(dir) — scans {mode,modes}/*.md (legacy, migrated to agent).
  • loadPlugin(dir) — scans {plugin,plugins}/*.{ts,js}, converts to file:// URLs.

Markdown frontmatter parser (src/config/markdown.ts)

Section titled “Markdown frontmatter parser (src/config/markdown.ts)”
  • Uses gray-matter for YAML frontmatter parsing.
  • Regex patterns: @file references (@path/to/file) and !`command` shell blocks.
  • Fallback sanitizer for non-YAML-compliant frontmatter: wraps values with colons in block scalars.
Permission = Record<string, PermissionAction | PermissionObject>
PermissionAction = "ask" | "allow" | "deny"
PermissionObject = Record<string, PermissionAction>
Fields: read, edit, glob, grep, list, bash, task, external_directory,
todowrite, todoread, question, webfetch, websearch, codesearch, lsp, doom_loop, skill, ...

Exports:

  • File.status() — runs git diff HEAD --numstat + git ls-files --others + git diff --name-only --diff-filter=D HEAD. Returns Info[] with status: "added" | "modified" | "deleted".
  • File.read(file) — images: fast path via extension check, base64 encode. Binary: empty content. Text: read + generate git diff using structuredPatch() from diff package.
  • File.list(dir) — readdir, exclude .git/.DS_Store, apply gitignore via ignore package, sort directories first.
  • File.search(input) — fuzzy search via fuzzysort over pre-cached file list from Ripgrep.files(). Hides hidden files unless query starts with .. Limits: 100 default.

Access control: checks Instance.containsPath() to prevent symlink escapes.

33 folder names to skip: dist, build, out, target, node_modules, .git, .next, __pycache__, .vscode, .idea, .cache, etc.

File globs: **/*.swp, **/*.pyc, **/.DS_Store, **/logs/**, **/coverage/**, etc.

Match algorithm: whitelist check → folder blacklist → file glob match → extra globs.

  • Uses @parcel/watcher (native bindings: inotify on Linux, FSEvents on macOS, Windows filesystem events).
  • Subscribes to Instance.directory + .git separately.
  • Filters via FileIgnore.PATTERNS + config ignore list.
  • Publishes FileWatcher.Event.Updated via Bus.

Binary management:

  • Checks Bun.which("rg") first (system ripgrep).
  • Falls back to $OPENCODE_BIN/rg.
  • Auto-downloads v14.1.1 from GitHub if missing — platform-specific (arm64/x64, darwin/linux/win32, musl/glibc variants).

Functions:

  • Ripgrep.files({ cwd, glob?, hidden, follow, maxDepth }) — yields file paths line-by-line via rg --files.
  • Ripgrep.search({ cwd, pattern, glob?, limit, follow }) — runs rg --json, parses match events. Returns Match[] with path, line_number, submatches.
  • Ripgrep.tree({ cwd, limit }) — builds directory hierarchy via BFS traversal over file list. Returns formatted tree string.

Prevents concurrent writes and detects external modifications:

  • FileTime.read(sessionID, file) — records session read timestamp.
  • FileTime.assert(sessionID, filepath) — checks mtime hasn’t changed since read.
  • FileTime.withLock(filepath, fn) — serializes writes to same file via chained promises.

Persistence model (src/session/session.sql.ts)

Section titled “Persistence model (src/session/session.sql.ts)”

Tables:

  • session: metadata, title, parent, summary counters, permission overrides, revert markers.
  • message: user/assistant info JSON.
  • part: message parts (text/tool/reasoning/patch/step/subtask/etc.) JSON.
  • todo: ordered todo rows.
  • permission: persisted project approval rules.

Operations:

  • Session.create(parentID?, title?) — new session.
  • Session.get(sessionID) — retrieve by ID.
  • Session.fork(sessionID, messageID?) — clone session history up to message.
  • Session.setTitle, setArchived, setPermission, setRevert, setSummary.
  • Session.remove(sessionID) — cascade delete children.
  • Session.list(directory?, roots?, start?, search?, limit?) — paginated query.

Message operations:

  • updateMessage(msg) — upsert.
  • updatePart(part) — upsert.
  • updatePartDelta(sessionID, messageID, partID, field, delta) — streaming incremental updates.
  • removeMessage, removePart — delete with bus events.

Cost tracking:

  • Session.getUsage(model, usage, metadata?) — converts token counts to cost via model.cost.
  • Handles Anthropic vs others token accounting (Anthropic includes cached tokens in inputTokens, others exclude).
  • Supports over-200k context pricing tier.
  • Formula: (tokens.input * cost.input + tokens.output * cost.output + tokens.cache.read * cost.read + tokens.cache.write * cost.write + tokens.reasoning * cost.output) / 1_000_000.

Bus events: Session.Created, Updated, Deleted, Diff, Error, MessageV2.Event.Updated, Removed, PartUpdated, PartRemoved, PartDelta.

Message schema (src/session/message-v2.ts)

Section titled “Message schema (src/session/message-v2.ts)”

Part types (discriminated union):

  • TextPart — synthetic flag, ignored flag, time tracking, metadata.
  • ReasoningPart — model reasoning content (Reasoning models).
  • FilePart — attached files/images with source tracking (file/symbol/resource).
  • ToolPart — tool invocation with state machine: pending -> running -> completed | error.
  • StepStartPart, StepFinishPart — agentic step boundaries with cost accounting.
  • SnapshotPart, PatchPart — filesystem change tracking.
  • SubtaskPart — delegated subtask with model selection.
  • CompactionPart — message history compaction marker.
  • RetryPart — retry attempts with error info.

Output formats: OutputFormatText (default), OutputFormatJsonSchema (includes schema + retryCount up to 2).

Error types: OutputLengthError, AbortedError, StructuredOutputError, AuthError, APIError, ContextOverflowError.

toModelMessages(...) adaptation:

  • Converts internal message/part graph to AI SDK ModelMessage[].
  • Translates tool results into tool-output parts.
  • Injects fallback user file parts for providers with weaker media-in-tool-result handling.
  • Handles compacted tool outputs with placeholder text.

System prompt construction (src/session/system.ts)

Section titled “System prompt construction (src/session/system.ts)”

Model-specific prompt selection:

  • gpt-5.2-codex → PROMPT_CODEX. Gemini → PROMPT_GEMINI.
  • Claude → PROMPT_ANTHROPIC. Trinity → PROMPT_TRINITY. Default (Qwen, others) → PROMPT_ANTHROPIC_WITHOUT_TODO.

Environment block injected dynamically: working directory, git status, platform, date — wrapped in <env> tags.

Assembly in LLM.stream(): combines provider() + input.system + user.system. Plugins can transform via experimental.chat.system.transform hook. Maintains 2-part structure for cache efficiency.

Instruction loading (src/session/instruction.ts)

Section titled “Instruction loading (src/session/instruction.ts)”

Files scanned (priority order): AGENTS.md, CLAUDE.md, CONTEXT.md (deprecated).

System paths resolution:

  1. Project-level: walks up from Instance.directory to worktree root using Filesystem.findUp(). Stops at first match.
  2. Global-level: checks $OPENCODE_CONFIG_DIR/AGENTS.md, ~/.opencode/AGENTS.md, ~/.claude/CLAUDE.md. First existing file wins.
  3. Config instructions array: supports https:// URLs (fetched with 5s timeout), ~/path expansion, absolute paths, relative paths (globUp from project root).

Claiming mechanism: per-messageID map of claimed file paths prevents duplicate inclusion within a single message.

Prompt entry + loop (src/session/prompt.ts)

Section titled “Prompt entry + loop (src/session/prompt.ts)”

Primary path:

  1. SessionPrompt.prompt(...) creates user message + parts.
  2. Optionally applies legacy per-prompt tools permissions to session permission rules.
  3. Calls loop({ sessionID }) unless noReply.

State machine: per-sessionID AbortController + callbacks array. assertNotBusy() throws Session.BusyError if session active.

Loop behavior:

  • Pull recent, non-compacted message stream.
  • Detect pending subtask or compaction parts and process those first.
  • Detect context overflow and enqueue auto-compaction.
  • Resolve active agent + model.
  • Resolve all tools (built-in + MCP) with provider-schema transformation.
  • Stream model output via SessionProcessor.process(...).
  • If structured JSON output mode is requested, enforce StructuredOutput tool completion with retry (up to 2 attempts).

LLM.stream builds provider call params:

  • System prompt stack (provider default prompt + runtime instructions + user/system additions).
  • Provider/agent/model options merge.
  • Tool filtering based on permission (PermissionNext.disabled(...)).
  • Provider headers and retries.
  • Tool call repair hook (invalid casing -> lowercase fallback or invalid tool).

Streaming event processor (src/session/processor.ts)

Section titled “Streaming event processor (src/session/processor.ts)”

Consumes SDK stream events and persists incremental parts:

  • reasoning-start/delta/end -> reasoning parts.
  • tool-input-start and tool-call/result/error -> tool state lifecycle.
  • text-start/delta/end -> text parts.
  • start-step/finish-step -> snapshot and token/cost accounting.

Important controls:

  • Doom loop detection: repeated same tool+input 3 times requests explicit permission.
  • Permission/question rejection can halt or continue loop depending on config.
  • Retry policy for transient provider errors (SessionRetry).
  • Auto-compaction signal when token usage nears model limits.

Compaction and pruning (src/session/compaction.ts)

Section titled “Compaction and pruning (src/session/compaction.ts)”
  • isOverflow(...) compares token usage against model input/context minus reserved buffer.
  • process(...) spawns hidden compaction agent to summarize prior conversation.
  • prune(...) marks old completed tool outputs as compacted after token budget thresholds, keeping recent context + protected tools.

Session diff summary (src/session/summary.ts)

Section titled “Session diff summary (src/session/summary.ts)”

After steps complete:

  • Compute aggregate file additions/deletions/files changed via snapshot diff.
  • Persist per-session summary stats and per-message diff snippets.

Tool.define(id, init) wraps every tool with:

  • Zod parameter validation.
  • Unified error on invalid args.
  • Output truncation via Truncate.output(...) unless tool marks itself pre-truncated.

Registry + model-aware gating (src/tool/registry.ts)

Section titled “Registry + model-aware gating (src/tool/registry.ts)”

Built-ins + plugins are merged and exposed based on runtime:

  • Core tools always loaded (bash, read, glob, grep, task, etc.).
  • apply_patch preferred for modern GPT family; edit/write disabled in that mode.
  • websearch/codesearch gated to opencode provider or explicit env enable.
  • Optional tools behind flags (lsp, plan_enter/exit, batch).

Truncation system (src/tool/truncation.ts)

Section titled “Truncation system (src/tool/truncation.ts)”
  • Truncate.output(content) — truncates tool output exceeding MAX_LINES (2000) or MAX_BYTES (50KB).
  • Saves full output to Global.Path.data/tool-output/{id} with file path hint in truncation message.
  • Automatic cleanup after 7 days; scheduler runs hourly.
  • Tools can mark themselves as pre-truncated to skip double-truncation.

tool/bash.ts — Shell command execution:

  • Lazy-loads tree-sitter bash WASM parser to derive command AST for permission checks.
  • Shell detection via Shell.acceptable() (excludes fish, nu).
  • Parameters: command (string), timeout (ms, default 2min), workdir (defaults to Instance.directory), description (5-10 word summary).
  • Execution: spawns via child_process.spawn, streams metadata incrementally. MAX_METADATA_LENGTH = 30_000.

tool/read.ts — File/directory reading:

  • Parameters: filePath (absolute), offset (1-indexed line), limit (default 2000 lines).
  • Constants: DEFAULT_READ_LIMIT = 2000, MAX_LINE_LENGTH = 2000, MAX_BYTES = 50 * 1024.
  • Handles: directories (sorted listing), images (base64 encode), PDFs, binary detection, text files.
  • Post-read: injects instruction files via InstructionPrompt.resolve().
  • File-not-found: suggests similar filenames from parent directory.

tool/edit.ts — Targeted file editing:

  • Robust replacement strategy chain: exact match → trimmed → anchor → whitespace/indent/escape/context-aware.
  • Parameters: filePath, old_string, new_string.
  • Post-edit: triggers LSP.touchFile() and reports diagnostics (errors only, max 20 per file).
  • Uses FileTime.assert() to detect external modifications before write.

tool/write.ts — Full file write:

  • Parameters: content, filePath.
  • Post-write: publishes File.Event.Edited + FileWatcher.Event.Updated, triggers LSP diagnostics.
  • Constants: MAX_DIAGNOSTICS_PER_FILE = 20, MAX_PROJECT_DIAGNOSTICS_FILES = 5.

tool/apply_patch.ts — Unified diff/patch application:

  • Preferred for modern GPT family models over edit/write.
  • Patch format: *** Begin Patch / *** End Patch with *** Add/Update/Delete File: headers.
  • Multi-pass line matching: exact → rstrip → trim → Unicode normalization (smart quotes, em-dashes, ellipsis).
  • Returns per-file metadata: additions, deletions, type (add/update/delete/move).

tool/task.ts — Subagent execution:

  • Creates child session for subagent execution.
  • Enforces anti-recursion depth limits. Subagents cannot use todowrite/todoread.

tool/batch.ts — Parallel tool execution:

  • Executes up to 25 tool calls in parallel and emits per-tool parts.

tool/glob.ts — Glob pattern file search:

  • Parameters: pattern (glob string), path? (search directory, defaults to Instance.directory).
  • Uses Ripgrep.files() for fast file matching.
  • Returns max 100 files sorted by mtime (newest first).
  • Permission: glob with pattern allowlisting.
  • Output: absolute paths, file count, truncation flag.

tool/grep.ts — Content search via ripgrep:

  • Parameters: pattern (regex), path? (search directory), include? (glob filter e.g. “*.js”).
  • Direct ripgrep invocation with -nH --hidden --no-messages.
  • Max 100 matches. Line text capped at 2000 chars.
  • Exit code handling: 0=matches found, 1=no matches, 2=errors but may have partial matches.
  • Handles broken symlinks silently with --no-messages.

tool/multiedit.ts — Multi-file editing:

  • Parameters: filePath, edits[] array of { filePath, oldString, newString, replaceAll? }.
  • Wraps EditTool, applies edits in sequence to a single file.
  • Returns combined result of last edit. Each edit must produce different content.

tool/question.ts — User question:

  • Parameters: questions array of Question.Info objects with type, options.
  • Integrates with OpenCode’s Question system — blocks execution until user answers.
  • Returns metadata with answers. Session-aware: stores answers in context.
  • Gated behind OPENCODE_ENABLE_QUESTION_TOOL flag.

tool/skill.ts — Skill loading:

  • Loads skill payload + sampled files from configured skill paths/URLs.
  • Injects a structured skill block into the conversation context.
  • Skills are markdown files with YAML frontmatter defining parameters and templates.

tool/plan.ts — Plan/build mode:

  • plan_enter / plan_exit tool pair for mode handoff.
  • Uses question prompts to confirm mode transitions with user.
  • Plan agent denies all edit tools except .opencode/plans/*.md files.
  • Gated behind OPENCODE_EXPERIMENTAL_PLAN_MODE or OPENCODE_EXPERIMENTAL.

tool/webfetch.ts — Web content fetching:

  • Parameters: url (http/https), format? (“text”|“markdown”|“html”, default: markdown), timeout? (seconds, max 120).
  • Max response 5MB. Image detection: returns base64-encoded images as attachments.
  • HTML to markdown conversion via TurndownService.
  • Cloudflare bot detection: retry with altered User-Agent.
  • Accept header negotiation for requested format.

tool/websearch.ts — Web search:

  • Parameters: query, numResults? (1-100, default 8), livecrawl?, type? (“auto”|“fast”|“deep”), contextMaxCharacters?.
  • Backend: Exa API (https://mcp.exa.ai/mcp) via JSON-RPC 2.0 with SSE response.
  • 25-second timeout. Returns aggregated context text from multiple results.
  • Gated to opencode provider or OPENCODE_ENABLE_EXA.

tool/codesearch.ts — Code search:

  • Parameters: query (natural language), tokensNum (1000-50000, default 5000).
  • Backend: Exa get_code_context_exa endpoint. 30-second timeout.
  • Token count determines context depth. Returns code snippets and documentation.
  • Gated to opencode provider.

tool/lsp.ts — LSP tool exposure:

  • Operations: goToDefinition, findReferences, hover, documentSymbol, workspaceSymbol, etc.
  • Gated behind OPENCODE_EXPERIMENTAL_LSP_TOOL.

tool/external-directory.ts — External directory permission gating:

  • Checks if path is outside project boundary. Requests external_directory permission with { path, kind } metadata.

tool/invalid.ts — Invalid tool handler:

  • Catches malformed tool calls and returns helpful error message.

Nearly every mutating or sensitive tool path performs:

  1. Path boundary check (assertExternalDirectory).
  2. Permission request via ctx.ask(...) with patterns + metadata.
  3. Operation execution.

Defines many language server adapters with:

  • Root detection strategy (NearestRoot(...), project markers).
  • File extension filters.
  • Spawn logic (local binaries, npm/bun installs, remote release download fallback).
  • Maintains server catalog, connected clients, failed keys, and in-flight spawns.
  • Deduplicates client per (serverID, root).
  • Exposes operations: hover, definition, references, symbols, call hierarchy, diagnostics.

LSP client protocol bridge (src/lsp/client.ts)

Section titled “LSP client protocol bridge (src/lsp/client.ts)”
  • JSON-RPC stdio connection via child process spawn.
  • Sends initialize capabilities and workspace config.
  • Publishes diagnostics via bus with debounce for stabilization (prevents flicker from rapid diagnostic updates).
  • notify.open(...) uses didOpen/didChange and watched-file notifications.
  • Per-client state: capabilities, open documents, pending requests, diagnostic buffer.

Maps file extensions to language server IDs for automatic server selection. Covers TypeScript, Python, Rust, Go, Java, C/C++, Ruby, PHP, and many more.

Supports:

  • Local stdio MCP servers.
  • Remote StreamableHTTP and SSE transports.
  • OAuth-enabled remote auth with callback server and persisted token/state handling.

Tool conversion path:

  • client.listTools() -> sanitize schema -> dynamicTool(...) wrappers.
  • Tools are namespaced as <server>_<tool>.
  • Per-server/request timeouts configurable.

Also exposes prompt/resource discovery and direct prompt/resource reads.

OAuth flow (src/mcp/oauth-callback.ts, src/mcp/oauth-provider.ts, src/mcp/auth.ts)

Section titled “OAuth flow (src/mcp/oauth-callback.ts, src/mcp/oauth-provider.ts, src/mcp/auth.ts)”
  • oauth-callback.ts: spins up local HTTP server for OAuth redirect handling. Serves callback page that captures auth code.
  • oauth-provider.ts: implements MCP OAuth provider interface with persisted token/state. Handles PKCE code challenge, token exchange, and refresh.
  • auth.ts: credential storage and retrieval for MCP servers. Persists tokens per-server in config directory.
  • Spawns terminal sessions via bun-pty native bindings.
  • Keeps rolling output buffer (2MB cap) and cursor metadata per PTY session.
  • Supports reconnect with cursor-based replay — clients send their last cursor position, server replays buffered output from that point.
  • Each PTY has: id, shell path, working directory, environment, dimensions (rows/cols).
  • Lifecycle: create → connect (WebSocket) → write input → read output → resize → destroy.
  • Hono app with OpenAPI descriptions on every route.
  • Basic auth support via OPENCODE_SERVER_PASSWORD / OPENCODE_SERVER_USERNAME env flags.
  • CORS allowlist + localhost/tauri automatic handling.
  • Instance binding middleware: every request runs inside Instance.provide(...) scope, ensuring project context is set.
  • SSE /event stream for all bus events + 10-second heartbeat to keep proxies alive.
  • All route modules lazily loaded via lazy() to minimize startup cost.
  • Route modules: project, session, pty, mcp, file, config, provider, question, permission, tui, experimental, global.

Server error handling (src/server/error.ts)

Section titled “Server error handling (src/server/error.ts)”

Standard error responses:

  • 400: { data, errors[], success: false }.
  • 404: NotFoundError schema.
  • Event.Connectedserver.connected bus event (sent on SSE connection).
  • Event.Disposedglobal.disposed bus event (sent on shutdown).

Agent.Info schema: { name, description?, mode: "subagent"|"primary"|"all", native?, hidden?, topP?, temperature?, color?, permission: PermissionNext.Ruleset, model?, variant?, prompt?, options, steps? }.

  1. build (primary, visible) — default agent. Full tool permissions. Allows questions and plan mode.
  2. plan (primary, visible) — planning mode. Denies all edit tools except .opencode/plans/*.md. Forces plan_exit.
  3. general (subagent) — multi-task executor. Denies todo read/write.
  4. explore (subagent) — read-only. Allows: grep, glob, list, bash, webfetch, websearch, codesearch, read.
  5. compaction (primary, hidden) — session summarization. Minimal permissions.
  6. title (primary, hidden) — auto-generate session title. Temperature 0.5.
  7. summary (primary, hidden) — session summarization.

Lazy-loaded via Instance.state(): builds permission defaults, merges user config from cfg.agent overrides, caches per instance.

Agent.generate({ description, model? }) uses generateObject() with Zod schema to create new agents: { identifier, whenToUse, systemPrompt }.

Location: ~/.opencode/data/snapshot/{projectID}/ — a separate .git directory per project (not the project’s own git repo).

Operations:

  • Snapshot.track() — initializes git repo if needed, stages all files, returns tree hash via git write-tree. Disables core.autocrlf on Windows.
  • Snapshot.patch(hash) — stages current files, diffs against hash via git diff --name-only. Returns { hash, files: string[] }.
  • Snapshot.restore(hash)git read-tree + git checkout-index -a -f to restore filesystem state.
  • Snapshot.revert(patches) — iterates patches, git checkout {hash} -- {file} per file. Deletes files that didn’t exist in snapshot.
  • Snapshot.diff(hash) — full unified diff text against current state.
  • Snapshot.diffFull(from, to) — per-file diffs with before/after content, additions/deletions counts, status (added/deleted/modified). Uses git show to retrieve content at each tree.
  • Snapshot.cleanup(prune) — runs git gc --prune={date} on snapshot repo. Hourly scheduler.

Skips operations when config.snapshot === false or Flag.OPENCODE_CLIENT === "acp".

ACP.Agent implements external ACPAgent interface, bridging OpenCode sessions/tools/permissions to external clients.

Capabilities: newSession(), loadSession(), unstable_listSessions(), unstable_forkSession(), unstable_resumeSession(), unstable_setSessionModel(), setSessionMode(), prompt(), cancel(), initialize().

Event subscription loop: subscribes to GlobalBus, forwards events:

  • permission.asked → forwards permission requests (maps to ACP ToolKind).
  • message.part.updated → tracks tool execution state (pending→running→completed→error).
  • message.part.delta → streams text/reasoning chunks.

In-memory mapping of ACP session IDs to OpenCode state: { id, cwd, mcpServers, createdAt, model?, variant?, modeId? }.

Builtin plugins: ["opencode-anthropic-auth@0.0.13"]. Internal plugins: CodexAuthPlugin, CopilotAuthPlugin, GitlabAuthPlugin.

Hook system: auth, event, tool, tool.definition, chat.headers, config. Triggered via Plugin.trigger(name, input, output).

OpenAI Codex integration. Auth: browser OAuth (PKCE, auth.openai.com) or headless device flow or manual API key. Model filtering: gpt-5.1-codex-max, gpt-5.1-codex-mini, gpt-5.2, gpt-5.2-codex. Token management: auto-refresh, JWT accountId extraction, ChatGPT-Account-Id header. Endpoint: chatgpt.com/backend-api/codex/responses.

GitHub Copilot integration. Device code auth flow. Custom headers: x-initiator, Copilot-Vision-Request, Openai-Intent. Zero-cost model (included with subscription).

Service endpoint: configurable (default: https://opncd.ai).

API calls:

  • POST /api/share → create share (returns id, url, secret).
  • POST /api/share/{id}/sync → sync data batches.
  • DELETE /api/share/{id} → remove share.

Synced data types (discriminated union):

  • { type: "session", data: SDK.Session } — session metadata.
  • { type: "message", data: SDK.Message } — message envelope.
  • { type: "part", data: SDK.Part } — message part (text/tool/reasoning/etc).
  • { type: "session_diff", data: SDK.FileDiff[] } — file change diffs.
  • { type: "model", data: SDK.Model[] } — models used in session.

Sync model:

  1. Event subscriptions: Session.Event.Updated → sync session, MessageV2.Event.Updated → sync message + models, MessageV2.Event.PartUpdated → sync part, Session.Event.Diff → sync diffs.
  2. Debounced batching (1-second delay): queues updates by sessionID, batches multiple updates, uses ULID for deduplication.
  3. Full sync on share creation: syncs entire session history (metadata, all messages, all parts, file diffs, models).

Disable: OPENCODE_DISABLE_SHARE=true or config.share === "disabled".

SessionShareTable: session_id (PK, FK to Session), id (share ID), secret (auth secret), url (share URL), timestamps.

Provider state building (src/provider/provider.ts, ~1328 lines)

Section titled “Provider state building (src/provider/provider.ts, ~1328 lines)”

Provider.state() builds the complete inventory via lazy state machine:

  1. Database bootstrap — loads ModelsDev.get() from cache or https://models.dev/api.json. Transforms via fromModelsDevProvider().
  2. GitHub Copilot Enterprise injection — creates synthetic provider by duplicating and remapping.
  3. Config merging — extends with config.provider.* overrides. Per-model: cost, capabilities, options, headers, variants.
  4. Environment variable detection — scans providers for matching env vars. Sets source: "env".
  5. Auth credential loading — loads OAuth tokens + API keys from Auth storage. Plugin-based loader hooks.
  6. Custom loader executionCUSTOM_LOADERS[providerID](dbProvider) for provider-specific logic.
  7. Config finalization — applies blacklist/whitelist, filters deprecated/alpha models, deletes empty providers.

Provider-specific adapters (CUSTOM_LOADERS)

Section titled “Provider-specific adapters (CUSTOM_LOADERS)”

Anthropic:

  • Custom header: anthropic-beta: claude-code-20250219,interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14.
  • Opts into extended thinking and tool streaming features. autoload: false.

OpenCode:

  • Filters paid models without auth (cost.input > 0). Free tier gets apiKey: "public".
  • autoload: true if any models remain after filtering.

OpenAI:

  • Uses sdk.responses(modelID) for Responses API (structured output). autoload: false.

GitHub Copilot / Enterprise:

  • Version detection: shouldUseCopilotResponsesApi() for GPT-5+ models.
  • GPT-5+ → sdk.responses(modelID), others → sdk.chat(modelID).
  • Dual auth handling for standard vs enterprise accounts. autoload: false.

Amazon Bedrock (most complex adapter):

  • Cross-region inference profiles: detects global., us., eu., jp., apac., au. prefixes.
  • Region resolution: config → AWS_REGIONus-east-1.
  • US region: adds “us.” prefix for nova/claude/deepseek. Skips in GovCloud regions.
  • EU region: “eu.” prefix for specific regions + claude/nova/llama3/pixtral.
  • APAC: jp. prefix for Tokyo, au. for Australia (Claude), apac. for others.
  • Credential chain: Bearer token (AWS_BEARER_TOKEN_BEDROCK) → auth.key → profile-based → IAM/WebIdentity → container credentials via @aws-sdk/credential-providers.
  • autoload: checks region + at least one auth method present.

Google Vertex AI:

  • Dynamic auth via google-auth-library (dynamically installed via BunProc.install()).
  • Custom fetch wrapper injects Bearer token from application default credentials.
  • Project from GOOGLE_CLOUD_PROJECT / GCP_PROJECT. Location from GOOGLE_CLOUD_LOCATION / VERTEX_LOCATION or us-central1.
  • autoload: true if project is set.

GitLab:

  • Instance URL from GITLAB_INSTANCE_URL or https://gitlab.com.
  • API key from OAuth access > API key > GITLAB_TOKEN env.
  • Feature flags: duo_agent_platform_agentic_chat. Custom User-Agent with opencode version.
  • Model method: sdk.agenticChat(modelID, aiGatewayHeaders, featureFlags).

Cloudflare Workers AI / AI Gateway:

  • Workers AI: requires CLOUDFLARE_ACCOUNT_ID + CLOUDFLARE_API_KEY. BaseURL constructed from account ID.
  • AI Gateway: requires account ID + CLOUDFLARE_GATEWAY_ID + API token. Uses ai-gateway-provider v2.x. Model IDs in provider/model format.

OpenRouter, Vercel: Custom headers (HTTP-Referer: https://opencode.ai/, X-Title: opencode). autoload: false.

SAP AI Core: Requires AICORE_SERVICE_KEY. Options: deploymentId, resourceGroup.

Cerebras: Header X-Cerebras-3rd-Party-Integration: opencode.

  • Hourly background refresh with 10s timeout.
  • Fallback chain: OPENCODE_MODELS_PATH → cached file (~/.opencode/cache/models.json) → bundled snapshot → fetch from OPENCODE_MODELS_URL.
  • Schema per model: capabilities (temperature, reasoning, attachment, tool_call), modalities (text/audio/image/video/pdf input/output), pricing (input/output/cache/over-200k tiers), limits (context/input/output), status (alpha/beta/deprecated/active).
  • Uses Installation.USER_AGENT header. Silent failures — uses stale cache on network error.
  1. Options assembly: merge provider + model options + headers.
  2. BaseURL resolution: ${VAR} → env substitution.
  3. Custom fetch wrapper: timeout via AbortSignal.timeout(). OpenAI-specific: strips itemId metadata.
  4. SDK cache key: xxHash32 of { providerID, npm, options }.
  5. Bundled providers: amazon-bedrock, anthropic, azure, google, vertex, openai, openai-compatible, xai, mistral, groq, deepinfra, cerebras, cohere, gateway, togetherai, perplexity, vercel, gitlab.
  6. Dynamic: BunProc.install(model.api.npm, "latest"), resolves first createXxx export.
  • getModel(providerID, modelID) — fuzzy matching via fuzzysort (threshold -10000).
  • getSmallModel() — priority: claude-4-haiku-5, haiku-4-5, gemini-3-flash, gpt-5-nano. Provider-specific adjustments.
  • defaultModel() — reads config.model or recent history from ~/.opencode/state/model.json.
  • sort() — priority keywords: gpt-5.2-codex, claude-4-6-sonnet, big-pickle, gemini-3.1-pro-preview. Then -latest. Then alphabetical desc.
  • parseModel("provider/model/id") — splits on /, modelID can contain slashes.
{ id, providerID, api: { id, url, npm }, name, family?,
capabilities: { temperature, reasoning, attachment, toolcall,
input/output: { text, audio, image, video, pdf }, interleaved },
cost: { input, output, cache: {read, write}, experimentalOver200K? },
limit: { context, input?, output },
status: "alpha"|"beta"|"deprecated"|"active",
options, headers, release_date, variants }
  • SessionRevert.revert(...) is not a simple pointer rollback; it reconstructs a safe revert boundary by walking all messages/parts and tracking the latest user turn.
  • Revert target selection rule:
    • If caller gives messageID only, revert from that message.
    • If caller gives partID, it may still escalate to full-message revert if no meaningful parts (text/tool) remain before that part.
    • If assistant-side partial revert would leave invalid turn structure, boundary is moved back to the last user message.
  • Patch rollback rule:
    • While scanning, all patch parts after the revert boundary are collected.
    • Snapshot.revert(patches) applies inverse patch set to filesystem state.
    • Revert snapshot is persisted (session.revert.snapshot) so unrevert can restore the pre-revert workspace.
  • Diff/summary recomputation is immediate:
    • Recompute message range from revert point.
    • SessionSummary.computeDiff(...) recalculates file-level deltas.
    • Persist to Storage["session_diff", sessionID] and publish Session.Event.Diff.
    • Update session summary counters (additions, deletions, files) inside Session.setRevert(...).
  • unrevert(...) restores snapshot and clears revert marker.
  • cleanup(...) finalizes revert by deleting post-boundary DB rows:
    • Deletes full message rows when reverting whole turns.
    • Deletes trailing part rows when reverting within a message.
    • Publishes MessageV2.Event.Removed and MessageV2.Event.PartRemoved for UI/state convergence.

Retry policy (src/session/retry.ts, used by src/session/processor.ts)

Section titled “Retry policy (src/session/retry.ts, used by src/session/processor.ts)”
  • SessionRetry.retryable(...) classifies provider/runtime errors into retryable vs terminal.
  • Hard stop class: ContextOverflowError is never retried (must compact/prune instead).
  • APIError branch:
    • Honors provider isRetryable flag.
    • Parses known provider body fragments (overload, free-tier exhaustion).
    • Produces user-facing retry status message.
  • Delay strategy (SessionRetry.delay(...)):
    • Highest priority: retry-after-ms header.
    • Next: retry-after seconds/date parsing.
    • Fallback: exponential backoff (2s * 2^(attempt-1)) with 30s cap when no headers.
  • sleep(ms, signal) is abort-aware and clamps to max JS timeout int; retry waits terminate immediately on session cancel.
  • In SessionProcessor.process(...), retry path updates SessionStatus to {type:"retry", attempt, message, next} before waiting, then re-enters stream loop.

Session activity state (src/session/status.ts)

Section titled “Session activity state (src/session/status.ts)”
  • State model is a union: idle | busy | retry.
  • Storage is per-instance in-memory via Instance.state(...), keyed by sessionID.
  • set(...) always publishes session.status; legacy session.idle is also emitted for backwards compatibility when transitioning to idle.
  • Design intent: low-latency ephemeral status bus for UI progress indicators, not durable database state.
  • Todo rows are session-scoped and ordered (position column in TodoTable).
  • Update semantics are full-replace, transactional:
    1. Delete all existing todos for session.
    2. Insert new ordered list.
    3. Publish todo.updated event with full list payload.
  • Read path (get) returns stable order by position.
  • Consequence: this avoids partial merge complexity and keeps tool protocol simple (todowrite always submits complete authoritative list).

OpenOxide Blueprint (Rust Monolith Mapping)

Section titled “OpenOxide Blueprint (Rust Monolith Mapping)”

Recommended Rust module mapping (no client-server split):

  • crate::instance: context-scoped state cache (equivalent of Instance.state).
  • crate::session: prompt loop + stream processor + storage models.
  • crate::tool: trait-based tool registry with runtime gating.
  • crate::provider: model/provider adapters with per-provider option transformers.
  • crate::lsp: server registry + client pool keyed by (language_server, root).
  • crate::mcp: async transport manager + tool bridge.
  • crate::snapshot: git-tree snapshot + diff/revert utility.
  • crate::pty: terminal sessions + replay buffers.
  • crate::bus: typed event pub/sub with tokio broadcast channels.
  • crate::config: layered config loading with serde + schemars.
  • crate::file: ripgrep integration, ignore patterns, file watching.
  • crate::agent: agent definitions, permission rulesets, built-in agents.

Rust crate suggestions:

  • Async/runtime: tokio, futures, tokio-util.
  • Persistence: sqlx (SQLite) or rusqlite + typed wrappers.
  • Serialization/schema: serde, schemars.
  • LSP/JSON-RPC: tower-lsp (or custom jsonrpc over stdio).
  • Git ops: gix or subprocess strategy for parity-first implementation.
  • Glob/grep/search: ignore, globset, ripgrep subprocess for fidelity.
  • HTTP server: axum + tower (equivalent of Hono).
  • Event bus: tokio::sync::broadcast or custom typed channel system.
  • File watching: notify crate (cross-platform inotify/FSEvents/ReadDirectoryChanges).
  • Terminal: ratatui + crossterm.
  • GET / — list sessions (directory, search, limit filters).
  • GET /status — all session statuses. GET /:id — get session. GET /:id/children — forked children.
  • POST / — create. DELETE /:id — delete. PATCH /:id — update (title, archive).
  • POST /:id/init — initialize with AGENTS.md. POST /:id/fork — fork at message point.
  • POST /:id/abort — stop processing. POST /:id/share / DELETE /:id/share — shareable link.
  • GET /:id/diff — file changes. POST /:id/summarize — compact via AI.
  • GET /:id/message — all messages. POST /:id/message — send message (streams response).
  • POST /:id/prompt_async — async message (fire-and-forget, 204).
  • POST /:id/command — execute command. POST /:id/shell — run shell command.
  • POST /:id/revert / POST /:id/unrevert — undo/redo.
  • GET / — list all. GET /current — active project. PATCH /:id — update (name, icon, commands).
  • GET /find?pattern=... — ripgrep search (10-item default limit).
  • GET /find/file?query=... — fuzzy file search. GET /find/symbol?query=... — LSP symbol search (stub).
  • GET /file?path=... — list directory. GET /file/content?path=... — read file. GET /file/status — git status.
  • CRUD for PTY sessions + WebSocket upgrade for real-time I/O.
  • WebSocket protocol: onOpenPty.connect(), onMessage → forward input, onClose → cleanup.
  • GET / — server status. POST / — add server.
  • POST /:name/auth — start OAuth. POST /:name/auth/callback — complete OAuth.
  • POST /:name/connect / disconnect — manage connections. DELETE /:name/auth — remove OAuth credentials.
  • GET / — project config. PATCH / — update. GET /providers — list providers with defaults.
  • GET / — all providers (connected + defaults). GET /auth — available auth methods.
  • POST /:id/oauth/authorize / callback — OAuth flow.
  • GET / — pending questions. POST /:id/reply — answer. POST /:id/reject — reject.
  • GET / — pending permissions. POST /:id/reply — grant/deny ("once" | "always" | "reject").
  • POST /append-prompt, /open-help, /open-sessions, /open-themes, /open-models, /submit-prompt, /clear-prompt, /execute-command, /show-toast, /publish, /select-session.
  • /control/next — get next TUI request (AsyncQueue consumer). /control/response — submit response.
  • GET /tool/ids — list tool IDs. GET /tool — tools with schemas for provider/model.
  • Worktree CRUD: POST, GET, DELETE /worktree. POST /worktree/reset.
  • GET /health — version. GET /event — SSE stream (10s heartbeat).
  • GET /config / PATCH /config — global config. POST /dispose — shutdown all instances.

Uses bonjour-service to advertise OpenCode server on local network.

Worktree Management (src/worktree/index.ts)

Section titled “Worktree Management (src/worktree/index.ts)”

Git worktree operations for isolated sandboxes (agent execution environments):

Worktree.create({ name?, startCommand? }):

  • Generates {adjective}-{noun} names (e.g., “brave-cabin”) to avoid conflicts.
  • Runs git worktree add -b {branch} {path} HEAD.
  • Startup sequence: runs project’s configured start command → runs optional worktree-specific command.
  • Emits Worktree.Event.Ready or Worktree.Event.Failed via GlobalBus.
  • Queues start scripts with a timeout (10s) for the initial setup.

Worktree.remove({ directory }):

  • Locates worktree via git worktree list --porcelain.
  • Removes with git worktree remove --force.
  • Falls back to manual fs.rm(target, { recursive: true, force: true, maxRetries: 5 }).
  • Deletes the associated branch via git branch -D.
  • Handles case where worktree directory exists but git doesn’t track it.

Worktree.reset({ directory }):

  • Cannot reset the primary workspace (safety check).
  • Remote detection: origin → upstream → single remote.
  • Default branch detection: remote HEAD → refs/heads/main → refs/heads/master.
  • Fetch from remote, then git reset --hard {target}.
  • Full cleanup: git clean -fdx, git submodule update --init --recursive --force, git submodule foreach --recursive git reset --hard, git submodule foreach --recursive git clean -fdx.
  • Verifies clean state via git status --porcelain=v1.
  • Re-runs project start scripts after reset.

Context.create(name){ use(), provide(value, fn) } via AsyncLocalStorage. Used by Database (transactions), Instance scoping.

git(args, { cwd, env? }) — when OPENCODE_CLIENT === "acp", uses Bun.spawn() with stdin: "ignore" to avoid pipe deadlock.

Token.estimate(input)Math.round(input.length / 4). Simple character-based heuristic.

Consumer-producer pattern: push(item), next(), async iterable. Also work(concurrency, items, fn) for parallel batch processing.

Lock.read(key) / Lock.write(key) — multiple readers, exclusive writer, writer priority. Returns disposable.

Worker-to-main communication: { type: "rpc.request", method, input, id } / { type: "rpc.result", result, id }.

exists(), isDir(), normalizePath(), overlaps(a, b), contains(parent, child), findUp(), up(), globUp().

  • util/wildcard.ts — shell-style pattern matching, glob → regex conversion.
  • util/lazy.tslazy(fn) deferred init with reset().
  • util/signal.tssignal() returns { trigger(), wait() } for synchronization.
  • util/log.ts — structured logging. util/format.ts — formatting helpers.
  • util/color.ts — terminal colors. util/archive.ts — tar.gz/zip extraction.
  • util/keybind.ts — keybind parsing. util/abort.ts — abort signal utilities.
  • PackageRegistry.info(pkg, field, cwd?) — calls bun info pkg field, returns string value.
  • PackageRegistry.isOutdated(pkg, cachedVersion, cwd?):
    • Fetches latest version from registry.
    • If cachedVersion is range (^, ~, *, x, <, >, |, =): uses semver.satisfies().
    • If pinned: uses semver.order() to detect downgrade.
    • Returns boolean.
  • BunProc.run(cmd[], options?) — spawns bun process with stdout/stderr capture. Sets BUN_BE_BUN=1. Returns exit code or throws on non-zero.
  • BunProc.install(pkg, version="latest"):
    • Write-locked to prevent concurrent installs of the same package.
    • Cache location: ~/.opencode/cache/node_modules/{pkg}.
    • Version strategy: if already installed exact version → return cached path. If latest → check isOutdated(), skip if current.
    • Install via: bun add --force --exact [--no-cache] --cwd ~/.opencode/cache pkg@version.
    • On success: reads installed package.json to resolve actual version. Updates ~/.opencode/cache/package.json with dependency entry.
    • Returns absolute path to installed module. Throws InstallFailedError on failure.
    • Registry resolution: Bun handles .npmrc automatically, no --registry flag needed.

OAuth credentials for OpenCode Control (enterprise portal):

  • Control.account() — queries DB for active account (active=true). Synchronous DB read. Returns { email, url } or undefined.
  • Control.token():
    1. Get active account from DB.
    2. Check if token_expiry > Date.now() — if yes, return access_token (valid).
    3. If expired: refresh via POST {url}/oauth/token with refresh_token grant.
    4. Update DB with new access_token, refresh_token, token_expiry.
    5. Return new token or undefined if refresh fails.

ControlAccountTable:

  • Columns: email (text), url (text), access_token (text), refresh_token (text), token_expiry (integer, optional, Unix ms), active (integer, boolean, default false), timestamps.
  • Primary key: (email, url) — composite allows multiple Control instances per user.
  • active flag selects which account to use (one at a time).

30+ environment variable flags, all read at module load time except dynamic getters:

  • OPENCODE_AUTO_SHARE — auto-share sessions.
  • OPENCODE_GIT_BASH_PATH — custom Git Bash path (Windows).
  • OPENCODE_CONFIG — custom config file path.
  • OPENCODE_CONFIG_CONTENT — inline config JSON.
  • OPENCODE_DISABLE_AUTOUPDATE — skip auto-update checks.
  • OPENCODE_DISABLE_PRUNE — disable message pruning.
  • OPENCODE_DISABLE_TERMINAL_TITLE — don’t set terminal title.
  • OPENCODE_PERMISSION — global permission override.
  • OPENCODE_DISABLE_DEFAULT_PLUGINS — skip builtin plugins.
  • OPENCODE_DISABLE_LSP_DOWNLOAD — don’t download language servers.
  • OPENCODE_ENABLE_EXPERIMENTAL_MODELS — show alpha/beta models.
  • OPENCODE_DISABLE_AUTOCOMPACT — disable automatic compaction.
  • OPENCODE_DISABLE_MODELS_FETCH — don’t refresh models.dev data.
  • OPENCODE_SERVER_PASSWORD / OPENCODE_SERVER_USERNAME — basic auth credentials.
  • OPENCODE_ENABLE_QUESTION_TOOL — enable question tool.
  • OPENCODE_DISABLE_FILETIME_CHECK — skip file modification time checks.
  • OPENCODE_DISABLE_CLAUDE_CODE — master toggle, disables prompt and skills.
  • OPENCODE_DISABLE_CLAUDE_CODE_PROMPT — don’t use Claude Code system prompt.
  • OPENCODE_DISABLE_CLAUDE_CODE_SKILLS — don’t load Claude Code skills.
  • OPENCODE_DISABLE_EXTERNAL_SKILLS — don’t load any external skills.
  • OPENCODE_EXPERIMENTAL — master toggle for all experimental features.
  • OPENCODE_EXPERIMENTAL_FILEWATCHER / OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER — file watcher control.
  • OPENCODE_EXPERIMENTAL_ICON_DISCOVERY — project icon auto-detection.
  • OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS — custom bash timeout.
  • OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX — custom output token limit.
  • OPENCODE_EXPERIMENTAL_OXFMT — experimental formatter.
  • OPENCODE_EXPERIMENTAL_LSP_TY — ty language server.
  • OPENCODE_EXPERIMENTAL_LSP_TOOL — LSP as a tool.
  • OPENCODE_EXPERIMENTAL_PLAN_MODE — plan/build agent mode.
  • OPENCODE_EXPERIMENTAL_MARKDOWN — markdown rendering.
  • OPENCODE_ENABLE_EXA — enable Exa web/code search.

Dynamic getters (via Object.defineProperty)

Section titled “Dynamic getters (via Object.defineProperty)”
  • OPENCODE_CLIENT — evaluated at access time, default "cli". Set to "acp" for Agent Client Protocol mode.
  • OPENCODE_DISABLE_PROJECT_CONFIG — evaluated at access time for runtime tooling overrides.
  • OPENCODE_CONFIG_DIR — evaluated at access time for runtime config directory changes.

XDG-compliant directory layout:

Global.Path.home — os.homedir() or OPENCODE_TEST_HOME
Global.Path.data — XDG_DATA_HOME/opencode
Global.Path.bin — data/bin (ripgrep, language servers)
Global.Path.log — data/log
Global.Path.cache — XDG_CACHE_HOME/opencode (models.json, node_modules)
Global.Path.config — XDG_CONFIG_HOME/opencode (opencode.json)
Global.Path.state — XDG_STATE_HOME/opencode (model.json recent history)

Cache versioning: auto-clears cache if CACHE_VERSION changes (currently v21). Prevents stale cached data after updates.

Identifier.ascending(prefix) / descending(prefix) — monotonic timestamp-based IDs.

Format: {PREFIX}_{TIMESTAMP_HEX}{RANDOM}.

Prefixes: session, message, permission, question, user, part, pty, tool.

Monotonic guarantees: maintains last timestamp + counter. If called within same millisecond, increments counter. Ensures IDs are strictly ordered even under high throughput. Descending variant inverts timestamp for reverse-chronological ordering.

  • Shell.preferred()process.env.SHELL or platform fallback.
  • Shell.acceptable() — non-blacklisted shell. Blacklist: fish, nu (incompatible with interactive I/O).
  • Shell.killTree(proc) — cross-platform process tree kill.

Platform support:

  • Windows: CMD, Git Bash, or Git-provided bash.exe (via OPENCODE_GIT_BASH_PATH).
  • macOS: /bin/zsh.
  • Linux: bash or /bin/sh.

All routes, state, and heavy modules use lazy(fn) pattern — deferred computation on first access with optional reset(). Every server route file exports a lazily-constructed Hono router. State objects (Provider.state(), Config.state(), Agent.state()) are all lazy-loaded per instance.

Instance.state(init, dispose) provides per-directory cached state. Used by Agent, Config, InstructionPrompt, Bus, File, LSP, MCP, and more. Disposed on Instance.disposeAll() (triggered by config changes). This is the primary mechanism for multi-tenant isolation — each project directory gets its own independent state.

Server publishes events via GlobalBus; clients listen via SSE /api/global/event. Bus events are Zod-typed and instance-scoped. Key event producers: worktree creation, permission requests, message updates, TUI commands, file changes, session status transitions.

All agents have explicit permission: PermissionNext.Ruleset defining:

  • Tool access (allow/deny per tool).
  • Scope (directory patterns — which paths a tool can access).
  • Escalation behavior (ask/allow/deny defaults).
  • The plan agent denies all edit tools except .opencode/plans/*.md.
  • The explore agent allows only read-only tools.

Three streaming protocols:

  • Session prompt: HTTP stream with JSON.stringify(message) per chunk.
  • PTY output: WebSocket binary frames with cursor-based reconnect replay.
  • Global events: Server-Sent Events with 10-second heartbeat to prevent proxy stalling.

TUI routes use AsyncQueue for request/response coordination between TUI worker and server. The /control/next endpoint blocks until a request is available; /control/response delivers the answer.

Context.create(name) wraps AsyncLocalStorage for zero-prop-drilling DI. Database transactions are the primary consumer — Database.use() auto-detects transaction context and provides the appropriate client. Instance scoping also uses this pattern.

Every API boundary uses Zod schemas: tool parameters, config files, bus events, message parts, provider models. Runtime validation at parse boundaries, TypeScript inference everywhere else. The BusEvent.define() pattern registers events with Zod schemas for both runtime validation and discriminated union generation.

  • Tool output can explode context; truncation + external output files are mandatory.
  • Provider behavior diverges significantly; one adapter path is insufficient. Each provider needs custom loaders with specific quirks.
  • Pending tool calls must be normalized before resend, or providers can reject transcript shape.
  • LSP startup and diagnostics timing are inherently flaky; debounce + timeout boundaries are required.
  • Compaction and prune are separate controls: summarize conversation vs trim old tool output payloads.
  • Permission checks need path-aware semantics (project dir vs worktree vs external filesystem).
  • Revert is two-phase (revert marker + cleanup hard delete); collapsing these can break UI synchronization and unrevert safety.
  • Retry timing must respect provider headers first; naive fixed backoff fights rate-limit windows and increases failure loops.
  • Token counting is provider-specific: Anthropic includes cached tokens in inputTokens, others exclude them.
  • JSON to SQLite migration must handle orphaned records gracefully; batch processing with FK validation is essential.
  • File time locking via chained promises prevents concurrent writes but requires all tools to call FileTime.read() before write.
  • Snapshot system uses a separate .git directory per project, not the project’s own git repo. Critical for correct revert.
  • Dynamic SDK installation needs write locking to prevent concurrent installs of the same package.
  • System prompt selection must be model-aware; different model families expect different prompt structures.
  • ACP bridge must translate between OpenCode’s rich part types and ACP’s simpler tool_call/text_delta protocol.

Captured live from the installed binary. Reveals every user-facing feature and most internal subsystems.

  • --port (default: 0) — binds the embedded HTTP server to a random port; non-zero forces a specific port. Shows that even opencode tui starts a server.
  • --hostname (default: 127.0.0.1) — server bind address.
  • --mdns / --mdns-domain — optional mDNS broadcast for LAN discovery of a running instance. The default domain is opencode.local.
  • --cors — array of additional origins allowed by the Hono CORS middleware.
  • --log-level (DEBUG|INFO|WARN|ERROR) / --print-logs — log subsystem controls.
  • -m, --model — provider/model format string, e.g. anthropic/claude-4-6-sonnet.
  • -c, --continue / -s, --session / --fork — session continuation. Fork creates a branch; continue resumes in place.
  • --prompt / --agent — pre-select a custom prompt or named agent definition.
CommandPurpose
opencode [project]Launch full TUI. Default command.
opencode serveHeadless HTTP server only — no TUI. Same port/hostname/cors flags.
opencode webserve + opens browser to web interface.
opencode acpStarts ACP (Agent Client Protocol) server. Adds --cwd flag.
opencode attach <url>Connect this process as a client to an already-running opencode server. Adds -p, --password (reads from OPENCODE_SERVER_PASSWORD).
opencode run [message..]Non-interactive headless agent run. Key for scripting and CI.
opencode models [provider]List all available models. --verbose adds cost/metadata. --refresh busts models.dev cache.
opencode statsToken usage and cost statistics. --days, --tools, --models, --project filters.
opencode export [sessionID]Dump session as JSON.
opencode import <file>Restore session from JSON file or share URL.
opencode pr <number>Fetch/checkout a GitHub PR branch, then launch TUI.
opencode upgrade [target]Self-update. --method selects: curl, npm, pnpm, bun, brew, choco, scoop.
opencode uninstallRemove binary + data. --keep-config, --keep-data, --dry-run, --force.

opencode run — Headless Mode (Important)

Section titled “opencode run — Headless Mode (Important)”
opencode run [message..]
--command the command to run (use message for args)
--format default (pretty) | json (raw JSONL events)
--file attach files to message
--title session title
--attach attach to a running server (http://localhost:4096)
--port local server port
--share share session
--variant model reasoning variant (high, max, minimal)
--thinking show thinking blocks

The --format json flag emits raw SSE events as JSONL — this is the machine-readable protocol that the TUI client itself consumes. Critical for understanding the client-server protocol shape.

opencode mcp add # add an MCP server (interactive)
opencode mcp list # list MCP servers + status
opencode mcp auth [name] # OAuth login for OAuth-enabled MCP server
opencode mcp auth list # list OAuth-capable servers + auth state
opencode mcp logout [name] # remove OAuth credentials
opencode mcp debug <name> # debug OAuth connection

Exposes that OpenCode manages MCP servers with full OAuth lifecycle (not just stdio).

opencode auth login [url] # log in to a provider; url is optional (for custom endpoints)
opencode auth logout # log out from configured provider
opencode auth list # list all providers

opencode agent — Custom Agent Management

Section titled “opencode agent — Custom Agent Management”
opencode agent create # interactive agent creation
opencode agent list # list all defined agents

Agents are named configurations of system prompt + model + tool permissions. Managed separately from sessions.

opencode session list # list all sessions
opencode github install # install GitHub agent (for PR-level automation)
opencode github run # run GitHub agent

opencode debug — Internal Diagnostics (Deep)

Section titled “opencode debug — Internal Diagnostics (Deep)”
opencode debug config # show fully-resolved config (all layers merged)
opencode debug paths # data/config/cache/state global paths
opencode debug scrap # list all known projects
opencode debug skill # list all available skills
opencode debug lsp diagnostics <file> # get LSP diagnostics for a file
opencode debug lsp symbols <query> # search workspace symbols
opencode debug lsp document-symbols <uri> # get symbols from a document
opencode debug rg tree # show file tree via ripgrep
opencode debug rg files # list files via ripgrep
opencode debug rg search <pattern> # search file contents via ripgrep
opencode debug file read <path> # read file as JSON
opencode debug file status # file status info
opencode debug file list <path> # list directory
opencode debug file search <query> # search files by query
opencode debug file tree [dir] # directory tree
opencode debug snapshot track # track current snapshot state
opencode debug snapshot patch <hash> # show patch for snapshot hash
opencode debug snapshot diff <hash> # show diff for snapshot hash
opencode debug agent <name> # show agent config details
opencode debug wait # wait indefinitely (for process attach/debugging)

The debug tree mirrors the internal module boundaries exactly: lsp, rg (ripgrep file subsystem), file (VFS abstraction), snapshot, agent, skill, config. This is the most reliable map of OpenCode’s internal subsystem structure.

  • Build Codex index (references/codex) with the same entrypoint->state->leaf tracing depth.
  • Start converting these index notes into Starlight docs pages with the six-part template from AGENTS.md.