Skip to main content

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().to_string(),
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().to_string(),
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. **Initialize** — `initialize` + `initialized` handshake (handled automatically by `start()`)
103//! 2. **Start a thread** — `thread/start` creates a conversation session
104//! 3. **Start a turn** — `turn/start` sends user input, triggering agent work
105//! 4. **Stream notifications** — The server emits `item/agentMessage/delta`,
106//!    `item/commandExecution/outputDelta`, etc. as the agent works
107//! 5. **Handle approvals** — The server may send requests like
108//!    `item/commandExecution/requestApproval` that require a response
109//! 6. **Turn completes** — `turn/completed` signals the agent is done
110//! 7. **Repeat** — Send another `turn/start` for follow-up questions
111//!
112//! # Feature Flags
113//!
114//! | Feature | Description | WASM-compatible |
115//! |---------|-------------|-----------------|
116//! | `types` | Core message types and protocol structs only | Yes |
117//! | `sync-client` | Synchronous client with blocking I/O | No |
118//! | `async-client` | Asynchronous client using tokio | No |
119//!
120//! All features are enabled by default. For WASM or type-sharing use cases:
121//!
122//! ```toml
123//! [dependencies]
124//! codex-codes = { version = "0.100", default-features = false, features = ["types"] }
125//! ```
126//!
127//! # Version Compatibility
128//!
129//! The Codex CLI protocol is evolving. This crate automatically checks your
130//! installed CLI version and warns if it's newer than tested. Current tested
131//! version: **0.104.0**
132//!
133//! Report compatibility issues at: <https://github.com/meawoppl/rust-code-agent-sdks/issues>
134//!
135//! # Examples
136//!
137//! See the `examples/` directory for complete working examples:
138//! - `async_client.rs` — Single-turn async query with streaming deltas
139//! - `sync_client.rs` — Single-turn synchronous query
140//! - `basic_repl.rs` — Interactive REPL with multi-turn conversation and approval handling
141//!
142//! # Parsing Raw Protocol Messages
143//!
144//! ```
145//! use codex_codes::{ThreadEvent, ThreadItem, JsonRpcMessage};
146//!
147//! // Parse exec-format JSONL events
148//! let json = r#"{"type":"thread.started","thread_id":"th_abc"}"#;
149//! let event: ThreadEvent = serde_json::from_str(json).unwrap();
150//!
151//! // Parse app-server JSON-RPC messages
152//! let rpc = r#"{"id":1,"result":{"threadId":"th_abc"}}"#;
153//! let msg: JsonRpcMessage = serde_json::from_str(rpc).unwrap();
154//! ```
155
156mod io;
157
158pub mod error;
159pub mod jsonrpc;
160pub mod protocol;
161
162#[cfg(any(feature = "sync-client", feature = "async-client"))]
163pub mod cli;
164
165#[cfg(any(feature = "sync-client", feature = "async-client"))]
166pub mod version;
167
168#[cfg(feature = "sync-client")]
169pub mod client_sync;
170
171#[cfg(feature = "async-client")]
172pub mod client_async;
173
174// Exec-level event types (JSONL protocol)
175pub use io::events::{
176    ItemCompletedEvent, ItemStartedEvent, ItemUpdatedEvent, ThreadError, ThreadErrorEvent,
177    ThreadEvent, ThreadStartedEvent, TurnCompletedEvent, TurnFailedEvent, TurnStartedEvent, Usage,
178};
179
180// Thread item types (shared between exec and app-server)
181pub use io::items::{
182    AgentMessageItem, CommandExecutionItem, CommandExecutionStatus, ErrorItem, FileChangeItem,
183    FileUpdateChange, McpToolCallError, McpToolCallItem, McpToolCallResult, McpToolCallStatus,
184    PatchApplyStatus, PatchChangeKind, ReasoningItem, ThreadItem, TodoItem, TodoListItem,
185    WebSearchItem,
186};
187
188// Configuration types
189pub use io::options::{
190    ApprovalMode, ModelReasoningEffort, SandboxMode, ThreadOptions, WebSearchMode,
191};
192
193// Error types (always available)
194pub use error::{Error, Result};
195
196// JSON-RPC types (always available)
197pub use jsonrpc::{
198    JsonRpcError, JsonRpcErrorData, JsonRpcMessage, JsonRpcNotification, JsonRpcRequest,
199    JsonRpcResponse, RequestId,
200};
201
202// App-server protocol types (always available)
203pub use protocol::{
204    AgentMessageDeltaNotification, ClientInfo, CmdOutputDeltaNotification, CommandApprovalDecision,
205    CommandExecutionApprovalParams, CommandExecutionApprovalResponse, ErrorNotification,
206    FileChangeApprovalDecision, FileChangeApprovalParams, FileChangeApprovalResponse,
207    FileChangeOutputDeltaNotification, InitializeCapabilities, InitializeParams,
208    InitializeResponse, ItemCompletedNotification, ItemStartedNotification,
209    ReasoningDeltaNotification, ServerMessage, ThreadArchiveParams, ThreadArchiveResponse,
210    ThreadInfo, ThreadStartParams, ThreadStartResponse, ThreadStartedNotification, ThreadStatus,
211    ThreadStatusChangedNotification, ThreadTokenUsageUpdatedNotification, TokenUsage, Turn,
212    TurnCompletedNotification, TurnError, TurnInterruptParams, TurnInterruptResponse,
213    TurnStartParams, TurnStartResponse, TurnStartedNotification, TurnStatus, UserInput,
214};
215
216// CLI builder (feature-gated)
217#[cfg(any(feature = "sync-client", feature = "async-client"))]
218pub use cli::AppServerBuilder;
219
220// Sync client
221#[cfg(feature = "sync-client")]
222pub use client_sync::{EventIterator, SyncClient};
223
224// Async client
225#[cfg(feature = "async-client")]
226pub use client_async::{AsyncClient, EventStream};