codex-cli-sdk 0.0.1

Rust SDK for the OpenAI Codex CLI
Documentation
use std::path::PathBuf;
use thiserror::Error;

/// All errors produced by codex-cli-sdk.
#[derive(Error, Debug)]
pub enum Error {
    #[error(
        "Codex CLI not found. Install via `npm i -g @openai/codex` or `brew install --cask codex`"
    )]
    CliNotFound,

    #[error("Codex CLI at {path} is not executable")]
    CliNotExecutable { path: PathBuf },

    #[error("Codex CLI version check failed: {0}")]
    VersionCheck(String),

    #[error("failed to spawn Codex CLI: {0}")]
    SpawnFailed(#[source] std::io::Error),

    #[error("Codex CLI exited with code {code}: {stderr}")]
    ProcessExited { code: i32, stderr: String },

    #[error("not connected — call connect() first")]
    NotConnected,

    #[error("already connected")]
    AlreadyConnected,

    #[error("transport closed unexpectedly")]
    TransportClosed,

    #[error("write to stdin failed: {0}")]
    WriteFailed(#[source] std::io::Error),

    #[error("read from stdout failed: {0}")]
    ReadFailed(#[source] std::io::Error),

    #[error("JSON parse error: {0}")]
    Json(#[from] serde_json::Error),

    #[error("unexpected event format: {message}")]
    UnexpectedEvent { message: String },

    #[error("timeout: {operation}")]
    Timeout { operation: String },

    #[error("invalid configuration: {0}")]
    Config(String),

    #[error("approval denied: {0}")]
    ApprovalDenied(String),

    #[error("concurrent turn in progress — wait for current turn to complete")]
    ConcurrentTurn,

    /// Thread not found on the CLI side.
    ///
    /// TODO: The CLI currently uses unstructured stderr for error reporting.
    /// Return this variant once the CLI adds structured error codes (e.g. a
    /// JSON `{"type":"error","code":"thread_not_found"}` event on stdout).
    #[error("thread not found: {0}")]
    ThreadNotFound(String),

    #[error("{0}")]
    Other(String),
}

/// Convenience alias.
pub type Result<T> = std::result::Result<T, Error>;

impl Error {
    /// Whether this error is transient and the operation could be retried.
    pub fn is_transient(&self) -> bool {
        matches!(self, Self::Timeout { .. } | Self::TransportClosed)
    }
}