Repository navigation
Description
Direct providers: full-function completeness + quota single-source (opencode quota subcommand)
Drew's order, 2026-11-05 01:25 CST (verbatim key lines): "if other direct lanes don't handle all functions including quota then fix them too tonight don't wait. renewal, everything. dashboard and all other sources should rely on opencode quota subcommand only, 1 single source for quota info. if a tracked files that a program populate with quota readings exclusively from opencode is used that's acceptable. all quota info should be landing in supa on regular basis in addition to lvis dashboard. if dashboard quota are cached make a refresh button that pipes true values from opencode quota subcommand. for dashboard only, if there isn't already a non direct then -direct in names can be stripped in markup. but if there is an openai then openai-direct shouldn't be stripped, for instance."
Doctrine (Drew, 2026-11-05 01:13): the *-direct providers EMULATE first-party clients' wire behavior in our code; vendor binaries are login/bootstrap-only. Quota is a provider FUNCTION like any other — each provider's quota implementation emulates its first-party billing/usage endpoint(s), and lives IN the provider (opencode fork), exposed through ONE subcommand: opencode quota.
Current state (evidence, 2026-11-05 ~01:40 CST)
- The opencode fork CLI (v2.0.30, deployed) has no
quotasubcommand (subcommands: upgrade, uninstall, acp, api, debug, auth, mcp, plugin, models, stats, mini, run, session, msg, service, reload, pair, serve). The fork has no provider quota function for any provider. - Today's quota producer is the OpenChamber quota module (
packages/web/server/lib/quotain the openchamber repo; per-provider JS fetchers), imported in-process by the laptop gathererpm-stack/aggregator/scripts/gather-quota.ts(quota-gatherer.timer, ~15 min), which writes~/.local/state/pm-aggregator/quota-current.json. Credentials are read from the opencode server (GET /api/credential) with a read-only local-DB fallback. - Supabase
agent.quota_records(muse-dev2) receives rows on a ~5-min recorder cadence (verify + keep; its input must be the subcommand-sourced file). - The aggregator (LVIS) dashboard reads quota-current.json;
src/quota-refresh.tstriggers the gatherer when data is >5 min stale, but there is no user-facing refresh button and no-directdisplay-name rule.
Function matrix (deployed config providers × functions)
Legend: ✓ works (evidence) · ✗ missing · ⚠ broken/degraded · n/a by design. "Quota today" = read by the OpenChamber module fetcher (to be replaced by the in-provider implementation behind opencode quota).
| Provider (config id) | Kind | Inference | Model discovery | Quota today | Quota in provider (target) | Auth renewal |
|---|---|---|---|---|---|---|
| antigravity-direct | fork-native emulation (@opencode/ai/providers/antigravity) | ✓ (gemini-flash/argon/sonnet/opus seats) | ✓ | ✓ module antigravity fetcher, tick ok | ✗ → build | OAuth refresh in fork antigravity-auth.ts; registry via auth-renewal lane |
| vibe-direct | fork-native emulation (@opencode/ai/providers/vibe) | ✓ proven live 2026-11-05 | ✓ | ⚠ module mistral fetcher returns no %; the plan's real figures (API $0.47/$15, Vibe $0.04/$150, reset Dec 1) come only from the Mistral admin console budget endpoint (session-cookie auth) — not yet a provider function | ✗ → vibe lane (epic opencode#1709), acceptance: opencode quota reports both dollar budgets matching console truth |
keyring credential ai.mistral.vibe; cookie for console expires 2027-02-02; auto-renew classification per auth-renewal lane |
| mistral | fork-native API provider (@opencode/ai/providers/mistral) | ✓ | ✓ | ⚠ same console-budget truth as vibe (same account); module fetcher no-data | ✗ → build (shares the console budget read with vibe-direct's quota; the provider performs it, cookie held by provider credential machinery) | API key (BWS mistral.api-key); no renewal |
| claude-code | local proxy :18749 (@ai-sdk/openai-compatible) — provider DISABLED (temp, free tier) | ✓ when enabled (setup confirmed) | ✓ | ⚠ module claude fetcher; latest tick state error "Rate limited. Retrying soon." | ✗ → build | ⚠ leaf credential functionally expired — Drew's sign-in act outstanding; classify in renewal registry |
| opendesign | openai-compatible (AMR API) | ✓ | ✓ | ✓ module opendesign fetcher, tick ok — but rides a Better Auth session cookie in OpenChamber's store, not the opencode credential (code#5060 gap) | ✗ → build (provider quota reads the session the renewal keeper maintains) | ✓ automatic CDP renewal keeper (auth-renewal lane, 2026-11-05) — cookie would otherwise expire 2026-11-06 |
| meta | @ai-sdk/openai — direct door DISABLED, route-only pool | ✓ via model routes | ✓ | ⚠ module meta fetcher wired but the portal % read is dead server-side (window exhausted, key endpoint retired); restore tracked in #6079 — under this program the portal-API fetch moves INTO the meta provider's quota implementation | ✗ → build (absorbs #6079's fetcher restore) | portal cookie — must gain auto-renew per Drew's standing rule (auth-renewal lane registry) |
| cline-pass | openai-compatible (ClinePass) | ✓ | ✓ | ✓ module cline-pass fetcher + aggregator native adapter (#1159), tick ok | ✗ → build | token; expired = session-expired handling, last-known retained |
| commandcode | openai-compatible | ✓ | ✓ | ✓ module command-code fetcher (plan read, not balance), tick ok | ✗ → build | API credential; no renewal |
| deepseek | openai-compatible (api.deepseek.com, key 1017) | ✓ | ✓ | ✓ module deepseek fetcher, tick ok | ✗ → build | API key; no renewal |
| openai.main | ChatGPT/Codex OAuth (drewrypope) | ✓ | ✓ | ✓ module openai/codex fetchers, tick ok | ✗ → build | OAuth refresh (in-server) |
| openai.alternate | ChatGPT/Codex OAuth (popesdrew) | ✓ | ✓ | ✓ module openai/codex fetchers, tick ok | ✗ → build | OAuth refresh (in-server) |
| opencode-go.drewrypope | OpenCode Go subscription | ✓ | ✓ | ✓ module opencode-go fetcher, tick ok | ✗ → build | subscription credential |
| opencode-go.popesdrew | OpenCode Go subscription | ✓ | ✓ | ✓ (same fetcher, per-credential) | ✗ → build | subscription credential |
| openrouter | @ai-sdk/openai-compatible | ✓ | ✓ | ⚠ module fetcher exists; not in current tick pools (no pool configured) | ✗ → build when pooled | API key |
| groq | @ai-sdk/openai-compatible | ✓ | ✓ | ✗ no fetcher runs for groq | ✗ → build or declare no-quota-surface with evidence | API key |
| turso (tick pool, not an opencode provider) | org usage API | n/a | n/a | ✓ gatherer family-3 direct API read | → subcommand built-in fetcher (single-source rule leaves no gatherer-side reads) | platform API token (BWS by name) |
| virtual route providers (openai-plan, mistral-plan, deepseek-flash-plan, deepseek-pro-plan, haiku-plan, luna-plan, gemini-flash-plan, sonnet-plan, opus-plan, spark-plan, sol-plan, terra-plan, argon-plan, beam-plan, mimo-flash-plan) | opencode-route | ✓ | ✓ | n/a — pools are the leaf providers above | n/a by design | n/a |
Program (this issue tracks; children carry the work)
- Fork:
opencode quotasubcommand (developing-today-forks/opencode) — provider quota interface inpackages/ai; per-provider quota implementations for every configured pool above, porting the OpenChamber module fetchers' wire behavior into the providers that own the credentials; CLI emits the module's existing result shape ({providerId, ok, configured, usage.windows{usedPercent, resetAt, valueLabel}, fetchedAt, error, planLabel, credentialId}) so downstream mapping is unchanged.--json, per-provider args, non-zero exit when a requested provider has no successful result (mirror the openchamber CLI's contract). - Vibe-direct quota — handed to the vibe lane on opencode#1709 with acceptance above.
- Gatherer rewiring (pm-stack/aggregator
scripts/gather-quota.ts): populate quota-current.json EXCLUSIVELY fromopencode quota --jsonoutput. Retire: the in-process OpenChamber module import, the gatherer-side Turso read, the ClinePass native adapter fill, file-export producers. The OpenDesign renewal keeper stays (it maintains the credential the provider's quota function consumes). - Supabase: recorder input verified = subcommand-sourced quota-current.json; rows landing per pool on cadence, proven by fresh timestamps.
- Dashboard (LVIS aggregator + openchamber surfaces): refresh button that triggers a fresh subcommand-sourced gather (not a file re-read); display-name rule — strip
-directin markup IFF no non-direct provider with the same base name exists in the actual provider set (vibe-direct → "vibe"; a hypothetical openai-direct beside openai stays "openai-direct"). Rule over the real set, never a hardcoded list. - Corrections to fix(quota-plane): Meta percentage read — restore portal-API fetcher (subscription-quota) as the gatherer's source #6079 / quota aggregator: exempt declared key-validity (console-only) providers from the no-data error degradation #6087 / openchamber#1630: console/portal fetches move inside the providers' quota implementations; gatherer-side console-fetch designs are superseded.
Acceptance: one transcript — opencode quota covering every pool incl. Mistral's dollar figures; quota-current.json regenerated solely from it; fresh Supabase rows per pool; dashboard showing the same values, refresh button working, naming rule applied on the real provider set.
Activity
developing-today commentedon Nov 5, 2026
CODE-1766
Merge pull request #6141 from developing-today/feat/dashboard-quota-r…
dezren39 commentedon Nov 5, 2026
Groq — no first-party quota surface (evidence)
Implemented in the
opencode quotasubcommand (fork PR, providerpackages/ai/src/quota/providers/groq.ts). Finding: Groq exposes no first-party usage/quota endpoint an API key can read.Evidence gathered 2026-11-05:
api.groq.com/openai/v1is the OpenAI-compatible inference API (/models,/chat/completions, …). There is no/usage,/billing,/subscription, or organization-usage route; the docs' Rate Limits page (console.groq.com/docs/rate-limits) describes limits as enforced per model/organization and surfaced only:x-ratelimit-*response headers on inference calls (e.g.x-ratelimit-limit-requests,x-ratelimit-remaining-requests,x-ratelimit-limit-tokens,x-ratelimit-remaining-tokens,x-ratelimit-reset-requests,x-ratelimit-reset-tokens), andGET https://api.groq.com/openai/v1/models— succeeds (key valid) and returns nox-ratelimit-*headers on this route, so not even the header channel yields quota state without spending an inference call.Behavior in the subcommand: the groq provider probes
/modelsand maps anyx-ratelimit-*headers present into windows; when none are present it returnsconfigured: true, ok: falsewitherrorKind/failureClassno-quota-surfaceand an explanatory error — never fabricated numbers. If Groq ships a usage endpoint later, this is the provider to extend.dezren39 commentedon Nov 5, 2026
E2E proof — quota single-source is LIVE (2026-11-05 ~04:05 CST)
(i)
opencode quotacovers every pool. Fork PRs: #1722 (provider quota functions + subcommand, merged13ab3dc5) + #1728 (type cleanup, merged255fa099). Live default run: 14 results — antigravity, cline-pass, codex ×2, command-code, deepseek, mistral, opencode-go ×2, opendesign, openai.main, openai.alternate, turso ok; claude error (Anthropic 429, the provider's real state tonight); groq explicitno-quota-surface(evidence comment below). Mistral carries BOTH console budgets as dollar windows (trajectory across the night's heavy use: API $0.47→$0.84→$1.34→$1.81→~$2.04 of $15; Vibe $0.04→$0.68 of $150; reset 2026-12-01) — matching the admin-console truth in shape and magnitude at every read. Named asks fetch route-disabled pools (meta, command-code) by name, bypassing the disabled filter, per contract.(ii) quota-current.json is regenerated SOLELY from the subcommand. Gatherer rewire merged: #6136 + #6190 (neutral cwd) + #6191 (OPENCODE_CONFIG_PATH default). Retired: the in-process OpenChamber module import, gatherer-side Turso read, ClinePass adapter fill, file-export family. The production unit's final tick (journal in the proof bundle):
providers=14 ok=12, file source = opencode-quota, duration 46s. Deployment note: until the nix switch ships the new system opencode binary, the unit's drop-in points QUOTA_SUBCOMMAND at the fork-built binary (~/git/opencode-builds/quota-13ab3dc5/bin/opencode) — remove the drop-in at the switch. One honest root-cause note: the first three production ticks failed on an UNQUOTED multi-wordEnvironment=value in the hand-written drop-in (systemd split it, so the tick ran bareopencodewith no args); the fail-soft contract held every time (previous file stood, heartbeat carried the error). The cwd/config-path changes are retained hardening — both effects were independently reproduced under systemd.(iii) Supabase lands on the same tick. The gatherer's new sink writes one
agent.quota_recordsrow per pool entry per tick, sourceopencode-quota(verified in muse-dev2: fresh rows for every pool at each tick tonight). This replaces thepm-poolwriter, which I found DEAD since 2026-10-29 (12 rows ever) — the "recorder is running" assumption in earlier passes was wrong; it is running now, from the single source.(iv) Dashboard. #6141 merged: refresh button on the quota surface (POST /api/quota/refresh → manual gatherer trigger; polls until generatedAt advances; honest failure states; verified live, generatedAt advanced on click) and the
-directdisplay rule as a pure function over the rendered set (10 tests; live page rendersantigravity.geminietc., ids unchanged). Post-tick /api/quota.json shows the mistral pool healthy with both budget windows, freshness sourceopencode-quota.Remaining open items (tracked, not silent):
Proof bundle: ~/workspace/scratch/quota-single-source-2026-11-05/ (subcommand outputs, tick logs, final quota-current.json, dashboard verification).