ReviewOS

stacks/uplink

feat(engine): support Codex alongside Claude Code

#4
Open glennmichael123 opened this 9 days ago ·
9 days ago

Uplink runs every text through Claude Code. Codex should be a supported alternative, chosen per installation.

The seam already exists, so this is an implementation rather than a refactor

  • Engine is a one-method interface: run(request: EngineRequest) => EngineRun (app/Uplink/engine.ts#L46).
  • ClaudeEngine implements Engine (engine.ts#L61) and is the only implementation.
  • The orchestrator is already engine-agnostic: app/Uplink/uplink.ts imports only Engine and EngineRun (#L3), holds one engine: Engine (#L39) and calls engine.run(...) once (#L305).

So a CodexEngine implements Engine drops in, provided it produces the same two shapes: the EngineEvent stream (session, tool, text) and an EngineResult with ok, text, sessionId, costCents, durationMs, turns and authFailure.

What has to change beyond the new class

  1. Both construction sites select an engine. app/Uplink/desktop-agent.ts#L175 and app/Commands/Uplink.ts#L22 each do new ClaudeEngine({...}) today.
  2. The health probe hardcodes Claude. desktop-agent.ts#L134 spawns [bin, '-p', 'Reply with exactly: ok', '--model', 'haiku', '--output-format', 'json']. With Codex selected this probes the wrong CLI and a healthy install reports broken.
  3. Settings are Claude-shaped. app/Uplink/settings.ts carries model: string | null (#L26, #L37) and exports it as UPLINK_MODEL (#L68). An engine field is needed alongside it, and the menubar (app/Uplink/menubar.html) has to offer it.
  4. Auth detection is Claude's wording. The AUTH_FAILURE regex in engine.ts matches please run /login, credit balance and similar, and CLAUDE_CODE_OAUTH_TOKEN is deleted when empty (engine.ts#L89-L92). Codex needs its own detection, otherwise a signed-out Codex texts back a generic failure instead of telling the person to sign in.
  5. uplink:doctor (app/Uplink/doctor.ts) has to check whichever engine is selected.

Session continuity is the real design question

A thread keeps context because EngineRequest.sessionId is passed to claude --resume (engine.ts#L79), and new starts a fresh one. Codex's resume model is not the same. Decide explicitly how a thread maps onto it, because if it silently starts fresh every text, follow-ups break in a way that is invisible until someone relies on one.

Cost

costCents is documented as integer cents at API rates. If Codex does not report a comparable number, report null rather than a wrong one, and let the dashboard show it as unknown.

Acceptance

  • With Codex selected: text a question, get the "Working on it" acknowledgement, then the answer.
  • status, stop, new, more, ping and help behave identically on both engines.
  • A follow-up text continues the same conversation, and new starts a fresh one.
  • A signed-out Codex produces a reply that says so, not a generic failure.
  • uplink:doctor passes on a working Codex install and fails with the exact fix on a broken one.

Sign in to comment on this issue.