14.3 传输层(Stdio / WebSocket)

RAF 宿主服务支持两种传输模式:Stdio(标准输入输出,用于本地子进程通信)和 WebSocket(用于远程网络通信)。

传输架构

graph LR
    subgraph "传输选择"
        CONFIG[HostConfig.mode]
    end

    subgraph "Stdio 模式"
        SI[stdin]
        SO[stdout]
        ST[agent_client_protocol::Stdio]
    end

    subgraph "WebSocket 模式"
        AX[axum HTTP Server]
        WS[WebSocket /acp]
        BST[ByteStreams Transport]
    end

    CONFIG -->|stdio| ST
    CONFIG -->|ws| AX
    ST --> SI
    ST --> SO
    AX --> WS
    WS --> BST

Stdio 传输

Stdio 模式是默认传输方式,适用于本地子进程场景(如 IDE 插件启动 Agent 进程并与之通信)。

实现

pub async fn run_stdio(
    registry: Arc<AgentRegistry>,
    session_bridge: Arc<SessionBridge>,
) -> Result<()> {
    info!("Starting ACP server in Stdio mode");

    let host = RafAgentHost {
        registry,
        session_bridge,
    };

    // 使用 ACP SDK 内置的 Stdio 传输
    let transport = agent_client_protocol::Stdio::new();

    host.run(transport).await?;

    Ok(())
}

通信格式

Stdio 传输使用行分隔的 JSON(Line-Delimited JSON):

- **stdin**:每行一个 JSON-RPC 2.0 消息 - **stdout**:每行一个 JSON-RPC 2.0 消息或 `session/update` 通知

→ {"jsonrpc":"2.0","method":"initialize","params":{...}}
← {"jsonrpc":"2.0","result":{...}}
→ {"jsonrpc":"2.0","method":"session/new","params":{...}}
← {"jsonrpc":"2.0","result":{"session_id":"..."}}
→ {"jsonrpc":"2.0","method":"session/prompt","params":{...}}
← {"jsonrpc":"2.0","method":"session/update","params":{"content":[...]}}
...

使用方式

# 启动 Stdio 模式(默认)
rust-agent-host --api-key $AGNES_API_KEY

# 或显式指定
rust-agent-host --mode stdio --api-key $DEEPSEEK_API_KEY

# 客户端通过子进程通信
# IDE 插件: spawn("rust-agent-host", ["--api-key", key])

WebSocket 传输

WebSocket 模式适用于远程部署,通过 axum HTTP 框架提供 WebSocket 升级端点。

实现

pub async fn run_ws_server(
    bind_addr: String,
    registry: Arc<AgentRegistry>,
    session_bridge: Arc<SessionBridge>,
) -> Result<()> {
    info!(addr = %bind_addr, "Starting ACP WebSocket server");

    let app = Router::new()
        .route("/acp", any(ws_handler))   // WebSocket 升级端点
        .with_state(WsState {
            registry,
            session_bridge,
        });

    let listener = tokio::net::TcpListener::bind(&bind_addr).await?;
    axum::serve(listener, app).await?;

    Ok(())
}

WebSocket 连接处理

每个 WebSocket 连接的处理流程:

async fn handle_socket(socket: WebSocket, state: WsState) {
    // 1. 分离 WebSocket 为 sender/receiver
    let (_ws_sender, mut ws_receiver) = socket.split();

    // 2. 创建双工通道 (64KB 缓冲区)
    let (dup_a, mut dup_b) = tokio::io::duplex(64 * 1024);

    // 3. 创建 ACP ByteStreams 传输
    let (reader, writer) = tokio::io::split(dup_a);
    let transport = ByteStreams::new(writer.compat_write(), reader.compat());

    // 4. 启动 ACP 处理器
    let host = RafAgentHost {
        registry: state.registry.clone(),
        session_bridge: state.session_bridge.clone(),
    };
    let acp_handle = tokio::spawn(async move {
        host.run(transport).await
    });

    // 5. 桥接 WebSocket ↔ ACP
    // WebSocket Text → dup_b (write)
    // dup_b → ACP 处理器消费

    // 6. 等待任一端完成
    tokio::select! {
        res = acp_handle => { /* ... */ }
        res = ws_to_acp => { /* ... */ }
    }
}

WebSocket 消息格式

与 Stdio 相同,使用行分隔的 JSON:

- **Text 消息**:每行一个 JSON-RPC 2.0 消息 - **Binary 消息**:每行一个 JSON-RPC 2.0 消息 - **Ping/Pong**:自动处理保持连接活跃 - **Close**:正常关闭连接

使用方式

# 启动 WebSocket 模式
rust-agent-host --mode ws --bind 0.0.0.0:9876 --api-key $DEEPSEEK_API_KEY

# 客户端连接
# ws://localhost:9876/acp

配置切换

通过 CLI

# Stdio
rust-agent-host --mode stdio

# WebSocket
rust-agent-host --mode ws --bind 127.0.0.1:9876

通过配置文件 (host.toml)

# Stdio 模式
mode = "stdio"

# WebSocket 模式
mode = "ws"
ws_bind = "0.0.0.0:9876"

[provider]
provider = "openai"
model = "agnes-2.0-flash"
api_key = "$AGNES_API_KEY"

[agents]
coding = true
general = true
analysis = true

通过环境变量

export RAF_MODE=ws
export RAF_WS_BIND=0.0.0.0:9876
export RAF_PROVIDER__PROVIDER=openai
export RAF_PROVIDER__MODEL=agnes-2.0-flash

传输对比

| 特性 | Stdio | WebSocket | |------|-------|-----------| | 适用场景 | 本地 IDE 集成 | 远程服务部署 | | 连接方式 | 父子进程 stdin/stdout | TCP WebSocket | | 并发客户端 | 单客户端 | 多客户端 | | 部署模式 | 进程内 | 独立服务 | | 安全隔离 | 操作系统进程隔离 | 需要 TLS + 认证 | | 默认端口 | N/A | 9876 | | 状态管理 | 进程生命周期 | 连接生命周期 |

多客户端处理

WebSocket 模式下,每个客户端连接拥有独立的: - ACP session(通过 `SessionBridge` 隔离) - 取消令牌 - 流式输出通道

// 每个 WebSocket 连接创建独立的 RafAgentHost
let host = RafAgentHost {
    registry: state.registry.clone(),       // 共享 AgentRegistry
    session_bridge: state.session_bridge.clone(), // 共享 SessionBridge
};

`AgentRegistry` 和 `SessionBridge` 在多个连接间共享(通过 `Arc`),确保 Agent 状态的一致性和跨连接会话管理。