1use crate::content::ContentBlock;
26use serde::{Deserialize, Serialize};
27use serde_json::Value;
28
29pub use crate::prompts::Role;
32
33#[deprecated(
34 since = "0.4.0",
35 note = "Deprecated per SEP-2577 (2026-07-28). \
36 Replacement: integrate directly with LLM provider APIs. \
37 Earliest removal: first release on/after 2027-07-28."
38)]
39#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
44#[serde(rename_all = "camelCase")]
45pub struct ModelHint {
46 #[serde(skip_serializing_if = "Option::is_none")]
48 pub name: Option<String>,
49}
50
51#[allow(deprecated)]
52impl ModelHint {
53 pub fn new(name: impl Into<String>) -> Self {
54 Self {
55 name: Some(name.into()),
56 }
57 }
58}
59
60#[deprecated(
61 since = "0.4.0",
62 note = "Deprecated per SEP-2577 (2026-07-28). \
63 Replacement: integrate directly with LLM provider APIs. \
64 Earliest removal: first release on/after 2027-07-28."
65)]
66#[derive(Debug, Clone, Serialize, Deserialize)]
68#[serde(rename_all = "camelCase")]
69pub struct ModelPreferences {
70 #[serde(skip_serializing_if = "Option::is_none")]
72 #[allow(deprecated)]
73 pub hints: Option<Vec<ModelHint>>,
74 #[serde(skip_serializing_if = "Option::is_none")]
76 pub cost_priority: Option<f64>,
77 #[serde(skip_serializing_if = "Option::is_none")]
79 pub speed_priority: Option<f64>,
80 #[serde(skip_serializing_if = "Option::is_none")]
82 pub intelligence_priority: Option<f64>,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
87#[serde(rename_all = "lowercase")]
88pub enum ToolChoiceMode {
89 Auto,
91 None,
93 #[serde(alias = "any")]
96 Required,
97}
98
99#[deprecated(
100 since = "0.4.0",
101 note = "Deprecated per SEP-2577 (2026-07-28). \
102 Replacement: integrate directly with LLM provider APIs. \
103 Earliest removal: first release on/after 2027-07-28."
104)]
105#[derive(Debug, Clone, Serialize, Deserialize, Default)]
110#[serde(rename_all = "camelCase")]
111pub struct ToolChoice {
112 #[serde(skip_serializing_if = "Option::is_none")]
114 pub mode: Option<ToolChoiceMode>,
115}
116
117#[allow(deprecated)]
118impl ToolChoice {
119 pub fn auto() -> Self {
120 Self {
121 mode: Some(ToolChoiceMode::Auto),
122 }
123 }
124
125 pub fn none() -> Self {
126 Self {
127 mode: Some(ToolChoiceMode::None),
128 }
129 }
130
131 pub fn required() -> Self {
133 Self {
134 mode: Some(ToolChoiceMode::Required),
135 }
136 }
137
138 pub fn any() -> Self {
141 Self::required()
142 }
143
144 pub fn effective_mode(&self) -> ToolChoiceMode {
146 self.mode.clone().unwrap_or(ToolChoiceMode::Auto)
147 }
148}
149
150#[deprecated(
159 since = "0.4.0",
160 note = "Deprecated per SEP-2577 (2026-07-28). \
161 Replacement: integrate directly with LLM provider APIs. \
162 Earliest removal: first release on/after 2027-07-28."
163)]
164#[derive(Debug, Clone, Serialize, Deserialize)]
165#[serde(tag = "type")]
166pub enum SamplingMessageContentBlock {
167 #[serde(rename = "text")]
168 Text {
169 text: String,
170 #[serde(skip_serializing_if = "Option::is_none")]
171 annotations: Option<crate::meta::Annotations>,
172 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
173 meta: Option<std::collections::HashMap<String, Value>>,
174 },
175 #[serde(rename = "image")]
176 Image {
177 data: String,
178 #[serde(rename = "mimeType")]
179 mime_type: String,
180 #[serde(skip_serializing_if = "Option::is_none")]
181 annotations: Option<crate::meta::Annotations>,
182 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
183 meta: Option<std::collections::HashMap<String, Value>>,
184 },
185 #[serde(rename = "audio")]
186 Audio {
187 data: String,
188 #[serde(rename = "mimeType")]
189 mime_type: String,
190 #[serde(skip_serializing_if = "Option::is_none")]
191 annotations: Option<crate::meta::Annotations>,
192 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
193 meta: Option<std::collections::HashMap<String, Value>>,
194 },
195 #[serde(rename = "tool_use")]
196 ToolUse {
197 id: String,
198 name: String,
199 input: std::collections::HashMap<String, Value>,
200 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
201 meta: Option<std::collections::HashMap<String, Value>>,
202 },
203 #[serde(rename = "tool_result")]
204 ToolResult {
205 #[serde(rename = "toolUseId")]
206 tool_use_id: String,
207 content: Vec<ContentBlock>,
208 #[serde(rename = "structuredContent", skip_serializing_if = "Option::is_none")]
209 structured_content: Option<Value>,
210 #[serde(rename = "isError", skip_serializing_if = "Option::is_none")]
211 is_error: Option<bool>,
212 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
213 meta: Option<std::collections::HashMap<String, Value>>,
214 },
215}
216
217#[allow(deprecated)]
218impl SamplingMessageContentBlock {
219 pub fn text(text: impl Into<String>) -> Self {
221 Self::Text {
222 text: text.into(),
223 annotations: None,
224 meta: None,
225 }
226 }
227}
228
229#[deprecated(
236 since = "0.4.0",
237 note = "Deprecated per SEP-2577 (2026-07-28). \
238 Replacement: integrate directly with LLM provider APIs. \
239 Earliest removal: first release on/after 2027-07-28."
240)]
241#[allow(deprecated)]
242#[derive(Debug, Clone, Serialize, Deserialize)]
243#[serde(untagged)]
244pub enum SamplingMessageContent {
245 Single(SamplingMessageContentBlock),
247 Multiple(Vec<SamplingMessageContentBlock>),
249}
250
251#[deprecated(
255 since = "0.4.0",
256 note = "Deprecated per SEP-2577 (2026-07-28). \
257 Replacement: integrate directly with LLM provider APIs. \
258 Earliest removal: first release on/after 2027-07-28."
259)]
260#[allow(deprecated)]
261#[derive(Debug, Clone, Serialize, Deserialize)]
262#[serde(rename_all = "camelCase")]
263pub struct SamplingMessage {
264 pub role: Role,
265 pub content: SamplingMessageContent,
266 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
267 pub meta: Option<std::collections::HashMap<String, Value>>,
268}
269
270#[deprecated(
278 since = "0.4.0",
279 note = "Deprecated per SEP-2577 (2026-07-28). \
280 Replacement: integrate directly with LLM provider APIs. \
281 Earliest removal: first release on/after 2027-07-28."
282)]
283#[allow(deprecated)]
284#[derive(Debug, Clone, Serialize, Deserialize)]
285#[serde(rename_all = "camelCase")]
286pub struct CreateMessageRequestParams {
287 pub messages: Vec<SamplingMessage>,
289 #[serde(skip_serializing_if = "Option::is_none")]
291 pub model_preferences: Option<ModelPreferences>,
292 #[serde(skip_serializing_if = "Option::is_none")]
294 pub system_prompt: Option<String>,
295 #[serde(skip_serializing_if = "Option::is_none")]
297 pub include_context: Option<String>,
298 #[serde(skip_serializing_if = "Option::is_none")]
300 pub temperature: Option<f64>,
301 pub max_tokens: u32,
303 #[serde(skip_serializing_if = "Option::is_none")]
305 pub stop_sequences: Option<Vec<String>>,
306 #[serde(skip_serializing_if = "Option::is_none")]
312 pub metadata: Option<std::collections::HashMap<String, Value>>,
313 #[serde(skip_serializing_if = "Option::is_none")]
315 pub tools: Option<Vec<crate::tools::Tool>>,
316 #[serde(skip_serializing_if = "Option::is_none")]
318 pub tool_choice: Option<ToolChoice>,
319 }
323
324#[deprecated(
328 since = "0.4.0",
329 note = "Deprecated per SEP-2577 (2026-07-28). \
330 Replacement: integrate directly with LLM provider APIs. \
331 Earliest removal: first release on/after 2027-07-28."
332)]
333#[allow(deprecated)]
334#[derive(Debug, Clone, Serialize, Deserialize)]
335#[serde(rename_all = "camelCase")]
336pub struct CreateMessageRequest {
337 pub method: String,
339 pub params: CreateMessageRequestParams,
341}
342
343#[deprecated(
348 since = "0.4.0",
349 note = "Deprecated per SEP-2577 (2026-07-28). \
350 Replacement: integrate directly with LLM provider APIs. \
351 Earliest removal: first release on/after 2027-07-28."
352)]
353#[allow(deprecated)]
354#[derive(Debug, Clone, Serialize, Deserialize)]
355#[serde(rename_all = "camelCase")]
356pub struct CreateMessageResult {
357 pub role: Role,
358 pub content: SamplingMessageContent,
359 pub model: String,
360 #[serde(skip_serializing_if = "Option::is_none")]
361 pub stop_reason: Option<String>,
362 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
363 pub meta: Option<crate::meta::ResultMetaObject>,
364}
365
366#[allow(deprecated)]
367impl CreateMessageRequestParams {
368 pub fn new(messages: Vec<SamplingMessage>, max_tokens: u32) -> Self {
369 Self {
370 messages,
371 model_preferences: None,
372 system_prompt: None,
373 include_context: None,
374 temperature: None,
375 max_tokens,
376 stop_sequences: None,
377 metadata: None,
378 tools: None,
379 tool_choice: None,
380 }
381 }
382
383 pub fn with_tools(mut self, tools: Vec<crate::tools::Tool>) -> Self {
384 self.tools = Some(tools);
385 self
386 }
387
388 pub fn with_tool_choice(mut self, tool_choice: ToolChoice) -> Self {
389 self.tool_choice = Some(tool_choice);
390 self
391 }
392
393 pub fn with_model_preferences(mut self, preferences: ModelPreferences) -> Self {
394 self.model_preferences = Some(preferences);
395 self
396 }
397
398 pub fn with_system_prompt(mut self, prompt: impl Into<String>) -> Self {
399 self.system_prompt = Some(prompt.into());
400 self
401 }
402
403 pub fn with_temperature(mut self, temperature: f64) -> Self {
404 self.temperature = Some(temperature);
405 self
406 }
407
408 pub fn with_stop_sequences(mut self, sequences: Vec<String>) -> Self {
409 self.stop_sequences = Some(sequences);
410 self
411 }
412
413 pub fn validate_message_shape(&self) -> Result<(), String> {
422 fn blocks(content: &SamplingMessageContent) -> Vec<&SamplingMessageContentBlock> {
423 match content {
424 SamplingMessageContent::Single(b) => vec![b],
425 SamplingMessageContent::Multiple(bs) => bs.iter().collect(),
426 }
427 }
428
429 fn is_tool_result(block: &SamplingMessageContentBlock) -> bool {
430 matches!(block, SamplingMessageContentBlock::ToolResult { .. })
431 }
432
433 for (i, message) in self.messages.iter().enumerate() {
434 let content_blocks = blocks(&message.content);
435
436 if message.role == Role::User {
437 let any_tool_result = content_blocks.iter().any(|b| is_tool_result(b));
438 let all_tool_result = content_blocks.iter().all(|b| is_tool_result(b));
439 if any_tool_result && !all_tool_result {
440 return Err(format!(
441 "message {i}: a user message containing a ToolResult block must contain ONLY ToolResult blocks"
442 ));
443 }
444 }
445
446 if message.role == Role::Assistant {
447 let tool_use_ids: Vec<&str> = content_blocks
448 .iter()
449 .filter_map(|b| match b {
450 SamplingMessageContentBlock::ToolUse { id, .. } => Some(id.as_str()),
451 _ => None,
452 })
453 .collect();
454 if tool_use_ids.is_empty() {
455 continue;
456 }
457
458 let next = self.messages.get(i + 1).filter(|m| m.role == Role::User);
459 let Some(next) = next else {
460 return Err(format!(
461 "message {i}: assistant ToolUse must be immediately followed by a user message of ToolResult blocks"
462 ));
463 };
464 let next_blocks = blocks(&next.content);
465 if next_blocks.is_empty() || !next_blocks.iter().all(|b| is_tool_result(b)) {
466 return Err(format!(
467 "message {i}: assistant ToolUse must be immediately followed by a user message consisting entirely of ToolResult blocks"
468 ));
469 }
470 let next_ids: std::collections::HashSet<&str> = next_blocks
471 .iter()
472 .filter_map(|b| match b {
473 SamplingMessageContentBlock::ToolResult { tool_use_id, .. } => {
474 Some(tool_use_id.as_str())
475 }
476 _ => None,
477 })
478 .collect();
479 for id in tool_use_ids {
480 if !next_ids.contains(id) {
481 return Err(format!(
482 "message {i}: ToolUse id '{id}' has no matching ToolResult in the following message"
483 ));
484 }
485 }
486 }
487 }
488
489 Ok(())
490 }
491}
492
493#[allow(deprecated)]
494impl CreateMessageRequest {
495 pub fn new(messages: Vec<SamplingMessage>, max_tokens: u32) -> Self {
496 Self {
497 method: "sampling/createMessage".to_string(),
498 params: CreateMessageRequestParams::new(messages, max_tokens),
499 }
500 }
501
502 pub fn with_params(mut self, params: CreateMessageRequestParams) -> Self {
504 self.params = params;
505 self
506 }
507
508 pub fn with_model_preferences(mut self, preferences: ModelPreferences) -> Self {
509 self.params = self.params.with_model_preferences(preferences);
510 self
511 }
512
513 pub fn with_system_prompt(mut self, prompt: impl Into<String>) -> Self {
514 self.params = self.params.with_system_prompt(prompt);
515 self
516 }
517
518 pub fn with_temperature(mut self, temperature: f64) -> Self {
519 self.params = self.params.with_temperature(temperature);
520 self
521 }
522
523 pub fn with_stop_sequences(mut self, sequences: Vec<String>) -> Self {
524 self.params = self.params.with_stop_sequences(sequences);
525 self
526 }
527
528 pub fn with_tools(mut self, tools: Vec<crate::tools::Tool>) -> Self {
529 self.params = self.params.with_tools(tools);
530 self
531 }
532
533 pub fn with_tool_choice(mut self, tool_choice: ToolChoice) -> Self {
534 self.params = self.params.with_tool_choice(tool_choice);
535 self
536 }
537}
538
539#[allow(deprecated)]
540impl CreateMessageResult {
541 pub fn new(role: Role, content: SamplingMessageContent, model: impl Into<String>) -> Self {
542 Self {
543 role,
544 content,
545 model: model.into(),
546 stop_reason: None,
547 meta: None,
548 }
549 }
550
551 pub fn single(
553 role: Role,
554 block: SamplingMessageContentBlock,
555 model: impl Into<String>,
556 ) -> Self {
557 Self::new(role, SamplingMessageContent::Single(block), model)
558 }
559
560 pub fn with_stop_reason(mut self, reason: impl Into<String>) -> Self {
561 self.stop_reason = Some(reason.into());
562 self
563 }
564
565 pub fn with_meta(mut self, meta: impl Into<crate::meta::ResultMetaObject>) -> Self {
566 self.meta = Some(meta.into());
567 self
568 }
569}
570
571use crate::traits::*;
581use std::collections::HashMap;
582
583#[allow(deprecated)]
585impl Params for CreateMessageRequestParams {}
586
587#[allow(deprecated)]
588impl HasCreateMessageRequestParams for CreateMessageRequestParams {
589 fn messages(&self) -> &Vec<SamplingMessage> {
590 &self.messages
591 }
592
593 fn model_preferences(&self) -> Option<&ModelPreferences> {
594 self.model_preferences.as_ref()
595 }
596
597 fn system_prompt(&self) -> Option<&String> {
598 self.system_prompt.as_ref()
599 }
600
601 fn include_context(&self) -> Option<&String> {
602 self.include_context.as_ref()
603 }
604
605 fn temperature(&self) -> Option<&f64> {
606 self.temperature.as_ref()
607 }
608
609 fn max_tokens(&self) -> u32 {
610 self.max_tokens
611 }
612
613 fn stop_sequences(&self) -> Option<&Vec<String>> {
614 self.stop_sequences.as_ref()
615 }
616
617 fn metadata(&self) -> Option<&std::collections::HashMap<String, Value>> {
618 self.metadata.as_ref()
619 }
620}
621
622#[allow(deprecated)]
628impl HasMethod for CreateMessageRequest {
629 fn method(&self) -> &str {
630 &self.method
631 }
632}
633
634#[allow(deprecated)]
635impl HasParams for CreateMessageRequest {
636 fn params(&self) -> Option<&dyn Params> {
637 Some(&self.params)
638 }
639}
640
641#[allow(deprecated)]
643impl HasData for CreateMessageResult {
644 fn data(&self) -> HashMap<String, Value> {
645 let mut data = HashMap::new();
646 data.insert(
647 "role".to_string(),
648 serde_json::to_value(&self.role).unwrap_or(Value::String("user".to_string())),
649 );
650 data.insert(
651 "content".to_string(),
652 serde_json::to_value(&self.content).unwrap_or(Value::Null),
653 );
654 data.insert("model".to_string(), Value::String(self.model.clone()));
655 if let Some(ref stop_reason) = self.stop_reason {
656 data.insert("stopReason".to_string(), Value::String(stop_reason.clone()));
657 }
658 data
659 }
660}
661
662#[allow(deprecated)]
663impl HasMeta for CreateMessageResult {
664 fn meta(&self) -> Option<&crate::meta::ResultMetaObject> {
665 self.meta.as_ref()
666 }
667}
668
669#[allow(deprecated)]
675impl crate::traits::CreateMessageResult for CreateMessageResult {
676 fn role(&self) -> &Role {
677 &self.role
678 }
679
680 fn content(&self) -> &SamplingMessageContent {
681 &self.content
682 }
683
684 fn model(&self) -> &String {
685 &self.model
686 }
687
688 fn stop_reason(&self) -> Option<&String> {
689 self.stop_reason.as_ref()
690 }
691}
692
693#[allow(deprecated)]
700impl ModelPreferences {
701 pub fn new() -> Self {
702 Self {
703 hints: None,
704 cost_priority: None,
705 speed_priority: None,
706 intelligence_priority: None,
707 }
708 }
709
710 pub fn with_hints(mut self, hints: Vec<ModelHint>) -> Self {
711 self.hints = Some(hints);
712 self
713 }
714
715 pub fn with_cost_priority(mut self, priority: f64) -> Self {
716 self.cost_priority = Some(priority);
717 self
718 }
719
720 pub fn with_speed_priority(mut self, priority: f64) -> Self {
721 self.speed_priority = Some(priority);
722 self
723 }
724
725 pub fn with_intelligence_priority(mut self, priority: f64) -> Self {
726 self.intelligence_priority = Some(priority);
727 self
728 }
729}
730
731#[allow(deprecated)]
732impl Default for ModelPreferences {
733 fn default() -> Self {
734 Self::new()
735 }
736}
737
738#[allow(deprecated)]
739impl SamplingMessage {
740 pub fn new(role: Role, content: SamplingMessageContent) -> Self {
741 Self {
742 role,
743 content,
744 meta: None,
745 }
746 }
747
748 pub fn single(role: Role, block: SamplingMessageContentBlock) -> Self {
750 Self::new(role, SamplingMessageContent::Single(block))
751 }
752
753 pub fn user_text(text: impl Into<String>) -> Self {
754 Self::single(Role::User, SamplingMessageContentBlock::text(text))
755 }
756
757 pub fn assistant_text(text: impl Into<String>) -> Self {
758 Self::single(Role::Assistant, SamplingMessageContentBlock::text(text))
759 }
760
761 pub fn with_meta(mut self, meta: std::collections::HashMap<String, Value>) -> Self {
762 self.meta = Some(meta);
763 self
764 }
765}
766
767#[cfg(test)]
768#[allow(deprecated)]
769mod tests {
770 use super::*;
771
772 #[test]
773 fn test_tool_choice_mode_serializes_as_required() {
774 let tc = ToolChoice::required();
775 let json = serde_json::to_value(&tc).unwrap();
776 assert_eq!(json["mode"], "required");
777 }
778
779 #[test]
780 fn test_tool_choice_mode_deserializes_legacy_any() {
781 let json = serde_json::json!({"mode": "any"});
782 let tc: ToolChoice = serde_json::from_value(json).unwrap();
783 assert_eq!(tc.mode, Some(ToolChoiceMode::Required));
784 }
785
786 #[test]
787 fn test_tool_choice_mode_deserializes_required() {
788 let json = serde_json::json!({"mode": "required"});
789 let tc: ToolChoice = serde_json::from_value(json).unwrap();
790 assert_eq!(tc.mode, Some(ToolChoiceMode::Required));
791 }
792
793 #[test]
794 fn test_tool_choice_any_alias_returns_required() {
795 let tc = ToolChoice::any();
796 assert_eq!(tc.mode, Some(ToolChoiceMode::Required));
797 }
798
799 #[test]
800 fn test_tool_choice_absent_mode_is_auto() {
801 let tc: ToolChoice = serde_json::from_value(serde_json::json!({})).unwrap();
803 assert_eq!(tc.mode, None);
804 assert_eq!(tc.effective_mode(), ToolChoiceMode::Auto);
805 }
806
807 #[test]
808 fn create_message_request_params_metadata_is_an_object() {
809 let mut metadata = std::collections::HashMap::new();
813 metadata.insert("traceId".to_string(), serde_json::json!("abc-123"));
814 let params = CreateMessageRequestParams::new(vec![SamplingMessage::user_text("hi")], 100)
815 .with_temperature(0.5);
816 let mut params = params;
817 params.metadata = Some(metadata);
818
819 let v = serde_json::to_value(¶ms).unwrap();
820 assert!(
821 v["metadata"].is_object(),
822 "metadata must serialize as a JSON object"
823 );
824 assert_eq!(v["metadata"]["traceId"], "abc-123");
825
826 let back: CreateMessageRequestParams = serde_json::from_value(v).unwrap();
827 assert_eq!(
828 back.metadata.unwrap().get("traceId"),
829 Some(&serde_json::json!("abc-123"))
830 );
831 }
832
833 #[test]
834 fn create_message_request_params_metadata_rejects_non_object_wire_value() {
835 let wire = serde_json::json!({
838 "messages": [],
839 "maxTokens": 10,
840 "metadata": "not-an-object"
841 });
842 let result: Result<CreateMessageRequestParams, _> = serde_json::from_value(wire);
843 assert!(
844 result.is_err(),
845 "scalar metadata must be rejected per the JSONObject contract"
846 );
847 }
848
849 fn tool_use(id: &str) -> SamplingMessageContentBlock {
850 SamplingMessageContentBlock::ToolUse {
851 id: id.to_string(),
852 name: "get_weather".to_string(),
853 input: std::collections::HashMap::new(),
854 meta: None,
855 }
856 }
857
858 fn tool_result(id: &str) -> SamplingMessageContentBlock {
859 SamplingMessageContentBlock::ToolResult {
860 tool_use_id: id.to_string(),
861 content: vec![],
862 structured_content: None,
863 is_error: None,
864 meta: None,
865 }
866 }
867
868 #[test]
869 fn valid_tool_use_tool_result_pairing_passes() {
870 let params = CreateMessageRequestParams::new(
871 vec![
872 SamplingMessage::user_text("what's the weather?"),
873 SamplingMessage::new(
874 Role::Assistant,
875 SamplingMessageContent::Single(tool_use("call-1")),
876 ),
877 SamplingMessage::new(
878 Role::User,
879 SamplingMessageContent::Single(tool_result("call-1")),
880 ),
881 ],
882 100,
883 );
884 assert_eq!(params.validate_message_shape(), Ok(()));
885 }
886
887 #[test]
888 fn user_message_mixing_text_and_tool_result_fails() {
889 let params = CreateMessageRequestParams::new(
890 vec![
891 SamplingMessage::new(
892 Role::Assistant,
893 SamplingMessageContent::Single(tool_use("call-1")),
894 ),
895 SamplingMessage::new(
896 Role::User,
897 SamplingMessageContent::Multiple(vec![
898 SamplingMessageContentBlock::text("also here's some text"),
899 tool_result("call-1"),
900 ]),
901 ),
902 ],
903 100,
904 );
905 assert!(
906 params.validate_message_shape().is_err(),
907 "a user message mixing text with a ToolResult block must be rejected"
908 );
909 }
910
911 #[test]
912 fn assistant_tool_use_not_followed_by_matching_tool_result_fails() {
913 let params = CreateMessageRequestParams::new(
915 vec![SamplingMessage::new(
916 Role::Assistant,
917 SamplingMessageContent::Single(tool_use("call-1")),
918 )],
919 100,
920 );
921 assert!(params.validate_message_shape().is_err());
922
923 let params = CreateMessageRequestParams::new(
925 vec![
926 SamplingMessage::new(
927 Role::Assistant,
928 SamplingMessageContent::Single(tool_use("call-1")),
929 ),
930 SamplingMessage::new(
931 Role::User,
932 SamplingMessageContent::Single(tool_result("call-DIFFERENT")),
933 ),
934 ],
935 100,
936 );
937 assert!(
938 params.validate_message_shape().is_err(),
939 "a mismatched tool_use_id must be rejected"
940 );
941
942 let params = CreateMessageRequestParams::new(
944 vec![
945 SamplingMessage::new(
946 Role::Assistant,
947 SamplingMessageContent::Single(tool_use("call-1")),
948 ),
949 SamplingMessage::assistant_text("no tool result here"),
950 ],
951 100,
952 );
953 assert!(params.validate_message_shape().is_err());
954 }
955}