diff --git a/AiOverviewControlSettings.qml b/AiOverviewControlSettings.qml
index 3033d48..25291f6 100644
--- a/AiOverviewControlSettings.qml
+++ b/AiOverviewControlSettings.qml
@@ -290,7 +290,7 @@ PluginSettings {
{ id:"perplexity", name:"Perplexity", icon:"auto_awesome", mode:"informational", requirement:"none", envVar:"", note:"No public read-only quota API" },
{ id:"cursor", name:"Cursor", icon:"mouse", mode:"informational", requirement:"none", envVar:"", note:"No public read-only quota API" },
{ id:"cline", name:"Cline", icon:"code_blocks", mode:"informational", requirement:"none", envVar:"", note:"No public read-only quota API" },
- { id:"opencode", name:"OpenCode", icon:"open_in_new", mode:"informational", requirement:"none", envVar:"", note:"Usage belongs to configured upstream providers" },
+ { id:"opencode", name:"OpenCode Go", icon:"open_in_new", mode:"telemetry", requirement:"API key or CLI login", envVar:"OPENCODE_API_KEY", note:"Uses OPENCODE_API_KEY or the credential saved by `opencode auth login` to ~/.local/share/opencode/auth.json. Reads OpenCode Zen's /zen/go/v1/usage for live 5h/weekly/monthly quota; shows when balance fallback is enabled; falls back to /zen/go/v1/models on failure." },
{ id:"kiro", name:"Kiro", icon:"tune", mode:"informational", requirement:"none", envVar:"", note:"Subscription-only IDE; no public API" },
{ id:"warp", name:"Warp", icon:"speed", mode:"informational", requirement:"none", envVar:"", note:"No public read-only quota API" },
{ id:"amp", name:"Amp", icon:"bolt", mode:"informational", requirement:"none", envVar:"", note:"No public read-only quota API" }
diff --git a/AiOverviewControlWidget.qml b/AiOverviewControlWidget.qml
index 7a3b697..9f4c03a 100644
--- a/AiOverviewControlWidget.qml
+++ b/AiOverviewControlWidget.qml
@@ -613,7 +613,7 @@ PluginComponent {
grok: "xAI",
perplexity: "Perplexity",
cline: "Cline",
- opencode: "OpenCode",
+ opencode: "OpenCode Go",
kilo: "Kilo",
kiro: "Kiro",
amp: "Amp",
@@ -850,6 +850,7 @@ PluginComponent {
if (providerId === "glm" || providerId === "zhipu" || providerId === "zai") return Theme.primary;
if (providerId === "minimax") return Theme.success;
if (providerId === "commandcode" || providerId === "cmd" || providerId === "cmdcode") return Theme.primary;
+ if (providerId === "opencode") return Theme.secondary;
if (providerId === "qwen" || providerId === "dashscope" || providerId === "alibaba") return Theme.warning;
if (providerId === "nvidia" || providerId === "nim") return Theme.success;
if (providerId === "cloudflare") return Theme.warning;
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b829603..fa33171 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,10 @@
## Unreleased
+### OpenCode Go provider
+
+- New **OpenCode Go** (opencode.ai) provider surfaces the live 5-hour, weekly, and monthly usage windows via OpenCode Zen's `/zen/go/v1/usage`, authenticated with `OPENCODE_API_KEY` or the CLI credential in `${XDG_DATA_HOME:-$HOME/.local/share}/opencode/auth.json`. The endpoint is young, so malformed or unavailable quota data degrades to the documented `/zen/go/v1/models` auth-only check — never a fabricated percentage. `useBalance: true` is shown as **balance fallback enabled**; the endpoint does not expose an amount, so none is claimed. `opencode` moves from the informational tier (no quota API) to a full telemetry provider, and `get-provider-health` gained a matching `opencode` case.
+
## 1.13.3 - 2026-08-26
### Hermetic test suites
diff --git a/docs/providers.md b/docs/providers.md
index faf5118..9a8acc7 100644
--- a/docs/providers.md
+++ b/docs/providers.md
@@ -13,7 +13,7 @@ This document is the authoritative reference for adapter authors. It records, fo
- the **current flagship model(s)** and recent **changelog** highlights,
- and the **official documentation source** used.
-Reviewed 2026-08-25. Provider APIs change; re-check the linked sources before changing an adapter.
+Reviewed 2026-08-26. Provider APIs change; re-check the linked sources before changing an adapter.
## Coverage levels
@@ -21,12 +21,12 @@ Every provider maps to exactly one coverage level. The level dictates what the w
| Level | Meaning | Example providers |
| --- | --- | --- |
-| **Quota** | Real `usedPercent` + reset window from a protocol/API. | `codex`, `copilot`, `antigravity`, `openrouter`, `zai`, `glm`, `fireworks` (with account ID) |
+| **Quota** | Real `usedPercent` + reset window from a protocol/API. | `codex`, `copilot`, `antigravity`, `openrouter`, `zai`, `glm`, `fireworks` (with account ID), `commandcode`, `opencode` |
| **Balance** | Remaining prepaid balance / credits in real currency. | `kimi`, `deepseek` |
| **Analytics** | Consumption counters (requests/tokens/neurons/cost) with no remaining-quota value. | `cloudflare` (GraphQL), `9router`, `claude` (local), `pi` (local), `hermes` (local) |
| **Auth / configured** | Validates credentials with a read-only endpoint when possible; otherwise reports only that a credential is configured and states the limitation. No usage numbers. | `gemini`, `mistral`, `nvidia`, `qwen`, `byteplus`, `groq`, `cohere`, `replicate`, `together`, `minimax`, `xai`, `kilo`, `ai21` |
| **Local runtime** | Local process / installed models. | `ollama`, `vertexai` (gcloud) |
-| **Informational** | No public read-only API at all; the card just links to the dashboard. | `perplexity`, `cursor`, `cline`, `opencode`, `kiro`, `warp`, `amp` |
+| **Informational** | No public read-only API at all; the card just links to the dashboard. | `perplexity`, `cursor`, `cline`, `kiro`, `warp`, `amp` |
## Provider kinds
@@ -189,6 +189,17 @@ The matrix below summarises the **authentication/billing surface** for every sup
Provider API docs |
+opencode |
+Quota |
+✅ GET /zen/go/v1/models |
+✅ /zen/go/v1/usage (5h + weekly + monthly) |
+✅ OpenCode Go $10/mo |
+— |
+OPENCODE_API_KEY or CLI ~/.local/share/opencode/auth.json |
+opencode.ai/zen |
+opencode.ai/docs/go |
+
+
kimi |
Balance / Quota |
✅ GET /v1/models |
@@ -432,17 +443,6 @@ The matrix below summarises the **authentication/billing surface** for every sup
cline.bot |
-opencode |
-Informational |
-❌ |
-❌ |
-— |
-— |
-— |
-upstream providers |
-opencode.ai |
-
-
warp |
Informational |
❌ |
@@ -601,6 +601,20 @@ Detailed adapter notes for the focus providers (Gemini, Cloudflare, Mistral, GLM
| **Dashboard** | [commandcode.ai/billing](https://commandcode.ai/billing) — billing, plan, and credit top-ups. |
| **Adapter** | `fetch_commandcode_native` — `/alpha/billing/credits` + `/alpha/whoami` + `/alpha/billing/subscriptions`, with documented-endpoint fallback. |
+### OpenCode Go
+
+| | |
+| --- | --- |
+| **API base** | `https://opencode.ai/zen/go/v1` — OpenCode Zen's Go-plan quota surface, alongside the OpenAI-compatible `/zen/v1` inference API. |
+| **Credentials** | `OPENCODE_API_KEY` (preferred when available to DMS), or the `key` in CLI-owned `${XDG_DATA_HOME:-$HOME/.local/share}/opencode/auth.json` (saved by `opencode auth login`). The adapter follows this XDG path on Linux, the plugin's supported platform. The same key created in OpenCode Studio/Zen drives both inference and quota introspection. |
+| **Auth** | `Authorization: Bearer ***` |
+| **Quota / balance** | `/zen/go/v1/usage` returns `rollingUsage` (5h window), `weeklyUsage`, and `monthlyUsage`, each `{status, resetInSec, usagePercent}` — percentages are computed server-side, so the adapter never derives them from raw byte/token counts. The adapter accepts a quota response only when all three windows have valid percentage/reset values; otherwise it falls back to auth-only status. `resetInSec` is seconds-from-now, converted to an absolute ISO 8601 timestamp. `useBalance: true` is rendered as **balance fallback enabled**; the endpoint does not return the balance amount, so none is invented. |
+| **Plans** | **OpenCode Go** $10/mo — $12 of usage per 5-hour window, $30 weekly, $60 monthly. |
+| **Billing** | Flat monthly subscription; no PAYG surfaced through this endpoint. |
+| **Stability** | The `/zen/go/` namespace shipped alongside the Go plan (opencode PR #16513) and has no documented versioned contract. Adapter degrades to `/zen/go/v1/models` (auth-only) on failure and emits a clearly-labeled "quota endpoint unavailable" note; it never fabricates a percentage. A missing/invalid key returns `401 AuthError`; a valid key without a Go subscription returns `403 EntitlementError` — both surface as hard errors, not soft notes. |
+| **Dashboard** | [opencode.ai/zen](https://opencode.ai/zen) — Zen usage and Go plan management. |
+| **Adapter** | `fetch_opencode_native` — `/zen/go/v1/usage`, with `/zen/go/v1/models` fallback. |
+
### Kimi (Moonshot AI)
| | |
diff --git a/providers/get-provider-health b/providers/get-provider-health
index bdab874..33ba2e3 100755
--- a/providers/get-provider-health
+++ b/providers/get-provider-health
@@ -16,6 +16,14 @@ has_commandcode_credentials() {
&& jq -e '(.apiKey | type) == "string" and (.apiKey | length) > 0' "$auth_file" >/dev/null 2>&1
}
+has_opencode_credentials() {
+ # Match the CLI's XDG data directory on Linux DMS sessions.
+ local auth_file="${XDG_DATA_HOME:-$HOME/.local/share}/opencode/auth.json"
+ has_any_env OPENCODE_API_KEY && return 0
+ [ -r "$auth_file" ] \
+ && jq -e '((."opencode".key // ."opencode-go".key // ."opencode_go".key // .apiKey) | type) == "string" and (length > 0)' "$auth_file" >/dev/null 2>&1
+}
+
status_for() {
local provider="$1"
local status="ready"
@@ -41,6 +49,7 @@ status_for() {
kimi|moonshot) has_any_env MOONSHOT_API_KEY KIMI_API_KEY KIMI_CODING_API_KEY || { status="missing"; detail="MOONSHOT_API_KEY, KIMI_API_KEY, or KIMI_CODING_API_KEY"; } ;;
minimax) has_any_env MINIMAX_API_KEY || { status="missing"; detail="MINIMAX_API_KEY"; } ;;
commandcode|cmd|cmdcode) has_commandcode_credentials || { status="missing"; detail="COMMAND_CODE_API_KEY or ~/.commandcode/auth.json"; } ;;
+ opencode) has_opencode_credentials || { status="missing"; detail="OPENCODE_API_KEY or ~/.local/share/opencode/auth.json"; } ;;
glm|zhipu) has_any_env GLM_API_KEY ZHIPU_API_KEY || { status="missing"; detail="GLM_API_KEY or ZHIPU_API_KEY"; } ;;
zai|z.ai) has_any_env ZAI_API_KEY GLM_API_KEY ZHIPU_API_KEY || { status="missing"; detail="ZAI_API_KEY"; } ;;
mistral) has_any_env MISTRAL_API_KEY || { status="missing"; detail="MISTRAL_API_KEY"; } ;;
@@ -60,7 +69,7 @@ status_for() {
ai21) has_any_env AI21_API_KEY || { status="missing"; detail="AI21_API_KEY"; } ;;
pi) command -v pi >/dev/null 2>&1 || [ -d "$HOME/.pi/agent/sessions" ] || { status="missing"; detail="pi CLI or ~/.pi/agent/sessions"; } ;;
hermes) [ -f "${HERMES_HOME:-$HOME/.hermes}/state.db" ] || command -v hermes >/dev/null 2>&1 || { status="missing"; detail="hermes CLI or ~/.hermes/state.db"; } ;;
- perplexity|cursor|cline|opencode|kiro|warp|amp)
+ perplexity|cursor|cline|kiro|warp|amp)
status="info"
detail="No public read-only quota API"
;;
diff --git a/providers/get-provider-usage b/providers/get-provider-usage
index 507f2d4..8563d02 100755
--- a/providers/get-provider-usage
+++ b/providers/get-provider-usage
@@ -1606,6 +1606,113 @@ fetch_commandcode_native() {
"commandcode.ai/billing"
}
+opencode_api_key() {
+ # OpenCode Zen issues one API key from OpenCode Studio; a local CLI login
+ # (opencode auth login) persists the same key to this CLI-owned file. Path
+ # and key shape verified against opencode's docs and the opencode CLI
+ # behavior on Linux. Honor XDG_DATA_HOME for non-default DMS sessions.
+ local auth_file="${XDG_DATA_HOME:-$HOME/.local/share}/opencode/auth.json"
+ if [ -n "${OPENCODE_API_KEY:-}" ]; then
+ printf '%s' "$OPENCODE_API_KEY"
+ elif [ -r "$auth_file" ]; then
+ # Accept either the {"opencode":{"key":...}} shape (current CLI) or a
+ # legacy flat {"apiKey":...} shape so the bridge still works for users
+ # who hand-edited the file before the rename.
+ jq -r '."opencode".key // ."opencode-go".key // ."opencode_go".key // .apiKey // empty' "$auth_file" 2>/dev/null || true
+ fi
+}
+
+fetch_opencode_native() {
+ # OpenCode Go (opencode.ai) — coding-agent subscription quota via OpenCode
+ # Zen. /zen/go/v1/usage returns percent-already-computed 5h/weekly/monthly
+ # windows. The endpoint is young (opencode PR #16513), so on failure we
+ # degrade to /zen/go/v1/models (auth-only check), matching the Command Code
+ # adapter's fallback shape.
+ local key
+ key="$(opencode_api_key)"
+ if [ -z "$key" ]; then
+ json_error opencode opencode-api 2 provider "OpenCode credentials are unavailable. Set OPENCODE_API_KEY or run 'opencode auth login' so ~/.local/share/opencode/auth.json has a key."
+ return 0
+ fi
+
+ local tmp_body http_status
+ tmp_body="$(mktemp)"
+ http_status="$(curl -sS --max-time 8 -o "$tmp_body" -w '%{http_code}' \
+ -H "Authorization: Bearer ${key}" \
+ -H "Accept: application/json" \
+ "https://opencode.ai/zen/go/v1/usage" 2>/dev/null || true)"
+
+ if [ "$http_status" = "401" ] || [ "$http_status" = "403" ]; then
+ local message
+ message="$(jq -r '.error.message // empty' "$tmp_body" 2>/dev/null || true)"
+ [ -n "$message" ] || message="OpenCode API key invalid (HTTP ${http_status})."
+ rm -f "$tmp_body"
+ json_error opencode opencode-api "${http_status}" provider "$message"
+ return 0
+ fi
+
+ if [ "$http_status" = "200" ] && jq -e '
+ def valid_window:
+ type == "object"
+ and (.usagePercent | type == "number" and . >= 0 and . <= 100)
+ and (.resetInSec | type == "number" and . >= 0);
+ (.rollingUsage | valid_window)
+ and (.weeklyUsage | valid_window)
+ and (.monthlyUsage | valid_window)
+ ' "$tmp_body" >/dev/null 2>&1; then
+ jq -c '
+ def make_window($w; $minutes; $label): {
+ usedPercent: $w.usagePercent,
+ windowMinutes: $minutes,
+ resetsAt: ((now + $w.resetInSec) | todate),
+ resetDescription: $label
+ };
+ . as $body |
+ {
+ provider: "opencode",
+ source: "opencode-usage",
+ usage: {
+ identity: {providerID: "opencode", accountEmail: "OpenCode Go account", loginMethod: "api-key"},
+ accountEmail: "OpenCode Go account",
+ loginMethod: "api-key",
+ primary: make_window($body.rollingUsage; 300; "5h"),
+ secondary: make_window($body.weeklyUsage; 10080; "weekly"),
+ tertiary: make_window($body.monthlyUsage; 43200; "monthly"),
+ updatedAt: (now | todate)
+ },
+ credits: {
+ remaining: (if $body.useBalance == true
+ then "OpenCode Go · balance fallback enabled"
+ else "OpenCode Go"
+ end)
+ }
+ }
+ ' "$tmp_body"
+ rm -f "$tmp_body"
+ return 0
+ fi
+
+ # /zen/go/v1/usage failed (timeout, 5xx, parse error). Try the documented
+ # /zen/go/v1/models endpoint — auth-only confirmation that the key works.
+ rm -f "$tmp_body"
+ tmp_body="$(mktemp)"
+ http_status="$(curl -sS --max-time 8 -o "$tmp_body" -w '%{http_code}' \
+ -H "Authorization: Bearer ${key}" \
+ -H "Accept: application/json" \
+ "https://opencode.ai/zen/go/v1/models" || true)"
+ rm -f "$tmp_body"
+
+ if [ "$http_status" = "200" ]; then
+ json_note_usage opencode opencode-models "OpenCode Go account" "api-key" \
+ "Authenticated — quota endpoint unavailable, usage at opencode.ai/zen" \
+ "OpenCode Go"
+ return 0
+ fi
+ json_note_usage opencode opencode-api "OpenCode Go account" "api-key" \
+ "API key set; OpenCode Zen unreachable" \
+ "OpenCode Go"
+}
+
fetch_glm_native() {
# GLM and Z.ai share one credential family. Honor ZAI_API_KEY here too so a
# single key drives both cards (matches docs/providers.md credential matrix).
@@ -2441,7 +2548,7 @@ fetch_provider() {
json_note_usage cline cline-local "Cline account" "subscription" "No public quota API; usage is available in the Cline client" "app.cline.bot"
;;
opencode)
- json_note_usage opencode opencode-local "OpenCode" "proxy" "OpenCode routes to underlying providers directly — check each provider's card" "opencode.ai"
+ fetch_opencode_native
;;
together)
fetch_together_native
diff --git a/tests/fixtures/opencode-error-401.json b/tests/fixtures/opencode-error-401.json
new file mode 100644
index 0000000..20d7466
--- /dev/null
+++ b/tests/fixtures/opencode-error-401.json
@@ -0,0 +1 @@
+{ "type": "error", "error": { "type": "AuthError", "message": "Missing API key." } }
diff --git a/tests/fixtures/opencode-error-403.json b/tests/fixtures/opencode-error-403.json
new file mode 100644
index 0000000..08c77c1
--- /dev/null
+++ b/tests/fixtures/opencode-error-403.json
@@ -0,0 +1 @@
+{ "type": "error", "error": { "type": "EntitlementError", "message": "OpenCode Go subscription required." } }
diff --git a/tests/fixtures/opencode-models.json b/tests/fixtures/opencode-models.json
new file mode 100644
index 0000000..0c0af5f
--- /dev/null
+++ b/tests/fixtures/opencode-models.json
@@ -0,0 +1 @@
+{ "object": "list", "data": [{"id": "claude-sonnet-5", "object": "model", "created": 1, "owned_by": "opencode", "name": "Claude Sonnet 5", "context_length": 1000000}] }
diff --git a/tests/fixtures/opencode-usage-balance.json b/tests/fixtures/opencode-usage-balance.json
new file mode 100644
index 0000000..0ca2ef9
--- /dev/null
+++ b/tests/fixtures/opencode-usage-balance.json
@@ -0,0 +1,6 @@
+{
+ "useBalance": true,
+ "rollingUsage": { "status": "ok", "resetInSec": 9000, "usagePercent": 42 },
+ "weeklyUsage": { "status": "ok", "resetInSec": 302400, "usagePercent": 18 },
+ "monthlyUsage": { "status": "ok", "resetInSec": 1209600, "usagePercent": 7 }
+}
diff --git a/tests/fixtures/opencode-usage-malformed.json b/tests/fixtures/opencode-usage-malformed.json
new file mode 100644
index 0000000..8ba71a5
--- /dev/null
+++ b/tests/fixtures/opencode-usage-malformed.json
@@ -0,0 +1,5 @@
+{
+ "useBalance": false,
+ "rollingUsage": { "status": "ok", "resetInSec": 9000, "usagePercent": 42 },
+ "weeklyUsage": { "status": "ok", "resetInSec": 302400, "usagePercent": 18 }
+}
diff --git a/tests/fixtures/opencode-usage.json b/tests/fixtures/opencode-usage.json
new file mode 100644
index 0000000..208e982
--- /dev/null
+++ b/tests/fixtures/opencode-usage.json
@@ -0,0 +1,6 @@
+{
+ "useBalance": false,
+ "rollingUsage": { "status": "ok", "resetInSec": 9000, "usagePercent": 42 },
+ "weeklyUsage": { "status": "ok", "resetInSec": 302400, "usagePercent": 18 },
+ "monthlyUsage": { "status": "ok", "resetInSec": 1209600, "usagePercent": 7 }
+}
diff --git a/tests/test-opencode.sh b/tests/test-opencode.sh
new file mode 100755
index 0000000..56999a4
--- /dev/null
+++ b/tests/test-opencode.sh
@@ -0,0 +1,143 @@
+#!/usr/bin/env bash
+# OpenCode Go subscription quota tracking — fake-curl unit test.
+# Verifies /zen/go/v1/usage maps to 5h (primary) + weekly (secondary) +
+# monthly (tertiary) percentages, with graceful fallback to
+# /zen/go/v1/models on usage failure, and hard errors on 401/403.
+set -euo pipefail
+
+ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
+TMP="$(mktemp -d)"
+trap 'rm -rf "$TMP"' EXIT
+HDR_LOG="$TMP/headers.log"
+# The dispatcher appends non-zero quota snapshots; keep fixtures out of the
+# developer's real history store, regardless of the calling environment.
+export XDG_CACHE_HOME="$TMP/cache"
+mkdir -p "$TMP/bin"
+
+# Fake curl that mimics real curl behavior:
+# -o file → writes body to file, suppresses body on stdout
+# -w FORMAT → writes FORMAT (here: the simulated HTTP status) to stdout
+# OC_USAGE_MODE picks the fixture/status served for /zen/go/v1/usage; the
+# stub always exits 0 so a simulated non-200 never looks like a curl network
+# failure to the parent script (see test-commandcode.sh for the same note).
+cat > "$TMP/bin/curl" <<'STUB'
+#!/usr/bin/env bash
+out=""
+url=""
+write_code=0
+args=("$@")
+for ((i = 0; i < ${#args[@]}; i++)); do
+ case "${args[$i]}" in
+ -o) out="${args[$((i + 1))]}" ;;
+ -w) write_code=1 ;;
+ -H) printf '%s\n' "${args[$((i + 1))]}" >> "${HDR_LOG:-/dev/null}" ;;
+ http*) url="${args[$i]}" ;;
+ esac
+done
+status=200
+case "$url" in
+ *zen/go/v1/usage*)
+ case "${OC_USAGE_MODE:-ok}" in
+ ok) [ -n "$out" ] && cp "${OC_FIXTURE_DIR}/opencode-usage.json" "$out"; status=200 ;;
+ unauthorized) [ -n "$out" ] && cp "${OC_FIXTURE_DIR}/opencode-error-401.json" "$out"; status=401 ;;
+ forbidden) [ -n "$out" ] && cp "${OC_FIXTURE_DIR}/opencode-error-403.json" "$out"; status=403 ;;
+ down) [ -n "$out" ] && printf '' > "$out"; status=500 ;;
+ balance) [ -n "$out" ] && cp "${OC_FIXTURE_DIR}/opencode-usage-balance.json" "$out"; status=200 ;;
+ malformed) [ -n "$out" ] && cp "${OC_FIXTURE_DIR}/opencode-usage-malformed.json" "$out"; status=200 ;;
+ esac
+ ;;
+ *zen/go/v1/models*)
+ [ -n "$out" ] && cp "${OC_FIXTURE_DIR}/opencode-models.json" "$out"
+ status=200
+ ;;
+esac
+if [ "$write_code" -eq 1 ]; then printf '%s' "$status"; fi
+exit 0
+STUB
+chmod +x "$TMP/bin/curl"
+
+run() { PATH="$TMP/bin:$PATH" HDR_LOG="$HDR_LOG" OC_FIXTURE_DIR="$ROOT/tests/fixtures" "$@" "$ROOT/providers/get-provider-usage" opencode 2>/dev/null; }
+fail() { echo "FAIL: $1" >&2; exit 1; }
+
+# 1. Happy path — /zen/go/v1/usage returns 5h + weekly + monthly percentages.
+out="$(OC_USAGE_MODE=ok run env -u OPENCODE_API_KEY OPENCODE_API_KEY=user_test)"
+[ "$(jq -r '.[0].source' <<<"$out")" = "opencode-usage" ] || fail "source"
+[ "$(jq -r '.[0].usage.primary.windowMinutes' <<<"$out")" = "300" ] || fail "5h minutes"
+[ "$(jq -r '.[0].usage.primary.resetDescription' <<<"$out")" = "5h" ] || fail "5h label"
+[ "$(jq -r '.[0].usage.primary.usedPercent' <<<"$out")" = "42" ] || fail "5h percent"
+[ "$(jq -r '.[0].usage.secondary.windowMinutes' <<<"$out")" = "10080" ] || fail "weekly minutes"
+[ "$(jq -r '.[0].usage.secondary.resetDescription' <<<"$out")" = "weekly" ] || fail "weekly label"
+[ "$(jq -r '.[0].usage.secondary.usedPercent' <<<"$out")" = "18" ] || fail "weekly percent"
+[ "$(jq -r '.[0].usage.tertiary.resetDescription' <<<"$out")" = "monthly" ] || fail "monthly label"
+[ "$(jq -r '.[0].usage.tertiary.usedPercent' <<<"$out")" = "7" ] || fail "monthly percent"
+[ "$(jq -r '.[0].credits.remaining' <<<"$out")" = "OpenCode Go" ] || fail "credits remaining"
+[ "$(jq -r '.[0].usage.identity.accountEmail' <<<"$out")" = "OpenCode Go account" ] || fail "identity"
+[ "$(grep -c '^Authorization: Bearer user_test$' "$HDR_LOG")" -ge 1 ] || fail "auth header forwarded"
+
+# 2. A local auth.json credential is a safe fallback for graphical sessions.
+CLI_HOME="$TMP/cli-home"
+mkdir -p "$CLI_HOME/.local/share/opencode"
+printf '%s\n' '{"opencode":{"type":"api","key":"cli_test"}}' > "$CLI_HOME/.local/share/opencode/auth.json"
+chmod 600 "$CLI_HOME/.local/share/opencode/auth.json"
+: > "$HDR_LOG"
+out="$(OC_USAGE_MODE=ok run env -u OPENCODE_API_KEY -u XDG_DATA_HOME HOME="$CLI_HOME")"
+[ "$(jq -r '.[0].source' <<<"$out")" = "opencode-usage" ] || fail "CLI auth source"
+grep -q '^Authorization: Bearer cli_test$' "$HDR_LOG" || fail "CLI auth header"
+health="$(OC_USAGE_MODE=ok env -u OPENCODE_API_KEY -u XDG_DATA_HOME HOME="$CLI_HOME" "$ROOT/providers/get-provider-health" opencode 2>/dev/null)"
+[ "$(jq -r '.[0].status' <<<"$health")" = "ready" ] || fail "CLI auth health"
+
+# 2b. Legacy flat {"apiKey": ...} shape (hand-edited or older opencode build)
+# still resolves to a usable credential — same file path, just the old key name.
+CLI_HOME_LEGACY="$TMP/cli-home-legacy"
+mkdir -p "$CLI_HOME_LEGACY/.local/share/opencode"
+printf '%s\n' '{"apiKey":"cli_legacy"}' > "$CLI_HOME_LEGACY/.local/share/opencode/auth.json"
+chmod 600 "$CLI_HOME_LEGACY/.local/share/opencode/auth.json"
+out="$(OC_USAGE_MODE=ok run env -u OPENCODE_API_KEY -u XDG_DATA_HOME HOME="$CLI_HOME_LEGACY")"
+[ "$(jq -r '.[0].source' <<<"$out")" = "opencode-usage" ] || fail "legacy cli auth source"
+health="$(OC_USAGE_MODE=ok env -u OPENCODE_API_KEY -u XDG_DATA_HOME HOME="$CLI_HOME_LEGACY" "$ROOT/providers/get-provider-health" opencode 2>/dev/null)"
+[ "$(jq -r '.[0].status' <<<"$health")" = "ready" ] || fail "legacy cli auth health"
+
+# 3. No supported credential source.
+EMPTY_HOME="$TMP/empty-home"
+mkdir -p "$EMPTY_HOME"
+out="$(OC_USAGE_MODE=ok run env -u OPENCODE_API_KEY -u XDG_DATA_HOME HOME="$EMPTY_HOME")"
+[ "$(jq -r '.[0].error.kind' <<<"$out")" = "provider" ] || fail "no-key error kind"
+health="$(env -u OPENCODE_API_KEY -u XDG_DATA_HOME HOME="$EMPTY_HOME" "$ROOT/providers/get-provider-health" opencode 2>/dev/null)"
+[ "$(jq -r '.[0].status' <<<"$health")" = "missing" ] || fail "no-key health"
+
+# 4. A caller-provided XDG data directory is honored by both adapter and health check.
+XDG_HOME="$TMP/xdg-data"
+mkdir -p "$XDG_HOME/opencode"
+printf '%s\n' '{"opencode":{"type":"api","key":"xdg_test"}}' > "$XDG_HOME/opencode/auth.json"
+out="$(OC_USAGE_MODE=ok run env -u OPENCODE_API_KEY HOME="$EMPTY_HOME" XDG_DATA_HOME="$XDG_HOME")"
+[ "$(jq -r '.[0].source' <<<"$out")" = "opencode-usage" ] || fail "XDG auth source"
+grep -q '^Authorization: Bearer xdg_test$' "$HDR_LOG" || fail "XDG auth header"
+health="$(env -u OPENCODE_API_KEY HOME="$EMPTY_HOME" XDG_DATA_HOME="$XDG_HOME" "$ROOT/providers/get-provider-health" opencode 2>/dev/null)"
+[ "$(jq -r '.[0].status' <<<"$health")" = "ready" ] || fail "XDG auth health"
+
+# 5. useBalance is an enabled fallback, not a numerical balance the endpoint provides.
+out="$(OC_USAGE_MODE=balance run env -u OPENCODE_API_KEY OPENCODE_API_KEY=user_test)"
+[ "$(jq -r '.[0].credits.remaining' <<<"$out")" = "OpenCode Go · balance fallback enabled" ] || fail "balance fallback label"
+
+# 6. A malformed quota payload must degrade to auth-only rather than invent a value.
+out="$(OC_USAGE_MODE=malformed run env -u OPENCODE_API_KEY OPENCODE_API_KEY=user_test)"
+[ "$(jq -r '.[0].source' <<<"$out")" = "opencode-models" ] || fail "malformed payload fallback"
+
+# 7. 401 → hard error, no fabricated percentage.
+out="$(OC_USAGE_MODE=unauthorized run env -u OPENCODE_API_KEY OPENCODE_API_KEY=user_test)"
+[ "$(jq -r '.[0].error.code' <<<"$out")" = "401" ] || fail "401 code"
+[ "$(jq -r '.[0].error.kind' <<<"$out")" = "provider" ] || fail "401 kind"
+[ "$(jq -r '.[0].error.message' <<<"$out")" = "Missing API key." ] || fail "401 message"
+
+# 8. 403 (valid key, no Go subscription) → hard error.
+out="$(OC_USAGE_MODE=forbidden run env -u OPENCODE_API_KEY OPENCODE_API_KEY=user_test)"
+[ "$(jq -r '.[0].error.code' <<<"$out")" = "403" ] || fail "403 code"
+[ "$(jq -r '.[0].error.message' <<<"$out")" = "OpenCode Go subscription required." ] || fail "403 message"
+
+# 9. /zen/go/v1/usage down (non-401/403 failure) → fallback to /zen/go/v1/models.
+: > "$HDR_LOG"
+out="$(OC_USAGE_MODE=down run env -u OPENCODE_API_KEY OPENCODE_API_KEY=user_test)"
+[ "$(jq -r '.[0].source' <<<"$out")" = "opencode-models" ] || fail "fallback source"
+grep -q '^Authorization: Bearer user_test$' "$HDR_LOG" || fail "auth header forwarded to models fallback"
+
+echo "OK: test-opencode"