Skip to main content

claude_sdk/
types.rs

1//! Type definitions for Claude API requests and responses.
2//!
3//! This module contains all the core types used for communicating with the Claude API:
4//!
5//! - [`MessagesRequest`] - The main request type for sending messages
6//! - [`MessagesResponse`] - Response from the Messages API
7//! - [`Message`] - Conversation messages with user/assistant roles
8//! - [`ContentBlock`] - Text, images, documents, tool calls, and more
9//! - [`CustomTool`] - Custom client-side tool definitions
10//! - [`ToolDefinition`] - Enum wrapping custom and server tools
11//! - [`ToolChoice`] - Control how Claude uses tools
12//!
13//! # Example
14//!
15//! ```rust
16//! use claude_sdk::types::{MessagesRequest, Message, CustomTool, ToolChoice};
17//! use serde_json::json;
18//!
19//! // Create a basic request
20//! let request = MessagesRequest::new(
21//!     "claude-sonnet-4-5-20250929",
22//!     1024,
23//!     vec![Message::user("Hello!")],
24//! )
25//! .with_system("You are a helpful assistant.")
26//! .with_temperature(0.7);
27//! ```
28
29use serde::{Deserialize, Serialize};
30
31/// Container metadata in API response
32#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct Container {
34    /// Container ID for reuse
35    pub id: String,
36    /// When the container expires (ISO 8601)
37    pub expires_at: String,
38}
39
40/// Role in a conversation
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
42#[serde(rename_all = "lowercase")]
43pub enum Role {
44    User,
45    Assistant,
46}
47
48/// Content block in a message
49///
50/// Known content block types are deserialized into their respective variants.
51/// Unrecognized types (e.g., new API features) are captured in [`ContentBlock::Unknown`]
52/// instead of causing a deserialization error.
53#[derive(Debug, Clone, Serialize)]
54#[serde(tag = "type", rename_all = "snake_case")]
55pub enum ContentBlock {
56    /// Text content
57    Text {
58        text: String,
59        #[serde(skip_serializing_if = "Option::is_none")]
60        cache_control: Option<CacheControl>,
61        /// Citations (appears in responses when using search_result blocks)
62        #[serde(skip_serializing_if = "Option::is_none")]
63        citations: Option<Vec<Citation>>,
64    },
65    /// Image content
66    Image {
67        source: ImageSource,
68        #[serde(skip_serializing_if = "Option::is_none")]
69        cache_control: Option<CacheControl>,
70    },
71    /// Document content (PDFs, text files)
72    ///
73    /// Requires beta header: `anthropic-beta: files-api-2025-04-14`
74    Document {
75        source: DocumentSource,
76        #[serde(skip_serializing_if = "Option::is_none")]
77        title: Option<String>,
78        #[serde(skip_serializing_if = "Option::is_none")]
79        context: Option<String>,
80        #[serde(skip_serializing_if = "Option::is_none")]
81        citations: Option<CitationConfig>,
82        #[serde(skip_serializing_if = "Option::is_none")]
83        cache_control: Option<CacheControl>,
84    },
85    /// Tool use request from the assistant
86    ToolUse {
87        id: String,
88        name: String,
89        input: serde_json::Value,
90        /// Which system invoked this tool ("direct", "code_execution_20250825", etc.)
91        #[serde(skip_serializing_if = "Option::is_none")]
92        caller: Option<String>,
93        #[serde(skip_serializing_if = "Option::is_none")]
94        cache_control: Option<CacheControl>,
95    },
96    /// Tool result from the user
97    ToolResult {
98        tool_use_id: String,
99        #[serde(skip_serializing_if = "Option::is_none")]
100        content: Option<ToolResultContent>,
101        #[serde(skip_serializing_if = "Option::is_none")]
102        is_error: Option<bool>,
103    },
104    /// Thinking block from extended thinking
105    ///
106    /// Contains Claude's step-by-step reasoning process.
107    /// Appears when extended thinking is enabled.
108    Thinking {
109        thinking: String,
110        #[serde(skip_serializing_if = "Option::is_none")]
111        signature: Option<String>,
112    },
113    /// Redacted thinking block
114    ///
115    /// Contains encrypted thinking that was flagged by safety systems.
116    /// Must be passed back unmodified in multi-turn conversations.
117    RedactedThinking { data: String },
118    /// Search result for RAG with automatic citations
119    ///
120    /// Supported in: Opus 4.5, Opus 4.1, Opus 4, Sonnet 4.5, Sonnet 4, Haiku 3.5
121    SearchResult {
122        source: String,
123        title: String,
124        content: Vec<TextBlock>,
125        #[serde(skip_serializing_if = "Option::is_none")]
126        citations: Option<CitationConfig>,
127        #[serde(skip_serializing_if = "Option::is_none")]
128        cache_control: Option<CacheControl>,
129    },
130    /// Server-side tool invocation
131    ServerToolUse {
132        id: String,
133        name: String,
134        input: serde_json::Value,
135        #[serde(skip_serializing_if = "Option::is_none")]
136        cache_control: Option<CacheControl>,
137    },
138    /// Web search tool result
139    WebSearchToolResult {
140        tool_use_id: String,
141        content: serde_json::Value,
142        #[serde(skip_serializing_if = "Option::is_none")]
143        cache_control: Option<CacheControl>,
144    },
145    /// Code execution tool result
146    CodeExecutionToolResult {
147        tool_use_id: String,
148        content: serde_json::Value,
149        #[serde(skip_serializing_if = "Option::is_none")]
150        cache_control: Option<CacheControl>,
151    },
152    /// File uploaded to a container during code execution
153    ContainerUpload {
154        file_id: String,
155        #[serde(skip_serializing_if = "Option::is_none")]
156        cache_control: Option<CacheControl>,
157    },
158    /// System instruction injected mid-conversation
159    MidConvSystem {
160        content: Vec<TextBlock>,
161        #[serde(skip_serializing_if = "Option::is_none")]
162        cache_control: Option<CacheControl>,
163    },
164    /// Unknown content block type (forward compatibility)
165    ///
166    /// When the API returns a content block type this SDK doesn't
167    /// recognize, it's captured here rather than causing a deserialization error.
168    #[serde(untagged)]
169    Unknown {
170        /// The `type` field value
171        block_type: String,
172        /// Raw JSON of the unknown block
173        data: serde_json::Value,
174    },
175}
176
177/// Content of a tool result -- either plain text or structured content blocks
178#[derive(Debug, Clone, Serialize, Deserialize)]
179#[serde(untagged)]
180pub enum ToolResultContent {
181    /// Plain text result
182    Text(String),
183    /// Array of content blocks
184    Blocks(Vec<ContentBlock>),
185}
186
187/// Private helper enum for deserialization of known ContentBlock variants.
188///
189/// Mirrors all known variants of [`ContentBlock`] exactly, with derived
190/// `Deserialize`. Used by the custom `Deserialize` impl on `ContentBlock` to
191/// avoid infinite recursion (since `ContentBlock` can't derive `Deserialize`
192/// due to the `Unknown` catch-all variant needing to capture raw JSON data).
193#[derive(Deserialize)]
194#[serde(tag = "type", rename_all = "snake_case")]
195enum ContentBlockHelper {
196    Text {
197        text: String,
198        cache_control: Option<CacheControl>,
199        citations: Option<Vec<Citation>>,
200    },
201    Image {
202        source: ImageSource,
203        cache_control: Option<CacheControl>,
204    },
205    Document {
206        source: DocumentSource,
207        title: Option<String>,
208        context: Option<String>,
209        citations: Option<CitationConfig>,
210        cache_control: Option<CacheControl>,
211    },
212    ToolUse {
213        id: String,
214        name: String,
215        input: serde_json::Value,
216        caller: Option<String>,
217        cache_control: Option<CacheControl>,
218    },
219    ToolResult {
220        tool_use_id: String,
221        content: Option<ToolResultContent>,
222        is_error: Option<bool>,
223    },
224    Thinking {
225        thinking: String,
226        signature: Option<String>,
227    },
228    RedactedThinking {
229        data: String,
230    },
231    SearchResult {
232        source: String,
233        title: String,
234        content: Vec<TextBlock>,
235        citations: Option<CitationConfig>,
236        cache_control: Option<CacheControl>,
237    },
238    ServerToolUse {
239        id: String,
240        name: String,
241        input: serde_json::Value,
242        cache_control: Option<CacheControl>,
243    },
244    WebSearchToolResult {
245        tool_use_id: String,
246        content: serde_json::Value,
247        cache_control: Option<CacheControl>,
248    },
249    CodeExecutionToolResult {
250        tool_use_id: String,
251        content: serde_json::Value,
252        cache_control: Option<CacheControl>,
253    },
254    ContainerUpload {
255        file_id: String,
256        cache_control: Option<CacheControl>,
257    },
258    MidConvSystem {
259        content: Vec<TextBlock>,
260        cache_control: Option<CacheControl>,
261    },
262}
263
264impl<'de> serde::Deserialize<'de> for ContentBlock {
265    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
266    where
267        D: serde::Deserializer<'de>,
268    {
269        let value = serde_json::Value::deserialize(deserializer)?;
270
271        // Try to deserialize as a known variant via the helper enum
272        match serde_json::from_value::<ContentBlockHelper>(value.clone()) {
273            Ok(helper) => Ok(match helper {
274                ContentBlockHelper::Text {
275                    text,
276                    cache_control,
277                    citations,
278                } => ContentBlock::Text {
279                    text,
280                    cache_control,
281                    citations,
282                },
283                ContentBlockHelper::Image {
284                    source,
285                    cache_control,
286                } => ContentBlock::Image {
287                    source,
288                    cache_control,
289                },
290                ContentBlockHelper::Document {
291                    source,
292                    title,
293                    context,
294                    citations,
295                    cache_control,
296                } => ContentBlock::Document {
297                    source,
298                    title,
299                    context,
300                    citations,
301                    cache_control,
302                },
303                ContentBlockHelper::ToolUse {
304                    id,
305                    name,
306                    input,
307                    caller,
308                    cache_control,
309                } => ContentBlock::ToolUse {
310                    id,
311                    name,
312                    input,
313                    caller,
314                    cache_control,
315                },
316                ContentBlockHelper::ToolResult {
317                    tool_use_id,
318                    content,
319                    is_error,
320                } => ContentBlock::ToolResult {
321                    tool_use_id,
322                    content,
323                    is_error,
324                },
325                ContentBlockHelper::Thinking {
326                    thinking,
327                    signature,
328                } => ContentBlock::Thinking {
329                    thinking,
330                    signature,
331                },
332                ContentBlockHelper::RedactedThinking { data } => {
333                    ContentBlock::RedactedThinking { data }
334                }
335                ContentBlockHelper::SearchResult {
336                    source,
337                    title,
338                    content,
339                    citations,
340                    cache_control,
341                } => ContentBlock::SearchResult {
342                    source,
343                    title,
344                    content,
345                    citations,
346                    cache_control,
347                },
348                ContentBlockHelper::ServerToolUse {
349                    id,
350                    name,
351                    input,
352                    cache_control,
353                } => ContentBlock::ServerToolUse {
354                    id,
355                    name,
356                    input,
357                    cache_control,
358                },
359                ContentBlockHelper::WebSearchToolResult {
360                    tool_use_id,
361                    content,
362                    cache_control,
363                } => ContentBlock::WebSearchToolResult {
364                    tool_use_id,
365                    content,
366                    cache_control,
367                },
368                ContentBlockHelper::CodeExecutionToolResult {
369                    tool_use_id,
370                    content,
371                    cache_control,
372                } => ContentBlock::CodeExecutionToolResult {
373                    tool_use_id,
374                    content,
375                    cache_control,
376                },
377                ContentBlockHelper::ContainerUpload {
378                    file_id,
379                    cache_control,
380                } => ContentBlock::ContainerUpload {
381                    file_id,
382                    cache_control,
383                },
384                ContentBlockHelper::MidConvSystem {
385                    content,
386                    cache_control,
387                } => ContentBlock::MidConvSystem {
388                    content,
389                    cache_control,
390                },
391            }),
392            Err(_) => {
393                // Unknown type -- extract the type field and capture the raw data
394                let block_type = value
395                    .get("type")
396                    .and_then(|t| t.as_str())
397                    .unwrap_or("unknown")
398                    .to_string();
399                Ok(ContentBlock::Unknown {
400                    block_type,
401                    data: value,
402                })
403            }
404        }
405    }
406}
407
408/// Text block for search result content
409#[derive(Debug, Clone, Serialize, Deserialize)]
410pub struct TextBlock {
411    #[serde(rename = "type")]
412    pub block_type: String, // Always "text"
413    pub text: String,
414}
415
416/// Image source for vision
417#[derive(Debug, Clone, Serialize, Deserialize)]
418#[serde(tag = "type", rename_all = "snake_case")]
419pub enum ImageSource {
420    /// Base64-encoded image
421    Base64 { media_type: String, data: String },
422    /// Image URL
423    Url { url: String },
424    /// File ID from Files API
425    ///
426    /// Requires beta header: `anthropic-beta: files-api-2025-04-14`
427    File { file_id: String },
428}
429
430/// Document source
431#[derive(Debug, Clone, Serialize, Deserialize)]
432#[serde(tag = "type", rename_all = "snake_case")]
433pub enum DocumentSource {
434    /// File ID from Files API
435    File { file_id: String },
436    /// Inline text document
437    Text { media_type: String, data: String },
438}
439
440/// Citation configuration for documents and search results
441#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
442pub struct CitationConfig {
443    pub enabled: bool,
444}
445
446/// Citation location in a response
447///
448/// Claude automatically includes these when using search_result blocks
449#[derive(Debug, Clone, Serialize, Deserialize)]
450pub struct Citation {
451    #[serde(rename = "type")]
452    pub citation_type: String, // "search_result_location"
453
454    pub source: String,
455
456    #[serde(skip_serializing_if = "Option::is_none")]
457    pub title: Option<String>,
458
459    pub cited_text: String,
460
461    pub search_result_index: usize,
462
463    pub start_block_index: usize,
464
465    pub end_block_index: usize,
466}
467
468/// Cache control for prompt caching
469#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
470pub struct CacheControl {
471    #[serde(rename = "type")]
472    pub cache_type: CacheType,
473
474    /// TTL for cached content
475    #[serde(skip_serializing_if = "Option::is_none")]
476    pub ttl: Option<CacheTtl>,
477}
478
479#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
480#[serde(rename_all = "lowercase")]
481pub enum CacheType {
482    Ephemeral,
483}
484
485/// Cache TTL duration
486#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
487pub enum CacheTtl {
488    /// 5-minute TTL (default)
489    #[serde(rename = "5m")]
490    FiveMinutes,
491    /// 1-hour TTL
492    #[serde(rename = "1h")]
493    OneHour,
494}
495
496impl CacheControl {
497    /// Create an ephemeral cache control
498    pub fn ephemeral() -> Self {
499        Self {
500            cache_type: CacheType::Ephemeral,
501            ttl: None,
502        }
503    }
504
505    /// Create an ephemeral cache control with a specific TTL
506    pub fn ephemeral_with_ttl(ttl: CacheTtl) -> Self {
507        Self {
508            cache_type: CacheType::Ephemeral,
509            ttl: Some(ttl),
510        }
511    }
512}
513
514/// A message in the conversation
515#[derive(Debug, Clone, Serialize, Deserialize)]
516pub struct Message {
517    pub role: Role,
518    pub content: Vec<ContentBlock>,
519}
520
521impl Message {
522    /// Create a user message with text content
523    pub fn user(text: impl Into<String>) -> Self {
524        Self {
525            role: Role::User,
526            content: vec![ContentBlock::Text {
527                text: text.into(),
528                cache_control: None,
529                citations: None,
530            }],
531        }
532    }
533
534    /// Create an assistant message with text content
535    pub fn assistant(text: impl Into<String>) -> Self {
536        Self {
537            role: Role::Assistant,
538            content: vec![ContentBlock::Text {
539                text: text.into(),
540                cache_control: None,
541                citations: None,
542            }],
543        }
544    }
545
546    /// Create a user message with a tool result
547    pub fn tool_result(tool_use_id: impl Into<String>, content: impl Into<String>) -> Self {
548        Self {
549            role: Role::User,
550            content: vec![ContentBlock::ToolResult {
551                tool_use_id: tool_use_id.into(),
552                content: Some(ToolResultContent::Text(content.into())),
553                is_error: None,
554            }],
555        }
556    }
557}
558
559/// System prompt format
560#[derive(Debug, Clone, Serialize, Deserialize)]
561#[serde(untagged)]
562pub enum SystemPrompt {
563    String(String),
564    Blocks(Vec<SystemBlock>),
565}
566
567#[derive(Debug, Clone, Serialize, Deserialize)]
568pub struct SystemBlock {
569    #[serde(rename = "type")]
570    pub block_type: String,
571    pub text: String,
572    #[serde(skip_serializing_if = "Option::is_none")]
573    pub cache_control: Option<CacheControl>,
574}
575
576/// Custom client-side tool definition
577///
578/// Defines a tool with a name, description, and JSON Schema for its inputs.
579/// Claude can call this tool and the client handles execution.
580///
581/// # Example
582///
583/// ```rust
584/// use claude_sdk::CustomTool;
585/// use serde_json::json;
586///
587/// let tool = CustomTool::new(
588///     "get_weather",
589///     "Get weather for a location",
590///     json!({
591///         "type": "object",
592///         "properties": {
593///             "location": { "type": "string" }
594///         },
595///         "required": ["location"]
596///     }),
597/// )
598/// .programmatic();
599/// ```
600#[derive(Debug, Clone, Serialize, Deserialize)]
601pub struct CustomTool {
602    pub name: String,
603    pub description: String,
604    pub input_schema: serde_json::Value,
605
606    /// If true, Claude will use this tool programmatically without asking the user
607    #[serde(skip_serializing_if = "Option::is_none")]
608    pub disable_user_input: Option<bool>,
609
610    /// Example inputs for the tool (beta feature)
611    ///
612    /// Requires beta header: `anthropic-beta: advanced-tool-use-2025-11-20`
613    /// Each example must be valid according to the input_schema.
614    #[serde(skip_serializing_if = "Option::is_none")]
615    pub input_examples: Option<Vec<serde_json::Value>>,
616
617    /// Cache control for this tool definition
618    #[serde(skip_serializing_if = "Option::is_none")]
619    pub cache_control: Option<CacheControl>,
620
621    /// Defer loading this tool until needed
622    #[serde(skip_serializing_if = "Option::is_none")]
623    pub defer_loading: Option<bool>,
624
625    /// Stream tool inputs as they're generated
626    #[serde(skip_serializing_if = "Option::is_none")]
627    pub eager_input_streaming: Option<bool>,
628
629    /// Enable strict JSON schema validation
630    #[serde(skip_serializing_if = "Option::is_none")]
631    pub strict: Option<bool>,
632}
633
634impl CustomTool {
635    /// Create a new custom tool definition
636    pub fn new(
637        name: impl Into<String>,
638        description: impl Into<String>,
639        input_schema: serde_json::Value,
640    ) -> Self {
641        Self {
642            name: name.into(),
643            description: description.into(),
644            input_schema,
645            disable_user_input: None,
646            input_examples: None,
647            cache_control: None,
648            defer_loading: None,
649            eager_input_streaming: None,
650            strict: None,
651        }
652    }
653
654    /// Set programmatic tool calling (no user confirmation)
655    pub fn programmatic(mut self) -> Self {
656        self.disable_user_input = Some(true);
657        self
658    }
659
660    /// Enable strict JSON schema validation
661    pub fn with_strict(mut self) -> Self {
662        self.strict = Some(true);
663        self
664    }
665}
666
667/// Renamed to [`CustomTool`] in v2.0. Use `CustomTool` directly.
668#[deprecated(
669    since = "2.0.0",
670    note = "Renamed to CustomTool. Use CustomTool directly."
671)]
672pub type Tool = CustomTool;
673
674/// Tool definition -- either a custom client tool or a built-in server tool.
675///
676/// Use this type in [`MessagesRequest::tools`].
677///
678/// # Variants
679///
680/// - [`ToolDefinition::Custom`] - A client-side tool with name, description, and JSON schema
681/// - [`ToolDefinition::Server`] - Any server-managed tool (web search, code execution, etc.)
682///
683/// # Example
684///
685/// ```rust
686/// use claude_sdk::{CustomTool, ToolDefinition};
687/// use serde_json::json;
688///
689/// // Custom tool
690/// let custom = ToolDefinition::Custom(
691///     CustomTool::new("my_tool", "A custom tool", json!({"type": "object"}))
692/// );
693///
694/// // Server tool (raw JSON)
695/// let server = ToolDefinition::Server(json!({
696///     "type": "web_search_20250305",
697///     "name": "web_search",
698///     "max_uses": 5
699/// }));
700/// ```
701#[derive(Debug, Clone, Serialize, Deserialize)]
702#[serde(untagged)]
703pub enum ToolDefinition {
704    /// Custom client-side tool with name, description, and JSON schema
705    Custom(CustomTool),
706    /// Any server tool -- uses raw JSON since server tool types vary
707    Server(serde_json::Value),
708}
709
710impl From<CustomTool> for ToolDefinition {
711    fn from(tool: CustomTool) -> Self {
712        ToolDefinition::Custom(tool)
713    }
714}
715
716/// Tool choice configuration
717#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
718#[serde(tag = "type", rename_all = "lowercase")]
719pub enum ToolChoice {
720    /// Let Claude decide whether to use tools (default)
721    Auto {
722        #[serde(skip_serializing_if = "Option::is_none")]
723        disable_parallel_tool_use: Option<bool>,
724    },
725    /// Claude must use one of the provided tools
726    Any {
727        #[serde(skip_serializing_if = "Option::is_none")]
728        disable_parallel_tool_use: Option<bool>,
729    },
730    /// Force Claude to use a specific tool
731    Tool {
732        name: String,
733        #[serde(skip_serializing_if = "Option::is_none")]
734        disable_parallel_tool_use: Option<bool>,
735    },
736    /// Prevent Claude from using any tools
737    None,
738}
739
740impl ToolChoice {
741    /// Create Auto variant
742    pub fn auto() -> Self {
743        Self::Auto {
744            disable_parallel_tool_use: None,
745        }
746    }
747
748    /// Create Any variant
749    pub fn any() -> Self {
750        Self::Any {
751            disable_parallel_tool_use: None,
752        }
753    }
754
755    /// Create Tool variant with specific tool name
756    pub fn tool(name: impl Into<String>) -> Self {
757        Self::Tool {
758            name: name.into(),
759            disable_parallel_tool_use: None,
760        }
761    }
762
763    /// Create None variant
764    pub fn none() -> Self {
765        Self::None
766    }
767}
768
769/// Detailed output token breakdown
770#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
771pub struct OutputTokensDetails {
772    /// Tokens used for thinking
773    #[serde(skip_serializing_if = "Option::is_none")]
774    pub thinking_tokens: Option<u32>,
775}
776
777/// Server tool usage counts
778#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
779pub struct ServerToolUsage {
780    /// Number of web search requests made
781    #[serde(skip_serializing_if = "Option::is_none")]
782    pub web_search_requests: Option<u32>,
783    /// Number of web fetch requests made
784    #[serde(skip_serializing_if = "Option::is_none")]
785    pub web_fetch_requests: Option<u32>,
786}
787
788/// Token usage information
789#[derive(Debug, Clone, Serialize, Deserialize)]
790pub struct Usage {
791    pub input_tokens: u32,
792    pub output_tokens: u32,
793
794    /// Tokens written to cache (prompt caching)
795    #[serde(skip_serializing_if = "Option::is_none")]
796    pub cache_creation_input_tokens: Option<u32>,
797
798    /// Tokens read from cache (prompt caching)
799    #[serde(skip_serializing_if = "Option::is_none")]
800    pub cache_read_input_tokens: Option<u32>,
801
802    /// Detailed breakdown of output tokens
803    #[serde(skip_serializing_if = "Option::is_none")]
804    pub output_tokens_details: Option<OutputTokensDetails>,
805
806    /// Server tool invocation counts
807    #[serde(skip_serializing_if = "Option::is_none")]
808    pub server_tool_use: Option<ServerToolUsage>,
809
810    /// Which service tier handled this request
811    #[serde(skip_serializing_if = "Option::is_none")]
812    pub service_tier: Option<String>,
813
814    /// Which geographic region processed this request
815    #[serde(skip_serializing_if = "Option::is_none")]
816    pub inference_geo: Option<String>,
817}
818
819/// Extended usage information for responses with thinking
820#[derive(Debug, Clone, Serialize, Deserialize)]
821pub struct ExtendedUsage {
822    #[serde(flatten)]
823    pub base: Usage,
824
825    /// Tokens used for thinking (extended thinking)
826    ///
827    /// Note: With summarized thinking (Claude 4+), you're billed for the full
828    /// thinking tokens, not the summarized tokens you see in the response.
829    #[serde(skip_serializing_if = "Option::is_none")]
830    pub thinking_tokens: Option<u32>,
831}
832
833/// Request to create a message
834#[derive(Debug, Clone, Serialize, Deserialize)]
835pub struct MessagesRequest {
836    /// Model identifier (e.g., "claude-3-5-sonnet-20241022")
837    pub model: String,
838
839    /// Maximum tokens to generate
840    pub max_tokens: u32,
841
842    /// Conversation messages
843    pub messages: Vec<Message>,
844
845    /// System prompt
846    #[serde(skip_serializing_if = "Option::is_none")]
847    pub system: Option<SystemPrompt>,
848
849    /// Available tools (custom client tools and/or server tools)
850    #[serde(skip_serializing_if = "Option::is_none")]
851    pub tools: Option<Vec<ToolDefinition>>,
852
853    /// Tool choice configuration
854    ///
855    /// Controls how Claude uses tools:
856    /// - `Auto` (default): Claude decides whether to use tools
857    /// - `Any`: Claude must use one of the provided tools
858    /// - `Tool { name }`: Force Claude to use a specific tool
859    /// - `None`: Prevent Claude from using any tools
860    #[serde(skip_serializing_if = "Option::is_none")]
861    pub tool_choice: Option<ToolChoice>,
862
863    /// Sampling temperature (0.0 to 1.0)
864    #[serde(skip_serializing_if = "Option::is_none")]
865    pub temperature: Option<f32>,
866
867    /// Top-p sampling
868    #[serde(skip_serializing_if = "Option::is_none")]
869    pub top_p: Option<f32>,
870
871    /// Top-k sampling
872    #[serde(skip_serializing_if = "Option::is_none")]
873    pub top_k: Option<u32>,
874
875    /// Stop sequences
876    #[serde(skip_serializing_if = "Option::is_none")]
877    pub stop_sequences: Option<Vec<String>>,
878
879    /// Whether to stream the response
880    #[serde(skip_serializing_if = "Option::is_none")]
881    pub stream: Option<bool>,
882
883    /// Output configuration (beta)
884    ///
885    /// Controls output behavior like effort level.
886    /// Requires beta header for effort: `anthropic-beta: effort-2025-11-24`
887    #[serde(skip_serializing_if = "Option::is_none")]
888    pub output_config: Option<OutputConfig>,
889
890    /// Extended thinking configuration
891    ///
892    /// Enables Claude's step-by-step reasoning process.
893    /// Supported models: Sonnet 4.5, Haiku 4.5, Opus 4.5, and more.
894    #[serde(skip_serializing_if = "Option::is_none")]
895    pub thinking: Option<ThinkingConfig>,
896
897    /// Request metadata for abuse detection
898    #[serde(skip_serializing_if = "Option::is_none")]
899    pub metadata: Option<Metadata>,
900
901    /// Service tier for request routing
902    #[serde(skip_serializing_if = "Option::is_none")]
903    pub service_tier: Option<ServiceTier>,
904
905    /// Geographic inference routing
906    #[serde(skip_serializing_if = "Option::is_none")]
907    pub inference_geo: Option<String>,
908
909    /// Container ID for persistent code execution
910    #[serde(skip_serializing_if = "Option::is_none")]
911    pub container: Option<String>,
912}
913
914/// Extended thinking configuration
915#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
916#[serde(tag = "type", rename_all = "lowercase")]
917pub enum ThinkingConfig {
918    /// Enable extended thinking with token budget
919    Enabled {
920        /// Maximum tokens Claude can use for thinking
921        ///
922        /// Minimum: 1024 tokens
923        /// Can exceed max_tokens with interleaved thinking (beta: interleaved-thinking-2025-05-14)
924        budget_tokens: u32,
925        #[serde(skip_serializing_if = "Option::is_none")]
926        display: Option<ThinkingDisplay>,
927    },
928    /// Disable extended thinking
929    Disabled,
930    /// Adaptive thinking -- let the model decide how much to think
931    Adaptive {
932        #[serde(skip_serializing_if = "Option::is_none")]
933        display: Option<ThinkingDisplay>,
934    },
935}
936
937/// How to display thinking blocks in responses
938#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
939#[serde(rename_all = "lowercase")]
940pub enum ThinkingDisplay {
941    /// Show summarized thinking
942    Summarized,
943    /// Omit thinking from response
944    Omitted,
945}
946
947/// Output configuration for controlling response behavior
948#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
949pub struct OutputConfig {
950    /// Effort level: controls token spending vs. response quality
951    ///
952    /// - `high` (default): Maximum capability, uses as many tokens as needed
953    /// - `medium`: Balanced approach with moderate token savings
954    /// - `low`: Most efficient, significant token savings
955    ///
956    /// Requires beta header: `anthropic-beta: effort-2025-11-24`
957    /// Only supported by Claude Opus 4.5
958    #[serde(skip_serializing_if = "Option::is_none")]
959    pub effort: Option<EffortLevel>,
960
961    /// Output format specification for structured outputs
962    #[serde(skip_serializing_if = "Option::is_none")]
963    pub format: Option<OutputFormat>,
964}
965
966/// Output format specification for structured outputs
967#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
968pub struct OutputFormat {
969    #[serde(rename = "type")]
970    pub format_type: String,
971    pub schema: serde_json::Value,
972}
973
974/// Effort level for response generation
975#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
976#[serde(rename_all = "lowercase")]
977pub enum EffortLevel {
978    /// Maximum capability (default if omitted)
979    High,
980    /// Balanced token savings
981    Medium,
982    /// Maximum token efficiency
983    Low,
984    /// Extra-high effort
985    #[serde(rename = "xhigh")]
986    XHigh,
987    /// Maximum effort
988    Max,
989}
990
991/// Request metadata for abuse detection
992#[derive(Debug, Clone, Serialize, Deserialize)]
993pub struct Metadata {
994    /// Opaque user identifier (uuid or hash, no PII)
995    #[serde(skip_serializing_if = "Option::is_none")]
996    pub user_id: Option<String>,
997}
998
999/// Service tier for request routing
1000#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1001#[serde(rename_all = "snake_case")]
1002pub enum ServiceTier {
1003    Auto,
1004    StandardOnly,
1005}
1006
1007impl MessagesRequest {
1008    /// Create a new message request with required fields
1009    ///
1010    /// # Example
1011    ///
1012    /// ```rust
1013    /// use claude_sdk::{MessagesRequest, Message};
1014    ///
1015    /// let request = MessagesRequest::new(
1016    ///     "claude-3-5-sonnet-20241022",
1017    ///     1024,
1018    ///     vec![Message::user("Hello!")]
1019    /// );
1020    /// ```
1021    pub fn new(model: impl Into<String>, max_tokens: u32, messages: Vec<Message>) -> Self {
1022        Self {
1023            model: model.into(),
1024            max_tokens,
1025            messages,
1026            system: None,
1027            tools: None,
1028            tool_choice: None,
1029            temperature: None,
1030            top_p: None,
1031            top_k: None,
1032            stop_sequences: None,
1033            stream: None,
1034            output_config: None,
1035            thinking: None,
1036            metadata: None,
1037            service_tier: None,
1038            inference_geo: None,
1039            container: None,
1040        }
1041    }
1042
1043    /// Set the system prompt.
1044    ///
1045    /// The system prompt provides instructions and context that guide Claude's behavior.
1046    ///
1047    /// # Example
1048    ///
1049    /// ```rust
1050    /// use claude_sdk::{MessagesRequest, Message};
1051    ///
1052    /// let request = MessagesRequest::new(
1053    ///     "claude-sonnet-4-5-20250929",
1054    ///     1024,
1055    ///     vec![Message::user("What's 2+2?")],
1056    /// )
1057    /// .with_system("You are a math tutor. Always explain your reasoning step by step.");
1058    /// ```
1059    pub fn with_system(mut self, system: impl Into<String>) -> Self {
1060        self.system = Some(SystemPrompt::String(system.into()));
1061        self
1062    }
1063
1064    /// Set the available tools for this request.
1065    ///
1066    /// Accepts any mix of custom and server tools via [`ToolDefinition`].
1067    ///
1068    /// # Example
1069    ///
1070    /// ```rust
1071    /// use claude_sdk::{MessagesRequest, Message, CustomTool, ToolDefinition};
1072    /// use serde_json::json;
1073    ///
1074    /// let calculator = ToolDefinition::Custom(
1075    ///     CustomTool::new(
1076    ///         "calculator",
1077    ///         "Perform basic arithmetic operations",
1078    ///         json!({
1079    ///             "type": "object",
1080    ///             "properties": {
1081    ///                 "operation": { "type": "string", "enum": ["add", "subtract", "multiply", "divide"] },
1082    ///                 "a": { "type": "number" },
1083    ///                 "b": { "type": "number" }
1084    ///             },
1085    ///             "required": ["operation", "a", "b"]
1086    ///         }),
1087    ///     )
1088    ///     .programmatic()
1089    /// );
1090    ///
1091    /// let request = MessagesRequest::new(
1092    ///     "claude-sonnet-4-5-20250929",
1093    ///     1024,
1094    ///     vec![Message::user("What's 15 * 7?")],
1095    /// )
1096    /// .with_tools(vec![calculator]);
1097    /// ```
1098    pub fn with_tools(mut self, tools: Vec<ToolDefinition>) -> Self {
1099        self.tools = Some(tools);
1100        self
1101    }
1102
1103    /// Set tools using only custom (client-side) tools.
1104    ///
1105    /// Convenience method that wraps each [`CustomTool`] in [`ToolDefinition::Custom`].
1106    ///
1107    /// # Example
1108    ///
1109    /// ```rust
1110    /// use claude_sdk::{MessagesRequest, Message, CustomTool};
1111    /// use serde_json::json;
1112    ///
1113    /// let tool = CustomTool::new("my_tool", "A tool", json!({"type": "object"}));
1114    ///
1115    /// let request = MessagesRequest::new(
1116    ///     "claude-sonnet-4-5-20250929",
1117    ///     1024,
1118    ///     vec![Message::user("Hello")],
1119    /// )
1120    /// .with_custom_tools(vec![tool]);
1121    /// ```
1122    pub fn with_custom_tools(mut self, tools: Vec<CustomTool>) -> Self {
1123        self.tools = Some(tools.into_iter().map(ToolDefinition::Custom).collect());
1124        self
1125    }
1126
1127    /// Set tool choice configuration.
1128    ///
1129    /// Controls how Claude decides whether and which tools to use.
1130    ///
1131    /// # Example
1132    ///
1133    /// ```rust
1134    /// use claude_sdk::{MessagesRequest, Message, ToolChoice};
1135    ///
1136    /// // Force Claude to use a specific tool
1137    /// let request = MessagesRequest::new(
1138    ///     "claude-sonnet-4-5-20250929",
1139    ///     1024,
1140    ///     vec![Message::user("Search for weather")],
1141    /// )
1142    /// .with_tool_choice(ToolChoice::tool("get_weather"));
1143    ///
1144    /// // Or let Claude decide (default)
1145    /// let request2 = MessagesRequest::new(
1146    ///     "claude-sonnet-4-5-20250929",
1147    ///     1024,
1148    ///     vec![Message::user("Hello")],
1149    /// )
1150    /// .with_tool_choice(ToolChoice::auto());
1151    /// ```
1152    pub fn with_tool_choice(mut self, choice: ToolChoice) -> Self {
1153        self.tool_choice = Some(choice);
1154        self
1155    }
1156
1157    /// Set the sampling temperature.
1158    ///
1159    /// Temperature controls randomness in the output:
1160    /// - `0.0` - Deterministic, most likely tokens
1161    /// - `0.5` - Balanced creativity
1162    /// - `1.0` - Maximum randomness
1163    ///
1164    /// # Example
1165    ///
1166    /// ```rust
1167    /// use claude_sdk::{MessagesRequest, Message};
1168    ///
1169    /// // Low temperature for factual responses
1170    /// let factual = MessagesRequest::new(
1171    ///     "claude-sonnet-4-5-20250929",
1172    ///     1024,
1173    ///     vec![Message::user("What is the capital of France?")],
1174    /// )
1175    /// .with_temperature(0.0);
1176    ///
1177    /// // Higher temperature for creative writing
1178    /// let creative = MessagesRequest::new(
1179    ///     "claude-sonnet-4-5-20250929",
1180    ///     1024,
1181    ///     vec![Message::user("Write a short poem about the ocean.")],
1182    /// )
1183    /// .with_temperature(0.8);
1184    /// ```
1185    pub fn with_temperature(mut self, temperature: f32) -> Self {
1186        self.temperature = Some(temperature);
1187        self
1188    }
1189
1190    /// Set effort level (beta - requires `anthropic-beta: effort-2025-11-24` header).
1191    ///
1192    /// Controls the trade-off between response quality and token usage.
1193    /// Only supported by Claude Opus 4.5.
1194    ///
1195    /// # Effort Levels
1196    ///
1197    /// - [`EffortLevel::High`] - Maximum capability (default)
1198    /// - [`EffortLevel::Medium`] - Balanced token savings
1199    /// - [`EffortLevel::Low`] - Maximum efficiency
1200    ///
1201    /// # Example
1202    ///
1203    /// ```rust
1204    /// use claude_sdk::{MessagesRequest, Message, EffortLevel};
1205    ///
1206    /// let request = MessagesRequest::new(
1207    ///     "claude-opus-4-5-20251101",  // Opus 4.5 only
1208    ///     1024,
1209    ///     vec![Message::user("Summarize this document briefly.")],
1210    /// )
1211    /// .with_effort(EffortLevel::Low);  // Optimize for efficiency
1212    /// ```
1213    pub fn with_effort(mut self, effort: EffortLevel) -> Self {
1214        let config = self.output_config.get_or_insert(OutputConfig {
1215            effort: None,
1216            format: None,
1217        });
1218        config.effort = Some(effort);
1219        self
1220    }
1221
1222    /// Set JSON schema for structured output
1223    pub fn with_json_schema(mut self, schema: serde_json::Value) -> Self {
1224        let config = self.output_config.get_or_insert(OutputConfig {
1225            effort: None,
1226            format: None,
1227        });
1228        config.format = Some(OutputFormat {
1229            format_type: "json_schema".into(),
1230            schema,
1231        });
1232        self
1233    }
1234
1235    /// Enable extended thinking with a token budget.
1236    ///
1237    /// Extended thinking allows Claude to reason through complex problems
1238    /// step-by-step before providing a final answer.
1239    ///
1240    /// # Requirements
1241    ///
1242    /// - Supported by: Claude Sonnet 4.5, Haiku 4.5, Opus 4.5, and other Claude 4+ models
1243    /// - Minimum budget: 1024 tokens
1244    /// - The thinking process appears in [`ContentBlock::Thinking`] blocks
1245    ///
1246    /// # Example
1247    ///
1248    /// ```rust
1249    /// use claude_sdk::{MessagesRequest, Message};
1250    ///
1251    /// let request = MessagesRequest::new(
1252    ///     "claude-sonnet-4-5-20250929",
1253    ///     8192,
1254    ///     vec![Message::user("Solve this step by step: If a train travels...")],
1255    /// )
1256    /// .with_thinking(4096);  // Allow up to 4096 tokens for reasoning
1257    /// ```
1258    pub fn with_thinking(mut self, budget_tokens: u32) -> Self {
1259        self.thinking = Some(ThinkingConfig::Enabled {
1260            budget_tokens,
1261            display: None,
1262        });
1263        self
1264    }
1265
1266    /// Enable adaptive thinking -- let the model decide how much to think
1267    pub fn with_adaptive_thinking(mut self) -> Self {
1268        self.thinking = Some(ThinkingConfig::Adaptive { display: None });
1269        self
1270    }
1271
1272    /// Set request metadata for abuse detection.
1273    pub fn with_metadata(mut self, metadata: Metadata) -> Self {
1274        self.metadata = Some(metadata);
1275        self
1276    }
1277
1278    /// Set the service tier for request routing.
1279    pub fn with_service_tier(mut self, tier: ServiceTier) -> Self {
1280        self.service_tier = Some(tier);
1281        self
1282    }
1283
1284    /// Set the geographic inference routing.
1285    pub fn with_inference_geo(mut self, geo: impl Into<String>) -> Self {
1286        self.inference_geo = Some(geo.into());
1287        self
1288    }
1289
1290    /// Set container ID for persistent code execution state
1291    pub fn with_container(mut self, container_id: impl Into<String>) -> Self {
1292        self.container = Some(container_id.into());
1293        self
1294    }
1295}
1296
1297/// Response from the token counting endpoint
1298///
1299/// Use `ClaudeClient::count_tokens()` to get server-side token counts
1300/// before sending a request.
1301#[derive(Debug, Clone, Serialize, Deserialize)]
1302pub struct TokenCount {
1303    /// Number of input tokens the request would use
1304    pub input_tokens: u32,
1305    /// Tokens that would be written to cache
1306    #[serde(skip_serializing_if = "Option::is_none")]
1307    pub cache_creation_input_tokens: Option<u32>,
1308    /// Tokens that would be read from cache
1309    #[serde(skip_serializing_if = "Option::is_none")]
1310    pub cache_read_input_tokens: Option<u32>,
1311}
1312
1313/// Stop reason for a message
1314#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1315#[serde(rename_all = "snake_case")]
1316pub enum StopReason {
1317    /// Natural end of message
1318    EndTurn,
1319    /// Hit max tokens
1320    MaxTokens,
1321    /// Hit a stop sequence
1322    StopSequence,
1323    /// Model wants to use a tool
1324    ToolUse,
1325    /// Long-running server tool paused the turn
1326    ///
1327    /// Continue by sending the response content back in the next request.
1328    /// Used with server tools like web search.
1329    PauseTurn,
1330    /// Model refused the request
1331    Refusal,
1332}
1333
1334/// Details about why the model stopped (currently only for refusals)
1335#[derive(Debug, Clone, Serialize, Deserialize)]
1336pub struct StopDetails {
1337    #[serde(rename = "type")]
1338    pub stop_type: String,
1339
1340    #[serde(skip_serializing_if = "Option::is_none")]
1341    pub category: Option<RefusalCategory>,
1342
1343    #[serde(skip_serializing_if = "Option::is_none")]
1344    pub explanation: Option<String>,
1345}
1346
1347/// Category of content refusal
1348#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1349#[serde(rename_all = "snake_case")]
1350pub enum RefusalCategory {
1351    Cyber,
1352    Bio,
1353    ReasoningExtraction,
1354}
1355
1356/// Response from creating a message
1357#[derive(Debug, Clone, Serialize, Deserialize)]
1358pub struct MessagesResponse {
1359    pub id: String,
1360
1361    #[serde(rename = "type")]
1362    pub response_type: String,
1363
1364    pub role: Role,
1365
1366    pub content: Vec<ContentBlock>,
1367
1368    pub model: String,
1369
1370    #[serde(skip_serializing_if = "Option::is_none")]
1371    pub stop_reason: Option<StopReason>,
1372
1373    #[serde(skip_serializing_if = "Option::is_none")]
1374    pub stop_sequence: Option<String>,
1375
1376    #[serde(skip_serializing_if = "Option::is_none")]
1377    pub stop_details: Option<StopDetails>,
1378
1379    pub usage: Usage,
1380
1381    /// Container metadata (present when code execution used a container)
1382    #[serde(skip_serializing_if = "Option::is_none")]
1383    pub container: Option<Container>,
1384
1385    /// Rate limit info from response headers (not part of JSON body)
1386    #[serde(skip)]
1387    pub rate_limit_info: Option<RateLimitInfo>,
1388}
1389
1390/// Rate limit information from API response headers
1391///
1392/// The API returns these headers with every response to help
1393/// clients manage their request rate.
1394#[derive(Debug, Clone, Default)]
1395pub struct RateLimitInfo {
1396    /// Remaining requests in current window
1397    pub requests_remaining: Option<u32>,
1398    /// Remaining tokens in current window
1399    pub tokens_remaining: Option<u32>,
1400    /// Time until request limit resets (ISO 8601)
1401    pub requests_reset: Option<String>,
1402    /// Time until token limit resets (ISO 8601)
1403    pub tokens_reset: Option<String>,
1404}
1405
1406#[cfg(test)]
1407mod tests {
1408    use super::*;
1409
1410    #[test]
1411    fn test_cache_control_ephemeral() {
1412        let cache = CacheControl::ephemeral();
1413        assert_eq!(cache.cache_type, CacheType::Ephemeral);
1414    }
1415
1416    #[test]
1417    fn test_cache_control_serialization() {
1418        let cache = CacheControl::ephemeral();
1419        let json = serde_json::to_string(&cache).unwrap();
1420        assert_eq!(json, r#"{"type":"ephemeral"}"#);
1421    }
1422
1423    #[test]
1424    fn test_text_content_with_cache() {
1425        let content = ContentBlock::Text {
1426            text: "test".into(),
1427            cache_control: Some(CacheControl::ephemeral()),
1428            citations: None,
1429        };
1430
1431        let json = serde_json::to_value(&content).unwrap();
1432        assert_eq!(json["type"], "text");
1433        assert_eq!(json["text"], "test");
1434        assert_eq!(json["cache_control"]["type"], "ephemeral");
1435    }
1436
1437    #[test]
1438    fn test_system_block_with_cache() {
1439        let block = SystemBlock {
1440            block_type: "text".into(),
1441            text: "You are helpful".into(),
1442            cache_control: Some(CacheControl::ephemeral()),
1443        };
1444
1445        let json = serde_json::to_value(&block).unwrap();
1446        assert_eq!(json["type"], "text");
1447        assert_eq!(json["cache_control"]["type"], "ephemeral");
1448    }
1449
1450    #[test]
1451    fn test_tool_with_cache() {
1452        let mut tool = CustomTool::new("test", "test tool", serde_json::json!({"type": "object"}))
1453            .programmatic();
1454        tool.cache_control = Some(CacheControl::ephemeral());
1455
1456        let json = serde_json::to_value(&tool).unwrap();
1457        assert_eq!(json["name"], "test");
1458        assert_eq!(json["cache_control"]["type"], "ephemeral");
1459    }
1460
1461    #[test]
1462    fn test_custom_tool_with_new_fields() {
1463        let mut tool =
1464            CustomTool::new("test", "test", serde_json::json!({"type": "object"})).with_strict();
1465        tool.defer_loading = Some(true);
1466        tool.eager_input_streaming = Some(true);
1467        let json = serde_json::to_value(&tool).unwrap();
1468        assert_eq!(json["defer_loading"], true);
1469        assert_eq!(json["eager_input_streaming"], true);
1470        assert_eq!(json["strict"], true);
1471    }
1472
1473    #[test]
1474    fn test_message_constructors() {
1475        let user_msg = Message::user("hello");
1476        assert_eq!(user_msg.role, Role::User);
1477        assert_eq!(user_msg.content.len(), 1);
1478
1479        let assistant_msg = Message::assistant("hi");
1480        assert_eq!(assistant_msg.role, Role::Assistant);
1481
1482        let tool_result = Message::tool_result("id_123", "result");
1483        assert_eq!(tool_result.role, Role::User);
1484        match &tool_result.content[0] {
1485            ContentBlock::ToolResult { tool_use_id, .. } => {
1486                assert_eq!(tool_use_id, "id_123");
1487            }
1488            _ => panic!("Expected ToolResult"),
1489        }
1490    }
1491
1492    #[test]
1493    fn test_messages_request_builder() {
1494        let request = MessagesRequest::new(
1495            "claude-sonnet-4-5-20250929",
1496            1024,
1497            vec![Message::user("test")],
1498        )
1499        .with_system("System prompt")
1500        .with_temperature(0.7);
1501
1502        assert_eq!(request.model, "claude-sonnet-4-5-20250929");
1503        assert_eq!(request.max_tokens, 1024);
1504        assert_eq!(request.messages.len(), 1);
1505        assert!(request.system.is_some());
1506        assert_eq!(request.temperature, Some(0.7));
1507    }
1508
1509    #[test]
1510    fn test_metadata_serialization() {
1511        let request = MessagesRequest::new(
1512            "claude-sonnet-4-5-20250929",
1513            1024,
1514            vec![Message::user("Hello")],
1515        )
1516        .with_metadata(Metadata {
1517            user_id: Some("user-abc-123".into()),
1518        });
1519
1520        let json = serde_json::to_value(&request).unwrap();
1521        assert_eq!(json["metadata"]["user_id"], "user-abc-123");
1522    }
1523
1524    #[test]
1525    fn test_service_tier_serialization() {
1526        let json = serde_json::to_string(&ServiceTier::StandardOnly).unwrap();
1527        assert_eq!(json, r#""standard_only""#);
1528
1529        let json = serde_json::to_string(&ServiceTier::Auto).unwrap();
1530        assert_eq!(json, r#""auto""#);
1531    }
1532
1533    #[test]
1534    fn test_refusal_stop_reason_deserialization() {
1535        let json = r#"{
1536            "id": "msg_123",
1537            "type": "message",
1538            "role": "assistant",
1539            "content": [],
1540            "model": "claude-sonnet-4-5-20250929",
1541            "stop_reason": "refusal",
1542            "stop_details": {
1543                "type": "refusal",
1544                "category": "cyber",
1545                "explanation": "Request involves prohibited content"
1546            },
1547            "usage": { "input_tokens": 10, "output_tokens": 0 }
1548        }"#;
1549
1550        let response: MessagesResponse = serde_json::from_str(json).unwrap();
1551        assert_eq!(response.stop_reason, Some(StopReason::Refusal));
1552        let details = response.stop_details.as_ref().unwrap();
1553        assert_eq!(details.category, Some(RefusalCategory::Cyber));
1554        assert_eq!(
1555            details.explanation.as_deref(),
1556            Some("Request involves prohibited content")
1557        );
1558    }
1559
1560    #[test]
1561    fn test_effort_xhigh_and_max() {
1562        let json_xhigh = serde_json::to_string(&EffortLevel::XHigh).unwrap();
1563        assert_eq!(json_xhigh, r#""xhigh""#);
1564
1565        let json_max = serde_json::to_string(&EffortLevel::Max).unwrap();
1566        assert_eq!(json_max, r#""max""#);
1567
1568        // Round-trip
1569        let parsed: EffortLevel = serde_json::from_str(r#""xhigh""#).unwrap();
1570        assert_eq!(parsed, EffortLevel::XHigh);
1571
1572        let parsed: EffortLevel = serde_json::from_str(r#""max""#).unwrap();
1573        assert_eq!(parsed, EffortLevel::Max);
1574    }
1575
1576    // Task 5: CacheTtl tests
1577
1578    #[test]
1579    fn test_cache_control_with_ttl() {
1580        let cache = CacheControl::ephemeral_with_ttl(CacheTtl::OneHour);
1581        let json = serde_json::to_value(&cache).unwrap();
1582        assert_eq!(json["type"], "ephemeral");
1583        assert_eq!(json["ttl"], "1h");
1584    }
1585
1586    #[test]
1587    fn test_cache_control_ttl_deserialization() {
1588        let json = r#"{"type": "ephemeral", "ttl": "5m"}"#;
1589        let cache: CacheControl = serde_json::from_str(json).unwrap();
1590        assert_eq!(cache.ttl, Some(CacheTtl::FiveMinutes));
1591    }
1592
1593    #[test]
1594    fn test_cache_control_without_ttl_still_works() {
1595        // Existing behavior: no ttl field
1596        let cache = CacheControl::ephemeral();
1597        let json = serde_json::to_string(&cache).unwrap();
1598        assert_eq!(json, r#"{"type":"ephemeral"}"#);
1599        assert_eq!(cache.ttl, None);
1600    }
1601
1602    // Task 6: Usage expansion tests
1603
1604    #[test]
1605    fn test_usage_with_details_deserialization() {
1606        let json = r#"{
1607            "input_tokens": 100,
1608            "output_tokens": 50,
1609            "cache_creation_input_tokens": 10,
1610            "cache_read_input_tokens": 5,
1611            "output_tokens_details": {
1612                "thinking_tokens": 20
1613            },
1614            "server_tool_use": {
1615                "web_search_requests": 3
1616            },
1617            "service_tier": "priority",
1618            "inference_geo": "us"
1619        }"#;
1620
1621        let usage: Usage = serde_json::from_str(json).unwrap();
1622        assert_eq!(usage.input_tokens, 100);
1623        assert_eq!(usage.output_tokens, 50);
1624        let details = usage.output_tokens_details.unwrap();
1625        assert_eq!(details.thinking_tokens, Some(20));
1626        let server = usage.server_tool_use.unwrap();
1627        assert_eq!(server.web_search_requests, Some(3));
1628        assert_eq!(usage.service_tier.as_deref(), Some("priority"));
1629    }
1630
1631    #[test]
1632    fn test_usage_without_new_fields_still_works() {
1633        let json = r#"{"input_tokens": 10, "output_tokens": 5}"#;
1634        let usage: Usage = serde_json::from_str(json).unwrap();
1635        assert_eq!(usage.input_tokens, 10);
1636        assert!(usage.output_tokens_details.is_none());
1637        assert!(usage.server_tool_use.is_none());
1638    }
1639
1640    // Task 6: TokenCount tests
1641
1642    #[test]
1643    fn test_token_count_deserialization() {
1644        let json = r#"{
1645            "input_tokens": 1234,
1646            "cache_creation_input_tokens": 100,
1647            "cache_read_input_tokens": 50
1648        }"#;
1649        let count: TokenCount = serde_json::from_str(json).unwrap();
1650        assert_eq!(count.input_tokens, 1234);
1651        assert_eq!(count.cache_creation_input_tokens, Some(100));
1652        assert_eq!(count.cache_read_input_tokens, Some(50));
1653    }
1654
1655    #[test]
1656    fn test_token_count_minimal() {
1657        let json = r#"{"input_tokens": 42}"#;
1658        let count: TokenCount = serde_json::from_str(json).unwrap();
1659        assert_eq!(count.input_tokens, 42);
1660        assert!(count.cache_creation_input_tokens.is_none());
1661    }
1662
1663    // Task 8: RateLimitInfo tests
1664
1665    #[test]
1666    fn test_rate_limit_info_default() {
1667        let info = RateLimitInfo::default();
1668        assert!(info.requests_remaining.is_none());
1669        assert!(info.tokens_remaining.is_none());
1670        assert!(info.requests_reset.is_none());
1671        assert!(info.tokens_reset.is_none());
1672    }
1673
1674    #[test]
1675    fn test_response_deserialization_with_skipped_rate_limit() {
1676        let json = r#"{
1677            "id": "msg_123",
1678            "type": "message",
1679            "role": "assistant",
1680            "content": [{"type": "text", "text": "hello"}],
1681            "model": "claude-sonnet-4-5-20250929",
1682            "stop_reason": "end_turn",
1683            "usage": {"input_tokens": 10, "output_tokens": 5}
1684        }"#;
1685
1686        let response: MessagesResponse = serde_json::from_str(json).unwrap();
1687        assert!(response.rate_limit_info.is_none()); // Skipped in JSON
1688    }
1689
1690    // Task 7: ContentBlock::Unknown forward-compatible deserialization tests
1691
1692    #[test]
1693    fn test_unknown_content_block_deserializes() {
1694        let json = r#"{"type": "some_future_block", "id": "fb_123", "data": "test"}"#;
1695        let block: ContentBlock = serde_json::from_str(json).unwrap();
1696        match block {
1697            ContentBlock::Unknown { block_type, data } => {
1698                assert_eq!(block_type, "some_future_block");
1699                assert_eq!(data["id"], "fb_123");
1700            }
1701            _ => panic!("Expected Unknown variant"),
1702        }
1703    }
1704
1705    #[test]
1706    fn test_known_content_blocks_still_work() {
1707        let json = r#"{"type": "text", "text": "hello"}"#;
1708        let block: ContentBlock = serde_json::from_str(json).unwrap();
1709        match block {
1710            ContentBlock::Text { text, .. } => assert_eq!(text, "hello"),
1711            _ => panic!("Expected Text variant"),
1712        }
1713    }
1714
1715    #[test]
1716    fn test_content_block_roundtrip() {
1717        let original = ContentBlock::Text {
1718            text: "test".into(),
1719            cache_control: None,
1720            citations: None,
1721        };
1722        let json = serde_json::to_string(&original).unwrap();
1723        let deserialized: ContentBlock = serde_json::from_str(&json).unwrap();
1724        match deserialized {
1725            ContentBlock::Text { text, .. } => assert_eq!(text, "test"),
1726            _ => panic!("Expected Text variant after roundtrip"),
1727        }
1728    }
1729
1730    #[test]
1731    fn test_unknown_block_in_response() {
1732        let json = r#"{
1733            "id": "msg_123",
1734            "type": "message",
1735            "role": "assistant",
1736            "content": [
1737                {"type": "text", "text": "hello"},
1738                {"type": "some_future_block", "id": "fb_1", "data": "test"}
1739            ],
1740            "model": "claude-sonnet-4-5-20250929",
1741            "stop_reason": "end_turn",
1742            "usage": {"input_tokens": 10, "output_tokens": 5}
1743        }"#;
1744
1745        let response: MessagesResponse = serde_json::from_str(json).unwrap();
1746        assert_eq!(response.content.len(), 2);
1747        match &response.content[0] {
1748            ContentBlock::Text { text, .. } => assert_eq!(text, "hello"),
1749            _ => panic!("Expected Text variant"),
1750        }
1751        match &response.content[1] {
1752            ContentBlock::Unknown { block_type, .. } => {
1753                assert_eq!(block_type, "some_future_block");
1754            }
1755            _ => panic!("Expected Unknown variant"),
1756        }
1757    }
1758
1759    #[test]
1760    fn test_unknown_block_without_type_field() {
1761        // Edge case: a JSON object with no "type" field should fail for the helper
1762        // and fall through to Unknown with "unknown" as block_type
1763        let json = r#"{"foo": "bar"}"#;
1764        let block: ContentBlock = serde_json::from_str(json).unwrap();
1765        match block {
1766            ContentBlock::Unknown { block_type, data } => {
1767                assert_eq!(block_type, "unknown");
1768                assert_eq!(data["foo"], "bar");
1769            }
1770            _ => panic!("Expected Unknown variant"),
1771        }
1772    }
1773
1774    // Task 3: Server tool content block tests
1775
1776    #[test]
1777    fn test_server_tool_use_deserialization() {
1778        let json = r#"{"type": "server_tool_use", "id": "stu_123", "name": "web_search", "input": {"query": "rust"}}"#;
1779        let block: ContentBlock = serde_json::from_str(json).unwrap();
1780        match block {
1781            ContentBlock::ServerToolUse {
1782                id, name, input, ..
1783            } => {
1784                assert_eq!(id, "stu_123");
1785                assert_eq!(name, "web_search");
1786                assert_eq!(input["query"], "rust");
1787            }
1788            _ => panic!("Expected ServerToolUse variant"),
1789        }
1790    }
1791
1792    #[test]
1793    fn test_web_search_tool_result_deserialization() {
1794        let json = r#"{"type": "web_search_tool_result", "tool_use_id": "stu_123", "content": [{"type": "web_page", "url": "https://example.com"}]}"#;
1795        let block: ContentBlock = serde_json::from_str(json).unwrap();
1796        match block {
1797            ContentBlock::WebSearchToolResult {
1798                tool_use_id,
1799                content,
1800                ..
1801            } => {
1802                assert_eq!(tool_use_id, "stu_123");
1803                assert!(content.is_array());
1804            }
1805            _ => panic!("Expected WebSearchToolResult variant"),
1806        }
1807    }
1808
1809    #[test]
1810    fn test_code_execution_tool_result_deserialization() {
1811        let json = r#"{"type": "code_execution_tool_result", "tool_use_id": "ce_123", "content": {"stdout": "hello"}}"#;
1812        let block: ContentBlock = serde_json::from_str(json).unwrap();
1813        match block {
1814            ContentBlock::CodeExecutionToolResult {
1815                tool_use_id,
1816                content,
1817                ..
1818            } => {
1819                assert_eq!(tool_use_id, "ce_123");
1820                assert_eq!(content["stdout"], "hello");
1821            }
1822            _ => panic!("Expected CodeExecutionToolResult variant"),
1823        }
1824    }
1825
1826    // Task 4: Structured output + adaptive thinking tests
1827
1828    #[test]
1829    fn test_structured_output_serialization() {
1830        let schema = serde_json::json!({
1831            "type": "object",
1832            "properties": {
1833                "name": {"type": "string"}
1834            }
1835        });
1836        let request = MessagesRequest::new(
1837            "claude-sonnet-4-5-20250929",
1838            1024,
1839            vec![Message::user("test")],
1840        )
1841        .with_json_schema(schema.clone());
1842
1843        let json = serde_json::to_value(&request).unwrap();
1844        assert_eq!(json["output_config"]["format"]["type"], "json_schema");
1845        assert_eq!(json["output_config"]["format"]["schema"], schema);
1846    }
1847
1848    #[test]
1849    fn test_effort_and_schema_coexist() {
1850        let request = MessagesRequest::new(
1851            "claude-sonnet-4-5-20250929",
1852            1024,
1853            vec![Message::user("test")],
1854        )
1855        .with_effort(EffortLevel::Low)
1856        .with_json_schema(serde_json::json!({"type": "object"}));
1857
1858        let config = request.output_config.as_ref().unwrap();
1859        assert_eq!(config.effort, Some(EffortLevel::Low));
1860        assert!(config.format.is_some());
1861    }
1862
1863    #[test]
1864    fn test_adaptive_thinking_serialization() {
1865        let request = MessagesRequest::new(
1866            "claude-sonnet-4-5-20250929",
1867            1024,
1868            vec![Message::user("test")],
1869        )
1870        .with_adaptive_thinking();
1871
1872        let json = serde_json::to_value(&request).unwrap();
1873        assert_eq!(json["thinking"]["type"], "adaptive");
1874    }
1875
1876    #[test]
1877    fn test_thinking_config_enabled_with_display() {
1878        let config = ThinkingConfig::Enabled {
1879            budget_tokens: 4096,
1880            display: Some(ThinkingDisplay::Summarized),
1881        };
1882        let json = serde_json::to_value(&config).unwrap();
1883        assert_eq!(json["type"], "enabled");
1884        assert_eq!(json["budget_tokens"], 4096);
1885        assert_eq!(json["display"], "summarized");
1886    }
1887
1888    // Task 5: ToolChoice, ToolResultContent, ToolUse caller tests
1889
1890    #[test]
1891    fn test_tool_choice_auto_serialization() {
1892        let choice = ToolChoice::auto();
1893        let json = serde_json::to_value(&choice).unwrap();
1894        assert_eq!(json["type"], "auto");
1895        // disable_parallel_tool_use should be omitted when None
1896        assert!(json.get("disable_parallel_tool_use").is_none());
1897    }
1898
1899    #[test]
1900    fn test_tool_choice_with_disable_parallel() {
1901        let choice = ToolChoice::Auto {
1902            disable_parallel_tool_use: Some(true),
1903        };
1904        let json = serde_json::to_value(&choice).unwrap();
1905        assert_eq!(json["type"], "auto");
1906        assert_eq!(json["disable_parallel_tool_use"], true);
1907    }
1908
1909    #[test]
1910    fn test_tool_choice_tool_serialization() {
1911        let choice = ToolChoice::tool("my_tool");
1912        let json = serde_json::to_value(&choice).unwrap();
1913        assert_eq!(json["type"], "tool");
1914        assert_eq!(json["name"], "my_tool");
1915        assert!(json.get("disable_parallel_tool_use").is_none());
1916    }
1917
1918    #[test]
1919    fn test_tool_result_content_text_serialization() {
1920        let content = ToolResultContent::Text("hello".into());
1921        let json = serde_json::to_value(&content).unwrap();
1922        assert_eq!(json, serde_json::json!("hello"));
1923    }
1924
1925    #[test]
1926    fn test_tool_result_content_blocks_serialization() {
1927        let content = ToolResultContent::Blocks(vec![ContentBlock::Text {
1928            text: "result".into(),
1929            cache_control: None,
1930            citations: None,
1931        }]);
1932        let json = serde_json::to_value(&content).unwrap();
1933        assert!(json.is_array());
1934        assert_eq!(json[0]["type"], "text");
1935        assert_eq!(json[0]["text"], "result");
1936    }
1937
1938    #[test]
1939    fn test_tool_use_with_caller() {
1940        let block = ContentBlock::ToolUse {
1941            id: "tu_123".into(),
1942            name: "my_tool".into(),
1943            input: serde_json::json!({}),
1944            caller: Some("code_execution_20250825".into()),
1945            cache_control: None,
1946        };
1947        let json = serde_json::to_value(&block).unwrap();
1948        assert_eq!(json["caller"], "code_execution_20250825");
1949    }
1950
1951    #[test]
1952    fn test_tool_use_without_caller() {
1953        let block = ContentBlock::ToolUse {
1954            id: "tu_123".into(),
1955            name: "my_tool".into(),
1956            input: serde_json::json!({}),
1957            caller: None,
1958            cache_control: None,
1959        };
1960        let json = serde_json::to_value(&block).unwrap();
1961        assert!(json.get("caller").is_none());
1962    }
1963
1964    #[test]
1965    fn test_container_request_serialization() {
1966        let request = MessagesRequest::new(
1967            "claude-sonnet-4-5-20250929",
1968            1024,
1969            vec![Message::user("Hello")],
1970        )
1971        .with_container("container_123");
1972        let json = serde_json::to_value(&request).unwrap();
1973        assert_eq!(json["container"], "container_123");
1974    }
1975
1976    #[test]
1977    fn test_container_response_deserialization() {
1978        let json = r#"{
1979            "id": "msg_1",
1980            "type": "message",
1981            "role": "assistant",
1982            "content": [],
1983            "model": "claude-sonnet-4-5-20250929",
1984            "stop_reason": "end_turn",
1985            "usage": {"input_tokens": 10, "output_tokens": 5},
1986            "container": {"id": "ctr_abc", "expires_at": "2026-01-01T00:00:00Z"}
1987        }"#;
1988        let response: MessagesResponse = serde_json::from_str(json).unwrap();
1989        let container = response.container.unwrap();
1990        assert_eq!(container.id, "ctr_abc");
1991    }
1992
1993    #[test]
1994    fn test_container_upload_deserialization() {
1995        let json = r#"{"type": "container_upload", "file_id": "file_123"}"#;
1996        let block: ContentBlock = serde_json::from_str(json).unwrap();
1997        match block {
1998            ContentBlock::ContainerUpload { file_id, .. } => {
1999                assert_eq!(file_id, "file_123");
2000            }
2001            _ => panic!("Expected ContainerUpload"),
2002        }
2003    }
2004
2005    #[test]
2006    fn test_mid_conv_system_deserialization() {
2007        let json = r#"{
2008            "type": "mid_conv_system",
2009            "content": [{"type": "text", "text": "New instruction"}]
2010        }"#;
2011        let block: ContentBlock = serde_json::from_str(json).unwrap();
2012        match block {
2013            ContentBlock::MidConvSystem { content, .. } => {
2014                assert_eq!(content[0].text, "New instruction");
2015            }
2016            _ => panic!("Expected MidConvSystem"),
2017        }
2018    }
2019
2020    #[test]
2021    fn test_server_tool_use_in_response() {
2022        let json = r#"{
2023            "id": "msg_123",
2024            "type": "message",
2025            "role": "assistant",
2026            "content": [
2027                {"type": "text", "text": "searching..."},
2028                {"type": "server_tool_use", "id": "stu_1", "name": "web_search", "input": {"query": "rust"}}
2029            ],
2030            "model": "claude-sonnet-4-5-20250929",
2031            "stop_reason": "end_turn",
2032            "usage": {"input_tokens": 10, "output_tokens": 5}
2033        }"#;
2034
2035        let response: MessagesResponse = serde_json::from_str(json).unwrap();
2036        assert_eq!(response.content.len(), 2);
2037        match &response.content[1] {
2038            ContentBlock::ServerToolUse { id, name, .. } => {
2039                assert_eq!(id, "stu_1");
2040                assert_eq!(name, "web_search");
2041            }
2042            _ => panic!("Expected ServerToolUse variant"),
2043        }
2044    }
2045}