use serde::{Deserialize, Serialize};
use typed_builder::TypedBuilder;
#[derive(Debug, Clone, TypedBuilder, Serialize, Deserialize)]
#[derive(Default)]
pub struct SessionOptions {
#[builder(default, setter(strip_option))]
pub model: Option<String>,
#[builder(default, setter(strip_option))]
pub permission_mode: Option<PermissionMode>,
#[builder(default, setter(strip_option))]
pub max_budget_usd: Option<f64>,
#[builder(default, setter(strip_option))]
pub max_turns: Option<u32>,
#[builder(default, setter(strip_option))]
pub max_thinking_tokens: Option<u32>,
#[builder(default, setter(strip_option))]
pub system_prompt: Option<String>,
#[builder(default = false)]
pub include_partial_messages: bool,
}
impl From<SessionOptions> for crate::types::config::ClaudeAgentOptions {
fn from(options: SessionOptions) -> Self {
let permission_mode: Option<crate::types::config::PermissionMode> =
options.permission_mode.map(|pm| pm.into());
let system_prompt: Option<crate::types::config::SystemPrompt> =
options.system_prompt.map(crate::types::config::SystemPrompt::Text);
match (options.model, permission_mode, options.max_budget_usd) {
(Some(model), Some(pm), Some(max_budget)) => {
crate::types::config::ClaudeAgentOptions::builder()
.model(model)
.permission_mode(pm)
.max_budget_usd(max_budget)
.max_turns(options.max_turns.unwrap_or(0))
.max_thinking_tokens(options.max_thinking_tokens.unwrap_or(0))
.system_prompt(system_prompt.unwrap_or(
crate::types::config::SystemPrompt::Text(String::new())
))
.include_partial_messages(options.include_partial_messages)
.build()
}
(Some(model), Some(pm), None) => {
crate::types::config::ClaudeAgentOptions::builder()
.model(model)
.permission_mode(pm)
.max_turns(options.max_turns.unwrap_or(0))
.max_thinking_tokens(options.max_thinking_tokens.unwrap_or(0))
.system_prompt(system_prompt.unwrap_or(
crate::types::config::SystemPrompt::Text(String::new())
))
.include_partial_messages(options.include_partial_messages)
.build()
}
(Some(model), None, Some(max_budget)) => {
crate::types::config::ClaudeAgentOptions::builder()
.model(model)
.max_budget_usd(max_budget)
.max_turns(options.max_turns.unwrap_or(0))
.max_thinking_tokens(options.max_thinking_tokens.unwrap_or(0))
.system_prompt(system_prompt.unwrap_or(
crate::types::config::SystemPrompt::Text(String::new())
))
.include_partial_messages(options.include_partial_messages)
.build()
}
(Some(model), None, None) => {
crate::types::config::ClaudeAgentOptions::builder()
.model(model)
.max_turns(options.max_turns.unwrap_or(0))
.max_thinking_tokens(options.max_thinking_tokens.unwrap_or(0))
.system_prompt(system_prompt.unwrap_or(
crate::types::config::SystemPrompt::Text(String::new())
))
.include_partial_messages(options.include_partial_messages)
.build()
}
(None, Some(pm), Some(max_budget)) => {
crate::types::config::ClaudeAgentOptions::builder()
.permission_mode(pm)
.max_budget_usd(max_budget)
.max_turns(options.max_turns.unwrap_or(0))
.max_thinking_tokens(options.max_thinking_tokens.unwrap_or(0))
.system_prompt(system_prompt.unwrap_or(
crate::types::config::SystemPrompt::Text(String::new())
))
.include_partial_messages(options.include_partial_messages)
.build()
}
(None, Some(pm), None) => {
crate::types::config::ClaudeAgentOptions::builder()
.permission_mode(pm)
.max_turns(options.max_turns.unwrap_or(0))
.max_thinking_tokens(options.max_thinking_tokens.unwrap_or(0))
.system_prompt(system_prompt.unwrap_or(
crate::types::config::SystemPrompt::Text(String::new())
))
.include_partial_messages(options.include_partial_messages)
.build()
}
(None, None, Some(max_budget)) => {
crate::types::config::ClaudeAgentOptions::builder()
.max_budget_usd(max_budget)
.max_turns(options.max_turns.unwrap_or(0))
.max_thinking_tokens(options.max_thinking_tokens.unwrap_or(0))
.system_prompt(system_prompt.unwrap_or(
crate::types::config::SystemPrompt::Text(String::new())
))
.include_partial_messages(options.include_partial_messages)
.build()
}
(None, None, None) => {
crate::types::config::ClaudeAgentOptions::builder()
.max_turns(options.max_turns.unwrap_or(0))
.max_thinking_tokens(options.max_thinking_tokens.unwrap_or(0))
.system_prompt(system_prompt.unwrap_or(
crate::types::config::SystemPrompt::Text(String::new())
))
.include_partial_messages(options.include_partial_messages)
.build()
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum PermissionMode {
Default,
AcceptEdits,
Plan,
BypassPermissions,
}
impl From<PermissionMode> for crate::types::config::PermissionMode {
fn from(mode: PermissionMode) -> Self {
match mode {
PermissionMode::Default => Self::Default,
PermissionMode::AcceptEdits => Self::AcceptEdits,
PermissionMode::Plan => Self::Plan,
PermissionMode::BypassPermissions => Self::BypassPermissions,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PromptResult {
pub content: String,
pub input_tokens: u64,
pub output_tokens: u64,
pub model: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub buffer_metrics: Option<crate::internal::transport::BufferMetricsSnapshot>,
}
impl PromptResult {
pub fn total_tokens(&self) -> u64 {
self.input_tokens + self.output_tokens
}
pub fn estimated_cost_usd(&self) -> f64 {
let input_cost = (self.input_tokens as f64) / 1_000_000.0 * 3.0;
let output_cost = (self.output_tokens as f64) / 1_000_000.0 * 15.0;
input_cost + output_cost
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Message {
User {
content: String,
},
Assistant {
content: String,
},
ToolResult {
tool_name: String,
result: String,
},
}
impl Message {
pub fn as_text(&self) -> Option<&str> {
match self {
Message::User { content } => Some(content),
Message::Assistant { content } => Some(content),
Message::ToolResult { .. } => None,
}
}
pub fn is_user(&self) -> bool {
matches!(self, Message::User { .. })
}
pub fn is_assistant(&self) -> bool {
matches!(self, Message::Assistant { .. })
}
pub fn is_tool_result(&self) -> bool {
matches!(self, Message::ToolResult { .. })
}
}
impl From<crate::types::messages::Message> for Message {
fn from(msg: crate::types::messages::Message) -> Self {
match msg {
crate::types::messages::Message::User(user_msg) => {
let content = if let Some(text) = user_msg.text {
text
} else if let Some(blocks) = user_msg.content {
blocks
.iter()
.filter_map(|block| match block {
crate::types::messages::ContentBlock::Text(text) => Some(text.text.clone()),
_ => None,
})
.collect::<Vec<_>>()
.join("\n")
} else {
String::new()
};
Message::User { content }
}
crate::types::messages::Message::Assistant(assist_msg) => {
let content = assist_msg
.message
.content
.iter()
.filter_map(|block| match block {
crate::types::messages::ContentBlock::Text(text) => Some(text.text.clone()),
_ => None,
})
.collect::<Vec<_>>()
.join("\n");
Message::Assistant { content }
}
_ => Message::Assistant {
content: String::new(),
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_session_options_builder() {
let options = SessionOptions::builder()
.model("claude-sonnet-4-20250514".to_string())
.max_turns(5)
.build();
assert_eq!(options.model, Some("claude-sonnet-4-20250514".to_string()));
assert_eq!(options.max_turns, Some(5));
}
#[test]
fn test_permission_mode_conversion() {
let mode = PermissionMode::BypassPermissions;
let converted: crate::types::config::PermissionMode = mode.into();
assert!(matches!(
converted,
crate::types::config::PermissionMode::BypassPermissions
));
}
#[test]
fn test_prompt_result_total_tokens() {
let result = PromptResult {
content: "Test".to_string(),
input_tokens: 100,
output_tokens: 50,
model: None,
buffer_metrics: None,
};
assert_eq!(result.total_tokens(), 150);
}
#[test]
fn test_message_is_user() {
let msg = Message::User {
content: "Hello".to_string(),
};
assert!(msg.is_user());
assert!(!msg.is_assistant());
assert_eq!(msg.as_text(), Some("Hello"));
}
#[test]
fn test_message_is_assistant() {
let msg = Message::Assistant {
content: "Hi there!".to_string(),
};
assert!(!msg.is_user());
assert!(msg.is_assistant());
assert_eq!(msg.as_text(), Some("Hi there!"));
}
#[test]
fn test_message_is_tool_result() {
let msg = Message::ToolResult {
tool_name: "calculator".to_string(),
result: "42".to_string(),
};
assert!(msg.is_tool_result());
assert!(!msg.is_user());
assert!(!msg.is_assistant());
assert_eq!(msg.as_text(), None);
}
#[test]
fn test_prompt_result_cost_estimation() {
let result = PromptResult {
content: "Test".to_string(),
input_tokens: 1_000_000, output_tokens: 1_000_000, model: None,
buffer_metrics: None,
};
let cost = result.estimated_cost_usd();
assert!((cost - 18.0).abs() < 0.01); }
}