use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::future::Future;
use std::path::PathBuf;
use std::pin::Pin;
use std::sync::Arc;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub enum PermissionMode {
#[default]
#[serde(rename = "default")]
Default,
#[serde(rename = "acceptEdits")]
AcceptEdits,
#[serde(rename = "plan")]
Plan,
#[serde(rename = "bypassPermissions")]
BypassPermissions,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum PermissionBehavior {
Allow,
Deny,
Ask,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum PermissionUpdateDestination {
UserSettings,
ProjectSettings,
LocalSettings,
Session,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PermissionRuleValue {
#[serde(rename = "toolName")]
pub tool_name: String,
#[serde(rename = "ruleContent", skip_serializing_if = "Option::is_none")]
pub rule_content: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum PermissionUpdateType {
AddRules,
ReplaceRules,
RemoveRules,
SetMode,
AddDirectories,
RemoveDirectories,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PermissionUpdate {
#[serde(rename = "type")]
pub update_type: PermissionUpdateType,
#[serde(skip_serializing_if = "Option::is_none")]
pub rules: Option<Vec<PermissionRuleValue>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub behavior: Option<PermissionBehavior>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mode: Option<PermissionMode>,
#[serde(skip_serializing_if = "Option::is_none")]
pub directories: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub destination: Option<PermissionUpdateDestination>,
}
#[derive(Debug, Clone, Default)]
pub struct ToolPermissionContext {
pub suggestions: Vec<PermissionUpdate>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PermissionResultAllow {
pub behavior: String,
#[serde(rename = "updatedInput", skip_serializing_if = "Option::is_none")]
pub updated_input: Option<serde_json::Value>,
#[serde(rename = "updatedPermissions", skip_serializing_if = "Option::is_none")]
pub updated_permissions: Option<Vec<PermissionUpdate>>,
}
impl PermissionResultAllow {
pub fn new() -> Self {
Self {
behavior: "allow".to_string(),
updated_input: None,
updated_permissions: None,
}
}
pub fn with_updated_input(input: serde_json::Value) -> Self {
Self {
behavior: "allow".to_string(),
updated_input: Some(input),
updated_permissions: None,
}
}
}
impl Default for PermissionResultAllow {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PermissionResultDeny {
pub behavior: String,
#[serde(default)]
pub message: String,
#[serde(default)]
pub interrupt: bool,
}
impl PermissionResultDeny {
pub fn new() -> Self {
Self {
behavior: "deny".to_string(),
message: String::new(),
interrupt: false,
}
}
pub fn with_message(message: impl Into<String>) -> Self {
Self {
behavior: "deny".to_string(),
message: message.into(),
interrupt: false,
}
}
pub fn with_interrupt(message: impl Into<String>) -> Self {
Self {
behavior: "deny".to_string(),
message: message.into(),
interrupt: true,
}
}
}
impl Default for PermissionResultDeny {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum PermissionResult {
Allow(PermissionResultAllow),
Deny(PermissionResultDeny),
}
impl PermissionResult {
pub fn allow() -> Self {
Self::Allow(PermissionResultAllow::new())
}
pub fn deny() -> Self {
Self::Deny(PermissionResultDeny::new())
}
pub fn deny_with_message(message: impl Into<String>) -> Self {
Self::Deny(PermissionResultDeny::with_message(message))
}
}
pub type CanUseToolFuture = Pin<Box<dyn Future<Output = PermissionResult> + Send>>;
pub type CanUseTool =
Arc<dyn Fn(String, serde_json::Value, ToolPermissionContext) -> CanUseToolFuture + Send + Sync>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum HookEvent {
PreToolUse,
PostToolUse,
PostToolUseFailure,
UserPromptSubmit,
Stop,
SubagentStop,
PreCompact,
Notification,
SubagentStart,
PermissionRequest,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BaseHookInput {
pub session_id: String,
pub transcript_path: String,
pub cwd: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub permission_mode: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PreToolUseHookInput {
#[serde(flatten)]
pub base: BaseHookInput,
#[serde(default)]
pub hook_event_name: String,
pub tool_name: String,
pub tool_input: serde_json::Value,
#[serde(default)]
pub tool_use_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PostToolUseHookInput {
#[serde(flatten)]
pub base: BaseHookInput,
#[serde(default)]
pub hook_event_name: String,
pub tool_name: String,
pub tool_input: serde_json::Value,
pub tool_response: serde_json::Value,
#[serde(default)]
pub tool_use_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PostToolUseFailureHookInput {
#[serde(flatten)]
pub base: BaseHookInput,
#[serde(default)]
pub hook_event_name: String,
pub tool_name: String,
pub tool_input: serde_json::Value,
pub tool_use_id: String,
pub error: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub is_interrupt: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserPromptSubmitHookInput {
#[serde(flatten)]
pub base: BaseHookInput,
#[serde(default)]
pub hook_event_name: String,
pub prompt: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StopHookInput {
#[serde(flatten)]
pub base: BaseHookInput,
#[serde(default)]
pub hook_event_name: String,
pub stop_hook_active: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SubagentStopHookInput {
#[serde(flatten)]
pub base: BaseHookInput,
#[serde(default)]
pub hook_event_name: String,
pub stop_hook_active: bool,
#[serde(default)]
pub agent_id: String,
#[serde(default)]
pub agent_transcript_path: String,
#[serde(default)]
pub agent_type: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum CompactTrigger {
Manual,
Auto,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PreCompactHookInput {
#[serde(flatten)]
pub base: BaseHookInput,
#[serde(default)]
pub hook_event_name: String,
pub trigger: CompactTrigger,
#[serde(skip_serializing_if = "Option::is_none")]
pub custom_instructions: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NotificationHookInput {
#[serde(flatten)]
pub base: BaseHookInput,
#[serde(default)]
pub hook_event_name: String,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
pub notification_type: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SubagentStartHookInput {
#[serde(flatten)]
pub base: BaseHookInput,
#[serde(default)]
pub hook_event_name: String,
pub agent_id: String,
pub agent_type: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PermissionRequestHookInput {
#[serde(flatten)]
pub base: BaseHookInput,
#[serde(default)]
pub hook_event_name: String,
pub tool_name: String,
pub tool_input: serde_json::Value,
#[serde(skip_serializing_if = "Option::is_none")]
pub permission_suggestions: Option<Vec<serde_json::Value>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "hook_event_name")]
pub enum HookInput {
PreToolUse(PreToolUseHookInput),
PostToolUse(PostToolUseHookInput),
PostToolUseFailure(PostToolUseFailureHookInput),
UserPromptSubmit(UserPromptSubmitHookInput),
Stop(StopHookInput),
SubagentStop(SubagentStopHookInput),
PreCompact(PreCompactHookInput),
Notification(NotificationHookInput),
SubagentStart(SubagentStartHookInput),
PermissionRequest(PermissionRequestHookInput),
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PreToolUseHookSpecificOutput {
#[serde(rename = "hookEventName")]
pub hook_event_name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub permission_decision: Option<PermissionBehavior>,
#[serde(skip_serializing_if = "Option::is_none")]
pub permission_decision_reason: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub updated_input: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub additional_context: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PostToolUseHookSpecificOutput {
#[serde(rename = "hookEventName")]
pub hook_event_name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub additional_context: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub updated_mcp_tool_output: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PostToolUseFailureHookSpecificOutput {
#[serde(rename = "hookEventName")]
pub hook_event_name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub additional_context: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UserPromptSubmitHookSpecificOutput {
#[serde(rename = "hookEventName")]
pub hook_event_name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub additional_context: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NotificationHookSpecificOutput {
#[serde(rename = "hookEventName")]
pub hook_event_name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub additional_context: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SubagentStartHookSpecificOutput {
#[serde(rename = "hookEventName")]
pub hook_event_name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub additional_context: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PermissionRequestHookSpecificOutput {
#[serde(rename = "hookEventName")]
pub hook_event_name: String,
pub decision: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum HookSpecificOutput {
PreToolUse(PreToolUseHookSpecificOutput),
PostToolUse(PostToolUseHookSpecificOutput),
PostToolUseFailure(PostToolUseFailureHookSpecificOutput),
UserPromptSubmit(UserPromptSubmitHookSpecificOutput),
Notification(NotificationHookSpecificOutput),
SubagentStart(SubagentStartHookSpecificOutput),
PermissionRequest(PermissionRequestHookSpecificOutput),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AsyncHookOutput {
#[serde(rename = "async")]
pub async_: bool,
#[serde(rename = "asyncTimeout", skip_serializing_if = "Option::is_none")]
pub async_timeout: Option<u64>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SyncHookOutput {
#[serde(rename = "continue", skip_serializing_if = "Option::is_none")]
pub continue_: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub suppress_output: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stop_reason: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub decision: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub system_message: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub hook_specific_output: Option<HookSpecificOutput>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum HookOutput {
Async(AsyncHookOutput),
Sync(SyncHookOutput),
}
impl Default for HookOutput {
fn default() -> Self {
Self::Sync(SyncHookOutput::default())
}
}
#[derive(Debug, Clone, Default)]
pub struct HookContext {
}
pub type HookCallbackFuture = Pin<Box<dyn Future<Output = HookOutput> + Send>>;
pub type HookCallback =
Arc<dyn Fn(HookInput, Option<String>, HookContext) -> HookCallbackFuture + Send + Sync>;
#[derive(Clone, Default)]
pub struct HookMatcher {
pub matcher: Option<String>,
pub hooks: Vec<HookCallback>,
pub timeout: Option<f64>,
}
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()))
.field("timeout", &self.timeout)
.finish()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpStdioServerConfig {
#[serde(rename = "type", default = "default_stdio", skip_serializing)]
pub server_type: String,
pub command: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub args: Vec<String>,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub env: HashMap<String, String>,
}
fn default_stdio() -> String {
"stdio".to_string()
}
fn default_sse() -> String {
"sse".to_string()
}
fn default_http() -> String {
"http".to_string()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpSSEServerConfig {
#[serde(rename = "type", default = "default_sse", skip_serializing)]
pub server_type: String,
pub url: String,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub headers: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpHttpServerConfig {
#[serde(rename = "type", default = "default_http", skip_serializing)]
pub server_type: String,
pub url: String,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub headers: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum McpServerConfig {
#[serde(rename = "stdio")]
Stdio(McpStdioServerConfig),
#[serde(rename = "sse")]
SSE(McpSSEServerConfig),
#[serde(rename = "http")]
Http(McpHttpServerConfig),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SdkPluginConfig {
#[serde(rename = "type")]
pub plugin_type: String,
pub path: String,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SandboxNetworkConfig {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub allow_unix_sockets: Vec<String>,
#[serde(default)]
pub allow_all_unix_sockets: bool,
#[serde(default)]
pub allow_local_binding: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub http_proxy_port: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
pub socks_proxy_port: Option<u16>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct SandboxIgnoreViolations {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub file: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub network: Vec<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SandboxSettings {
#[serde(default)]
pub enabled: bool,
#[serde(default = "default_true")]
pub auto_allow_bash_if_sandboxed: bool,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub excluded_commands: Vec<String>,
#[serde(default = "default_true")]
pub allow_unsandboxed_commands: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub network: Option<SandboxNetworkConfig>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ignore_violations: Option<SandboxIgnoreViolations>,
#[serde(default)]
pub enable_weaker_nested_sandbox: bool,
}
fn default_true() -> bool {
true
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TextBlock {
pub text: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ThinkingBlock {
pub thinking: String,
pub signature: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolUseBlock {
pub id: String,
pub name: String,
pub input: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolResultBlock {
pub tool_use_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub content: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub is_error: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum ContentBlock {
#[serde(rename = "text")]
Text(TextBlock),
#[serde(rename = "thinking")]
Thinking(ThinkingBlock),
#[serde(rename = "tool_use")]
ToolUse(ToolUseBlock),
#[serde(rename = "tool_result")]
ToolResult(ToolResultBlock),
}
impl ContentBlock {
pub fn as_text(&self) -> Option<&str> {
match self {
ContentBlock::Text(block) => Some(&block.text),
_ => None,
}
}
pub fn is_tool_use(&self) -> bool {
matches!(self, ContentBlock::ToolUse(_))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AssistantMessageError {
AuthenticationFailed,
BillingError,
RateLimit,
InvalidRequest,
ServerError,
Unknown,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserMessage {
pub content: UserMessageContent,
#[serde(skip_serializing_if = "Option::is_none")]
pub uuid: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub parent_tool_use_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum UserMessageContent {
Text(String),
Blocks(Vec<ContentBlock>),
}
impl UserMessage {
pub fn text(&self) -> Option<&str> {
match &self.content {
UserMessageContent::Text(s) => Some(s),
UserMessageContent::Blocks(blocks) => {
if blocks.len() == 1 {
blocks[0].as_text()
} else {
None
}
}
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AssistantMessage {
pub content: Vec<ContentBlock>,
pub model: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub parent_tool_use_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<AssistantMessageError>,
}
impl AssistantMessage {
pub fn text(&self) -> String {
self.content
.iter()
.filter_map(|block| block.as_text())
.collect::<Vec<_>>()
.join("")
}
pub fn tool_uses(&self) -> Vec<&ToolUseBlock> {
self.content
.iter()
.filter_map(|block| match block {
ContentBlock::ToolUse(tu) => Some(tu),
_ => None,
})
.collect()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SystemMessage {
pub subtype: String,
pub data: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResultMessage {
pub subtype: String,
pub duration_ms: u64,
pub duration_api_ms: u64,
pub is_error: bool,
pub num_turns: u32,
pub session_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub total_cost_usd: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub usage: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub structured_output: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StreamEvent {
pub uuid: String,
pub session_id: String,
pub event: serde_json::Value,
#[serde(skip_serializing_if = "Option::is_none")]
pub parent_tool_use_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum Message {
#[serde(rename = "user")]
User(UserMessage),
#[serde(rename = "assistant")]
Assistant(AssistantMessage),
#[serde(rename = "system")]
System(SystemMessage),
#[serde(rename = "result")]
Result(ResultMessage),
#[serde(rename = "stream_event")]
StreamEvent(StreamEvent),
}
impl Message {
pub fn is_result(&self) -> bool {
matches!(self, Message::Result(_))
}
pub fn is_assistant(&self) -> bool {
matches!(self, Message::Assistant(_))
}
pub fn as_assistant(&self) -> Option<&AssistantMessage> {
match self {
Message::Assistant(msg) => Some(msg),
_ => None,
}
}
pub fn as_result(&self) -> Option<&ResultMessage> {
match self {
Message::Result(msg) => Some(msg),
_ => None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SystemPromptPreset {
#[serde(rename = "type")]
pub preset_type: String,
pub preset: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub append: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolsPreset {
#[serde(rename = "type")]
pub preset_type: String,
pub preset: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum SystemPromptConfig {
Text(String),
Preset(SystemPromptPreset),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ToolsConfig {
List(Vec<String>),
Preset(ToolsPreset),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum AgentModel {
Sonnet,
Opus,
Haiku,
Inherit,
}
#[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<AgentModel>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum SettingSource {
User,
Project,
Local,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum SdkBeta {
#[serde(rename = "context-1m-2025-08-07")]
Context1m,
}
#[derive(Debug, Clone)]
pub enum McpServersConfig {
Map(HashMap<String, McpServerConfig>),
Path(PathBuf),
}
impl Default for McpServersConfig {
fn default() -> Self {
Self::Map(HashMap::new())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "lowercase")]
pub enum ThinkingConfig {
Adaptive,
Enabled {
budget_tokens: u32,
},
Disabled,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Effort {
Low,
Medium,
High,
Max,
}
impl std::fmt::Display for Effort {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Low => write!(f, "low"),
Self::Medium => write!(f, "medium"),
Self::High => write!(f, "high"),
Self::Max => write!(f, "max"),
}
}
}
#[derive(Clone, Default)]
pub struct ClaudeAgentOptions {
pub tools: Option<ToolsConfig>,
pub allowed_tools: Vec<String>,
pub system_prompt: Option<SystemPromptConfig>,
pub mcp_servers: McpServersConfig,
pub permission_mode: Option<PermissionMode>,
pub continue_conversation: bool,
pub resume: Option<String>,
pub max_turns: Option<u32>,
pub max_budget_usd: Option<f64>,
pub disallowed_tools: Vec<String>,
pub model: Option<String>,
pub fallback_model: Option<String>,
pub betas: Vec<SdkBeta>,
pub permission_prompt_tool_name: Option<String>,
pub cwd: Option<PathBuf>,
pub cli_path: Option<PathBuf>,
pub settings: Option<String>,
pub add_dirs: Vec<PathBuf>,
pub env: HashMap<String, String>,
pub extra_args: HashMap<String, Option<String>>,
pub max_buffer_size: Option<usize>,
pub stderr: Option<Arc<dyn Fn(String) + Send + Sync>>,
pub can_use_tool: Option<CanUseTool>,
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>>,
pub sandbox: Option<SandboxSettings>,
pub plugins: Vec<SdkPluginConfig>,
pub max_thinking_tokens: Option<u32>,
pub thinking: Option<ThinkingConfig>,
pub effort: Option<Effort>,
pub output_format: Option<serde_json::Value>,
pub enable_file_checkpointing: bool,
pub timeout_secs: Option<u64>,
}
impl std::fmt::Debug for ClaudeAgentOptions {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ClaudeAgentOptions")
.field("tools", &self.tools)
.field("allowed_tools", &self.allowed_tools)
.field("system_prompt", &self.system_prompt)
.field("permission_mode", &self.permission_mode)
.field("continue_conversation", &self.continue_conversation)
.field("resume", &self.resume)
.field("max_turns", &self.max_turns)
.field("max_budget_usd", &self.max_budget_usd)
.field("disallowed_tools", &self.disallowed_tools)
.field("model", &self.model)
.field(
"can_use_tool",
&self.can_use_tool.as_ref().map(|_| "<callback>"),
)
.field(
"hooks",
&self.hooks.as_ref().map(|h| format!("{} events", h.len())),
)
.field("stderr", &self.stderr.as_ref().map(|_| "<callback>"))
.finish_non_exhaustive()
}
}
impl ClaudeAgentOptions {
pub fn new() -> Self {
Self::default()
}
pub fn with_system_prompt(mut self, prompt: impl Into<String>) -> Self {
self.system_prompt = Some(SystemPromptConfig::Text(prompt.into()));
self
}
pub fn with_model(mut self, model: impl Into<String>) -> Self {
self.model = Some(model.into());
self
}
pub fn with_permission_mode(mut self, mode: PermissionMode) -> Self {
self.permission_mode = Some(mode);
self
}
pub fn with_max_turns(mut self, turns: u32) -> Self {
self.max_turns = Some(turns);
self
}
pub fn with_cwd(mut self, cwd: impl Into<PathBuf>) -> Self {
self.cwd = Some(cwd.into());
self
}
pub fn with_allowed_tools(mut self, tools: Vec<String>) -> Self {
self.allowed_tools = tools;
self
}
pub fn with_partial_messages(mut self) -> Self {
self.include_partial_messages = true;
self
}
pub fn with_thinking(mut self, thinking: ThinkingConfig) -> Self {
self.thinking = Some(thinking);
self
}
pub fn with_effort(mut self, effort: Effort) -> Self {
self.effort = Some(effort);
self
}
pub fn with_timeout_secs(mut self, timeout: u64) -> Self {
self.timeout_secs = Some(timeout);
self
}
pub fn with_can_use_tool<F, Fut>(mut self, callback: F) -> Self
where
F: Fn(String, serde_json::Value, ToolPermissionContext) -> Fut + Send + Sync + 'static,
Fut: Future<Output = PermissionResult> + Send + 'static,
{
self.can_use_tool = Some(Arc::new(move |name, input, ctx| {
Box::pin(callback(name, input, ctx))
}));
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "subtype")]
pub enum ControlRequestPayload {
#[serde(rename = "interrupt")]
Interrupt,
#[serde(rename = "can_use_tool")]
CanUseTool {
tool_name: String,
input: serde_json::Value,
permission_suggestions: Option<Vec<serde_json::Value>>,
blocked_path: Option<String>,
},
#[serde(rename = "initialize")]
Initialize {
hooks: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
agents: Option<serde_json::Value>,
},
#[serde(rename = "set_permission_mode")]
SetPermissionMode {
mode: String,
},
#[serde(rename = "set_model")]
SetModel {
model: String,
},
#[serde(rename = "hook_callback")]
HookCallback {
callback_id: String,
input: serde_json::Value,
tool_use_id: Option<String>,
},
#[serde(rename = "mcp_message")]
McpMessage {
server_name: String,
message: serde_json::Value,
},
#[serde(rename = "mcp_status")]
McpStatus,
#[serde(rename = "rewind_files")]
RewindFiles {
user_message_id: String,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ControlRequest {
#[serde(rename = "type")]
pub request_type: String,
pub request_id: String,
pub request: ControlRequestPayload,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ControlSuccessResponse {
pub subtype: String,
pub request_id: String,
pub response: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ControlErrorResponse {
pub subtype: String,
pub request_id: String,
pub error: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "subtype")]
pub enum ControlResponsePayload {
#[serde(rename = "success")]
Success {
request_id: String,
response: Option<serde_json::Value>,
},
#[serde(rename = "error")]
Error {
request_id: String,
error: String,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ControlResponse {
#[serde(rename = "type")]
pub response_type: String,
pub response: ControlResponsePayload,
}
impl ControlResponse {
pub fn request_id(&self) -> &str {
match &self.response {
ControlResponsePayload::Success { request_id, .. } => request_id,
ControlResponsePayload::Error { request_id, .. } => request_id,
}
}
pub fn is_success(&self) -> bool {
matches!(&self.response, ControlResponsePayload::Success { .. })
}
pub fn data(&self) -> Option<&serde_json::Value> {
match &self.response {
ControlResponsePayload::Success { response, .. } => response.as_ref(),
ControlResponsePayload::Error { .. } => None,
}
}
pub fn error(&self) -> Option<&str> {
match &self.response {
ControlResponsePayload::Success { .. } => None,
ControlResponsePayload::Error { error, .. } => Some(error),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_permission_result_allow() {
let result = PermissionResult::allow();
let json = serde_json::to_string(&result).unwrap();
assert!(json.contains("allow"));
}
#[test]
fn test_message_parsing() {
let json = r#"{"type": "assistant", "content": [{"type": "text", "text": "Hello"}], "model": "claude-3"}"#;
let msg: Message = serde_json::from_str(json).unwrap();
assert!(msg.is_assistant());
}
#[test]
fn test_content_block_text() {
let block = ContentBlock::Text(TextBlock {
text: "Hello".to_string(),
});
assert_eq!(block.as_text(), Some("Hello"));
}
#[test]
fn test_options_builder() {
let opts = ClaudeAgentOptions::new()
.with_model("claude-3-sonnet")
.with_max_turns(5)
.with_permission_mode(PermissionMode::AcceptEdits);
assert_eq!(opts.model, Some("claude-3-sonnet".to_string()));
assert_eq!(opts.max_turns, Some(5));
assert_eq!(opts.permission_mode, Some(PermissionMode::AcceptEdits));
}
}