1use crate::context::RawToolEvent;
2
3pub trait IntoToolResponse {
13 fn into_tool_response(self, default_language: &str) -> Vec<RawToolEvent>;
14}
15
16impl IntoToolResponse for String {
17 fn into_tool_response(self, _default_language: &str) -> Vec<RawToolEvent> {
18 vec![RawToolEvent::Content {
19 data: self.into_bytes(),
20 mime_type: Some(crate::constants::MIME_TEXT.to_string()),
21 metadata: vec![],
22 }]
23 }
24}
25
26impl IntoToolResponse for &str {
27 fn into_tool_response(self, default_language: &str) -> Vec<RawToolEvent> {
28 self.to_string().into_tool_response(default_language)
29 }
30}
31
32impl IntoToolResponse for () {
33 fn into_tool_response(self, _default_language: &str) -> Vec<RawToolEvent> {
34 vec![]
35 }
36}
37
38impl IntoToolResponse for Vec<u8> {
39 fn into_tool_response(self, _default_language: &str) -> Vec<RawToolEvent> {
40 vec![RawToolEvent::Content {
41 data: self,
42 mime_type: Some(crate::constants::MIME_OCTET_STREAM.to_string()),
43 metadata: vec![],
44 }]
45 }
46}
47
48pub struct Json<T>(pub T);
50
51impl<T: serde::Serialize> IntoToolResponse for Json<T> {
52 fn into_tool_response(self, _default_language: &str) -> Vec<RawToolEvent> {
53 vec![RawToolEvent::Content {
54 data: serde_json::to_vec(&self.0).unwrap_or_default(),
55 mime_type: Some(crate::constants::MIME_JSON.to_string()),
56 metadata: vec![],
57 }]
58 }
59}
60
61pub struct Content(pub &'static str, pub Vec<u8>);
65
66impl IntoToolResponse for Content {
67 fn into_tool_response(self, _default_language: &str) -> Vec<RawToolEvent> {
68 vec![RawToolEvent::Content {
69 data: self.1,
70 mime_type: Some(self.0.to_string()),
71 metadata: vec![],
72 }]
73 }
74}
75
76#[doc(hidden)]
80pub trait IntoToolResponseViaSerialize {
81 #[allow(clippy::wrong_self_convention)]
82 fn into_tool_response(&self, default_language: &str) -> Vec<RawToolEvent>;
83}
84
85impl<T: serde::Serialize> IntoToolResponseViaSerialize for T {
86 fn into_tool_response(&self, default_language: &str) -> Vec<RawToolEvent> {
87 cbor_encode_response(self, default_language)
88 }
89}
90
91pub fn cbor_encode_response<T: serde::Serialize>(
96 val: &T,
97 _default_language: &str,
98) -> Vec<RawToolEvent> {
99 let mut buf = Vec::new();
100 ciborium::into_writer(val, &mut buf).expect("CBOR serialization should not fail");
101 vec![RawToolEvent::Content {
102 data: buf,
103 mime_type: Some(crate::constants::MIME_CBOR.to_string()),
104 metadata: vec![],
105 }]
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111 use serde_json::json;
112
113 fn extract(events: Vec<RawToolEvent>) -> (Vec<u8>, Option<String>) {
114 match events.into_iter().next().unwrap() {
115 RawToolEvent::Content {
116 data, mime_type, ..
117 } => (data, mime_type),
118 _ => panic!("expected Content event"),
119 }
120 }
121
122 #[test]
123 fn string_is_text_plain() {
124 let (data, mime) = extract("hello".to_string().into_tool_response("en"));
125 assert_eq!(mime.as_deref(), Some(crate::constants::MIME_TEXT));
126 assert_eq!(data, b"hello");
127 }
128
129 #[test]
130 fn str_ref_is_text_plain() {
131 let s: &str = "hello";
132 let (data, mime) = extract(s.into_tool_response("en"));
133 assert_eq!(mime.as_deref(), Some(crate::constants::MIME_TEXT));
134 assert_eq!(data, b"hello");
135 }
136
137 #[test]
138 fn unit_is_empty() {
139 assert!(().into_tool_response("en").is_empty());
140 }
141
142 #[test]
143 fn vec_u8_is_octet_stream() {
144 let (_d, mime) = extract(vec![1u8, 2, 3].into_tool_response("en"));
145 assert_eq!(mime.as_deref(), Some(crate::constants::MIME_OCTET_STREAM));
146 }
147
148 #[test]
149 fn content_keeps_explicit_mime() {
150 let (data, mime) = extract(Content("image/png", vec![0x89]).into_tool_response("en"));
151 assert_eq!(mime.as_deref(), Some("image/png"));
152 assert_eq!(data, vec![0x89]);
153 }
154
155 #[test]
156 fn json_wrapper_is_json_mime() {
157 let value = json!({"rows": [1, 2, 3]});
158 let (data, mime) = extract(Json(value.clone()).into_tool_response("en"));
159 assert_eq!(mime.as_deref(), Some(crate::constants::MIME_JSON));
160 let parsed: serde_json::Value = serde_json::from_slice(&data).unwrap();
161 assert_eq!(parsed, value);
162 }
163
164 #[test]
165 fn serialize_struct_falls_back_to_cbor() {
166 #[derive(serde::Serialize)]
167 struct S {
168 n: u32,
169 }
170 let (data, mime) = extract(S { n: 7 }.into_tool_response("en"));
171 assert_eq!(mime.as_deref(), Some(crate::constants::MIME_CBOR));
172 let v: ciborium::value::Value = ciborium::from_reader(&data[..]).unwrap();
173 assert_eq!(
174 v,
175 ciborium::value::Value::Map(vec![(
176 ciborium::value::Value::Text("n".into()),
177 ciborium::value::Value::Integer(7.into())
178 )])
179 );
180 }
181
182 #[test]
183 fn bytes_falls_back_to_cbor_byte_string() {
184 let (data, mime) = extract(crate::bytes::Bytes(b"hi".to_vec()).into_tool_response("en"));
185 assert_eq!(mime.as_deref(), Some(crate::constants::MIME_CBOR));
186 let v: ciborium::value::Value = ciborium::from_reader(&data[..]).unwrap();
187 assert_eq!(v, ciborium::value::Value::Bytes(b"hi".to_vec()));
188 }
189}