This document defines the requirements, shared machinery, and safety invariants for onboarding new quota providers or modifying existing adapters in quota-axi.
Clean-Room & Dependency Policy
- Clean-room implementation: Adapter behavior (retry-after handling, snake/camel field tolerance, window parsing) must be an original implementation derived solely from the vendor's own HTTP/OAuth behavior. quota-axi carries no vendored third-party adapter code. The Z.AI adapter is the single attribution exception (derived from opencode-glm-quota, MIT) credited in
src/providers/zai.ts. - HTTP & proxy handling: Remote HTTP requests must route through
src/lib/http.ts(providerFetch) so standardHTTP_PROXY,HTTPS_PROXY, andNO_PROXYpolicies apply consistently without leaking proxy credentials in logs. Do not use globalfetchdirectly.- Node 26 compatibility:
src/lib/http.tspairs the installedundicibuild'sProxyAgentwith that same build'sfetchbecause Node's built-in global fetch only accepts dispatchers from its bundledundici, and Node 26 rejects externalProxyAgentinstances withInvalidArgumentError: invalid onError method.
- Node 26 compatibility:
- Process table reads: Any process-table inspection (e.g. Antigravity loopback detection or Claude process checks) must route through
currentUserProcessListArgsinsrc/lib/process.tsto handle platform flag differences portably (Linux procps rejects BSD-xalongside-u).
Provider Onboarding Checklist
When adding a new provider or migrating an existing adapter:
-
Enumerate every source in ownership-stability order:
- Declare source priority via a named constant (e.g.
COPILOT_SOURCE_ORDER). - Distinguish
absent,structurally_invalid,unsupported,read_error, andresolved(stored-validorstored-expired) at the typed local resolution boundary. - Route Pi agent entries through
classifyPiAuthEntryinsrc/lib/pi-auth-store.ts. - Derive
credentialPresentonce from local resolution rather than ad hoc at call sites.
- Declare source priority via a named constant (e.g.
-
Separate local resolution from the bounded read-only liveness probe:
- Enroll stored-expired credentials instead of skipping them.
- Never infer liveness from presence alone.
- Classify probe outcomes as
usable,live_no_quota,definitively_rejected, ortransient. - Only a first-party HTTP 401 or 403 is an authentication verdict. Server errors, rate limits, network timeouts, or schema mismatches are request failures, not auth verdicts.
-
Handover only after definitive credential failure:
- An absent source is never marked degraded.
- A present-but-broken source superseded by a working sibling is marked degraded on fresh readings only (
state.degradedSources). - Stale means last-known cache.
- Never exchange a refresh token, and never retain, log, render, cache, or send its value. Delegated refresh checks presence only; Pi brokers may read a stored refresh value only to classify it as a usable literal, then discard it.
-
Add comprehensive adapter regression tests:
- Test cases: primary healthy, stored-expired plus live sibling, structurally invalid present, absent source, all rejected, refreshable expired, and transient failure stops handover.
- Extend the cross-provider invariant table in
test/credential-contract.test.ts.
Credential Selection Machinery
Credential selection is shared in src/providers/credential-selection.ts:
- Advisory stored expiry: Stored
expiresAtorexpiredfields are advisory only within a source, never a verdict or a reason to reorder declared sources. - Empirical testing: Stored-expired credentials are tested in that source's fixed priority position before any sign-in or expired verdict. An empirically live credential always wins.
- Transient failures: Network errors, 5xx responses, or timeouts must never switch candidates within one source or become auth verdicts.
- Adapters using selection: Grok, Codex, Kimi, Command Code, Copilot, OpenCode Go, Devin, and Muse route through
selectCredential. Codex, Kimi, Copilot, and Devin call it once per source so each provider's ownership-stability order remains authoritative. - Probe token safety: A broker's
expiredresolution carries the stored token for probe use only; it must never be logged, cached, or rendered. - Profile-only mode:
--profile-onlyis the fail-closed single-account quota probe for Claude and Codex: it requiresCLAUDE_CONFIG_DIRorCODEX_HOME, reads only that profile's native credential file, and bypasses alternate sources, delegated refresh, and quota cache access (full JSON keeps non-secret account/source/attempt evidence; ordinary output stays redacted). Omitting the flag must preserve legacy discovery and cache behavior. Contract: README Profile-only quota reads.
Multi-Source Handover & Degraded Sources
- Working store precedence: A broken store must never speak for a provider whose sibling store still answers. Consult sources in priority order.
- Handover boundaries: Handover happens on credential problems only, never on transport, decoding, or server failures.
- Tracking attempts:
src/lib/source-attempts.tsclassifies attempts asstatus: "failed"orskippedwithcredentialPresent. Adapters setdegraded: falseon non-credential attempts (e.g. Grok's live model catalog probe). - Reporting degradation:
withQuotaSemanticsinsrc/interpretation.tspublishesstate.degradedSourceson fresh readings only.src/render.tsemits thedegraded_sourceattention row.
Delegated Credential Refresh
Shared machinery lives in src/providers/delegated-refresh.ts. It is the single carve-out to quota-axi's read-only boundary:
-
Eligibility criteria: A delegate is eligible ONLY when:
- The same stored access token is expired,
- The stored credential carries a refresh token, and
- The token was definitively rejected (HTTP 401/403) by the vendor's quota/user endpoint.
-
Mechanism: quota-axi executes the vendor CLI's own smallest non-interactive rotation command (e.g.
claude doctor,grok models) and re-reads the file/store that CLI updated. -
Never exchange tokens: quota-axi must never perform an OAuth refresh-token exchange itself. These tokens rotate on use; a second exchange signs the user out of the measured tool.
-
Presence only: Delegated refresh inspects the refresh token's presence only (
Object.hasOwn(credential, "refresh_token")). quota-axi never retains, logs, renders, caches, or sends its value. Pi brokers may read a stored refresh value only to classify it as a usable literal, then discard it. -
Never signal the child: Quota-axi never signals or force-kills a delegated child process. The budget bounds only how long quota-axi waits. The child runs in its own process group (
detached: true) so Ctrl+C on a live TUI does not abort it. If the command exceeds budget, resolve asunconfirmed/refresh_timed_out(reported as unmeasured or stale, never as sign-out, and never retiring cache). -
Claude safety check: Before delegating Claude refresh,
src/lib/running-processes.tsmust confirm no Claude Code process is running, since Claude Code owns that session's refresh. If the process table cannot be listed, stay read-only. The check and spawn are not atomic - the check only narrows the common repeated--tuiversus live-session collision; together with never signaling the delegate it is strictly safer than force-killing without adding a failure mode. Contract: README Delegated credential refresh. -
Approved delegates: Only commands whose rotation behavior is empirically established from the vendor CLI are permitted:
- Claude:
claude doctor - Grok:
grok models - Codex:
app-serverJSON-RPC probe
All other providers (Cursor, Copilot, Kimi, Z.AI, Alibaba, OpenCode Go, Antigravity, Command Code, MiniMax, MiMo, DeepSeek, OpenRouter, ElevenLabs, Devin, Muse, Higgsfield) remain strictly read-only.
- Claude:
-
Option gating: Delegated refresh is gated by
ProviderOptions.refreshCredentials.--no-credential-refreshdisables it; theauthcommand always passesfalse. Tests must specify it explicitly to prevent accidental CLI spawning.
Test & Mocking Discipline
- Synthetic credentials: All tests must use synthetic credentials and fixtures located in
test/fixtures/. Never use real secrets or developer tokens. - Mock all boundaries: Mock Keychain (
security), Windows Credential Manager (CredReadW), process table (ps), and remote HTTP requests. - No live network or execution: Tests and CLI runs during validation must never contact live provider endpoints or execute real credential refresh commands.