cc_sdk/
types.rs

1//! Type definitions for the Claude Code SDK
2//!
3//! This module contains all the core types used throughout the SDK,
4//! including messages, configuration options, and content blocks.
5
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use std::path::PathBuf;
9
10/// Permission mode for tool execution
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
12#[serde(rename_all = "camelCase")]
13pub enum PermissionMode {
14    /// Default mode - CLI prompts for dangerous tools
15    Default,
16    /// Auto-accept file edits
17    AcceptEdits,
18    /// Plan mode - for planning tasks
19    Plan,
20    /// Allow all tools without prompting (use with caution)
21    BypassPermissions,
22}
23
24impl Default for PermissionMode {
25    fn default() -> Self {
26        Self::Default
27    }
28}
29
30/// MCP (Model Context Protocol) server configuration
31#[derive(Debug, Clone, Serialize, Deserialize)]
32#[serde(tag = "type", rename_all = "lowercase")]
33pub enum McpServerConfig {
34    /// Standard I/O based MCP server
35    Stdio {
36        /// Command to execute
37        command: String,
38        /// Command arguments
39        #[serde(skip_serializing_if = "Option::is_none")]
40        args: Option<Vec<String>>,
41        /// Environment variables
42        #[serde(skip_serializing_if = "Option::is_none")]
43        env: Option<HashMap<String, String>>,
44    },
45    /// Server-Sent Events based MCP server
46    Sse {
47        /// Server URL
48        url: String,
49        /// HTTP headers
50        #[serde(skip_serializing_if = "Option::is_none")]
51        headers: Option<HashMap<String, String>>,
52    },
53    /// HTTP-based MCP server
54    Http {
55        /// Server URL
56        url: String,
57        /// HTTP headers
58        #[serde(skip_serializing_if = "Option::is_none")]
59        headers: Option<HashMap<String, String>>,
60    },
61}
62
63/// Configuration options for Claude Code SDK
64#[derive(Debug, Clone, Default)]
65pub struct ClaudeCodeOptions {
66    /// System prompt to prepend to all messages
67    pub system_prompt: Option<String>,
68    /// Additional system prompt to append
69    pub append_system_prompt: Option<String>,
70    /// List of allowed tools
71    pub allowed_tools: Vec<String>,
72    /// List of disallowed tools
73    pub disallowed_tools: Vec<String>,
74    /// Permission mode for tool execution
75    pub permission_mode: PermissionMode,
76    /// MCP server configurations
77    pub mcp_servers: HashMap<String, McpServerConfig>,
78    /// MCP tools to enable
79    pub mcp_tools: Vec<String>,
80    /// Maximum number of conversation turns
81    pub max_turns: Option<i32>,
82    /// Maximum thinking tokens
83    pub max_thinking_tokens: i32,
84    /// Model to use
85    pub model: Option<String>,
86    /// Working directory
87    pub cwd: Option<PathBuf>,
88    /// Continue from previous conversation
89    pub continue_conversation: bool,
90    /// Resume from a specific conversation ID
91    pub resume: Option<String>,
92    /// Custom permission prompt tool name
93    pub permission_prompt_tool_name: Option<String>,
94    /// Settings file path for Claude Code CLI
95    pub settings: Option<String>,
96    /// Additional directories to add as working directories
97    pub add_dirs: Vec<PathBuf>,
98    /// Extra arbitrary CLI flags
99    pub extra_args: HashMap<String, Option<String>>,
100}
101
102impl ClaudeCodeOptions {
103    /// Create a new options builder
104    pub fn builder() -> ClaudeCodeOptionsBuilder {
105        ClaudeCodeOptionsBuilder::default()
106    }
107}
108
109/// Builder for ClaudeCodeOptions
110#[derive(Debug, Default)]
111pub struct ClaudeCodeOptionsBuilder {
112    options: ClaudeCodeOptions,
113}
114
115impl ClaudeCodeOptionsBuilder {
116    /// Set system prompt
117    pub fn system_prompt(mut self, prompt: impl Into<String>) -> Self {
118        self.options.system_prompt = Some(prompt.into());
119        self
120    }
121
122    /// Set append system prompt
123    pub fn append_system_prompt(mut self, prompt: impl Into<String>) -> Self {
124        self.options.append_system_prompt = Some(prompt.into());
125        self
126    }
127
128    /// Add allowed tools
129    pub fn allowed_tools(mut self, tools: Vec<String>) -> Self {
130        self.options.allowed_tools = tools;
131        self
132    }
133
134    /// Add a single allowed tool
135    pub fn allow_tool(mut self, tool: impl Into<String>) -> Self {
136        self.options.allowed_tools.push(tool.into());
137        self
138    }
139
140    /// Add disallowed tools
141    pub fn disallowed_tools(mut self, tools: Vec<String>) -> Self {
142        self.options.disallowed_tools = tools;
143        self
144    }
145
146    /// Add a single disallowed tool
147    pub fn disallow_tool(mut self, tool: impl Into<String>) -> Self {
148        self.options.disallowed_tools.push(tool.into());
149        self
150    }
151
152    /// Set permission mode
153    pub fn permission_mode(mut self, mode: PermissionMode) -> Self {
154        self.options.permission_mode = mode;
155        self
156    }
157
158    /// Add MCP server
159    pub fn add_mcp_server(mut self, name: impl Into<String>, config: McpServerConfig) -> Self {
160        self.options.mcp_servers.insert(name.into(), config);
161        self
162    }
163
164    /// Set MCP tools
165    pub fn mcp_tools(mut self, tools: Vec<String>) -> Self {
166        self.options.mcp_tools = tools;
167        self
168    }
169
170    /// Set max turns
171    pub fn max_turns(mut self, turns: i32) -> Self {
172        self.options.max_turns = Some(turns);
173        self
174    }
175
176    /// Set max thinking tokens
177    pub fn max_thinking_tokens(mut self, tokens: i32) -> Self {
178        self.options.max_thinking_tokens = tokens;
179        self
180    }
181
182    /// Set model
183    pub fn model(mut self, model: impl Into<String>) -> Self {
184        self.options.model = Some(model.into());
185        self
186    }
187
188    /// Set working directory
189    pub fn cwd(mut self, path: impl Into<PathBuf>) -> Self {
190        self.options.cwd = Some(path.into());
191        self
192    }
193
194    /// Enable continue conversation
195    pub fn continue_conversation(mut self, enable: bool) -> Self {
196        self.options.continue_conversation = enable;
197        self
198    }
199
200    /// Set resume conversation ID
201    pub fn resume(mut self, id: impl Into<String>) -> Self {
202        self.options.resume = Some(id.into());
203        self
204    }
205
206    /// Set permission prompt tool name
207    pub fn permission_prompt_tool_name(mut self, name: impl Into<String>) -> Self {
208        self.options.permission_prompt_tool_name = Some(name.into());
209        self
210    }
211
212    /// Set settings file path
213    pub fn settings(mut self, settings: impl Into<String>) -> Self {
214        self.options.settings = Some(settings.into());
215        self
216    }
217
218    /// Add directories as working directories
219    pub fn add_dirs(mut self, dirs: Vec<PathBuf>) -> Self {
220        self.options.add_dirs = dirs;
221        self
222    }
223
224    /// Add a single directory as working directory
225    pub fn add_dir(mut self, dir: impl Into<PathBuf>) -> Self {
226        self.options.add_dirs.push(dir.into());
227        self
228    }
229
230    /// Add extra CLI arguments
231    pub fn extra_args(mut self, args: HashMap<String, Option<String>>) -> Self {
232        self.options.extra_args = args;
233        self
234    }
235
236    /// Add a single extra CLI argument
237    pub fn add_extra_arg(mut self, key: impl Into<String>, value: Option<String>) -> Self {
238        self.options.extra_args.insert(key.into(), value);
239        self
240    }
241
242    /// Build the options
243    pub fn build(self) -> ClaudeCodeOptions {
244        self.options
245    }
246}
247
248/// Main message type enum
249#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
250#[serde(tag = "type", rename_all = "lowercase")]
251pub enum Message {
252    /// User message
253    User {
254        /// Message content
255        message: UserMessage,
256    },
257    /// Assistant message
258    Assistant {
259        /// Message content
260        message: AssistantMessage,
261    },
262    /// System message
263    System {
264        /// Subtype of system message
265        subtype: String,
266        /// Additional data
267        data: serde_json::Value,
268    },
269    /// Result message indicating end of turn
270    Result {
271        /// Result subtype
272        subtype: String,
273        /// Duration in milliseconds
274        duration_ms: i64,
275        /// API duration in milliseconds
276        duration_api_ms: i64,
277        /// Whether an error occurred
278        is_error: bool,
279        /// Number of turns
280        num_turns: i32,
281        /// Session ID
282        session_id: String,
283        /// Total cost in USD
284        #[serde(skip_serializing_if = "Option::is_none")]
285        total_cost_usd: Option<f64>,
286        /// Usage statistics
287        #[serde(skip_serializing_if = "Option::is_none")]
288        usage: Option<serde_json::Value>,
289        /// Result message
290        #[serde(skip_serializing_if = "Option::is_none")]
291        result: Option<String>,
292    },
293}
294
295/// User message content
296#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
297pub struct UserMessage {
298    /// Message content
299    pub content: String,
300}
301
302/// Assistant message content
303#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
304pub struct AssistantMessage {
305    /// Content blocks
306    pub content: Vec<ContentBlock>,
307}
308
309/// Result message (re-export for convenience)  
310pub use Message::Result as ResultMessage;
311/// System message (re-export for convenience)
312pub use Message::System as SystemMessage;
313
314/// Content block types
315#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
316#[serde(untagged)]
317pub enum ContentBlock {
318    /// Text content
319    Text(TextContent),
320    /// Thinking content
321    Thinking(ThinkingContent),
322    /// Tool use request
323    ToolUse(ToolUseContent),
324    /// Tool result
325    ToolResult(ToolResultContent),
326}
327
328/// Text content block
329#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
330pub struct TextContent {
331    /// Text content
332    pub text: String,
333}
334
335/// Thinking content block
336#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
337pub struct ThinkingContent {
338    /// Thinking content
339    pub thinking: String,
340    /// Signature
341    pub signature: String,
342}
343
344/// Tool use content block
345#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
346pub struct ToolUseContent {
347    /// Tool use ID
348    pub id: String,
349    /// Tool name
350    pub name: String,
351    /// Tool input parameters
352    pub input: serde_json::Value,
353}
354
355/// Tool result content block
356#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
357pub struct ToolResultContent {
358    /// Tool use ID this result corresponds to
359    pub tool_use_id: String,
360    /// Result content
361    #[serde(skip_serializing_if = "Option::is_none")]
362    pub content: Option<ContentValue>,
363    /// Whether this is an error result
364    #[serde(skip_serializing_if = "Option::is_none")]
365    pub is_error: Option<bool>,
366}
367
368/// Content value for tool results
369#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
370#[serde(untagged)]
371pub enum ContentValue {
372    /// Text content
373    Text(String),
374    /// Structured content
375    Structured(Vec<serde_json::Value>),
376}
377
378/// User content structure for internal use
379#[derive(Debug, Clone, Serialize, Deserialize)]
380pub struct UserContent {
381    /// Role (always "user")
382    pub role: String,
383    /// Message content
384    pub content: String,
385}
386
387/// Assistant content structure for internal use
388#[derive(Debug, Clone, Serialize, Deserialize)]
389pub struct AssistantContent {
390    /// Role (always "assistant")
391    pub role: String,
392    /// Content blocks
393    pub content: Vec<ContentBlock>,
394}
395
396/// Control request types
397#[derive(Debug, Clone, Serialize, Deserialize)]
398#[serde(tag = "type", rename_all = "lowercase")]
399pub enum ControlRequest {
400    /// Interrupt the current operation
401    Interrupt {
402        /// Request ID
403        request_id: String,
404    },
405}
406
407/// Control response types
408#[derive(Debug, Clone, Serialize, Deserialize)]
409#[serde(tag = "type", rename_all = "lowercase")]
410pub enum ControlResponse {
411    /// Interrupt acknowledged
412    InterruptAck {
413        /// Request ID
414        request_id: String,
415        /// Whether interrupt was successful
416        success: bool,
417    },
418}
419
420#[cfg(test)]
421mod tests {
422    use super::*;
423
424    #[test]
425    fn test_permission_mode_serialization() {
426        let mode = PermissionMode::AcceptEdits;
427        let json = serde_json::to_string(&mode).unwrap();
428        assert_eq!(json, r#""acceptEdits""#);
429
430        let deserialized: PermissionMode = serde_json::from_str(&json).unwrap();
431        assert_eq!(deserialized, mode);
432
433        // Test Plan mode
434        let plan_mode = PermissionMode::Plan;
435        let plan_json = serde_json::to_string(&plan_mode).unwrap();
436        assert_eq!(plan_json, r#""plan""#);
437
438        let plan_deserialized: PermissionMode = serde_json::from_str(&plan_json).unwrap();
439        assert_eq!(plan_deserialized, plan_mode);
440    }
441
442    #[test]
443    fn test_message_serialization() {
444        let msg = Message::User {
445            message: UserMessage {
446                content: "Hello".to_string(),
447            },
448        };
449
450        let json = serde_json::to_string(&msg).unwrap();
451        assert!(json.contains(r#""type":"user""#));
452        assert!(json.contains(r#""content":"Hello""#));
453
454        let deserialized: Message = serde_json::from_str(&json).unwrap();
455        assert_eq!(deserialized, msg);
456    }
457
458    #[test]
459    fn test_options_builder() {
460        let options = ClaudeCodeOptions::builder()
461            .system_prompt("Test prompt")
462            .model("claude-3-opus")
463            .permission_mode(PermissionMode::AcceptEdits)
464            .allow_tool("read")
465            .allow_tool("write")
466            .max_turns(10)
467            .build();
468
469        assert_eq!(options.system_prompt, Some("Test prompt".to_string()));
470        assert_eq!(options.model, Some("claude-3-opus".to_string()));
471        assert_eq!(options.permission_mode, PermissionMode::AcceptEdits);
472        assert_eq!(options.allowed_tools, vec!["read", "write"]);
473        assert_eq!(options.max_turns, Some(10));
474    }
475
476    #[test]
477    fn test_extra_args() {
478        let mut extra_args = HashMap::new();
479        extra_args.insert("custom-flag".to_string(), Some("value".to_string()));
480        extra_args.insert("boolean-flag".to_string(), None);
481
482        let options = ClaudeCodeOptions::builder()
483            .extra_args(extra_args.clone())
484            .add_extra_arg("another-flag", Some("another-value".to_string()))
485            .build();
486
487        assert_eq!(options.extra_args.len(), 3);
488        assert_eq!(options.extra_args.get("custom-flag"), Some(&Some("value".to_string())));
489        assert_eq!(options.extra_args.get("boolean-flag"), Some(&None));
490        assert_eq!(options.extra_args.get("another-flag"), Some(&Some("another-value".to_string())));
491    }
492
493    #[test]
494    fn test_thinking_content_serialization() {
495        let thinking = ThinkingContent {
496            thinking: "Let me think about this...".to_string(),
497            signature: "sig123".to_string(),
498        };
499
500        let json = serde_json::to_string(&thinking).unwrap();
501        assert!(json.contains(r#""thinking":"Let me think about this...""#));
502        assert!(json.contains(r#""signature":"sig123""#));
503
504        let deserialized: ThinkingContent = serde_json::from_str(&json).unwrap();
505        assert_eq!(deserialized.thinking, thinking.thinking);
506        assert_eq!(deserialized.signature, thinking.signature);
507    }
508}