Skip to content

feat(agents): add OpenCode Go collector with watermark incremental scan - #6464

Closed
markbus-ai wants to merge 1 commit into
omacom:quattrofrom
markbus-ai:feat/model-usage-opencode
Closed

markbus-ai wants to merge 1 commit into
omacom:quattrofrom
markbus-ai:feat/model-usage-opencode

Conversation

@markbus-ai

@markbus-ai markbus-ai commented Jul 31, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds an OpenCode Go collector to the omarchy.agents usage widget, in the standalone-collector architecture (bin/omarchy-agent-usage-<agent> prints a display-ready JSON record; the panel discovers it by convention, no panel changes needed).

The collector shows the real subscription meters dhh asked for — rolling 5-hour / weekly / monthly windows as percentages with reset times from OpenCode's official usage API — plus token stats from the local opencode.db.

What's new (vs the earlier model-usage approach)

Piece Role
bin/omarchy-agent-usage-opencode-go Collector: live limits probe + pure-SQL scan + watermark incremental cache
shell/plugins/agents/assets/opencode-go.svg (+ -light) Panel mark
test/shell.d/agent-usage-opencode-scanner-test.sh 53-assertion suite (bash + python fixtures)
README Record contract, scan behavior, channel selection

Highlights

  • Real plan limits — GET https://opencode.ai/zen/go/v1/usage with the Go API key from ~/.local/share/opencode/auth.json or OPENCODE_GO_API_KEY. Handles both the deployed response shape (percent/resetsAt) and the upstream-PR shape (usagePercent/resetInSec), with payload-wide percent-scale detection (no meter discontinuity after a window reset).
  • Watermark incremental scan — the headline difference. The collector never re-scans the whole table in steady state: it tracks a watermark (max time_created), the db/WAL mtimes, and a 10-minute overlap band with id-dedupe, so a normal refresh reads only what changed. On a 1.9 GB / 62k-row database:
    • cold full scan: 4.3–5.2 s / ~166 MB RSS
    • steady-state fast path (db unchanged): 0.72 ms (≈6,000× faster, SQLite not even opened)
  • Pure-SQL aggregation — json_extract exact-match on role/providerID is the authority (LIKE prefilters are pure acceleration); rows stream as lightweight columns, raw JSON blobs never accumulate in Python.
  • Multi-db channel selection — among opencode-*.db files it prefers the one with the newest WAL activity, so users on a release channel see the right database instead of a stale default.
  • Hardening — https-only endpoint override (refuses http:// with no key sent, loud warning on non-default host), Authorization stripped on any (scheme, netloc) redirect change (including https→http downgrade), O_NOFOLLOW lock, cache files 0600 / dir 0700, empty XDG_CACHE_HOME fallback, cache failures always degrade to a valid record — the collector never prints an empty stdout.
  • Concurrency-safe cache commits — envelope writes are gated on watermark progress (not mtimes) so a concurrent older merge can never regress a fresher envelope, including at the midnight day-rollover boundary; --force always rewrites a fresh scan even when the table shrank.

Test plan

  • 53-assertion scanner suite: record contract, SQL totals, both response shapes, percent scale, envelope v3 (bandMs, watermark, mtimes, session-id sets), FAST/INCREMENTAL/FULL paths, overlap dedupe, band-migration guard, day rollover, race/rollover concurrency, force-after-delete, channel selection, redirect safety, permissions, unwritable-cache degrade, timestamp unit guard
  • Existing collector suites still green (codex 20, claude 8)
  • Live run on a 1.9 GB database (cold + fast path + incremental), benchmark table above

Notes

  • During local verification the live probe returned HTTP 403 for the test key; the collector maps this to usageStatusText/authHelpText and still emits a valid record with local stats. Both payload shapes are unit-tested.

Copilot AI review requested due to automatic review settings July 31, 2026 15:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds OpenCode usage reporting to the model-usage panel alongside Claude Code and Codex.

Changes:

  • Adds a SQLite usage scanner and OpenCode provider adapter.
  • Integrates OpenCode into the panel, defaults, synchronization, and icons.
  • Adds scanner and widget-contract tests plus documentation.

Tip

If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.

Reviewed changes

Copilot reviewed 8 out of 10 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
shell/plugins/model-usage/Main.qml Registers and aggregates OpenCode usage.
shell/plugins/model-usage/Panel.qml Adds OpenCode icon selection.
shell/plugins/model-usage/README.md Documents OpenCode behavior and configuration.
shell/plugins/model-usage/manifest.json Enables OpenCode by default.
shell/plugins/model-usage/providers/OpenCode.qml Implements the provider adapter.
shell/plugins/model-usage/scripts/opencode_usage_scanner.py Scans OpenCode’s SQLite session data.
shell/plugins/model-usage/assets/opencode.svg Adds the dark-surface icon.
shell/plugins/model-usage/assets/opencode-light.svg Adds the light-surface icon.
test/shell.d/model-usage-opencode-scanner-test.sh Tests scanner aggregation and missing data.
test/shell.d/fixtures/bar-widget-contract/shell.qml Tests OpenCode icon selection.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +247 to +255
query = f"""
SELECT {", ".join(select_cols)}
FROM session
WHERE COALESCE(tokens_input, 0)
+ COALESCE(tokens_output, 0)
+ COALESCE(tokens_cache_read, 0)
+ COALESCE(tokens_cache_write, 0)
+ COALESCE(tokens_reasoning, 0) > 0
"""
Comment thread shell/plugins/model-usage/manifest.json Outdated
Copilot AI review requested due to automatic review settings July 31, 2026 20:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 12 changed files in this pull request and generated no new comments.

Suppressed comments (1)

shell/plugins/model-usage/scripts/opencode_usage_scanner.py:169

  • The scanner takes the full message path that the PR explicitly says it avoids: every refresh selects and JSON-parses every message, and totalPrompts is consequently counted per assistant message rather than per usage-bearing session. Current OpenCode databases expose tokens_input, tokens_output, tokens_reasoning, and cache aggregates on session, so this also misses the advertised millisecond-scale aggregate scan and can become expensive on large histories. Please use the session aggregates as the primary path (with a legacy message-schema fallback if needed) and update the fixture/semantics accordingly.
    rows = conn.execute("SELECT id, session_id, time_created, data FROM message")

@dhh

dhh commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Hmmm. I'm a little skeptical on this. I think what we could need is the subscription status for OpenCode Zen and Go. But it needs to expose the limits. That's actually what makes this useful.

@markbus-ai

Copy link
Copy Markdown
Contributor Author

@dhh Agree — the useful part is Go/Zen subscription meters, not local token totals.

I dug into what OpenCode actually exposes:

  • API keys (opencode / opencode-go) only get inference + GET /zen/v1/models. No spend, balance, or plan windows.
  • Go limits ($12 / 5h, $30 weekly, $60 monthly) exist server-side and power GoUsageLimitError when you hit the wall, plus the web console via queryLiteSubscription — but that path is session-cookie SolidStart RPCs, not something the CLI/API key can read.
  • Tools like OpenUsage reverse-engineer those console RPCs with browser cookies and deploy-rotating function IDs. That works as a hobby scraper; it is not something Omarchy should ship.

So this PR’s local opencode.db stats are real, but they are not the Claude/Codex-shaped product (plan % + reset). Happy to close this until OpenCode exposes a stable usage endpoint for API keys (or reopen/extend once they do). Local day/model totals can stay as a follow-up under real meters, not as the main feature.

@dhh

dhh commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Maybe we can convince @thdxr to add this!

@markbus-ai

Copy link
Copy Markdown
Contributor Author

@dhh The endpoint you're describing is already requested upstream — we don't need to invent the shape:

That maps 1:1 onto the session/weekly meter pair the panel already renders for Claude and Codex. If @thdxr's team exposes it to API keys, this provider becomes the real product (plan %, reset countdown, bar alarm) with the local opencode.db totals as the fallback layer instead of the headline.

I'd say: keep this PR as the ready-to-rebase foundation, and let's put the +1 with the Omarchy use case on #16017 so the OpenCode team sees a concrete consumer asking for it.

@dhh

dhh commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Heads up: #6603 just landed on quattro and restructured this whole area, so this PR needs a rework before it can merge.

The model-usage plugin is gone. It's been renamed to omarchy.agents (shell/plugins/agents/), and the panel is now strictly a display: it watches JSON records under ~/.local/state/omarchy/agents/usage/ and draws whatever appears there. All the in-plugin machinery this PR touches — providers/*.qml adapters and scripts/*_usage_scanner.py — no longer exists.

Usage collection now lives in standalone collectors in bin/. One omarchy-agent-usage-<agent> script per agent prints a complete, display-ready usage record (identity, tier, status, rate limits, today/week/all-time stats), and omarchy-agent-usage-update runs every collector it finds and writes the records atomically to the state directory.

The upside: adding an agent no longer touches the panel at all. Ship a collector that prints the record contract, optionally add an assets/<id>.svg mark (with an -light twin if it needs one for light surfaces), and the panel gains a tab on its own. See shell/plugins/agents/README.md for the contract and the claude/codex collectors in bin/ as reference implementations — #6604 (Cursor) is a good example of a provider PR in the new style.

If you'd like to update this PR to the new structure, rebase on quattro and reshape the scanner into a collector. Happy to answer questions here.

— Claude Fable 5, written on behalf of @dhh

@markbus-ai

Copy link
Copy Markdown
Contributor Author

Closing this in favor of #6779, which implements the OpenCode Go collector in the new omarchy.agents architecture (standalone collector + assets + tests) and now uses the official usage endpoint (GET /zen/go/v1/usage) for the rolling/weekly/monthly meters dhh asked about. This PR predates the #6603 restructure that removed the model-usage plugin it targets, so keeping it open would just confuse the history. Thanks for the discussion.

@markbus-ai markbus-ai closed this Aug 13, 2026
@markbus-ai markbus-ai reopened this Aug 18, 2026
Add an omarchy-agent-usage-opencode-go collector that feeds a new Go
subscription tab in the agents panel. Local token/session stats come from
opencode's message database (filtered to the opencode-go provider) via a
pure-SQL scan with a watermark incremental cache: a fast path serves the
envelope without opening SQLite when the database is unchanged, and a
changed database only rescans rows newer than the last watermark (with a
10-minute overlap band) and merges the deltas. Rate limits come from
OpenCode's zen usage endpoint (session/weekly/monthly), with the key read
from the opencode-go auth entry or OPENCODE_GO_API_KEY, https-only base
URL validation, and a safe redirect handler that strips the Authorization
header on origin changes including https->http downgrades.

Hardens the cache layer: 0600/0700 permissions, O_NOFOLLOW locks, atomic
temp+replace writes, stale-tmp sweep, multi-db channel selection, and
graceful degradation when the cache or limits cache is unavailable. Ships
the opencode-go mark (+ -light), the scanner test suite, and a README row.

Refs: feat/model-usage-opencode
@markbus-ai
markbus-ai force-pushed the feat/model-usage-opencode branch from 696158f to 3659dc9 Compare August 19, 2026 12:36
@markbus-ai markbus-ai changed the title Add OpenCode provider to model-usage panel feat(agents): add OpenCode Go collector with watermark incremental scan Aug 19, 2026
kczimm added a commit to kczimm/omarchy that referenced this pull request Aug 28, 2026
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
@bjarneo

bjarneo commented Sep 20, 2026

Copy link
Copy Markdown
Member

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.

@bjarneo bjarneo closed this Sep 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants