use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::future::Future;
use std::path::PathBuf;
use std::pin::Pin;
use std::sync::Arc;
use crate::error::Result;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct SessionId(String);
impl SessionId {
pub fn new(id: impl Into<String>) -> Self {
Self(id.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl Default for SessionId {
fn default() -> Self {
Self("default".to_string())
}
}
impl From<String> for SessionId {
fn from(s: String) -> Self {
Self(s)
}
}
impl From<&str> for SessionId {
fn from(s: &str) -> Self {
Self(s.to_string())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct ToolName(String);
impl ToolName {
pub fn new(name: impl Into<String>) -> Self {
Self(name.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl From<String> for ToolName {
fn from(s: String) -> Self {
Self(s)
}
}
impl From<&str> for ToolName {
fn from(s: &str) -> Self {
Self(s.to_string())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct RequestId(String);
impl RequestId {
pub fn new(id: impl Into<String>) -> Self {
Self(id.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl From<String> for RequestId {
fn from(s: String) -> Self {
Self(s)
}
}
impl From<&str> for RequestId {
fn from(s: &str) -> Self {
Self(s.to_string())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum PermissionMode {
Default,
AcceptEdits,
Plan,
BypassPermissions,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum SettingSource {
User,
Project,
Local,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum PermissionUpdateDestination {
UserSettings,
ProjectSettings,
LocalSettings,
Session,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum PermissionBehavior {
Allow,
Deny,
Ask,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PermissionRuleValue {
pub tool_name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub rule_content: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum PermissionUpdate {
AddRules {
#[serde(skip_serializing_if = "Option::is_none")]
rules: Option<Vec<PermissionRuleValue>>,
#[serde(skip_serializing_if = "Option::is_none")]
destination: Option<PermissionUpdateDestination>,
},
ReplaceRules {
#[serde(skip_serializing_if = "Option::is_none")]
rules: Option<Vec<PermissionRuleValue>>,
#[serde(skip_serializing_if = "Option::is_none")]
destination: Option<PermissionUpdateDestination>,
},
RemoveRules {
#[serde(skip_serializing_if = "Option::is_none")]
rules: Option<Vec<PermissionRuleValue>>,
#[serde(skip_serializing_if = "Option::is_none")]
destination: Option<PermissionUpdateDestination>,
},
SetMode {
mode: PermissionMode,
#[serde(skip_serializing_if = "Option::is_none")]
destination: Option<PermissionUpdateDestination>,
},
AddDirectories {
#[serde(skip_serializing_if = "Option::is_none")]
directories: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
destination: Option<PermissionUpdateDestination>,
},
RemoveDirectories {
#[serde(skip_serializing_if = "Option::is_none")]
directories: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
destination: Option<PermissionUpdateDestination>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolPermissionContext {
pub suggestions: Vec<PermissionUpdate>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PermissionRequest {
pub tool_name: ToolName,
pub tool_input: serde_json::Value,
pub context: ToolPermissionContext,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PermissionResultAllow {
#[serde(skip_serializing_if = "Option::is_none")]
pub updated_input: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub updated_permissions: Option<Vec<PermissionUpdate>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PermissionResultDeny {
pub message: String,
#[serde(default)]
pub interrupt: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "lowercase")]
pub enum PermissionResult {
Allow(PermissionResultAllow),
Deny(PermissionResultDeny),
}
pub type CanUseToolCallback = Arc<
dyn Fn(
ToolName,
serde_json::Value,
ToolPermissionContext,
) -> Pin<Box<dyn Future<Output = Result<PermissionResult>> + Send>>
+ Send
+ Sync,
>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum HookEvent {
PreToolUse,
PostToolUse,
UserPromptSubmit,
Stop,
SubagentStop,
PreCompact,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum HookDecision {
Block,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct HookOutput {
#[serde(skip_serializing_if = "Option::is_none")]
pub decision: Option<HookDecision>,
#[serde(skip_serializing_if = "Option::is_none", rename = "systemMessage")]
pub system_message: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", rename = "hookSpecificOutput")]
pub hook_specific_output: Option<serde_json::Value>,
}
#[derive(Debug, Clone)]
pub struct HookContext {
}
pub type HookCallback = Arc<
dyn Fn(
serde_json::Value,
Option<String>,
HookContext,
) -> Pin<Box<dyn Future<Output = Result<HookOutput>> + Send>>
+ Send
+ Sync,
>;
#[derive(Clone)]
pub struct HookMatcher {
pub matcher: Option<String>,
pub hooks: Vec<HookCallback>,
}
impl std::fmt::Debug for HookMatcher {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("HookMatcher")
.field("matcher", &self.matcher)
.field("hooks", &format!("[{} callbacks]", self.hooks.len()))
.finish()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpStdioServerConfig {
#[serde(skip_serializing_if = "Option::is_none", rename = "type")]
pub server_type: Option<String>,
pub command: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub args: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub env: Option<HashMap<String, String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpSseServerConfig {
#[serde(rename = "type")]
pub server_type: String,
pub url: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub headers: Option<HashMap<String, String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpHttpServerConfig {
#[serde(rename = "type")]
pub server_type: String,
pub url: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub headers: Option<HashMap<String, String>>,
}
#[derive(Debug, Clone)]
pub struct SdkMcpServerMarker {
#[allow(dead_code)]
pub name: String,
}
#[derive(Debug, Clone)]
pub enum McpServerConfig {
Stdio(McpStdioServerConfig),
Sse(McpSseServerConfig),
Http(McpHttpServerConfig),
Sdk(SdkMcpServerMarker),
}
#[derive(Debug, Clone, Default)]
pub enum McpServers {
#[default]
None,
Dict(HashMap<String, McpServerConfig>),
Path(PathBuf),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ContentValue {
String(String),
Blocks(Vec<serde_json::Value>),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ContentBlock {
Text {
text: String,
},
Thinking {
thinking: String,
signature: String,
},
ToolUse {
id: String,
name: String,
input: serde_json::Value,
},
ToolResult {
tool_use_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
content: Option<ContentValue>,
#[serde(skip_serializing_if = "Option::is_none")]
is_error: Option<bool>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserMessageContent {
pub role: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub content: Option<UserContent>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum UserContent {
String(String),
Blocks(Vec<ContentBlock>),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AssistantMessageContent {
pub model: String,
pub content: Vec<ContentBlock>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum Message {
User {
#[serde(skip_serializing_if = "Option::is_none")]
parent_tool_use_id: Option<String>,
message: UserMessageContent,
#[serde(skip_serializing_if = "Option::is_none")]
session_id: Option<SessionId>,
},
Assistant {
#[serde(skip_serializing_if = "Option::is_none")]
parent_tool_use_id: Option<String>,
message: AssistantMessageContent,
#[serde(skip_serializing_if = "Option::is_none")]
session_id: Option<SessionId>,
},
System {
subtype: String,
#[serde(flatten)]
data: serde_json::Value,
},
Result {
subtype: String,
duration_ms: u64,
duration_api_ms: u64,
is_error: bool,
num_turns: u32,
session_id: SessionId,
#[serde(skip_serializing_if = "Option::is_none")]
total_cost_usd: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
usage: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
result: Option<String>,
},
StreamEvent {
uuid: String,
session_id: SessionId,
event: serde_json::Value,
#[serde(skip_serializing_if = "Option::is_none")]
parent_tool_use_id: Option<String>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SystemPromptPreset {
#[serde(rename = "type")]
pub prompt_type: String,
pub preset: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub append: Option<String>,
}
#[derive(Debug, Clone)]
pub enum SystemPrompt {
String(String),
Preset(SystemPromptPreset),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentDefinition {
pub description: String,
pub prompt: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub tools: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub model: Option<String>,
}
#[derive(Clone, Default)]
pub struct ClaudeAgentOptions {
pub allowed_tools: Vec<ToolName>,
pub system_prompt: Option<SystemPrompt>,
pub mcp_servers: McpServers,
pub permission_mode: Option<PermissionMode>,
pub continue_conversation: bool,
pub resume: Option<SessionId>,
pub max_turns: Option<u32>,
pub disallowed_tools: Vec<ToolName>,
pub model: Option<String>,
pub permission_prompt_tool_name: Option<String>,
pub cwd: Option<PathBuf>,
pub settings: Option<PathBuf>,
pub add_dirs: Vec<PathBuf>,
pub env: HashMap<String, String>,
pub extra_args: HashMap<String, Option<String>>,
pub max_buffer_size: Option<usize>,
pub can_use_tool: Option<CanUseToolCallback>,
pub hooks: Option<HashMap<HookEvent, Vec<HookMatcher>>>,
pub user: Option<String>,
pub include_partial_messages: bool,
pub fork_session: bool,
pub agents: Option<HashMap<String, AgentDefinition>>,
pub setting_sources: Option<Vec<SettingSource>>,
}
impl ClaudeAgentOptions {
pub fn builder() -> ClaudeAgentOptionsBuilder {
ClaudeAgentOptionsBuilder::default()
}
}
impl std::fmt::Debug for ClaudeAgentOptions {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ClaudeAgentOptions")
.field("allowed_tools", &self.allowed_tools)
.field("system_prompt", &self.system_prompt)
.field("mcp_servers", &self.mcp_servers)
.field("permission_mode", &self.permission_mode)
.field("continue_conversation", &self.continue_conversation)
.field("resume", &self.resume)
.field("max_turns", &self.max_turns)
.field("disallowed_tools", &self.disallowed_tools)
.field("model", &self.model)
.field(
"permission_prompt_tool_name",
&self.permission_prompt_tool_name,
)
.field("cwd", &self.cwd)
.field("settings", &self.settings)
.field("add_dirs", &self.add_dirs)
.field("env", &self.env)
.field("extra_args", &self.extra_args)
.field("max_buffer_size", &self.max_buffer_size)
.field(
"can_use_tool",
&self.can_use_tool.as_ref().map(|_| "<callback>"),
)
.field(
"hooks",
&self
.hooks
.as_ref()
.map(|h| format!("[{} hook types]", h.len())),
)
.field("user", &self.user)
.field("include_partial_messages", &self.include_partial_messages)
.field("fork_session", &self.fork_session)
.field("agents", &self.agents)
.field("setting_sources", &self.setting_sources)
.finish()
}
}
#[derive(Debug, Default)]
pub struct ClaudeAgentOptionsBuilder {
options: ClaudeAgentOptions,
}
impl ClaudeAgentOptionsBuilder {
pub fn allowed_tools(mut self, tools: Vec<impl Into<ToolName>>) -> Self {
self.options.allowed_tools = tools.into_iter().map(|t| t.into()).collect();
self
}
pub fn add_allowed_tool(mut self, tool: impl Into<ToolName>) -> Self {
self.options.allowed_tools.push(tool.into());
self
}
pub fn system_prompt(mut self, prompt: impl Into<SystemPrompt>) -> Self {
self.options.system_prompt = Some(prompt.into());
self
}
pub fn mcp_servers(mut self, servers: HashMap<String, McpServerConfig>) -> Self {
self.options.mcp_servers = McpServers::Dict(servers);
self
}
pub fn permission_mode(mut self, mode: PermissionMode) -> Self {
self.options.permission_mode = Some(mode);
self
}
pub fn max_turns(mut self, turns: u32) -> Self {
const MAX_ALLOWED_TURNS: u32 = 1000;
if turns > MAX_ALLOWED_TURNS {
panic!(
"max_turns {turns} exceeds maximum allowed: {MAX_ALLOWED_TURNS}"
);
}
self.options.max_turns = Some(turns);
self
}
pub fn cwd(mut self, path: impl Into<PathBuf>) -> Self {
self.options.cwd = Some(path.into());
self
}
pub fn can_use_tool(mut self, callback: CanUseToolCallback) -> Self {
self.options.can_use_tool = Some(callback);
self
}
pub fn hooks(mut self, hooks: HashMap<HookEvent, Vec<HookMatcher>>) -> Self {
self.options.hooks = Some(hooks);
self
}
pub fn build(self) -> ClaudeAgentOptions {
self.options
}
}
impl From<String> for SystemPrompt {
fn from(s: String) -> Self {
SystemPrompt::String(s)
}
}
impl From<&str> for SystemPrompt {
fn from(s: &str) -> Self {
SystemPrompt::String(s.to_string())
}
}
impl From<SystemPromptPreset> for SystemPrompt {
fn from(preset: SystemPromptPreset) -> Self {
SystemPrompt::Preset(preset)
}
}