Add Codex JSON sessions and usage limits

This commit is contained in:
iris committed 2026-09-07 23:29:15 -04:00
1 parent 0862b47f76
commit 6a0202b1b5
14 files changed
+1173 -52

No files matched your search

+38 -7
View File
@@ -1,6 +1,6 @@
# ai-app — plan
A phone interface to AI coding sessions — Claude Code and llama.cpp — built
A phone interface to AI coding sessions — Codex, Claude Code and llama.cpp — built
to replace the Claude app for day-to-day use. Two motivations: local models
need a front end at all, and owning the client means fixing what the official
app gets wrong (it won't deliver a typed message until the turn fully
@@ -29,11 +29,12 @@ Android app (Compose)
backend (Rust/Axum, desktop)
├─ SessionManager ── Session ── Driver (trait)
│ ├─ ClaudeDriver (claude stream-json over stdio)
│ ├─ CodexDriver (codex exec --json, one process per turn)
│ ├─ LlamaDriver (llama-server over HTTP)
│ └─ EchoDriver (the test rig)
│ each driver's process is spawned through a Transport,
│ locally or as `ssh host …`, decided by the setup it names
├─ usage.rs (Anthropic OAuth usage endpoint, per machine)
├─ usage.rs (provider usage meters, per machine)
├─ models.rs (HuggingFace browsing and GGUF downloads)
├─ files.rs (the file explorer's half of the backend)
└─ config.ron + per-session transcript files
@@ -79,7 +80,7 @@ axum 0.8, axum-server + rustls, tokio, serde, clap, tracing. Rust edition
- `config.rs` — the persisted schema.
- `setups.rs` — machines and provider discovery.
- `files.rs` — the file explorer (`EXPLORER.md`).
- `usage.rs`Anthropic usage polling, per machine.
- `usage.rs`provider usage polling, per machine.
- `models.rs` — HuggingFace browsing and GGUF downloads.
- `media.rs` — the image media-type/extension table, shared by the four
places that must agree: storing an upload, serving it back, handing one to
@@ -87,7 +88,8 @@ axum 0.8, axum-server + rustls, tokio, serde, clap, tracing. Rust edition
- `session/mod.rs``SessionManager`, the live registry; every mutation
funnels through it so in-memory and on-disk state cannot come apart.
- `session/driver.rs` — the `Driver` trait and the common event model.
- `session/claude.rs`, `session/llama.rs`, `session/echo.rs` — the drivers.
- `session/claude.rs`, `session/codex.rs`, `session/llama.rs`,
`session/echo.rs` — the drivers.
- `session/transcript.rs` — the append-only JSONL event log per session,
with monotonically increasing sequence numbers (the phone's resume cursor).
- `session/transport.rs`, `ssh.rs` — running a driver's command locally or
@@ -146,9 +148,10 @@ server is going away and means to come back) and `stop` (the session is being
deleted, so the process must not survive). Every driver owes exactly one of
the two.
`send_user_message` during a run is the point of the whole app: the dialect
queues it for injection at the next tool boundary rather than the end of the
turn.
`send_user_message` during a run is the point of the whole app: a dialect with
a live input channel injects it at the next tool boundary. A turn-at-a-time
dialect persists it and starts the next turn as soon as the current process
ends.
### Claude driver specifics
@@ -190,6 +193,34 @@ is the rule behind the import refusal, the single `ClaudeDriver::launch`
entry point, and the `Exited` correction below; two CLIs on one session file
duplicate the conversation into it and bill the second for re-reading it all.
### Codex driver specifics (2026-09-07)
Codex uses `codex exec --json`, whose stdout is JSONL: `thread.started`, turn
boundaries, item start/completion records, and the final token usage. The
driver translates those records into the same events as every other session
and persists the reported thread id. Later turns run `codex exec resume <id>
--json`; model, effort and attachments remain launch arguments owned by the
driver rather than branches in routes or screens.
One `exec` process is one turn and exits normally at its end. The session
therefore owns a sequence of child processes rather than one permanently idle
child: a clean exit after `turn.completed` means `idle`, while an exit before a
turn boundary is an error. A process in flight still uses `process.json` plus
detached stdout/stderr logs, so it survives and is adopted across a backend
restart exactly like the long-lived CLI. Messages received during a turn are
persisted and start later turns in order. The JSON exec surface has no stdin
steering or interactive approval protocol, so it cannot inject a message at a
tool boundary or answer a question inside the same process; those limits are
reported rather than guessed around.
Codex subscription limits come from the CLI's `account/rateLimits/read`
app-server request on the machine whose setup runs Codex. This keeps login and
token refresh inside the CLI. Its primary and secondary windows are normalized
into the existing usage snapshot shape, under provider name `codex`, so the
phone and auto-resume need no Codex branch. This is the CLI's local protocol
and is treated defensively for the same reason as Claude's undocumented usage
endpoint: missing fields or a refusal degrade to an unavailable snapshot.
### The llama driver
One `llama-server` per session, started through the same `Transport` as any