Skip to content

Port upstream 0.64.0: LiteLLM identity, team/user budgets, private-network HTTP - #702

Draft
Finesssee wants to merge 4 commits into
port/micro-0.68.0-mistral-plan-detailfrom
port/micro-0.64.0-litellm-identity-budgets
Draft

Finesssee wants to merge 4 commits into
port/micro-0.68.0-mistral-plan-detailfrom
port/micro-0.64.0-litellm-identity-budgets

Conversation

@Finesssee

@Finesssee Finesssee commented Sep 30, 2026 •

Copy link
Copy Markdown
Collaborator

Stacked on #657 (detail-line usage windows), so merge #657 first. Found by the 0.60.4-0.69.0 port gap audit (gap G6, 0.64.0).

Summary

LiteLLM now reads the key-bound identity and budgets the way upstream 0.64.0 does, instead of reading /key/info only.

  • Routes. GET /key/info names the key's user_id and team_id. A user-bound key then calls GET /user/info?user_id=…, and a team-only key calls GET /team/info?team_id=…. A response whose user or team ID differs from the key's is rejected. A key with neither ID fails with "Parse error: LiteLLM key info did not include a user_id or team_id."
  • Lanes. The personal budget is the primary lane ("Personal budget"). The team whose team_id matches the key is the secondary lane ("Team budget"). Other teams in the /user/info response are ignored. When only the team budget exists, it takes the primary lane as "Team budget". A key without any budget shows an informational "No budget set" lane.
  • Amounts are detail lines. "$212.35 / $300.00" and "Team ai: $215.32 / $1,000.00" go through Port upstream 0.68.0: show Mistral plan amounts as detail lines (stacked on #647) #657's with_description_as_detail, so they never read as reset text, and a real reset date stays visible beside them.
  • Automatic metric. The tray icon, tray menu and float bar show the team budget unless a budget is exhausted, checking the primary lane first. This matches upstream's LiteLLM menu bar resolver. An explicit choice in Settings still wins. A new Provider::automatic_metric_prefers_secondary_window() hook (default false, true for LiteLLM) is read by the shell's usage_metric.rs, so shared code has no LiteLLM branch.
  • Identity. The account is user_email, then user_alias, then metadata.preferred_username. The organization is the matching team's alias. The key's expires becomes the subscription expiry.
  • Cost. "Personal budget" or "Team budget" carries the spend, limit and reset. Without a budget it is "Personal spend" or "Team spend", which stays visible like other API spend. No pace is derived from budget resets, because a budget window has no length.
  • Base URL policy (upstream https-or-private-network-http). HTTPS works anywhere. Plain HTTP works only for localhost, .local names, and loopback, RFC 1918, link-local or IPv6 unique-local literals. Embedded credentials and encoded delimiters (%2f %5c %3f %23 %40 %3a) are rejected, because the key is sent as a bearer token. The same validator checks the base URL saved in Settings, and the en-US help text states the rule.
  • Paths. A trailing /v1 is dropped and other base path segments are kept, so https://host/litellm/v1 becomes https://host/litellm/key/info. The old Url::join dropped the last segment.
  • Docs. A new "LiteLLM budgets" section in docs/PROVIDERS.md.

Upstream reference

  • v0.64.0 changelog: "Improved: LiteLLM via plugin, preserving private-network proxies, key-bound identities, team budgets, spend-only accounts".
  • Tag-pinned files, read with GET requests at v0.64.0: Sources/CodexBarCore/Resources/Plugins/litellm.ts, docs/litellm.md and Tests/CodexBarTests/LiteLLMUsageFetcherTests.swift.

Ported

Everything in the summary. Also ported: upstream's ID cross-checks, the "missing team_id" and wrong-type failures, the required info envelope on /key/info, the tolerant date parsing (a date-only value is UTC midnight), and the USD formatting with thousands separators.

Deferred or different

  • Spend-only fallback when a management route returns 401, 403 or 404: tracked by Port upstream 0.65.0: LiteLLM spend-report fallback #630 (0.65.0). This PR keeps the existing mapping of 401 and 403 to "authentication required".
  • Model activity: tracked by Port upstream 0.67.0: opt-in LiteLLM model activity and Claude workspace spend #664.
  • Error body snippet: upstream appends the first 500 characters of a failed response. This port shows the route and status only.
  • Parse error wording: upstream reads "LiteLLM parse error: X". ProviderError::Parse already displays as "Parse error: …", so the port reads "Parse error: LiteLLM X" instead of doubling the prefix.
  • Timestamps without an offset are read as UTC, because LiteLLM stores budget times in UTC. Upstream's JavaScript Date reads them in local time.
  • Query strings on the base URL are kept for /key/info and replaced for /user/info and /team/info.
  • Shared validator: the audit pointed at the private-network validator from Port upstream 0.66.0: llmman provider #650, which is not in this base. The policy lives in providers/litellm/endpoint.rs so it stays provider-local. The two can be unified once Port upstream 0.66.0: llmman provider #650 lands.
  • Settings lane names: the Settings Usage section and metric picker still say "Session" and "Weekly" for every provider. This gap is on main and also affects Kimi and StepFun. The tray, the float bar and the Options toggles use "Personal budget" and "Team budget".

Validation

At f376f5ae, all with cargo +1.98.0 through the E-core wrapper:

  • cargo fmt --all --check: clean
  • cargo clippy --workspace --all-targets -- -D warnings: pass
  • cargo test -p codexbar litellm: 26 passed
  • cargo test -p codexbar: 2191 passed, 0 failed, 1 ignored
  • cargo test -p codexbar-desktop-tauri: 466 passed, 0 failed, including the three new litellm_automatic_* tests. bootstrap_payload_exposes_every_provider_variant was skipped because it reads host settings on this base; Make the bootstrap catalog test hermetic (#684) #711 fixes it.
  • Frontend: no frontend files changed. The proof build ran pnpm install --frozen-lockfile, check-locale (879 keys), tsc and vite build.

Affected areas

  • Provider: LiteLLM (rust/src/providers/litellm/: mod.rs, the new endpoint.rs and info.rs, and tests.rs)
  • Core: Provider::automatic_metric_prefers_secondary_window (rust/src/core/provider.rs)
  • Shell: Automatic metric selection (apps/desktop-tauri/src-tauri/src/usage_metric.rs), which drives the tray icon, the tray menu and the float bar
  • Settings: LiteLLM base URL validation (rust/src/settings/provider_workspace.rs) and the en-US LiteLlmBaseUrlHelp text
  • Docs: docs/PROVIDERS.md
  • UI-affecting: the tray card, the float bar and the Settings provider detail

Merge notes

UI proof

PASS at f376f5ae (browser-use over WebView2 CDP, no keyboard, mouse or focus): #702 (comment)

  • The personal and team budgets sit in separate lanes with their amounts as detail lines. Automatic shows the team budget (22%) and switches to an exhausted personal budget (100%).
  • A team-only key, a spend-only key, and the single-prefix ID mismatch error.
  • Metric picker round trip: the explicit first lane shows 71%, and Automatic shows 22% again.
  • Not covered: tray icon pixels and the native tray menu. Unit tests cover the selected and companion icon windows.

@coderabbitai

coderabbitai Bot commented Sep 30, 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
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


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.

Personal and team budgets fill the primary and secondary lanes, amounts are detail lines, Automatic prefers the team budget unless one is exhausted, and the private-network HTTP check matches upstream isPrivateNetworkHost.
@Finesssee
Finesssee changed the base branch from port/upstream-0.64.0 to port/micro-0.68.0-mistral-plan-detail October 1, 2026 08:27
@Finesssee

Copy link
Copy Markdown
Collaborator Author

UI proof (browser-use)

Result: PASS on build f376f5aef1205e536f50d0bd37f5c488874f11c9, the current PR head "Show LiteLLM parse errors without a doubled prefix". A mock LiteLLM proxy served upstream's test payloads. The personal budget fills the first lane and the key's team budget fills the second, and each amount is a detail line. The Automatic tray and float bar metric shows the team budget unless a budget is exhausted. A team-only key shows "Team budget", and a key without a budget shows "No budget set" with its spend. A user ID mismatch fails with a single "Parse error: LiteLLM …" prefix.

At the maintainer's direction, this proof drove the app's WebView2 over CDP with the browser-use CLI instead of CUA. It used no keyboard, mouse or focus. All proof windows stayed on the second display, the foreground window was never part of the proof process tree, and the kit settings turned the global shortcut off.

Setup

  • Build: pnpm run tauri:build:debug at f376f5ae in the worker worktree. The frozen install, check-locale (879 keys), tsc and vite build passed. The exe (SHA-256 471ad1fd…fd938a) was copied to the proof kit.
  • Proof-only patch: never committed, and reverted after the build.
    • A workspace Cargo.toml [patch.crates-io] dirs shim for an isolated home, plus the resulting Cargo.lock change.
    • The no-activation overlay for proof windows (focus: false, no set_focus, SWP_NOACTIVATE), because this branch's base does not have Stop CodexBar from stealing focus #713.
  • Data: isolated USERPROFILE, HOME, APPDATA and LOCALAPPDATA under the kit. Only LiteLLM is enabled, the theme is auto, the float bar is on, and the global shortcut is off. The base URL is http://127.0.0.1:18702, which the new policy accepts as loopback HTTP. The stored key is a synthetic proof-dummy-… value.
  • Mock proxy: mock_server.py on 127.0.0.1:18702 serves /key/info, /user/info and /team/info, checks the bearer key, and logs every request. The payloads are the upstream v0.64.0 LiteLLMUsageFetcherTests.swift shapes, with the e-mail replaced by the alias proof-user and the dates moved past 2026-10-01.
    • s1-both: personal $212.35 of $300. Team ai $215.32 of $1,000, resetting 2026-10-15. An unrelated team in the same response must be ignored.
    • s2-personal-exhausted: personal $300 of $300, same team.
    • s3-team-only: the key has only a team_id. Team platform $25 of $100, resetting 2026-11-01.
    • s4-spend-only: a user key with $12.50 spend and no budget.
    • s5-user-mismatch: /user/info returns a different user_id from the one /key/info named.
  • Commands: bash launch.sh trayPanel and bash launch.sh settings:providers (proof mode) export WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS=--remote-debugging-port=9335 .... Each step ran BU_CDP_URL=http://127.0.0.1:9335 BU_NAME=worker-702 BH_TAB_MARKER=0 browser-use <<'PY' ... PY. Before each attach, curl /json/version showed Edg/154 WebView2, and the port 9335 listener's parent was this kit's exe. A scenario was switched by writing mocks/current.txt and clicking the tray panel's Refresh button through the DOM.

Results

# Assertion Result
A0 No real email or account from the host is visible. DOM scans of the tray panel, the float bar and Settings in every step found no @ addresses and no host user name. PASS
A1 Dark under theme auto: matchMedia('(prefers-color-scheme: dark)') is true and data-theme=dark in the tray panel, the float bar and Settings. The tray body is rgb(28, 28, 30) with text rgb(245, 245, 247). PASS
A2 s1 requests: GET /key/info, then GET /user/info?user_id=user-123, both with the bearer key. PASS
A3 s1 tray card, LiteLLM selected: "Personal budget" 71% used with the detail "$212.35 / $300.00" and no reset line. "Team budget" 22% used, "Resets in 13d 14h", detail "Team ai: $215.32 / $1,000.00". Account proof-user, plan api, cost line "Used: $212.35 / $300.00". The unrelated team is not shown. Bridge: descriptionIsDetail is true on both lanes, the team resetsAt is 2026-10-15T00:00:00Z, the cost period is "Personal budget" with limit 300, and accountOrganization is "ai". PASS
A4 s1 float bar, Automatic: the pill reads 22% (the team budget, not the fuller 71% personal budget). The tooltip is LiteLLM: 22% used followed by Resets in 13d 14h, and the tone is ok. PASS
A5 s2: "Personal budget" 100% used, "$300.00 / $300.00", with the Exhausted marker. The team lane is unchanged at 22%. Automatic switches to the exhausted personal budget: the float bar reads 100% with the crit tone. PASS
A6 s3 team-only: the requests are /key/info, then /team/info?team_id=team-456, with no /user/info. One lane, "Team budget", 25% used, "Resets in 30d 14h", detail "Team platform: $25.00 / $100.00". No account line, organization platform, and the cost is "Team budget" $25 of $100 with the reset date. The float bar reads 25%. PASS
A7 s4 spend-only: one informational lane, "Personal budget: No budget set", with no bar. The cost line "Used: $12.50" stays visible (period "Personal spend", alwaysVisible true). The bridge marks the selected metric isInformational: true. PASS. The float bar shows "0%", a defect on main (see below).
A8 s5 mismatch: the card error is exactly "Parse error: LiteLLM user_id did not match /key/info", with no usage and no cost. The float bar reads "Usage unavailable" with the crit tone. PASS
A9 Settings → Providers → LiteLLM: Account proof-user; the Usage section shows 71% with "$212.35 / $300.00" and 22% with "Resets in 13d 14h" and "Team ai: $215.32 / $1,000.00". Cost Used $212.35, Limit $300.00. The base URL help reads "Use HTTPS, or HTTP on a loopback or private-network address." PASS
A10 Metric picker. Automatic: the float bar shows 22%. Choosing the first lane saved provider_metrics.litellm = "session", and the float bar switched to 71% (warn). Choosing Automatic again saved "automatic", and the float bar returned to 22%. PASS
— Tray icon pixels and the native tray menu. Not covered (native, browser-use per maintainer). The litellm_automatic_* tests in usage_metric.rs cover the selected and companion icon windows.

Validation at f376f5ae

Command Result
cargo fmt --all --check clean
cargo clippy --workspace --all-targets -- -D warnings pass
cargo test -p codexbar litellm 26 passed
cargo test -p codexbar 2191 passed, 0 failed, 1 ignored
cargo test -p codexbar-desktop-tauri 466 passed, 0 failed. bootstrap_payload_exposes_every_provider_variant was skipped because it reads host settings on this base; #711 fixes it.
Frontend Not changed by this PR. The proof build ran the frozen install, check-locale, tsc and vite build.

Screenshots

All paths are under C:\Users\FSOS\AppData\Local\Win-CodexBar\port-audit\proof\702\shots\. The float bar window is transparent, so its captures are RGBA. Each -on-dark.png copy composites the capture onto #1c1c1e at 4x so the white pill text is readable.

  • 702-s1-tray-personal-and-team.png: the overview card with both budgets.
  • 702-s1-tray-litellm-detail.png: the LiteLLM card with the amounts, the team reset and the cost line.
  • 702-s1-floatbar-automatic-on-dark.png: Automatic shows the team budget, 22%.
  • 702-s2-tray-personal-exhausted.png and 702-s2-floatbar-automatic-on-dark.png: the exhausted personal budget, 100%.
  • 702-s3-tray-team-only.png and 702-s3-floatbar-automatic-on-dark.png: the team-only key, 25%.
  • 702-s4-tray-spend-only.png and 702-s4-floatbar-automatic-on-dark.png: "No budget set" with the spend line, and the "0%" pill from main.
  • 702-s5-tray-user-mismatch.png and 702-s5-floatbar-on-dark.png: the single-prefix parse error.
  • 702-settings-litellm-automatic.png, 702-settings-litellm-explicit-personal.png and 702-settings-explicit-personal-floatbar-on-dark.png: the Settings detail and the metric picker round trip.

Seen during this proof (on main or the base, not caused by this PR)

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