feat(acp): Read files (#4531)

This commit is contained in:
Jack Amadeo
2025-09-05 13:29:14 -04:00
committed by GitHub
parent 72eaa20d2b
commit a2bd9e2af0
3 changed files with 81 additions and 10 deletions
Generated
+9 -8
View File
@@ -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",
+1
View File
@@ -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"] }
+71 -2
View File
@@ -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<dyn goose::providers::base::Provider>,
}
fn read_resource_link(link: acp::ResourceLink) -> Option<String> {
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(..) => (),
}
}
@@ -493,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,)
}
}