# rcoder ## 项目概述 基于 ACP (Agent Protocol) 协议的 AI 代理开发平台,提供 Docker 容器化运行时和反向代理。本项目的核心模块 `agent_runner` 提供了完整的 Agent 生命周期管理能力,包括 Docker 容器管理、消息收发、超时自动销毁等功能。 **本地路径**: `vendors/rcoder` ## 目录结构 ``` rcoder/ ├── Cargo.toml # workspace 配置 ├── crates/ │ ├── rcoder/ # 主应用服务 │ │ ├── src/ │ │ │ ├── lib.rs │ │ │ ├── main.rs │ │ │ ├── app.rs # 应用主逻辑 │ │ │ ├── router.rs # HTTP 路由 │ │ │ ├── state.rs # 应用状态 │ │ │ ├── error.rs # 错误处理 │ │ │ └── middlewares/ # 中间件 │ │ └── Cargo.toml │ │ │ ├── agent_runner/ # Agent 运行时核心(最重要) │ │ ├── src/ │ │ │ ├── lib.rs │ │ │ ├── manager.rs # AgentWorkerManager │ │ │ ├── worker.rs # Agent Worker │ │ │ ├── adapter.rs # ACP 协议适配器 │ │ │ ├── types.rs # 类型定义 │ │ │ ├── message.rs # 消息处理 │ │ │ └── docker.rs # Docker 集成 │ │ └── Cargo.toml │ │ │ ├── docker_manager/ # Docker 容器管理 │ │ ├── src/ │ │ │ ├── lib.rs │ │ │ ├── manager.rs # DockerManager │ │ │ ├── actor.rs # Actor 模式状态 │ │ │ ├── container.rs # 容器操作 │ │ │ └── network.rs # 网络管理 │ │ └── Cargo.toml │ │ │ ├── acp_adapter/ # ACP 协议适配器 │ │ ├── src/ │ │ │ ├── lib.rs │ │ │ ├── types.rs # ACP 类型定义 │ │ │ ├── client.rs # ACP 客户端 │ │ │ └── handler.rs # 消息处理 │ │ └── Cargo.toml │ │ │ ├── shared_types/ # 共享类型定义 │ │ ├── src/ │ │ │ ├── lib.rs │ │ │ ├── proto/ # gRPC proto 文件 │ │ │ │ └── agent.proto │ │ │ └── types.rs │ │ └── Cargo.toml │ │ │ └── rcoder-proxy/ # 反向代理 │ ├── src/ │ │ ├── lib.rs │ │ ├── proxy.rs │ │ └── upstream.rs │ └── Cargo.toml └── Cargo.lock ``` ## Agent Worker Manager (agent_runner) 这是 rcoder 的核心模块,负责管理 agent 的生命周期。 ### 核心结构体 ```rust // crates/agent_runner/src/manager.rs use tokio::sync::{mpsc, watch}; use std::sync::{Arc, Mutex}; use chrono::DateTime; use dashmap::DashMap; pub struct AgentWorkerManager { // 任务发送器(使用 ArcSwap 支持热更新) sender: ArcSwap>>, // 状态广播 state_tx: watch::Sender, // 最后心跳时间 last_heartbeat: Arc>>>, // 状态变化时间 last_state_change: Arc>>, // 活跃请求追踪 active_requests: Arc>>, // 配置 config: AgentRunnerConfig, } // Worker 状态 pub enum WorkerState { Starting, Running, Stopping, Stopped, } impl Default for WorkerState { fn default() -> Self { WorkerState::Stopped } } ``` ### 心跳超时检测 ```rust // crates/agent_runner/src/manager.rs impl AgentWorkerManager { /// 检查心跳是否超时 pub fn check_heartbeat_timeout(&self) -> bool { let last_heartbeat_opt = { let last = self.last_heartbeat.lock().expect("mutex poisoned"); *last // Copy 数据,立即释放锁 }; if let Some(timestamp) = last_heartbeat_opt { // 正常运行时:15秒无心跳视为超时 let elapsed = Utc::now() - timestamp; elapsed.num_seconds() > 15 } else { // 首次启动:30秒内未收到心跳视为超时 let state_change = self.last_state_change.lock().expect("mutex poisoned"); let elapsed = Utc::now() - *state_change; elapsed.num_seconds() > 30 } } /// 更新心跳时间 pub fn update_heartbeat(&self) { let mut last = self.last_heartbeat.lock().expect("mutex poisoned"); *last = Some(Utc::now()); } } ``` ### Agent Worker 核心逻辑 ```rust // crates/agent_runner/src/worker.rs pub struct AgentWorker { config: AgentRunnerConfig, receiver: mpsc::UnboundedReceiver, state_tx: watch::Sender, docker: DockerManager, adapter: ACPAdapter, } impl AgentWorker { pub async fn run(&mut self) { tracing::info!("AgentWorker started"); self.state_tx.send(WorkerState::Running).unwrap(); loop { tokio::select! { Some(request) = self.receiver.recv() => { self.handle_request(request).await; } _ = self.docker.health_check() => { // Docker 健康检查 } } } } async fn handle_request(&mut self, request: LocalSetAgentRequest) { match request { LocalSetAgentRequest::Message { session_id, content } => { self.handle_message(&session_id, &content).await; } LocalSetAgentRequest::Stop => { self.shutdown().await; return; } LocalSetAgentRequest::Spawn { config } => { self.spawn_container(&config).await; } } } /// 启动 Docker 容器 async fn spawn_container(&mut self, config: &AgentConfig) -> Result<(), AgentError> { // 1. 创建容器 let container_id = self.docker.create_container(CreateContainerOptions { image: config.image.clone(), env: config.env_vars.clone(), Cmd: config.command.clone().unwrap_or_else(|| vec!["/bin/sh"]), ..Default::default() }).await?; // 2. 启动容器 self.docker.start_container(&container_id).await?; // 3. 等待容器就绪 self.docker.wait_until_ready(&container_id).await?; // 4. 建立 ACP 连接 self.adapter.connect(&container_id, config.acp_url).await?; Ok(()) } } ``` ### ACP 协议消息类型 ```rust // crates/agent_runner/src/types.rs use agent_client_protocol::{Message, MessageChunk}; // ACP 流更新事件 pub enum StreamUpdate { UserMessageChunk { session_id: String, content: String, }, AgentMessageChunk { session_id: String, content: String, }, ToolCall { session_id: String, tool_call: ToolCall, }, ToolCallUpdate { session_id: String, tool_call_update: ToolCallUpdate, }, SessionStateChanged { session_id: String, new_state: SessionState, message: String, }, Plan { session_id: String, plan: Plan, }, StepFinished { session_id: String, step_id: String, }, SessionFinished { session_id: String, reason: String, }, } pub enum SessionState { Initializing, Connected, Prompting, Paused, Closed, Error(String), } pub struct Plan { pub entries: Vec, pub created_at: SystemTime, pub status: PlanStatus, } pub enum PlanStatus { Pending, Approved, Executing, Finished, Cancelled, } pub struct ToolCall { pub id: String, pub name: String, pub arguments: serde_json::Value, } pub struct ToolCallUpdate { pub id: String, pub status: ToolCallStatus, pub result: Option, pub error: Option, } pub enum ToolCallStatus { Pending, Running, Completed, Failed, Cancelled, } ``` ## Docker Manager (docker_manager) Docker 容器管理模块,使用 Actor 模式管理容器状态,避免并发问题。 ### 核心结构体 ```rust // crates/docker_manager/src/manager.rs use bollard::{Docker, APIError}; use std::sync::Arc; use tokio::sync::RwLock; pub struct DockerManager { docker: Docker, // Docker 客户端 config: DockerManagerConfig, // 配置 containers: ContainerStateHandle, // Actor 模式状态 main_network_name: Arc>, // 网络名称 } pub struct DockerManagerConfig { pub socket_path: Option, // Docker socket 路径 pub network_name: String, // 网络名称 pub max_containers: usize, // 最大容器数 pub container_ttl: Duration, // 容器存活时间 pub auto_remove: bool, // 是否自动删除 } ``` ### Actor 模式状态管理 ```rust // crates/docker_manager/src/container_state_actor.rs // 使用 Actor 模式避免 DashMap 跨 await 持有锁导致的死锁 pub struct ContainerStateActor { containers: HashMap, receiver: mpsc::Receiver, } pub enum ContainerStateCommand { Get { key: String, reply: oneshot::Sender> }, Insert { key: String, info: DockerContainerInfo }, Remove { key: String, reply: oneshot::Sender> }, List { reply: oneshot::Sender> }, Keys { reply: oneshot::Sender> }, RemoveIfContainerId { key: String, container_id: String, reply: oneshot::Sender> }, } impl ContainerStateActor { pub async fn run(&mut self) { while let Some(cmd) = self.receiver.recv().await { match cmd { ContainerStateCommand::Get { key, reply } => { let info = self.containers.get(&key).cloned(); reply.send(info).ok(); } ContainerStateCommand::Insert { key, info } => { self.containers.insert(key, info); } // ... 其他命令处理 } } } } ``` ### 容器创建流程 ```rust // crates/docker_manager/src/manager.rs impl DockerManager { pub async fn create_container( &self, config: DockerContainerConfig, ) -> DockerResult { let container_name = format!("{}-{}", self.config.network_name, config.name); // 1. 清理同名旧容器 if let Ok(Some((existing_id, _, status, is_running))) = self.find_container_realtime(&container_name).await { if is_running { self.stop_container_by_id(&existing_id).await?; } self.remove_container_by_id(&existing_id).await?; } // 2. 拉取镜像 self.ensure_image_exists(&config.image).await?; // 3. 构建挂载点 let mut mounts = Vec::new(); if !config.host_path.is_empty() { mounts.push(Mount { target: Some(config.container_path.clone()), source: Some(config.host_path.clone()), typ: Some(MountTypeEnum::BIND), read_only: Some(false), ..Default::default() }); } // 4. 构建环境变量 let mut env_vars: Vec = config.env_vars .into_iter() .map(|(k, v)| format!("{}={}", k, v)) .collect(); // 5. 构建网络配置 let (networking_config, container_network_name) = if config.network_mode != "host" { let main_network = self.get_main_network_name().await; let mut endpoints = HashMap::new(); endpoints.insert(main_network.clone(), EndpointSettings { aliases: Some(vec![container_name.clone()]), ..Default::default() }); (Some(NetworkingConfig { endpoints_config: Some(endpoints) }), main_network) } else { (None, "host".to_string()) }; // 6. 创建并启动容器 let container_config = ContainerConfig { image: config.image.clone(), env: Some(env_vars), cmd: config.command.map(|c| vec![c]), host_config: Some(HostConfig { mounts: Some(mounts), network_mode: Some(container_network_name), auto_remove: Some(self.config.auto_remove), ..Default::default() }), ..Default::default() }; let response = self.docker.create_container( Some(CreateContainerOptions { name: container_name.clone(), ..Default::default() }), container_config, ).await?; self.docker.start_container(&response.id, None::).await?; // 7. 健康检查 self.check_container_health(&response.id).await?; // 8. 获取容器信息 let inspect = self.docker.inspect_container(&response.id, None).await?; Ok(DockerContainerInfo { id: response.id, name: container_name, image: config.image, status: "running".to_string(), ip_address: inspect.network_settings?.ip_address, created_at: Utc::now(), }) } /// 等待容器就绪 pub async fn wait_until_ready(&self, container_id: &str) -> Result<(), DockerError> { let timeout = Duration::from_secs(60); let start_time = Utc::now(); loop { // 检查容器状态 let info = self.inspect_container(container_id).await?; if info.state.as_ref().map(|s| &s.status) == Some(&ContainerStateStatusEnum::RUNNING) { // 检查健康检查 if let Some(health) = &info.state.as_ref().unwrap().healthcheck { if health.status == ContainerHealthStatusEnum::HEALTHY { return Ok(()); } } else { return Ok(()); } } // 超时检查 if (Utc::now() - start_time).num_seconds() > timeout.as_secs() as i64 { return Err(DockerError::Timeout); } tokio::time::sleep(Duration::from_millis(500)).await; } } } ``` ## 超时自动销毁机制 ```rust // crates/agent_runner/src/proxy_agent/cleanup_task.rs pub struct CleanupConfig { pub idle_timeout: Duration, // 闲置超时(默认3分钟) pub cleanup_interval: Duration, // 清理间隔(默认30秒) } impl Default for CleanupConfig { fn default() -> Self { Self { idle_timeout: Duration::from_secs(3 * 60), // 3分钟 cleanup_interval: Duration::from_secs(30), // 30秒 } } } // 清理逻辑:基于 RAII 模式 async fn cleanup_idle_agents(&mut self) -> Result { for entry in AGENT_REGISTRY.iter_agents() { let agent_info = entry.value(); // 只清理 Idle 状态的 agent if agent_info.status == AgentStatus::Idle && self.is_agent_idle_timeout(agent_info.last_activity, current_time) { // 从 Registry 中移除,AgentLifecycleGuard 自动清理资源 AGENT_REGISTRY.remove_by_project(project_id); } } } ``` ## 环境变量和 PATH 管理 ```rust // crates/agent_runner/src/docker.rs pub fn build_environment( node_path: &Path, npm_global_path: &Path, extra_env: &HashMap, ) -> Vec { let mut env = vec![ // Node.js 环境 format!("PATH={}:{}:{}", node_path.join("bin").to_string_lossy(), npm_global_path.join("bin").to_string_lossy(), std::env::var("PATH").unwrap_or_default() ), format!("NODE_PATH={}", node_path.join("lib").to_string_lossy()), format!("NPM_CONFIG_PREFIX={}", npm_global_path.to_string_lossy()), ]; // 添加额外的环境变量 for (k, v) in extra_env { env.push(format!("{}={}", k, v)); } env } // 在容器启动时设置环境变量 pub async fn start_container_with_env( &self, config: &AgentConfig, ) -> Result { let env = build_environment( &config.node_path, &config.npm_global_path, &config.extra_env, ); let container_id = self.docker.create_container(CreateContainerOptions { image: config.image.clone(), env: Some(env), Cmd: config.command.clone().unwrap_or_default(), ..Default::default() }).await?; self.docker.start_container(&container_id, None).await?; Ok(container_id) } ``` ## Node.js 自动安装 ```rust // crates/agent_runner/src/installer/node_installer.rs pub struct NodeInstaller { install_dir: PathBuf, platform: Platform, } pub enum Platform { Windows, MacOSIntel, MacOSAppleSilicon, Linux, } impl NodeInstaller { /// 检测系统是否已有 Node.js pub async fn detect_system_node(&self) -> Option { // 检查系统 PATH if let Ok(path) = which("node") { if let Ok(output) = Command::new(&path) .arg("--version") .output() .await { if output.status.success() { let version_str = String::from_utf8_lossy(&output.stdout); return Version::parse(&version_str.trim_start_matches('v')).ok(); } } } None } /// 下载并安装 Node.js pub async fn download_and_install(&self, version: &Version) -> Result<(), InstallationError> { let url = self.get_download_url(version); let archive_path = self.install_dir.join("cache").join(format!("node-{}.tar.xz", version)); // 1. 下载 if !archive_path.exists() { self.download(&url, &archive_path).await?; } // 2. 解压 let extract_dir = self.install_dir.join("node").join(version.to_string()); self.extract(&archive_path, &extract_dir).await?; // 3. 创建 symlink let bin_path = extract_dir.join("bin"); let symlink_path = self.install_dir.join("bin").join("node"); if symlink_path.exists() { std::fs::remove_file(&symlink_path)?; } std::os::unix::fs::symlink(&bin_path.join("node"), &symlink_path)?; Ok(()) } fn get_download_url(&self, version: &Version) -> String { match self.platform { Platform::Windows => format!( "https://nodejs.org/dist/v{}/node-v{}-win-x64.zip", version, version ), Platform::MacOSIntel => format!( "https://nodejs.org/dist/v{}/node-v{}-darwin-x64.tar.gz", version, version ), Platform::MacOSAppleSilicon => format!( "https://nodejs.org/dist/v{}/node-v{}-darwin-arm64.tar.gz", version, version ), Platform::Linux => format!( "https://nodejs.org/dist/v{}/node-v{}-linux-x64.tar.xz", version, version ), } } } ``` ## ACP 协议适配器 ```rust // crates/acp_adapter/src/client.rs use agent_client_protocol::{Client, Message as AcpMessage}; pub struct ACPAdapter { client: Option>>, session_id: String, pending_requests: Arc>>>, } impl ACPAdapter { /// 连接到 Agent 容器 pub async fn connect( &mut self, container_id: &str, url: &str, ) -> Result<(), AdapterError> { // 获取容器 IP let container_ip = self.get_container_ip(container_id).await?; // 建立 WebSocket 连接 let stream = WebSocket::connect(url).await?; let (ws_stream, _) = stream.split(); let (write, read) = ws_stream.split(); // 创建 ACP 客户端 self.client = Some(Client::new(read, write)); // 生成会话 ID self.session_id = uuid::Uuid::new_v4().to_string(); Ok(()) } /// 发送消息 pub async fn send_message(&self, session_id: &str, content: &str) { if let Some(client) = &self.client { let message = AcpMessage::user_message_chunk(session_id, content); client.send(message).await; } } /// 接收消息 pub async fn receive(&self) -> Option { if let Some(client) = &self.client { if let Some(msg) = client.recv().await { Some(self.convert_message(msg)) } else { None } } else { None } } } ``` ## 在本项目中的使用 ### Agent 依赖管理界面 ```rust // agent-client/src/ui/dependency_management.rs use crate::agent::AgentManager; pub struct DependencyManager { agent_manager: Arc, node_installer: Arc, } impl DependencyManager { /// 检查所有依赖状态 pub async fn check_all_dependencies(&self) -> Vec { let mut statuses = vec![]; // 检查 Node.js statuses.push(self.check_node().await); // 检查 npm statuses.push(self.check_npm().await); // 检查 opencode statuses.push(self.check_agent("opencode").await); // 检查 claude-code statuses.push(self.check_agent("@anthropic-ai/claude-code").await); statuses } async fn check_node(&self) -> DependencyStatus { // 1. 先检查系统全局 if let Some(version) = self.node_installer.detect_system_node().await { return DependencyStatus { name: "Node.js", version: Some(version.to_string()), source: DependencySource::System, status: DependencyStatus::Installed, action: None, }; } // 2. 检查客户端目录 if let Some(version) = self.detect_local_node().await { return DependencyStatus { name: "Node.js", version: Some(version.to_string()), source: DependencySource::Local, status: DependencyStatus::Installed, action: Some(DependencyAction::Update), }; } // 3. 未安装 DependencyStatus { name: "Node.js", version: None, source: DependencySource::None, status: DependencyStatus::NotInstalled, action: Some(DependencyAction::Install), } } } pub enum DependencySource { System, Local, None, } pub enum DependencyStatus { Installed, Installing, Failed, NotInstalled, NeedsUpdate, } ``` ### 安装目录结构 ``` / # 客户端应用数据目录 ├── tools/ │ ├── node/ # Node.js 安装目录 │ │ ├── v20.10.0/ │ │ │ ├── bin/ │ │ │ │ ├── node │ │ │ │ └── npm │ │ │ └── lib/ │ │ └── current -> v20.10.0 # 当前版本 symlink │ ├── npm-global/ # npm 全局安装目录(隔离) │ │ ├── bin/ │ │ │ ├── opencode │ │ │ └── claude │ │ └── lib/ │ └── versions.json # 已安装工具的版本记录 ├── config/ # 配置文件 ├── logs/ # 日志文件 └── cache/ # 缓存文件(下载的安装包等) ``` ## 关键设计模式 1. **Actor 模式**: DockerManager 使用 Actor 模式管理容器状态,避免死锁 2. **ArcSwap**: 热更新配置而不需要重启 3. **DashMap**: 高并发场景下的线程安全 HashMap 4. **Watch 通道**: 状态变更广播 5. **RAII 清理**: Agent 闲置超时自动销毁 6. **隔离安装**: Node.js 和 Agent 工具安装到独立目录,不污染全局环境 ## 可复用代码 | 模块 | 路径 | 用途 | |------|------|------| | **agent_runner** | `crates/agent_runner/` | Agent 生命周期管理 | | **docker_manager** | `crates/docker_manager/` | Docker 容器操作 | | **acp_adapter** | `crates/acp_adapter/` | ACP 协议客户端 | ## 与本项目的集成 ``` agent-client │ ├── UI 层 │ ├── 依赖管理界面 (DataTable, Badge, Button) │ ├── 安装进度弹窗 (Dialog, Progress) │ └── 设置界面 (Form, Switch, Input) │ └── Agent 运行时 ├── rcoder/agent_runner # Agent 生命周期管理 │ │ │ ├── Docker Manager # 容器管理 │ │ └── opencode 容器 │ │ │ └── ACP Adapter # ACP 协议通信 │ │ │ └── opencode ACP 端口 │ └── Node.js 安装器 # 环境隔离安装 ```