codex-cli-sdk 0.0.1

Rust SDK for the OpenAI Codex CLI
Documentation
//! # codex-cli-sdk
//!
//! Rust SDK for the [OpenAI Codex CLI](https://github.com/openai/codex).
//!
//! This crate provides a programmatic interface to the Codex CLI by spawning
//! it as a subprocess and communicating via JSONL. It mirrors the API of the
//! official [TypeScript SDK](https://www.npmjs.com/package/@openai/codex-sdk).
//!
//! ## Quick Start
//!
//! ```rust,no_run
//! use codex_cli_sdk::{Codex, CodexConfig, ThreadOptions};
//!
//! #[tokio::main]
//! async fn main() -> codex_cli_sdk::Result<()> {
//!     let codex = Codex::new(CodexConfig::default())?;
//!     let mut thread = codex.start_thread(ThreadOptions::default());
//!     let turn = thread.run("List the files in this directory", Default::default()).await?;
//!     println!("{}", turn.final_response);
//!     Ok(())
//! }
//! ```
//!
//! ## Streaming
//!
//! ```rust,no_run
//! use codex_cli_sdk::{Codex, CodexConfig, ThreadOptions, ThreadEvent, ThreadItem};
//! use tokio_stream::StreamExt;
//!
//! #[tokio::main]
//! async fn main() -> codex_cli_sdk::Result<()> {
//!     let codex = Codex::new(CodexConfig::default())?;
//!     let mut thread = codex.start_thread(ThreadOptions::default());
//!     let mut stream = thread.run_streamed("Explain this codebase", Default::default()).await?;
//!
//!     while let Some(event) = stream.next().await {
//!         match event? {
//!             ThreadEvent::ItemUpdated { item: ThreadItem::AgentMessage { text, .. } } => {
//!                 print!("{}", text);
//!             }
//!             ThreadEvent::ItemStarted { item: ThreadItem::CommandExecution { command, .. } } => {
//!                 println!("\n> Running: {}", command);
//!             }
//!             ThreadEvent::TurnCompleted { usage } => {
//!                 println!("\n--- done ({} tokens) ---", usage.output_tokens);
//!             }
//!             _ => {}
//!         }
//!     }
//!     Ok(())
//! }
//! ```
//!
//! ## Resuming Threads
//!
//! ```rust,no_run
//! use codex_cli_sdk::{Codex, CodexConfig, ThreadOptions};
//!
//! #[tokio::main]
//! async fn main() -> codex_cli_sdk::Result<()> {
//!     let codex = Codex::new(CodexConfig::default())?;
//!
//!     // First turn
//!     let mut thread = codex.start_thread(ThreadOptions::default());
//!     let turn = thread.run("Create a hello.py file", Default::default()).await?;
//!     let thread_id = thread.id().unwrap();
//!
//!     // Resume later
//!     let mut thread = codex.resume_thread(&thread_id, ThreadOptions::default());
//!     let turn = thread.run("Now add error handling to hello.py", Default::default()).await?;
//!     Ok(())
//! }
//! ```
//!
//! ## Approval Handling
//!
//! ```rust,no_run
//! use codex_cli_sdk::{Codex, CodexConfig, ThreadOptions};
//! use codex_cli_sdk::permissions::{ApprovalCallback, ApprovalDecision};
//! use std::sync::Arc;
//!
//! #[tokio::main]
//! async fn main() -> codex_cli_sdk::Result<()> {
//!     let codex = Codex::new(CodexConfig::default())?;
//!     let options = ThreadOptions::builder()
//!         .approval(codex_cli_sdk::config::ApprovalPolicy::OnRequest)
//!         .build();
//!
//!     let callback: ApprovalCallback = Arc::new(|ctx| {
//!         Box::pin(async move {
//!             println!("Agent wants to run: {}", ctx.request.command);
//!             ApprovalDecision::Approved.into()
//!         })
//!     });
//!
//!     let mut thread = codex.start_thread(options)
//!         .with_approval_callback(callback);
//!     let turn = thread.run("Set up a new Rust project", Default::default()).await?;
//!     Ok(())
//! }
//! ```

pub mod callback;
pub mod client;
pub mod config;
pub(crate) mod discovery;
pub mod errors;
pub mod hooks;
pub mod mcp;
pub mod permissions;
pub(crate) mod transport;
pub mod types;

#[cfg(any(test, feature = "testing"))]
pub mod testing;

// ── Re-exports ─────────────────────────────────────────────────

pub use callback::{EventCallback, apply_callback};
pub use client::{Codex, Thread};

pub use config::{
    ApprovalPolicy, CodexConfig, ReasoningEffort, SandboxPolicy, ThreadOptions, TurnOptions,
    WebSearchMode,
};
pub use errors::{Error, Result};
pub use hooks::{
    HookCallback, HookContext, HookDecision, HookEvent, HookInput, HookMatcher, HookOutput,
    HookTimeoutBehavior,
};
pub use mcp::{McpServerConfig, McpServers};
pub use permissions::{
    ApprovalCallback, ApprovalContext, ApprovalDecision, ApprovalOutcome, PatchApprovalCallback,
    PatchApprovalContext,
};
#[cfg(any(test, feature = "testing"))]
pub use transport::Transport;
pub use types::events::{
    ApprovalRequestEvent, PatchApprovalRequestEvent, StreamedTurn, ThreadError, ThreadEvent, Turn,
    Usage,
};
pub use types::input::{Input, UserInput};
pub use types::items::ThreadItem;

// ── Top-level convenience functions ────────────────────────────

/// One-shot query: start a thread, run the prompt, return all events as a `Turn`.
pub async fn query(
    prompt: impl Into<String>,
    config: CodexConfig,
    options: ThreadOptions,
) -> Result<Turn> {
    let codex = Codex::new(config)?;
    let mut thread = codex.start_thread(options);
    let input = Input::Text(prompt.into());
    thread.run(input, Default::default()).await
}

/// Streaming query: start a thread, run the prompt, return event stream.
pub async fn query_stream(
    prompt: impl Into<String>,
    config: CodexConfig,
    options: ThreadOptions,
) -> Result<StreamedTurn> {
    let codex = Codex::new(config)?;
    let mut thread = codex.start_thread(options);
    let input = Input::Text(prompt.into());
    thread.run_streamed(input, Default::default()).await
}

/// Collect all events from a `StreamedTurn` into a `Vec<ThreadEvent>`.
pub async fn collect_stream(mut stream: StreamedTurn) -> Result<Vec<ThreadEvent>> {
    use tokio_stream::StreamExt;
    let mut events = Vec::new();
    while let Some(event) = stream.next().await {
        events.push(event?);
    }
    Ok(events)
}