use std::sync::Arc;
use std::time::Instant;
use adk_core::{AdkError, Result, Tool, ToolContext};
use async_trait::async_trait;
use serde_json::{Value, json};
use tracing::{debug, info, warn};
use crate::connection::{AcpAgentConfig, prompt_agent_with_policy};
use crate::permissions::PermissionPolicy;
use crate::usage::{AcpUsage, UsageTracker};
pub struct AcpAgentTool {
name: String,
description: String,
config: AcpAgentConfig,
permission_policy: Arc<PermissionPolicy>,
usage_tracker: Option<UsageTracker>,
}
impl AcpAgentTool {
pub fn new(command: impl Into<String>) -> Self {
let command = command.into();
let name = command.split_whitespace().next().unwrap_or("acp-agent").to_string();
Self {
name: name.clone(),
description: format!("Delegate tasks to the {name} ACP agent"),
config: AcpAgentConfig::new(&command),
permission_policy: Arc::new(PermissionPolicy::AutoApprove),
usage_tracker: None,
}
}
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = name.into();
self
}
pub fn description(mut self, desc: impl Into<String>) -> Self {
self.description = desc.into();
self
}
pub fn working_dir(mut self, path: impl Into<std::path::PathBuf>) -> Self {
self.config.working_dir = path.into();
self
}
pub fn permission_policy(mut self, policy: PermissionPolicy) -> Self {
self.permission_policy = Arc::new(policy);
self.config.auto_approve = matches!(*self.permission_policy, PermissionPolicy::AutoApprove);
self
}
pub fn usage_tracker(mut self, tracker: UsageTracker) -> Self {
self.usage_tracker = Some(tracker);
self
}
}
impl std::fmt::Debug for AcpAgentTool {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AcpAgentTool")
.field("name", &self.name)
.field("command", &self.config.command)
.field("permission_policy", &self.permission_policy)
.finish()
}
}
#[async_trait]
impl Tool for AcpAgentTool {
fn name(&self) -> &str {
&self.name
}
fn description(&self) -> &str {
&self.description
}
fn parameters_schema(&self) -> Option<Value> {
Some(json!({
"type": "object",
"properties": {
"prompt": {
"type": "string",
"description": "The task or question to send to the ACP agent"
}
},
"required": ["prompt"]
}))
}
async fn execute(&self, _ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
let prompt = args
.get("prompt")
.and_then(|v| v.as_str())
.ok_or_else(|| AdkError::tool("AcpAgentTool requires a 'prompt' string field"))?;
info!(
tool = %self.name,
prompt_len = prompt.len(),
cwd = %self.config.working_dir.display(),
"invoking ACP agent"
);
let start = Instant::now();
let result =
prompt_agent_with_policy(&self.config, prompt, self.permission_policy.clone()).await;
let duration = start.elapsed();
match &result {
Ok(response) => {
debug!(
tool = %self.name,
response_len = response.len(),
duration_ms = duration.as_millis() as u64,
"ACP agent responded"
);
if let Some(tracker) = &self.usage_tracker {
tracker.record(&AcpUsage {
tool_name: self.name.clone(),
prompt_chars: prompt.len(),
response_chars: response.len(),
duration,
success: true,
permission_requests: 0,
permissions_denied: 0,
});
}
Ok(json!({ "response": response }))
}
Err(e) => {
warn!(
tool = %self.name,
error = %e,
duration_ms = duration.as_millis() as u64,
"ACP agent failed"
);
if let Some(tracker) = &self.usage_tracker {
tracker.record(&AcpUsage {
tool_name: self.name.clone(),
prompt_chars: prompt.len(),
response_chars: 0,
duration,
success: false,
permission_requests: 0,
permissions_denied: 0,
});
}
Err(AdkError::tool(format!("ACP agent error: {e}")))
}
}
}
}