1use crate::mcp_utils::ToolResult;
2use chrono::Utc;
3use rmcp::model::{
4 AnnotateAble, CallToolRequestParam, CallToolResult, Content, ImageContent, JsonObject,
5 PromptMessage, PromptMessageContent, PromptMessageRole, RawContent, RawImageContent,
6 RawTextContent, ResourceContents, Role, TextContent,
7};
8use serde::{Deserialize, Deserializer, Serialize};
9use std::collections::HashSet;
10use std::fmt;
11use utoipa::ToSchema;
12
13use crate::conversation::tool_result_serde;
14use crate::utils::sanitize_unicode_tags;
15
16#[derive(ToSchema)]
17pub enum ToolCallResult<T> {
18 Success { value: T },
19 Error { error: String },
20}
21
22fn deserialize_sanitized_content<'de, D>(deserializer: D) -> Result<Vec<MessageContent>, D::Error>
24where
25 D: Deserializer<'de>,
26{
27 use serde::de::Error;
28
29 let mut raw: Vec<serde_json::Value> = Vec::deserialize(deserializer)?;
30
31 raw.retain(|item| item.get("type").and_then(|v| v.as_str()) != Some("conversationCompacted"));
33
34 let mut content: Vec<MessageContent> = serde_json::from_value(serde_json::Value::Array(raw))
35 .map_err(|e| Error::custom(format!("Failed to deserialize MessageContent: {}", e)))?;
36
37 for message_content in &mut content {
38 if let MessageContent::Text(text_content) = message_content {
39 let original = &text_content.text;
40 let sanitized = sanitize_unicode_tags(original);
41 if *original != sanitized {
42 tracing::info!(
43 original = %original,
44 sanitized = %sanitized,
45 removed_count = original.len() - sanitized.len(),
46 "Unicode Tags sanitized during Message deserialization"
47 );
48 text_content.text = sanitized;
49 }
50 }
51 }
52
53 Ok(content)
54}
55
56pub type ProviderMetadata = serde_json::Map<String, serde_json::Value>;
59
60#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
61#[serde(rename_all = "camelCase")]
62#[derive(ToSchema)]
63pub struct ToolRequest {
64 pub id: String,
65 #[serde(with = "tool_result_serde")]
66 #[schema(value_type = Object)]
67 pub tool_call: ToolResult<CallToolRequestParam>,
68 #[serde(skip_serializing_if = "Option::is_none")]
69 #[schema(value_type = Object)]
70 pub metadata: Option<ProviderMetadata>,
71 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
72 #[schema(value_type = Object)]
73 pub tool_meta: Option<serde_json::Value>,
74}
75
76impl ToolRequest {
77 pub fn to_readable_string(&self) -> String {
78 match &self.tool_call {
79 Ok(tool_call) => {
80 format!(
81 "Tool: {}, Args: {}",
82 tool_call.name,
83 serde_json::to_string_pretty(&tool_call.arguments)
84 .unwrap_or_else(|_| "<<invalid json>>".to_string())
85 )
86 }
87 Err(e) => format!("Invalid tool call: {}", e),
88 }
89 }
90}
91
92#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
93#[serde(rename_all = "camelCase")]
94#[derive(ToSchema)]
95pub struct ToolResponse {
96 pub id: String,
97 #[serde(with = "tool_result_serde::call_tool_result")]
98 #[schema(value_type = Object)]
99 pub tool_result: ToolResult<CallToolResult>,
100 #[serde(skip_serializing_if = "Option::is_none")]
101 #[schema(value_type = Object)]
102 pub metadata: Option<ProviderMetadata>,
103}
104
105#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
106#[serde(rename_all = "camelCase")]
107#[derive(ToSchema)]
108pub struct ToolConfirmationRequest {
109 pub id: String,
110 pub tool_name: String,
111 pub arguments: JsonObject,
112 pub prompt: Option<String>,
113}
114
115#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema)]
116#[serde(tag = "actionType", rename_all = "camelCase")]
117pub enum ActionRequiredData {
118 #[serde(rename_all = "camelCase")]
119 ToolConfirmation {
120 id: String,
121 tool_name: String,
122 arguments: JsonObject,
123 prompt: Option<String>,
124 },
125 Elicitation {
126 id: String,
127 message: String,
128 requested_schema: serde_json::Value,
129 },
130 ElicitationResponse {
131 id: String,
132 user_data: serde_json::Value,
133 },
134}
135
136#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema)]
137#[serde(rename_all = "camelCase")]
138pub struct ActionRequired {
139 pub data: ActionRequiredData,
140}
141
142#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema)]
143pub struct ThinkingContent {
144 pub thinking: String,
145 pub signature: String,
146}
147
148#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema)]
149pub struct RedactedThinkingContent {
150 pub data: String,
151}
152
153#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema)]
154#[serde(rename_all = "camelCase")]
155pub struct FrontendToolRequest {
156 pub id: String,
157 #[serde(with = "tool_result_serde")]
158 #[schema(value_type = Object)]
159 pub tool_call: ToolResult<CallToolRequestParam>,
160}
161
162#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema)]
163#[serde(rename_all = "camelCase")]
164pub enum SystemNotificationType {
165 ThinkingMessage,
166 InlineMessage,
167}
168
169#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema)]
170#[serde(rename_all = "camelCase")]
171pub struct SystemNotificationContent {
172 pub notification_type: SystemNotificationType,
173 pub msg: String,
174}
175
176#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema)]
177#[serde(tag = "type", rename_all = "camelCase")]
179pub enum MessageContent {
180 Text(TextContent),
181 Image(ImageContent),
182 ToolRequest(ToolRequest),
183 ToolResponse(ToolResponse),
184 ToolConfirmationRequest(ToolConfirmationRequest),
185 ActionRequired(ActionRequired),
186 FrontendToolRequest(FrontendToolRequest),
187 Thinking(ThinkingContent),
188 RedactedThinking(RedactedThinkingContent),
189 SystemNotification(SystemNotificationContent),
190}
191
192impl fmt::Display for MessageContent {
193 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194 match self {
195 MessageContent::Text(t) => write!(f, "{}", t.text),
196 MessageContent::Image(i) => write!(f, "[Image: {}]", i.mime_type),
197 MessageContent::ToolRequest(r) => {
198 write!(f, "[ToolRequest: {}]", r.to_readable_string())
199 }
200 MessageContent::ToolResponse(r) => write!(
201 f,
202 "[ToolResponse: {}]",
203 match &r.tool_result {
204 Ok(result) => format!("{} content item(s)", result.content.len()),
205 Err(e) => format!("Error: {e}"),
206 }
207 ),
208 MessageContent::ToolConfirmationRequest(r) => {
209 write!(f, "[ToolConfirmationRequest: {}]", r.tool_name)
210 }
211 MessageContent::ActionRequired(a) => match &a.data {
212 ActionRequiredData::ToolConfirmation { tool_name, .. } => {
213 write!(f, "[ActionRequired: ToolConfirmation for {}]", tool_name)
214 }
215 ActionRequiredData::Elicitation { message, .. } => {
216 write!(f, "[ActionRequired: Elicitation - {}]", message)
217 }
218 ActionRequiredData::ElicitationResponse { id, .. } => {
219 write!(f, "[ActionRequired: ElicitationResponse for {}]", id)
220 }
221 },
222 MessageContent::FrontendToolRequest(r) => match &r.tool_call {
223 Ok(tool_call) => write!(f, "[FrontendToolRequest: {}]", tool_call.name),
224 Err(e) => write!(f, "[FrontendToolRequest: Error: {}]", e),
225 },
226 MessageContent::Thinking(t) => write!(f, "[Thinking: {}]", t.thinking),
227 MessageContent::RedactedThinking(_r) => write!(f, "[RedactedThinking]"),
228 MessageContent::SystemNotification(r) => {
229 write!(f, "[SystemNotification: {}]", r.msg)
230 }
231 }
232 }
233}
234
235impl MessageContent {
236 pub fn text<S: Into<String>>(text: S) -> Self {
237 MessageContent::Text(
238 RawTextContent {
239 text: text.into(),
240 meta: None,
241 }
242 .no_annotation(),
243 )
244 }
245
246 pub fn image<S: Into<String>, T: Into<String>>(data: S, mime_type: T) -> Self {
247 MessageContent::Image(
248 RawImageContent {
249 data: data.into(),
250 mime_type: mime_type.into(),
251 meta: None,
252 }
253 .no_annotation(),
254 )
255 }
256
257 pub fn tool_request<S: Into<String>>(
258 id: S,
259 tool_call: ToolResult<CallToolRequestParam>,
260 ) -> Self {
261 MessageContent::ToolRequest(ToolRequest {
262 id: id.into(),
263 tool_call,
264 metadata: None,
265 tool_meta: None,
266 })
267 }
268
269 pub fn tool_request_with_metadata<S: Into<String>>(
270 id: S,
271 tool_call: ToolResult<CallToolRequestParam>,
272 metadata: Option<&ProviderMetadata>,
273 ) -> Self {
274 MessageContent::ToolRequest(ToolRequest {
275 id: id.into(),
276 tool_call,
277 metadata: metadata.cloned(),
278 tool_meta: None,
279 })
280 }
281
282 pub fn tool_response<S: Into<String>>(id: S, tool_result: ToolResult<CallToolResult>) -> Self {
283 MessageContent::ToolResponse(ToolResponse {
284 id: id.into(),
285 tool_result,
286 metadata: None,
287 })
288 }
289
290 pub fn tool_response_with_metadata<S: Into<String>>(
291 id: S,
292 tool_result: ToolResult<CallToolResult>,
293 metadata: Option<&ProviderMetadata>,
294 ) -> Self {
295 MessageContent::ToolResponse(ToolResponse {
296 id: id.into(),
297 tool_result,
298 metadata: metadata.cloned(),
299 })
300 }
301
302 pub fn action_required<S: Into<String>>(
303 id: S,
304 tool_name: String,
305 arguments: JsonObject,
306 prompt: Option<String>,
307 ) -> Self {
308 MessageContent::ActionRequired(ActionRequired {
309 data: ActionRequiredData::ToolConfirmation {
310 id: id.into(),
311 tool_name,
312 arguments,
313 prompt,
314 },
315 })
316 }
317
318 pub fn action_required_elicitation<S: Into<String>>(
319 id: S,
320 message: String,
321 requested_schema: serde_json::Value,
322 ) -> Self {
323 MessageContent::ActionRequired(ActionRequired {
324 data: ActionRequiredData::Elicitation {
325 id: id.into(),
326 message,
327 requested_schema,
328 },
329 })
330 }
331
332 pub fn action_required_elicitation_response<S: Into<String>>(
333 id: S,
334 user_data: serde_json::Value,
335 ) -> Self {
336 MessageContent::ActionRequired(ActionRequired {
337 data: ActionRequiredData::ElicitationResponse {
338 id: id.into(),
339 user_data,
340 },
341 })
342 }
343
344 pub fn thinking<S1: Into<String>, S2: Into<String>>(thinking: S1, signature: S2) -> Self {
345 MessageContent::Thinking(ThinkingContent {
346 thinking: thinking.into(),
347 signature: signature.into(),
348 })
349 }
350
351 pub fn redacted_thinking<S: Into<String>>(data: S) -> Self {
352 MessageContent::RedactedThinking(RedactedThinkingContent { data: data.into() })
353 }
354
355 pub fn frontend_tool_request<S: Into<String>>(
356 id: S,
357 tool_call: ToolResult<CallToolRequestParam>,
358 ) -> Self {
359 MessageContent::FrontendToolRequest(FrontendToolRequest {
360 id: id.into(),
361 tool_call,
362 })
363 }
364
365 pub fn system_notification<S: Into<String>>(
366 notification_type: SystemNotificationType,
367 msg: S,
368 ) -> Self {
369 MessageContent::SystemNotification(SystemNotificationContent {
370 notification_type,
371 msg: msg.into(),
372 })
373 }
374
375 pub fn as_system_notification(&self) -> Option<&SystemNotificationContent> {
376 if let MessageContent::SystemNotification(ref notification) = self {
377 Some(notification)
378 } else {
379 None
380 }
381 }
382
383 pub fn as_tool_request(&self) -> Option<&ToolRequest> {
384 if let MessageContent::ToolRequest(ref tool_request) = self {
385 Some(tool_request)
386 } else {
387 None
388 }
389 }
390
391 pub fn as_tool_response(&self) -> Option<&ToolResponse> {
392 if let MessageContent::ToolResponse(ref tool_response) = self {
393 Some(tool_response)
394 } else {
395 None
396 }
397 }
398
399 pub fn as_action_required(&self) -> Option<&ActionRequired> {
400 if let MessageContent::ActionRequired(ref action_required) = self {
401 Some(action_required)
402 } else {
403 None
404 }
405 }
406
407 pub fn as_tool_response_text(&self) -> Option<String> {
408 if let Some(tool_response) = self.as_tool_response() {
409 if let Ok(result) = &tool_response.tool_result {
410 let texts: Vec<String> = result
411 .content
412 .iter()
413 .filter_map(|content| content.as_text().map(|t| t.text.to_string()))
414 .collect();
415 if !texts.is_empty() {
416 return Some(texts.join("\n"));
417 }
418 }
419 }
420 None
421 }
422
423 pub fn as_text(&self) -> Option<&str> {
425 match self {
426 MessageContent::Text(text) => Some(&text.text),
427 _ => None,
428 }
429 }
430
431 pub fn as_thinking(&self) -> Option<&ThinkingContent> {
433 match self {
434 MessageContent::Thinking(thinking) => Some(thinking),
435 _ => None,
436 }
437 }
438
439 pub fn as_redacted_thinking(&self) -> Option<&RedactedThinkingContent> {
441 match self {
442 MessageContent::RedactedThinking(redacted) => Some(redacted),
443 _ => None,
444 }
445 }
446}
447
448impl From<Content> for MessageContent {
449 fn from(content: Content) -> Self {
450 match content.raw {
451 RawContent::Text(text) => {
452 MessageContent::Text(text.optional_annotate(content.annotations))
453 }
454 RawContent::Image(image) => {
455 MessageContent::Image(image.optional_annotate(content.annotations))
456 }
457 RawContent::ResourceLink(_link) => MessageContent::text("[Resource link]"),
458 RawContent::Resource(resource) => {
459 let text = match &resource.resource {
460 ResourceContents::TextResourceContents { text, .. } => text.clone(),
461 ResourceContents::BlobResourceContents { blob, .. } => {
462 format!("[Binary content: {}]", blob.clone())
463 }
464 };
465 MessageContent::text(text)
466 }
467 RawContent::Audio(_) => {
468 MessageContent::text("[Audio content: not supported]".to_string())
469 }
470 }
471 }
472}
473
474impl From<PromptMessage> for Message {
475 fn from(prompt_message: PromptMessage) -> Self {
476 let message = match prompt_message.role {
478 PromptMessageRole::User => Message::user(),
479 PromptMessageRole::Assistant => Message::assistant(),
480 };
481
482 let content = match prompt_message.content {
484 PromptMessageContent::Text { text } => MessageContent::text(text),
485 PromptMessageContent::Image { image } => {
486 MessageContent::image(image.data.clone(), image.mime_type.clone())
487 }
488 PromptMessageContent::ResourceLink { .. } => MessageContent::text("[Resource link]"),
489 PromptMessageContent::Resource { resource } => {
490 match &resource.resource {
492 ResourceContents::TextResourceContents { text, .. } => {
493 MessageContent::text(text.clone())
494 }
495 ResourceContents::BlobResourceContents { blob, .. } => {
496 MessageContent::text(format!("[Binary content: {}]", blob.clone()))
497 }
498 }
499 }
500 };
501
502 message.with_content(content)
503 }
504}
505
506#[derive(ToSchema, Clone, Copy, PartialEq, Serialize, Deserialize, Debug)]
507#[serde(rename_all = "camelCase")]
509pub struct MessageMetadata {
510 pub user_visible: bool,
512 pub agent_visible: bool,
514}
515
516impl Default for MessageMetadata {
517 fn default() -> Self {
518 MessageMetadata {
519 user_visible: true,
520 agent_visible: true,
521 }
522 }
523}
524
525impl MessageMetadata {
526 pub fn agent_only() -> Self {
528 MessageMetadata {
529 user_visible: false,
530 agent_visible: true,
531 }
532 }
533
534 pub fn user_only() -> Self {
536 MessageMetadata {
537 user_visible: true,
538 agent_visible: false,
539 }
540 }
541
542 pub fn invisible() -> Self {
544 MessageMetadata {
545 user_visible: false,
546 agent_visible: false,
547 }
548 }
549
550 pub fn with_agent_invisible(self) -> Self {
552 Self {
553 agent_visible: false,
554 ..self
555 }
556 }
557
558 pub fn with_user_invisible(self) -> Self {
560 Self {
561 user_visible: false,
562 ..self
563 }
564 }
565
566 pub fn with_agent_visible(self) -> Self {
568 Self {
569 agent_visible: true,
570 ..self
571 }
572 }
573
574 pub fn with_user_visible(self) -> Self {
576 Self {
577 user_visible: true,
578 ..self
579 }
580 }
581}
582
583#[derive(ToSchema, Clone, PartialEq, Serialize, Deserialize, Debug)]
584#[serde(rename_all = "camelCase")]
586pub struct Message {
587 pub id: Option<String>,
588 pub role: Role,
589 pub created: i64,
590 #[serde(deserialize_with = "deserialize_sanitized_content")]
591 pub content: Vec<MessageContent>,
592 pub metadata: MessageMetadata,
593}
594
595impl Message {
596 pub fn new(role: Role, created: i64, content: Vec<MessageContent>) -> Self {
597 Message {
598 id: None,
599 role,
600 created,
601 content,
602 metadata: MessageMetadata::default(),
603 }
604 }
605 pub fn debug(&self) -> String {
606 format!("{:?}", self)
607 }
608
609 pub fn user() -> Self {
611 Message {
612 id: None,
613 role: Role::User,
614 created: Utc::now().timestamp(),
615 content: Vec::new(),
616 metadata: MessageMetadata::default(),
617 }
618 }
619
620 pub fn assistant() -> Self {
622 Message {
623 id: None,
624 role: Role::Assistant,
625 created: Utc::now().timestamp(),
626 content: Vec::new(),
627 metadata: MessageMetadata::default(),
628 }
629 }
630
631 pub fn with_id<S: Into<String>>(mut self, id: S) -> Self {
632 self.id = Some(id.into());
633 self
634 }
635
636 pub fn with_content(mut self, content: MessageContent) -> Self {
638 self.content.push(content);
639 self
640 }
641
642 pub fn with_text<S: Into<String>>(self, text: S) -> Self {
644 let raw_text = text.into();
645 let sanitized_text = sanitize_unicode_tags(&raw_text);
646
647 self.with_content(MessageContent::Text(
648 RawTextContent {
649 text: sanitized_text,
650 meta: None,
651 }
652 .no_annotation(),
653 ))
654 }
655
656 pub fn with_image<S: Into<String>, T: Into<String>>(self, data: S, mime_type: T) -> Self {
658 self.with_content(MessageContent::image(data, mime_type))
659 }
660
661 pub fn with_tool_request<S: Into<String>>(
663 self,
664 id: S,
665 tool_call: ToolResult<CallToolRequestParam>,
666 ) -> Self {
667 self.with_content(MessageContent::tool_request(id, tool_call))
668 }
669
670 pub fn with_tool_request_with_metadata<S: Into<String>>(
671 self,
672 id: S,
673 tool_call: ToolResult<CallToolRequestParam>,
674 metadata: Option<&ProviderMetadata>,
675 tool_meta: Option<serde_json::Value>,
676 ) -> Self {
677 self.with_content(MessageContent::ToolRequest(ToolRequest {
678 id: id.into(),
679 tool_call,
680 metadata: metadata.cloned(),
681 tool_meta,
682 }))
683 }
684
685 pub fn with_tool_response<S: Into<String>>(
687 self,
688 id: S,
689 result: ToolResult<CallToolResult>,
690 ) -> Self {
691 self.with_content(MessageContent::tool_response(id, result))
692 }
693
694 pub fn with_tool_response_with_metadata<S: Into<String>>(
695 self,
696 id: S,
697 result: ToolResult<CallToolResult>,
698 metadata: Option<&ProviderMetadata>,
699 ) -> Self {
700 self.with_content(MessageContent::tool_response_with_metadata(
701 id, result, metadata,
702 ))
703 }
704
705 pub fn with_action_required<S: Into<String>>(
707 self,
708 id: S,
709 tool_name: String,
710 arguments: JsonObject,
711 prompt: Option<String>,
712 ) -> Self {
713 self.with_content(MessageContent::action_required(
714 id, tool_name, arguments, prompt,
715 ))
716 }
717
718 pub fn with_frontend_tool_request<S: Into<String>>(
719 self,
720 id: S,
721 tool_call: ToolResult<CallToolRequestParam>,
722 ) -> Self {
723 self.with_content(MessageContent::frontend_tool_request(id, tool_call))
724 }
725
726 pub fn with_thinking<S1: Into<String>, S2: Into<String>>(
728 self,
729 thinking: S1,
730 signature: S2,
731 ) -> Self {
732 self.with_content(MessageContent::thinking(thinking, signature))
733 }
734
735 pub fn with_redacted_thinking<S: Into<String>>(self, data: S) -> Self {
737 self.with_content(MessageContent::redacted_thinking(data))
738 }
739
740 pub fn as_concat_text(&self) -> String {
742 self.content
743 .iter()
744 .filter_map(|c| c.as_text())
745 .collect::<Vec<_>>()
746 .join("\n")
747 }
748
749 pub fn is_tool_call(&self) -> bool {
751 self.content
752 .iter()
753 .any(|c| matches!(c, MessageContent::ToolRequest(_)))
754 }
755
756 pub fn is_tool_response(&self) -> bool {
758 self.content
759 .iter()
760 .any(|c| matches!(c, MessageContent::ToolResponse(_)))
761 }
762
763 pub fn get_tool_ids(&self) -> HashSet<&str> {
765 self.content
766 .iter()
767 .filter_map(|content| match content {
768 MessageContent::ToolRequest(req) => Some(req.id.as_str()),
769 MessageContent::ToolResponse(res) => Some(res.id.as_str()),
770 _ => None,
771 })
772 .collect()
773 }
774
775 pub fn get_tool_request_ids(&self) -> HashSet<&str> {
777 self.content
778 .iter()
779 .filter_map(|content| {
780 if let MessageContent::ToolRequest(req) = content {
781 Some(req.id.as_str())
782 } else {
783 None
784 }
785 })
786 .collect()
787 }
788
789 pub fn get_tool_response_ids(&self) -> HashSet<&str> {
791 self.content
792 .iter()
793 .filter_map(|content| {
794 if let MessageContent::ToolResponse(res) = content {
795 Some(res.id.as_str())
796 } else {
797 None
798 }
799 })
800 .collect()
801 }
802
803 pub fn has_only_text_content(&self) -> bool {
805 self.content
806 .iter()
807 .all(|c| matches!(c, MessageContent::Text(_)))
808 }
809
810 pub fn with_system_notification<S: Into<String>>(
811 self,
812 notification_type: SystemNotificationType,
813 msg: S,
814 ) -> Self {
815 self.with_content(MessageContent::system_notification(notification_type, msg))
816 .with_metadata(MessageMetadata::user_only())
817 }
818
819 pub fn with_visibility(mut self, user_visible: bool, agent_visible: bool) -> Self {
821 self.metadata.user_visible = user_visible;
822 self.metadata.agent_visible = agent_visible;
823 self
824 }
825
826 pub fn with_metadata(mut self, metadata: MessageMetadata) -> Self {
828 self.metadata = metadata;
829 self
830 }
831
832 pub fn user_only(mut self) -> Self {
834 self.metadata.user_visible = true;
835 self.metadata.agent_visible = false;
836 self
837 }
838
839 pub fn agent_only(mut self) -> Self {
841 self.metadata.user_visible = false;
842 self.metadata.agent_visible = true;
843 self
844 }
845
846 pub fn is_user_visible(&self) -> bool {
848 self.metadata.user_visible
849 }
850
851 pub fn is_agent_visible(&self) -> bool {
853 self.metadata.agent_visible
854 }
855}
856
857#[derive(Debug, Clone, Default, Serialize, Deserialize, ToSchema)]
858#[serde(rename_all = "camelCase")]
859pub struct TokenState {
860 pub input_tokens: i32,
861 pub output_tokens: i32,
862 pub total_tokens: i32,
863 pub accumulated_input_tokens: i32,
864 pub accumulated_output_tokens: i32,
865 pub accumulated_total_tokens: i32,
866}
867
868#[cfg(test)]
869mod tests {
870 use crate::conversation::message::{Message, MessageContent, MessageMetadata};
871 use crate::conversation::*;
872 use rmcp::model::{
873 AnnotateAble, CallToolRequestParam, PromptMessage, PromptMessageContent, PromptMessageRole,
874 RawEmbeddedResource, RawImageContent, ResourceContents,
875 };
876 use rmcp::model::{ErrorCode, ErrorData};
877 use rmcp::object;
878 use serde_json::Value;
879
880 #[test]
881 fn test_sanitize_with_text() {
882 let malicious = "Hello\u{E0041}\u{E0042}\u{E0043}world"; let message = Message::user().with_text(malicious);
884 assert_eq!(message.as_concat_text(), "Helloworld");
885 }
886
887 #[test]
888 fn test_no_sanitize_with_text() {
889 let clean_text = "Hello world δΈη π";
890 let message = Message::user().with_text(clean_text);
891 assert_eq!(message.as_concat_text(), clean_text);
892 }
893
894 #[test]
895 fn test_message_serialization() {
896 let message = Message::assistant()
897 .with_text("Hello, I'll help you with that.")
898 .with_tool_request(
899 "tool123",
900 Ok(CallToolRequestParam {
901 name: "test_tool".into(),
902 arguments: Some(object!({"param": "value"})),
903 }),
904 );
905
906 let json_str = serde_json::to_string_pretty(&message).unwrap();
907 println!("Serialized message: {}", json_str);
908
909 let value: Value = serde_json::from_str(&json_str).unwrap();
911
912 assert_eq!(value["role"], "assistant");
914 assert!(value["created"].is_i64());
915 assert!(value["content"].is_array());
916
917 let content = &value["content"];
919
920 assert_eq!(content[0]["type"], "text");
922 assert_eq!(content[0]["text"], "Hello, I'll help you with that.");
923
924 assert_eq!(content[1]["type"], "toolRequest");
926 assert_eq!(content[1]["id"], "tool123");
927
928 assert_eq!(content[1]["toolCall"]["status"], "success");
930 assert_eq!(content[1]["toolCall"]["value"]["name"], "test_tool");
931 assert_eq!(
932 content[1]["toolCall"]["value"]["arguments"]["param"],
933 "value"
934 );
935 }
936
937 #[test]
938 fn test_error_serialization() {
939 let message = Message::assistant().with_tool_request(
940 "tool123",
941 Err(ErrorData {
942 code: ErrorCode::INTERNAL_ERROR,
943 message: std::borrow::Cow::from("Something went wrong".to_string()),
944 data: None,
945 }),
946 );
947
948 let json_str = serde_json::to_string_pretty(&message).unwrap();
949 println!("Serialized error: {}", json_str);
950
951 let value: Value = serde_json::from_str(&json_str).unwrap();
953
954 let tool_call = &value["content"][0]["toolCall"];
956 assert_eq!(tool_call["status"], "error");
957 assert_eq!(tool_call["error"], "-32603: Something went wrong");
958 }
959
960 #[test]
961 fn test_deserialization() {
962 let json_str = r#"{
964 "role": "assistant",
965 "created": 1740171566,
966 "content": [
967 {
968 "type": "text",
969 "text": "I'll help you with that."
970 },
971 {
972 "type": "toolRequest",
973 "id": "tool123",
974 "toolCall": {
975 "status": "success",
976 "value": {
977 "name": "test_tool",
978 "arguments": {"param": "value"}
979 }
980 }
981 }
982 ],
983 "metadata": { "agentVisible": true, "userVisible": true }
984 }"#;
985
986 let message: Message = serde_json::from_str(json_str).unwrap();
987
988 assert_eq!(message.role, Role::Assistant);
989 assert_eq!(message.created, 1740171566);
990 assert_eq!(message.content.len(), 2);
991
992 if let MessageContent::Text(text) = &message.content[0] {
994 assert_eq!(text.text, "I'll help you with that.");
995 } else {
996 panic!("Expected Text content");
997 }
998
999 if let MessageContent::ToolRequest(req) = &message.content[1] {
1001 assert_eq!(req.id, "tool123");
1002 if let Ok(tool_call) = &req.tool_call {
1003 assert_eq!(tool_call.name, "test_tool");
1004 assert_eq!(tool_call.arguments, Some(object!({"param": "value"})))
1005 } else {
1006 panic!("Expected successful tool call");
1007 }
1008 } else {
1009 panic!("Expected ToolRequest content");
1010 }
1011 }
1012
1013 #[test]
1014 fn test_from_prompt_message_text() {
1015 let prompt_content = PromptMessageContent::Text {
1016 text: "Hello, world!".to_string(),
1017 };
1018
1019 let prompt_message = PromptMessage {
1020 role: PromptMessageRole::User,
1021 content: prompt_content,
1022 };
1023
1024 let message = Message::from(prompt_message);
1025
1026 if let MessageContent::Text(text_content) = &message.content[0] {
1027 assert_eq!(text_content.text, "Hello, world!");
1028 } else {
1029 panic!("Expected MessageContent::Text");
1030 }
1031 }
1032
1033 #[test]
1034 fn test_from_prompt_message_image() {
1035 let prompt_content = PromptMessageContent::Image {
1036 image: RawImageContent {
1037 data: "base64data".to_string(),
1038 mime_type: "image/jpeg".to_string(),
1039 meta: None,
1040 }
1041 .no_annotation(),
1042 };
1043
1044 let prompt_message = PromptMessage {
1045 role: PromptMessageRole::User,
1046 content: prompt_content,
1047 };
1048
1049 let message = Message::from(prompt_message);
1050
1051 if let MessageContent::Image(image_content) = &message.content[0] {
1052 assert_eq!(image_content.data, "base64data");
1053 assert_eq!(image_content.mime_type, "image/jpeg");
1054 } else {
1055 panic!("Expected MessageContent::Image");
1056 }
1057 }
1058
1059 #[test]
1060 fn test_from_prompt_message_text_resource() {
1061 let resource = ResourceContents::TextResourceContents {
1062 uri: "file:///test.txt".to_string(),
1063 mime_type: Some("text/plain".to_string()),
1064 text: "Resource content".to_string(),
1065 meta: None,
1066 };
1067
1068 let prompt_content = PromptMessageContent::Resource {
1069 resource: RawEmbeddedResource {
1070 resource,
1071 meta: None,
1072 }
1073 .no_annotation(),
1074 };
1075
1076 let prompt_message = PromptMessage {
1077 role: PromptMessageRole::User,
1078 content: prompt_content,
1079 };
1080
1081 let message = Message::from(prompt_message);
1082
1083 if let MessageContent::Text(text_content) = &message.content[0] {
1084 assert_eq!(text_content.text, "Resource content");
1085 } else {
1086 panic!("Expected MessageContent::Text");
1087 }
1088 }
1089
1090 #[test]
1091 fn test_from_prompt_message_blob_resource() {
1092 let resource = ResourceContents::BlobResourceContents {
1093 uri: "file:///test.bin".to_string(),
1094 mime_type: Some("application/octet-stream".to_string()),
1095 blob: "binary_data".to_string(),
1096 meta: None,
1097 };
1098
1099 let prompt_content = PromptMessageContent::Resource {
1100 resource: RawEmbeddedResource {
1101 resource,
1102 meta: None,
1103 }
1104 .no_annotation(),
1105 };
1106
1107 let prompt_message = PromptMessage {
1108 role: PromptMessageRole::User,
1109 content: prompt_content,
1110 };
1111
1112 let message = Message::from(prompt_message);
1113
1114 if let MessageContent::Text(text_content) = &message.content[0] {
1115 assert_eq!(text_content.text, "[Binary content: binary_data]");
1116 } else {
1117 panic!("Expected MessageContent::Text");
1118 }
1119 }
1120
1121 #[test]
1122 fn test_from_prompt_message() {
1123 let prompt_message = PromptMessage {
1125 role: PromptMessageRole::User,
1126 content: PromptMessageContent::Text {
1127 text: "Hello, world!".to_string(),
1128 },
1129 };
1130
1131 let message = Message::from(prompt_message);
1132 assert_eq!(message.role, Role::User);
1133 assert_eq!(message.content.len(), 1);
1134 assert_eq!(message.as_concat_text(), "Hello, world!");
1135
1136 let prompt_message = PromptMessage {
1138 role: PromptMessageRole::Assistant,
1139 content: PromptMessageContent::Text {
1140 text: "I can help with that.".to_string(),
1141 },
1142 };
1143
1144 let message = Message::from(prompt_message);
1145 assert_eq!(message.role, Role::Assistant);
1146 assert_eq!(message.content.len(), 1);
1147 assert_eq!(message.as_concat_text(), "I can help with that.");
1148 }
1149
1150 #[test]
1151 fn test_message_with_text() {
1152 let message = Message::user().with_text("Hello");
1153 assert_eq!(message.as_concat_text(), "Hello");
1154 }
1155
1156 #[test]
1157 fn test_message_with_tool_request() {
1158 let tool_call = Ok(CallToolRequestParam {
1159 name: "test_tool".into(),
1160 arguments: Some(object!({})),
1161 });
1162
1163 let message = Message::assistant().with_tool_request("req1", tool_call);
1164 assert!(message.is_tool_call());
1165 assert!(!message.is_tool_response());
1166
1167 let ids = message.get_tool_ids();
1168 assert_eq!(ids.len(), 1);
1169 assert!(ids.contains("req1"));
1170 }
1171
1172 #[test]
1173 fn test_message_deserialization_sanitizes_text_content() {
1174 let malicious_text = "Hello\u{E0041}\u{E0042}\u{E0043}world";
1176 let malicious_json = format!(
1177 r#"{{
1178 "id": "test-id",
1179 "role": "user",
1180 "created": 1640995200,
1181 "content": [
1182 {{
1183 "type": "text",
1184 "text": "{}"
1185 }},
1186 {{
1187 "type": "image",
1188 "data": "base64data",
1189 "mimeType": "image/png"
1190 }}
1191 ],
1192 "metadata": {{ "agentVisible": true, "userVisible": true }}
1193 }}"#,
1194 malicious_text
1195 );
1196
1197 let message: Message = serde_json::from_str(&malicious_json).unwrap();
1198
1199 assert_eq!(message.as_concat_text(), "Helloworld");
1201
1202 if let MessageContent::Image(img) = &message.content[1] {
1204 assert_eq!(img.data, "base64data");
1205 assert_eq!(img.mime_type, "image/png");
1206 } else {
1207 panic!("Expected ImageContent");
1208 }
1209 }
1210
1211 #[test]
1212 fn test_legitimate_unicode_preserved_during_message_deserialization() {
1213 let clean_json = r#"{
1214 "id": "test-id",
1215 "role": "user",
1216 "created": 1640995200,
1217 "content": [{
1218 "type": "text",
1219 "text": "Hello world δΈη π"
1220 }],
1221 "metadata": { "agentVisible": true, "userVisible": true }
1222 }"#;
1223
1224 let message: Message = serde_json::from_str(clean_json).unwrap();
1225
1226 assert_eq!(message.as_concat_text(), "Hello world δΈη π");
1227 }
1228
1229 #[test]
1230 fn test_message_metadata_defaults() {
1231 let message = Message::user().with_text("Test");
1232
1233 assert!(message.is_user_visible());
1235 assert!(message.is_agent_visible());
1236 }
1237
1238 #[test]
1239 fn test_message_visibility_methods() {
1240 let user_only_msg = Message::user().with_text("User only").user_only();
1242 assert!(user_only_msg.is_user_visible());
1243 assert!(!user_only_msg.is_agent_visible());
1244
1245 let agent_only_msg = Message::assistant().with_text("Agent only").agent_only();
1247 assert!(!agent_only_msg.is_user_visible());
1248 assert!(agent_only_msg.is_agent_visible());
1249
1250 let custom_msg = Message::user()
1252 .with_text("Custom visibility")
1253 .with_visibility(false, true);
1254 assert!(!custom_msg.is_user_visible());
1255 assert!(custom_msg.is_agent_visible());
1256 }
1257
1258 #[test]
1259 fn test_message_metadata_serialization() {
1260 let message = Message::user()
1261 .with_text("Test message")
1262 .with_visibility(false, true);
1263
1264 let json_str = serde_json::to_string(&message).unwrap();
1265 let value: Value = serde_json::from_str(&json_str).unwrap();
1266
1267 assert_eq!(value["metadata"]["userVisible"], false);
1268 assert_eq!(value["metadata"]["agentVisible"], true);
1269 }
1270
1271 #[test]
1272 fn test_message_metadata_deserialization() {
1273 let json_with_metadata = r#"{
1275 "role": "user",
1276 "created": 1640995200,
1277 "content": [{
1278 "type": "text",
1279 "text": "Test"
1280 }],
1281 "metadata": {
1282 "userVisible": false,
1283 "agentVisible": true
1284 }
1285 }"#;
1286
1287 let message: Message = serde_json::from_str(json_with_metadata).unwrap();
1288 assert!(!message.is_user_visible());
1289 assert!(message.is_agent_visible());
1290 }
1291
1292 #[test]
1293 fn test_message_metadata_static_methods() {
1294 let agent_only_metadata = MessageMetadata::agent_only();
1296 assert!(!agent_only_metadata.user_visible);
1297 assert!(agent_only_metadata.agent_visible);
1298
1299 let user_only_metadata = MessageMetadata::user_only();
1301 assert!(user_only_metadata.user_visible);
1302 assert!(!user_only_metadata.agent_visible);
1303
1304 let invisible_metadata = MessageMetadata::invisible();
1306 assert!(!invisible_metadata.user_visible);
1307 assert!(!invisible_metadata.agent_visible);
1308
1309 let agent_msg = Message::assistant()
1311 .with_text("Agent only message")
1312 .with_metadata(MessageMetadata::agent_only());
1313 assert!(!agent_msg.is_user_visible());
1314 assert!(agent_msg.is_agent_visible());
1315
1316 let user_msg = Message::user()
1317 .with_text("User only message")
1318 .with_metadata(MessageMetadata::user_only());
1319 assert!(user_msg.is_user_visible());
1320 assert!(!user_msg.is_agent_visible());
1321
1322 let invisible_msg = Message::user()
1323 .with_text("Invisible message")
1324 .with_metadata(MessageMetadata::invisible());
1325 assert!(!invisible_msg.is_user_visible());
1326 assert!(!invisible_msg.is_agent_visible());
1327 }
1328
1329 #[test]
1330 fn test_message_metadata_builder_methods() {
1331 let metadata = MessageMetadata::default().with_agent_invisible();
1333 assert!(metadata.user_visible);
1334 assert!(!metadata.agent_visible);
1335
1336 let metadata = MessageMetadata::default().with_user_invisible();
1338 assert!(!metadata.user_visible);
1339 assert!(metadata.agent_visible);
1340
1341 let metadata = MessageMetadata::invisible().with_agent_visible();
1343 assert!(!metadata.user_visible);
1344 assert!(metadata.agent_visible);
1345
1346 let metadata = MessageMetadata::invisible().with_user_visible();
1348 assert!(metadata.user_visible);
1349 assert!(!metadata.agent_visible);
1350
1351 let metadata = MessageMetadata::invisible()
1353 .with_user_visible()
1354 .with_agent_visible();
1355 assert!(metadata.user_visible);
1356 assert!(metadata.agent_visible);
1357 }
1358
1359 #[test]
1360 fn test_legacy_tool_response_deserialization() {
1361 let legacy_json = r#"{
1362 "role": "user",
1363 "created": 1640995200,
1364 "content": [{
1365 "type": "toolResponse",
1366 "id": "tool123",
1367 "toolResult": {
1368 "status": "success",
1369 "value": [
1370 {
1371 "type": "text",
1372 "text": "Tool output text"
1373 }
1374 ]
1375 }
1376 }],
1377 "metadata": { "agentVisible": true, "userVisible": true }
1378 }"#;
1379
1380 let message: Message = serde_json::from_str(legacy_json).unwrap();
1381 assert_eq!(message.content.len(), 1);
1382
1383 if let MessageContent::ToolResponse(response) = &message.content[0] {
1384 assert_eq!(response.id, "tool123");
1385 if let Ok(result) = &response.tool_result {
1386 assert_eq!(result.content.len(), 1);
1387 assert_eq!(
1388 result.content[0].as_text().unwrap().text,
1389 "Tool output text"
1390 );
1391 } else {
1392 panic!("Expected successful tool result");
1393 }
1394 } else {
1395 panic!("Expected ToolResponse content");
1396 }
1397 }
1398
1399 #[test]
1400 fn test_new_tool_response_deserialization() {
1401 let new_json = r#"{
1402 "role": "user",
1403 "created": 1640995200,
1404 "content": [{
1405 "type": "toolResponse",
1406 "id": "tool456",
1407 "toolResult": {
1408 "status": "success",
1409 "value": {
1410 "content": [
1411 {
1412 "type": "text",
1413 "text": "New format output"
1414 }
1415 ],
1416 "isError": false
1417 }
1418 }
1419 }],
1420 "metadata": { "agentVisible": true, "userVisible": true }
1421 }"#;
1422
1423 let message: Message = serde_json::from_str(new_json).unwrap();
1424 assert_eq!(message.content.len(), 1);
1425
1426 if let MessageContent::ToolResponse(response) = &message.content[0] {
1427 assert_eq!(response.id, "tool456");
1428 if let Ok(result) = &response.tool_result {
1429 assert_eq!(result.content.len(), 1);
1430 assert_eq!(
1431 result.content[0].as_text().unwrap().text,
1432 "New format output"
1433 );
1434 } else {
1435 panic!("Expected successful tool result");
1436 }
1437 } else {
1438 panic!("Expected ToolResponse content");
1439 }
1440 }
1441
1442 #[test]
1443 fn test_tool_request_with_value_arguments_backward_compatibility() {
1444 struct TestCase {
1445 name: &'static str,
1446 arguments_json: &'static str,
1447 expected: Option<Value>,
1448 }
1449
1450 let test_cases = [
1451 TestCase {
1452 name: "string",
1453 arguments_json: r#""string_argument""#,
1454 expected: Some(serde_json::json!({"value": "string_argument"})),
1455 },
1456 TestCase {
1457 name: "array",
1458 arguments_json: r#"["a", "b", "c"]"#,
1459 expected: Some(serde_json::json!({"value": ["a", "b", "c"]})),
1460 },
1461 TestCase {
1462 name: "number",
1463 arguments_json: "42",
1464 expected: Some(serde_json::json!({"value": 42})),
1465 },
1466 TestCase {
1467 name: "null",
1468 arguments_json: "null",
1469 expected: None,
1470 },
1471 TestCase {
1472 name: "object",
1473 arguments_json: r#"{"key": "value", "number": 123}"#,
1474 expected: Some(serde_json::json!({"key": "value", "number": 123})),
1475 },
1476 ];
1477
1478 for tc in test_cases {
1479 let json = format!(
1480 r#"{{
1481 "role": "assistant",
1482 "created": 1640995200,
1483 "content": [{{
1484 "type": "toolRequest",
1485 "id": "tool123",
1486 "toolCall": {{
1487 "status": "success",
1488 "value": {{
1489 "name": "test_tool",
1490 "arguments": {}
1491 }}
1492 }}
1493 }}],
1494 "metadata": {{ "agentVisible": true, "userVisible": true }}
1495 }}"#,
1496 tc.arguments_json
1497 );
1498
1499 let message: Message = serde_json::from_str(&json)
1500 .unwrap_or_else(|e| panic!("{}: parse failed: {}", tc.name, e));
1501
1502 let MessageContent::ToolRequest(request) = &message.content[0] else {
1503 panic!("{}: expected ToolRequest content", tc.name);
1504 };
1505
1506 let Ok(tool_call) = &request.tool_call else {
1507 panic!("{}: expected successful tool call", tc.name);
1508 };
1509
1510 assert_eq!(tool_call.name, "test_tool", "{}: wrong tool name", tc.name);
1511
1512 match (&tool_call.arguments, &tc.expected) {
1513 (None, None) => {}
1514 (Some(args), Some(expected)) => {
1515 let args_value = serde_json::to_value(args).unwrap();
1516 assert_eq!(&args_value, expected, "{}: arguments mismatch", tc.name);
1517 }
1518 (actual, expected) => {
1519 panic!("{}: expected {:?}, got {:?}", tc.name, expected, actual);
1520 }
1521 }
1522 }
1523 }
1524}