构建 ACP 客户端或主机

当 ADK-Rust 应用需要将编码工作委托给外部 ACP 进程时,请使用客户端方向。应用仍然是主机:它负责项目选择、用户体验、审批规则,以及向编码代理提供的任何本地服务。

安装

[dependencies]
adk-acp = "2.1.0"

默认功能集是客户端实现。只有在公开 ADK-Rust 代理时才需要 server 功能。

选择客户端形态

产品形态API
使用全新进程执行的单个隔离任务prompt_agent_with_policy
包含非文本(图像、音频、资源)内容的单个隔离任务prompt_agent_content_with_policy
可供 LLM 代理使用的编码专家AcpAgentTool
多个具名编码专家AcpToolset
持续的项目对话AcpSession
回合运行期间呈现的文本和工具进度stream_prompt

单次提示

use adk_acp::{
    AcpAgentConfig, PermissionPolicy, prompt_agent_with_policy,
};
use std::sync::Arc;

let config = AcpAgentConfig::new("my-coding-agent --acp")
    .working_dir("/absolute/path/to/project");

let answer = prompt_agent_with_policy(
    &config,
    "Inspect the failing test and explain the cause.",
    Arc::new(PermissionPolicy::DenyAll),
).await?;

DenyAll 是默认设置,因为生成的编码代理可能会请求执行具有实际副作用的操作。仅在受信任的本地工作流中使用 AutoApprove

发送富提示内容

prompt_agent_content_with_policy 传输完整的 adk_core::Content 值—— 而不仅仅是字符串——因此提示可以携带非文本内容。嵌入式资源、图像和音频部分会通过共享内容模块映射到匹配的 ACP 内容块,而不会被丢弃;文本始终会被保留。没有可传输 ACP 表示形式的部分会被跳过,而完全未映射到任何内容块的提示则会被拒绝。

use adk_acp::{AcpAgentConfig, PermissionPolicy};
use adk_acp::connection::prompt_agent_content_with_policy;
use adk_core::{Content, Part};
use std::sync::Arc;

let mut content = Content::new("user");
content.parts.push(Part::Text { text: "What is in this image?".into() });
content.parts.push(Part::InlineData { mime_type: "image/png".into(), data: png_bytes });

let config = AcpAgentConfig::new("my-coding-agent --acp")
    .working_dir("/absolute/path/to/project");

let answer = prompt_agent_content_with_policy(
    &config,
    &content,
    Arc::new(PermissionPolicy::DenyAll),
).await?;

从 ADK 代理进行委派

use adk_acp::{AcpAgentTool, PermissionDecision, PermissionPolicy};
use adk_agent::LlmAgentBuilder;
use std::sync::Arc;

let policy = PermissionPolicy::Custom(Box::new(|request| {
    if request.title.to_ascii_lowercase().contains("delete") {
        PermissionDecision::deny()
    } else {
        PermissionDecision::allow_once()
    }
}));

let coding_agent = AcpAgentTool::new("my-coding-agent --acp")
    .name("repository_specialist")
    .description("Inspect and improve the current Rust repository")
    .working_dir("/absolute/path/to/project")
    .permission_policy(policy);

let coordinator = LlmAgentBuilder::new("coordinator")
    .model(model)
    .instruction("Delegate repository changes to repository_specialist.")
    .tool(Arc::new(coding_agent))
    .build()?;

每次 AcpAgentTool 调用都会启动一个新的进程和会话。当委派的任务是自包含的,并且协调器只需要将最终文本作为工具结果时,应选择这种方式。

持久会话与取消

use adk_acp::{AcpAgentConfig, AcpSession, PermissionPolicy};
use std::sync::Arc;

let config = AcpAgentConfig::new("my-coding-agent --acp")
    .working_dir("/absolute/path/to/project");
let mut session = AcpSession::start(
    config,
    Arc::new(PermissionPolicy::DenyAll),
).await?;

let first = session.prompt("Map the error-handling modules.").await?;
let second = session.prompt("Now inspect the most central one.").await?;

let cancel = session.cancellation_handle()?;
// Move `cancel` into a stop-button, timeout, or shutdown task while another
// task awaits `session.prompt(...)`.

session.close().await?;

取消句柄会发送官方的 session/cancel 通知。在收到已取消的停止原因之前,应保持等待提示;这样,同一会话便可接受另一个提示,而不会在其队列中留下过时的响应。

将一轮交互流式传输到 UI

stream_prompt 会为代理文本、思考过程、工具启动、权限决策、完成和错误生成 OutputChunk 值。除此之外,它还会展示 External_Agent 一轮交互的两个更丰富的视图:

  • OutputChunk::ToolUpdate — External_Agent 的 ToolCallUpdate,按工具调用 id 进行关联,携带报告的状态、类型、更新后的标题、提取的内容文本以及受影响的文件位置。这使 UI 能够呈现工具进度、差异和受影响文件列表,而不只是最终文本。
  • OutputChunk::Usage — External_Agent 的 UsageUpdate,携带令牌 used 和上下文窗口 size,以及 agent 报告时提供的累计 costcurrency, 这样 UI 就可以显示上下文窗口的消耗情况。

Agent 消息文本的呈现方式与之前完全相同,因此只读取文本块的 UI 不受影响。应用可以隐藏思考块、单独呈现工具活动,并在其界面中公开共享的 StatusTracker

请参阅可运行的 acp_client_host crate, 了解完整循环。

让 agent 请求文件

实现 AcpFileSystem,并通过 AcpAgentConfig::filesystem 将其附加。读写能力通过 supports_readsupports_write 独立声明。

回调接收绝对路径。生产环境主机应当:

  1. 规范化已批准的工作区和请求的路径;
  2. 拒绝位于已批准根目录之外的路径,包括符号链接逃逸;
  3. 决定未保存的编辑器缓冲区是否覆盖磁盘内容;
  4. 应用文件大小和行范围限制;
  5. 仅在应用实现并授权写入时声明写入能力。

工作目录是上下文,而不是沙箱。文件系统验证和操作系统进程边界解决的是不同的问题。

让 agent 运行命令

实现 AcpTerminal,并通过 AcpAgentConfig::terminal 将其附加。ACP 将终端声明为一项能力,因此主机必须实现完整的创建、输出、等待、终止和释放生命周期。

主机选择命令允许列表、工作目录规则、环境变量、输出限制、进程隔离和清理行为。终端回调在 JSON-RPC 调度循环之外执行,因此长时间等待不会冻结权限或取消请求。

向会话提供 MCP 服务器

use adk_acp::AcpAgentConfig;
use adk_acp::agent_client_protocol::schema::v1::{
    McpServer, McpServerStdio,
};

let tools = McpServer::Stdio(
    McpServerStdio::new("project-tools", "/absolute/path/to/mcp-server")
        .args(vec!["--read-only".into()]),
);

let config = AcpAgentConfig::new("my-coding-agent --acp")
    .working_dir("/absolute/path/to/project")
    .mcp_server(tools);

稳定的 ACP v1 要求代理接受 stdio MCP 配置。仅当外部代理声明支持这些可选传输方式时,才会发送 HTTP 和 SSE 条目。AcpAgentConfig 调试输出会列出名称和环境变量键,但不会打印机密值。

权限策略

每个权限请求都包含会话 ID、精确的工具调用 ID、工具类型、原始输入以及代理提供的所有选项。选项 ID 是不透明的。ADK-Rust 匹配允许和拒绝语义,然后返回原始 ID;伪造的选择将变为取消操作。

PermissionPolicy::async_custom 可以等待桌面对话框、网页审批界面或组织策略服务。通过此 API 等待人工交互,而不是阻塞线程,以保持调度循环的响应能力。

后续步骤