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