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