1use std::sync::Arc;
9
10use serde_json::Value;
11
12use crate::AgentTool;
13use crate::types::{AgentMessage, AssistantMessage, LlmMessage, ToolResultMessage, UserMessage};
14
15pub trait MessageConverter {
23 type Message;
25
26 fn system_message(system_prompt: &str) -> Option<Self::Message>;
30
31 fn user_message(user: &UserMessage) -> Self::Message;
33
34 fn assistant_message(assistant: &AssistantMessage) -> Self::Message;
36
37 fn tool_result_message(result: &ToolResultMessage) -> Self::Message;
39}
40
41pub fn convert_messages<C: MessageConverter>(
45 messages: &[AgentMessage],
46 system_prompt: &str,
47) -> Vec<C::Message> {
48 let mut result = Vec::new();
49
50 if !system_prompt.is_empty()
51 && let Some(sys) = C::system_message(system_prompt)
52 {
53 result.push(sys);
54 }
55
56 for msg in messages {
57 let AgentMessage::Llm(llm) = msg else {
58 continue;
59 };
60 match llm {
61 LlmMessage::User(user) => result.push(C::user_message(user)),
62 LlmMessage::Assistant(assistant) => result.push(C::assistant_message(assistant)),
63 LlmMessage::ToolResult(tool_result) => {
64 result.push(C::tool_result_message(tool_result));
65 }
66 }
67 }
68
69 result
70}
71
72pub struct ToolSchema {
79 pub name: String,
80 pub description: String,
81 pub parameters: Value,
82}
83
84pub fn extract_tool_schemas(tools: &[Arc<dyn AgentTool>]) -> Vec<ToolSchema> {
86 tools
87 .iter()
88 .map(|t| ToolSchema {
89 name: t.name().to_string(),
90 description: t.description().to_string(),
91 parameters: t.parameters_schema().clone(),
92 })
93 .collect()
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99 use crate::types::{
100 AgentMessage, AssistantMessage, ContentBlock, Cost, LlmMessage, StopReason,
101 ToolResultMessage, Usage, UserMessage,
102 };
103
104 #[derive(Debug, PartialEq)]
107 struct TestMessage {
108 role: String,
109 content: String,
110 }
111
112 struct TestConverter;
113
114 impl MessageConverter for TestConverter {
115 type Message = TestMessage;
116
117 fn system_message(prompt: &str) -> Option<Self::Message> {
118 Some(TestMessage {
119 role: "system".to_string(),
120 content: prompt.to_string(),
121 })
122 }
123
124 fn user_message(msg: &UserMessage) -> Self::Message {
125 let text = ContentBlock::extract_text(&msg.content);
126 TestMessage {
127 role: "user".to_string(),
128 content: text,
129 }
130 }
131
132 fn assistant_message(msg: &AssistantMessage) -> Self::Message {
133 let text = ContentBlock::extract_text(&msg.content);
134 TestMessage {
135 role: "assistant".to_string(),
136 content: text,
137 }
138 }
139
140 fn tool_result_message(msg: &ToolResultMessage) -> Self::Message {
141 let text = ContentBlock::extract_text(&msg.content);
142 TestMessage {
143 role: "tool".to_string(),
144 content: text,
145 }
146 }
147 }
148
149 struct NoSystemConverter;
151
152 impl MessageConverter for NoSystemConverter {
153 type Message = TestMessage;
154
155 fn system_message(_prompt: &str) -> Option<Self::Message> {
156 None
157 }
158
159 fn user_message(msg: &UserMessage) -> Self::Message {
160 TestConverter::user_message(msg)
161 }
162
163 fn assistant_message(msg: &AssistantMessage) -> Self::Message {
164 TestConverter::assistant_message(msg)
165 }
166
167 fn tool_result_message(msg: &ToolResultMessage) -> Self::Message {
168 TestConverter::tool_result_message(msg)
169 }
170 }
171
172 fn make_user(text: &str) -> AgentMessage {
175 AgentMessage::Llm(LlmMessage::User(UserMessage {
176 content: vec![ContentBlock::Text {
177 text: text.to_string(),
178 }],
179 timestamp: 0,
180 cache_hint: None,
181 }))
182 }
183
184 fn make_assistant(text: &str) -> AgentMessage {
185 AgentMessage::Llm(LlmMessage::Assistant(AssistantMessage {
186 content: vec![ContentBlock::Text {
187 text: text.to_string(),
188 }],
189 provider: String::new(),
190 model_id: String::new(),
191 usage: Usage::default(),
192 cost: Cost::default(),
193 stop_reason: StopReason::Stop,
194 error_message: None,
195 error_kind: None,
196 timestamp: 0,
197 cache_hint: None,
198 }))
199 }
200
201 fn make_tool_result(text: &str) -> AgentMessage {
202 AgentMessage::Llm(LlmMessage::ToolResult(ToolResultMessage {
203 tool_call_id: "tc1".to_string(),
204 content: vec![ContentBlock::Text {
205 text: text.to_string(),
206 }],
207 is_error: false,
208 timestamp: 0,
209 details: serde_json::Value::Null,
210 cache_hint: None,
211 }))
212 }
213
214 #[test]
217 fn convert_empty_messages_no_system() {
218 let result = convert_messages::<TestConverter>(&[], "");
219 assert!(result.is_empty());
220 }
221
222 #[test]
223 fn convert_system_prompt_only() {
224 let result = convert_messages::<TestConverter>(&[], "test prompt");
225 assert_eq!(result.len(), 1);
226 assert_eq!(
227 result[0],
228 TestMessage {
229 role: "system".to_string(),
230 content: "test prompt".to_string(),
231 }
232 );
233 }
234
235 #[test]
236 fn convert_user_message_included() {
237 let messages = vec![make_user("hello")];
238 let result = convert_messages::<TestConverter>(&messages, "");
239 assert_eq!(result.len(), 1);
240 assert_eq!(
241 result[0],
242 TestMessage {
243 role: "user".to_string(),
244 content: "hello".to_string(),
245 }
246 );
247 }
248
249 #[test]
250 fn convert_assistant_message_included() {
251 let messages = vec![make_assistant("hi there")];
252 let result = convert_messages::<TestConverter>(&messages, "");
253 assert_eq!(result.len(), 1);
254 assert_eq!(
255 result[0],
256 TestMessage {
257 role: "assistant".to_string(),
258 content: "hi there".to_string(),
259 }
260 );
261 }
262
263 #[test]
264 fn convert_tool_result_message_included() {
265 let messages = vec![make_tool_result("result data")];
266 let result = convert_messages::<TestConverter>(&messages, "");
267 assert_eq!(result.len(), 1);
268 assert_eq!(
269 result[0],
270 TestMessage {
271 role: "tool".to_string(),
272 content: "result data".to_string(),
273 }
274 );
275 }
276
277 #[test]
278 fn convert_mixed_messages() {
279 let messages = vec![
280 make_user("question"),
281 make_assistant("answer"),
282 make_tool_result("tool output"),
283 ];
284 let result = convert_messages::<TestConverter>(&messages, "sys");
285 assert_eq!(result.len(), 4);
286 assert_eq!(result[0].role, "system");
287 assert_eq!(result[1].role, "user");
288 assert_eq!(result[2].role, "assistant");
289 assert_eq!(result[3].role, "tool");
290 }
291
292 #[test]
293 fn convert_skips_custom_messages() {
294 use std::any::Any;
295
296 #[derive(Debug)]
297 struct MyCustom;
298 impl crate::types::CustomMessage for MyCustom {
299 fn as_any(&self) -> &dyn Any {
300 self
301 }
302 }
303
304 let messages = vec![
305 make_user("before"),
306 AgentMessage::Custom(Box::new(MyCustom)),
307 make_user("after"),
308 ];
309 let result = convert_messages::<TestConverter>(&messages, "");
310 assert_eq!(result.len(), 2);
311 assert_eq!(result[0].content, "before");
312 assert_eq!(result[1].content, "after");
313 }
314
315 #[test]
316 fn convert_no_system_when_converter_returns_none() {
317 let messages = vec![make_user("hello")];
318 let result = convert_messages::<NoSystemConverter>(&messages, "ignored prompt");
319 assert_eq!(result.len(), 1);
320 assert_eq!(result[0].role, "user");
321 }
322}