- Errors: single enum mirroring the Python SDK's exception hierarchy (CliNotFound, CliConnection, Process, JsonDecode, MessageParse). Each Python class becomes a variant; the Process variant carries exit code + optional captured stderr. - Options: ClaudeAgentOptions as a plain struct with a builder. Covers the full subprocess-arg surface (system_prompt with String / PresetAppend / File forms, tools / allowed / disallowed, permission_mode, model and fallback, cwd / cli_path / settings, env, extra_args, effort, output_format, mcp_servers, fork_session, include_partial_messages, etc.). Deferred fields documented inline. - Messages: ContentBlock enum (Text / Thinking / ToolUse / ToolResult / ServerToolUse / ServerToolResult / Unknown) with serde tag dispatch and an Unknown fallthrough for forward compatibility. Top-level Message enum uses a hand-written Deserialize that dispatches on the "type" tag so unrecognised top-level frames land in Message::Unknown with the raw JSON preserved (matches the Python SDK's "skip unknown" behaviour, but lets Rust callers introspect). Unit tests cover roundtrip for the common shapes (assistant text + thinking + tool_use, user with both string and structured content, result, system, unknown top-level).
110 lines
4 KiB
Rust
110 lines
4 KiB
Rust
//! Error types for the Claude Agent SDK.
|
|
//!
|
|
//! Mirrors the exception hierarchy in the upstream Python SDK
|
|
//! (`claude_agent_sdk._errors`):
|
|
//!
|
|
//! - [`Error::CliNotFound`] — `CLINotFoundError`
|
|
//! - [`Error::CliConnection`] — `CLIConnectionError`
|
|
//! - [`Error::Process`] — `ProcessError`
|
|
//! - [`Error::JsonDecode`] — `CLIJSONDecodeError`
|
|
//! - [`Error::MessageParse`] — `MessageParseError`
|
|
//!
|
|
//! All variants inherit from `ClaudeSDKError` in Python; in Rust we expose a
|
|
//! single `Error` enum and rely on pattern matching (or [`Error::kind`]) to
|
|
//! distinguish them.
|
|
|
|
use thiserror::Error;
|
|
|
|
/// All errors surfaced by the SDK.
|
|
///
|
|
/// The Python SDK uses an exception hierarchy rooted at `ClaudeSDKError`. We
|
|
/// flatten that into a single enum because Rust pattern matching is more
|
|
/// ergonomic than `try`/`except` chains, and because most callers only care
|
|
/// about a small subset (e.g. "did the CLI not exist?" vs everything else).
|
|
#[derive(Debug, Error)]
|
|
pub enum Error {
|
|
/// The `claude` CLI binary could not be located on `PATH` or in any of the
|
|
/// fallback search locations, and no explicit `cli_path` was supplied via
|
|
/// [`crate::ClaudeAgentOptions::cli_path`].
|
|
///
|
|
/// Mirrors Python's `CLINotFoundError`.
|
|
#[error("{0}")]
|
|
CliNotFound(String),
|
|
|
|
/// Generic connection / transport problem with the CLI subprocess —
|
|
/// failed to spawn, stdin write failed, transport not ready, etc.
|
|
///
|
|
/// Mirrors Python's `CLIConnectionError`.
|
|
#[error("{0}")]
|
|
CliConnection(String),
|
|
|
|
/// The CLI subprocess exited with a non-zero status before yielding a
|
|
/// `ResultMessage`.
|
|
///
|
|
/// Mirrors Python's `ProcessError`.
|
|
#[error("{message}{exit_code}{stderr}",
|
|
exit_code = match exit_code { Some(c) => format!(" (exit code: {c})"), None => String::new() },
|
|
stderr = match stderr { Some(s) if !s.is_empty() => format!("\nError output: {s}"), _ => String::new() },
|
|
)]
|
|
Process {
|
|
/// Human-readable description (e.g. `"Command failed"`).
|
|
message: String,
|
|
/// Subprocess exit code, when known.
|
|
exit_code: Option<i32>,
|
|
/// Captured stderr output, when stderr was piped via
|
|
/// [`crate::ClaudeAgentOptions::capture_stderr`].
|
|
stderr: Option<String>,
|
|
},
|
|
|
|
/// A line on the CLI's stdout could not be decoded as JSON even after
|
|
/// buffering up to `max_buffer_size` bytes.
|
|
///
|
|
/// Mirrors Python's `CLIJSONDecodeError`.
|
|
#[error("Failed to decode JSON: {line_preview}...")]
|
|
JsonDecode {
|
|
/// First ~100 chars of the offending line, included in the message.
|
|
line_preview: String,
|
|
/// The underlying serde error.
|
|
#[source]
|
|
source: serde_json::Error,
|
|
},
|
|
|
|
/// A JSON message from the CLI was decoded but did not match any known
|
|
/// shape, or was missing a required field.
|
|
///
|
|
/// Mirrors Python's `MessageParseError`.
|
|
#[error("Message parse error: {message}")]
|
|
MessageParse {
|
|
/// Human-readable description.
|
|
message: String,
|
|
/// The raw JSON value the parser was looking at, if available.
|
|
data: Option<serde_json::Value>,
|
|
},
|
|
|
|
/// Underlying I/O failure (rare — most I/O is surfaced as
|
|
/// [`Error::CliConnection`]).
|
|
#[error("io error: {0}")]
|
|
Io(#[from] std::io::Error),
|
|
|
|
/// Invalid configuration (e.g. mutually exclusive options).
|
|
#[error("invalid configuration: {0}")]
|
|
Config(String),
|
|
}
|
|
|
|
impl Error {
|
|
/// Construct a [`Error::CliConnection`] from any displayable value.
|
|
pub(crate) fn conn(msg: impl Into<String>) -> Self {
|
|
Self::CliConnection(msg.into())
|
|
}
|
|
|
|
/// Construct a [`Error::MessageParse`] with raw data attached.
|
|
pub(crate) fn parse_with_data(msg: impl Into<String>, data: serde_json::Value) -> Self {
|
|
Self::MessageParse {
|
|
message: msg.into(),
|
|
data: Some(data),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Convenience alias for `Result<T, claude_agent_sdk::Error>`.
|
|
pub type Result<T> = std::result::Result<T, Error>;
|