Skip to main content

openai_protocol/
messages.rs

1//! Anthropic Messages API protocol definitions
2//!
3//! This module provides Rust types for the Anthropic Messages API.
4//! See: https://docs.anthropic.com/en/api/messages
5
6use std::collections::HashMap;
7
8use serde::{Deserialize, Serialize};
9use serde_json::{Map, Value};
10use validator::Validate;
11
12use crate::{common::GenerationRequest, validated::Normalizable};
13
14// ============================================================================
15// Request Types
16// ============================================================================
17
18/// Request to create a message using the Anthropic Messages API.
19///
20/// This is the main request type for `/v1/messages` endpoint.
21#[serde_with::skip_serializing_none]
22#[derive(Debug, Clone, Serialize, Deserialize, Validate, schemars::JsonSchema)]
23#[validate(schema(function = "validate_message_request"))]
24pub struct CreateMessageRequest {
25    /// The model that will complete your prompt.
26    #[validate(length(min = 1, message = "model field is required and cannot be empty"))]
27    pub model: String,
28
29    /// Input messages for the conversation.
30    #[validate(length(min = 1, message = "messages array is required and cannot be empty"))]
31    pub messages: Vec<InputMessage>,
32
33    /// The maximum number of tokens to generate before stopping.
34    #[validate(range(min = 1, message = "max_tokens must be greater than 0"))]
35    pub max_tokens: u32,
36
37    /// An object describing metadata about the request.
38    pub metadata: Option<Metadata>,
39
40    /// Service tier for the request (auto or standard_only).
41    pub service_tier: Option<ServiceTier>,
42
43    /// Custom text sequences that will cause the model to stop generating.
44    pub stop_sequences: Option<Vec<String>>,
45
46    /// Whether to incrementally stream the response using server-sent events.
47    pub stream: Option<bool>,
48
49    /// System prompt for providing context and instructions.
50    pub system: Option<SystemContent>,
51
52    /// Amount of randomness injected into the response (0.0 to 1.0).
53    pub temperature: Option<f64>,
54
55    /// Configuration for extended thinking.
56    pub thinking: Option<ThinkingConfig>,
57
58    /// How the model should use the provided tools.
59    pub tool_choice: Option<ToolChoice>,
60
61    /// Definitions of tools that the model may use.
62    pub tools: Option<Vec<Tool>>,
63
64    /// Only sample from the top K options for each subsequent token.
65    pub top_k: Option<u32>,
66
67    /// Use nucleus sampling.
68    pub top_p: Option<f64>,
69
70    // Beta features
71    /// Container configuration for code execution (beta).
72    pub container: Option<ContainerConfig>,
73
74    /// MCP servers to be utilized in this request (beta).
75    pub mcp_servers: Option<Vec<McpServerConfig>>,
76
77    /// Additional fields not explicitly defined above (e.g. beta features like
78    /// context_management, output_config). Captured and forwarded to backends.
79    #[serde(flatten)]
80    pub other: Map<String, Value>,
81}
82
83impl Normalizable for CreateMessageRequest {
84    // Use default no-op implementation
85}
86
87impl CreateMessageRequest {
88    /// Check if the request is for streaming
89    pub fn is_stream(&self) -> bool {
90        self.stream.unwrap_or(false)
91    }
92
93    /// Get the model name
94    pub fn get_model(&self) -> &str {
95        &self.model
96    }
97
98    /// Check if the request contains any `mcp_toolset` tool entries.
99    pub fn has_mcp_toolset(&self) -> bool {
100        self.tools
101            .as_ref()
102            .is_some_and(|tools| tools.iter().any(|t| matches!(t, Tool::McpToolset(_))))
103    }
104
105    /// Return MCP server configs if present and non-empty.
106    pub fn mcp_server_configs(&self) -> Option<&[McpServerConfig]> {
107        self.mcp_servers
108            .as_deref()
109            .filter(|servers| !servers.is_empty())
110    }
111}
112
113impl GenerationRequest for CreateMessageRequest {
114    fn is_stream(&self) -> bool {
115        self.stream.unwrap_or(false)
116    }
117
118    fn get_model(&self) -> Option<&str> {
119        Some(&self.model)
120    }
121
122    fn extract_text_for_routing(&self) -> String {
123        let mut buffer = String::new();
124        let mut has_content = false;
125
126        let push = |s: &str, has_content: &mut bool, buffer: &mut String| {
127            if s.is_empty() {
128                return;
129            }
130            if *has_content {
131                buffer.push(' ');
132            }
133            buffer.push_str(s);
134            *has_content = true;
135        };
136
137        if let Some(system) = &self.system {
138            match system {
139                SystemContent::String(s) => push(s, &mut has_content, &mut buffer),
140                SystemContent::Blocks(blocks) => {
141                    for block in blocks {
142                        let SystemContentBlock::Text(text_block) = block;
143                        push(&text_block.text, &mut has_content, &mut buffer);
144                    }
145                }
146            }
147        }
148
149        for msg in &self.messages {
150            match &msg.content {
151                InputContent::String(s) => push(s, &mut has_content, &mut buffer),
152                InputContent::Blocks(blocks) => {
153                    for block in blocks {
154                        if let InputContentBlock::Text(text_block) = block {
155                            push(&text_block.text, &mut has_content, &mut buffer);
156                        }
157                    }
158                }
159            }
160        }
161
162        buffer
163    }
164}
165
166impl Tool {
167    fn matches_tool_choice_name(&self, name: &str) -> bool {
168        match self {
169            Self::Custom(tool) => tool.name == name,
170            Self::ToolSearch(tool) => tool.name == name,
171            Self::Bash(tool) => tool.name == name,
172            Self::TextEditor(tool) => tool.name == name,
173            Self::WebSearch(tool) => tool.name == name,
174            Self::McpToolset(toolset) => {
175                let default_enabled = toolset
176                    .default_config
177                    .as_ref()
178                    .and_then(|config| config.enabled)
179                    .unwrap_or(true);
180
181                toolset
182                    .configs
183                    .as_ref()
184                    .and_then(|configs| configs.get(name))
185                    .and_then(|config| config.enabled)
186                    .unwrap_or(default_enabled)
187            }
188        }
189    }
190}
191/// Validate cross-field constraints for Messages API requests.
192fn validate_message_request(req: &CreateMessageRequest) -> Result<(), validator::ValidationError> {
193    if req.has_mcp_toolset() && req.mcp_server_configs().is_none() {
194        let mut e = validator::ValidationError::new("mcp_servers_required");
195        e.message = Some("mcp_servers is required when mcp_toolset tools are present".into());
196        return Err(e);
197    }
198
199    let Some(tool_choice) = &req.tool_choice else {
200        return Ok(());
201    };
202
203    let has_tools = req.tools.as_ref().is_some_and(|tools| !tools.is_empty());
204    let requires_tools = !matches!(tool_choice, ToolChoice::None);
205
206    if requires_tools && !has_tools {
207        let mut e = validator::ValidationError::new("tool_choice_requires_tools");
208        e.message = Some(
209            "Invalid value for 'tool_choice': 'tool_choice' is only allowed when 'tools' are specified."
210                .into(),
211        );
212        return Err(e);
213    }
214
215    if let ToolChoice::Tool { name, .. } = tool_choice {
216        let tool_exists = req
217            .tools
218            .as_ref()
219            .is_some_and(|tools| tools.iter().any(|tool| tool.matches_tool_choice_name(name)));
220
221        if !tool_exists {
222            let mut e = validator::ValidationError::new("tool_choice_tool_not_found");
223            e.message = Some(
224                format!("Invalid value for 'tool_choice': tool '{name}' not found in 'tools'.")
225                    .into(),
226            );
227            return Err(e);
228        }
229    }
230
231    Ok(())
232}
233
234/// Request metadata
235#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
236pub struct Metadata {
237    /// An external identifier for the user who is associated with the request.
238    #[serde(skip_serializing_if = "Option::is_none")]
239    pub user_id: Option<String>,
240}
241
242/// Service tier options
243#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
244#[serde(rename_all = "snake_case")]
245pub enum ServiceTier {
246    Auto,
247    StandardOnly,
248}
249
250/// System content can be a string or an array of text blocks
251#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
252#[serde(untagged)]
253pub enum SystemContent {
254    String(String),
255    Blocks(Vec<SystemContentBlock>),
256}
257
258/// System content block — wraps TextBlock with the required `type` discriminator
259/// so it round-trips correctly through serialization.
260#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
261#[serde(tag = "type", rename_all = "snake_case")]
262pub enum SystemContentBlock {
263    Text(TextBlock),
264}
265
266/// A single input message in a conversation
267#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
268pub struct InputMessage {
269    /// The role of the message sender (user or assistant)
270    pub role: Role,
271
272    /// The content of the message
273    pub content: InputContent,
274}
275
276/// Role of a message sender
277#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, schemars::JsonSchema)]
278#[serde(rename_all = "lowercase")]
279pub enum Role {
280    User,
281    Assistant,
282}
283
284/// Input content can be a string or an array of content blocks
285#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
286#[serde(untagged)]
287pub enum InputContent {
288    String(String),
289    Blocks(Vec<InputContentBlock>),
290}
291
292// ============================================================================
293// Input Content Blocks
294// ============================================================================
295
296/// Input content block types
297#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
298#[serde(tag = "type", rename_all = "snake_case")]
299pub enum InputContentBlock {
300    /// Text content
301    Text(TextBlock),
302    /// Image content
303    Image(ImageBlock),
304    /// Document content
305    Document(DocumentBlock),
306    /// Tool use block (for assistant messages)
307    ToolUse(ToolUseBlock),
308    /// Tool result block (for user messages)
309    ToolResult(ToolResultBlock),
310    /// Thinking block
311    Thinking(ThinkingBlock),
312    /// Redacted thinking block
313    RedactedThinking(RedactedThinkingBlock),
314    /// Server tool use block
315    ServerToolUse(ServerToolUseBlock),
316    /// Search result block
317    SearchResult(SearchResultBlock),
318    /// Web search tool result block
319    WebSearchToolResult(WebSearchToolResultBlock),
320    /// Tool search tool result block
321    ToolSearchToolResult(ToolSearchToolResultBlock),
322    /// Tool reference block
323    ToolReference(ToolReferenceBlock),
324}
325
326/// Text content block
327#[serde_with::skip_serializing_none]
328#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
329pub struct TextBlock {
330    /// The text content
331    pub text: String,
332
333    /// Cache control for this block
334    pub cache_control: Option<CacheControl>,
335
336    /// Citations for this text block
337    pub citations: Option<Vec<Citation>>,
338}
339
340/// Image content block
341#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
342pub struct ImageBlock {
343    /// The image source
344    pub source: ImageSource,
345
346    /// Cache control for this block
347    #[serde(skip_serializing_if = "Option::is_none")]
348    pub cache_control: Option<CacheControl>,
349}
350
351/// Image source (base64 or URL)
352#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
353#[serde(tag = "type", rename_all = "snake_case")]
354pub enum ImageSource {
355    Base64 { media_type: String, data: String },
356    Url { url: String },
357}
358
359/// Document content block
360#[serde_with::skip_serializing_none]
361#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
362pub struct DocumentBlock {
363    /// The document source
364    pub source: DocumentSource,
365
366    /// Cache control for this block
367    pub cache_control: Option<CacheControl>,
368
369    /// Optional title for the document
370    pub title: Option<String>,
371
372    /// Optional context for the document
373    pub context: Option<String>,
374
375    /// Citations configuration
376    pub citations: Option<CitationsConfig>,
377}
378
379/// Document source (base64, text, or URL)
380#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
381#[serde(tag = "type", rename_all = "snake_case")]
382pub enum DocumentSource {
383    Base64 { media_type: String, data: String },
384    Text { data: String },
385    Url { url: String },
386    Content { content: Vec<InputContentBlock> },
387}
388
389/// Tool use block (in assistant messages)
390#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
391pub struct ToolUseBlock {
392    /// Unique identifier for this tool use
393    pub id: String,
394
395    /// Name of the tool being used
396    pub name: String,
397
398    /// Input arguments for the tool
399    pub input: Value,
400
401    /// Cache control for this block
402    #[serde(skip_serializing_if = "Option::is_none")]
403    pub cache_control: Option<CacheControl>,
404}
405
406/// Tool result block (in user messages)
407#[serde_with::skip_serializing_none]
408#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
409pub struct ToolResultBlock {
410    /// The ID of the tool use this is a result for
411    pub tool_use_id: String,
412
413    /// The result content (string or blocks)
414    pub content: Option<ToolResultContent>,
415
416    /// Whether this result indicates an error
417    pub is_error: Option<bool>,
418
419    /// Cache control for this block
420    pub cache_control: Option<CacheControl>,
421}
422
423/// Tool result content (string or blocks)
424#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
425#[serde(untagged)]
426pub enum ToolResultContent {
427    String(String),
428    Blocks(Vec<ToolResultContentBlock>),
429}
430
431/// Content blocks allowed in tool results
432#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
433#[serde(tag = "type", rename_all = "snake_case")]
434pub enum ToolResultContentBlock {
435    Text(TextBlock),
436    Image(ImageBlock),
437    Document(DocumentBlock),
438    SearchResult(SearchResultBlock),
439}
440
441/// Thinking block
442#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
443pub struct ThinkingBlock {
444    /// The thinking content
445    pub thinking: String,
446
447    /// Signature for the thinking block
448    pub signature: String,
449}
450
451/// Redacted thinking block
452#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
453pub struct RedactedThinkingBlock {
454    /// The encrypted/redacted data
455    pub data: String,
456}
457
458/// Server tool use block
459#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
460pub struct ServerToolUseBlock {
461    /// Unique identifier for this tool use
462    pub id: String,
463
464    /// Name of the server tool
465    pub name: String,
466
467    /// Input arguments for the tool
468    pub input: Value,
469
470    /// Cache control for this block
471    #[serde(skip_serializing_if = "Option::is_none")]
472    pub cache_control: Option<CacheControl>,
473}
474
475/// Search result block
476#[serde_with::skip_serializing_none]
477#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
478pub struct SearchResultBlock {
479    /// Source URL or identifier
480    pub source: String,
481
482    /// Title of the search result
483    pub title: String,
484
485    /// Content of the search result
486    pub content: Vec<TextBlock>,
487
488    /// Cache control for this block
489    pub cache_control: Option<CacheControl>,
490
491    /// Citations configuration
492    pub citations: Option<CitationsConfig>,
493}
494
495/// Web search tool result block
496#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
497pub struct WebSearchToolResultBlock {
498    /// The tool use ID this result is for
499    pub tool_use_id: String,
500
501    /// The search results or error
502    pub content: WebSearchToolResultContent,
503
504    /// Cache control for this block
505    #[serde(skip_serializing_if = "Option::is_none")]
506    pub cache_control: Option<CacheControl>,
507}
508
509/// Web search tool result content
510#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
511#[serde(untagged)]
512pub enum WebSearchToolResultContent {
513    Results(Vec<WebSearchResultBlock>),
514    Error(WebSearchToolResultError),
515}
516
517/// Web search result block
518#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
519pub struct WebSearchResultBlock {
520    /// Title of the search result
521    pub title: String,
522
523    /// URL of the search result
524    pub url: String,
525
526    /// Encrypted content
527    pub encrypted_content: String,
528
529    /// Page age (if available)
530    #[serde(skip_serializing_if = "Option::is_none")]
531    pub page_age: Option<String>,
532}
533
534/// Web search tool result error
535#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
536pub struct WebSearchToolResultError {
537    #[serde(rename = "type")]
538    pub error_type: String,
539    pub error_code: WebSearchToolResultErrorCode,
540}
541
542/// Web search tool result error codes
543#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
544#[serde(rename_all = "snake_case")]
545pub enum WebSearchToolResultErrorCode {
546    InvalidToolInput,
547    Unavailable,
548    MaxUsesExceeded,
549    TooManyRequests,
550    QueryTooLong,
551}
552
553/// Cache control configuration
554#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
555#[serde(tag = "type", rename_all = "snake_case")]
556pub enum CacheControl {
557    Ephemeral,
558}
559
560/// Citations configuration
561#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
562pub struct CitationsConfig {
563    #[serde(skip_serializing_if = "Option::is_none")]
564    pub enabled: Option<bool>,
565}
566
567/// Citation types
568#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
569#[serde(tag = "type", rename_all = "snake_case")]
570#[expect(
571    clippy::enum_variant_names,
572    reason = "variant names match the OpenAI API citation type discriminators (char_location, page_location, etc.)"
573)]
574pub enum Citation {
575    CharLocation(CharLocationCitation),
576    PageLocation(PageLocationCitation),
577    ContentBlockLocation(ContentBlockLocationCitation),
578    WebSearchResultLocation(WebSearchResultLocationCitation),
579    SearchResultLocation(SearchResultLocationCitation),
580}
581
582/// Character location citation
583#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
584pub struct CharLocationCitation {
585    pub cited_text: String,
586    pub document_index: u32,
587    pub document_title: Option<String>,
588    pub start_char_index: u32,
589    pub end_char_index: u32,
590    #[serde(skip_serializing_if = "Option::is_none")]
591    pub file_id: Option<String>,
592}
593
594/// Page location citation
595#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
596pub struct PageLocationCitation {
597    pub cited_text: String,
598    pub document_index: u32,
599    pub document_title: Option<String>,
600    pub start_page_number: u32,
601    pub end_page_number: u32,
602}
603
604/// Content block location citation
605#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
606pub struct ContentBlockLocationCitation {
607    pub cited_text: String,
608    pub document_index: u32,
609    pub document_title: Option<String>,
610    pub start_block_index: u32,
611    pub end_block_index: u32,
612}
613
614/// Web search result location citation
615#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
616pub struct WebSearchResultLocationCitation {
617    pub cited_text: String,
618    pub url: String,
619    pub title: Option<String>,
620    pub encrypted_index: String,
621}
622
623/// Search result location citation
624#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
625pub struct SearchResultLocationCitation {
626    pub cited_text: String,
627    pub search_result_index: u32,
628    pub source: String,
629    pub title: Option<String>,
630    pub start_block_index: u32,
631    pub end_block_index: u32,
632}
633
634// ============================================================================
635// Tool Definitions
636// ============================================================================
637
638/// Tool definition
639#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
640#[serde(untagged)]
641#[expect(
642    clippy::enum_variant_names,
643    reason = "ToolSearch matches Anthropic API naming"
644)]
645#[schemars(rename = "MessagesTool")]
646pub enum Tool {
647    /// MCP toolset definition
648    McpToolset(McpToolset),
649    /// Custom tool definition (must come before ToolSearch: CustomTool requires
650    /// `input_schema` which acts as a discriminator — ToolSearchTool JSON lacks
651    /// it and falls through, while CustomTool JSON with "type" would incorrectly
652    /// match ToolSearchTool's less-restrictive shape if tried first)
653    Custom(CustomTool),
654    /// Tool search tool
655    ToolSearch(ToolSearchTool),
656    /// Bash tool (computer use)
657    Bash(BashTool),
658    /// Text editor tool (computer use)
659    TextEditor(TextEditorTool),
660    /// Web search tool
661    WebSearch(WebSearchTool),
662}
663
664/// Custom tool definition
665#[serde_with::skip_serializing_none]
666#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
667pub struct CustomTool {
668    /// Name of the tool
669    pub name: String,
670
671    /// Optional type (defaults to "custom")
672    #[serde(rename = "type")]
673    pub tool_type: Option<String>,
674
675    /// Description of what this tool does
676    pub description: Option<String>,
677
678    /// JSON schema for the tool's input
679    pub input_schema: InputSchema,
680
681    /// Whether to defer loading this tool
682    pub defer_loading: Option<bool>,
683
684    /// Cache control for this tool
685    pub cache_control: Option<CacheControl>,
686}
687
688/// JSON Schema for tool input
689#[serde_with::skip_serializing_none]
690#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
691pub struct InputSchema {
692    #[serde(rename = "type")]
693    pub schema_type: String,
694
695    pub properties: Option<HashMap<String, Value>>,
696
697    pub required: Option<Vec<String>>,
698
699    /// Additional properties can be stored here
700    #[serde(flatten)]
701    pub additional: HashMap<String, Value>,
702}
703
704/// Bash tool for computer use
705#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
706pub struct BashTool {
707    #[serde(rename = "type")]
708    pub tool_type: String, // "bash_20250124"
709
710    pub name: String, // "bash"
711
712    #[serde(skip_serializing_if = "Option::is_none")]
713    pub cache_control: Option<CacheControl>,
714}
715
716/// Text editor tool for computer use
717#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
718pub struct TextEditorTool {
719    #[serde(rename = "type")]
720    pub tool_type: String, // "text_editor_20250124", etc.
721
722    pub name: String, // "str_replace_editor"
723
724    #[serde(skip_serializing_if = "Option::is_none")]
725    pub cache_control: Option<CacheControl>,
726}
727
728/// Web search tool
729#[serde_with::skip_serializing_none]
730#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
731pub struct WebSearchTool {
732    #[serde(rename = "type")]
733    pub tool_type: String, // "web_search_20250305"
734
735    pub name: String, // "web_search"
736
737    pub allowed_domains: Option<Vec<String>>,
738
739    pub blocked_domains: Option<Vec<String>>,
740
741    pub max_uses: Option<u32>,
742
743    pub user_location: Option<UserLocation>,
744
745    pub cache_control: Option<CacheControl>,
746}
747
748/// User location for web search
749#[serde_with::skip_serializing_none]
750#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
751pub struct UserLocation {
752    #[serde(rename = "type")]
753    pub location_type: String, // "approximate"
754
755    pub city: Option<String>,
756
757    pub region: Option<String>,
758
759    pub country: Option<String>,
760
761    pub timezone: Option<String>,
762}
763
764// ============================================================================
765// Tool Choice
766// ============================================================================
767
768/// How the model should use the provided tools
769#[serde_with::skip_serializing_none]
770#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
771#[serde(tag = "type", rename_all = "snake_case")]
772#[schemars(rename = "MessagesToolChoice")]
773pub enum ToolChoice {
774    /// The model will automatically decide whether to use tools
775    Auto {
776        disable_parallel_tool_use: Option<bool>,
777    },
778    /// The model will use any available tools
779    Any {
780        disable_parallel_tool_use: Option<bool>,
781    },
782    /// The model will use the specified tool
783    Tool {
784        name: String,
785        disable_parallel_tool_use: Option<bool>,
786    },
787    /// The model will not use tools
788    None,
789}
790
791// ============================================================================
792// Thinking Configuration
793// ============================================================================
794
795/// Configuration for extended thinking
796#[serde_with::skip_serializing_none]
797#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
798#[serde(tag = "type", rename_all = "snake_case")]
799pub enum ThinkingConfig {
800    /// Enable extended thinking
801    Enabled {
802        /// Budget in tokens for thinking (minimum 1024)
803        budget_tokens: u32,
804        /// How thinking content is returned in the response.
805        display: Option<ThinkingDisplay>,
806    },
807    /// Disable extended thinking
808    Disabled,
809    /// Let the model decide when and how much to think. Required on Opus 4.7.
810    Adaptive {
811        /// How thinking content is returned in the response.
812        /// Defaults vary by model (Opus 4.7 / Mythos default to `Omitted`; others to `Summarized`).
813        display: Option<ThinkingDisplay>,
814    },
815}
816
817/// How thinking content is returned in API responses
818#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
819#[serde(rename_all = "snake_case")]
820pub enum ThinkingDisplay {
821    /// Thinking blocks contain summarized reasoning text
822    Summarized,
823    /// Thinking blocks return empty text; signature still carries encrypted content
824    Omitted,
825}
826
827// ============================================================================
828// Response Types
829// ============================================================================
830
831/// Response message from the API
832#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
833pub struct Message {
834    /// Unique object identifier
835    pub id: String,
836
837    /// Object type (always "message")
838    #[serde(rename = "type")]
839    pub message_type: String,
840
841    /// Conversational role (always "assistant")
842    pub role: String,
843
844    /// Content generated by the model
845    pub content: Vec<ContentBlock>,
846
847    /// The model that generated the message
848    pub model: String,
849
850    /// The reason the model stopped generating
851    pub stop_reason: Option<StopReason>,
852
853    /// Which custom stop sequence was generated (if any)
854    pub stop_sequence: Option<String>,
855
856    /// Billing and rate-limit usage
857    pub usage: Usage,
858}
859
860/// Output content block types
861#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
862#[serde(tag = "type", rename_all = "snake_case")]
863pub enum ContentBlock {
864    /// Text content
865    Text {
866        text: String,
867        #[serde(skip_serializing_if = "Option::is_none")]
868        citations: Option<Vec<Citation>>,
869    },
870    /// Tool use by the model
871    ToolUse {
872        id: String,
873        name: String,
874        input: Value,
875    },
876    /// Thinking content
877    Thinking { thinking: String, signature: String },
878    /// Redacted thinking content
879    RedactedThinking { data: String },
880    /// Server tool use
881    ServerToolUse {
882        id: String,
883        name: String,
884        input: Value,
885    },
886    /// Web search tool result
887    WebSearchToolResult {
888        tool_use_id: String,
889        content: WebSearchToolResultContent,
890    },
891    /// Tool search tool result
892    ToolSearchToolResult {
893        tool_use_id: String,
894        content: ToolSearchResultContent,
895    },
896    /// Tool reference (returned by tool search)
897    ToolReference {
898        tool_name: String,
899        #[serde(skip_serializing_if = "Option::is_none")]
900        description: Option<String>,
901    },
902    /// MCP tool use (beta) - model requesting tool execution via MCP
903    McpToolUse {
904        id: String,
905        name: String,
906        server_name: String,
907        input: Value,
908    },
909    /// MCP tool result (beta) - result from MCP tool execution
910    McpToolResult {
911        tool_use_id: String,
912        content: Option<ToolResultContent>,
913        is_error: Option<bool>,
914    },
915}
916
917/// Stop reasons
918#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, schemars::JsonSchema)]
919#[serde(rename_all = "snake_case")]
920pub enum StopReason {
921    /// The model reached a natural stopping point
922    EndTurn,
923    /// We exceeded the requested max_tokens
924    MaxTokens,
925    /// One of the custom stop_sequences was generated
926    StopSequence,
927    /// The model invoked one or more tools
928    ToolUse,
929    /// We paused a long-running turn
930    PauseTurn,
931    /// Streaming classifiers intervened
932    Refusal,
933}
934
935/// Billing and rate-limit usage
936#[serde_with::skip_serializing_none]
937#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
938#[schemars(rename = "MessagesUsage")]
939pub struct Usage {
940    /// The number of input tokens used
941    pub input_tokens: u32,
942
943    /// The number of output tokens used
944    pub output_tokens: u32,
945
946    /// The number of input tokens used to create the cache entry
947    pub cache_creation_input_tokens: Option<u32>,
948
949    /// The number of input tokens read from the cache
950    pub cache_read_input_tokens: Option<u32>,
951
952    /// Breakdown of cached tokens by TTL
953    pub cache_creation: Option<CacheCreation>,
954
955    /// Server tool usage information
956    pub server_tool_use: Option<ServerToolUsage>,
957
958    /// Service tier used for the request
959    pub service_tier: Option<String>,
960}
961
962/// Cache creation breakdown
963#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
964pub struct CacheCreation {
965    #[serde(flatten)]
966    pub tokens_by_ttl: HashMap<String, u32>,
967}
968
969/// Server tool usage information
970#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
971pub struct ServerToolUsage {
972    pub web_search_requests: u32,
973}
974
975// ============================================================================
976// Streaming Event Types
977// ============================================================================
978
979/// Server-sent event wrapper
980#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
981#[serde(tag = "type", rename_all = "snake_case")]
982pub enum MessageStreamEvent {
983    /// Start of a new message
984    MessageStart { message: Message },
985    /// Update to a message
986    MessageDelta {
987        delta: MessageDelta,
988        usage: MessageDeltaUsage,
989    },
990    /// End of a message
991    MessageStop,
992    /// Start of a content block
993    ContentBlockStart {
994        index: u32,
995        content_block: ContentBlock,
996    },
997    /// Update to a content block
998    ContentBlockDelta {
999        index: u32,
1000        delta: ContentBlockDelta,
1001    },
1002    /// End of a content block
1003    ContentBlockStop { index: u32 },
1004    /// Ping event (for keep-alive)
1005    Ping,
1006    /// Error event
1007    Error { error: ErrorResponse },
1008}
1009
1010/// Message delta for streaming updates
1011#[serde_with::skip_serializing_none]
1012#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1013pub struct MessageDelta {
1014    pub stop_reason: Option<StopReason>,
1015
1016    pub stop_sequence: Option<String>,
1017}
1018
1019/// Usage delta for streaming updates
1020#[serde_with::skip_serializing_none]
1021#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1022pub struct MessageDeltaUsage {
1023    pub output_tokens: u32,
1024
1025    pub input_tokens: Option<u32>,
1026
1027    pub cache_creation_input_tokens: Option<u32>,
1028
1029    pub cache_read_input_tokens: Option<u32>,
1030
1031    pub server_tool_use: Option<ServerToolUsage>,
1032}
1033
1034/// Content block delta for streaming updates
1035#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1036#[serde(tag = "type", rename_all = "snake_case")]
1037#[expect(
1038    clippy::enum_variant_names,
1039    reason = "variant names match the OpenAI/Anthropic streaming delta type discriminators (text_delta, input_json_delta, etc.)"
1040)]
1041pub enum ContentBlockDelta {
1042    /// Text delta
1043    TextDelta { text: String },
1044    /// JSON input delta (for tool use)
1045    InputJsonDelta { partial_json: String },
1046    /// Thinking delta
1047    ThinkingDelta { thinking: String },
1048    /// Signature delta
1049    SignatureDelta { signature: String },
1050    /// Citations delta
1051    CitationsDelta { citation: Citation },
1052}
1053
1054// ============================================================================
1055// Error Types
1056// ============================================================================
1057
1058/// Error response
1059#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1060#[schemars(rename = "MessagesErrorResponse")]
1061pub struct ErrorResponse {
1062    #[serde(rename = "type")]
1063    pub error_type: String,
1064
1065    pub message: String,
1066}
1067
1068/// API error types
1069#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1070#[serde(tag = "type", rename_all = "snake_case")]
1071#[expect(
1072    clippy::enum_variant_names,
1073    reason = "variant names match the OpenAI API error type discriminators (invalid_request_error, authentication_error, etc.)"
1074)]
1075pub enum ApiError {
1076    InvalidRequestError { message: String },
1077    AuthenticationError { message: String },
1078    BillingError { message: String },
1079    PermissionError { message: String },
1080    NotFoundError { message: String },
1081    RateLimitError { message: String },
1082    TimeoutError { message: String },
1083    ApiError { message: String },
1084    OverloadedError { message: String },
1085}
1086
1087// ============================================================================
1088// Count Tokens Types
1089// ============================================================================
1090
1091/// Request to count tokens in a message
1092#[serde_with::skip_serializing_none]
1093#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1094pub struct CountMessageTokensRequest {
1095    /// The model to use for token counting
1096    pub model: String,
1097
1098    /// Input messages
1099    pub messages: Vec<InputMessage>,
1100
1101    /// System prompt
1102    pub system: Option<SystemContent>,
1103
1104    /// Thinking configuration
1105    pub thinking: Option<ThinkingConfig>,
1106
1107    /// Tool choice
1108    pub tool_choice: Option<ToolChoice>,
1109
1110    /// Tool definitions
1111    pub tools: Option<Vec<Tool>>,
1112}
1113
1114/// Response from token counting
1115#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1116pub struct CountMessageTokensResponse {
1117    pub input_tokens: u32,
1118}
1119
1120// ============================================================================
1121// Model Info Types
1122// ============================================================================
1123
1124/// Model information
1125#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1126pub struct ModelInfo {
1127    /// Object type (always "model")
1128    #[serde(rename = "type")]
1129    pub model_type: String,
1130
1131    /// Model ID
1132    pub id: String,
1133
1134    /// Display name
1135    pub display_name: String,
1136
1137    /// When the model was created
1138    pub created_at: String,
1139}
1140
1141/// List of models response
1142#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1143pub struct ListModelsResponse {
1144    pub data: Vec<ModelInfo>,
1145    pub has_more: bool,
1146    pub first_id: Option<String>,
1147    pub last_id: Option<String>,
1148}
1149
1150// ============================================================================
1151// Beta Features - Container & MCP Configuration
1152// ============================================================================
1153
1154/// Container configuration for code execution (beta)
1155#[serde_with::skip_serializing_none]
1156#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1157pub struct ContainerConfig {
1158    /// Container ID for reuse across requests
1159    pub id: Option<String>,
1160}
1161
1162/// MCP server configuration (beta)
1163#[serde_with::skip_serializing_none]
1164#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1165pub struct McpServerConfig {
1166    /// Server type (always "url")
1167    #[serde(rename = "type", default = "McpServerConfig::default_type")]
1168    pub server_type: String,
1169
1170    /// Name of the MCP server
1171    pub name: String,
1172
1173    /// MCP server URL
1174    pub url: String,
1175
1176    /// Authorization token (if required)
1177    pub authorization_token: Option<String>,
1178
1179    /// Tool configuration for this server
1180    pub tool_configuration: Option<McpToolConfiguration>,
1181}
1182
1183impl McpServerConfig {
1184    fn default_type() -> String {
1185        "url".to_string()
1186    }
1187}
1188
1189/// MCP tool configuration
1190#[serde_with::skip_serializing_none]
1191#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1192pub struct McpToolConfiguration {
1193    /// Whether to allow all tools
1194    pub enabled: Option<bool>,
1195
1196    /// Allowed tool names
1197    pub allowed_tools: Option<Vec<String>>,
1198}
1199
1200// ============================================================================
1201// Beta Features - MCP Tool Types
1202// ============================================================================
1203
1204/// MCP tool use block (beta) - for assistant messages
1205#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1206pub struct McpToolUseBlock {
1207    /// Unique identifier for this tool use
1208    pub id: String,
1209
1210    /// Name of the tool being used
1211    pub name: String,
1212
1213    /// Name of the MCP server
1214    pub server_name: String,
1215
1216    /// Input arguments for the tool
1217    pub input: Value,
1218
1219    /// Cache control for this block
1220    #[serde(skip_serializing_if = "Option::is_none")]
1221    pub cache_control: Option<CacheControl>,
1222}
1223
1224/// MCP tool result block (beta) - for user messages
1225#[serde_with::skip_serializing_none]
1226#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1227pub struct McpToolResultBlock {
1228    /// The ID of the tool use this is a result for
1229    pub tool_use_id: String,
1230
1231    /// The result content
1232    pub content: Option<ToolResultContent>,
1233
1234    /// Whether this result indicates an error
1235    pub is_error: Option<bool>,
1236
1237    /// Cache control for this block
1238    pub cache_control: Option<CacheControl>,
1239}
1240
1241/// MCP toolset definition (beta)
1242#[serde_with::skip_serializing_none]
1243#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1244pub struct McpToolset {
1245    #[serde(rename = "type")]
1246    pub toolset_type: String, // "mcp_toolset"
1247
1248    /// Name of the MCP server to configure tools for
1249    pub mcp_server_name: String,
1250
1251    /// Default configuration applied to all tools from this server
1252    pub default_config: Option<McpToolDefaultConfig>,
1253
1254    /// Configuration overrides for specific tools
1255    pub configs: Option<HashMap<String, McpToolConfig>>,
1256
1257    /// Cache control for this toolset
1258    pub cache_control: Option<CacheControl>,
1259}
1260
1261/// Default configuration for MCP tools
1262#[serde_with::skip_serializing_none]
1263#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1264pub struct McpToolDefaultConfig {
1265    /// Whether tools are enabled
1266    pub enabled: Option<bool>,
1267
1268    /// Whether to defer loading
1269    pub defer_loading: Option<bool>,
1270}
1271
1272/// Per-tool MCP configuration
1273#[serde_with::skip_serializing_none]
1274#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1275pub struct McpToolConfig {
1276    /// Whether this tool is enabled
1277    pub enabled: Option<bool>,
1278
1279    /// Whether to defer loading
1280    pub defer_loading: Option<bool>,
1281}
1282
1283// ============================================================================
1284// Beta Features - Code Execution Types
1285// ============================================================================
1286
1287/// Code execution tool (beta)
1288#[serde_with::skip_serializing_none]
1289#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1290pub struct CodeExecutionTool {
1291    #[serde(rename = "type")]
1292    pub tool_type: String, // "code_execution_20250522" or "code_execution_20250825"
1293
1294    pub name: String, // "code_execution"
1295
1296    /// Allowed callers for this tool
1297    pub allowed_callers: Option<Vec<String>>,
1298
1299    /// Whether to defer loading
1300    pub defer_loading: Option<bool>,
1301
1302    /// Whether to use strict mode
1303    pub strict: Option<bool>,
1304
1305    /// Cache control for this tool
1306    pub cache_control: Option<CacheControl>,
1307}
1308
1309/// Code execution result block (beta)
1310#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1311pub struct CodeExecutionResultBlock {
1312    /// Stdout output
1313    pub stdout: String,
1314
1315    /// Stderr output
1316    pub stderr: String,
1317
1318    /// Return code
1319    pub return_code: i32,
1320
1321    /// Output files
1322    pub content: Vec<CodeExecutionOutputBlock>,
1323}
1324
1325/// Code execution output file reference
1326#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1327pub struct CodeExecutionOutputBlock {
1328    #[serde(rename = "type")]
1329    pub block_type: String, // "code_execution_output"
1330
1331    /// File ID
1332    pub file_id: String,
1333}
1334
1335/// Code execution tool result block (beta)
1336#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1337pub struct CodeExecutionToolResultBlock {
1338    /// The ID of the tool use this is a result for
1339    pub tool_use_id: String,
1340
1341    /// The result content (success or error)
1342    pub content: CodeExecutionToolResultContent,
1343
1344    /// Cache control for this block
1345    #[serde(skip_serializing_if = "Option::is_none")]
1346    pub cache_control: Option<CacheControl>,
1347}
1348
1349/// Code execution tool result content
1350#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1351#[serde(untagged)]
1352pub enum CodeExecutionToolResultContent {
1353    Success(CodeExecutionResultBlock),
1354    Error(CodeExecutionToolResultError),
1355}
1356
1357/// Code execution tool result error
1358#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1359pub struct CodeExecutionToolResultError {
1360    #[serde(rename = "type")]
1361    pub error_type: String, // "code_execution_tool_result_error"
1362
1363    pub error_code: CodeExecutionToolResultErrorCode,
1364}
1365
1366/// Code execution error codes
1367#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1368#[serde(rename_all = "snake_case")]
1369pub enum CodeExecutionToolResultErrorCode {
1370    Unavailable,
1371    CodeExecutionExceededTimeout,
1372    ContainerExpired,
1373    InvalidToolInput,
1374}
1375
1376/// Bash code execution result block (beta)
1377#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1378pub struct BashCodeExecutionResultBlock {
1379    /// Stdout output
1380    pub stdout: String,
1381
1382    /// Stderr output
1383    pub stderr: String,
1384
1385    /// Return code
1386    pub return_code: i32,
1387
1388    /// Output files
1389    pub content: Vec<BashCodeExecutionOutputBlock>,
1390}
1391
1392/// Bash code execution output file reference
1393#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1394pub struct BashCodeExecutionOutputBlock {
1395    #[serde(rename = "type")]
1396    pub block_type: String, // "bash_code_execution_output"
1397
1398    /// File ID
1399    pub file_id: String,
1400}
1401
1402/// Bash code execution tool result block (beta)
1403#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1404pub struct BashCodeExecutionToolResultBlock {
1405    /// The ID of the tool use this is a result for
1406    pub tool_use_id: String,
1407
1408    /// The result content (success or error)
1409    pub content: BashCodeExecutionToolResultContent,
1410
1411    /// Cache control for this block
1412    #[serde(skip_serializing_if = "Option::is_none")]
1413    pub cache_control: Option<CacheControl>,
1414}
1415
1416/// Bash code execution tool result content
1417#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1418#[serde(untagged)]
1419pub enum BashCodeExecutionToolResultContent {
1420    Success(BashCodeExecutionResultBlock),
1421    Error(BashCodeExecutionToolResultError),
1422}
1423
1424/// Bash code execution tool result error
1425#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1426pub struct BashCodeExecutionToolResultError {
1427    #[serde(rename = "type")]
1428    pub error_type: String, // "bash_code_execution_tool_result_error"
1429
1430    pub error_code: BashCodeExecutionToolResultErrorCode,
1431}
1432
1433/// Bash code execution error codes
1434#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1435#[serde(rename_all = "snake_case")]
1436pub enum BashCodeExecutionToolResultErrorCode {
1437    Unavailable,
1438    CodeExecutionExceededTimeout,
1439    ContainerExpired,
1440    InvalidToolInput,
1441}
1442
1443/// Text editor code execution tool result block (beta)
1444#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1445pub struct TextEditorCodeExecutionToolResultBlock {
1446    /// The ID of the tool use this is a result for
1447    pub tool_use_id: String,
1448
1449    /// The result content
1450    pub content: TextEditorCodeExecutionToolResultContent,
1451
1452    /// Cache control for this block
1453    #[serde(skip_serializing_if = "Option::is_none")]
1454    pub cache_control: Option<CacheControl>,
1455}
1456
1457/// Text editor code execution result content
1458#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1459#[serde(untagged)]
1460pub enum TextEditorCodeExecutionToolResultContent {
1461    CreateResult(TextEditorCodeExecutionCreateResultBlock),
1462    StrReplaceResult(TextEditorCodeExecutionStrReplaceResultBlock),
1463    ViewResult(TextEditorCodeExecutionViewResultBlock),
1464    Error(TextEditorCodeExecutionToolResultError),
1465}
1466
1467/// Text editor create result block
1468#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1469pub struct TextEditorCodeExecutionCreateResultBlock {
1470    #[serde(rename = "type")]
1471    pub block_type: String, // "text_editor_code_execution_create_result"
1472}
1473
1474/// Text editor str_replace result block
1475#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1476pub struct TextEditorCodeExecutionStrReplaceResultBlock {
1477    #[serde(rename = "type")]
1478    pub block_type: String, // "text_editor_code_execution_str_replace_result"
1479
1480    /// Snippet of content around the replacement
1481    #[serde(skip_serializing_if = "Option::is_none")]
1482    pub snippet: Option<String>,
1483}
1484
1485/// Text editor view result block
1486#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1487pub struct TextEditorCodeExecutionViewResultBlock {
1488    #[serde(rename = "type")]
1489    pub block_type: String, // "text_editor_code_execution_view_result"
1490
1491    /// Content of the viewed file
1492    pub content: String,
1493}
1494
1495/// Text editor code execution tool result error
1496#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1497pub struct TextEditorCodeExecutionToolResultError {
1498    #[serde(rename = "type")]
1499    pub error_type: String,
1500
1501    pub error_code: TextEditorCodeExecutionToolResultErrorCode,
1502}
1503
1504/// Text editor code execution error codes
1505#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1506#[serde(rename_all = "snake_case")]
1507pub enum TextEditorCodeExecutionToolResultErrorCode {
1508    Unavailable,
1509    InvalidToolInput,
1510    FileNotFound,
1511    ContainerExpired,
1512}
1513
1514// ============================================================================
1515// Beta Features - Web Fetch Types
1516// ============================================================================
1517
1518/// Web fetch tool (beta)
1519#[serde_with::skip_serializing_none]
1520#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1521pub struct WebFetchTool {
1522    #[serde(rename = "type")]
1523    pub tool_type: String, // "web_fetch_20250305" or similar
1524
1525    pub name: String, // "web_fetch"
1526
1527    /// Allowed callers for this tool
1528    pub allowed_callers: Option<Vec<String>>,
1529
1530    /// Maximum number of uses
1531    pub max_uses: Option<u32>,
1532
1533    /// Cache control for this tool
1534    pub cache_control: Option<CacheControl>,
1535}
1536
1537/// Web fetch result block (beta)
1538#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1539pub struct WebFetchResultBlock {
1540    #[serde(rename = "type")]
1541    pub block_type: String, // "web_fetch_result"
1542
1543    /// The URL that was fetched
1544    pub url: String,
1545
1546    /// The document content
1547    pub content: DocumentBlock,
1548
1549    /// When the content was retrieved
1550    #[serde(skip_serializing_if = "Option::is_none")]
1551    pub retrieved_at: Option<String>,
1552}
1553
1554/// Web fetch tool result block (beta)
1555#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1556pub struct WebFetchToolResultBlock {
1557    /// The ID of the tool use this is a result for
1558    pub tool_use_id: String,
1559
1560    /// The result content (success or error)
1561    pub content: WebFetchToolResultContent,
1562
1563    /// Cache control for this block
1564    #[serde(skip_serializing_if = "Option::is_none")]
1565    pub cache_control: Option<CacheControl>,
1566}
1567
1568/// Web fetch tool result content
1569#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1570#[serde(untagged)]
1571pub enum WebFetchToolResultContent {
1572    Success(WebFetchResultBlock),
1573    Error(WebFetchToolResultError),
1574}
1575
1576/// Web fetch tool result error
1577#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1578pub struct WebFetchToolResultError {
1579    #[serde(rename = "type")]
1580    pub error_type: String, // "web_fetch_tool_result_error"
1581
1582    pub error_code: WebFetchToolResultErrorCode,
1583}
1584
1585/// Web fetch error codes
1586#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1587#[serde(rename_all = "snake_case")]
1588pub enum WebFetchToolResultErrorCode {
1589    InvalidToolInput,
1590    Unavailable,
1591    MaxUsesExceeded,
1592    TooManyRequests,
1593    UrlNotAllowed,
1594    FetchFailed,
1595    ContentTooLarge,
1596}
1597
1598// ============================================================================
1599// Beta Features - Tool Search Types
1600// ============================================================================
1601
1602/// Tool search tool (beta)
1603#[serde_with::skip_serializing_none]
1604#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1605pub struct ToolSearchTool {
1606    #[serde(rename = "type")]
1607    pub tool_type: String, // "tool_search_tool_regex" or "tool_search_tool_bm25"
1608
1609    pub name: String,
1610
1611    /// Allowed callers for this tool
1612    pub allowed_callers: Option<Vec<String>>,
1613
1614    /// Cache control for this tool
1615    pub cache_control: Option<CacheControl>,
1616}
1617
1618/// Tool reference block (beta) - returned by tool search
1619#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1620pub struct ToolReferenceBlock {
1621    #[serde(rename = "type")]
1622    pub block_type: String, // "tool_reference"
1623
1624    /// Tool name
1625    pub tool_name: String,
1626
1627    /// Tool description
1628    #[serde(skip_serializing_if = "Option::is_none")]
1629    pub description: Option<String>,
1630}
1631
1632/// Tool search result content — wraps tool references returned by tool search
1633#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1634pub struct ToolSearchResultContent {
1635    #[serde(rename = "type")]
1636    pub block_type: String, // "tool_search_tool_search_result"
1637
1638    /// Tool references found by the search
1639    pub tool_references: Vec<ToolReferenceBlock>,
1640}
1641
1642/// Tool search tool result block (beta)
1643#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1644pub struct ToolSearchToolResultBlock {
1645    /// The ID of the tool use this is a result for
1646    pub tool_use_id: String,
1647
1648    /// The search results
1649    pub content: ToolSearchResultContent,
1650
1651    /// Cache control for this block
1652    #[serde(skip_serializing_if = "Option::is_none")]
1653    pub cache_control: Option<CacheControl>,
1654}
1655
1656// ============================================================================
1657// Beta Features - Container Upload Types
1658// ============================================================================
1659
1660/// Container upload block (beta)
1661#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1662pub struct ContainerUploadBlock {
1663    #[serde(rename = "type")]
1664    pub block_type: String, // "container_upload"
1665
1666    /// File ID
1667    pub file_id: String,
1668
1669    /// File name
1670    pub file_name: String,
1671
1672    /// File path in container
1673    #[serde(skip_serializing_if = "Option::is_none")]
1674    pub file_path: Option<String>,
1675}
1676
1677// ============================================================================
1678// Beta Features - Memory Tool Types
1679// ============================================================================
1680
1681/// Memory tool (beta)
1682#[serde_with::skip_serializing_none]
1683#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1684pub struct MemoryTool {
1685    #[serde(rename = "type")]
1686    pub tool_type: String, // "memory_20250818"
1687
1688    pub name: String, // "memory"
1689
1690    /// Allowed callers for this tool
1691    pub allowed_callers: Option<Vec<String>>,
1692
1693    /// Whether to defer loading
1694    pub defer_loading: Option<bool>,
1695
1696    /// Whether to use strict mode
1697    pub strict: Option<bool>,
1698
1699    /// Input examples
1700    pub input_examples: Option<Vec<Value>>,
1701
1702    /// Cache control for this tool
1703    pub cache_control: Option<CacheControl>,
1704}
1705
1706// ============================================================================
1707// Beta Features - Computer Use Tool Types
1708// ============================================================================
1709
1710/// Computer use tool (beta)
1711#[serde_with::skip_serializing_none]
1712#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1713pub struct ComputerUseTool {
1714    #[serde(rename = "type")]
1715    pub tool_type: String, // "computer_20241022" or "computer_20250124"
1716
1717    pub name: String, // "computer"
1718
1719    /// Display width
1720    pub display_width_px: u32,
1721
1722    /// Display height
1723    pub display_height_px: u32,
1724
1725    /// Display number (optional)
1726    pub display_number: Option<u32>,
1727
1728    /// Allowed callers for this tool
1729    pub allowed_callers: Option<Vec<String>>,
1730
1731    /// Cache control for this tool
1732    pub cache_control: Option<CacheControl>,
1733}
1734
1735// ============================================================================
1736// Beta Features - Extended Input Content Block Enum
1737// ============================================================================
1738
1739/// Beta input content block types (extends InputContentBlock)
1740#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1741#[serde(tag = "type", rename_all = "snake_case")]
1742pub enum BetaInputContentBlock {
1743    // Standard types
1744    Text(TextBlock),
1745    Image(ImageBlock),
1746    Document(DocumentBlock),
1747    ToolUse(ToolUseBlock),
1748    ToolResult(ToolResultBlock),
1749    Thinking(ThinkingBlock),
1750    RedactedThinking(RedactedThinkingBlock),
1751    ServerToolUse(ServerToolUseBlock),
1752    SearchResult(SearchResultBlock),
1753    WebSearchToolResult(WebSearchToolResultBlock),
1754
1755    // Beta MCP types
1756    McpToolUse(McpToolUseBlock),
1757    McpToolResult(McpToolResultBlock),
1758
1759    // Beta code execution types
1760    CodeExecutionToolResult(CodeExecutionToolResultBlock),
1761    BashCodeExecutionToolResult(BashCodeExecutionToolResultBlock),
1762    TextEditorCodeExecutionToolResult(TextEditorCodeExecutionToolResultBlock),
1763
1764    // Beta web fetch types
1765    WebFetchToolResult(WebFetchToolResultBlock),
1766
1767    // Beta tool search types
1768    ToolSearchToolResult(ToolSearchToolResultBlock),
1769    ToolReference(ToolReferenceBlock),
1770
1771    // Beta container types
1772    ContainerUpload(ContainerUploadBlock),
1773}
1774
1775/// Beta output content block types (extends ContentBlock)
1776#[serde_with::skip_serializing_none]
1777#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1778#[serde(tag = "type", rename_all = "snake_case")]
1779pub enum BetaContentBlock {
1780    // Standard types
1781    Text {
1782        text: String,
1783        citations: Option<Vec<Citation>>,
1784    },
1785    ToolUse {
1786        id: String,
1787        name: String,
1788        input: Value,
1789    },
1790    Thinking {
1791        thinking: String,
1792        signature: String,
1793    },
1794    RedactedThinking {
1795        data: String,
1796    },
1797    ServerToolUse {
1798        id: String,
1799        name: String,
1800        input: Value,
1801    },
1802    WebSearchToolResult {
1803        tool_use_id: String,
1804        content: WebSearchToolResultContent,
1805    },
1806
1807    // Beta MCP types
1808    McpToolUse {
1809        id: String,
1810        name: String,
1811        server_name: String,
1812        input: Value,
1813    },
1814    McpToolResult {
1815        tool_use_id: String,
1816        content: Option<ToolResultContent>,
1817        is_error: Option<bool>,
1818    },
1819
1820    // Beta code execution types
1821    CodeExecutionToolResult {
1822        tool_use_id: String,
1823        content: CodeExecutionToolResultContent,
1824    },
1825    BashCodeExecutionToolResult {
1826        tool_use_id: String,
1827        content: BashCodeExecutionToolResultContent,
1828    },
1829    TextEditorCodeExecutionToolResult {
1830        tool_use_id: String,
1831        content: TextEditorCodeExecutionToolResultContent,
1832    },
1833
1834    // Beta web fetch types
1835    WebFetchToolResult {
1836        tool_use_id: String,
1837        content: WebFetchToolResultContent,
1838    },
1839
1840    // Beta tool search types
1841    ToolSearchToolResult {
1842        tool_use_id: String,
1843        content: ToolSearchResultContent,
1844    },
1845    ToolReference {
1846        tool_name: String,
1847        description: Option<String>,
1848    },
1849
1850    // Beta container types
1851    ContainerUpload {
1852        file_id: String,
1853        file_name: String,
1854        file_path: Option<String>,
1855    },
1856}
1857
1858/// Beta tool definition (extends Tool)
1859#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1860#[serde(untagged)]
1861pub enum BetaTool {
1862    // Standard tools
1863    Custom(CustomTool),
1864    Bash(BashTool),
1865    TextEditor(TextEditorTool),
1866    WebSearch(WebSearchTool),
1867
1868    // Beta tools
1869    CodeExecution(CodeExecutionTool),
1870    McpToolset(McpToolset),
1871    WebFetch(WebFetchTool),
1872    ToolSearch(ToolSearchTool),
1873    Memory(MemoryTool),
1874    ComputerUse(ComputerUseTool),
1875}
1876
1877/// Server tool names for beta features
1878#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1879#[serde(rename_all = "snake_case")]
1880pub enum BetaServerToolName {
1881    WebSearch,
1882    WebFetch,
1883    CodeExecution,
1884    BashCodeExecution,
1885    TextEditorCodeExecution,
1886    ToolSearchToolRegex,
1887    ToolSearchToolBm25,
1888}
1889
1890/// Server tool caller types (beta)
1891#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1892#[serde(tag = "type", rename_all = "snake_case")]
1893pub enum ServerToolCaller {
1894    /// Direct caller (the model itself)
1895    Direct,
1896    /// Code execution caller
1897    #[serde(rename = "code_execution_20250825")]
1898    CodeExecution20250825,
1899}
1900
1901#[cfg(test)]
1902mod tests {
1903    use serde_json::{self, json};
1904
1905    use super::*;
1906
1907    #[test]
1908    fn test_system_blocks_preserve_type_field() {
1909        let input = json!({
1910            "model": "test",
1911            "messages": [{"role": "user", "content": "hi"}],
1912            "max_tokens": 100,
1913            "system": [
1914                {"type": "text", "text": "system prompt", "cache_control": {"type": "ephemeral"}}
1915            ]
1916        });
1917
1918        let req: CreateMessageRequest = serde_json::from_value(input).expect("should deserialize");
1919        let reserialized = serde_json::to_value(&req).expect("should serialize");
1920
1921        let system_blocks = reserialized.get("system").unwrap().as_array().unwrap();
1922        let first_block = &system_blocks[0];
1923        assert_eq!(
1924            first_block.get("type").and_then(|v| v.as_str()),
1925            Some("text"),
1926            "system block must retain 'type' field after round-trip: got {first_block:?}",
1927        );
1928    }
1929
1930    #[test]
1931    fn test_message_content_blocks_preserve_type_field() {
1932        let input = json!({
1933            "model": "test",
1934            "messages": [{
1935                "role": "user",
1936                "content": [
1937                    {"type": "text", "text": "hello", "cache_control": {"type": "ephemeral"}}
1938                ]
1939            }],
1940            "max_tokens": 100
1941        });
1942
1943        let req: CreateMessageRequest = serde_json::from_value(input).expect("should deserialize");
1944        let reserialized = serde_json::to_value(&req).expect("should serialize");
1945
1946        let msg = &reserialized["messages"][0];
1947        let content_blocks = msg["content"].as_array().unwrap();
1948        let first_block = &content_blocks[0];
1949        assert_eq!(
1950            first_block.get("type").and_then(|v| v.as_str()),
1951            Some("text"),
1952            "content block must retain 'type' field: got {first_block:?}",
1953        );
1954    }
1955
1956    #[test]
1957    fn test_unknown_fields_preserved_via_flatten() {
1958        let input = json!({
1959            "model": "test-model",
1960            "messages": [{"role": "user", "content": "hello"}],
1961            "max_tokens": 100,
1962            "thinking": {"type": "adaptive"},
1963            "context_management": {"edits": [{"type": "clear_thinking", "keep": "all"}]},
1964            "output_config": {"effort": "high"},
1965            "stream": true
1966        });
1967
1968        let req: CreateMessageRequest =
1969            serde_json::from_value(input.clone()).expect("should deserialize");
1970        assert!(matches!(
1971            req.thinking,
1972            Some(ThinkingConfig::Adaptive { .. })
1973        ));
1974
1975        let reserialized = serde_json::to_value(&req).expect("should serialize");
1976        assert_eq!(
1977            reserialized.get("context_management"),
1978            input.get("context_management"),
1979            "context_management must survive round-trip"
1980        );
1981        assert_eq!(
1982            reserialized.get("output_config"),
1983            input.get("output_config"),
1984            "output_config must survive round-trip"
1985        );
1986    }
1987
1988    fn base_request() -> CreateMessageRequest {
1989        CreateMessageRequest {
1990            model: "claude-test".to_string(),
1991            messages: vec![InputMessage {
1992                role: Role::User,
1993                content: InputContent::String("hello".to_string()),
1994            }],
1995            max_tokens: 16,
1996            metadata: None,
1997            service_tier: None,
1998            stop_sequences: None,
1999            stream: None,
2000            system: None,
2001            temperature: None,
2002            thinking: None,
2003            tool_choice: None,
2004            tools: None,
2005            top_k: None,
2006            top_p: None,
2007            container: None,
2008            mcp_servers: None,
2009            other: Map::new(),
2010        }
2011    }
2012
2013    fn custom_tool(name: &str) -> Tool {
2014        Tool::Custom(CustomTool {
2015            name: name.to_string(),
2016            tool_type: None,
2017            description: Some("test tool".to_string()),
2018            input_schema: InputSchema {
2019                schema_type: "object".to_string(),
2020                properties: None,
2021                required: None,
2022                additional: HashMap::new(),
2023            },
2024            defer_loading: None,
2025            cache_control: None,
2026        })
2027    }
2028
2029    fn mcp_toolset(configs: Option<HashMap<String, McpToolConfig>>) -> Tool {
2030        Tool::McpToolset(McpToolset {
2031            toolset_type: "mcp_toolset".to_string(),
2032            mcp_server_name: "brave".to_string(),
2033            default_config: None,
2034            configs,
2035            cache_control: None,
2036        })
2037    }
2038
2039    fn mcp_server_config() -> McpServerConfig {
2040        McpServerConfig {
2041            server_type: "url".to_string(),
2042            name: "brave".to_string(),
2043            url: "https://example.com/mcp".to_string(),
2044            authorization_token: None,
2045            tool_configuration: None,
2046        }
2047    }
2048    #[test]
2049    fn test_tool_mcp_toolset_defer_loading_deserialization() {
2050        let json = r#"{
2051            "type": "mcp_toolset",
2052            "mcp_server_name": "brave",
2053            "default_config": {"defer_loading": true}
2054        }"#;
2055
2056        let tool: Tool = serde_json::from_str(json).expect("Failed to deserialize McpToolset Tool");
2057        match tool {
2058            Tool::McpToolset(ts) => {
2059                assert_eq!(ts.mcp_server_name, "brave");
2060                let default_config = ts.default_config.expect("default_config should be Some");
2061                assert_eq!(default_config.defer_loading, Some(true));
2062            }
2063            other => panic!(
2064                "Expected McpToolset, got {:?}",
2065                std::mem::discriminant(&other)
2066            ),
2067        }
2068    }
2069
2070    #[test]
2071    fn test_tool_search_tool_deserialization() {
2072        let json = r#"{
2073            "type": "tool_search_tool_regex_20251119",
2074            "name": "tool_search_tool_regex"
2075        }"#;
2076
2077        let tool: Tool = serde_json::from_str(json).expect("Failed to deserialize ToolSearch Tool");
2078        match tool {
2079            Tool::ToolSearch(ts) => {
2080                assert_eq!(ts.name, "tool_search_tool_regex");
2081                assert_eq!(ts.tool_type, "tool_search_tool_regex_20251119");
2082            }
2083            other => panic!(
2084                "Expected ToolSearch, got {:?}",
2085                std::mem::discriminant(&other)
2086            ),
2087        }
2088    }
2089
2090    #[test]
2091    fn test_content_block_tool_search_tool_result_deserialization() {
2092        let json = r#"{
2093            "type": "tool_search_tool_result",
2094            "tool_use_id": "srvtoolu_015dw5iXvktXLmqwpyzo4Dp2",
2095            "content": {
2096                "type": "tool_search_tool_search_result",
2097                "tool_references": [
2098                    {"type": "tool_reference", "tool_name": "get_weather"}
2099                ]
2100            }
2101        }"#;
2102
2103        let block: ContentBlock = serde_json::from_str(json)
2104            .expect("Failed to deserialize tool_search_tool_result ContentBlock");
2105        match block {
2106            ContentBlock::ToolSearchToolResult {
2107                tool_use_id,
2108                content,
2109            } => {
2110                assert_eq!(tool_use_id, "srvtoolu_015dw5iXvktXLmqwpyzo4Dp2");
2111                assert_eq!(content.tool_references.len(), 1);
2112                assert_eq!(content.tool_references[0].tool_name, "get_weather");
2113            }
2114            _ => panic!("Expected ToolSearchToolResult variant"),
2115        }
2116    }
2117
2118    #[test]
2119    fn test_content_block_server_tool_use_deserialization() {
2120        let json = r#"{
2121            "type": "server_tool_use",
2122            "id": "srvtoolu_015dw5iXvktXLmqwpyzo4Dp2",
2123            "name": "tool_search_tool_regex",
2124            "input": {"query": "weather"}
2125        }"#;
2126
2127        let block: ContentBlock =
2128            serde_json::from_str(json).expect("Failed to deserialize server_tool_use ContentBlock");
2129        match block {
2130            ContentBlock::ServerToolUse { id, name, input: _ } => {
2131                assert_eq!(id, "srvtoolu_015dw5iXvktXLmqwpyzo4Dp2");
2132                assert_eq!(name, "tool_search_tool_regex");
2133            }
2134            _ => panic!("Expected ServerToolUse variant"),
2135        }
2136    }
2137
2138    #[test]
2139    fn test_content_block_tool_reference_deserialization() {
2140        let json = r#"{
2141            "type": "tool_reference",
2142            "tool_name": "get_weather",
2143            "description": "Get the weather for a location"
2144        }"#;
2145
2146        let block: ContentBlock =
2147            serde_json::from_str(json).expect("Failed to deserialize tool_reference ContentBlock");
2148        match block {
2149            ContentBlock::ToolReference {
2150                tool_name,
2151                description,
2152            } => {
2153                assert_eq!(tool_name, "get_weather");
2154                assert_eq!(description.unwrap(), "Get the weather for a location");
2155            }
2156            _ => panic!("Expected ToolReference variant"),
2157        }
2158    }
2159
2160    #[test]
2161    fn test_tool_choice_auto_requires_tools() {
2162        let mut request = base_request();
2163        request.tool_choice = Some(ToolChoice::Auto {
2164            disable_parallel_tool_use: None,
2165        });
2166
2167        assert!(request.validate().is_err());
2168    }
2169
2170    #[test]
2171    fn test_tool_choice_any_requires_tools() {
2172        let mut request = base_request();
2173        request.tool_choice = Some(ToolChoice::Any {
2174            disable_parallel_tool_use: None,
2175        });
2176
2177        assert!(request.validate().is_err());
2178    }
2179
2180    #[test]
2181    fn test_tool_choice_auto_with_tools_is_valid() {
2182        let mut request = base_request();
2183        request.tool_choice = Some(ToolChoice::Auto {
2184            disable_parallel_tool_use: None,
2185        });
2186        request.tools = Some(vec![custom_tool("get_weather")]);
2187
2188        assert!(request.validate().is_ok());
2189    }
2190
2191    #[test]
2192    fn test_tool_choice_any_with_tools_is_valid() {
2193        let mut request = base_request();
2194        request.tool_choice = Some(ToolChoice::Any {
2195            disable_parallel_tool_use: None,
2196        });
2197        request.tools = Some(vec![custom_tool("get_weather")]);
2198
2199        assert!(request.validate().is_ok());
2200    }
2201
2202    #[test]
2203    fn test_tool_choice_specific_tool_requires_tools() {
2204        let mut request = base_request();
2205        request.tool_choice = Some(ToolChoice::Tool {
2206            name: "get_weather".to_string(),
2207            disable_parallel_tool_use: None,
2208        });
2209
2210        assert!(request.validate().is_err());
2211    }
2212
2213    #[test]
2214    fn test_tool_choice_specific_tool_must_exist() {
2215        let mut request = base_request();
2216        request.tool_choice = Some(ToolChoice::Tool {
2217            name: "get_weather".to_string(),
2218            disable_parallel_tool_use: None,
2219        });
2220        request.tools = Some(vec![custom_tool("search_web")]);
2221
2222        assert!(request.validate().is_err());
2223    }
2224
2225    #[test]
2226    fn test_tool_choice_none_without_tools_is_valid() {
2227        let mut request = base_request();
2228        request.tool_choice = Some(ToolChoice::None);
2229
2230        assert!(request.validate().is_ok());
2231    }
2232
2233    #[test]
2234    fn test_tool_choice_specific_tool_is_valid_when_declared() {
2235        let mut request = base_request();
2236        request.tool_choice = Some(ToolChoice::Tool {
2237            name: "get_weather".to_string(),
2238            disable_parallel_tool_use: None,
2239        });
2240        request.tools = Some(vec![custom_tool("get_weather")]);
2241
2242        assert!(request.validate().is_ok());
2243    }
2244
2245    #[test]
2246    fn test_tool_choice_specific_tool_is_valid_with_mcp_toolset() {
2247        let mut request = base_request();
2248        request.tool_choice = Some(ToolChoice::Tool {
2249            name: "get_weather".to_string(),
2250            disable_parallel_tool_use: None,
2251        });
2252        request.tools = Some(vec![mcp_toolset(None)]);
2253        request.mcp_servers = Some(vec![mcp_server_config()]);
2254
2255        assert!(request.validate().is_ok());
2256    }
2257
2258    #[test]
2259    fn test_tool_choice_specific_tool_uses_mcp_toolset_default_when_override_missing() {
2260        let mut request = base_request();
2261        request.tool_choice = Some(ToolChoice::Tool {
2262            name: "get_weather".to_string(),
2263            disable_parallel_tool_use: None,
2264        });
2265        request.tools = Some(vec![mcp_toolset(Some(HashMap::from([(
2266            "search_web".to_string(),
2267            McpToolConfig {
2268                enabled: Some(false),
2269                defer_loading: None,
2270            },
2271        )])))]);
2272        request.mcp_servers = Some(vec![mcp_server_config()]);
2273
2274        assert!(request.validate().is_ok());
2275    }
2276
2277    #[test]
2278    fn test_tool_choice_specific_tool_must_be_enabled_in_mcp_toolset_configs() {
2279        let mut request = base_request();
2280        request.tool_choice = Some(ToolChoice::Tool {
2281            name: "get_weather".to_string(),
2282            disable_parallel_tool_use: None,
2283        });
2284        request.tools = Some(vec![mcp_toolset(Some(HashMap::from([(
2285            "get_weather".to_string(),
2286            McpToolConfig {
2287                enabled: Some(false),
2288                defer_loading: None,
2289            },
2290        )])))]);
2291        request.mcp_servers = Some(vec![mcp_server_config()]);
2292
2293        assert!(request.validate().is_err());
2294    }
2295
2296    #[test]
2297    fn test_thinking_config_adaptive_minimal() {
2298        let cfg: ThinkingConfig = serde_json::from_str(r#"{"type":"adaptive"}"#).unwrap();
2299        match cfg {
2300            ThinkingConfig::Adaptive { display } => assert_eq!(display, None),
2301            other => panic!("expected Adaptive, got {other:?}"),
2302        }
2303    }
2304
2305    #[test]
2306    fn test_thinking_config_adaptive_with_display() {
2307        let cfg: ThinkingConfig =
2308            serde_json::from_str(r#"{"type":"adaptive","display":"omitted"}"#).unwrap();
2309        match cfg {
2310            ThinkingConfig::Adaptive { display } => {
2311                assert_eq!(display, Some(ThinkingDisplay::Omitted));
2312            }
2313            other => panic!("expected Adaptive, got {other:?}"),
2314        }
2315
2316        let cfg: ThinkingConfig =
2317            serde_json::from_str(r#"{"type":"adaptive","display":"summarized"}"#).unwrap();
2318        match cfg {
2319            ThinkingConfig::Adaptive { display } => {
2320                assert_eq!(display, Some(ThinkingDisplay::Summarized));
2321            }
2322            other => panic!("expected Adaptive, got {other:?}"),
2323        }
2324    }
2325
2326    #[test]
2327    fn test_thinking_config_adaptive_round_trip_omits_null_display() {
2328        let cfg = ThinkingConfig::Adaptive { display: None };
2329        let json = serde_json::to_string(&cfg).unwrap();
2330        assert_eq!(json, r#"{"type":"adaptive"}"#);
2331    }
2332
2333    #[test]
2334    fn test_thinking_config_existing_variants_still_work() {
2335        let cfg: ThinkingConfig =
2336            serde_json::from_str(r#"{"type":"enabled","budget_tokens":1024}"#).unwrap();
2337        assert!(matches!(
2338            cfg,
2339            ThinkingConfig::Enabled {
2340                budget_tokens: 1024,
2341                display: None
2342            }
2343        ));
2344
2345        let cfg: ThinkingConfig = serde_json::from_str(r#"{"type":"disabled"}"#).unwrap();
2346        assert!(matches!(cfg, ThinkingConfig::Disabled));
2347    }
2348
2349    #[test]
2350    fn test_thinking_config_enabled_with_display() {
2351        let cfg: ThinkingConfig = serde_json::from_str(
2352            r#"{"type":"enabled","budget_tokens":2048,"display":"summarized"}"#,
2353        )
2354        .unwrap();
2355        match cfg {
2356            ThinkingConfig::Enabled {
2357                budget_tokens,
2358                display,
2359            } => {
2360                assert_eq!(budget_tokens, 2048);
2361                assert_eq!(display, Some(ThinkingDisplay::Summarized));
2362            }
2363            other => panic!("expected Enabled, got {other:?}"),
2364        }
2365    }
2366
2367    #[test]
2368    fn test_thinking_config_enabled_round_trip_omits_null_display() {
2369        let cfg = ThinkingConfig::Enabled {
2370            budget_tokens: 1024,
2371            display: None,
2372        };
2373        let json = serde_json::to_string(&cfg).unwrap();
2374        assert_eq!(json, r#"{"type":"enabled","budget_tokens":1024}"#);
2375    }
2376
2377    #[test]
2378    fn test_full_message_with_tool_search_flow_deserialization() {
2379        // Simulates the full response from Anthropic API with tool search flow
2380        let json = r#"{
2381            "id": "msg_01TEST",
2382            "type": "message",
2383            "role": "assistant",
2384            "model": "claude-sonnet-4-5-20250929",
2385            "content": [
2386                {
2387                    "type": "server_tool_use",
2388                    "id": "srvtoolu_015dw5iXvktXLmqwpyzo4Dp2",
2389                    "name": "tool_search_tool_regex",
2390                    "input": {"query": "weather"}
2391                },
2392                {
2393                    "type": "tool_search_tool_result",
2394                    "tool_use_id": "srvtoolu_015dw5iXvktXLmqwpyzo4Dp2",
2395                    "content": {
2396                        "type": "tool_search_tool_search_result",
2397                        "tool_references": [
2398                            {"type": "tool_reference", "tool_name": "get_weather"}
2399                        ]
2400                    }
2401                },
2402                {
2403                    "type": "tool_use",
2404                    "id": "toolu_01ABC",
2405                    "name": "get_weather",
2406                    "input": {"location": "San Francisco"}
2407                }
2408            ],
2409            "stop_reason": "tool_use",
2410            "stop_sequence": null,
2411            "usage": {
2412                "input_tokens": 100,
2413                "output_tokens": 50
2414            }
2415        }"#;
2416
2417        let msg: Message = serde_json::from_str(json)
2418            .expect("Failed to deserialize Message with tool search flow");
2419        assert_eq!(msg.content.len(), 3);
2420        assert!(matches!(msg.content[0], ContentBlock::ServerToolUse { .. }));
2421        assert!(matches!(
2422            msg.content[1],
2423            ContentBlock::ToolSearchToolResult { .. }
2424        ));
2425        assert!(matches!(msg.content[2], ContentBlock::ToolUse { .. }));
2426    }
2427}