acp_agent/runtime/serve.rs
1use anyhow::{Context, Result};
2use clap::ValueEnum;
3use std::process::ExitStatus;
4
5use crate::registry::fetch_registry;
6use crate::runtime::prepare::prepare_agent_command;
7use crate::runtime::transports::{h2, tcp, ws};
8
9/// Indicates which transport protocol will expose the agent's STDIO streams.
10///
11/// * `Http` stands for the HTTP/2 full-duplex stream transport exposed by `h2`.
12/// * `Tcp` exposes raw bytes over a single TCP connection (no framing or RPC).
13/// * `Ws` publishes a JSON-RPC over WebSocket wrapper used by the existing client.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
15pub enum ServeTransport {
16 /// HTTP/2 stream transport implemented by [`crate::runtime::transports::h2`].
17 Http,
18 /// Raw TCP byte-stream transport implemented by [`crate::runtime::transports::tcp`].
19 Tcp,
20 /// WebSocket + JSON-RPC transport implemented by [`crate::runtime::transports::ws`].
21 Ws,
22}
23
24/// Runtime options shared by all transport implementations.
25///
26/// `host`/`port` determine the bound listener address, while `transport` picks the
27/// serialization/multiplexing layer.
28#[derive(Debug, Clone, PartialEq, Eq)]
29pub struct ServeOptions {
30 /// Transport protocol that will expose the child process.
31 pub transport: ServeTransport,
32 /// Listener host or IP address to bind.
33 pub host: String,
34 /// Listener port. `0` lets the OS choose an ephemeral port.
35 pub port: u16,
36}
37
38/// Serves an ACP agent via the chosen transport so external clients can interact.
39///
40/// Fetches the registry entry, prepares the agent command (downloading binaries if
41/// needed), and then dispatches to the transport module that knows how to wire
42/// stdio across TCP, HTTP/2, or WebSocket.
43pub async fn serve_agent(
44 agent_id: &str,
45 options: ServeOptions,
46 user_args: &[String],
47) -> Result<ExitStatus> {
48 let registry = fetch_registry().await?;
49 let agent = registry
50 .get_agent(agent_id)
51 .with_context(|| format!("failed to resolve agent \"{agent_id}\" from registry"))?;
52
53 let prepared = prepare_agent_command(agent, user_args).await?;
54
55 match options.transport {
56 ServeTransport::Http => {
57 h2::serve_h2(prepared, &agent.id, &options.host, options.port).await
58 }
59 ServeTransport::Tcp => {
60 tcp::serve_tcp(prepared, &agent.id, &options.host, options.port).await
61 }
62 ServeTransport::Ws => ws::serve_ws(prepared, &agent.id, &options.host, options.port).await,
63 }
64}