Documentation
¶
Overview ¶
Package codex implements agent.Adapter against OpenAI's `codex` CLI (the `codex exec` non-interactive subcommand). It mirrors agent/goose for the Launch/stream scaffold and agent/claude for the NativeSchema:true contract; the agent package itself stays import-free of harness specifics.
Capabilities().NativeSchema is TRUE: `codex exec --output-schema <FILE>` API-constrains the model's final response (OpenAI Responses structured output), so the agent_message text is pure conforming JSON. The adapter writes the step's output_schema to a container temp file (via the same sh -c command), passes --output-schema, and strict-json.Unmarshals the LAST agent_message text into a map; the engine re-validates it against output_schema (engine ValidateOutputMap). Layer 2 (free-text parsing) is never reached — codex is the first non-Claude NativeSchema:true adapter (conformance Bucket 14).
Gate independence is structural: every Launch runs a bare `codex exec` with --ephemeral and never a resume/fork subcommand; ValidateConfig rejects the corresponding with-keys.
Index ¶
- Constants
- Variables
- type Adapter
- func (*Adapter) Capabilities() agent.Caps
- func (a *Adapter) Launch(ctx context.Context, handle container.Handle, inv agent.AgentInvocation) (<-chan agent.AgentEvent, <-chan agent.AgentOutcome, error)
- func (*Adapter) Ref() string
- func (*Adapter) RequiredEnv() []string
- func (a *Adapter) ValidateConfig(with ir.RawConfig) error
- func (a *Adapter) Version(ctx context.Context, handle container.Handle) (string, error)
- type ErrRuntimeNotFound
- type ErrSessionReuseAttempted
- type ErrStreamParse
- type ErrUnexpectedExit
- type Option
Constants ¶
const AdapterRef = "openai/codex"
AdapterRef must match the workflow `uses:` literal byte-for-byte.
Variables ¶
var DefaultEnvAllowlist = []string{"OPENAI_API_KEY", "CODEX_HOME"}
DefaultEnvAllowlist — env names the codex adapter forwards into each `codex exec`. Single source of truth for the CLI --agent-env default and resume's implicit allowlist. OPENAI_API_KEY overlaps goose's allowlist → defaultAgentEnv dedups (see cli/agent_registry.go). CODEX_HOME points codex at its auth.json/config dir (the ChatGPT-OAuth provisioning path). The adapter forces no env (--ephemeral handles session persistence).
Functions ¶
This section is empty.
Types ¶
type Adapter ¶
type Adapter struct {
// contains filtered or unexported fields
}
Adapter is the agent.Adapter implementation for OpenAI codex. One Adapter per CLI invocation; multiple Launch calls per Adapter; all state read-only after construction (no session state crosses Launch boundaries).
func (*Adapter) Capabilities ¶
Capabilities returns Caps{NativeSchema: true} — codex's --output-schema constrains generation API-side; layer 2 is never reached.
func (*Adapter) Launch ¶
func (a *Adapter) Launch(ctx context.Context, handle container.Handle, inv agent.AgentInvocation) (<-chan agent.AgentEvent, <-chan agent.AgentOutcome, error)
Launch runs `codex exec --json ...` inside handle via the streaming Backend.Exec. codex's --json emits JSONL flushed live; Launch scans it and emits ONE AgentEvent per line, records the LAST agent_message text (last-wins), captures the terminal usage + any error/turn.failed message, then sends exactly one AgentOutcome.
STREAMING GRANULARITY (accepted limitation): codex exec --json is EVENT-granular, not token-granular — each agent_message arrives as ONE complete item.completed line (no item.updated/text deltas). So events (tool calls, reasoning) render live, but the answer TEXT does NOT stream character-by-character like goose/claude. Token deltas exist only on codex's mcp-server/app-server JSON-RPC interface, which the stdin-less container.Cmd seam cannot drive. See the spec §1 streaming note.
γ contract: returns IMMEDIATELY with both channels open; events closes BEFORE outcome (defer LIFO); never reuses a session.
func (*Adapter) RequiredEnv ¶
RequiredEnv implements agent.CredentialNamer. Returns the CREDENTIAL env var name codex authenticates with. OPENAI_API_KEY is defined in DefaultEnvAllowlist; CODEX_HOME is a config directory (not a credential) and is intentionally excluded.
func (*Adapter) ValidateConfig ¶
ValidateConfig enforces codex's with-schema (session-reject → unknown-key → required-prompt → per-key types+enums). Deterministic (sorted keys); runs twice per node (run-start walk + defensive dispatch) → idempotent. No provider-conditional auth gate: codex auth (ChatGPT-OAuth or OPENAI_API_KEY) cannot be statically probed — a missing credential surfaces as a loud turn.failed at Launch.
type ErrRuntimeNotFound ¶
ErrRuntimeNotFound — Version failed.
func (*ErrRuntimeNotFound) Error ¶
func (e *ErrRuntimeNotFound) Error() string
func (*ErrRuntimeNotFound) Unwrap ¶
func (e *ErrRuntimeNotFound) Unwrap() error
type ErrSessionReuseAttempted ¶
type ErrSessionReuseAttempted struct{ Key string }
ErrSessionReuseAttempted — a with-key that would re-use a codex session, breaking gate independence (spec §5.5). Defense-in-depth: the adapter always runs bare `codex exec`, never a resume/fork subcommand.
func (*ErrSessionReuseAttempted) Error ¶
func (e *ErrSessionReuseAttempted) Error() string
type ErrStreamParse ¶
ErrStreamParse backs the parse unit tests; Launch tolerates a stray non-JSON line.
func (*ErrStreamParse) Error ¶
func (e *ErrStreamParse) Error() string
func (*ErrStreamParse) Unwrap ¶
func (e *ErrStreamParse) Unwrap() error
type ErrUnexpectedExit ¶
ErrUnexpectedExit is sent (bare) when codex exited with no usable result. Output is the captured stdout/diag tail. The engine default branch maps it to retryable_failure.
func (*ErrUnexpectedExit) Error ¶
func (e *ErrUnexpectedExit) Error() string
type Option ¶
type Option func(*Adapter)
Option configures the Adapter at construction time (functional-options).
func WithBackend ¶
WithBackend supplies the container.Backend used by Version and Launch.
func WithEnv ¶
WithEnv supplies the env-var allowlist forwarded into each `codex exec` exec environment. Copied into agent.SecretEnv (redacts under fmt verbs; json:"-" so secrets never reach the state log). Empty map permitted.
func WithPricing ¶
WithPricing injects the pricing.Table used to derive a USD cost from token usage. Tests pass a self-contained fixture table; production leaves it unset so New defaults it to pricing.Default() (embedded rates ⊕ $AWF_PRICING_FILE).