Help us shape the future of Go! Take the 2026 Go Developer Survey

codex

package
v0.18.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 14, 2026 License: MIT Imports: 31 Imported by: 0

README

codex

ACP bridge for the Codex CLI. Spawn launches codex app-server and translates its JSON-RPC notifications into ACP session updates.

Protocol reference

Codex App Server

The app-server surface is what this package speaks: initialize, thread/start, turn/start, and the item/* and turn/* notification streams handled in events.go.

ACP versions

This adapter implements ACP v1 using acp-go-sdk v0.13.5. A client requesting v2 receives protocolVersion: 1 and can continue with v1 or disconnect, as required by version negotiation. Codex's App Server API also has types named v2; that is a separate protocol and does not indicate ACP v2 support.

The ACP v2 draft was reviewed on 2026-09-07. It needs more than a version-number change:

Surface Implemented v1 Draft v2
Prompt completion Pending request returns stopReason Immediate acknowledgement; state_update reports completion
Initialization agentInfo, agentCapabilities info, capabilities with a different structure
Tools and permissions tool_call, tool_call_update, permission toolCall Tool upserts, permission title and subject
History session/load replays history session/resume with replayFrom

Keep one implementation while the Go SDK targets v1. Native v2 support should migrate the connection surface together with these lifecycle changes when SDK support is available. The wire tests exercise both a v1 initialization and a v2 initialization falling back to v1, including both cancellation methods.

Connection reliability

Each turn has an ordered, bounded event queue. The app-server reader only routes events, so a client update or permission dialog cannot block RPC replies. Events and approval requests with a turn ID must match the active turn, including events that arrive before the turn/start reply. Replaced sessions cannot remove the replacement's handlers during cancellation cleanup.

Command output is buffered and sent once when the command completes. Each command retains at most the last 1 MiB, with a visible truncation marker. The same limit applies to command output replayed from history. This follows the scratch Codex adapter's fixes for quadratic output growth (4e5cffb, 8aef91b).

RPC acknowledgements have a 30-second deadline; interrupts have a 2-second deadline. A timed-out start or interrupt retires the backend connection because the adapter cannot establish which work is still running. Stalled client writes fail after 10 seconds and close the stdio output. These limits apply to transport operations; model turns and user approval waits have no fixed overall deadline. Backend EOF, framing errors, malformed completion payloads, exhausted retries, and failed turns become errors. Retrying error events keep the turn open, and already-received completion events survive EOF. Process exit is also monitored independently of stdout, so a tool that inherits the pipe cannot keep a dead app-server session pending.

Codex answers no active turn to interrupt until a just-started turn becomes interruptible, so a cancellation retries turn/interrupt with a short bounded backoff instead of letting that turn run on. Cancellation releases active and cancelled queued prompts. Late permission responses cannot approve cancelled work, and steering leaves an open permission request intact. These checks apply the lifecycle lessons from the reference Codex cancellation fix (f67ca5f) and Claude's exact result attribution and JetBrains approval/steering fixes (a04d354, 8710ce1). The Codex app-server's turn/steer API already queues input without Claude's interrupting now delivery. The next prompt waits for cancellation cleanup, even when the previous start reply arrived after cancellation. Resolving a backend approval cancels only that request's dialog, using both its request ID and thread ID.

URL elicitation IDs are generated per interaction. Accepting a URL request means consent to open it; serverRequest/resolved does not prove that the external workflow finished. The adapter therefore omits the optional elicitation/complete notification instead of completing unrelated URL interactions. This follows the elicitation lifecycle. The pinned Go SDK still lacks top-level elicitation scope fields; session scope is currently carried in _meta.sessionId. Standard permission requests carry their normal top-level sessionId.

reliability_test.go, rpc_test.go, turn_stream_test.go, approval_lifecycle_test.go, and session_lifecycle_test.go cover these cases with local transports and a helper process. protocol_review_test.go and cancellation_order_test.go verify protocol envelopes and cancellation ordering. These tests do not make model requests or replace an IntelliJ integration test.

History and reference implementations

session/load reads all history before reporting success. Paginated Codex stores use thread/turns/list with full items, preserve chronological replay order, and reject repeated cursors. Legacy stores retain thread/read with includeTurns. Read failures are reported instead of silently returning an empty conversation. Resume and fork requests omit unnecessary history hydration. These changes follow the current adapter's history pagination fix. history_review_test.go covers both store formats and failure cases.

Codex writes a thread's rollout on its first user message. Resuming or loading a session that was never prompted falls back to thread/read and replays no history; deleting it, an unknown ID, or an ID Codex cannot parse succeeds.

The 2026-10-13 review compared the App Server adapter at a636804 (v2.2.2, pinned to Codex 0.160.1; Wingman launches 0.162.1) and classified all 18 commits since ca1d971. Prompt usage now covers every model request of the prompt, including later backend turns such as plan implementation (95bc33f): it is the thread total at the end minus the total at the start, or the sum of the distinct per-request counts when the start is unknown or the total moves back. A new thread starts at zero; the RPC reader retains restored thread totals and interrupt-cleanup updates even without an active prompt consumer. Compaction also counts as model work, including before ordinary output on a resumed thread. Synthetic context estimates and context-full markers are excluded from prompt spend; they still update the baseline used to identify the next model request. Cache writes are separated from fresh input and reported as cachedWriteTokens. usage_test.go covers these cases, and TestLiveCodexPromptUsageCoversEveryRequest checks a tool-using prompt against the installed CLI. Whitespace-only session titles and the deleted-workspace error hint (90fea43, a636804) remain low-priority differences.

The 2026-10-05 review compared scratch Codex at 7c2ce90716, the retired scratch Codex adapter at 296069e, Claude ACP at 0724b17, and the current App Server adapter at ca1d971. The scratch Codex adapter has moved development to that App Server adapter. Wingman already consumes consolidated aggregatedOutput and accepts developer input messages for base instructions. The review added readable service errors, an actionable thread_active_writer error on resume/load, and imported attachment replay. Desktop attachment envelopes become resource links with the original request text; duplicate native references are omitted, while unfamiliar envelopes remain verbatim. File mentions and local/remote audio also remain visible. upstream_review_test.go and attachments_test.go cover these fixes. Upstream session notices and compaction_update need session update variants and client capabilities that acp-go-sdk v0.13.5 does not model, so Codex advisories and compactions keep their text and tool-call presentation. Terminal output metadata is not implemented here.

Extensions

  • Tool calls name Codex's model-facing tool in _meta.codex.toolName (exec_command, write_stdin, view_image, request_permissions, or a namespaced dynamic tool). ACP v1 has no tool name field.
  • _meta.mcpStartupAwaitTimeoutMs on session/new, session/resume, and session/fork waits up to that many milliseconds for the requested MCP servers in that session to report ready, failed, or cancelled.
  • Codex request_user_input questions become one form elicitation for clients with form support; other clients return no answers. An "Other" question gets a "None of the above" choice and a note field whose answer is sent as user_note: ….
  • Authentication failures (unauthorized or HTTP 401) end the prompt with ACP auth_required instead of a chat message.

Plans

This bridge currently does not forward turn/plan/updated task lists as ACP v1 plan notifications. A completed Codex plan item is surfaced as agent text and offered to the client via requestPlanImplementation. Approving implementation starts a separate backend turn with its own event and approval lifecycle.

Live regression tests

live_regressions_test.go drives the installed Codex app-server through ACP without making model requests. It checks that an unprompted session retains Plan mode on resume and load, and that an MCP startup wait only completes for its own session. The MCP test starts two real stdio servers with independently controlled handshakes and observes the CLI's startup notifications.

Run from the repository root:

CODEX_ACP_LIVE=1 go test ./pkg/acp/codex -run '^TestLiveCodex(UnpromptedPlanMode|MCPStartupSessionIsolation)$' -count=1 -v -timeout=2m

The live usage tests compare prompt usage with the sum of distinct model requests reported by the CLI. They cover a tool-using prompt, a follow-up after cancellation, and a resumed thread forced to compact before ordinary output:

CODEX_ACP_LIVE=1 go test ./pkg/acp/codex -run '^TestLiveCodex(PromptUsageCoversEveryRequest|UsageAfterCancellation|UsageWithInitialCompaction)$' -count=1 -v -timeout=8m

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Run

func Run(ctx context.Context, opts Options, in io.Reader, out io.Writer, logger *slog.Logger) error

Types

type Agent

type Agent struct {
	// contains filtered or unexported fields
}

func Spawn

func Spawn(ctx context.Context, opts Options) (*Agent, error)

func (*Agent) Cancel

func (a *Agent) Cancel(ctx context.Context, params acp.CancelNotification) error

func (*Agent) Close

func (a *Agent) Close() error

func (*Agent) CloseSession

func (a *Agent) CloseSession(ctx context.Context, params acp.CloseSessionRequest) (acp.CloseSessionResponse, error)

func (*Agent) Done

func (a *Agent) Done() <-chan struct{}

func (*Agent) Initialize

func (*Agent) ListSessions

func (a *Agent) ListSessions(ctx context.Context, params acp.ListSessionsRequest) (acp.ListSessionsResponse, error)

func (*Agent) LoadSession

func (a *Agent) LoadSession(ctx context.Context, params acp.LoadSessionRequest) (acp.LoadSessionResponse, error)

func (*Agent) Logout added in v0.8.5

func (*Agent) NewSession

func (a *Agent) NewSession(ctx context.Context, params acp.NewSessionRequest) (acp.NewSessionResponse, error)

func (*Agent) Prompt

func (a *Agent) Prompt(ctx context.Context, params acp.PromptRequest) (acp.PromptResponse, error)

func (*Agent) ResumeSession

func (a *Agent) ResumeSession(ctx context.Context, params acp.ResumeSessionRequest) (acp.ResumeSessionResponse, error)

func (*Agent) SetAgentConnection

func (a *Agent) SetAgentConnection(conn *acp.AgentSideConnection)

func (*Agent) SetSessionMode

func (*Agent) Steer added in v0.10.9

func (a *Agent) Steer(ctx context.Context, sessionID acp.SessionId, prompt []acp.ContentBlock, messageID string) error

func (*Agent) UnstableDeleteSession added in v0.8.5

func (*Agent) UnstableForkSession added in v0.15.8

type Options

type Options struct {
	Model string

	Effort string

	Path string
	Dir  string
	Env  []string

	ExtraArgs []string

	Stderr io.Writer
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL