Skip to content

Port upstream 0.66.0: Vercel AI Gateway credit balance - #678

Draft
Finesssee wants to merge 1 commit into
port/upstream-0.66.0from
port/micro-0.66.0-vercel-ai-gateway
Draft

Finesssee wants to merge 1 commit into
port/upstream-0.66.0from
port/micro-0.66.0-vercel-ai-gateway

Conversation

@Finesssee

Copy link
Copy Markdown
Collaborator

Summary

Adds the Vercel AI Gateway provider (vercel, display name "Vercel AI Gateway"). With an API key it reads the team's credit balance and lifetime spend from GET https://ai-gateway.vercel.sh/v1/credits and shows them as informational rows. No quota, percentage, or billing period is invented; the primary window is informational.

  • Key: AI_GATEWAY_API_KEY (env, keyring target codexbar-vercel, or the Preferences API-key field). Off by default.
  • Request: bearer auth, 15 s timeout, no redirects, 1 MiB body cap, fixed HTTPS origin.
  • Status mapping: 401 auth required; 403 "returned HTTP 403"; 429 rate limited; >= 500 unavailable; anything else non-200 an API failure. Messages carry the status only, never the body.
  • Body {"balance":"95.50","total_used":"4.50"}: both fields must be strings matching ^-?\d+(?:\.\d+)?$ and finite; total_used < 0 is a parse failure. Numeric balance, missing fields, {}, null, non-JSON, NaN, 1e999, 0x10, empty and whitespace-only values fail with a fixed message that does not echo response data.
  • Output: rows "Available balance" ($95.50, -$1.25, $0.00) and "Lifetime spend" ($4.50), login method API, and a typed CostSnapshot (used = lifetime spend, period "Team credits", not always_visible so it never feeds the 30-day spend views) carrying the balance.
  • Clamp: CostSnapshot::with_balance clamps to >= 0, so a negative balance is not typed (it would read as a fabricated $0.00); it survives only through the signed display row. A zero balance is a typed 0.
  • Registration: ProviderId::Vercel (cli name vercel; aliases vercel-ai-gateway, ai-gateway), factory arm, token-accounts (unsupported), API-key catalog, frontend icon (upstream triangle, currentColor), tray dashboard-link set, provider source label, test catalog, README row.
  • Brand color: upstream is white (0xFFFFFF), which is invisible on light surfaces; uses neutral #737373.

Upstream reference

Ported / Deferred

Ported: everything in the spec above.

Deferred / adjusted:

  • "Team credits" section heading: ProviderDisplayDetail rows have no section concept here, so "Team credits" is the informational primary text and the cost card title instead of a grouped section.
  • Shared parse helper from the Atlas Cloud PR (Port Atlas Cloud balance provider from v0.66.0 #618) is not merged into this base; the small decimal check lives in this module. Fold into a shared helper when both land.
  • Dashboard web-server icon table (cli/serve/dashboard/icons.rs): not extended; the dashboard falls back to its neutral dot, as for other recently added providers.
  • Token-account (multi-key) support: not in the spec, so the provider is in the unsupported list.

Validation

All on Rust 1.98.0, E-core wrappers, slot-4 target dir.

  • cargo +1.98.0 fmt --all: clean
  • cargo +1.98.0 clippy --workspace --all-targets -- -D warnings: pass
  • cargo +1.98.0 test -p codexbar vercel -- --test-threads=4: 17 passed
  • cargo +1.98.0 test -p codexbar -- --test-threads=4 (full, shared core/settings touched): 2177 passed, 0 failed, 1 ignored
  • pnpm --dir apps/desktop-tauri exec vitest run src/components/providers src/surfaces/settings src/surfaces/TrayPanel.test.tsx: 26 files, 161 tests passed
  • pnpm --dir apps/desktop-tauri run build: pass; run lint: only pre-existing warnings in untouched files

Tests cover the upstream fixture, 0.00 / -1.250000, the invalid list ("", " ", NaN, 1e999, 0x10, private-response), overflow digits, wrong shapes (numeric balance, missing fields, {}, null, arrays), negative total_used, unknown fields, signed USD formatting, 401/403/429/503/400 mapping without the body, redirects not followed, trimmed bearer header over a local one-shot server, and the clamp behavior.

Known limit: the wire shape comes from upstream test fixtures only; no live Vercel account was used.

Affected areas

  • Provider (new)
  • Settings (API-key catalog)
  • Frontend (icon, catalog, tray dashboard link, source label)
  • Tray icon renderer / float bar / CLI-only changes: none

UI proof

Pending: coordinator will capture CUA proof on a fresh build (tray card, settings API-key field).

@coderabbitai

coderabbitai Bot commented Sep 29, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Finesssee

Copy link
Copy Markdown
Collaborator Author

Thermo-nuclear review

Reviewed by Codex gpt-6-luna (xhigh); verified and validated by Claude

Codex reported two findings. After verification, neither was applied:

  • Medium, rust/src/providers/vercel/mod.rs:39: Codex proposed rebuilding the HTTP client with the same timeout and no-redirect settings inside the unwrap_or_else fallback. Rejected: the second build uses the identical configuration as the first, so it fails the same way, and the .expect would turn a recoverable fallback into a panic. The existing Client::new() fallback matches the pattern used by other providers.
  • Medium, apps/desktop-tauri/src/components/MenuCardDetails.tsx:598: Codex found two render branches showing provider display details twice in the non-compact card. Valid, but the duplicate already exists on main and is not introduced by this PR, so it is out of scope for this port. It affects all providers with display details and should be fixed in a separate PR.

No code changes were made to this PR. No commit or push.

@Finesssee

Copy link
Copy Markdown
Collaborator Author

CUA proof (Windows desktop, real WebView2 build)

Build commit: 47f55c7a726364897075f7f4ba59c2feff2014fe (equals PR head at capture time). Debug build via pnpm run tauri:build:debug, not rebuilt for this run.

Proof-only patches (uncommitted, reverted in the worktree; exact text in proof-only.diff):

  • Cargo.toml [patch.crates-io] dirs shim so CODEXBAR_PROOF_HOME isolates the profile (Cargo.lock changed by the patch, also restored).
  • rust/src/providers/vercel/mod.rs VercelProvider::new(): CODEXBAR_PROOF_VERCEL_URL overrides the credits URL so the real fetch path runs against a local mock (127.0.0.1:18678, dummy key proof-dummy-vercel-key). No seed JSON used.

Commands: bash launch.sh trayPanel, bash launch.sh settings:providers, MOCK_BALANCE=-1.25 bash launch.sh trayPanel, then the mock was stopped and Refresh clicked. Driven with cua-driver call get_window_state / click / scroll (session label, background delivery; one foreground scroll for the settings panel). Profile isolated under the kit home\; no real accounts appeared in any capture.

# Assertion Result
0 No real email/account/plan visible PASS
1 Theme stays dark under auto PASS
2a Single provider "Vercel AI Gateway", Vercel icon (triangle, not letter tile), no error banner PASS
2b "Team credits" info section, no invented percent/pace; rows "Available balance $95.50", "Lifetime spend $4.50" PASS
2c Separate cost/spend line for Team credits totalling $4.50 NOT OBSERVED. The tray detail shows no cost group; the balance/spend rows are rendered twice (the displayDetails block appears twice in MenuCardDetails.tsx, present on main via de308f2, not touched by this PR). Cost data is present (Settings > Providers shows Used $4.50), so the gap is in tray rendering of the cost group. Needs a decision: fix, or accept.
3 Header hint "API"; "Usage dashboard" action present; no status-page link PASS
4 Settings > Providers: Vercel AI Gateway enabled, source hint shown as lowercase api (list row and Data Source), API key Set and masked (proo ... -key), help text matches PASS (note lowercase api, plan text said "API")
5 MOCK_BALANCE=-1.25: Available balance -$1.25, Lifetime spend $4.50, no crash, no banner PASS
6 Mock stopped, Refresh: friendly error, no body or key text PASS. Shows "Network error: error sending request for url (http://127.0.0.1:18678/v1/credits)" (proof mock URL; no key, no body)
7 Profile settings still list only vercel; real %APPDATA%\CodexBar untouched PASS

Screenshots (local, not committed), C:\Users\FSOS\AppData\Local\Win-CodexBar\port-audit\proof\678\shots\:

  • 01-tray.png (merged view)
  • 02-tray-vercel-detail.png
  • 03-settings-general.png (providers tab, top)
  • 04-settings-providers-apikey.png
  • 05-tray-negative-balance.png
  • 06-tray-error-mock-stopped.png, 07-tray-error-after-wait.png

@Finesssee

Copy link
Copy Markdown
Collaborator Author

Reviewed by Claude (triage of the CUA note on 47f55c7; Codex not used per operator request to use fewer agents). No code change: none of the three items is a defect of this PR.

  1. No separate cost/spend line for Team credits totalling $4.50: by design, not a regression. The provider sets a typed balance with no limit; the shared cost group (MenuCardDetails.tsx) renders that shape as the balance only ("Team credits" / balance), and the spec puts spend in the "Lifetime spend $4.50" display row under the "Team credits" section, which the proof confirmed (assertion 2b). Settings > Providers "Used $4.50" reads the same cost snapshot. Adding a spend line would duplicate the row. Left as is.
  2. Balance and spend rows rendered twice: pre-existing on main. MenuCardDetails.tsx renders the displayDetails block twice (one copy guarded by !compactOverview, one unguarded) since de308f2, and this PR does not touch that file (git diff origin/main...HEAD lists no MenuCardDetails). It affects every provider with display details, so it belongs in its own small fix, out of scope here.
  3. Data Source shows lowercase "api": pre-existing convention. The source label is the free-form string passed to ProviderFetchResult::new(usage, "api"); 21 providers already use lowercase api, and the spec's "API" is the login_method, which does show as "API" in the header (assertion 3). Left as is.

Commands run: read-only inspection of rust/src/providers/vercel/mod.rs, MenuCardDetails.tsx, CostSnapshot, the 0.66.0 spec entry; no build needed since no source changed.

@Finesssee

Copy link
Copy Markdown
Collaborator Author

Port re-review: Vercel AI Gateway credit balance (#678, GAP row 2 of 0.66.0)

Re-checked the branch at the current head 47f55c7a against the 0.66.0 manifest spec for item #2 (upstream vercel.js fetchUsage/money + VercelProviderDescriptor.swift, steipete#2975/steipete#3917, commit 5cfe3f1) and the thermo/proof thread.

What I verified at the current head:

  • Wire contract: GET https://ai-gateway.vercel.sh/v1/credits, bearer key (AI_GATEWAY_API_KEY, env/keyring/Preferences), fixed HTTPS origin, 15 s timeout, no redirects (the key is never replayed to a redirect target), 1 MiB body cap. Matches upstream.
  • Strict money parsing: both balance and total_used must be strings matching ^-?\d+(?:\.\d+)?$ that convert to a finite number (parse_amount splits on ., requires all-ASCII-digit parts, rejects 1e999, 0x10, whitespace, missing fields, {}, null, non-JSON, numeric types); total_used < 0 is a parse failure. The fixed failure message never echoes response data.
  • Status mapping: 401 auth required; 403 "returned HTTP 403"; 429 rate limited; >=500 unavailable; anything else non-200 an API failure — messages carry the status only.
  • Balance semantics: rows "Available balance" ($95.50/-$1.25/$0.00 via format_usd, negative-that-rounds-to-zero carries no sign) and "Lifetime spend" ($4.50), login method API; the typed CostSnapshot carries the balance only when >= 0 (with_balance clamps, so a negative balance survives only through the signed display row — matches the PR body's fail-safe reasoning); period "Team credits", not always_visible, so it never feeds the 30-day spend views.
  • Registration: ProviderId::Vercel with aliases vercel-ai-gateway/ai-gateway, factory arm, token-accounts unsupported, API-key catalog entry, frontend icon (upstream triangle, currentColor), tray dashboard-link set, source label, test catalog, README row. Brand #737373 instead of upstream's invisible-on-light white — documented.
  • Existing review coverage at this head: the thermo round's two Codex findings were verified and rejected with reasons recorded (identical-config client fallback; pre-existing MenuCardDetails duplication on main, out of scope — tracked separately as GAP-14-adjacent). The CUA proof at 47f55c7a exercised the real fetch path against a local mock: balance/spend rows, negative-balance rendering (-$1.25), friendly error with no key/body echo after stopping the mock, dark theme, isolated profile.

Result: no new findings. The port matches upstream semantics with documented deviations ("Team credits" as info text/cost title instead of a grouped section; no dashboard icon-table entry). Files under the cap; no new dependency.

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.

1 participant