1use agent_client_protocol::schema::v2 as acp;
2#[cfg(not(target_family = "wasm"))]
3use llm::ContentBlock as LlmContentBlock;
4use std::borrow::Cow;
5
6#[cfg(not(target_family = "wasm"))]
7pub fn map_acp_to_content_blocks(blocks: Vec<acp::ContentBlock>) -> Vec<LlmContentBlock> {
8 blocks
9 .into_iter()
10 .map(|block| match block {
11 acp::ContentBlock::Text(text) => LlmContentBlock::text(text.text),
12 acp::ContentBlock::Image(image) => {
13 LlmContentBlock::Image { data: image.data, mime_type: image.mime_type.to_string() }
14 }
15 acp::ContentBlock::Audio(audio) => {
16 LlmContentBlock::Audio { data: audio.data, mime_type: audio.mime_type.to_string() }
17 }
18 block => LlmContentBlock::text(block_text(&block).into_owned()),
19 })
20 .collect()
21}
22
23#[cfg(not(target_family = "wasm"))]
24pub fn map_user_content_block(block: &LlmContentBlock) -> acp::ContentBlock {
25 match block {
26 LlmContentBlock::Text { text } => acp::ContentBlock::from(text.clone()),
27 LlmContentBlock::Image { data, mime_type } => {
28 acp::ContentBlock::Image(acp::ImageContent::new(data.clone(), mime_type.clone()))
29 }
30 LlmContentBlock::Audio { data, mime_type } => {
31 acp::ContentBlock::Audio(acp::AudioContent::new(data.clone(), mime_type.clone()))
32 }
33 }
34}
35
36pub fn display_content_blocks(blocks: &[acp::ContentBlock]) -> Vec<acp::ContentBlock> {
37 blocks
38 .iter()
39 .map(|block| match block {
40 acp::ContentBlock::Resource(resource) => {
41 let uri = match &resource.resource {
42 acp::EmbeddedResourceResource::TextResourceContents(text) => &text.uri,
43 acp::EmbeddedResourceResource::BlobResourceContents(blob) => &blob.uri,
44 _ => return acp::ContentBlock::from("[Unknown resource type]"),
45 };
46 acp::ContentBlock::from(format!("[Resource: {uri}]"))
47 }
48 block => block.clone(),
49 })
50 .collect()
51}
52
53pub fn map_content_blocks_to_text(blocks: &[acp::ContentBlock]) -> String {
58 blocks.iter().map(block_text).collect::<Vec<_>>().join("\n")
59}
60
61pub fn format_embedded_resource(resource: &acp::EmbeddedResource) -> String {
63 match &resource.resource {
64 acp::EmbeddedResourceResource::TextResourceContents(text) => {
65 format!("<file uri=\"{}\">\n{}\n</file>", text.uri, text.text)
66 }
67 acp::EmbeddedResourceResource::BlobResourceContents(blob) => {
68 format!("[Binary resource: {}]", blob.uri)
69 }
70 _ => "[Unknown resource type]".to_string(),
71 }
72}
73
74fn block_text(block: &acp::ContentBlock) -> Cow<'_, str> {
75 match block {
76 acp::ContentBlock::Text(text) => Cow::Borrowed(&text.text),
77 acp::ContentBlock::Image(_) => Cow::Borrowed("[Image content]"),
78 acp::ContentBlock::Audio(_) => Cow::Borrowed("[Audio content]"),
79 acp::ContentBlock::ResourceLink(link) => Cow::Owned(format!("[Resource: {}]", link.uri)),
80 acp::ContentBlock::Resource(resource) => Cow::Owned(format_embedded_resource(resource)),
81 _ => Cow::Borrowed("[Unknown content]"),
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88
89 #[test]
90 fn prompt_conversion_preserves_media_and_shares_resource_display() {
91 let media = vec![
92 acp::ContentBlock::Image(acp::ImageContent::new("image-data", "image/png")),
93 acp::ContentBlock::Audio(acp::AudioContent::new("audio-data", "audio/wav")),
94 ];
95 let converted = map_acp_to_content_blocks(media.clone());
96 assert_eq!(converted.iter().map(map_user_content_block).collect::<Vec<_>>(), media);
97 assert_eq!(map_content_blocks_to_text(&media), "[Image content]\n[Audio content]");
98 let resources = vec![
99 acp::ContentBlock::ResourceLink(acp::ResourceLink::new("readme", "file://readme")),
100 acp::ContentBlock::Resource(acp::EmbeddedResource::new(
101 acp::EmbeddedResourceResource::TextResourceContents(acp::TextResourceContents::new(
102 "contents",
103 "file://source",
104 )),
105 )),
106 ];
107 let converted = map_acp_to_content_blocks(resources.clone());
108 let text = converted
109 .iter()
110 .map(|block| match block {
111 LlmContentBlock::Text { text } => text.as_str(),
112 _ => panic!("resources must become text"),
113 })
114 .collect::<Vec<_>>()
115 .join("\n");
116 assert_eq!(text, map_content_blocks_to_text(&resources));
117 }
118
119 #[test]
120 fn test_format_embedded_resource_text() {
121 let resource = acp::EmbeddedResource::new(acp::EmbeddedResourceResource::TextResourceContents(
122 acp::TextResourceContents::new("let x = 1;", "file://test.rs"),
123 ));
124
125 let result = format_embedded_resource(&resource);
126
127 assert_eq!(result, "<file uri=\"file://test.rs\">\nlet x = 1;\n</file>");
128 }
129
130 #[test]
131 fn test_format_embedded_resource_blob() {
132 let resource = acp::EmbeddedResource::new(acp::EmbeddedResourceResource::BlobResourceContents(
133 acp::BlobResourceContents::new("base64data", "file://image.png"),
134 ));
135
136 let result = format_embedded_resource(&resource);
137
138 assert_eq!(result, "[Binary resource: file://image.png]");
139 }
140
141 #[test]
142 fn test_map_content_blocks_to_text_with_embedded_resource() {
143 let blocks = vec![
144 acp::ContentBlock::Text(acp::TextContent::new("Check this file:")),
145 acp::ContentBlock::Resource(acp::EmbeddedResource::new(
146 acp::EmbeddedResourceResource::TextResourceContents(
147 acp::TextResourceContents::new("pub fn hello() {}", "file://src/lib.rs").mime_type("text/x-rust"),
148 ),
149 )),
150 ];
151
152 let result = map_content_blocks_to_text(&blocks);
153
154 assert!(result.contains("Check this file:"));
155 assert!(result.contains("<file uri=\"file://src/lib.rs\">"));
156 assert!(result.contains("pub fn hello() {}"));
157 assert!(result.contains("</file>"));
158 }
159
160 #[test]
161 fn test_map_content_blocks_text_only() {
162 let blocks = vec![
163 acp::ContentBlock::Text(acp::TextContent::new("Hello")),
164 acp::ContentBlock::Text(acp::TextContent::new("World")),
165 ];
166
167 assert_eq!(map_content_blocks_to_text(&blocks), "Hello\nWorld");
168 }
169
170 #[test]
171 fn test_map_content_blocks_empty() {
172 assert_eq!(map_content_blocks_to_text(&[]), "");
173 }
174
175 #[test]
176 fn test_map_content_blocks_resource_link() {
177 let blocks = vec![acp::ContentBlock::ResourceLink(acp::ResourceLink::new("readme.md", "file://readme.md"))];
178
179 let result = map_content_blocks_to_text(&blocks);
180 assert_eq!(result, "[Resource: file://readme.md]");
181 }
182}