Skip to content

Report Codex usage and account status on GET /v1/status - #122

Open
EmBista wants to merge 3 commits into
RayBytes:mainfrom
EmBista:codex-status-headers
Open

Report Codex usage and account status on GET /v1/status#122
EmBista wants to merge 3 commits into
RayBytes:mainfrom
EmBista:codex-status-headers

Conversation

@EmBista

@EmBista EmBista commented Aug 25, 2026

Copy link
Copy Markdown

Stacked on #121 — the first two commits here are that PR. Review only the last commit (Report Codex usage and account status on GET /v1/status); once #121 merges I'll rebase and this collapses to one commit.

What's missing

A client behind ChatMock is blind to two things the proxy already knows: how much of the plan's rate limit each reply consumed (the Codex backend reports x-codex-* usage headers on every response, which ChatMock records for the CLI's info command and then keeps to itself), and which account the proxy is signed in as. Anyone building a dashboard or picking between accounts has to shell out to chatmock info — there is no way to get either over HTTP.

Fix

GET /v1/status serves both, reusing what the proxy already has — reading it costs no Codex requests:

{
  "account": {
    "name": "Ada Lovelace",
    "email": "ada@example.com",
    "plan_type": "plus",
    "account_id": "1f6f92a2-..."
  },
  "rate_limits": {
    "captured_at": "2026-08-26T08:03:58+00:00",
    "primary": { "used_percent": 12.5, "window_minutes": 10080, "resets_in_seconds": 345600 },
    "secondary": { "used_percent": 3.0, "window_minutes": 300, "resets_in_seconds": 1799 }
  }
}
  • rate_limits is the snapshot from the most recent proxied request — the same usage_limits.json the CLI's info command prints, fed by the existing record_rate_limits_from_response calls on both the OpenAI and Ollama routes. Like the Codex CLI it is not a live probe: the backend only reports usage on replies it sends. A window upstream did not report stays null; nothing synthesises a value. null until a first request has been proxied.
  • account is derived from the id/access token claims the CLI's info command already reads. Only display values are emitted, each omitted when its claim is missing; no token or claim set is ever included. The claims only change when the auth file does, so they are derived once and cached against its mtime/size — steady state is one os.stat per candidate auth path. null when signed out.

Also carries the build_reasoning_param fix from #120 for #116: an explicit reasoning.effort upstream recognises is forwarded for upstream to judge, instead of being rewritten to the server default because the model catalog omitted it. The Responses route was clamping gpt-5.6-luna + effort: "none" to low through the same function. The hunk is identical to #120's so whichever lands first, the other rebases clean.

How to try locally

python chatmock.py serve --port 8000

# Signed in, nothing proxied yet: account is populated, rate_limits is null.
curl -s http://127.0.0.1:8000/v1/status

# Send any request through the proxy...
curl -s http://127.0.0.1:8000/v1/responses -H 'Content-Type: application/json' -d '{
  "model":"gpt-5.6-luna","input":"say ok","store":false,"stream":false,
  "reasoning":{"effort":"none"}}'

# ...and the snapshot is populated (and the reply above ran at effort none, per #116).
curl -s http://127.0.0.1:8000/v1/status

python -m unittest tests.test_routes

Verified against the live backend: the endpoint returned the signed-in account and the recorded windows, and the effort: "none" request completed with the response echoing "reasoning": {"effort": "none"}.

Checklist notes

  • README.md gains a "Usage and account status" section documenting the endpoint and its null semantics. DOCKER.md not required: no new flags, env vars, or ports.
  • Read-only and additive: no existing route, parameter name, or payload shape changes. The endpoint reflects traffic from both the OpenAI and Ollama routes, since both already record the snapshot.
  • Defaults unchanged; the only behaviour change is the Explicit reasoning.effort: none is silently rewritten to low #116 hunk shared with Carry the chat routes' request and usage to upstream faithfully #120 (an explicit effort upstream refuses now returns upstream's 400 naming the supported values, instead of silently running at the default).
  • Account values are display metadata only — chosen to match what chatmock info prints. A test asserts no token value or claim blob appears in the status body, headers, or verbose logs.
  • 8 tests added, 28 passing. The suite isolates CHATGPT_LOCAL_HOME into a temp dir so tests never read or clobber a real usage snapshot, and a test pins that the account derivation runs once while the auth file is unchanged on disk.

Disclosure

As with #118/#120/#121, AI was used to write this patch (Claude Code). The endpoint was exercised against the live backend, not just the route tests. An earlier revision of this branch delivered the same data as response headers on every /v1/responses reply; it was reworked to this endpoint as the less invasive shape. Happy to rename the route or adjust the payload if you'd prefer different conventions.

EmBista and others added 3 commits August 20, 2026 23:04
A client behind ChatMock has no way to see the plan usage the Codex backend
reports on every reply, nor which account the proxy is signed in as. Both are
now served by `GET /v1/status`, reusing the rate-limit snapshot the proxy
already records for the CLI's `info` command:

    {
      "account":     {"name", "email", "plan_type", "account_id"},
      "rate_limits": {"captured_at", "primary", "secondary"}
    }

- The usage half is the snapshot from the most recent proxied request — like
  the Codex CLI it is not a live probe, because the backend only reports usage
  on replies it sends. Windows upstream did not report stay null; nothing
  synthesises a value. `rate_limits` is null until a first request is proxied.
- The account half is derived from the id/access token claims the CLI's `info`
  command already reads. Only display values are emitted, each omitted when its
  claim is missing; no token or claim set is ever included. The claims only
  change when the auth file does, so they are derived once and cached against
  its mtime/size.

Also carries the `build_reasoning_param` fix from RayBytes#120 for RayBytes#116: an explicit
`reasoning.effort` upstream recognises is forwarded for upstream to judge,
instead of being rewritten to the server default because the model catalog
omitted it. The Responses route was clamping `gpt-5.6-luna` +
`effort: "none"` to `low` through the same function. The hunk is identical
to RayBytes#120's so whichever lands first, the other rebases clean.

Tests cover: the empty status before any request; a responses call feeding the
snapshot with account and both windows; partial upstream headers stored without
invention; usage recorded from a 429; account name fallback order; the account
derivation running once while the auth file is unchanged; the outgoing payload
keeping model / effort none / stream / store; and no token value in the status
body, headers, or verbose logs. The suite isolates CHATGPT_LOCAL_HOME so tests
never touch a real usage snapshot.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@EmBista
EmBista force-pushed the codex-status-headers branch from 31406bb to 998a4d0 Compare August 25, 2026 22:20
@EmBista EmBista changed the title Forward Codex usage and account status on /v1/responses Report Codex usage and account status on GET /v1/status Aug 25, 2026
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