14.1 Host Service 概述
`rust-agent-host` 是基于 ACP(Agent Client Protocol)的多 Agent 托管服务器,为 RAF 框架提供远程服务能力。它将 RAF Agent 桥接到 ACP 兼容客户端(如 GPUI 前端),支持多 Agent 注册、发现、流式输出和会话管理。
总体架构
graph TB
subgraph "客户端层"
GPUI[GPUI 前端]
CLI[CLI 客户端]
API[API 客户端]
end
subgraph "传输层"
STDIO[Stdio Transport]
WS[WebSocket Transport]
end
subgraph "协议层"
ACP[ACP Agent Client Protocol]
JRPC[JSON-RPC 2.0]
end
subgraph "宿主层"
HOST[RafAgentHost]
REG[AgentRegistry]
SB[SessionBridge]
end
subgraph "RAF 层"
AGENT1[CodingAgent]
AGENT2[GeneralAgent]
AGENT3[DeclarativeAgent]
AGENTN[...]
end
GPUI --> STDIO
GPUI --> WS
CLI --> WS
API --> WS
STDIO --> ACP
WS --> ACP
ACP --> JRPC
JRPC --> HOST
HOST --> REG
HOST --> SB
REG --> AGENT1
REG --> AGENT2
REG --> AGENT3
REG --> AGENTN
核心组件
RafAgentHost — 宿主服务核心
`RafAgentHost` 是 ACP Agent 处理器,负责所有 ACP 请求的处理:
pub struct RafAgentHost {
pub registry: Arc<AgentRegistry>,
pub session_bridge: Arc<SessionBridge>,
}
处理四种 ACP 操作:
| 操作 | 说明 | |------|------| | `initialize` | 返回 Agent 能力列表和注册 Agent 清单 | | `session/new` | 创建新会话,可指定目标 Agent | | `session/prompt` | 处理用户提示,执行 Agent 并流式返回 | | `session/cancel` | 取消当前运行 |
Run 方法
impl RafAgentHost {
pub async fn run(
self,
transport: impl ConnectTo<Agent>,
) -> Result<()> {
let acp_agent = Agent;
acp_agent.builder()
.name("rust-agent-host")
// 注册四个处理器
.on_receive_request::<InitializeRequest>(/* ... */)
.on_receive_request::<NewSessionRequest>(/* ... */)
.on_receive_request::<PromptRequest>(/* ... */)
.on_receive_notification::<CancelNotification>(/* ... */)
.connect_to(transport)
.await
}
}
关键特性
多 Agent 托管
单个 Host 进程可以同时托管多个 Agent:
- **内置 Agent**:通过 `AgentFactory` 创建 CodingAgent、GeneralAgent、AnalysisAgent - **声明式 Agent**:通过 `AgentDocument` 从 JSON/YAML/TOML 文件加载 - **自定义 Agent**:通过 `AgentRegistry::register()` 注册任意 IAgent 实现
标签化流式输出
每个 `session/update` 携带 `_meta.raf.agent_id`,使客户端可以区分多 Agent 对话中的发言者:
{
"type": "session/update",
"content": "这是代码专家的回复...",
"_meta": {
"raf": {
"agent_id": "coding",
"agent_type": "CodingAgent"
}
}
}
子 Agent 发现
通过 `get_subagent()` 递归发现 Agent 树: - `_raf/agent_list` — 返回所有注册 Agent 的扁平列表 - `_raf/subagent_list` — 返回指定 Agent 的直接子 Agent - `_raf/subagent_tree` — 返回完整的递归 Agent 树
双传输模式
支持两种传输协议,可通过配置文件切换:
| 模式 | 适用场景 | |------|---------| | Stdio | 本地子进程(IDE 集成) | | WebSocket | 远程部署(服务化) |
配置系统
分层配置
使用 Figment 实现四层配置优先级:
pub fn load_config() -> anyhow::Result<HostConfig> {
// Layer 1: TOML 配置文件 (host.toml 或 --config)
// Layer 2: 环境变量 (RAF_ 前缀)
// Layer 3: CLI 参数
// Layer 4: 默认值
Figment::new()
.merge(Toml::file("host.toml"))
.merge(Env::prefixed("RAF_"))
.merge(Serialized::defaults(&cli_args))
.extract()
}
配置结构
pub struct HostConfig {
pub mode: TransportMode, // stdio 或 ws
pub ws_bind: String, // WebSocket 绑定地址
pub provider: ProviderConfig, // LLM 提供商配置
pub agents: AgentPresetsConfig, // 内置 Agent 开关
pub agents_dir: Option<String>, // 声明式 Agent 目录
}
CLI 参数
# WebSocket 模式
rust-agent-host --mode ws --bind 0.0.0.0:9876 --api-key $AGNES_API_KEY
# Stdio 模式(默认)
rust-agent-host --api-key $AGNES_API_KEY
# 加载声明式 Agent
rust-agent-host --agents-dir ./agents
# 指定配置文件
rust-agent-host --config production.toml
快速启动
use rust_agent_host::{HostConfig, load_config, transport};
use rust_agent_host::registry::AgentRegistry;
use rust_agent_host::bridge::SessionBridge;
use rust_agent_host::agents::AgentFactory;
use std::sync::Arc;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// 1. 加载配置
let config = load_config()?;
// 2. 创建 Agent 注册表
let mut registry = AgentRegistry::new();
// 3. 创建内置 Agent
let factory = AgentFactory::new(&config);
let builtin_agents = factory.create_all().await?;
for agent in builtin_agents {
registry.register(agent);
}
// 4. 加载声明式 Agent
if let Some(ref dir) = config.agents_dir {
let decl_agents = load_declarative_agents(dir).await?;
for agent in decl_agents {
registry.register(agent);
}
}
// 5. 创建会话桥梁
let registry = Arc::new(registry);
let session_bridge = Arc::new(SessionBridge::new());
// 6. 启动传输
match config.mode {
TransportMode::Stdio => {
transport::stdio::run_stdio(registry, session_bridge).await?;
}
TransportMode::Ws => {
transport::websocket::run_ws_server(
config.ws_bind,
registry,
session_bridge,
).await?;
}
}
Ok(())
}