1use serde::{Deserialize, Serialize};
6use std::path::PathBuf;
7use std::time::Duration;
8
9pub struct RunOptions {
11 pub prompt: String,
13 pub cwd: Option<PathBuf>,
15 pub session_id: Option<String>,
17 pub mcp_config: Option<PathBuf>,
19 pub timeout: Duration,
21 pub on_progress: Option<ProgressCallback>,
23}
24
25impl Default for RunOptions {
26 fn default() -> Self {
27 Self {
28 prompt: String::new(),
29 cwd: None,
30 session_id: None,
31 mcp_config: None,
32 timeout: Duration::from_secs(5 * 60),
33 on_progress: None,
34 }
35 }
36}
37
38pub type ProgressCallback = Box<dyn Fn(StreamEvent) + Send + Sync>;
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
43#[serde(rename_all = "camelCase")]
44pub struct RunResult {
45 pub session_id: String,
47 pub result: String,
49 pub is_error: bool,
51 pub duration_ms: u64,
53 pub duration_api_ms: u64,
55 pub num_turns: u32,
57 #[serde(skip_serializing_if = "Option::is_none")]
59 pub total_cost_usd: Option<f64>,
60 #[serde(skip_serializing_if = "Option::is_none")]
62 pub usage: Option<Usage>,
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
67#[serde(rename_all = "camelCase")]
68pub struct Usage {
69 pub input_tokens: u64,
70 pub output_tokens: u64,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
75#[serde(tag = "type", rename_all = "lowercase")]
76pub enum StreamEvent {
77 System(SystemEvent),
78 Assistant(AssistantEvent),
79 User(UserEvent),
80 Result(ResultEvent),
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct SystemEvent {
86 pub subtype: String,
87 #[serde(skip_serializing_if = "Option::is_none")]
88 pub data: Option<serde_json::Value>,
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct AssistantEvent {
94 pub message: AssistantMessage,
95}
96
97#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct AssistantMessage {
100 pub content: Vec<ContentBlock>,
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize)]
105pub struct UserEvent {
106 pub message: UserMessage,
107}
108
109#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct UserMessage {
112 pub content: String,
113}
114
115#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct ResultEvent {
118 pub subtype: String,
119 pub session_id: String,
120 pub result: String,
121 pub is_error: bool,
122 pub duration_ms: u64,
123 pub duration_api_ms: u64,
124 pub num_turns: u32,
125 #[serde(skip_serializing_if = "Option::is_none")]
126 pub total_cost_usd: Option<f64>,
127 #[serde(skip_serializing_if = "Option::is_none")]
128 pub usage: Option<RawUsage>,
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize)]
133pub struct RawUsage {
134 pub input_tokens: u64,
135 pub output_tokens: u64,
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize)]
140#[serde(tag = "type", rename_all = "snake_case")]
141pub enum ContentBlock {
142 Text {
143 text: String,
144 },
145 ToolUse {
146 id: String,
147 name: String,
148 input: serde_json::Value,
149 },
150 ToolResult {
151 tool_use_id: String,
152 content: String,
153 #[serde(skip_serializing_if = "Option::is_none")]
154 is_error: Option<bool>,
155 },
156}
157
158#[derive(Debug, thiserror::Error)]
160pub enum RunnerError {
161 #[error("Task cancelled")]
162 Cancelled,
163
164 #[error("Task timed out after {0:?}")]
165 Timeout(Duration),
166
167 #[error("Claude CLI failed: {0}")]
168 CliFailed(String),
169
170 #[error("No result from Claude CLI: {0}")]
171 NoResult(String),
172
173 #[error("IO error: {0}")]
174 Io(#[from] std::io::Error),
175
176 #[error("JSON parse error: {0}")]
177 Json(#[from] serde_json::Error),
178}