# 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):

```json
{
  "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`):
   ```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.
