1use std::fmt;
2use std::str::FromStr;
3
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
22#[serde(rename_all = "lowercase")]
23pub enum Transport {
24 Stdio,
26 Http,
28 Sse,
30}
31
32impl fmt::Display for Transport {
33 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34 match self {
35 Self::Stdio => write!(f, "stdio"),
36 Self::Http => write!(f, "http"),
37 Self::Sse => write!(f, "sse"),
38 }
39 }
40}
41
42#[derive(Debug, Clone, thiserror::Error)]
44#[error("unknown transport: {0}")]
45pub struct TransportParseError(pub String);
46
47impl FromStr for Transport {
48 type Err = TransportParseError;
49
50 fn from_str(s: &str) -> Result<Self, Self::Err> {
51 match s {
52 "stdio" => Ok(Self::Stdio),
53 "http" => Ok(Self::Http),
54 "sse" => Ok(Self::Sse),
55 other => Err(TransportParseError(other.to_string())),
56 }
57 }
58}
59
60impl TryFrom<&str> for Transport {
61 type Error = TransportParseError;
62
63 fn try_from(s: &str) -> Result<Self, Self::Error> {
64 s.parse()
65 }
66}
67
68#[cfg(test)]
69mod transport_tests {
70 use super::*;
71
72 #[test]
73 fn from_str_accepts_known_variants() {
74 assert_eq!("stdio".parse::<Transport>().unwrap(), Transport::Stdio);
75 assert_eq!("http".parse::<Transport>().unwrap(), Transport::Http);
76 assert_eq!("sse".parse::<Transport>().unwrap(), Transport::Sse);
77 }
78
79 #[test]
80 fn from_str_returns_error_for_unknown() {
81 let err = "websocket".parse::<Transport>().unwrap_err();
82 assert!(err.to_string().contains("websocket"));
83 }
84
85 #[test]
86 fn try_from_str_returns_error_for_unknown() {
87 assert!(Transport::try_from("bogus").is_err());
88 }
89}
90
91#[derive(Debug, Clone, Copy, Default)]
93pub enum OutputFormat {
94 #[default]
96 Text,
97 Json,
99 StreamJson,
101}
102
103impl OutputFormat {
104 pub(crate) fn as_arg(&self) -> &'static str {
105 match self {
106 Self::Text => "text",
107 Self::Json => "json",
108 Self::StreamJson => "stream-json",
109 }
110 }
111}
112
113#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
115#[serde(rename_all = "camelCase")]
116pub enum PermissionMode {
117 #[default]
119 Default,
120 AcceptEdits,
122 #[deprecated(
134 since = "0.5.1",
135 note = "use claude_wrapper::dangerous::DangerousClient instead; \
136 direct BypassPermissions usage is a footgun and will go \
137 away in a future major release"
138 )]
139 BypassPermissions,
140 DontAsk,
142 Plan,
144 Auto,
146}
147
148impl PermissionMode {
149 pub(crate) fn as_arg(&self) -> &'static str {
150 match self {
151 Self::Default => "default",
152 Self::AcceptEdits => "acceptEdits",
153 #[allow(deprecated)]
154 Self::BypassPermissions => "bypassPermissions",
155 Self::DontAsk => "dontAsk",
156 Self::Plan => "plan",
157 Self::Auto => "auto",
158 }
159 }
160}
161
162#[derive(Debug, Clone, Copy, Default)]
164pub enum InputFormat {
165 #[default]
167 Text,
168 StreamJson,
170}
171
172impl InputFormat {
173 pub(crate) fn as_arg(&self) -> &'static str {
174 match self {
175 Self::Text => "text",
176 Self::StreamJson => "stream-json",
177 }
178 }
179}
180
181#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
183#[serde(rename_all = "lowercase")]
184pub enum Effort {
185 Low,
187 Medium,
189 High,
191 Xhigh,
193 Max,
195}
196
197impl Effort {
198 pub(crate) fn as_arg(&self) -> &'static str {
199 match self {
200 Self::Low => "low",
201 Self::Medium => "medium",
202 Self::High => "high",
203 Self::Xhigh => "xhigh",
204 Self::Max => "max",
205 }
206 }
207}
208
209#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
211pub enum Scope {
212 #[default]
214 Local,
215 User,
217 Project,
219 Managed,
223}
224
225impl Scope {
226 pub(crate) fn as_arg(&self) -> &'static str {
227 match self {
228 Self::Local => "local",
229 Self::User => "user",
230 Self::Project => "project",
231 Self::Managed => "managed",
232 }
233 }
234}
235
236#[cfg(feature = "json")]
253#[derive(Debug, Clone, Deserialize, Serialize)]
254#[serde(rename_all = "camelCase")]
255pub struct AuthStatus {
256 #[serde(default)]
258 pub logged_in: bool,
259 #[serde(default)]
261 pub auth_method: Option<String>,
262 #[serde(default)]
264 pub api_provider: Option<String>,
265 #[serde(default)]
267 pub email: Option<String>,
268 #[serde(default)]
270 pub org_id: Option<String>,
271 #[serde(default)]
273 pub org_name: Option<String>,
274 #[serde(default)]
276 pub subscription_type: Option<String>,
277 #[serde(flatten)]
279 pub extra: std::collections::HashMap<String, serde_json::Value>,
280}
281
282#[cfg(feature = "json")]
284#[derive(Debug, Clone, Deserialize, Serialize)]
285pub struct QueryMessage {
286 #[serde(default)]
288 pub role: String,
289 #[serde(default)]
291 pub content: serde_json::Value,
292 #[serde(flatten)]
294 pub extra: std::collections::HashMap<String, serde_json::Value>,
295}
296
297#[cfg(feature = "json")]
299#[derive(Debug, Clone, Deserialize, Serialize)]
300pub struct QueryResult {
301 #[serde(default)]
303 pub result: String,
304 #[serde(default)]
306 pub session_id: String,
307 #[serde(default, rename = "total_cost_usd", alias = "cost_usd")]
309 pub cost_usd: Option<f64>,
310 #[serde(default)]
312 pub duration_ms: Option<u64>,
313 #[serde(default)]
315 pub num_turns: Option<u32>,
316 #[serde(default)]
318 pub is_error: bool,
319 #[serde(flatten)]
321 pub extra: std::collections::HashMap<String, serde_json::Value>,
322}
323
324#[cfg(all(test, feature = "json"))]
325mod tests {
326 use super::*;
327
328 #[test]
329 fn query_result_deserializes_total_cost_usd() {
330 let json =
331 r#"{"result":"hello","session_id":"s1","total_cost_usd":0.042,"is_error":false}"#;
332 let qr: QueryResult = serde_json::from_str(json).unwrap();
333 assert_eq!(qr.cost_usd, Some(0.042));
334 }
335
336 #[test]
337 fn query_result_deserializes_cost_usd_alias() {
338 let json = r#"{"result":"hello","session_id":"s1","cost_usd":0.01,"is_error":false}"#;
339 let qr: QueryResult = serde_json::from_str(json).unwrap();
340 assert_eq!(qr.cost_usd, Some(0.01));
341 }
342
343 #[test]
344 fn query_result_missing_cost_defaults_to_none() {
345 let json = r#"{"result":"hello","session_id":"s1","is_error":false}"#;
346 let qr: QueryResult = serde_json::from_str(json).unwrap();
347 assert_eq!(qr.cost_usd, None);
348 }
349
350 #[test]
351 fn query_result_from_stream_result_event() {
352 let json = r#"{"type":"result","subtype":"success","result":"streamed","session_id":"sess-1","total_cost_usd":0.03,"num_turns":1,"is_error":false}"#;
356 let qr: QueryResult = serde_json::from_str(json).unwrap();
357 assert_eq!(qr.cost_usd, Some(0.03));
358 assert_eq!(qr.num_turns, Some(1));
359 assert_eq!(qr.session_id, "sess-1");
360 assert_eq!(qr.result, "streamed");
361 assert_eq!(
362 qr.extra.get("type").and_then(|v| v.as_str()),
363 Some("result")
364 );
365 }
366
367 #[test]
368 fn query_result_deserializes_num_turns() {
369 let json = r#"{"result":"done","session_id":"s2","total_cost_usd":0.1,"num_turns":5,"is_error":false}"#;
370 let qr: QueryResult = serde_json::from_str(json).unwrap();
371 assert_eq!(qr.num_turns, Some(5));
372 assert_eq!(qr.cost_usd, Some(0.1));
373 }
374
375 #[test]
376 fn query_result_serializes_as_total_cost_usd() {
377 let qr = QueryResult {
378 result: "ok".into(),
379 session_id: "s1".into(),
380 cost_usd: Some(0.05),
381 duration_ms: None,
382 num_turns: Some(3),
383 is_error: false,
384 extra: Default::default(),
385 };
386 let json = serde_json::to_string(&qr).unwrap();
387 assert!(json.contains("\"total_cost_usd\""));
388 assert!(json.contains("\"num_turns\""));
389 }
390}