Skip to main content

acp_utils/
content.rs

1use agent_client_protocol as acp;
2
3/// Converts ACP `ContentBlock` to plain text.
4///
5/// Embedded resources (e.g., file attachments) are formatted with their URI
6/// and content for inclusion in the agent's context.
7pub fn map_content_blocks_to_text(blocks: Vec<acp::ContentBlock>) -> String {
8    blocks
9        .into_iter()
10        .map(|block| match block {
11            acp::ContentBlock::Text(text) => text.text.clone(),
12            acp::ContentBlock::Image(_) => "[Image content]".to_string(),
13            acp::ContentBlock::Audio(_) => "[Audio content]".to_string(),
14            acp::ContentBlock::ResourceLink(link) => {
15                format!("[Resource: {}]", link.uri)
16            }
17            acp::ContentBlock::Resource(resource) => format_embedded_resource(&resource),
18            _ => "[Unknown content]".to_string(),
19        })
20        .collect::<Vec<_>>()
21        .join("\n")
22}
23
24/// Formats an embedded resource as text for inclusion in agent context.
25pub fn format_embedded_resource(resource: &acp::EmbeddedResource) -> String {
26    match &resource.resource {
27        acp::EmbeddedResourceResource::TextResourceContents(text) => {
28            format!("<file uri=\"{}\">\n{}\n</file>", text.uri, text.text)
29        }
30        acp::EmbeddedResourceResource::BlobResourceContents(blob) => {
31            format!("[Binary resource: {}]", blob.uri)
32        }
33        _ => "[Unknown resource type]".to_string(),
34    }
35}
36
37#[cfg(test)]
38mod tests {
39    use super::*;
40
41    #[test]
42    fn test_format_embedded_resource_text() {
43        let resource = acp::EmbeddedResource::new(acp::EmbeddedResourceResource::TextResourceContents(
44            acp::TextResourceContents::new("let x = 1;", "file://test.rs"),
45        ));
46
47        let result = format_embedded_resource(&resource);
48
49        assert_eq!(result, "<file uri=\"file://test.rs\">\nlet x = 1;\n</file>");
50    }
51
52    #[test]
53    fn test_format_embedded_resource_blob() {
54        let resource = acp::EmbeddedResource::new(acp::EmbeddedResourceResource::BlobResourceContents(
55            acp::BlobResourceContents::new("base64data", "file://image.png"),
56        ));
57
58        let result = format_embedded_resource(&resource);
59
60        assert_eq!(result, "[Binary resource: file://image.png]");
61    }
62
63    #[test]
64    fn test_map_content_blocks_to_text_with_embedded_resource() {
65        let blocks = vec![
66            acp::ContentBlock::Text(acp::TextContent::new("Check this file:")),
67            acp::ContentBlock::Resource(acp::EmbeddedResource::new(
68                acp::EmbeddedResourceResource::TextResourceContents(
69                    acp::TextResourceContents::new("pub fn hello() {}", "file://src/lib.rs").mime_type("text/x-rust"),
70                ),
71            )),
72        ];
73
74        let result = map_content_blocks_to_text(blocks);
75
76        assert!(result.contains("Check this file:"));
77        assert!(result.contains("<file uri=\"file://src/lib.rs\">"));
78        assert!(result.contains("pub fn hello() {}"));
79        assert!(result.contains("</file>"));
80    }
81
82    #[test]
83    fn test_map_content_blocks_text_only() {
84        let blocks = vec![
85            acp::ContentBlock::Text(acp::TextContent::new("Hello")),
86            acp::ContentBlock::Text(acp::TextContent::new("World")),
87        ];
88
89        assert_eq!(map_content_blocks_to_text(blocks), "Hello\nWorld");
90    }
91
92    #[test]
93    fn test_map_content_blocks_empty() {
94        assert_eq!(map_content_blocks_to_text(vec![]), "");
95    }
96
97    #[test]
98    fn test_map_content_blocks_resource_link() {
99        let blocks = vec![acp::ContentBlock::ResourceLink(acp::ResourceLink::new("readme.md", "file://readme.md"))];
100
101        let result = map_content_blocks_to_text(blocks);
102        assert_eq!(result, "[Resource: file://readme.md]");
103    }
104}