Files
ratulsarna c56ee9b13f Add Abacus AI provider (#729)
* feat(abacus): add Abacus AI provider with cookie-based usage fetching

Add support for Abacus AI (ChatLLM/RouteLLM) as a new provider. Uses
browser cookie authentication against the describeUser API endpoint to
fetch compute point usage. Values are in centi-credits (divided by 100
for display). Primary window shows monthly credit usage as percentage,
secondary window shows 7-day usage. Reset date derived from lastBilledAt
+ 1 month.

* fix(abacus): fix usage display formatting and match Claude pattern

- Fix credits format string (Swift String(format:) has no comma flag;
  use NumberFormatter for thousands separators)
- Remove secondary weekly window (Abacus has monthly billing only)
- Show credits detail below gauge (follow Warp/Kilo pattern for
  resetDescription rendering)
- Add pace/reserve/deficit estimate on primary monthly window
- Remove inactive status page URL (abacus.statuspage.io is inactive)
- Hide account email/org from menu (not relevant for display)
- Set windowMinutes to 30 days so pace calculation works correctly

* feat(abacus): add pace tick and detail lines to card view

- Show pace indicator tick (green/red) on the primary gauge bar
- Add reserve/deficit line below gauge with pace right label
- Show credits used/total as detail text below the gauge
- Restore account identity (email, org, plan tier) in card header

* fix(abacus): use correct API endpoints for credits and billing date

Switch from describeUser (stale centi-credit values, no billing date)
to _getOrganizationComputePoints (accurate credits in real units) and
_getBillingInfo (exact nextBillingDate and subscription tier). Both
endpoints are fetched concurrently.

* fix(abacus): validate session cookies and preserve API errors

Skip browser cookie sets that only contain anonymous/marketing cookies
by checking for session/auth cookie names before accepting a set. This
prevents using invalid cookies when a valid session exists in a later
browser profile.

Separate the cookie import and API fetch try blocks so that network,
parse, or auth errors from the API are not misreported as "Browser
cookie import failed" and incorrectly replaced with noSessionCookie.

* fix(abacus): fix compilation after UsagePaceText API refactor

Update three call sites that still used the removed
UsagePaceText.weeklySummary(provider🪟) and
weeklyPaceDetail(provider:window:now:showUsed:) signatures.

MenuDescriptor and MenuCardView now use the store.weeklyPace()+
UsagePaceText.weeklySummary(pace:) pattern. StatusItemController
computes weeklyPace from primary for Abacus (no secondary window).

* fix(abacus): fix menu bar metric options and pace indicator

Remove Secondary (Weekly) from the menu bar metric picker since
Abacus has no secondary window; only Automatic and Primary (Credits)
are valid options.

Enable pace computation for Abacus in weeklyPace() so the bar
tick indicator (reserve/deficit/on-pace) is rendered correctly.
Abacus uses the simple UsagePace.weekly() path with the monthly
window already set in RateWindow (30 days).

* docs(abacus): add provider documentation and update provider listings

Add docs/abacus.md with setup, API details, and troubleshooting for the
Abacus AI provider. Add Abacus AI entry to docs/providers.md strategy
table and detailed section. Add Abacus AI to README provider list.

* test(abacus): add unit tests for Abacus AI provider

Add AbacusProviderTests.swift with 23 tests in 3 suites covering:
- AbacusDescriptorTests: provider metadata, source modes, CLI config
- AbacusUsageSnapshotTests: credit conversion, formatting, edge cases
- AbacusErrorTests: error description completeness

Uses the Swift Testing framework (@Test + #expect) matching the
convention established by recent upstream provider tests.

* fix(abacus): hoist paceWindow binding out of if-expression

Swift 6.2.4 does not allow let bindings inside the else
branch of an if-expression. Move the paceWindow computation
before the if-expression to fix compilation.

* fix(abacus): tighten session cookie matching to avoid false positives

Replace overly broad "id" substring pattern with exact known cookie
names (sessionid, auth_token, etc.) checked first, then conservative
substring fallback. Prevents selecting unauthenticated cookie sets
from browsers that only have analytics cookies for abacus.ai.

* fix(abacus): add manual cookie header field to settings UI

settingsFields was returning empty array, making Manual cookie mode
non-functional. Add secure text field bound to abacusCookieHeader,
visible only when cookie source is set to manual, with link to open
the Abacus AI dashboard.

* fix(abacus): route cookie reads through BrowserCookieAccessGate

Use codexBarRecords instead of records directly so the access gate
cooldown is consulted before attempting Chromium keychain reads.
Also detect session-expired API error payloads and map them to
.sessionExpired so cached cookies are properly evicted.

* fix(abacus): suppress reset line when billing date unavailable

Clear primaryResetText when resetsAt is nil to prevent displaying
a misleading "Resets ..." line that duplicates the credit detail.

* fix(abacus): remove CSRF from session cookies, propagate billing auth errors

Remove csrftoken/csrf_token from knownSessionCookieNames — CSRF
tokens exist in anonymous jars and caused false-positive session
detection. Also replace try? on billing fetch with explicit error
handling that propagates auth errors while tolerating other failures.

* fix(abacus): tighten cookie matching and retry on auth failure

Remove "token" from fallback substrings (matched csrftoken). Add
excludedCookiePrefixes to reject csrf/analytics cookies even on
substring match. Change importSession to importSessions returning
all candidates so fetchUsage can try each in turn — stale session
in first source no longer blocks valid ones further down.

* refactor(abacus): comprehensive provider overhaul addressing review feedback

Split AbacusUsageFetcher.swift (396 lines) into 4 files matching
the Kimi/Perplexity provider pattern:
- AbacusCookieImporter.swift — browser cookie extraction
- AbacusUsageFetcher.swift — API fetching logic
- AbacusUsageSnapshot.swift — data model + conversion
- AbacusUsageError.swift — error types

Bug fixes:
- Pin NumberFormatter locale to en_US (was locale-dependent)
- Cached cookie parse failures now clear cache and fall through
  to fresh browser import instead of aborting
- Session loop retries on all recoverable errors (auth + parse),
  not just auth — prevents one stale source blocking valid ones
- Preserve last error on loop exhaustion instead of generic
  .noSessionCookie
- Log billing fetch failures instead of silently discarding
- Truncate HTTP response body in error messages (security)
- Capture JSON parse error details instead of using try?
- Throw parseFailed when critical credit fields missing instead
  of returning hollow 0% snapshot

Architecture alignment:
- Add structured CodexBarLog logging (abacusCookie, abacusUsage)
- Use cookieImportCandidates(using:) for browser filtering
- Accept BrowserDetection parameter for testability
- Add hasSession() convenience for isAvailable() check
- Improve isAvailable() to probe for actual session existence
- Add import AppKit (was implicit via SwiftUI)

* fix(abacus): derive pace window from actual billing cycle length

Use Calendar to compute the real billing cycle duration (one
calendar month before resetsAt) instead of hardcoding 30 days.
Falls back to 30-day approximation when resetsAt is nil. Fixes
pace drift on 28/31-day months.

* fix(abacus): require both credit fields and fix menu bar pace display

Require both totalComputePoints and computePointsLeft in API
response — partial data no longer silently shows 0% usage.

Fall back to primary window for menu bar pace calculation when
secondary is nil, so Abacus pace renders in Pace/Both display
modes.

* fix(abacus): restrict primary-window pace fallback to Abacus only

The supportsWeeklyPace guard was too broad — it applied to all
pace-capable providers, causing Claude's 5-hour primary window
to be misused for pace calculation. Scope the fallback to .abacus
which is the only provider using primary window for pace.

* fix(abacus): correct dashboard URL in manual cookie settings action

Point Open Dashboard to /chatllm/admin/compute-points-usage matching
the provider's configured dashboardURL.

* fix(abacus): default cookie import to Chrome-only per AGENTS.md

AGENTS.md mandates Chrome-only cookie imports by default. Change
browserCookieOrder from defaultImportOrder (all browsers) to
[.chrome] to avoid unnecessary browser/keychain prompts.

* fix(abacus): comprehensive quality pass from multi-agent review

Critical fixes:
- Remove .parseFailed from isRecoverable (deterministic error,
  retrying other sessions gives same result)
- Fix NumberFormatter thread safety (allocate per call instead
  of mutating shared static state)

High fixes:
- Add public init on SessionInfo (API surface parity with peers)
- Consistent self. usage throughout fetcher (was mixing Self/self)
- Make notSupported public (thrown from public function)
- Add Equatable conformance to AbacusUsageError (peer pattern)
- Simplify isAuthRelated to delegate to isRecoverable

Medium fixes:
- Add MARK sections throughout all files
- Add #if canImport(FoundationNetworking) guard (Linux compat)
- Add explanatory comment on pace fallback in StatusItemController

Tests:
- Add isRecoverable/isAuthRelated classification tests for all
  five error cases (28 tests total, 4 suites)

* fix(abacus): classify unauthorized JSON errors as auth failures

Add unauthorized/unauthenticated/forbidden to the API error keyword
detection so JSON-level auth failures (HTTP 200 with success:false)
throw .unauthorized instead of .parseFailed. This enables the
multi-session fallthrough for stale cookies that return auth errors
in the JSON payload rather than via HTTP status codes.

* fix(abacus): Chrome-first cookie import with multi-browser fallback

Reconcile AGENTS.md Chrome-only guideline with real-world need to
support Safari/Firefox users. Match OpenCodeCookieImporter pattern:

- Descriptor browserCookieOrder: full defaultImportOrder (all browsers)
- AbacusCookieImporter.importSessions: preferredBrowsers: [.chrome]
  default, empty array falls back to the full descriptor order
- fetchUsage: try Chrome first, broaden to all browsers if Chrome
  yields no sessions
- isAvailable: same Chrome-first then fallback probe

Users with Chrome get single-browser probe (AGENTS.md compliant);
Safari/Firefox users still get their cookies imported when Chrome
is absent or empty.

* fix(abacus): fall back to all browsers after Chrome auth exhaustion

Previously, the all-browsers fallback only triggered when Chrome
import threw. If Chrome cookies existed but all returned expired/
unauthorized, the loop exhausted Chrome candidates and returned an
auth error without ever trying Safari/Firefox.

Extract browser-tier logic into tryFetchFromBrowsers helper; call
it first with [.chrome], then with [] (all browsers) if Chrome
yielded no valid snapshot for any reason (import failure OR all
sessions failing with recoverable errors).

* fix(abacus): register in TokenAccountSupportCatalog and bound billing timeout

Two issues caught by Codex review:

- AbacusSettingsStore referenced TokenAccountSupportCatalog.support(
  for: .abacus) but no catalog entry existed, so token account
  overrides were dead code. Add cookie-header injection entry
  matching the pattern used by other cookie-based providers.

- Billing info fetch shared the full 15s timeout with credits. Cap
  it at min(timeout, 5s) so a slow/flaky billing endpoint doesn't
  delay credit rendering — billing is optional anyway.

* Improve Abacus web fetch error handling

* Harden Abacus web fetch fallbacks

* Fix Abacus lint failure

* Fix provider order test for Abacus

* Fallback after stale Abacus cache failures

* Handle Abacus fetch concurrency with sendable task results

---------

Co-authored-by: Christian C. Berclaz <christian.berclaz@mac.com>
2026-04-16 13:23:37 +05:30

2.5 KiB

summary, read_when
summary read_when
Abacus AI provider: browser cookie auth for ChatLLM/RouteLLM compute credit tracking.
Adding or modifying the Abacus AI provider
Debugging Abacus cookie imports or API responses
Adjusting Abacus usage display or credit formatting

Abacus AI Provider

The Abacus AI provider tracks ChatLLM/RouteLLM compute credit usage via browser cookie authentication.

Features

  • Monthly credit gauge: Shows credits used vs. plan total with pace tick indicator.
  • Reserve/deficit estimate: Projected credit usage through the billing cycle.
  • Reset timing: Displays the next billing date from the Abacus billing API.
  • Subscription tiers: Detects Basic and Pro plans.
  • Cookie auth: Automatic browser cookie import (Safari, Chrome, Firefox) or manual cookie header.

Setup

  1. Open Settings → Providers
  2. Enable Abacus AI
  3. Log in to apps.abacus.ai in your browser
  4. Cookie import happens automatically on the next refresh
  1. In Settings → Providers → Abacus AI, set Cookie source to Manual
  2. Open your browser DevTools on apps.abacus.ai, copy the Cookie: header from any API request
  3. Paste the header into the cookie field in CodexBar

How it works

Two API endpoints are fetched concurrently using browser session cookies:

  • GET https://apps.abacus.ai/api/_getOrganizationComputePoints — returns totalComputePoints and computePointsLeft (values are in credit units, no conversion needed).
  • POST https://apps.abacus.ai/api/_getBillingInfo — returns nextBillingDate (ISO 8601) and currentTier (plan name).

Cookie domains: abacus.ai, apps.abacus.ai. Session cookies are validated before use (anonymous/marketing-only cookie sets are skipped). Valid cookies are cached in Keychain and reused until the session expires.

The billing cycle window is set to 30 days for pace calculation.

CLI

codexbar usage --provider abacusai --verbose

Troubleshooting

"No Abacus AI session found"

Log in to apps.abacus.ai in a supported browser (Safari, Chrome, Firefox), then refresh CodexBar.

"Abacus AI session expired"

Re-login to Abacus AI. The cached cookie will be cleared automatically and a fresh one imported on the next refresh.

"Unauthorized"

Your session cookies may be invalid. Log out and back in to Abacus AI, or paste a fresh Cookie: header in manual mode.

Credits show 0

Verify that your Abacus AI account has an active subscription with compute credits allocated.