Skip to content

feat(usage): add DeepInfra quota provider - #3600

Merged
btriapitsyn merged 5 commits into
openchamber:mainfrom
Tobias-Conrad:feat/quota-deepinfra
Oct 3, 2026
Merged

btriapitsyn merged 5 commits into
openchamber:mainfrom
Tobias-Conrad:feat/quota-deepinfra

Conversation

@Tobias-Conrad

Copy link
Copy Markdown
Contributor

Intent

DeepInfra is present in the OpenCode auth.json (deepinfra entry with an API key) and its account exposes a spendable credit balance, but DeepInfra never appears in Settings → Usage, the work-status panel, or the VS Code runtime. The quota system is a hand-maintained provider allowlist, and DeepInfra was never registered there.

This PR adds deepinfra as a first-class quota provider: a DeepInfra row in Settings → Usage with a credits_balance card, opt-in work-status panel visibility through the existing per-provider checkbox, and VS Code extension parity.

The provider reads GET https://api.deepinfra.com/v1/me?checklist=true (bearer API key) and surfaces checklist.stripe_balance as the spendable credit. Per the documented semantics, stripe_balance is negative when funds are ready to spend and positive when money is owed, so the provider negates it before rendering the money label ($50.75 for a live stripe_balance of -50.75; -$5.50 when a balance is owed). This matches the existing balance-only provider pattern (DeepSeek, Charm Hyper).

Reference: https://docs.deepinfra.com/api-reference/account/me

Non-goals

  • No monthly usage window from GET /payment/usage; the balance-only card is the DeepSeek/Hyper pattern and keeps the fetch to one request.
  • No managed-credential form in Settings; DeepInfra uses the standard auth.json API key like DeepSeek and OpenRouter.
  • No local logo asset; the UI falls back to the remote models.dev logo (deepinfra.svg resolves today).
  • No new i18n keys; Credits Balance is an existing quota.window.creditsBalance label and DeepInfra is a product-name literal.
  • No model-level windows (usage.models): the DeepInfra account endpoint has no per-model quota data.

Affected surfaces

Surface Impact
packages/web server quota module New provider server/lib/quota/providers/deepinfra.js, one registry entry and named export in providers/index.js, new vitest suite
packages/ui shared types and registry One QuotaProviderId union member and one QUOTA_PROVIDERS entry. Settings → Usage, the work-status popover, the mobile popover, and tray sync all iterate this list dynamically, so one entry covers every surface
packages/vscode extension host Parity implementation in quotaProviders.ts (DeepinfraPayload type, fetchDeepinfraQuota, dispatcher case, listConfiguredQuotaProviders detection)
packages/web/server/lib/quota/DOCUMENTATION.md Provider table row and a balance-semantics section
External contract New deepinfra alias read from ~/.local/share/opencode/auth.json; no routes, persisted settings, or other provider behavior changed

Electron, hosted mobile, and Capacitor consume the web server runtime and the shared UI list, so they need no separate change. /api/quota/:providerId already dispatches by id, so the new provider flows through the existing dispatcher.

Repository guidance

Guidance Why it applies How the change complies
AGENTS.md runtime boundaries / cross-runtime parity Quota fetching lives in both the web server runtime and the VS Code extension host Both runtimes implement the same logic, error strings, and window shape
AGENTS.md correctness invariants (authoritative state, explicit failure) A fetch failure must not look like empty success ok / configured / error preserved via buildResult; 401/403 maps to session-expired; timeout and parse failures are classified explicitly
openchamber-change-discipline skill New module, extended type union, registry entry: a cross-workspace contract change Traced every QUOTA_PROVIDERS consumer (all dynamic iteration, no per-provider switch); focused tests plus per-package type-check and lint; bun run dead-code reports the same pre-existing fetch*Quota pattern, no new file/type finding
quota/DOCUMENTATION.md "Add a new provider" checklist Canonical checklist for exactly this task Followed step by step: new module with providerId/providerName/aliases/isConfigured/fetchQuota, shared utils helpers, registry entry, docs row, tests
Code precedent Style must match DeepSeek / Charm Hyper Line-by-line comparison against deepseek.js and the VS Code fetchDeepseekQuota; window naming reuses the localized credits_balance label

sync-state-invariants was not loaded: no session sync, reducers, or polling were touched.

Validation

Check Result
bunx vitest run lib/quota from packages/web 19 files, 190 passed (includes the 9 new DeepInfra cases)
bun run --cwd packages/web test 212 files, 2372 passed, 1 skipped
bun run --cwd packages/ui test 481/481 files passed
bun run --cwd packages/vscode test 41/41 files passed
bun run --cwd packages/ui type-check, --cwd packages/web type-check, --cwd packages/vscode type-check All clean (vscode runs both tsconfigs)
bun run --cwd packages/ui lint, --cwd packages/vscode lint Clean
bun run dead-code fetchDeepinfraQuota listed under unused exports, the same pre-existing pattern as every other named quota fetcher (Claude, OpenAI, DeepSeek, …). No file-level or type-level finding for the new module
bunx oxlint on changed paths One no-module-mocking finding in the new web test, identical to all sibling provider tests (deepseek.test.js, codex.test.js, …); the new deepinfra.js is clean
Live end-to-end Started the branch's web server against the real auth.json; GET /api/quota/deepinfra returned credits_balance $50.75, and Settings → Usage rendered the DeepInfra row with a Credits Balance $50.75 card

Not verified: full bun run build (only packages/web build was used to render the screenshots); root bun run test chain (per-package suites used instead); packaged Electron/mobile runtime (static analysis only — both consume the web runtime and shared UI list, so risk is low).

Visual evidence

Settings → Usage. The "before" is the installed 1.23.1 UI (no DeepInfra); the "after" is this branch built on current main (DeepInfra selected, live balance). Current main already removed the unrelated CrofAI row, which is not part of this PR.

Before After

Light theme only; no colors or styling changed.

Risks and failure behavior

  • Invalid or expired key: 401/403 maps to Session expired — please re-authenticate with DeepInfra; the provider stays configured: true so the UI shows the error instead of clearing state.
  • Network, timeout, or parse failure: classified as Request timed out, Invalid response from provider, or the request message with ok: false; the store keeps the failure signal, so a failure never looks like authoritative empty data.
  • Zero and owed balances: literal 0 renders as $0.00 (valid); a positive stripe_balance (money owed) renders as -$X.XX; a missing or non-numeric balance yields No quota data in response (ok: false, configured: true). All three are tested.
  • Security: the API key is read from the existing auth.json store and is never logged or persisted by this change; no secrets appear in tests or docs.
  • Compatibility: additive only (new provider ID deepinfra); no existing routes, settings, or provider behavior changed. Rollback is a single-commit revert.
  • Cross-runtime: web and VS Code logic is kept identical and documented as a sync pair in quota/DOCUMENTATION.md.

@Tobias-Conrad
Tobias-Conrad requested a review from a team September 16, 2026 00:51
@openchamber-bot openchamber-bot Bot added the review:pending Automated contribution review is in progress. label Sep 16, 2026
@github-actions github-actions Bot added review:automation-failed Automated review failed to complete and must be retried. and removed review:pending Automated contribution review is in progress. review:automation-failed Automated review failed to complete and must be retried. labels Sep 16, 2026
@openchamber-bot openchamber-bot Bot added the review:pending Automated contribution review is in progress. label Sep 18, 2026
@openchamber-bot

Copy link
Copy Markdown
Contributor

Code Review Summary

For the maintainer: Merge — a clean, pattern-following provider addition; only two small non-blocking polish items remain.

The PR registers DeepInfra as a quota provider in all three layers: a web-server module (providers/deepinfra.js, a faithful mirror of the DeepSeek balance-only pattern, with the documented stripe_balance negation), one registry/union entry in shared UI that every surface (Settings → Usage, work-status popovers, tray sync, quota store) already iterates dynamically, and a mirrored VS Code implementation with identical aliases, error strings, and window shape. The problem is real — the quota system is a hand-maintained allowlist and DeepInfra was simply missing. Docs, provider table, tests, and before/after screenshots all match the code at this HEAD. First review pass; no prior findings exist.

Verdict: PASS
Handoff: complete

Reviewed HEAD: 33fbbd5237bfe3e704883841ebcb622e0e491675
Previous reviewed HEAD: none

Findings

  1. non-blocker: blank/whitespace stripe_balance string renders as a valid $0.00
    File: packages/web/server/lib/quota/providers/deepinfra.js:65 (and the mirror at packages/vscode/src/quotaProviders.ts:2840)
    Problem: toNumber('') and toNumber(' ') return 0 (finite), so an empty numeric-string balance is presented as authoritative "$0.00 Credits Balance" instead of the intended No quota data in response failure. Both named precedents guard this: deepseek.js:67-69 requires a non-empty string, and the module docs' balance convention (Hyper section) says "missing, blank, or malformed balances remain explicit failures; zero is valid".
    Suggested fix: reuse the deepseek-style non-empty-string guard before toNumber in both runtimes.

  2. non-blocker: no VS Code tests for the new DeepInfra code
    File: packages/vscode/src/quotaProviders.ts:2797
    Problem: the PR adds 9 focused web tests but none in the VS Code suite, while sibling providers (e.g. fetchDeepseekQuota) have targeted VS Code cases; cross-workspace validation guidance expects each runtime's copy exercised. The VS Code logic is a verbatim mirror of tested web code, so this is a coverage gap, not a defect.
    Suggested fix: port 2-3 cases into quotaProviders.test.ts (negative/zero/owed balance, 401 mapping, alias detection).

Nits: fetchDeepinfraQuota named export in providers/index.js has no consumer (same pre-existing dead-code pattern as every sibling) — fine to keep for consistency.

Evidence and Residual Risk

  • Residual risk: live end-to-end validation was web-only; packaged Electron/Capacitor were validated by tracing only (both consume the web runtime and the shared UI list — verified registry-driven at this HEAD). The VS Code DeepInfra path is exercised by no test of its own (finding 2) and rests on mirror-equivalence with the tested web logic.
  • Review evidence: web tests cover the risky math (negative/zero/owed/string balance, 401/403, parse/timeout, missing-data), and the screenshots correspond to the reviewed HEAD.

@github-actions github-actions Bot added review:ready Ready for maintainer review. and removed review:pending Automated contribution review is in progress. labels Sep 18, 2026
@openchamber-bot openchamber-bot Bot added the size:L 100-499 changed lines (tests, lockfiles and translations excluded). label Sep 22, 2026
@openchamber-bot openchamber-bot Bot added review:pending Automated contribution review is in progress. and removed review:ready Ready for maintainer review. review:pending Automated contribution review is in progress. labels Oct 2, 2026
@openchamber-bot

Copy link
Copy Markdown
Contributor

Code Review Summary

For the maintainer: Don't merge yet. Since the last review the branch was rebased onto current main, and main has since removed the readAuthFile export the web provider imports. That one broken import fails to load the whole quota module, so every provider's quota routes go down in web and desktop, not just DeepInfra.

The prior review passed this PR at 33fbbd5. Two things happened since: the branch was rebased onto current main (Oct 2), and a fix commit repaired the same defect in the VS Code copy, where the type-checker caught it (TS2304) and the provider now correctly injects readOpenCodeCredentials. The web copy is plain server JS, which no local type-check covers, so it silently kept the removed import. The quota module loads through one lazy import('../quota/index.js'), so the ESM link error rejects for every route that touches quota. The 9 new web tests stay green only because the test file replaces the auth module with a mock that still defines the old name.

Verdict: BLOCKED
Handoff: complete

Reviewed HEAD: 1d2602928ff59d8a0acd50b12631c5ce11a4d4ae
Previous reviewed HEAD: 33fbbd5237bfe3e704883841ebcb622e0e491675

Findings

  1. blocker: web provider imports readAuthFile, which main no longer exports
    File: packages/web/server/lib/quota/providers/deepinfra.js:1
    Problem: packages/web/server/lib/opencode/auth.js exports only readOpenCodeCredentials() (async; the old sync reader was removed by #4281, main's current HEAD). Importing a missing named export is a module link error, so providers/index.js and quota/index.js fail to load wherever the server lazily imports them (feature-routes-runtime.js:70). Every quota route fails: the providers list, per-provider fetches, and the Electron runtime that embeds this server. The provider itself could never read a key. Commit 1d260292 fixed exactly this on the VS Code side; the web side needs the same treatment.
    Suggested fix: mirror deepseek.js: import and await readOpenCodeCredentials(), and take the handed auth in isConfigured(auth) instead of re-reading. Then repeat the live check and refresh the body's live-run statement and "after" screenshot at the new HEAD; both describe a run that cannot pass through this import chain as it stands.

Prior findings, both still open (see the earlier review comment): blank/whitespace stripe_balance still renders as a valid $0.00 in both runtimes, and the VS Code copy still has no tests of its own. No prior findings were closed by the new commits.

Evidence and Residual Risk

  • Review evidence: the new test suite cannot catch this class of breakage (deepinfra.test.js mocks the whole auth module with the removed name), and no local check covers server JS imports. The body's live run and after-screenshot predate the rebase and are contradicted by the code at this HEAD.
  • Residual risk: until the import is fixed and the live run repeated, nothing shows DeepInfra quota working against the real API on current main.

@github-actions github-actions Bot added the review:blocked Correctness or contribution-contract blockers must be resolved. label Oct 2, 2026
Tobias-Conrad and others added 4 commits October 3, 2026 12:17
…vider

fetchDeepinfraQuota called readAuthFile(), which does not exist in this
module, so the VS Code type-check failed with TS2304 and the provider could
never have read a key at runtime. Match the neighbouring Hyper provider:
inject readAuth with readOpenCodeCredentials as the default and await it,
since the credential reader may be asynchronous.
@btriapitsyn
btriapitsyn force-pushed the feat/quota-deepinfra branch from 1d26029 to 5b126b7 Compare October 3, 2026 09:20
@openchamber-bot openchamber-bot Bot added review:pending Automated contribution review is in progress. and removed review:blocked Correctness or contribution-contract blockers must be resolved. labels Oct 3, 2026
@btriapitsyn

Copy link
Copy Markdown
Collaborator

Thanks! After the rebase the web provider still imported the old auth reader that main had removed, so I switched it to readOpenCodeCredentials like DeepSeek, added the blank-balance guard, and ported a few tests to the VS Code side.

@btriapitsyn
btriapitsyn merged commit 4a14883 into openchamber:main Oct 3, 2026
2 checks passed
@openchamber-bot openchamber-bot Bot removed the review:pending Automated contribution review is in progress. label Oct 3, 2026
@openchamber-bot

Copy link
Copy Markdown
Contributor

Code Review Summary

For the maintainer: Merge after one refresh — all code findings from the last review are fixed at this HEAD, but the live-run statement and "after" screenshot still describe the pre-fix branch.

Delta since the last review (1d26029): the branch was rebased onto current main, then two commits fixed the stale readAuthFile import (the web provider now reads credentials exactly like its DeepSeek model — awaited, injectable readOpenCodeCredentials, isConfigured(auth) takes the handed auth), added the blank-balance guard to both runtimes, added a four-test VS Code parity suite, and justified the VS Code payload cast. I verified all three prior findings are closed against the base checkout: the auth export exists and is async; toNumber returns null for malformed values in both runtimes, so blank, non-numeric, and object balances all end as an explicit failure while literal zero stays $0.00; and the new VS Code tests exercise detection → dispatch → fetch through the real dispatcher.

Verdict: NEEDS_EVIDENCE
Handoff: incomplete — Validation table, live-run line, and screenshots predate the final two fix commits and were not refreshed (the body says "9 new DeepInfra cases"; the suite now has 10).

Reviewed HEAD: 28978841890af244ce353a6fca5e3e7aef676fb8
Previous reviewed HEAD: 1d2602928ff59d8a0acd50b12631c5ce11a4d4ae

Findings

  1. evidence-gap: live-run statement and after-screenshot are stale
    File: PR body (Validation / Visual evidence)
    Problem: the evidence images are byte-identical to the pre-rebase HEAD the last review rejected, and no comment claims a re-run. They demonstrate the old readAuthFile credential path, which no longer exists — the current code goes through awaited readOpenCodeCredentials(). Main's Usage page rendering also changed between the two rebases, so the surface in the screenshot has moved too. The one thing this PR exists to prove — DeepInfra quota working end to end — is currently demonstrated only against a code path the PR no longer contains.
    Suggested fix: start the branch's web server at 2897884, refresh the after-screenshot, update the live-run line and validation numbers, and note anything that no longer holds.

All three findings from the previous review (stale auth import, blank-balance $0.00, missing VS Code tests) are closed at this HEAD.

Evidence and Residual Risk

  • Review evidence: the test suites match the implementation risk — 10 web cases cover balance math (negative/zero/owed/numeric-string/blank), 401/403, parse, timeout, and missing data, and the VS Code suite covers its mirror through the dispatcher. The claimed validation results predate the final commits and were not repeated, which is the staleness above, not a test-design gap.
  • Residual risk: no demonstrated run of the new credential path since the fix; until the refresh, the end-to-end claim rests on mirror-equivalence with the tested DeepSeek pattern.

@github-actions github-actions Bot added the review:needs-evidence Additional validation or visual evidence is required. label Oct 3, 2026
MltStephane pushed a commit to MltStephane/openchamber that referenced this pull request Oct 6, 2026
fabiob pushed a commit to fabiob/openchamber that referenced this pull request Oct 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review:needs-evidence Additional validation or visual evidence is required. size:L 100-499 changed lines (tests, lockfiles and translations excluded).

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants