1use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use std::path::PathBuf;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
12#[serde(rename_all = "camelCase")]
13pub enum PermissionMode {
14 Default,
16 AcceptEdits,
18 Plan,
20 BypassPermissions,
22}
23
24impl Default for PermissionMode {
25 fn default() -> Self {
26 Self::Default
27 }
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
32#[serde(tag = "type", rename_all = "lowercase")]
33pub enum McpServerConfig {
34 Stdio {
36 command: String,
38 #[serde(skip_serializing_if = "Option::is_none")]
40 args: Option<Vec<String>>,
41 #[serde(skip_serializing_if = "Option::is_none")]
43 env: Option<HashMap<String, String>>,
44 },
45 Sse {
47 url: String,
49 #[serde(skip_serializing_if = "Option::is_none")]
51 headers: Option<HashMap<String, String>>,
52 },
53 Http {
55 url: String,
57 #[serde(skip_serializing_if = "Option::is_none")]
59 headers: Option<HashMap<String, String>>,
60 },
61}
62
63#[derive(Debug, Clone, Default)]
65pub struct ClaudeCodeOptions {
66 pub system_prompt: Option<String>,
68 pub append_system_prompt: Option<String>,
70 pub allowed_tools: Vec<String>,
72 pub disallowed_tools: Vec<String>,
74 pub permission_mode: PermissionMode,
76 pub mcp_servers: HashMap<String, McpServerConfig>,
78 pub mcp_tools: Vec<String>,
80 pub max_turns: Option<i32>,
82 pub max_thinking_tokens: i32,
84 pub model: Option<String>,
86 pub cwd: Option<PathBuf>,
88 pub continue_conversation: bool,
90 pub resume: Option<String>,
92 pub permission_prompt_tool_name: Option<String>,
94 pub settings: Option<String>,
96 pub add_dirs: Vec<PathBuf>,
98 pub extra_args: HashMap<String, Option<String>>,
100}
101
102impl ClaudeCodeOptions {
103 pub fn builder() -> ClaudeCodeOptionsBuilder {
105 ClaudeCodeOptionsBuilder::default()
106 }
107}
108
109#[derive(Debug, Default)]
111pub struct ClaudeCodeOptionsBuilder {
112 options: ClaudeCodeOptions,
113}
114
115impl ClaudeCodeOptionsBuilder {
116 pub fn system_prompt(mut self, prompt: impl Into<String>) -> Self {
118 self.options.system_prompt = Some(prompt.into());
119 self
120 }
121
122 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 pub fn allowed_tools(mut self, tools: Vec<String>) -> Self {
130 self.options.allowed_tools = tools;
131 self
132 }
133
134 pub fn allow_tool(mut self, tool: impl Into<String>) -> Self {
136 self.options.allowed_tools.push(tool.into());
137 self
138 }
139
140 pub fn disallowed_tools(mut self, tools: Vec<String>) -> Self {
142 self.options.disallowed_tools = tools;
143 self
144 }
145
146 pub fn disallow_tool(mut self, tool: impl Into<String>) -> Self {
148 self.options.disallowed_tools.push(tool.into());
149 self
150 }
151
152 pub fn permission_mode(mut self, mode: PermissionMode) -> Self {
154 self.options.permission_mode = mode;
155 self
156 }
157
158 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 pub fn mcp_tools(mut self, tools: Vec<String>) -> Self {
166 self.options.mcp_tools = tools;
167 self
168 }
169
170 pub fn max_turns(mut self, turns: i32) -> Self {
172 self.options.max_turns = Some(turns);
173 self
174 }
175
176 pub fn max_thinking_tokens(mut self, tokens: i32) -> Self {
178 self.options.max_thinking_tokens = tokens;
179 self
180 }
181
182 pub fn model(mut self, model: impl Into<String>) -> Self {
184 self.options.model = Some(model.into());
185 self
186 }
187
188 pub fn cwd(mut self, path: impl Into<PathBuf>) -> Self {
190 self.options.cwd = Some(path.into());
191 self
192 }
193
194 pub fn continue_conversation(mut self, enable: bool) -> Self {
196 self.options.continue_conversation = enable;
197 self
198 }
199
200 pub fn resume(mut self, id: impl Into<String>) -> Self {
202 self.options.resume = Some(id.into());
203 self
204 }
205
206 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 pub fn settings(mut self, settings: impl Into<String>) -> Self {
214 self.options.settings = Some(settings.into());
215 self
216 }
217
218 pub fn add_dirs(mut self, dirs: Vec<PathBuf>) -> Self {
220 self.options.add_dirs = dirs;
221 self
222 }
223
224 pub fn add_dir(mut self, dir: impl Into<PathBuf>) -> Self {
226 self.options.add_dirs.push(dir.into());
227 self
228 }
229
230 pub fn extra_args(mut self, args: HashMap<String, Option<String>>) -> Self {
232 self.options.extra_args = args;
233 self
234 }
235
236 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 pub fn build(self) -> ClaudeCodeOptions {
244 self.options
245 }
246}
247
248#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
250#[serde(tag = "type", rename_all = "lowercase")]
251pub enum Message {
252 User {
254 message: UserMessage,
256 },
257 Assistant {
259 message: AssistantMessage,
261 },
262 System {
264 subtype: String,
266 data: serde_json::Value,
268 },
269 Result {
271 subtype: String,
273 duration_ms: i64,
275 duration_api_ms: i64,
277 is_error: bool,
279 num_turns: i32,
281 session_id: String,
283 #[serde(skip_serializing_if = "Option::is_none")]
285 total_cost_usd: Option<f64>,
286 #[serde(skip_serializing_if = "Option::is_none")]
288 usage: Option<serde_json::Value>,
289 #[serde(skip_serializing_if = "Option::is_none")]
291 result: Option<String>,
292 },
293}
294
295#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
297pub struct UserMessage {
298 pub content: String,
300}
301
302#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
304pub struct AssistantMessage {
305 pub content: Vec<ContentBlock>,
307}
308
309pub use Message::Result as ResultMessage;
311pub use Message::System as SystemMessage;
313
314#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
316#[serde(untagged)]
317pub enum ContentBlock {
318 Text(TextContent),
320 Thinking(ThinkingContent),
322 ToolUse(ToolUseContent),
324 ToolResult(ToolResultContent),
326}
327
328#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
330pub struct TextContent {
331 pub text: String,
333}
334
335#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
337pub struct ThinkingContent {
338 pub thinking: String,
340 pub signature: String,
342}
343
344#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
346pub struct ToolUseContent {
347 pub id: String,
349 pub name: String,
351 pub input: serde_json::Value,
353}
354
355#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
357pub struct ToolResultContent {
358 pub tool_use_id: String,
360 #[serde(skip_serializing_if = "Option::is_none")]
362 pub content: Option<ContentValue>,
363 #[serde(skip_serializing_if = "Option::is_none")]
365 pub is_error: Option<bool>,
366}
367
368#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
370#[serde(untagged)]
371pub enum ContentValue {
372 Text(String),
374 Structured(Vec<serde_json::Value>),
376}
377
378#[derive(Debug, Clone, Serialize, Deserialize)]
380pub struct UserContent {
381 pub role: String,
383 pub content: String,
385}
386
387#[derive(Debug, Clone, Serialize, Deserialize)]
389pub struct AssistantContent {
390 pub role: String,
392 pub content: Vec<ContentBlock>,
394}
395
396#[derive(Debug, Clone, Serialize, Deserialize)]
398#[serde(tag = "type", rename_all = "lowercase")]
399pub enum ControlRequest {
400 Interrupt {
402 request_id: String,
404 },
405}
406
407#[derive(Debug, Clone, Serialize, Deserialize)]
409#[serde(tag = "type", rename_all = "lowercase")]
410pub enum ControlResponse {
411 InterruptAck {
413 request_id: String,
415 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 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}