//! Configuration for [`crate::query`] and [`crate::Client`]. //! //! [`ClaudeAgentOptions`] mirrors the Python SDK's dataclass of the same name, //! with one significant difference: in Rust the struct uses the builder //! pattern. The default-constructed value is always valid; chain //! `.with_*` / `.set_*` methods to configure. //! //! ``` //! use claude_agent_sdk::ClaudeAgentOptions; //! //! let opts = ClaudeAgentOptions::new() //! .with_system_prompt("You are a helpful assistant.") //! .with_max_turns(1) //! .with_allowed_tool("Read") //! .with_allowed_tool("Write"); //! ``` //! //! v0.1 ships the subset of fields needed to drive the CLI's `--output-format //! stream-json` mode end-to-end. The advanced fields (hooks, `can_use_tool`, //! session_store, plugins, sandbox, agents) are deferred — see the //! crate-level README for the v0.1 vs v0.2 split. use std::collections::HashMap; use std::path::PathBuf; use serde::{Deserialize, Serialize}; /// Permission mode for tool execution. /// /// Maps to the CLI's `--permission-mode` flag; values match the Python SDK's /// `PermissionMode` literal type. #[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub enum PermissionMode { /// CLI prompts for dangerous tools (default behaviour). Default, /// Auto-accept file edits. AcceptEdits, /// Plan-only mode (no tool execution). Plan, /// Allow all tools — use with caution. BypassPermissions, /// Deny anything not pre-approved by allow rules. DontAsk, /// Model classifier approves or denies each tool call. Auto, } impl PermissionMode { /// CLI-flag form of this mode. pub fn as_cli_str(self) -> &'static str { match self { Self::Default => "default", Self::AcceptEdits => "acceptEdits", Self::Plan => "plan", Self::BypassPermissions => "bypassPermissions", Self::DontAsk => "dontAsk", Self::Auto => "auto", } } } /// Adaptive-thinking effort level. #[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)] #[serde(rename_all = "lowercase")] pub enum Effort { /// Minimal thinking, fastest responses. Low, /// Moderate thinking. Medium, /// Deep reasoning (default for adaptive-capable models). High, /// Extended reasoning depth (Opus 4.7 only; falls back to `High` on /// other models). Xhigh, /// Maximum effort. Max, } impl Effort { /// CLI-flag form. pub fn as_cli_str(self) -> &'static str { match self { Self::Low => "low", Self::Medium => "medium", Self::High => "high", Self::Xhigh => "xhigh", Self::Max => "max", } } } /// System-prompt configuration. /// /// Mirrors the Python SDK union of `str | SystemPromptPreset | SystemPromptFile`. #[derive(Debug, Clone, PartialEq, Eq)] pub enum SystemPrompt { /// Raw string — passed verbatim via `--system-prompt`. String(String), /// Append-only on top of the Claude Code default preset, passed via /// `--append-system-prompt`. PresetAppend(String), /// Load the system prompt from a file via `--system-prompt-file`. File(PathBuf), } /// Options for [`crate::query`] and [`crate::Client`]. /// /// Construct with [`ClaudeAgentOptions::new`] (or `default()`) and configure /// via the builder methods. The struct is plain data — there is no validation /// at construction; mutually exclusive option combinations are rejected at /// `connect()` time with a [`crate::Error::Config`]. /// /// ## Field coverage /// /// v0.1 supports the fields used by the CLI's stream-json subprocess transport. /// Mirrors most of `ClaudeAgentOptions` from the Python SDK. Hook callbacks, /// `can_use_tool`, `session_store`, in-process MCP servers, sandbox settings, /// and plugins are deferred to v0.2 — see the crate README. #[derive(Debug, Clone, Default)] pub struct ClaudeAgentOptions { /// Custom system prompt. When `None`, the CLI's empty-prompt default /// is used. pub system_prompt: Option, /// Restrict the base set of tools available to the model. When `None`, /// the CLI default toolset is used. An empty `Vec` disables all built-in /// tools. Mirrors the Python SDK's `tools` field. pub tools: Option>, /// Tool names that are auto-allowed without prompting. Maps to the /// CLI's `--allowedTools` flag. pub allowed_tools: Vec, /// Tool names explicitly disallowed. Maps to `--disallowedTools`. pub disallowed_tools: Vec, /// Permission mode for the session. pub permission_mode: Option, /// Maximum number of conversation turns. pub max_turns: Option, /// Maximum budget in USD; the run stops when exceeded. pub max_budget_usd: Option, /// Continue the most recent conversation in `cwd`. pub continue_conversation: bool, /// Session ID to resume. pub resume: Option, /// Specific session ID for the conversation (UUID). pub session_id: Option, /// Model identifier (e.g. `"claude-sonnet-4-5"`). pub model: Option, /// Fallback model when the primary fails. pub fallback_model: Option, /// Working directory for the subprocess. pub cwd: Option, /// Explicit path to the `claude` CLI binary. When unset, the SDK /// searches `PATH` and a handful of standard install locations. pub cli_path: Option, /// Path or JSON string for an additional settings file (`--settings`). pub settings: Option, /// Additional directories Claude may access (`--add-dir`). pub add_dirs: Vec, /// Extra environment variables for the subprocess. pub env: HashMap, /// Pass-through CLI flags (`--flag value`). Value `None` becomes a /// boolean flag. pub extra_args: HashMap>, /// Maximum stdout buffer size in bytes for the CLI subprocess. /// Defaults to 1 MiB if unset. pub max_buffer_size: Option, /// Capture stderr from the subprocess. When `false` (default), stderr /// is inherited from the parent process. When `true`, stderr is piped /// and surfaced on the [`crate::Error::Process`] error variant. pub capture_stderr: bool, /// Include partial assistant streaming events in the message stream. pub include_partial_messages: bool, /// Include hook lifecycle events in the message stream. pub include_hook_events: bool, /// Resumed sessions fork into a new session ID. pub fork_session: bool, /// Adaptive-thinking effort level. pub effort: Option, /// Output format — passed through as `--json-schema` when shaped as /// `{"type": "json_schema", "schema": {...}}`. pub output_format: Option, /// MCP server configurations. Either a `serde_json::Value` (passed /// verbatim as `--mcp-config` JSON) or a path string. v0.1 does not /// support in-process SDK MCP servers — use external stdio/sse/http /// servers configured via JSON. pub mcp_servers: Option, /// Skip the CLI version check at connect time. pub skip_version_check: bool, } /// MCP server configuration, passed to the CLI as `--mcp-config`. #[derive(Debug, Clone, PartialEq, Eq)] pub enum McpServersConfig { /// Inline JSON value with shape `{"mcpServers": {...}}` (or a single /// server dict). Serialized and passed via `--mcp-config `. Inline(serde_json::Value), /// Path to an MCP config JSON file. Path(PathBuf), } impl ClaudeAgentOptions { /// Construct an empty options struct — equivalent to `Default::default()`. pub fn new() -> Self { Self::default() } /// Set a plain-string system prompt. pub fn with_system_prompt(mut self, prompt: impl Into) -> Self { self.system_prompt = Some(SystemPrompt::String(prompt.into())); self } /// Append a custom string to the Claude Code default system prompt. pub fn with_append_system_prompt(mut self, append: impl Into) -> Self { self.system_prompt = Some(SystemPrompt::PresetAppend(append.into())); self } /// Read the system prompt from a file at runtime. pub fn with_system_prompt_file(mut self, path: impl Into) -> Self { self.system_prompt = Some(SystemPrompt::File(path.into())); self } /// Restrict the base toolset. Pass `None` for an empty list, or a `Vec` /// of tool names. pub fn with_tools(mut self, tools: Vec) -> Self { self.tools = Some(tools); self } /// Add one auto-allowed tool name. pub fn with_allowed_tool(mut self, tool: impl Into) -> Self { self.allowed_tools.push(tool.into()); self } /// Replace the entire allowed-tools list. pub fn with_allowed_tools(mut self, tools: Vec) -> Self { self.allowed_tools = tools; self } /// Add one disallowed tool name. pub fn with_disallowed_tool(mut self, tool: impl Into) -> Self { self.disallowed_tools.push(tool.into()); self } /// Set the permission mode. pub fn with_permission_mode(mut self, mode: PermissionMode) -> Self { self.permission_mode = Some(mode); self } /// Cap the number of conversation turns. pub fn with_max_turns(mut self, turns: i32) -> Self { self.max_turns = Some(turns); self } /// Cap the budget in USD. pub fn with_max_budget_usd(mut self, usd: f64) -> Self { self.max_budget_usd = Some(usd); self } /// Continue the most recent conversation in `cwd`. pub fn with_continue_conversation(mut self, on: bool) -> Self { self.continue_conversation = on; self } /// Resume a specific session by ID. pub fn with_resume(mut self, session_id: impl Into) -> Self { self.resume = Some(session_id.into()); self } /// Pin a session ID (must be a valid UUID). pub fn with_session_id(mut self, session_id: impl Into) -> Self { self.session_id = Some(session_id.into()); self } /// Pick a specific model. pub fn with_model(mut self, model: impl Into) -> Self { self.model = Some(model.into()); self } /// Pick a fallback model. pub fn with_fallback_model(mut self, model: impl Into) -> Self { self.fallback_model = Some(model.into()); self } /// Set the working directory for the subprocess. pub fn with_cwd(mut self, cwd: impl Into) -> Self { self.cwd = Some(cwd.into()); self } /// Override the path to the `claude` CLI binary. pub fn with_cli_path(mut self, path: impl Into) -> Self { self.cli_path = Some(path.into()); self } /// Set the `--settings` argument (JSON string or file path). pub fn with_settings(mut self, settings: impl Into) -> Self { self.settings = Some(settings.into()); self } /// Add an extra directory accessible to Claude. pub fn with_add_dir(mut self, dir: impl Into) -> Self { self.add_dirs.push(dir.into()); self } /// Add an environment variable for the subprocess. pub fn with_env(mut self, key: impl Into, value: impl Into) -> Self { self.env.insert(key.into(), value.into()); self } /// Add a pass-through CLI flag with a value. pub fn with_extra_arg( mut self, flag: impl Into, value: Option>, ) -> Self { self.extra_args.insert(flag.into(), value.map(Into::into)); self } /// Cap stdout buffer size. pub fn with_max_buffer_size(mut self, size: usize) -> Self { self.max_buffer_size = Some(size); self } /// Capture stderr in errors (otherwise inherited from parent). pub fn with_capture_stderr(mut self, on: bool) -> Self { self.capture_stderr = on; self } /// Include partial streaming events in the message stream. pub fn with_include_partial_messages(mut self, on: bool) -> Self { self.include_partial_messages = on; self } /// Include hook lifecycle events in the message stream. pub fn with_include_hook_events(mut self, on: bool) -> Self { self.include_hook_events = on; self } /// Set adaptive-thinking effort level. pub fn with_effort(mut self, effort: Effort) -> Self { self.effort = Some(effort); self } /// Set MCP-server configuration (inline JSON or file path). pub fn with_mcp_servers(mut self, cfg: McpServersConfig) -> Self { self.mcp_servers = Some(cfg); self } /// Skip the `claude --version` check at connect time. Mirrors the /// `CLAUDE_AGENT_SDK_SKIP_VERSION_CHECK` env var behaviour. pub fn with_skip_version_check(mut self, on: bool) -> Self { self.skip_version_check = on; self } /// Set structured-output JSON schema. Pass the full `{"type": /// "json_schema", "schema": {...}}` shape. pub fn with_output_format(mut self, format: serde_json::Value) -> Self { self.output_format = Some(format); self } } #[cfg(test)] mod tests { use super::*; #[test] fn builder_chains() { let opts = ClaudeAgentOptions::new() .with_system_prompt("hi") .with_max_turns(3) .with_allowed_tool("Read") .with_allowed_tool("Bash") .with_permission_mode(PermissionMode::AcceptEdits) .with_model("claude-sonnet-4-5") .with_env("FOO", "bar"); assert!(matches!(opts.system_prompt, Some(SystemPrompt::String(ref s)) if s == "hi")); assert_eq!(opts.max_turns, Some(3)); assert_eq!(opts.allowed_tools, vec!["Read", "Bash"]); assert_eq!(opts.permission_mode, Some(PermissionMode::AcceptEdits)); assert_eq!(opts.model.as_deref(), Some("claude-sonnet-4-5")); assert_eq!(opts.env.get("FOO").map(String::as_str), Some("bar")); } #[test] fn permission_mode_cli_str_roundtrip() { assert_eq!(PermissionMode::Default.as_cli_str(), "default"); assert_eq!(PermissionMode::AcceptEdits.as_cli_str(), "acceptEdits"); assert_eq!(PermissionMode::BypassPermissions.as_cli_str(), "bypassPermissions"); assert_eq!(PermissionMode::DontAsk.as_cli_str(), "dontAsk"); assert_eq!(PermissionMode::Auto.as_cli_str(), "auto"); assert_eq!(PermissionMode::Plan.as_cli_str(), "plan"); } #[test] fn effort_cli_str() { assert_eq!(Effort::Low.as_cli_str(), "low"); assert_eq!(Effort::Xhigh.as_cli_str(), "xhigh"); assert_eq!(Effort::Max.as_cli_str(), "max"); } #[test] fn default_is_empty() { let opts = ClaudeAgentOptions::default(); assert!(opts.system_prompt.is_none()); assert!(opts.allowed_tools.is_empty()); assert_eq!(opts.continue_conversation, false); assert_eq!(opts.capture_stderr, false); } }