Repository navigation
Add OpenCode Zen/Go local usage to usage panel - #6526
chipkoziara wants to merge 2 commits into
Conversation
|
Screenshot of the OpenCode view: Tried to mirror approach from Claude, Codex, and Fireworks (via PR #6488). |
There was a problem hiding this comment.
🟡 Not ready to approve
The database query cannot read actual OpenCode tokens, and route-selection edge cases can hide available usage.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Pull request overview
Adds OpenCode Zen/Go local usage scanning, route filtering, synchronization, and panel presentation.
Changes:
- Scans Pi transcripts and the OpenCode database.
- Adds Zen, Go, and merged usage views.
- Extends synchronization, documentation, assets, and tests.
[!TIP]
If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or rungh pr ready --undo.
Click "Ready for review" or rungh pr readyto reengage.
File summaries
| File | Description |
|---|---|
test/shell.d/model-usage-opencode-scanner-test.sh |
Tests scanner aggregation and route separation. |
shell/plugins/model-usage/scripts/opencode_usage_scanner.py |
Scans Pi and OpenCode usage. |
shell/plugins/model-usage/README.md |
Documents OpenCode behavior. |
shell/plugins/model-usage/providers/OpenCode.qml |
Adds the provider adapter. |
shell/plugins/model-usage/Panel.qml |
Adds route selection and presentation. |
shell/plugins/model-usage/manifest.json |
Enables and describes OpenCode. |
shell/plugins/model-usage/Main.qml |
Integrates provider state and synchronization. |
shell/plugins/model-usage/assets/opencode.svg |
Adds the dark-surface logo. |
shell/plugins/model-usage/assets/opencode-light.svg |
Adds the light-surface logo. |
Review details
- Files reviewed: 7/9 changed files
- Comments generated: 6
- Review effort level: Balanced
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
There was a problem hiding this comment.
🟡 Not ready to approve
Database aggregation misattributes resumed-session usage and omits reasoning tokens.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Suppressed comments (2)
shell/plugins/model-usage/scripts/opencode_usage_scanner.py:251
- The daily buckets group each session's cumulative token total by
time_created. If an existing OpenCode session is resumed on a later day, all newly consumed tokens remain attributed to the original creation day, so “today” and the seven-day chart are guaranteed to be wrong for resumed sessions. Aggregate usage records by their own timestamps (for example, from message/part data) and count distinct session IDs per day instead.
"SELECT date(time_created/1000,'unixepoch','localtime') AS day, "
"count(*) AS sessions, "
"sum(tokens_input) AS input, sum(tokens_output) AS output, "
"sum(tokens_cache_read) AS cache_read, sum(tokens_cache_write) AS cache_write "
"FROM session GROUP BY day ORDER BY day"
shell/plugins/model-usage/scripts/opencode_usage_scanner.py:250
- The session schema also exposes
tokens_reasoning, but this aggregation omits it and later totals only input/output/cache fields. Reasoning-heavy OpenCode sessions will therefore be under-reported. Include the reasoning sum in the query and fold it into the token total (or expose a dedicated bucket).
"sum(tokens_input) AS input, sum(tokens_output) AS output, "
"sum(tokens_cache_read) AS cache_read, sum(tokens_cache_write) AS cache_write "
- Files reviewed: 7/9 changed files
- Comments generated: 0 new
- Review effort level: Balanced
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
|
Addressed both suppressed review points in Resumed-session attribution — the CLI-db scan previously bucketed each session's cumulative SELECT date(m.time_created/1000,'unixepoch','localtime') AS day,
count(DISTINCT m.session_id) AS sessions,
sum(json_extract(m.data,'$.tokens.input')) AS input,
...
FROM message m
WHERE json_extract(m.data,'$.role') = 'assistant'
GROUP BY day ORDER BY dayVerified against a live opencode DB: every session has message rows, and full-history message sums match the Reasoning tokens — Both changes are covered by the updated scanner test (fixture now includes a reasoning component, merged-day total updated accordingly); the full scanner suite passes 8/8. |
There was a problem hiding this comment.
🟡 Not ready to approve
CLI reasoning tokens are double-counted, and sessions spanning multiple dates inflate the total session count.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Suppressed comments (2)
shell/plugins/model-usage/scripts/opencode_usage_scanner.py:125
- OpenCode stores
tokens.reasoningas the reasoning-token detail withintokens.output, so adding both inflates every CLI day containing reasoning. This is the same subset relationship handled by the Codex scanner. Compute the total from input, output, and cache fields only; the fixture's merged expectation should consequently be 4,450 rather than 4,700.
# Reasoning tokens are part of the billed total, so they fold into the
# day's messageCount just like input/output/cache do.
total = input_tokens + output_tokens + reasoning_tokens + cache_read + cache_write
shell/plugins/model-usage/scripts/opencode_usage_scanner.py:302
- These synthetic keys are scoped by day, so one OpenCode session with assistant messages on two dates is counted twice in
totalSessions, unlike Pi sessions and the other scanners, which deduplicate by the real session identity. Return/group bym.session_idand use that ID as the key while still aggregating tokens per day.
session_keys = ["cli:" + day + ":" + str(index) for index in range(sessions)]
both.add_db_day(day, session_keys, input_tokens, output_tokens, reasoning_tokens, cache_read, cache_write)
- Files reviewed: 7/9 changed files
- Comments generated: 0 new
- Review effort level: Balanced
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
There was a problem hiding this comment.
🟡 Not ready to approve
Native OpenCode database metadata is discarded, preventing CLI usage from appearing in Zen/Go route views.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Suppressed comments (1)
shell/plugins/model-usage/scripts/opencode_usage_scanner.py:256
- Current OpenCode assistant rows carry
providerIDandmodelIDinm.data, but this aggregation discards both by grouping only on the day. As a result, all native OpenCode CLI tokens are added only toboth; CLI-only Zen/Go users get no route switch, and the per-route/model views omit their usage. Select and group by those fields, route recognized providers into Zen/Go while keeping untagged legacy rows merged-only, and extend the fixture for both cases.
"SELECT date(m.time_created/1000,'unixepoch','localtime') AS day, "
"group_concat(DISTINCT m.session_id) AS session_ids, "
"sum(json_extract(m.data,'$.tokens.input')) AS input, "
"sum(json_extract(m.data,'$.tokens.output')) AS output, "
"sum(json_extract(m.data,'$.tokens.reasoning')) AS reasoning, "
- Files reviewed: 7/9 changed files
- Comments generated: 0 new
- Review effort level: Balanced
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
There was a problem hiding this comment.
🟡 Not ready to approve
OpenCode reasoning tokens are omitted from the per-model breakdown, producing inconsistent totals.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Suppressed comments (1)
shell/plugins/model-usage/scripts/opencode_usage_scanner.py:142
- Reasoning tokens are included in
messageCount,todayTotalTokens, andtodayTokensByModel, but omitted frommodelUsage. As a result, the panel's “Tokens by model” totals undercount OpenCode CLI usage (the fixture's Zen model shows 3,500 while the day shows 3,750) and can rank models incorrectly. Fold reasoning into the output bucket, since the shared model schema has no separate reasoning field.
bucket["inputTokens"] += input_tokens
bucket["outputTokens"] += output_tokens
bucket["cacheReadInputTokens"] += cache_read
bucket["cacheCreationInputTokens"] += cache_write
- Files reviewed: 7/9 changed files
- Comments generated: 0 new
- Review effort level: Balanced
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
There was a problem hiding this comment.
🟡 Not ready to approve
Sync initialization can replace the documented Zen default with Both before remote route data loads.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Suppressed comments (1)
shell/plugins/model-usage/Main.qml:200
- When sync is enabled on a machine with no local routed usage, the local scan can run before the first aggregate arrives and reset the initial
zenvalue toboth. Once synced Zen data arrives, this early return preservesboth, so the panel does not use the documented Zen default. Track whetherbothwas explicitly selected, or defer the no-route fallback until the initial sync completes.
function opencodeSubViewReset() {
var routes = root.displayActiveRoutes()
// "both" (the merged view) always exists, so it survives any scan;
// single-route views survive while their route has data in the displayed set.
if (root.opencodeSubView === "both" || routes.indexOf(root.opencodeSubView) >= 0)
return
- Files reviewed: 7/9 changed files
- Comments generated: 0 new
- Review effort level: Balanced
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
There was a problem hiding this comment.
🟡 Not ready to approve
Automatic route fallback can become incorrectly sticky, and test timestamps are timezone-dependent.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Suppressed comments (3)
test/shell.d/model-usage-opencode-scanner-test.sh:91
- Like the Zen fixture, this fixed
11:00Ztimestamp can resolve outside localDAY(for example, the next day in UTC+14), making the Go route and merged-today assertions timezone-dependent. Emit an offset-aware timestamp for localDAYbefore writing it.
{"type":"session","version":3,"id":"go-ses","timestamp":"${DAY}T11:00:00Z","cwd":"/demo"}
shell/plugins/model-usage/Main.qml:208
- An automatic fallback does not clear
opencodeSubViewExplicit. For example, after a user selects Go, if all attributed route data disappears this function changes the view tobothbut leaves the flag true; when route data returns, line 204 then treats that fallback as an explicit Both choice and never restores the Zen/Go default. Clear the flag whenever this function changes an invalid view automatically.
if (root.opencodeSubView === "both" && root.opencodeSubViewExplicit) return
if (routes.indexOf(root.opencodeSubView) >= 0) return
if (routes.indexOf("zen") >= 0) root.opencodeSubView = "zen"
else if (routes.indexOf("go") >= 0) root.opencodeSubView = "go"
else root.opencodeSubView = "both"
test/shell.d/model-usage-opencode-scanner-test.sh:87
- This fixture combines a local
DAYwith a fixed UTC time, so it is not always onDAY: at UTC-11,10:00Zis the previous local date, and at UTC+14 it is the next date. The “today” totals therefore fail solely based on the machine timezone. Emit an offset-aware timestamp for a local time onDAYinstead.
This issue also appears on line 91 of the same file.
{"type":"session","version":3,"id":"zen-ses","timestamp":"${DAY}T10:00:00Z","cwd":"/demo"}
- Files reviewed: 7/9 changed files
- Comments generated: 0 new
- Review effort level: Balanced
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
There was a problem hiding this comment.
🟡 Not ready to approve
The database test stub does not validate the SQL contract, allowing scanner query regressions to pass unnoticed.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Suppressed comments (1)
test/shell.d/model-usage-opencode-scanner-test.sh:28
- The fake CLI returns fixture rows for every
dbinvocation, so this test never validates the SQL contract it is meant to cover. A regression to the wrong table or token JSON path would still pass even though real CLI usage disappears. Make the stub reject unexpected query text/arguments (or execute the query against a fixture database) so the scanner’s message-table query is exercised.
cat >"$TEST_HOME/bin/opencode" <<EOF
#!/bin/bash
if [[ \$1 == "db" ]]; then
- Files reviewed: 7/9 changed files
- Comments generated: 0 new
- Review effort level: Balanced
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
|
Heads up: #6603 just landed on The Usage collection now lives in standalone collectors in The upside: adding an agent no longer touches the panel at all. Ship a collector that prints the record contract, optionally add an If you'd like to update this PR to the new structure, rebase on — Claude Fable 5, written on behalf of @dhh |
Convert the old in-plugin opencode scanner into two standalone
collectors for the omarchy.agents panel. Zen (API-key billing)
and Go (subscription) are separate records with shared scanning
logic, each appearing as its own panel tab when usage exists.
- bin/opencode-usage-base.py: shared Python base, --route flag
- bin/omarchy-agent-usage-opencode-{zen,go}: thin shell wrappers
- shell/plugins/agents/assets/: copied opencode SVG icons
- test/shell.d/agent-usage-opencode-test.sh: route-specific tests
c70aa0e to
33ac610
Compare
|
Updated to match the new omarchy.agents architecture. Rebased on quattro and reshaped the old in-plugin scanner into two standalone collectors, following the pattern from #6604 What changedThe old model-usage plugin was renamed to omarchy.agents in #6603, and the panel is now strictly a display — it watches JSON records under ~/.local/state/omarchy/agents/usage/ and draws whatever appears. All the old in-plugin machinery (providers/.qml, scripts/.py) no longer exists. This update replaces those with:
Why split Zen and Go into separate records?Zen is API-key billing (pay-per-token) and Go is a monthly subscription — different billing models that each deserve their own panel tab with independent enable/disable and future limits. The panel treats each JSON record as a separate tab, so two records fit naturally. Each tab auto-hides when there's no usage for that route. Neither has rate limits details available (OpenCode doesn't expose them via a public API), so both records carry empty limits arrays. The panel shows local token stats only. Screenshot / Demo |
The agents panel resolves marks by provider id (assets/<id>.svg plus a -light twin for light surfaces), but the OpenCode mark was only committed as opencode.svg / opencode-light.svg. With records id'd opencode-zen and opencode-go, nothing matched and the hero fell back to the text glyph. Copy the shared brand mark to opencode-zen.svg / opencode-zen-light.svg and opencode-go.svg / opencode-go-light.svg, and guard it with a test so the panel convention can't silently regress again.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 11 changed files in this pull request and generated no new comments.
Suppressed comments (5)
test/shell.d/agent-usage-opencode-test.sh:1
- The heredoc delimiter is single-quoted (
<<'JSON'), so$DAY/$YDAYwill not expand and will be emitted literally in the fake CLI JSON. This prevents CLI rows from matching the collector’srecentDaysdates and will break the day-based token assertions. Use an unquoted heredoc delimiter (or otherwise interpolate the variables) so the fixture outputs real date strings.
#!/bin/bash
SPEC-opencode-collector.md:1
- The spec states
tierLabelshould be empty / no tier label, but the implementation and test asserttierLabelisAPIfor Zen andSubscriptionfor Go. Please reconcile the spec with the current behavior (either document the labels as intentional UI metadata, or change the collector/tests to emit an empty tier label).
# Spec: OpenCode Agent Usage Collector
test/shell.d/agent-usage-opencode-test.sh:270
- This 'no CLI' check can be non-hermetic: including
/usr/local/binmay still expose a realopencodeinstallation, making the test flaky across environments. To reliably simulate 'no opencode CLI', constrainPATHto directories that containpython3but notopencode(or setPATHto a temp dir and invokepython3via an absolute path discovered earlier in the test).
result_no_cli=$(HOME="$TEST_HOME" PATH="/usr/bin:/bin:/usr/local/bin" \
python3 "$BASE" --route zen)
SPEC-opencode-collector.md:43
- The spec states
tierLabelshould be empty / no tier label, but the implementation and test asserttierLabelisAPIfor Zen andSubscriptionfor Go. Please reconcile the spec with the current behavior (either document the labels as intentional UI metadata, or change the collector/tests to emit an empty tier label).
- The record will have `limits: []` and `tierLabel: ""`
SPEC-opencode-collector.md:85
- The spec states
tierLabelshould be empty / no tier label, but the implementation and test asserttierLabelisAPIfor Zen andSubscriptionfor Go. Please reconcile the spec with the current behavior (either document the labels as intentional UI metadata, or change the collector/tests to emit an empty tier label).
| `tierLabel` | string | `""` — no tier info |
Adds native support for OpenCode Go subscriptions in the agents panel. Queries OpenCode's official /zen/go/v1/usage API for rolling (5h), weekly, and monthly limit usage with reset timers. Unique features compared to other PRs (omacom#7157, omacom#8065, omacom#6526, omacom#6779, omacom#6464): - Pi session scanning for local token stats - Harness-agnostic key discovery (Pi, OpenCode CLI, env var) - Account-scoped metadata (scope, hasPromptStats) Key design decisions: - Uses curl instead of urllib to bypass Cloudflare TLS fingerprinting - Includes light/dark icon variants matching OpenCode brand colors - Adds test coverage for auth lookup, API parsing, and error handling
I thought so too at first, but |
|
Automated duplication check: this pull request looks similar to #8065, which covers the same OpenCode usage collector. I keep that one open and close this one to consolidate review. If you feel this is the wrong decision, please open the PR again with a note on the difference. |


Summary
~/.pi/agent/sessionstranscripts (zen/go split by provider tag) and the opencode CLI session database for per-route tokensTesting
omarchy-shell omarchy.model-usage refreshNotes
OpenCode Go usage isn't available via API, so this is just showing OpenCode and Pi sessions: the panel reads only transcripts and sessions recorded on this machine, never the live Zen/Go balance from the web console (due to lack of public API availability).