rmcp/model/
content.rs

1//! Content sent around agents, extensions, and LLMs
2//! The various content types can be display to humans but also understood by models
3//! They include optional annotations used to help inform agent usage
4use super::resource::ResourceContents;
5use super::{AnnotateAble, Annotated};
6use serde::{Deserialize, Serialize};
7use serde_json::json;
8
9#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
10#[serde(rename_all = "camelCase")]
11pub struct RawTextContent {
12    pub text: String,
13}
14pub type TextContent = Annotated<RawTextContent>;
15#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
16#[serde(rename_all = "camelCase")]
17pub struct RawImageContent {
18    /// The base64-encoded image
19    pub data: String,
20    pub mime_type: String,
21}
22
23pub type ImageContent = Annotated<RawImageContent>;
24#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
25#[serde(rename_all = "camelCase")]
26pub struct RawEmbeddedResource {
27    pub resource: ResourceContents,
28}
29pub type EmbeddedResource = Annotated<RawEmbeddedResource>;
30
31impl EmbeddedResource {
32    pub fn get_text(&self) -> String {
33        match &self.resource {
34            ResourceContents::TextResourceContents { text, .. } => text.clone(),
35            _ => String::new(),
36        }
37    }
38}
39
40#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
41#[serde(tag = "type", rename_all = "camelCase")]
42pub enum RawContent {
43    Text(RawTextContent),
44    Image(RawImageContent),
45    Resource(RawEmbeddedResource),
46}
47
48pub type Content = Annotated<RawContent>;
49
50impl RawContent {
51    pub fn json<S: Serialize>(json: S) -> Result<Self, crate::Error> {
52        let json = serde_json::to_string(&json).map_err(|e| {
53            crate::Error::internal_error(
54                "fail to serialize response to json",
55                Some(json!(
56                    {"reason": e.to_string()}
57                )),
58            )
59        })?;
60        Ok(RawContent::text(json))
61    }
62
63    pub fn text<S: Into<String>>(text: S) -> Self {
64        RawContent::Text(RawTextContent { text: text.into() })
65    }
66
67    pub fn image<S: Into<String>, T: Into<String>>(data: S, mime_type: T) -> Self {
68        RawContent::Image(RawImageContent {
69            data: data.into(),
70            mime_type: mime_type.into(),
71        })
72    }
73
74    pub fn resource(resource: ResourceContents) -> Self {
75        RawContent::Resource(RawEmbeddedResource { resource })
76    }
77
78    pub fn embedded_text<S: Into<String>, T: Into<String>>(uri: S, content: T) -> Self {
79        RawContent::Resource(RawEmbeddedResource {
80            resource: ResourceContents::TextResourceContents {
81                uri: uri.into(),
82                mime_type: Some("text".to_string()),
83                text: content.into(),
84            },
85        })
86    }
87
88    /// Get the text content if this is a TextContent variant
89    pub fn as_text(&self) -> Option<&RawTextContent> {
90        match self {
91            RawContent::Text(text) => Some(text),
92            _ => None,
93        }
94    }
95
96    /// Get the image content if this is an ImageContent variant
97    pub fn as_image(&self) -> Option<&RawImageContent> {
98        match self {
99            RawContent::Image(image) => Some(image),
100            _ => None,
101        }
102    }
103
104    /// Get the resource content if this is an ImageContent variant
105    pub fn as_resource(&self) -> Option<&RawEmbeddedResource> {
106        match self {
107            RawContent::Resource(resource) => Some(resource),
108            _ => None,
109        }
110    }
111}
112
113impl Content {
114    pub fn text<S: Into<String>>(text: S) -> Self {
115        RawContent::text(text).no_annotation()
116    }
117
118    pub fn image<S: Into<String>, T: Into<String>>(data: S, mime_type: T) -> Self {
119        RawContent::image(data, mime_type).no_annotation()
120    }
121
122    pub fn resource(resource: ResourceContents) -> Self {
123        RawContent::resource(resource).no_annotation()
124    }
125
126    pub fn embedded_text<S: Into<String>, T: Into<String>>(uri: S, content: T) -> Self {
127        RawContent::embedded_text(uri, content).no_annotation()
128    }
129
130    pub fn json<S: Serialize>(json: S) -> Result<Self, crate::Error> {
131        RawContent::json(json).map(|c| c.no_annotation())
132    }
133}
134
135#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
136pub struct JsonContent<S: Serialize>(S);
137/// Types that can be converted into a list of contents
138pub trait IntoContents {
139    fn into_contents(self) -> Vec<Content>;
140}
141
142impl IntoContents for Content {
143    fn into_contents(self) -> Vec<Content> {
144        vec![self]
145    }
146}
147
148impl IntoContents for String {
149    fn into_contents(self) -> Vec<Content> {
150        vec![Content::text(self)]
151    }
152}