codex_codes/lib.rs
1//! A typed Rust interface for the [OpenAI Codex CLI](https://github.com/openai/codex) protocol.
2//!
3//! This crate provides type-safe bindings for communicating with the Codex CLI's
4//! app-server via its JSON-RPC protocol. It handles message framing, request/response
5//! correlation, approval flows, and streaming notifications for multi-turn agent
6//! conversations.
7//!
8//! # Quick Start
9//!
10//! ```bash
11//! cargo add codex-codes
12//! ```
13//!
14//! ## Using the Async Client (Recommended)
15//!
16//! ```ignore
17//! use codex_codes::{AsyncClient, ThreadStartParams, TurnStartParams, UserInput, ServerMessage};
18//!
19//! #[tokio::main]
20//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
21//! // Start the app-server (spawns `codex app-server --listen stdio://`)
22//! let mut client = AsyncClient::start().await?;
23//!
24//! // Create a thread (a conversation session)
25//! let thread = client.thread_start(&ThreadStartParams::default()).await?;
26//!
27//! // Send a turn (a user message that triggers an agent response)
28//! client.turn_start(&TurnStartParams {
29//! thread_id: thread.thread_id.clone(),
30//! input: vec![UserInput::Text { text: "What is 2 + 2?".into() }],
31//! model: None,
32//! reasoning_effort: None,
33//! sandbox_policy: None,
34//! }).await?;
35//!
36//! // Stream notifications until the turn completes
37//! while let Some(msg) = client.next_message().await? {
38//! match msg {
39//! ServerMessage::Notification { method, params } => {
40//! if method == "turn/completed" { break; }
41//! }
42//! ServerMessage::Request { id, .. } => {
43//! // Approval request — auto-accept for this example
44//! client.respond(id, &serde_json::json!({"decision": "accept"})).await?;
45//! }
46//! }
47//! }
48//!
49//! client.shutdown().await?;
50//! Ok(())
51//! }
52//! ```
53//!
54//! ## Using the Sync Client
55//!
56//! ```ignore
57//! use codex_codes::{SyncClient, ThreadStartParams, TurnStartParams, UserInput, ServerMessage};
58//!
59//! fn main() -> Result<(), Box<dyn std::error::Error>> {
60//! let mut client = SyncClient::start()?;
61//! let thread = client.thread_start(&ThreadStartParams::default())?;
62//!
63//! client.turn_start(&TurnStartParams {
64//! thread_id: thread.thread_id.clone(),
65//! input: vec![UserInput::Text { text: "What is 2 + 2?".into() }],
66//! model: None,
67//! reasoning_effort: None,
68//! sandbox_policy: None,
69//! })?;
70//!
71//! for result in client.events() {
72//! match result? {
73//! ServerMessage::Notification { method, .. } => {
74//! if method == "turn/completed" { break; }
75//! }
76//! _ => {}
77//! }
78//! }
79//! Ok(())
80//! }
81//! ```
82//!
83//! # Architecture
84//!
85//! The crate is organized into several key modules:
86//!
87//! - [`client_async`] / [`client_sync`] — High-level clients that manage the
88//! app-server process, request/response correlation, and message buffering
89//! - [`protocol`] — App-server v2 request params, response types, and notification
90//! bodies (thread/turn lifecycle, approvals, deltas)
91//! - [`jsonrpc`] — Low-level JSON-RPC message types (request, response, error,
92//! notification) matching the app-server's wire format
93//! - [`cli`] — Builder for spawning `codex app-server --listen stdio://`
94//! - [`error`] — Error types and result aliases
95//! - [`version`] — Version compatibility checking against the installed CLI
96//!
97//! # Protocol Overview
98//!
99//! The Codex app-server communicates via newline-delimited JSON-RPC 2.0 over stdio
100//! (without the standard `"jsonrpc":"2.0"` field). The conversation lifecycle is:
101//!
102//! 1. **Start a thread** — `thread/start` creates a conversation session
103//! 2. **Start a turn** — `turn/start` sends user input, triggering agent work
104//! 3. **Stream notifications** — The server emits `item/agentMessage/delta`,
105//! `item/commandExecution/outputDelta`, etc. as the agent works
106//! 4. **Handle approvals** — The server may send requests like
107//! `item/commandExecution/requestApproval` that require a response
108//! 5. **Turn completes** — `turn/completed` signals the agent is done
109//! 6. **Repeat** — Send another `turn/start` for follow-up questions
110//!
111//! # Feature Flags
112//!
113//! | Feature | Description | WASM-compatible |
114//! |---------|-------------|-----------------|
115//! | `types` | Core message types and protocol structs only | Yes |
116//! | `sync-client` | Synchronous client with blocking I/O | No |
117//! | `async-client` | Asynchronous client using tokio | No |
118//!
119//! All features are enabled by default. For WASM or type-sharing use cases:
120//!
121//! ```toml
122//! [dependencies]
123//! codex-codes = { version = "0.100", default-features = false, features = ["types"] }
124//! ```
125//!
126//! # Version Compatibility
127//!
128//! The Codex CLI protocol is evolving. This crate automatically checks your
129//! installed CLI version and warns if it's newer than tested. Current tested
130//! version: **0.104.0**
131//!
132//! Report compatibility issues at: <https://github.com/meawoppl/rust-code-agent-sdks/issues>
133//!
134//! # Examples
135//!
136//! See the `examples/` directory for complete working examples:
137//! - `async_client.rs` — Single-turn async query with streaming deltas
138//! - `sync_client.rs` — Single-turn synchronous query
139//! - `basic_repl.rs` — Interactive REPL with multi-turn conversation and approval handling
140//!
141//! # Parsing Raw Protocol Messages
142//!
143//! ```
144//! use codex_codes::{ThreadEvent, ThreadItem, JsonRpcMessage};
145//!
146//! // Parse exec-format JSONL events
147//! let json = r#"{"type":"thread.started","thread_id":"th_abc"}"#;
148//! let event: ThreadEvent = serde_json::from_str(json).unwrap();
149//!
150//! // Parse app-server JSON-RPC messages
151//! let rpc = r#"{"id":1,"result":{"threadId":"th_abc"}}"#;
152//! let msg: JsonRpcMessage = serde_json::from_str(rpc).unwrap();
153//! ```
154
155mod io;
156
157pub mod error;
158pub mod jsonrpc;
159pub mod protocol;
160
161#[cfg(any(feature = "sync-client", feature = "async-client"))]
162pub mod cli;
163
164#[cfg(any(feature = "sync-client", feature = "async-client"))]
165pub mod version;
166
167#[cfg(feature = "sync-client")]
168pub mod client_sync;
169
170#[cfg(feature = "async-client")]
171pub mod client_async;
172
173// Exec-level event types (JSONL protocol)
174pub use io::events::{
175 ItemCompletedEvent, ItemStartedEvent, ItemUpdatedEvent, ThreadError, ThreadErrorEvent,
176 ThreadEvent, ThreadStartedEvent, TurnCompletedEvent, TurnFailedEvent, TurnStartedEvent, Usage,
177};
178
179// Thread item types (shared between exec and app-server)
180pub use io::items::{
181 AgentMessageItem, CommandExecutionItem, CommandExecutionStatus, ErrorItem, FileChangeItem,
182 FileUpdateChange, McpToolCallError, McpToolCallItem, McpToolCallResult, McpToolCallStatus,
183 PatchApplyStatus, PatchChangeKind, ReasoningItem, ThreadItem, TodoItem, TodoListItem,
184 WebSearchItem,
185};
186
187// Configuration types
188pub use io::options::{
189 ApprovalMode, ModelReasoningEffort, SandboxMode, ThreadOptions, WebSearchMode,
190};
191
192// Error types (always available)
193pub use error::{Error, Result};
194
195// JSON-RPC types (always available)
196pub use jsonrpc::{
197 JsonRpcError, JsonRpcErrorData, JsonRpcMessage, JsonRpcNotification, JsonRpcRequest,
198 JsonRpcResponse, RequestId,
199};
200
201// App-server protocol types (always available)
202pub use protocol::{
203 AgentMessageDeltaNotification, CmdOutputDeltaNotification, CommandApprovalDecision,
204 CommandExecutionApprovalParams, CommandExecutionApprovalResponse, ErrorNotification,
205 FileChangeApprovalDecision, FileChangeApprovalParams, FileChangeApprovalResponse,
206 FileChangeOutputDeltaNotification, ItemCompletedNotification, ItemStartedNotification,
207 ReasoningDeltaNotification, ServerMessage, ThreadArchiveParams, ThreadArchiveResponse,
208 ThreadStartParams, ThreadStartResponse, ThreadStartedNotification, ThreadStatus,
209 ThreadStatusChangedNotification, ThreadTokenUsageUpdatedNotification, TokenUsage, Turn,
210 TurnCompletedNotification, TurnError, TurnInterruptParams, TurnInterruptResponse,
211 TurnStartParams, TurnStartResponse, TurnStartedNotification, TurnStatus, UserInput,
212};
213
214// CLI builder (feature-gated)
215#[cfg(any(feature = "sync-client", feature = "async-client"))]
216pub use cli::AppServerBuilder;
217
218// Sync client
219#[cfg(feature = "sync-client")]
220pub use client_sync::{EventIterator, SyncClient};
221
222// Async client
223#[cfg(feature = "async-client")]
224pub use client_async::{AsyncClient, EventStream};