codex

package
v0.0.45 Latest Latest
Warning

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

Go to latest
Published: Jul 17, 2026 License: MIT Imports: 13 Imported by: 0

Documentation

Overview

Package codex implements a minimal client for the Codex CLI's app-server, which speaks JSON-RPC 2.0 over newline-delimited JSON on stdin/stdout.

This is deliberately not ACP: the Codex app-server has its own protocol (initialize → thread/start → turn/start → notifications → turn/completed), so it cannot reuse the coder/acp-go-sdk plumbing in internal/acp. Only the methods pi-go needs for a one-shot subagent turn are implemented; the rest are noted as TODOs below.

Index

Constants

View Source
const (
	MethodInitialize    = "initialize"
	MethodInitialized   = "initialized"
	MethodThreadStart   = "thread/start"
	MethodTurnStart     = "turn/start"
	MethodReviewStart   = "review/start"
	MethodTurnInterrupt = "turn/interrupt"
)

JSON-RPC methods sent by the client.

View Source
const (
	NotifyThreadStarted = "thread/started"
	NotifyTurnStarted   = "turn/started"
	NotifyItemStarted   = "item/started"
	NotifyItemCompleted = "item/completed"
	NotifyTurnCompleted = "turn/completed"
	NotifyError         = "error"
)

Notification methods sent by the app-server.

View Source
const (
	SandboxReadOnly       = "read-only"
	SandboxWorkspaceWrite = "workspace-write"
)

Sandbox modes accepted by thread/start.

View Source
const (
	TurnInProgress  = "inProgress"
	TurnCompleted   = "completed"
	TurnInterrupted = "interrupted"
	TurnFailed      = "failed"
)

Turn statuses reported on the turn/completed notification.

View Source
const (
	ItemAgentMessage     = "agentMessage"
	ItemReasoning        = "reasoning"
	ItemCommandExecution = "commandExecution"
	ItemFileChange       = "fileChange"
	ItemMCPToolCall      = "mcpToolCall"
	ItemDynamicToolCall  = "dynamicToolCall"
	ItemWebSearch        = "webSearch"
	ItemExitedReviewMode = "exitedReviewMode"
)

Item types of the ThreadItem discriminated union.

View Source
const (
	StatusSuccess = "success"
	StatusError   = "error"
)

Result statuses, mirroring internal/acp so the subagent dispatcher can treat codex and ACP results the same way.

View Source
const (
	EventTypeMessage  = "message"  // agent text output
	EventTypeProgress = "progress" // reasoning / thinking
	EventTypeTool     = "tool"     // command execution, file changes, tool calls
	EventTypeStderr   = "stderr"   // one line of app-server stderr
	EventTypeError    = "error"    // server-reported error
)

Event types emitted by a Session.

View Source
const ApprovalNever = "never"

ApprovalNever runs the thread autonomously: the app-server never asks the client to approve a command or edit. Subagents are unattended, so this is the only policy pi-go uses.

View Source
const BinaryName = "codex"

BinaryName is the expected name of the Codex CLI binary.

View Source
const EnvCodexCmd = "PI_CODEX_CMD"

EnvCodexCmd overrides the codex command. Format: "binary arg1 arg2 ..." or a bare "binary", in which case DefaultArgs are appended.

View Source
const PhaseFinalAnswer = "final_answer"

PhaseFinalAnswer marks the agentMessage item that carries the turn's answer, as opposed to intermediate analysis chatter.

View Source
const ReviewDeliveryInline = "inline"

ReviewDeliveryInline streams review findings as ordinary thread items rather than delivering them as a separate artifact.

View Source
const ReviewTargetUncommitted = "uncommittedChanges"

ReviewTargetUncommitted reviews the working tree's uncommitted changes.

Variables

View Source
var DefaultArgs = []string{"app-server"}

DefaultArgs puts the codex CLI into app-server (JSON-RPC over stdio) mode.

View Source
var DefaultBinaryPaths = []string{
	"codex",
	".local/bin/codex",
	"/usr/local/bin/codex",
	"/usr/bin/codex",
	"/opt/homebrew/bin/codex",
}

DefaultBinaryPaths lists common installation locations for the codex CLI.

Functions

This section is empty.

Types

type Client

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

Client wraps a `codex app-server` subprocess speaking JSON-RPC 2.0 over newline-delimited JSON on stdin/stdout.

A single reader goroutine owns stdout: it routes responses to the pending request that carries the same ID and forwards notifications to the channel returned by notifications(). A second goroutine drains stderr, and a third reaps the process once both are done.

func NewClient

func NewClient(ctx context.Context, opts ClientOpts) (*Client, error)

NewClient spawns `codex app-server`, performs the initialize handshake and sends the initialized notification. The returned Client is ready for thread/start.

type ClientInfo

type ClientInfo struct {
	Title   string `json:"title"`
	Name    string `json:"name"`
	Version string `json:"version"`
}

ClientInfo describes the client to the app-server.

type ClientOpts

type ClientOpts struct {
	CWD     string   // working directory of the subprocess
	Env     []string // full environment for the subprocess (not merged with os.Environ)
	Command []string // command override; when set, used verbatim (tests)
}

ClientOpts configures the app-server subprocess.

type ErrorParams

type ErrorParams struct {
	Error RPCError `json:"error"`
}

ErrorParams is the payload of the error notification.

type Event

type Event struct {
	Type      string `json:"type"`
	Content   string `json:"content,omitempty"`
	Error     string `json:"error,omitempty"`
	SessionID string `json:"session_id,omitempty"` // codex thread ID
}

Event is one streamed update from a codex turn.

type FileChange

type FileChange struct {
	Path string `json:"path"`
}

FileChange is one path touched by a fileChange item.

type InitializeCaps

type InitializeCaps struct {
	ExperimentalAPI           bool     `json:"experimentalApi"`
	RequestAttestation        bool     `json:"requestAttestation"`
	OptOutNotificationMethods []string `json:"optOutNotificationMethods"`
}

InitializeCaps declares which optional protocol features the client wants.

type InitializeParams

type InitializeParams struct {
	ClientInfo   ClientInfo     `json:"clientInfo"`
	Capabilities InitializeCaps `json:"capabilities"`
}

InitializeParams identifies pi-go to the app-server and opts out of the high-frequency delta notifications: pi-go renders whole items, so streaming per-token deltas would only add churn on the notification channel.

type InitializeResponse

type InitializeResponse struct {
	UserAgent string `json:"userAgent"`
}

InitializeResponse is the app-server's reply to initialize.

type Item

type Item struct {
	Type     string       `json:"type"`
	ID       string       `json:"id"`
	Text     string       `json:"text,omitempty"`     // agentMessage
	Phase    string       `json:"phase,omitempty"`    // agentMessage: "final_answer" | "analysis"
	Command  string       `json:"command,omitempty"`  // commandExecution
	Status   string       `json:"status,omitempty"`   // commandExecution, fileChange, mcpToolCall
	ExitCode *int         `json:"exitCode,omitempty"` // commandExecution
	Tool     string       `json:"tool,omitempty"`     // mcpToolCall, dynamicToolCall
	Server   string       `json:"server,omitempty"`   // mcpToolCall
	Query    string       `json:"query,omitempty"`    // webSearch
	Review   string       `json:"review,omitempty"`   // exitedReviewMode
	Summary  []string     `json:"summary,omitempty"`  // reasoning
	Changes  []FileChange `json:"changes,omitempty"`  // fileChange
}

Item is a partially-typed ThreadItem. Only the fields pi-go renders are decoded; the discriminator is Type.

type ItemParams

type ItemParams struct {
	ThreadID string `json:"threadId"`
	TurnID   string `json:"turnId"`
	Item     Item   `json:"item"`
}

ItemParams is the payload of the item/started and item/completed notifications.

type JSONRPCNotification

type JSONRPCNotification struct {
	JSONRPC string          `json:"jsonrpc"`
	Method  string          `json:"method"`
	Params  json.RawMessage `json:"params"`
}

JSONRPCNotification is a JSON-RPC 2.0 notification: a method call with no ID and therefore no response.

type JSONRPCRequest

type JSONRPCRequest struct {
	JSONRPC string `json:"jsonrpc"`
	ID      int    `json:"id"`
	Method  string `json:"method"`
	Params  any    `json:"params"`
}

JSONRPCRequest is a client→server JSON-RPC 2.0 request.

type JSONRPCResponse

type JSONRPCResponse struct {
	JSONRPC string          `json:"jsonrpc"`
	ID      int             `json:"id"`
	Result  json.RawMessage `json:"result,omitempty"`
	Error   *RPCError       `json:"error,omitempty"`
}

JSONRPCResponse is a server→client response to a request, matched by ID.

type RPCError

type RPCError struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
}

RPCError is the JSON-RPC error object.

func (*RPCError) Error

func (e *RPCError) Error() string

Error implements error so an RPC failure can be returned directly.

type ReviewStartParams

type ReviewStartParams struct {
	ThreadID string       `json:"threadId"`
	Delivery string       `json:"delivery"`
	Target   ReviewTarget `json:"target"`
}

ReviewStartParams starts a code review turn instead of a chat turn.

type ReviewStartResponse

type ReviewStartResponse struct {
	Turn Turn `json:"turn"`
}

ReviewStartResponse carries the turn created by review/start.

type ReviewTarget

type ReviewTarget struct {
	Type string `json:"type"`
}

ReviewTarget selects what the review covers.

type RunResult

type RunResult struct {
	Status     string `json:"status"`
	Result     string `json:"result,omitempty"`
	Error      string `json:"error,omitempty"`
	SessionID  string `json:"session_id,omitempty"`  // codex thread ID
	Stderr     string `json:"stderr,omitempty"`      // captured app-server stderr
	StopReason string `json:"stop_reason,omitempty"` // turn status: completed/interrupted/failed
}

RunResult is the terminal outcome of a codex turn.

type Session

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

Session runs one turn (or review) against a `codex app-server` subprocess and streams the result back as Events.

All events are emitted by a single goroutine (loop), which also owns the terminal transition, so the events channel is closed exactly once and never written to afterwards.

func NewSession

func NewSession(ctx context.Context, opts SessionOpts) (*Session, error)

NewSession spawns the app-server, opens a thread and starts the turn.

The notification handler is running before turn/start is sent: codex emits turn/started (and can emit the first items) as soon as the request lands, so a handler started afterwards would miss them.

func (*Session) Cancel

func (s *Session) Cancel() error

Cancel interrupts the running turn and terminates the app-server.

The interrupt is sent first so codex gets a chance to unwind a running command, but Cancel does not wait for it to be acknowledged: the caller may hold a lock, and the kill that follows is the real backstop. The loop observes the client closing and finishes the session.

func (*Session) Done

func (s *Session) Done() <-chan struct{}

Done is closed when the turn ends.

func (*Session) Events

func (s *Session) Events() <-chan Event

Events returns the streaming event channel. It is closed when the turn ends.

func (*Session) TurnID

func (s *Session) TurnID() string

TurnID returns the ID of the in-flight turn, or "" before turn/start returns.

func (*Session) Wait

func (s *Session) Wait() RunResult

Wait blocks until the turn ends and returns its result.

type SessionOpts

type SessionOpts struct {
	CWD     string   // working directory for the thread
	Prompt  string   // task prompt (ignored when Review is set; review has no prompt)
	Sandbox string   // SandboxReadOnly or SandboxWorkspaceWrite
	Env     []string // full environment for the app-server subprocess
	Review  bool     // run review/start instead of turn/start
	Command []string // command override; used verbatim (tests)
}

SessionOpts configures a single codex turn.

type Thread

type Thread struct {
	ID string `json:"id"`
}

Thread is a codex conversation thread.

type ThreadStartParams

type ThreadStartParams struct {
	CWD            string  `json:"cwd"`
	Model          *string `json:"model"`
	ApprovalPolicy string  `json:"approvalPolicy"`
	Sandbox        string  `json:"sandbox"`
	ServiceName    string  `json:"serviceName"`
	Ephemeral      bool    `json:"ephemeral"`
}

ThreadStartParams opens a new thread rooted at CWD.

type ThreadStartResponse

type ThreadStartResponse struct {
	Thread Thread `json:"thread"`
}

ThreadStartResponse carries the ID of the newly created thread.

type Turn

type Turn struct {
	ID     string `json:"id"`
	Status string `json:"status"`
	Error  string `json:"error"`
}

Turn is a single agent turn within a thread.

type TurnCompletedParams

type TurnCompletedParams struct {
	ThreadID string `json:"threadId"`
	Turn     Turn   `json:"turn"`
}

TurnCompletedParams is the payload of the turn/completed notification. The thread ID matters: codex spawns child threads for collab/subagent work and their completions must not be mistaken for the outer turn finishing.

type TurnInterruptParams

type TurnInterruptParams struct {
	ThreadID string `json:"threadId"`
	TurnID   string `json:"turnId"`
}

TurnInterruptParams cancels an in-flight turn.

type TurnStartParams

type TurnStartParams struct {
	ThreadID string      `json:"threadId"`
	Input    []UserInput `json:"input"`
	Model    *string     `json:"model"`
	Effort   *string     `json:"effort"`
}

TurnStartParams sends user input to a thread and starts a turn.

type TurnStartResponse

type TurnStartResponse struct {
	Turn Turn `json:"turn"`
}

TurnStartResponse carries the turn created by turn/start.

type TurnStartedParams

type TurnStartedParams struct {
	ThreadID string `json:"threadId"`
	Turn     Turn   `json:"turn"`
}

TurnStartedParams is the payload of the turn/started notification.

type UserInput

type UserInput struct {
	Type         string `json:"type"`
	Text         string `json:"text"`
	TextElements []any  `json:"text_elements"`
}

UserInput is one input block of a turn. Only text input is used.

Jump to

Keyboard shortcuts

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