# OpenCode provider

Sessions live in a SQLite DB at `~/.local/share/opencode/opencode.db` (`%LOCALAPPDATA%\\opencode` on Windows, `OPENCODE_DATA` env var wins). Tables: `session`/`message`/`part`; role/user and tool payloads are JSON in `message.data`/`part.data`. Discovery writes `<dbPath>#session:<id>` marker paths. Parse is SQLite-backed (`parseSessionFromDb`), so background scan uses a lightweight path from discovery-computed stats (`buildLightweightOpencodeScanResult` in `scanner.ts`) to avoid opening the DB per session. Synthetic user messages carrying a `compaction` part are compaction events even when they also carry text; discovery must exclude that text from prompt counts and `firstPrompt`, matching the parser's early compaction branch. Text parts flagged `synthetic: true` are model-feeding artifacts — parser and discovery keep them out of prompts. `sql.js` named-param binds need their `:`-prefix in the key — this provider uses positional `?` params instead. The `session.cost` column (populated only by some opencode versions) feeds `reportedCostUsd` when positive, with a fallback to summing per-message `cost`. `task` tool parts spawn child sessions (`session.parent_id`, hidden from top-level discovery) linked via `state.metadata.sessionId` — the parser attaches them as `_subAgent` trajectories without expanding nested grandchildren. MCP tool names are `sanitize(server)_sanitize(tool)` with no stored server field, so attribution reads configured server names (global/`OPENCODE_CONFIG`/session-dir `opencode.json[c]`) and longest-prefix matches (`mcp-servers.ts`). AI-SDK truncation arrives as `finish: "length"` (→ `stopReason: "max_tokens"`); `finish: "error"` feeds `apiErrors` with only the structured `error.name`, never the message text. Tool ids span two naming generations (`bash`/`todowrite` old, `shell`/`todo` new); only verified ids are mapped in `tool-mapping.ts`. See `packages/provider-opencode/`.

OpenCode v2 (observed in 2.0.22) uses `session_v2` and `session_message`: user text/files live in message data, assistant content holds reasoning/text/tool states, model references use `model.id`, and API error categories use `error.type`. When both layouts coexist, retain legacy-only sessions and prefer the complete v2 trajectory for duplicate IDs; transient tables shadow legacy tables without removing them. `storage.ts` adapts these records into transient legacy tables in the sql.js snapshot; it never modifies the source database. Preserve sequence order, exclude system/synthetic/idle messages from human prompts, retain compactions and cost/usage, and keep raw API error text out of diagnostics. Fixtures for both layouts are independent compatibility contracts. Unknown schemas must throw an actionable discovery error rather than look empty.

Newer v2 shell records carry `shellID`, lifecycle status/exit, and an output page object; older records carry `callID` and a string. Normalize both without passing objects into replay text. Running shells have no completed result; nonzero exits, timeouts, and killed shells are errors. Newer compactions carry running/completed/failed status: only completed records count as successful compactions; legacy status-less v2 snapshots retain their earlier completed meaning. Failed records emit privacy-safe diagnostics, running records remain inconclusive, and separate compaction usage adds to aggregate billing without entering human turn statistics.

Release-schema prompt attachments use `data`/`mime` plus `source.uri`; earlier v2 uses a top-level `uri`. Prefer recorded bytes as a data URL and retain remote URI fallback for explicit viewer consent. Tool file content with image MIME types feeds `_images`, preferring inline `data`/`mime` bytes over URI fallback. V2 token records report reasoning separately from output; fold both into the shared output-token total (including compaction usage) without changing legacy message accounting. Native `skill` messages contribute only their names to skill metadata and activation counts; their full instruction text is not conversation content.

CLI stdout export and share preflight carry a zero-write SQLite scope into `openOpencodeDb`; reject pending WAL before WASM reads so native ID resolution cannot export a stale main-file snapshot.
