1use serde::{Deserialize, Serialize};
9use serde_json::Value;
10
11#[cfg(not(feature = "std"))]
12use alloc::{collections::BTreeMap as HashMap, string::String, vec, vec::Vec};
13#[cfg(feature = "std")]
14use std::collections::HashMap;
15
16use crate::content::{
17 BlobResourceContents, Content, Message, ResourceContents, Role, TextResourceContents,
18};
19
20#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
22pub struct ToolResult {
23 pub content: Vec<Content>,
25 #[serde(rename = "isError", skip_serializing_if = "Option::is_none")]
27 pub is_error: Option<bool>,
28 #[serde(rename = "structuredContent", skip_serializing_if = "Option::is_none")]
30 pub structured_content: Option<Value>,
31 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
33 pub meta: Option<HashMap<String, Value>>,
34}
35
36impl ToolResult {
37 #[must_use]
39 pub fn text(text: impl Into<String>) -> Self {
40 Self {
41 content: vec![Content::text(text)],
42 ..Default::default()
43 }
44 }
45
46 #[must_use]
48 pub fn error(text: impl Into<String>) -> Self {
49 Self {
50 content: vec![Content::text(text)],
51 is_error: Some(true),
52 ..Default::default()
53 }
54 }
55
56 pub fn json<T: Serialize>(value: &T) -> Result<Self, serde_json::Error> {
58 let structured = serde_json::to_value(value)?;
59 let text = serde_json::to_string_pretty(value)?;
60 Ok(Self {
61 content: vec![Content::text(text)],
62 structured_content: Some(structured),
63 ..Default::default()
64 })
65 }
66
67 #[must_use]
69 pub fn empty() -> Self {
70 Self::default()
71 }
72
73 #[must_use]
75 pub fn is_error(&self) -> bool {
76 self.is_error.unwrap_or(false)
77 }
78
79 #[must_use]
81 pub fn with_structured<T: Serialize>(mut self, value: &T) -> Self {
82 self.structured_content = serde_json::to_value(value).ok();
83 self
84 }
85
86 #[must_use]
88 pub fn with_content(mut self, content: Content) -> Self {
89 self.content.push(content);
90 self
91 }
92
93 #[must_use]
95 pub fn with_image(self, data: impl Into<String>, mime_type: impl Into<String>) -> Self {
96 self.with_content(Content::image(data, mime_type))
97 }
98
99 #[must_use]
101 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
102 self.meta = Some(meta);
103 self
104 }
105
106 #[must_use]
108 pub fn first_text(&self) -> Option<&str> {
109 self.content.first().and_then(|c| c.as_text())
110 }
111}
112
113#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
115pub struct ResourceResult {
116 pub contents: Vec<ResourceContents>,
118 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
120 pub meta: Option<HashMap<String, Value>>,
121}
122
123impl ResourceResult {
124 #[must_use]
126 pub fn text(uri: impl Into<String>, content: impl Into<String>) -> Self {
127 Self {
128 contents: vec![ResourceContents::Text(TextResourceContents {
129 uri: uri.into(),
130 mime_type: Some("text/plain".into()),
131 text: content.into(),
132 meta: None,
133 })],
134 ..Default::default()
135 }
136 }
137
138 pub fn json<T: Serialize>(
140 uri: impl Into<String>,
141 value: &T,
142 ) -> Result<Self, serde_json::Error> {
143 Ok(Self {
144 contents: vec![ResourceContents::Text(TextResourceContents {
145 uri: uri.into(),
146 mime_type: Some("application/json".into()),
147 text: serde_json::to_string_pretty(value)?,
148 meta: None,
149 })],
150 ..Default::default()
151 })
152 }
153
154 #[must_use]
156 pub fn binary(
157 uri: impl Into<String>,
158 data: impl Into<String>,
159 mime_type: impl Into<String>,
160 ) -> Self {
161 Self {
162 contents: vec![ResourceContents::Blob(BlobResourceContents {
163 uri: uri.into(),
164 mime_type: Some(mime_type.into()),
165 blob: data.into(),
166 meta: None,
167 })],
168 ..Default::default()
169 }
170 }
171
172 #[must_use]
174 pub fn empty() -> Self {
175 Self::default()
176 }
177
178 #[must_use]
180 pub fn with_content(mut self, content: ResourceContents) -> Self {
181 self.contents.push(content);
182 self
183 }
184
185 #[must_use]
187 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
188 self.meta = Some(meta);
189 self
190 }
191
192 #[must_use]
194 pub fn first_text(&self) -> Option<&str> {
195 self.contents.first().and_then(|c| match c {
196 ResourceContents::Text(t) => Some(t.text.as_str()),
197 ResourceContents::Blob(_) => None,
198 })
199 }
200}
201
202#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
204pub struct PromptResult {
205 #[serde(skip_serializing_if = "Option::is_none")]
207 pub description: Option<String>,
208 pub messages: Vec<Message>,
210 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
212 pub meta: Option<HashMap<String, Value>>,
213}
214
215impl PromptResult {
216 #[must_use]
218 pub fn new(messages: Vec<Message>) -> Self {
219 Self {
220 messages,
221 ..Default::default()
222 }
223 }
224
225 #[must_use]
227 pub fn user(text: impl Into<String>) -> Self {
228 Self::new(vec![Message::user(text)])
229 }
230
231 #[must_use]
233 pub fn assistant(text: impl Into<String>) -> Self {
234 Self::new(vec![Message::assistant(text)])
235 }
236
237 #[must_use]
239 pub fn empty() -> Self {
240 Self::default()
241 }
242
243 #[must_use]
245 pub fn with_description(mut self, desc: impl Into<String>) -> Self {
246 self.description = Some(desc.into());
247 self
248 }
249
250 #[must_use]
252 pub fn add_user(mut self, text: impl Into<String>) -> Self {
253 self.messages.push(Message::user(text));
254 self
255 }
256
257 #[must_use]
259 pub fn add_assistant(mut self, text: impl Into<String>) -> Self {
260 self.messages.push(Message::assistant(text));
261 self
262 }
263
264 #[must_use]
266 pub fn add_message(mut self, role: Role, text: impl Into<String>) -> Self {
267 self.messages.push(Message::new(role, Content::text(text)));
268 self
269 }
270
271 #[must_use]
273 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
274 self.meta = Some(meta);
275 self
276 }
277
278 #[must_use]
280 pub fn is_empty(&self) -> bool {
281 self.messages.is_empty()
282 }
283
284 #[must_use]
286 pub fn len(&self) -> usize {
287 self.messages.len()
288 }
289}
290
291#[cfg(test)]
292mod tests {
293 use super::*;
294
295 #[test]
296 fn test_tool_result_text() {
297 let result = ToolResult::text("Hello");
298 assert!(!result.is_error());
299 assert_eq!(result.first_text(), Some("Hello"));
300 }
301
302 #[test]
303 fn test_tool_result_error() {
304 let result = ToolResult::error("Failed");
305 assert!(result.is_error());
306 assert_eq!(result.first_text(), Some("Failed"));
307 }
308
309 #[test]
310 fn test_tool_result_json() {
311 let data = serde_json::json!({"key": "value"});
312 let result = ToolResult::json(&data).unwrap();
313 assert!(result.structured_content.is_some());
314 assert!(!result.is_error());
315 }
316
317 #[test]
318 fn test_resource_result_text() {
319 let result = ResourceResult::text("file:///test.txt", "content");
320 assert_eq!(result.first_text(), Some("content"));
321 match &result.contents[0] {
322 ResourceContents::Text(t) => assert_eq!(t.uri, "file:///test.txt"),
323 _ => panic!("Expected text resource contents"),
324 }
325 }
326
327 #[test]
328 fn test_resource_result_binary() {
329 let result = ResourceResult::binary("file:///img.png", "base64data", "image/png");
330 match &result.contents[0] {
331 ResourceContents::Blob(b) => {
332 assert_eq!(b.blob, "base64data");
333 assert_eq!(b.mime_type, Some("image/png".into()));
334 }
335 _ => panic!("Expected blob resource contents"),
336 }
337 }
338
339 #[test]
340 fn test_prompt_result_builder() {
341 let result = PromptResult::user("Hello")
342 .add_assistant("Hi there")
343 .add_user("How are you?")
344 .with_description("Greeting");
345
346 assert_eq!(result.len(), 3);
347 assert_eq!(result.description, Some("Greeting".into()));
348 assert!(result.messages[0].is_user());
349 assert!(result.messages[1].is_assistant());
350 assert!(result.messages[2].is_user());
351 }
352
353 #[test]
354 fn test_prompt_result_serde() {
355 let result = PromptResult::user("Test").with_description("A test prompt");
356 let json = serde_json::to_string(&result).unwrap();
357 let parsed: PromptResult = serde_json::from_str(&json).unwrap();
358 assert_eq!(result, parsed);
359 }
360}