Documentation
¶
Overview ¶
Package codex implements agentproxy.Facade for the codex CLI's app-server JSON-RPC protocol (github.com/openai/codex, codex-rs/app-server).
Package codex: raw event passthrough (v1.5) — the prefer-source_raw half of the translator strategy translate.go's doc comment reserved a seam for.
When the hosted session's agent runtime is codex itself (Facade.AgentKind), every canonical event the OHR adapter emitted carries the exact native app-server frame it was mapped from (HostedAgentEvent.SourceRaw — one JSON-RPC message as read off the codex app-server transport in the sandbox, requested via StreamSession's IncludeRaw). Forwarding that frame to the TUI beats reconstructing an approximation from canonical's ~15-event common denominator: canonical carries no file diffs, no command output, no reasoning deltas, no item metadata — the native frame carries all of it.
"Verbatim" has exactly one deliberate exception: identifier rewriting. The in-sandbox adapter drove its own thread/start and turn/start against the VM's app-server, so the raw params reference the VM's thread and turn ids — but this proxy's client was told the thread id is Facade.SessionID (thread/start's synthesized reply) and each turn's id is the harness run id (turn/start's synthesized reply). A frame forwarded with the VM's ids would describe a thread and turn the client has never heard of, so threadId/turnId (and turn.id) are rewritten to the proxy's ids. Native item ids are kept: the proxy never promises item ids in any reply, so the raw frames' own item lifecycle (started → deltas → completed, all sharing the native id) is self-consistent as-is.
The fallback is per event, as the v1.5 plan specifies: an event with no usable raw frame (adapter-synthesized events like a TurnError-mapped run.failed; a server that doesn't retain bytes; a non-codex session) takes translate.go's canonical reconstruction instead. The two paths keep their item-id families from colliding naturally: a turn whose run.started was forwarded raw never sets turnState.itemStarted, so finishTurn's synthesized agentMessage item/completed — the only canonical notification that could duplicate a raw item — stays suppressed for that turn.
This file also holds the inbound halves, which come in two shapes because the client sends two kinds of message.
Notifications and turns (v2): rawTurnStartFrame packages the TUI's turn/start params as SendInput's source_raw, so the in-sandbox adapter drives the turn from the client's actual params (input items, model, effort, approval policy) instead of a synthesized single-text turn.
Requests (M1): relayRequest forwards a client *request* — one that blocks waiting for an answer — to the agent and returns its reply. turn/start is deliberately not one of these: it already has a canonical meaning (SendInput) and a run to attribute events to, so relaying it around that would start a turn no one is tracking. Everything else this facade can't answer itself goes through, which is what makes turn/interrupt real and lets new codex methods work without a doctl release.
The direction is inverted but the id discipline is symmetric. Outbound rewrites VM ids to proxy ids. Inbound ships the proxy-side ids as-is and the adapter rewrites them to the VM's own — it is the only component that knows those. JSON-RPC request ids never cross a hop at all: each side mints its own and answers on the id it was given.
Package codex: --replay support, feeding a session's durable event history into the first thread this facade bootstraps.
Package codex: canonical-event-to-codex-notification translation.
This file is the "translator-strategy seam" the original implementation plan called for: the logic that decides what a canonical event means in codex terms lives here, not scattered inline in runEventLoop/drainStream's own dispatch loop (facade.go). As of v1.5 the seam carries two strategies: translateEvent first offers each event to tryRawPassthrough (raw.go), which forwards the event's native codex frame when one is available, and only reconstructs from canonical — everything below — when it isn't.
This is deliberately not a Go interface with multiple implementations: the raw strategy is one conditional at the top of translateEvent plus its own self-contained file, exactly the shape the v1 comment here reserved. The canonical reconstruction is not legacy — it is the permanent fallback (adapter-synthesized events carry no raw bytes; non-codex sessions never forward raw at all) and the only path that can ever serve cross-agent use.
Index ¶
Constants ¶
const TestedVersion = "0.142.5"
TestedVersion is the codex-cli version this facade's protocol handling was captured against (hosted-agents' docs/design/codex-app-server-protocol-capture.md: stdio send-message-v2 capture plus a live codex --remote / app-server pairing over WebSocket). Re-run that capture and update this pin on every codex upgrade — the WS/app-server transport is officially experimental and can change without notice.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Facade ¶
type Facade struct {
// SessionID is the hosted session this facade bridges to. thread/start's
// synthesized thread id is exactly this value — simplest possible way to
// satisfy "a synthesized thread whose id embeds the session id" while
// matching what the real capture showed too (thread.id == thread.sessionId
// there as well).
SessionID string
// Sessions is the harness bridge: turn/start calls SendInput on it, and
// the background streaming goroutine reads StreamSession's SSE events to
// translate into codex notifications. The bootstrap methods (initialize,
// thread/start, etc.) don't touch it.
Sessions do.HostedAgentsService
// AgentKind is the hosted session's agent runtime, from GetSession at
// proxy startup. Raw passthrough (v1.5) is gated on it: an event's
// native SourceRaw bytes are only forwarded when the session's agent
// speaks this facade's protocol (AGENT_KIND_CODEX_CLI) — cross-agent
// sessions (e.g. codex TUI driving an OpenCode session) always take the
// canonical-reconstruction path, where raw bytes would be the wrong
// protocol entirely. Empty (older CLI paths, tests that don't care)
// disables raw passthrough.
AgentKind godo.HostedAgentKind
// Replay, when true, feeds this session's full durable event history
// into the first thread this facade bootstraps before that thread would
// otherwise appear to start with no prior conversation — see
// replaySessionHistory. Set once at construction from the --replay CLI
// flag; never toggled per-connection.
Replay bool
// contains filtered or unexported fields
}
Facade implements agentproxy.Facade for codex --remote.
func (*Facade) AfterReply ¶
AfterReply implements agentproxy.AfterReply: once handleConn has written a successful thread/start or thread/resume result, start the deferred replaySessionHistory goroutine if maybeReplay armed one.
func (*Facade) SetNotifier ¶
func (f *Facade) SetNotifier(n agentproxy.Notifier)
SetNotifier implements agentproxy.NotifierAware.