fork of prime agent with changes i want but are not upstreamable probqbly (and some fixes)
prime-agent packages ai docs usage-query.md
5.3 kB
Markdown
at main

Provider Usage Query Architecture and JSON Contract #

The usage query core extracts quota, rate limit, and account usage queries into the provider layer (@earendil-works/pi-ai). It provides a language-neutral interface and a standardized JSON contract for CLI and external consumers.

JSON Contract #

Running prime-agent usage --json outputs exactly one JSON object on stdout (with no ANSI codes or supplementary text):

{
  "fetchedAt": 1742294400000,
  "reports": [
    {
      "provider": "openai-codex",
      "providerName": "ChatGPT Codex",
      "accountId": "acct_12345",
      "email": "user@example.com",
      "planType": "plus",
      "limits": [
        {
          "label": "Primary",
          "usedPercent": 12.5,
          "windowLabel": "5h window",
          "resetsAt": 1742312400000,
          "status": "ok"
        },
        {
          "label": "Secondary",
          "usedPercent": 40.0,
          "windowLabel": "24h window",
          "resetsAt": 1742380800000,
          "status": "ok"
        }
      ],
      "error": null
    }
  ],
  "skipped": [
    "anthropic"
  ]
}

Fields #

  • fetchedAt: Epoch timestamp in milliseconds when the query was executed.
  • reports: Array of usage reports, one per credential entry (supporting multi-account configurations).
    • provider: Provider identifier string (e.g. "openai-codex").
    • providerName: Human-readable display name for the provider.
    • accountId: Stored or token-extracted account identifier (null if unavailable).
    • email: Stored or token-extracted account email address (null if unavailable).
    • planType: Subscription tier or plan type when reported by the upstream API (null if not reported).
    • limits: Array of quota and rate limit windows:
      • label: Name of the limit window (e.g. "Primary", "Secondary", "Feature · Primary").
      • usedPercent: Current usage percentage as a number between 0 and 100 (null if unknown).
      • windowLabel: Duration of the rate window (e.g. "5h window", "24h window").
      • resetsAt: Epoch timestamp in milliseconds when the limit window resets (null if unknown).
      • status: One of "ok", "warning", "exhausted", or "unknown".
    • error: Error message string if the provider query failed, or null on success. (Provider errors are captured as data and do not cause non-zero process exits.)
  • skipped: Array of configured provider IDs whose OAuth provider implementation does not provide a quota feed (deduplicated in stable order).

How to Add a Provider to /usage #

To add a quota/usage feed for a provider:

  1. Implement the optional usage method on the provider's OAuthProviderInterface in its oauth provider file (packages/ai/src/utils/oauth/<provider>.ts):
    async usage(
      credentials: OAuthCredentials,
      options?: { fetchImpl?: FetchLike; signal?: AbortSignal },
    ): Promise<UsageReport> {
      // Fetch quota from provider backend and return a UsageReport
    }
    
  2. No provider registry changes are needed: collectUsage dynamically checks whether getOAuthProvider(entry.provider)?.usage is defined. Providers without this hook are automatically appended to skipped.

Non-OAuth Providers #

Non-OAuth providers (such as API-key-only models or custom proxies) currently have no stored OAuth accounts in AuthStorage. If a feed is added for API-key providers, it can implement the same UsageReport return shape via an adapter function or provider extension. Currently, /usage queries stored OAuth accounts.

Every provider with a feed implements one: openai-codex, anthropic, google-antigravity, opencode-go, zai, deepseek, hyper and alibaba. A known provider with no feed reports that as its reason and appears in skipped instead of looking unlimited.

Native Hosts (Zig) #

A macOS Swift app cannot run this package, so the query exists a second time as a C ABI library: the usage-core repository, checked out at native/usage/ as a submodule (Zig, pinned to 0.16.0, built by zig build).

The core is pure there too — usage_advance returns the requests to answer or the finished report, and usage_supported_providers names the feeds and the credential kinds each accepts — so the app performs the request with NSURLSession (system trust store, proxies, HTTP/2) and hands back the response. A request is {"kind":"http",...} to send or {"kind":"command",...} for the host to run; a host that runs no commands answers status: 127 and the feed routes around it. Every call returns JSON the caller frees with usage_free; failures are {"error":"..."} in that JSON, and NULL arguments mean an absent string.

Two implementations of one contract only stay honest if they are compared, so packages/ai/test/usage-zig-parity.test.ts computes both from the same recorded bodies at test time (fixtures under native/usage/tests/fixtures/, one captured live from wham/usage with user_id/account_id/email removed), and zig build test compiles and links a C program against the installed library to exercise the ABI the way a native caller does. npm run check runs both.

When the contract changes, change it in both: the typescript parser and its fixture, then the Zig parser and the parity test.