From e1a60149de64a1a32fd41e4ad0cbe364fedd00de Mon Sep 17 00:00:00 2001 From: Jack Amadeo Date: Fri, 5 Sep 2025 00:08:58 -0400 Subject: [PATCH 1/2] feat(acp): Read files --- Cargo.lock | 17 +++++++++-------- crates/goose-cli/Cargo.toml | 1 + crates/goose-cli/src/commands/acp.rs | 25 +++++++++++++++++++++++-- 3 files changed, 33 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 73eda921ec8a..de63e2aa7f34 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2376,9 +2376,9 @@ checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" [[package]] name = "form_urlencoded" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] @@ -2746,6 +2746,7 @@ dependencies = [ "tracing", "tracing-appender", "tracing-subscriber", + "url", "uuid", "webbrowser 1.0.4", "winapi", @@ -3346,9 +3347,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "1.0.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", "smallvec", @@ -4694,9 +4695,9 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" @@ -7035,9 +7036,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.4" +version = "2.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" dependencies = [ "form_urlencoded", "idna", diff --git a/crates/goose-cli/Cargo.toml b/crates/goose-cli/Cargo.toml index 185f59c0cfbe..cd76ec44cd19 100644 --- a/crates/goose-cli/Cargo.toml +++ b/crates/goose-cli/Cargo.toml @@ -60,6 +60,7 @@ indicatif = "0.17.11" tokio-util = { version = "0.7.15", features = ["compat"] } is-terminal = "0.4.16" anstream = "0.6.18" +url = "2.5.7" [target.'cfg(target_os = "windows")'.dependencies] winapi = { version = "0.3", features = ["wincred"] } diff --git a/crates/goose-cli/src/commands/acp.rs b/crates/goose-cli/src/commands/acp.rs index 089cc82ea459..7689aa500a79 100644 --- a/crates/goose-cli/src/commands/acp.rs +++ b/crates/goose-cli/src/commands/acp.rs @@ -6,12 +6,14 @@ use goose::conversation::message::{Message, MessageContent}; use goose::conversation::Conversation; use goose::providers::create; use std::collections::{HashMap, HashSet}; +use std::fs; use std::sync::Arc; use tokio::sync::{mpsc, oneshot, Mutex}; use tokio::task::JoinSet; use tokio_util::compat::{TokioAsyncReadCompatExt as _, TokioAsyncWriteCompatExt as _}; use tokio_util::sync::CancellationToken; use tracing::{error, info, warn}; +use url::Url; /// Represents a single Goose session for ACP struct GooseSession { @@ -28,6 +30,22 @@ struct GooseAcpAgent { provider: Arc, } +fn read_resource_link(link: acp::ResourceLink) -> Option { + let url = Url::parse(&link.uri).ok()?; + if url.scheme() == "file" { + let path = url.to_file_path().ok()?; + let contents = fs::read_to_string(&path).ok()?; + + Some(format!( + "\n\n# {}\n```\n{}\n```", + path.to_string_lossy(), + contents + )) + } else { + None + } +} + impl GooseAcpAgent { async fn new( session_update_tx: mpsc::UnboundedSender<(acp::SessionNotification, oneshot::Sender<()>)>, @@ -234,9 +252,12 @@ impl acp::Agent for GooseAcpAgent { } } } - _ => { - // Ignore unsupported content types for now + acp::ContentBlock::ResourceLink(link) => { + if let Some(text) = read_resource_link(link) { + user_message = user_message.with_text(text) + } } + acp::ContentBlock::Audio(..) => (), } } From 340c86341f0097aa4c260215a5d63d8dbf0f8ee8 Mon Sep 17 00:00:00 2001 From: Jack Amadeo Date: Fri, 5 Sep 2025 10:03:45 -0400 Subject: [PATCH 2/2] Add a basic test --- crates/goose-cli/src/commands/acp.rs | 48 ++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/crates/goose-cli/src/commands/acp.rs b/crates/goose-cli/src/commands/acp.rs index 7689aa500a79..8e0cac5a50c9 100644 --- a/crates/goose-cli/src/commands/acp.rs +++ b/crates/goose-cli/src/commands/acp.rs @@ -514,3 +514,51 @@ pub async fn run_acp_agent() -> Result<()> { Ok(()) } + +#[cfg(test)] +mod tests { + use agent_client_protocol::ResourceLink; + use std::io::Write; + use tempfile::NamedTempFile; + + use crate::commands::acp::read_resource_link; + + fn new_resource_link(content: &str) -> anyhow::Result<(ResourceLink, NamedTempFile)> { + let mut file = NamedTempFile::new()?; + file.write_all(content.as_bytes())?; + + let link = ResourceLink { + annotations: None, + description: None, + mime_type: None, + name: file + .path() + .file_name() + .unwrap() + .to_string_lossy() + .to_string(), + size: None, + title: None, + uri: format!("file://{}", file.path().to_str().unwrap()), + }; + Ok((link, file)) + } + + #[test] + fn test_read_resource_link_non_file_scheme() { + let (link, file) = new_resource_link("print(\"hello, world\")").unwrap(); + + let result = read_resource_link(link).unwrap(); + let expected = format!( + " + +# {} +``` +print(\"hello, world\") +```", + file.path().to_str().unwrap(), + ); + + assert_eq!(result, expected,) + } +}