Skip to main content

zeph_acp/transport/
mod.rs

1// SPDX-FileCopyrightText: 2026 Andrei G <bug-ops>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! ACP transport layer — stdio, HTTP+SSE, and WebSocket.
5//!
6//! Each transport variant drives the same [`ZephAcpAgentState`] via the `agent-client-protocol`
7//! JSON-RPC connection. The agent state is `Send + Sync`, so connections run on the standard
8//! multi-thread tokio runtime.
9//!
10//! # Entry points
11//!
12//! | Transport | Function/Type |
13//! |-----------|---------------|
14//! | stdio | [`serve_stdio`], [`serve_connection`] |
15//! | HTTP + SSE | [`acp_router`] (feature `acp-http`) |
16//! | WebSocket | part of [`acp_router`] (feature `acp-http`) |
17//!
18//! [`ZephAcpAgentState`]: crate::agent::ZephAcpAgentState
19
20#[cfg(feature = "acp-http")]
21pub mod auth;
22#[cfg(feature = "acp-http")]
23pub mod discovery;
24pub mod http;
25pub mod router;
26pub mod stdio;
27pub mod ws;
28
29#[cfg(test)]
30mod tests;
31
32pub use stdio::{serve_connection, serve_stdio};
33
34#[cfg(feature = "acp-http")]
35pub use http::AcpHttpState;
36#[cfg(feature = "acp-http")]
37pub use router::acp_router;
38
39/// Startup readiness notification sent as the first stdio JSON-RPC frame.
40///
41/// When set in [`AcpServerConfig::ready_notification`], `serve_stdio` / `serve_connection`
42/// emit a `zeph/ready` JSON-RPC notification **before** the ACP handshake so that
43/// process supervisors (e.g. the Zed extension host) know the agent is alive and can
44/// surface the log file path to the user.
45#[derive(Clone, Debug)]
46pub struct ReadyNotification {
47    /// Semver version string of the running agent binary.
48    pub version: String,
49    /// PID of the agent process (for supervisor tracking).
50    pub pid: u32,
51    /// Absolute path to the log file, if file logging is configured.
52    pub log_file: Option<String>,
53}
54
55/// Thread-safe, shared list of available model identifiers advertised in `new_session`.
56pub type SharedAvailableModels = std::sync::Arc<parking_lot::RwLock<Vec<String>>>;
57
58/// Configuration for the ACP server, threaded through to the agent on every connection.
59///
60/// Construct with `AcpServerConfig::default()` and override the fields you need.
61///
62/// # Examples
63///
64/// ```
65/// use zeph_acp::AcpServerConfig;
66///
67/// let config = AcpServerConfig {
68///     agent_name: "zeph".to_owned(),
69///     agent_version: "1.0.0".to_owned(),
70///     max_sessions: 4,
71///     ..AcpServerConfig::default()
72/// };
73///
74/// assert_eq!(config.agent_name, "zeph");
75/// assert_eq!(config.max_sessions, 4);
76/// ```
77pub struct AcpServerConfig {
78    /// Display name of the agent reported to IDEs during handshake.
79    pub agent_name: String,
80    /// Semver version of the agent reported to IDEs during handshake.
81    pub agent_version: String,
82    /// Maximum number of concurrent ACP sessions (default: 4).
83    pub max_sessions: usize,
84    /// Seconds of inactivity before an idle session is reaped (default: 1800).
85    pub session_idle_timeout_secs: u64,
86    /// Path to the TOML permission file for tool-call approval decisions.
87    ///
88    /// Defaults to `$XDG_CONFIG_HOME/zeph/acp-permissions.toml` when `None`.
89    pub permission_file: Option<std::path::PathBuf>,
90    /// Optional factory for runtime model switching via `set_session_config_option`.
91    pub provider_factory: Option<crate::agent::ProviderFactory>,
92    /// Available model identifiers to advertise in `new_session` `config_options`.
93    pub available_models: SharedAvailableModels,
94    /// Optional shared MCP manager for `ext_method` add/remove/list.
95    pub mcp_manager: Option<std::sync::Arc<zeph_mcp::McpManager>>,
96    /// Bearer token for HTTP and WebSocket transport authentication.
97    ///
98    /// When `Some`, all `/acp` and `/acp/ws` requests must include the token in
99    /// an `Authorization: Bearer <token>` header. When `None`, the endpoints are
100    /// publicly accessible and a warning is logged at startup.
101    pub auth_bearer_token: Option<String>,
102    /// Whether to serve the `/.well-known/acp.json` discovery manifest.
103    pub discovery_enabled: bool,
104    /// Timeout in seconds for terminal command execution before the process is killed.
105    pub terminal_timeout_secs: u64,
106    /// Project rule file paths to advertise in `new_session` `_meta`.
107    pub project_rules: Vec<std::path::PathBuf>,
108    /// Maximum characters for auto-generated session titles (0 = no limit).
109    pub title_max_chars: usize,
110    /// Maximum number of sessions returned by list endpoints (0 = unlimited).
111    pub max_history: usize,
112    /// Path to the `SQLite` database for ACP session persistence.
113    ///
114    /// When set, the agent persists session events and loads conversation history
115    /// from this database. When `None`, sessions are in-memory only.
116    pub sqlite_path: Option<String>,
117    /// Optional startup notification emitted as the first stdio JSON-RPC frame.
118    pub ready_notification: Option<ReadyNotification>,
119    /// Canonicalized allowlist of directories ACP clients may reference in session requests.
120    pub additional_directories: Vec<zeph_core::config::AdditionalDir>,
121    /// Auth methods to advertise in the `initialize` response.
122    pub auth_methods: Vec<zeph_core::config::AcpAuthMethod>,
123    /// When `true`, echo `PromptRequest.message_id` through responses and chunks.
124    pub message_ids_enabled: bool,
125}
126
127impl Clone for AcpServerConfig {
128    fn clone(&self) -> Self {
129        Self {
130            agent_name: self.agent_name.clone(),
131            agent_version: self.agent_version.clone(),
132            max_sessions: self.max_sessions,
133            session_idle_timeout_secs: self.session_idle_timeout_secs,
134            permission_file: self.permission_file.clone(),
135            provider_factory: self.provider_factory.clone(),
136            available_models: self.available_models.clone(),
137            mcp_manager: self.mcp_manager.clone(),
138            auth_bearer_token: self.auth_bearer_token.clone(),
139            discovery_enabled: self.discovery_enabled,
140            terminal_timeout_secs: self.terminal_timeout_secs,
141            project_rules: self.project_rules.clone(),
142            title_max_chars: self.title_max_chars,
143            max_history: self.max_history,
144            sqlite_path: self.sqlite_path.clone(),
145            ready_notification: self.ready_notification.clone(),
146            additional_directories: self.additional_directories.clone(),
147            auth_methods: self.auth_methods.clone(),
148            message_ids_enabled: self.message_ids_enabled,
149        }
150    }
151}
152
153impl Default for AcpServerConfig {
154    fn default() -> Self {
155        Self {
156            agent_name: String::new(),
157            agent_version: String::new(),
158            max_sessions: 4,
159            session_idle_timeout_secs: 1800,
160            permission_file: None,
161            provider_factory: None,
162            available_models: std::sync::Arc::new(parking_lot::RwLock::new(Vec::new())),
163            mcp_manager: None,
164            auth_bearer_token: None,
165            discovery_enabled: true,
166            terminal_timeout_secs: 120,
167            project_rules: Vec::new(),
168            title_max_chars: 60,
169            max_history: 100,
170            sqlite_path: None,
171            ready_notification: None,
172            additional_directories: Vec::new(),
173            auth_methods: vec![zeph_core::config::AcpAuthMethod::Agent],
174            message_ids_enabled: true,
175        }
176    }
177}