acproxy 是一个使用官方 Rust ACP SDK 构建的 ACP Agent CLI demo。它通过
stdin/stdout 与 Zed 等 ACP Client 通信,目前不访问 HTTP 服务,而是直接调用本地
DirectLogic 生成回复。
- Rust:1.88 或更高版本
- Rust edition:2024
- ACP SDK:
agent-client-protocol = "2.0.0" - ACP 协议:稳定版 v1
- Transport:stdio JSON-RPC
flowchart LR
Client["ACP Client<br/>Zed / smoke client"]
Transport["官方 Rust ACP SDK<br/>stdio JSON-RPC"]
Adapter["AcpAdapter<br/>协议转换与生命周期"]
State["AgentState<br/>内存 Session Store"]
Logger["DebugLogger<br/>追加写入日志文件"]
Service["ConversationService<br/>业务接口"]
Logic["DirectLogic<br/>当前演示实现"]
Client <-->|"ACP request / response / notification"| Transport
Transport <--> Adapter
Adapter <--> State
Adapter --> Logger
Adapter --> Service
Service --> Logic
Adapter -->|"session/update"| Transport
各层职责:
main.rs:解析serve、smoke子命令并启动程序。agent.rs:注册 ACP handler、维护 Session 状态、校验请求、处理取消,并把业务回复转换为session/update。debug_log.rs:线程安全地记录 ACP 请求、响应和通知,不占用协议 stdout。logic.rs:定义与 ACP SDK 解耦的ConversationService、请求/响应模型,以及当前DirectLogic。smoke.rs:启动一个真实子进程,通过 stdio 执行端到端 ACP 验证。
src/
├── main.rs
├── agent.rs
├── debug_log.rs
├── logic.rs
└── smoke.rs
| ACP 方法或能力 | 状态 | 当前行为 |
|---|---|---|
initialize |
支持 | 协商 ACP v1,并返回准确的 Agent capabilities |
session/new |
支持 | 创建内存 Session,生成 UUID,保存工作目录和附加目录 |
session/prompt |
支持 | 接受文本和 Resource Link,调用 ConversationService |
session/update |
支持 | 使用 AgentMessageChunk 分段发送文本 |
session/cancel |
支持 | 取消当前 Prompt、停止业务 future 和分段等待 |
session/list |
支持 | 支持 cwd 过滤、每页 20 条和稳定的 keyset cursor |
session/resume |
支持 | 恢复本进程内已存在的 Session,不回放历史消息 |
session/close |
支持 | 取消进行中的工作并关闭 Session,但仍保留在 Session List |
session/delete |
支持 | 删除 Session;若正在执行 Prompt,也会先取消 |
additionalDirectories |
支持 | 校验、保存、列出,并传递给业务逻辑 |
| Text Prompt | 支持 | 多个 Text Block 使用换行拼接 |
| Resource Link Prompt | 支持 | 向业务层传递 name、uri 和 mimeType |
同一个 Session 同一时间只允许一个活动 Prompt。Session 关闭后不能继续 Prompt,必须先调用
session/resume。只有 session/delete 会将它从 session/list 中移除。
session/load:它要求恢复并通过通知回放完整历史;当前没有持久化历史,因此只声明session/resume。- MCP Server:
mcpServers非空时返回明确的参数错误,不会静默忽略。 - 图片、音频和 Embedded Resource Prompt。
- Tool Call、终端命令执行和人工权限确认。
- Session Mode、模型切换和配置选项。
- 认证能力。
- 跨进程 Session 恢复。
每个 Session 当前保存:
- ACP Session ID
- 工作目录
cwd additionalDirectories- 业务侧
conversation_id - 根据第一次 Prompt 生成的标题
- 最近更新时间
- 当前 Prompt 的取消令牌
- 打开或关闭状态
状态存放在进程内的 HashMap 中,程序退出后全部清空。conversation_id 用于以后映射真实
对话接口中的 Conversation ID。
输入校验规则:
cwd必须是绝对路径。additionalDirectories中的每个路径必须是绝对路径。- 当前不支持非空
mcpServers。 prompt、resume、close遇到未知 Session 时返回resource_not_found;delete采用幂等语义。- 已关闭 Session 或同 Session 中重复提交 Prompt 返回协议错误。
当前 ConversationService 先生成完整回复,ACP adapter 再模拟流式输出:
- 将回复按 6 个 Unicode 字符拆分。
- 每段发送一条
session/update。 - 相邻分段等待 120ms。
- 最后用
session/promptresponse 返回end_turn。
示意:
session/prompt
│
├── session/update: "Direct"
├── 等待 120ms
├── session/update: " respo"
├── 等待 120ms
├── session/update: "nse:你好"
└── PromptResponse: stopReason = "end_turn"
业务调用和分段等待都同时监听 CancellationToken。收到 session/cancel、
session/close 或 session/delete 后,会丢弃正在等待的业务 future,并返回
stopReason = "cancelled"。Prompt 状态使用对应的 token 做身份校验,旧 Prompt 的异步收尾
不会清除新 Prompt 的状态。
cd /Users/simon/dev/acproxy
cargo build
cargo test
cargo clippy --all-targets -- -D warnings启动 ACP Server:
cargo run -- serve默认将 Debug 日志追加写入启动工作目录下的 acproxy-debug.log。也可以指定路径:
cargo run -- serve --log-file /tmp/acproxy-debug.logserve 是默认子命令,下面的命令效果相同:
cargo runRelease 构建:
cargo build --release
/Users/simon/dev/acproxy/target/release/acproxy serve \
--log-file /Users/simon/dev/acproxy/acproxy-debug.logACP 协议独占 stdout。所有日志必须写入 stderr,不能使用 stdout 打印普通日志,否则会破坏 JSON-RPC 数据流。
Server 会记录:
- 所有进入 typed handler 的 ACP request。
- 成功 response 和 error response。
- 入站
session/cancelnotification。 - 出站
session/updatenotification,包括每个文本分段。
日志为单行追加格式并在每条记录后立即 flush:
2026-07-25T10:30:00.123Z DEBUG REQUEST method=session/prompt id=Number(3) payload=...
2026-07-25T10:30:00.124Z DEBUG NOTIFICATION_OUT method=session/update id=- payload=...
2026-07-25T10:30:00.488Z DEBUG RESPONSE method=session/prompt id=Number(3) payload=...
默认路径:
./acproxy-debug.log
路径相对于 Server 的启动工作目录。父目录不存在时会自动创建,已有日志文件不会覆盖,而是继续
追加。项目的 .gitignore 已忽略默认日志文件。
Prompt 内容、文件 URI 和工作目录可能出现在 Debug 日志中,因此生产环境应:
- 将日志写入访问受控的目录。
- 不在日志中记录 Token 等认证信息。
- 配置外部日志轮转和保留周期。
- 不需要调试时关闭或降低详细程度;当前 demo 暂未提供日志级别开关。
日志记录位于 typed ACP handler 边界。无法解析的 JSON 或没有注册 handler 的未知方法不会进入 当前 Debug 日志。
所有普通问题都会触发分段输出:
cargo run -- smoke "请介绍一下 ACP 协议"当前 DirectLogic 还提供三个演示命令:
cargo run -- smoke /help
cargo run -- smoke /about
cargo run -- smoke /error/help:显示演示命令。/about:显示 Session、Conversation、工作目录和资源信息。/error:模拟业务错误,用于验证 ACP error response。
smoke client 实际执行以下生命周期:
initialize
→ session/new
→ session/list
→ session/prompt + session/update
→ session/close
→ session/list
→ session/resume
→ session/delete
先构建 Release 版本:
cargo build --release在 Zed 中配置自定义 Agent:
{
"agent_servers": {
"acproxy": {
"type": "custom",
"command": "/Users/simon/dev/acproxy/target/release/acproxy",
"args": [
"serve",
"--log-file",
"/Users/simon/dev/acproxy/acproxy-debug.log"
],
"env": {
"RUST_BACKTRACE": "1"
}
}
}
}serve 用于启动 ACP Server,--log-file 指定 Debug 日志路径。对应命令为:
/Users/simon/dev/acproxy/target/release/acproxy serve \
--log-file /Users/simon/dev/acproxy/acproxy-debug.log字段说明:
"type": "custom":注册自定义 External Agent。"command":acproxyRelease 可执行文件的绝对路径。"args":依次传入serve和日志文件参数。"env":传递给 Agent 进程的环境变量;RUST_BACKTRACE=1用于在 panic 时输出堆栈, 不需要时可以删除。
如果 settings.json 已经包含主题、快捷键或其他 Agent 配置,只合并 agent_servers.acproxy
这一项,不要覆盖整个文件。
配置完成后:
- 重启 Zed 或重新加载配置。
- 打开 Agent Panel。
- 创建新会话。
- 从 External Agents 中选择
acproxy。 - 输入任意问题,客户端将接收分段
session/update。
实时查看 acproxy 自身的请求、响应和通知日志:
tail -f /Users/simon/dev/acproxy/acproxy-debug.log如需查看 Zed 侧的完整 ACP 通信日志,在 Command Palette 中运行:
dev: open acp logs
业务扩展点是 logic.rs 中的 ConversationService:
use std::future::Future;
pub(crate) trait ConversationService: Clone + Send + Sync + 'static {
fn chat(
&self,
request: ChatRequest,
cancel: &CancellationToken,
) -> impl Future<Output = ConversationOutcome> + Send;
}ChatRequest 提供:
pub(crate) struct ChatRequest {
pub acp_session_id: String,
pub conversation_id: Option<String>,
pub message: String,
pub cwd: PathBuf,
pub additional_directories: Vec<PathBuf>,
pub resources: Vec<ChatResource>,
}业务实现返回以下结果之一:
ConversationOutcome::Completed(ChatCompletion):成功完成。ConversationOutcome::Cancelled:主动取消。ConversationOutcome::Failed(String):业务失败;详细错误写 stderr,客户端收到通用错误。
可以在 logic.rs 中新增实现:
#[derive(Clone)]
pub(crate) struct MyConversationService {
// 后续可放 HTTP client、配置或认证信息
}
impl ConversationService for MyConversationService {
async fn chat(
&self,
request: ChatRequest,
cancel: &CancellationToken,
) -> ConversationOutcome {
if cancel.is_cancelled() {
return ConversationOutcome::Cancelled;
}
// 在这里调用真实对话逻辑。
let content = format!("业务回复:{}", request.message);
ConversationOutcome::Completed(ChatCompletion {
conversation_id: request
.conversation_id
.or_else(|| Some(format!("remote-{}", request.acp_session_id))),
content,
})
}
}trait 使用 Rust 原生的 return-position impl Future 明确保证 future 为 Send,实现端仍可直接
使用 async fn,不需要引入 async-trait。
agent.rs 提供泛型启动入口:
use std::path::Path;
agent::serve_with(
MyConversationService { /* config */ },
Path::new("/path/to/acproxy-debug.log"),
).await例如在 main.rs 的 Serve 分支中替换:
Command::Serve { log_file } => {
let service = logic::MyConversationService { /* config */ };
agent::serve_with(service, &log_file).await.map_err(Into::into)
}ACP handler、Session 生命周期、取消和 session/update 转换都不需要修改。
HTTP 实现通常需要完成以下映射:
ChatRequest.acp_session_id → 本地链路追踪或请求 ID
ChatRequest.conversation_id → HTTP 接口的 Conversation ID
ChatRequest.message → 用户消息
ChatRequest.cwd → 当前工作目录
ChatRequest.additional_directories → 其他工作区根目录
ChatRequest.resources → Prompt 中引用的资源
HTTP response.conversation_id → ChatCompletion.conversation_id
HTTP response.content → ChatCompletion.content
建议把 HTTP client 保存在 Service struct 中并复用连接,不要每次 Prompt 都创建新 client。认证 Token 应从环境变量或安全存储读取,不要放在命令行参数中。
ACP adapter 使用 tokio::select! 监听取消;取消时会 drop 正在等待的 chat future。业务实现如果
还启动了后台任务,也应监听 CancellationToken 并主动停止这些任务。
当前 ConversationService 返回完整 content,再由 ACP 层模拟分段。如果真实接口是 SSE、
NDJSON 或其他流式协议,建议下一步把业务接口扩展为事件流或 channel:
HTTP/SSE chunk
→ ConversationService event
→ SessionUpdate::AgentMessageChunk
→ ACP Client
届时应直接转发上游 chunk,并移除 demo 中的 6 字符拆分和 120ms sleep,避免二次缓冲。
当前测试覆盖:
- ACP v1 版本协商
- Text 与 Resource Link 解析
- Unicode 分段
- 非法 cursor
- 路径和 MCP 参数校验
- Prompt 取消
- 旧 Prompt/新 Prompt 并发状态隔离
- 业务成功、取消和失败
- 完整 stdio ACP 生命周期 smoke
运行:
cargo test
cargo clippy --all-targets -- -D warnings
cargo run -- smoke "端到端验证"agent-client-protocol:官方 ACP Rust SDK。tokio:异步运行时、stdio、子进程和定时器。tokio-util:CancellationToken。clap:CLI 参数和子命令。chrono:Session RFC 3339 时间戳。uuid:ACP Session ID。serde_json:smoke client JSON-RPC 和 Session cursor。anyhow:CLI 与 smoke client 错误处理。