1use serde::{Deserialize, Serialize};
6use typed_builder::TypedBuilder;
7
8#[derive(Debug, Clone, TypedBuilder, Serialize, Deserialize)]
32pub struct SessionOptions {
33 #[builder(default, setter(strip_option))]
35 pub model: Option<String>,
36
37 #[builder(default, setter(strip_option))]
39 pub permission_mode: Option<PermissionMode>,
40
41 #[builder(default, setter(strip_option))]
43 pub max_budget_usd: Option<f64>,
44
45 #[builder(default, setter(strip_option))]
47 pub max_turns: Option<u32>,
48
49 #[builder(default, setter(strip_option))]
51 pub max_thinking_tokens: Option<u32>,
52
53 #[builder(default, setter(strip_option))]
55 pub system_prompt: Option<String>,
56
57 #[builder(default = false)]
59 pub include_partial_messages: bool,
60}
61
62impl Default for SessionOptions {
63 fn default() -> Self {
64 Self {
65 model: None,
66 permission_mode: None,
67 max_budget_usd: None,
68 max_turns: None,
69 max_thinking_tokens: None,
70 system_prompt: None,
71 include_partial_messages: false,
72 }
73 }
74}
75
76impl From<SessionOptions> for crate::types::config::ClaudeAgentOptions {
77 fn from(options: SessionOptions) -> Self {
78 let permission_mode: Option<crate::types::config::PermissionMode> =
80 options.permission_mode.map(|pm| pm.into());
81
82 let system_prompt: Option<crate::types::config::SystemPrompt> =
84 options.system_prompt.map(|text| crate::types::config::SystemPrompt::Text(text));
85
86 match (options.model, permission_mode, options.max_budget_usd) {
90 (Some(model), Some(pm), Some(max_budget)) => {
91 crate::types::config::ClaudeAgentOptions::builder()
92 .model(model)
93 .permission_mode(pm)
94 .max_budget_usd(max_budget)
95 .max_turns(options.max_turns.unwrap_or(0))
96 .max_thinking_tokens(options.max_thinking_tokens.unwrap_or(0))
97 .system_prompt(system_prompt.unwrap_or(
98 crate::types::config::SystemPrompt::Text(String::new())
99 ))
100 .include_partial_messages(options.include_partial_messages)
101 .build()
102 }
103 (Some(model), Some(pm), None) => {
104 crate::types::config::ClaudeAgentOptions::builder()
105 .model(model)
106 .permission_mode(pm)
107 .max_turns(options.max_turns.unwrap_or(0))
108 .max_thinking_tokens(options.max_thinking_tokens.unwrap_or(0))
109 .system_prompt(system_prompt.unwrap_or(
110 crate::types::config::SystemPrompt::Text(String::new())
111 ))
112 .include_partial_messages(options.include_partial_messages)
113 .build()
114 }
115 (Some(model), None, Some(max_budget)) => {
116 crate::types::config::ClaudeAgentOptions::builder()
117 .model(model)
118 .max_budget_usd(max_budget)
119 .max_turns(options.max_turns.unwrap_or(0))
120 .max_thinking_tokens(options.max_thinking_tokens.unwrap_or(0))
121 .system_prompt(system_prompt.unwrap_or(
122 crate::types::config::SystemPrompt::Text(String::new())
123 ))
124 .include_partial_messages(options.include_partial_messages)
125 .build()
126 }
127 (Some(model), None, None) => {
128 crate::types::config::ClaudeAgentOptions::builder()
129 .model(model)
130 .max_turns(options.max_turns.unwrap_or(0))
131 .max_thinking_tokens(options.max_thinking_tokens.unwrap_or(0))
132 .system_prompt(system_prompt.unwrap_or(
133 crate::types::config::SystemPrompt::Text(String::new())
134 ))
135 .include_partial_messages(options.include_partial_messages)
136 .build()
137 }
138 (None, Some(pm), Some(max_budget)) => {
139 crate::types::config::ClaudeAgentOptions::builder()
140 .permission_mode(pm)
141 .max_budget_usd(max_budget)
142 .max_turns(options.max_turns.unwrap_or(0))
143 .max_thinking_tokens(options.max_thinking_tokens.unwrap_or(0))
144 .system_prompt(system_prompt.unwrap_or(
145 crate::types::config::SystemPrompt::Text(String::new())
146 ))
147 .include_partial_messages(options.include_partial_messages)
148 .build()
149 }
150 (None, Some(pm), None) => {
151 crate::types::config::ClaudeAgentOptions::builder()
152 .permission_mode(pm)
153 .max_turns(options.max_turns.unwrap_or(0))
154 .max_thinking_tokens(options.max_thinking_tokens.unwrap_or(0))
155 .system_prompt(system_prompt.unwrap_or(
156 crate::types::config::SystemPrompt::Text(String::new())
157 ))
158 .include_partial_messages(options.include_partial_messages)
159 .build()
160 }
161 (None, None, Some(max_budget)) => {
162 crate::types::config::ClaudeAgentOptions::builder()
163 .max_budget_usd(max_budget)
164 .max_turns(options.max_turns.unwrap_or(0))
165 .max_thinking_tokens(options.max_thinking_tokens.unwrap_or(0))
166 .system_prompt(system_prompt.unwrap_or(
167 crate::types::config::SystemPrompt::Text(String::new())
168 ))
169 .include_partial_messages(options.include_partial_messages)
170 .build()
171 }
172 (None, None, None) => {
173 crate::types::config::ClaudeAgentOptions::builder()
174 .max_turns(options.max_turns.unwrap_or(0))
175 .max_thinking_tokens(options.max_thinking_tokens.unwrap_or(0))
176 .system_prompt(system_prompt.unwrap_or(
177 crate::types::config::SystemPrompt::Text(String::new())
178 ))
179 .include_partial_messages(options.include_partial_messages)
180 .build()
181 }
182 }
183 }
184}
185
186#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
197pub enum PermissionMode {
198 Default,
200
201 AcceptEdits,
203
204 Plan,
206
207 BypassPermissions,
209}
210
211impl From<PermissionMode> for crate::types::config::PermissionMode {
212 fn from(mode: PermissionMode) -> Self {
213 match mode {
214 PermissionMode::Default => Self::Default,
215 PermissionMode::AcceptEdits => Self::AcceptEdits,
216 PermissionMode::Plan => Self::Plan,
217 PermissionMode::BypassPermissions => Self::BypassPermissions,
218 }
219 }
220}
221
222#[derive(Debug, Clone, Serialize, Deserialize)]
241pub struct PromptResult {
242 pub content: String,
244
245 pub input_tokens: u64,
247
248 pub output_tokens: u64,
250
251 pub model: Option<String>,
253}
254
255impl PromptResult {
256 pub fn total_tokens(&self) -> u64 {
258 self.input_tokens + self.output_tokens
259 }
260
261 pub fn estimated_cost_usd(&self) -> f64 {
266 let input_cost = (self.input_tokens as f64) / 1_000_000.0 * 3.0;
269 let output_cost = (self.output_tokens as f64) / 1_000_000.0 * 15.0;
270 input_cost + output_cost
271 }
272}
273
274#[derive(Debug, Clone, Serialize, Deserialize)]
285pub enum Message {
286 User {
288 content: String,
290 },
291
292 Assistant {
294 content: String,
296 },
297
298 ToolResult {
300 tool_name: String,
302 result: String,
304 },
305}
306
307impl Message {
308 pub fn as_text(&self) -> Option<&str> {
310 match self {
311 Message::User { content } => Some(content),
312 Message::Assistant { content } => Some(content),
313 Message::ToolResult { .. } => None,
314 }
315 }
316
317 pub fn is_user(&self) -> bool {
319 matches!(self, Message::User { .. })
320 }
321
322 pub fn is_assistant(&self) -> bool {
324 matches!(self, Message::Assistant { .. })
325 }
326
327 pub fn is_tool_result(&self) -> bool {
329 matches!(self, Message::ToolResult { .. })
330 }
331}
332
333impl From<crate::types::messages::Message> for Message {
334 fn from(msg: crate::types::messages::Message) -> Self {
335 match msg {
336 crate::types::messages::Message::User(user_msg) => {
337 let content = if let Some(text) = user_msg.text {
340 text
341 } else if let Some(blocks) = user_msg.content {
342 blocks
343 .iter()
344 .filter_map(|block| match block {
345 crate::types::messages::ContentBlock::Text(text) => Some(text.text.clone()),
346 _ => None,
347 })
348 .collect::<Vec<_>>()
349 .join("\n")
350 } else {
351 String::new()
352 };
353
354 Message::User { content }
355 }
356 crate::types::messages::Message::Assistant(assist_msg) => {
357 let content = assist_msg
359 .message
360 .content
361 .iter()
362 .filter_map(|block| match block {
363 crate::types::messages::ContentBlock::Text(text) => Some(text.text.clone()),
364 _ => None,
365 })
366 .collect::<Vec<_>>()
367 .join("\n");
368
369 Message::Assistant { content }
370 }
371 _ => Message::Assistant {
372 content: String::new(),
373 },
374 }
375 }
376}
377
378#[cfg(test)]
379mod tests {
380 use super::*;
381
382 #[test]
383 fn test_session_options_builder() {
384 let options = SessionOptions::builder()
385 .model("claude-sonnet-4-20250514".to_string())
386 .max_turns(5)
387 .build();
388
389 assert_eq!(options.model, Some("claude-sonnet-4-20250514".to_string()));
390 assert_eq!(options.max_turns, Some(5));
391 }
392
393 #[test]
394 fn test_permission_mode_conversion() {
395 let mode = PermissionMode::BypassPermissions;
396 let converted: crate::types::config::PermissionMode = mode.into();
397 assert!(matches!(
398 converted,
399 crate::types::config::PermissionMode::BypassPermissions
400 ));
401 }
402
403 #[test]
404 fn test_prompt_result_total_tokens() {
405 let result = PromptResult {
406 content: "Test".to_string(),
407 input_tokens: 100,
408 output_tokens: 50,
409 model: None,
410 };
411
412 assert_eq!(result.total_tokens(), 150);
413 }
414
415 #[test]
416 fn test_message_is_user() {
417 let msg = Message::User {
418 content: "Hello".to_string(),
419 };
420
421 assert!(msg.is_user());
422 assert!(!msg.is_assistant());
423 assert_eq!(msg.as_text(), Some("Hello"));
424 }
425
426 #[test]
427 fn test_message_is_assistant() {
428 let msg = Message::Assistant {
429 content: "Hi there!".to_string(),
430 };
431
432 assert!(!msg.is_user());
433 assert!(msg.is_assistant());
434 assert_eq!(msg.as_text(), Some("Hi there!"));
435 }
436
437 #[test]
438 fn test_message_is_tool_result() {
439 let msg = Message::ToolResult {
440 tool_name: "calculator".to_string(),
441 result: "42".to_string(),
442 };
443
444 assert!(msg.is_tool_result());
445 assert!(!msg.is_user());
446 assert!(!msg.is_assistant());
447 assert_eq!(msg.as_text(), None);
448 }
449
450 #[test]
451 fn test_prompt_result_cost_estimation() {
452 let result = PromptResult {
453 content: "Test".to_string(),
454 input_tokens: 1_000_000, output_tokens: 1_000_000, model: None,
457 };
458
459 let cost = result.estimated_cost_usd();
463 assert!((cost - 18.0).abs() < 0.01); }
465}