Skip to main content

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}