Skip to content

Direct providers: full-function completeness + quota single-source (opencode quota subcommand) #6125

Closed

Description

@dezren39

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 quota subcommand (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/quota in the openchamber repo; per-provider JS fetchers), imported in-process by the laptop gatherer pm-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.ts triggers the gatherer when data is >5 min stale, but there is no user-facing refresh button and no -direct display-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)

  1. Fork: opencode quota subcommand (developing-today-forks/opencode) — provider quota interface in packages/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).
  2. Vibe-direct quota — handed to the vibe lane on opencode#1709 with acceptance above.
  3. Gatherer rewiring (pm-stack/aggregator scripts/gather-quota.ts): populate quota-current.json EXCLUSIVELY from opencode quota --json output. 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).
  4. Supabase: recorder input verified = subcommand-sourced quota-current.json; rows landing per pool on cadence, proven by fresh timestamps.
  5. Dashboard (LVIS aggregator + openchamber surfaces): refresh button that triggers a fresh subcommand-sourced gather (not a file re-read); display-name rule — strip -direct in 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.
  6. 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

  1. developing-today commented on Nov 5, 2026

    @developing-today
    Contributor
  2. added a commit that references this issue on Nov 5, 2026
  3. dezren39 commented on Nov 5, 2026

    @dezren39
    MemberAuthor

    Groq — no first-party quota surface (evidence)

    Implemented in the opencode quota subcommand (fork PR, provider packages/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:

    • Groq's public API surface under api.groq.com/openai/v1 is 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:
      • as 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), and
      • on the authenticated web console's Limits page (console session, not the API key).
    • A live probe with the stored Groq credential — GET https://api.groq.com/openai/v1/models — succeeds (key valid) and returns no x-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 /models and maps any x-ratelimit-* headers present into windows; when none are present it returns configured: true, ok: false with errorKind/failureClass no-quota-surface and an explanatory error — never fabricated numbers. If Groq ships a usage endpoint later, this is the provider to extend.

  4. dezren39 commented on Nov 5, 2026

    @dezren39
    MemberAuthor

    E2E proof — quota single-source is LIVE (2026-11-05 ~04:05 CST)

    (i) opencode quota covers every pool. Fork PRs: #1722 (provider quota functions + subcommand, merged 13ab3dc5) + #1728 (type cleanup, merged 255fa099). 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 explicit no-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-word Environment= value in the hand-written drop-in (systemd split it, so the tick ran bare opencode with 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_records row per pool entry per tick, source opencode-quota (verified in muse-dev2: fresh rows for every pool at each tick tonight). This replaces the pm-pool writer, 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 -direct display rule as a pure function over the rendered set (10 tests; live page renders antigravity.gemini etc., ids unchanged). Post-tick /api/quota.json shows the mistral pool healthy with both budget windows, freshness source opencode-quota.

    Remaining open items (tracked, not silent):

    • opencode#1729 — subcommand hardening: 30s per-provider cap too tight for mistral's CDP read under listing concurrency (the gatherer carries documented explicit-ask workarounds for mistral + deepseek meanwhile), the listing currently omits deepseek though the named ask resolves it, and default config discovery without OPENCODE_CONFIG_PATH is minutes-slow.
    • vibe-direct's OWN provider quota function: the requirement + acceptance sit on opencode#1709 with the vibe lane. Tonight the Vibe budget figures flow through the mistral provider's console read (same account/endpoint); the subcommand-verified figures above are the acceptance numbers the vibe lane must match.
    • Meta %: the provider implementation faithfully reports the portal read's no-data state (window exhausted server-side until 2026-11-09; key endpoint retired). No percentage exists to report right now; fix(quota-plane): Meta percentage read — restore portal-API fetcher (subscription-quota) as the gatherer's source #6079's portal design is implemented provider-side and will surface a % when Meta's surface yields one.
    • Out-of-scope service docs (named for the record): the aggregator also displays Supabase-platform and Cloudflare usage from their own export latest.json files — those are infrastructure-service usage documents, not provider quota, and were left as-is.

    Proof bundle: ~/workspace/scratch/quota-single-source-2026-11-05/ (subcommand outputs, tick logs, final quota-current.json, dashboard verification).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions