Skip to content

feat: Codex sessions as first-class sessions, with their own turns, tokens and honest cost - #16

Closed
daimonbot wants to merge 1 commit into
mainfrom
issue-15
Closed

daimonbot wants to merge 1 commit into
mainfrom
issue-15

Conversation

@daimonbot

Copy link
Copy Markdown
Owner

Closes #15

What was asked

A Codex provider, so Codex sessions are first-class sessions in atlas — their own turns, tools, tokens and cost — the way Claude sessions already are.

The transcript-side detection shipped in #14 already places a Codex node in the tree. What it places is a stub: apiCalls: 0, all-zero tokens, cost.confidence: "n/a", with the only real number stranded in reported.tokensUsed. Nothing had ever read Codex's own record of what the session did.

What changed

  • src/providers/codex.mjs — new provider. discover() / buildTree() / refFromPath() over Codex's own session-metadata store, its turns/tool-calls store, and its rollout log. Read-only, no network.
  • src/providers/index.mjs — a registry dispatching by ref.provider, replacing the direct claude.* calls at the 7 sites in cli.mjs and server.mjs.
  • src/providers/node.mjs — the node shape, authored in one place instead of two.
  • src/prices.mjs — a CODEX price table and priceCodex(), sibling to the existing Claude pricer.
  • src/render.mjs, src/cli.mjs, src/server.mjs — the money-rendering fix, below.
  • thread_spawn_edges is honoured for native Codex→Codex nesting. The table is empty everywhere it could be checked, so the empty case is what is actually proven; the populated case is exercised against a fixture.

A Codex session now lists, builds and renders on the same footing as a Claude one, with no provider-specific flag or route.

Cost: a correction to the issue's own premise

The issue asked for a subscription state that withholds a dollar figure. That is not what shipped, by explicit decision, because it contradicts what atlas already does: priceClaude() has never had any notion of billing mode — it is a pure function of model, timestamp and usage — and subscription-billed Claude sessions have always rendered a plain list-price figure.

The useful number is what the tokens would cost, because that is what transfers when the same workload moves to a metered account. What a flat plan happens to charge is not a property of the session.

So billing mode is not read at all, and there is no third state. Cost has the same two outcomes priceClaude() has always had: a figure flagged computed when the model has a price row, n/a when it does not.

The $0.00 fix

A cost of exactly $0.00 read as "free" where it meant "unknown". Fixed at all three sites that rendered a money value unconditionally: treeTerminal(), the list command, and indexHTML()'s session-list column. The third was not named in the issue and was found while specifying this.

Verification

  • All 13 acceptance criteria pass, plus 2 contract-level checks. Verified against this host's real Codex data, not only fixtures: a live session builds with 48 API calls, 8 turns and a full token breakdown, against the stub's zeros.
  • An unpriced model renders n/a on live data — one appeared mid-review and is unpriced by design.
  • A host without Codex is unaffected byte for byte on every machine-readable surface: list --json, tree --json, /api/sessions and /api/tree are byte-identical to the pre-change tree, with the two scans run concurrently to avoid a false diff on a live corpus.
  • node:sqlite stays behind an existence gate, so its experimental warning never reaches an existing Claude-only user — stderr is empty on such a host.
  • One declared deviation: human-rendered rows do change on a no-Codex host, because that is the $0.00 fix landing — 56 of 1469 terminal rows and 43 HTML rows. Sort keys, row counts and aggregate totals are unchanged.

Follow-ups, deliberately not done here

Centralising price data into its own module, a price-refresh mechanism, per-effort price rows, and subscription-utilisation reporting. All deferred by decision; worth their own issues.

…okens and honest cost

Codex sessions were already noticed — a Bash shell-out to the Codex CLI is
detected from inside the Claude transcript — but what that produced was a stub:
apiCalls 0, all-zero tokens, cost always $0.00 / "n/a". Nothing had ever read
Codex's own record of what the session actually did.

This adds a Codex provider that reads Codex's own on-disk stores, the same
direction atlas already reads Claude's transcript: it asks nobody to write
anything for it. A Codex session now discovers, builds and renders with real
turns, real tool calls, a real per-class token breakdown, and a cost computed
exactly the way a Claude session's is.

- src/providers/codex.mjs — discover/buildTree/refFromPath over Codex's
  session-metadata and turn stores plus its rollout log
- src/providers/index.mjs — a registry dispatching both providers by
  ref.provider, replacing the direct claude.* calls at 7 sites in cli.mjs and
  server.mjs
- src/providers/node.mjs — the node shape, authored in one place instead of two
- src/prices.mjs — a CODEX price table and priceCodex(), sibling to the Claude
  pricer
- thread_spawn_edges is honoured for native Codex-to-Codex nesting; the table is
  empty everywhere it could be checked, so the empty case is what is proven

Cost carries no notion of how anyone is billed. Billing mode is not read, and
there is no "subscription" state: list price times tokens when the model has a
price row, n/a when it does not — the same two outcomes priceClaude() has always
had. What the tokens would cost is the number that transfers to a metered
account; what a flat plan happens to charge is not a property of the session.

Fixes the $0.00 that read as "free" where it meant "unknown", at all three
unconditional money sites — treeTerminal(), the list command, and indexHTML()'s
session-list column. The third was not in the original report.

A host without Codex is unaffected byte for byte on every machine-readable
surface, and node:sqlite stays behind an existence gate so its experimental
warning never reaches an existing user.
@daimonbot

Copy link
Copy Markdown
Owner Author

Superseded by #22, which is now merged and provides the same first-class Codex provider, provider registry, turns, token and cost ingestion, plus the React index-worker integration and the empty-corpus test isolation fix.

@daimonbot daimonbot closed this Sep 24, 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.

feat: Codex provider — Codex sessions as first-class sessions, with their own turns, tokens and honest cost

1 participant