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