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}