添加qiming-rcoder模块

This commit is contained in:
Codex
2026-06-01 13:54:52 +08:00
parent 8092c4b1f8
commit 4b1a580132
539 changed files with 151650 additions and 0 deletions

View File

@@ -0,0 +1,676 @@
# SACP 协议升级技术方案
> 从官方 ACP (`agent-client-protocol`) 迁移到 Symposium ACP (`sacp`) 的技术方案设计
## 1. 背景与动机
### 1.1 当前问题
当前项目使用官方 ACP 协议库 `agent-client-protocol = "0.9.3"`,存在以下核心问题:
1. **Send trait 限制**`ClientSideConnection` 未实现 `Send` trait导致
- 必须在 `LocalSet` 中运行
- 必须使用 `spawn_local` 而非标准的 `tokio::spawn`
- 需要 `spawn_blocking` + 单线程运行时 + `LocalSet` 的复杂组合
2. **并发模型复杂**
```rust
// 当前代码模式acp_agent.rs
tokio::task::spawn_blocking(move || {
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()?;
rt.block_on(async move {
let local_set = tokio::task::LocalSet::new();
local_set.run_until(async move {
// ACP 连接处理必须在这里
}).await
})
})
```
3. **代码耦合度高**:连接建立、消息处理、生命周期管理紧密耦合在 `ClaudeCodeLauncher` 中
### 1.2 SACP 优势
Symposium ACP (`sacp = "10.1.0"`) 提供:
1. **Send + 'static 支持**`Component<L>` trait 要求类型满足 `Send + 'static`
2. **简化的并发模型**:可直接使用标准 Tokio 多线程运行时
3. **类型安全的链接系统**`ClientToAgent`, `AgentToClient` 等编译时类型检查
4. **Builder 模式 API**:更简洁、更符合 Rust 习惯的 API 设计
5. **MCP 原生集成**:内置 MCP-over-ACP 支持
## 2. 架构设计
### 2.1 模块结构变化
```
crates/
├── agent_abstraction/ # 抽象层(需要重构)
│ ├── acp/
│ │ ├── mod.rs
│ │ ├── connection.rs # AgentConnection保留调整内部实现
│ │ └── sacp_adapter.rs # 新增SACP 适配器
│ ├── launcher/
│ │ ├── mod.rs
│ │ ├── lifecycle.rs # AgentLifecycleGuard保留
│ │ ├── channel.rs # 通道处理器(需要适配)
│ │ └── claude_code_sacp.rs # 新增SACP 版本启动器
│ └── session/
│ ├── worker.rs # AgentWorker trait保留
│ └── acp_worker.rs # AcpAgentWorker需要适配
├── agent_runner/ # 运行时(需要重构)
│ ├── proxy_agent/
│ │ ├── mod.rs
│ │ └── acp_agent.rs # 移除 LocalSet 依赖
│ └── main.rs # 简化运行时架构
└── acp_adapter/ # 新增ACP 协议适配层
├── Cargo.toml
├── src/
│ ├── lib.rs
│ ├── traits.rs # 协议无关的抽象 trait
│ ├── sacp_impl.rs # SACP 实现
│ └── legacy_impl.rs # 官方 ACP 兼容层(可选)
```
### 2.2 核心 Trait 设计
#### 2.2.1 协议无关的客户端抽象
```rust
// crates/acp_adapter/src/traits.rs
use async_trait::async_trait;
use std::path::PathBuf;
/// ACP 客户端抽象 trait
///
/// 这是协议无关的抽象层,支持 SACP 和官方 ACP 的切换
#[async_trait]
pub trait AcpClient: Send + Sync + 'static {
/// 会话 ID 类型
type SessionId: Clone + Send + Sync + std::fmt::Display;
/// 错误类型
type Error: std::error::Error + Send + Sync + 'static;
/// 初始化连接
async fn initialize(&self, client_info: ClientInfo) -> Result<InitializeResponse, Self::Error>;
/// 创建新会话
async fn new_session(&self, config: SessionConfig) -> Result<Self::SessionId, Self::Error>;
/// 发送 Prompt
async fn prompt(&self, session_id: &Self::SessionId, request: PromptRequest) -> Result<PromptResponse, Self::Error>;
/// 取消会话
async fn cancel(&self, session_id: &Self::SessionId) -> Result<(), Self::Error>;
/// 检查连接是否有效
fn is_connected(&self) -> bool;
}
/// 客户端信息
#[derive(Debug, Clone)]
pub struct ClientInfo {
pub name: String,
pub version: String,
pub title: Option<String>,
}
/// 会话配置
#[derive(Debug, Clone)]
pub struct SessionConfig {
pub working_directory: PathBuf,
pub mcp_servers: Vec<McpServerConfig>,
pub meta: Option<serde_json::Value>,
}
/// MCP 服务器配置
#[derive(Debug, Clone)]
pub struct McpServerConfig {
pub name: String,
pub command: String,
pub args: Vec<String>,
pub env: Vec<(String, String)>,
}
/// Prompt 请求
#[derive(Debug, Clone)]
pub struct PromptRequest {
pub messages: Vec<Message>,
pub session_id: String,
}
/// Prompt 响应
#[derive(Debug, Clone)]
pub struct PromptResponse {
pub content: Vec<ContentBlock>,
pub stop_reason: StopReason,
}
```
#### 2.2.2 SACP 实现
```rust
// crates/acp_adapter/src/sacp_impl.rs
use sacp::{
ClientToAgent, Channel, JrConnectionCx, JrConnectionBuilder,
schema::{InitializeRequest, ProtocolVersion, NewSessionRequest, SessionId},
on_receive_request,
};
use std::sync::Arc;
use tokio::sync::RwLock;
/// SACP 客户端实现
///
/// 使用 SACP 的 Component trait 和 JrConnectionBuilder 构建
pub struct SacpClient {
/// 连接上下文(线程安全)
connection_cx: Arc<RwLock<Option<JrConnectionCx<ClientToAgent>>>>,
/// 连接状态
connected: Arc<std::sync::atomic::AtomicBool>,
}
impl SacpClient {
/// 创建新客户端并连接到 Agent
pub async fn connect(transport: impl sacp::Component<sacp::AgentToClient>) -> Result<Self, sacp::Error> {
let connected = Arc::new(std::sync::atomic::AtomicBool::new(false));
let connected_clone = connected.clone();
let (channel, server_future) = transport.into_server();
// 在后台运行服务
tokio::spawn(server_future);
// 构建客户端连接
let client = Self {
connection_cx: Arc::new(RwLock::new(None)),
connected,
};
Ok(client)
}
}
#[async_trait]
impl AcpClient for SacpClient {
type SessionId = SessionId;
type Error = sacp::Error;
async fn initialize(&self, client_info: ClientInfo) -> Result<InitializeResponse, Self::Error> {
// 使用 SACP 的 InitializeRequest
let request = InitializeRequest::new(ProtocolVersion::LATEST)
.client_info(sacp::schema::Implementation::new(
&client_info.name,
&client_info.version,
));
// 发送请求并等待响应
// ...
todo!()
}
async fn new_session(&self, config: SessionConfig) -> Result<Self::SessionId, Self::Error> {
// 使用 SACP 的 NewSessionRequest
let request = NewSessionRequest::new(config.working_directory);
// ...
todo!()
}
async fn prompt(&self, session_id: &Self::SessionId, request: PromptRequest) -> Result<PromptResponse, Self::Error> {
// 使用 SACP 的 PromptRequest
// ...
todo!()
}
async fn cancel(&self, session_id: &Self::SessionId) -> Result<(), Self::Error> {
// 使用 SACP 的 CancelNotification
// ...
todo!()
}
fn is_connected(&self) -> bool {
self.connected.load(std::sync::atomic::Ordering::Relaxed)
}
}
```
### 2.3 启动器重构
#### 2.3.1 SACP 版本启动器
```rust
// crates/agent_abstraction/src/launcher/claude_code_sacp.rs
use sacp::{ClientToAgent, ByteStreams, Component};
use std::process::Stdio;
use tokio::process::Command;
/// SACP 版本的 Claude Code 启动器
///
/// 关键变化:
/// 1. 不再需要 LocalSet
/// 2. 使用 SACP 的 Component trait
/// 3. 支持标准 Tokio spawn
pub struct SacpClaudeCodeLauncher<N: SessionNotifier> {
notifier: Arc<N>,
}
impl<N: SessionNotifier + 'static> SacpClaudeCodeLauncher<N> {
pub fn new(notifier: Arc<N>) -> Self {
Self { notifier }
}
/// 启动 Claude Code Agent
///
/// 与旧版本的关键区别:
/// - 使用 `tokio::spawn` 而非 `spawn_local`
/// - 使用 SACP 的 `ByteStreams` 作为传输层
/// - 使用 `ClientToAgent::builder()` 构建连接
pub async fn launch(
&self,
project_id: String,
project_path: PathBuf,
model_provider: Option<ModelProviderConfig>,
start_config: AgentStartConfig,
) -> Result<SacpConnectionInfo> {
// 1. 加载配置
let agent_config = load_agent_config(model_provider.as_ref(), &start_config.service_type).await?;
// 2. 启动子进程
let mut child = Command::new(&agent_config.command)
.args(&agent_config.args)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.kill_on_drop(true)
.current_dir(&project_path)
.envs(&agent_config.env)
.spawn()?;
let stdin = child.stdin.take().unwrap();
let stdout = child.stdout.take().unwrap();
// 3. 创建 SACP 传输层关键变化ByteStreams 实现了 Component + Send
let transport = ByteStreams::new(stdout, stdin);
// 4. 创建通道
let (prompt_tx, prompt_rx) = tokio::sync::mpsc::unbounded_channel();
let (cancel_tx, cancel_rx) = tokio::sync::mpsc::unbounded_channel();
let cancel_token = CancellationToken::new();
// 5. 在标准 Tokio task 中运行(无需 LocalSet
let notifier = self.notifier.clone();
let cancel_token_clone = cancel_token.clone();
let connection_handle = tokio::spawn(async move {
Self::run_connection(
transport,
project_id,
project_path,
start_config,
prompt_rx,
cancel_rx,
cancel_token_clone,
notifier,
).await
});
// 6. 等待初始化完成并获取 session_id
// ...
Ok(SacpConnectionInfo {
session_id,
prompt_tx,
cancel_tx,
lifecycle_guard: Arc::new(lifecycle_guard),
})
}
/// 运行 SACP 连接
///
/// 使用 SACP 的 `ClientToAgent::builder().run_until()` 模式
async fn run_connection(
transport: impl Component<sacp::AgentToClient>,
project_id: String,
project_path: PathBuf,
start_config: AgentStartConfig,
mut prompt_rx: mpsc::UnboundedReceiver<PromptRequest>,
mut cancel_rx: mpsc::UnboundedReceiver<CancelNotification>,
cancel_token: CancellationToken,
notifier: Arc<impl SessionNotifier>,
) -> Result<()> {
ClientToAgent::builder()
.name("rcoder-agent-runner")
// 注册请求处理器(使用 SACP 宏)
.on_receive_request(
async |req: PermissionRequest, req_cx: JrRequestCx<_>, _| {
// 处理权限请求
req_cx.respond(PermissionResponse::default())
},
on_receive_request!(),
)
// 注册通知处理器
.on_receive_notification(
async |notif: SessionUpdate, _| {
// 处理会话更新通知
Ok(())
},
on_receive_notification!(),
)
// 运行连接
.run_until(transport, async |cx| {
// Step 1: 初始化
cx.send_request(InitializeRequest::new(ProtocolVersion::LATEST))
.block_task()
.await?;
// Step 2: 创建会话
let meta = start_config.build_meta();
let session = cx.build_session(NewSessionRequest::new(project_path).meta(meta))
.block_task()
.await?;
let session_id = session.session_id();
// Step 3: 处理消息循环
loop {
tokio::select! {
// 处理 Prompt 请求
Some(prompt) = prompt_rx.recv() => {
session.send_prompt(&prompt.content)?;
let response = session.read_to_string().await?;
// 通知结果
notifier.notify_prompt_end(&project_id, session_id, StopReason::EndTurn, None, None).await?;
}
// 处理取消请求
Some(cancel) = cancel_rx.recv() => {
cx.send_notification(CancelNotification::new(session_id.clone()))?;
}
// 处理取消信号
_ = cancel_token.cancelled() => {
break;
}
}
}
Ok(())
})
.await
}
}
```
### 2.4 Worker 简化
#### 2.4.1 移除 LocalSet 依赖
```rust
// crates/agent_runner/src/proxy_agent/acp_agent.rs
/// SACP 版本的 Agent Worker
///
/// 关键变化:移除了 LocalSet 和 spawn_blocking
pub async fn agent_worker_sacp(
mut receiver: mpsc::UnboundedReceiver<AgentRequest>,
handle: WorkerHandle,
) {
while let Some(request) = receiver.recv().await {
// 直接使用 tokio::spawn无需 spawn_blocking + LocalSet
let handle = handle.clone();
tokio::spawn(async move {
let result = process_agent_request(request).await;
// 处理结果
});
}
}
/// 处理单个 Agent 请求
///
/// 现在可以直接在标准 Tokio task 中运行
async fn process_agent_request(request: AgentRequest) -> Result<AgentResponse> {
let worker = AcpAgentWorker::new(/* ... */);
// 直接调用,无需 LocalSet
worker.process_request(request.into()).await
}
```
### 2.5 连接信息结构
```rust
// crates/agent_abstraction/src/acp/connection.rs
/// SACP 版本的连接信息
///
/// 与旧版本兼容,但内部使用 SACP
#[derive(Debug)]
pub struct SacpConnectionInfo {
/// 会话 ID
pub session_id: sacp::schema::SessionId,
/// Prompt 发送通道
pub prompt_tx: mpsc::UnboundedSender<PromptRequest>,
/// Cancel 发送通道
pub cancel_tx: mpsc::UnboundedSender<CancelNotification>,
/// 生命周期守卫
pub lifecycle_guard: Arc<AgentLifecycleGuard>,
}
impl SacpConnectionInfo {
/// 发送 Prompt异步
pub async fn send_prompt(&self, request: PromptRequest) -> Result<()> {
self.prompt_tx.send(request)?;
Ok(())
}
/// 发送取消请求
pub async fn send_cancel(&self, notification: CancelNotification) -> Result<()> {
self.cancel_tx.send(notification)?;
Ok(())
}
/// 检查通道是否关闭
pub fn is_closed(&self) -> bool {
self.prompt_tx.is_closed() || self.cancel_tx.is_closed()
}
}
```
## 3. 迁移策略
### 3.1 分阶段迁移
#### 第一阶段:添加 SACP 依赖和适配层
1. 添加 `sacp = "10.1.0"` 依赖
2. 创建 `acp_adapter` crate定义协议无关的抽象
3. 实现 SACP 适配器
4. 保留官方 ACP 实现作为 fallback
#### 第二阶段:重构启动器
1. 创建 `claude_code_sacp.rs`,实现 SACP 版本启动器
2. 通过 feature flag 控制使用哪个实现
3. 测试 SACP 版本的功能完整性
#### 第三阶段:移除 LocalSet 依赖
1. 修改 `acp_agent.rs`,移除 `spawn_blocking` + `LocalSet`
2. 使用标准 `tokio::spawn`
3. 简化 Worker 架构
#### 第四阶段:清理和优化
1. 移除官方 ACP 依赖(可选保留兼容层)
2. 优化连接管理
3. 添加单元测试和集成测试
### 3.2 Feature Flag 设计
```toml
# crates/agent_abstraction/Cargo.toml
[features]
default = ["sacp"]
# SACP 实现(新版本,推荐)
sacp = ["dep:sacp"]
# 官方 ACP 实现(兼容层)
legacy-acp = ["dep:agent-client-protocol"]
[dependencies]
# SACP 依赖(可选)
sacp = { version = "10.1.0", optional = true }
# 官方 ACP 依赖(可选,保留兼容)
agent-client-protocol = { version = "0.9.3", features = ["unstable"], optional = true }
```
### 3.3 运行时切换
```rust
// crates/agent_abstraction/src/launcher/mod.rs
#[cfg(feature = "sacp")]
pub use claude_code_sacp::SacpClaudeCodeLauncher as ClaudeCodeLauncher;
#[cfg(all(feature = "legacy-acp", not(feature = "sacp")))]
pub use claude_code::ClaudeCodeLauncher;
/// 创建默认启动器
pub fn create_launcher<N: SessionNotifier + 'static>(
notifier: Arc<N>,
) -> impl AgentLauncher {
#[cfg(feature = "sacp")]
{
SacpClaudeCodeLauncher::new(notifier)
}
#[cfg(all(feature = "legacy-acp", not(feature = "sacp")))]
{
ClaudeCodeLauncher::new(notifier)
}
}
```
## 4. 关键数据结构对照
### 4.1 消息类型映射
| 官方 ACP | SACP | 说明 |
|---------|------|------|
| `InitializeRequest` | `sacp::schema::InitializeRequest` | 初始化请求 |
| `NewSessionRequest` | `sacp::schema::NewSessionRequest` | 创建会话 |
| `PromptRequest` | `sacp::schema::PromptRequest` | Prompt 请求 |
| `CancelNotification` | `sacp::schema::CancelNotification` | 取消通知 |
| `SessionId` | `sacp::schema::SessionId` | 会话 ID |
| `StopReason` | `sacp::schema::StopReason` | 停止原因 |
### 4.2 连接类型映射
| 官方 ACP | SACP | 说明 |
|---------|------|------|
| `ClientSideConnection` | `ClientToAgent::builder()` | 客户端连接 |
| `AgentSideConnection` | `AgentToClient::builder()` | Agent 连接 |
| N/A | `ByteStreams` | stdio 传输层 |
| N/A | `Channel` | 进程内通道 |
### 4.3 Trait 映射
| 官方 ACP | SACP | 说明 |
|---------|------|------|
| `Client` | `Component<AgentToClient>` | 客户端组件 |
| `Agent` | `Component<ClientToAgent>` | Agent 组件 |
| N/A | `JrRequest` | 请求 trait |
| N/A | `JrNotification` | 通知 trait |
| N/A | `JrResponsePayload` | 响应 trait |
## 5. 注意事项
### 5.1 兼容性
1. **SessionId 类型**SACP 的 `SessionId` 与官方 ACP 基本兼容,但需要适配
2. **Meta 字段**`_meta.claudeCode.options.resume` 等字段格式需要保持一致
3. **MCP 服务器配置**SACP 内置 MCP 支持,配置方式略有不同
### 5.2 错误处理
1. **SACP 错误类型**:使用 `sacp::Error` 替代 `anyhow::Error`
2. **错误码**:确保错误码与现有 gRPC 接口兼容
3. **降级处理**Resume 失败时的降级逻辑需要在新架构中重新实现
### 5.3 性能考量
1. **移除 LocalSet 开销**:预期减少线程切换和上下文开销
2. **连接池**:考虑实现 SACP 连接池,复用连接
3. **消息序列化**SACP 使用 `jsonrpcmsg`,性能与官方 ACP 相当
### 5.4 测试策略
1. **单元测试**:使用 SACP 的 `sacp-test` crate
2. **集成测试**:使用 `Channel::duplex()` 进行进程内测试
3. **E2E 测试**:验证与 Claude Code 子进程的实际通信
## 6. 依赖变更
### 6.1 移除的依赖
```toml
# 移除(或标记为 optional
agent-client-protocol = { version = "0.9.3", features = ["unstable"] }
```
### 6.2 新增的依赖
```toml
# 新增
sacp = "10.1.0"
sacp-tokio = "10.1.0" # 可选:用于 AcpAgent 进程管理
# 间接依赖(通过 sacp
agent-client-protocol-schema = "0.9.3" # Schema 类型定义
jsonrpcmsg = "..." # JSON-RPC 消息
```
### 6.3 本地依赖(开发阶段)
```toml
# 使用 vendors 目录的本地源码(便于调试)
sacp = { path = "../vendors/symposium-acp/src/sacp" }
```
## 7. 时间线(建议)
| 阶段 | 内容 | 风险 |
|-----|------|------|
| 阶段一 | 添加适配层,保持双实现 | 低 |
| 阶段二 | 实现 SACP 启动器 | 中 |
| 阶段三 | 移除 LocalSet简化架构 | 中 |
| 阶段四 | 清理旧代码,完善测试 | 低 |
## 8. 回滚方案
如果迁移过程中遇到严重问题:
1. 通过 feature flag 快速切回官方 ACP 实现
2. 保留 `legacy-acp` feature 作为长期回退选项
3. 使用条件编译隔离新旧代码
```toml
# 回滚:禁用 SACP启用官方 ACP
[features]
default = ["legacy-acp"] # 改为默认使用旧实现
```
## 9. 参考资料
- SACP 官方仓库https://github.com/symposium-dev/symposium-acp
- SACP 文档:`vendors/symposium-acp/md/` 目录
- 官方 ACP 协议https://agentclientprotocol.com/
- 项目本地源码:`vendors/symposium-acp/src/sacp/`

View File

@@ -0,0 +1,5 @@
# Instractions
## Project Alpha
我现在使用的官方版本: agent-client-protocol = { version = "0.9.3", features = ["unstable"] } , 目前官方acp协议有个问题,agent端,只能在 LocalSet 下运行,因为没实现 Send trait ,我现在发现一个基于官方acp协议重新封装的acp库: https://github.com/symposium-dev/symposium-acp ,对acp协议封装的更好,我想把acp协议从官方的,切换到 symposium-acp 来使用,对应最新版本是: sacp = "10.1.0" , 需要更新 crates/agent_runner , crates/agent_abstraction 等模块