Skip to main content

zeph_config/
ui.rs

1// SPDX-FileCopyrightText: 2026 Andrei G <bug-ops>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use serde::{Deserialize, Serialize};
5
6use crate::defaults::default_true;
7
8fn default_acp_agent_name() -> String {
9    "zeph".to_owned()
10}
11
12fn default_acp_agent_version() -> String {
13    env!("CARGO_PKG_VERSION").to_owned()
14}
15
16fn default_acp_max_sessions() -> usize {
17    4
18}
19
20fn default_acp_session_idle_timeout_secs() -> u64 {
21    1800
22}
23
24fn default_acp_broadcast_capacity() -> usize {
25    256
26}
27
28fn default_acp_transport() -> AcpTransport {
29    AcpTransport::Stdio
30}
31
32fn default_acp_http_bind() -> String {
33    "127.0.0.1:9800".to_owned()
34}
35
36fn default_acp_discovery_enabled() -> bool {
37    true
38}
39
40fn default_acp_lsp_max_diagnostics_per_file() -> usize {
41    20
42}
43
44fn default_acp_lsp_max_diagnostic_files() -> usize {
45    5
46}
47
48fn default_acp_lsp_max_references() -> usize {
49    100
50}
51
52fn default_acp_lsp_max_workspace_symbols() -> usize {
53    50
54}
55
56fn default_acp_lsp_request_timeout_secs() -> u64 {
57    10
58}
59fn default_lsp_mcp_server_id() -> String {
60    "mcpls".into()
61}
62fn default_lsp_token_budget() -> usize {
63    2000
64}
65fn default_lsp_max_per_file() -> usize {
66    20
67}
68fn default_lsp_max_symbols() -> usize {
69    5
70}
71fn default_lsp_call_timeout_secs() -> u64 {
72    5
73}
74
75/// TUI (terminal user interface) configuration, nested under `[tui]` in TOML.
76///
77/// # Example (TOML)
78///
79/// ```toml
80/// [tui]
81/// show_source_labels = true
82/// ```
83#[derive(Debug, Clone, Copy, Default, Deserialize, Serialize)]
84pub struct TuiConfig {
85    /// Show memory source labels (episodic / semantic / graph) in the message view.
86    /// Default: `false`.
87    #[serde(default)]
88    pub show_source_labels: bool,
89}
90
91/// ACP server transport mode.
92#[derive(Debug, Clone, Default, Deserialize, Serialize)]
93#[serde(rename_all = "lowercase")]
94pub enum AcpTransport {
95    /// JSON-RPC over stdin/stdout (default, IDE embedding).
96    #[default]
97    Stdio,
98    /// JSON-RPC over HTTP+SSE and WebSocket.
99    Http,
100    /// Both stdio and HTTP transports active simultaneously.
101    Both,
102}
103
104/// ACP (Agent Communication Protocol) server configuration, nested under `[acp]` in TOML.
105///
106/// When `enabled = true`, Zeph exposes an ACP endpoint that IDE integrations (e.g. Zed, VS Code)
107/// can connect to for conversational coding assistance. Supports stdio and HTTP transports.
108///
109/// # Example (TOML)
110///
111/// ```toml
112/// [acp]
113/// enabled = true
114/// transport = "stdio"
115/// agent_name = "zeph"
116/// max_sessions = 4
117/// ```
118#[derive(Clone, Deserialize, Serialize)]
119pub struct AcpConfig {
120    /// Enable the ACP server. Default: `false`.
121    #[serde(default)]
122    pub enabled: bool,
123    /// Agent name advertised in the ACP `initialize` response. Default: `"zeph"`.
124    #[serde(default = "default_acp_agent_name")]
125    pub agent_name: String,
126    /// Agent version advertised in the ACP `initialize` response. Default: crate version.
127    #[serde(default = "default_acp_agent_version")]
128    pub agent_version: String,
129    /// Maximum number of concurrent ACP sessions. Default: `4`.
130    #[serde(default = "default_acp_max_sessions")]
131    pub max_sessions: usize,
132    /// Seconds of inactivity before an idle session is closed. Default: `1800`.
133    #[serde(default = "default_acp_session_idle_timeout_secs")]
134    pub session_idle_timeout_secs: u64,
135    /// Broadcast channel capacity for streaming events. Default: `256`.
136    #[serde(default = "default_acp_broadcast_capacity")]
137    pub broadcast_capacity: usize,
138    /// Path to the ACP permission TOML file controlling per-session tool access.
139    #[serde(skip_serializing_if = "Option::is_none")]
140    pub permission_file: Option<std::path::PathBuf>,
141    /// List of `{provider}:{model}` identifiers advertised to the IDE for model switching.
142    /// Example: `["claude:claude-sonnet-4-5", "ollama:llama3"]`
143    #[serde(default)]
144    pub available_models: Vec<String>,
145    /// Transport mode: "stdio" (default), "http", or "both".
146    #[serde(default = "default_acp_transport")]
147    pub transport: AcpTransport,
148    /// Bind address for the HTTP transport.
149    #[serde(default = "default_acp_http_bind")]
150    pub http_bind: String,
151    /// Bearer token for HTTP and WebSocket transport authentication.
152    /// When set, all /acp and /acp/ws requests must include `Authorization: Bearer <token>`.
153    /// Omit for local unauthenticated access. TLS termination is assumed to be handled by a
154    /// reverse proxy.
155    #[serde(skip_serializing_if = "Option::is_none")]
156    pub auth_token: Option<String>,
157    /// Whether to serve the /.well-known/acp.json agent discovery manifest.
158    /// Only effective when transport is "http" or "both". Default: true.
159    #[serde(default = "default_acp_discovery_enabled")]
160    pub discovery_enabled: bool,
161    /// LSP extension configuration (`[acp.lsp]`).
162    #[serde(default)]
163    pub lsp: AcpLspConfig,
164}
165
166impl Default for AcpConfig {
167    fn default() -> Self {
168        Self {
169            enabled: false,
170            agent_name: default_acp_agent_name(),
171            agent_version: default_acp_agent_version(),
172            max_sessions: default_acp_max_sessions(),
173            session_idle_timeout_secs: default_acp_session_idle_timeout_secs(),
174            broadcast_capacity: default_acp_broadcast_capacity(),
175            permission_file: None,
176            available_models: Vec::new(),
177            transport: default_acp_transport(),
178            http_bind: default_acp_http_bind(),
179            auth_token: None,
180            discovery_enabled: default_acp_discovery_enabled(),
181            lsp: AcpLspConfig::default(),
182        }
183    }
184}
185
186impl std::fmt::Debug for AcpConfig {
187    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
188        f.debug_struct("AcpConfig")
189            .field("enabled", &self.enabled)
190            .field("agent_name", &self.agent_name)
191            .field("agent_version", &self.agent_version)
192            .field("max_sessions", &self.max_sessions)
193            .field("session_idle_timeout_secs", &self.session_idle_timeout_secs)
194            .field("broadcast_capacity", &self.broadcast_capacity)
195            .field("permission_file", &self.permission_file)
196            .field("available_models", &self.available_models)
197            .field("transport", &self.transport)
198            .field("http_bind", &self.http_bind)
199            .field(
200                "auth_token",
201                &self.auth_token.as_ref().map(|_| "[REDACTED]"),
202            )
203            .field("discovery_enabled", &self.discovery_enabled)
204            .field("lsp", &self.lsp)
205            .finish()
206    }
207}
208
209/// Configuration for the ACP LSP extension.
210///
211/// Controls LSP code intelligence features when connected to an IDE that advertises
212/// `meta["lsp"]` capability during ACP `initialize`.
213#[derive(Debug, Clone, Deserialize, Serialize)]
214pub struct AcpLspConfig {
215    /// Enable LSP extension when the IDE supports it. Default: `true`.
216    #[serde(default = "default_true")]
217    pub enabled: bool,
218    /// Automatically fetch diagnostics when `lsp/didSave` notification is received.
219    #[serde(default = "default_true")]
220    pub auto_diagnostics_on_save: bool,
221    /// Maximum diagnostics to accept per file. Default: 20.
222    #[serde(default = "default_acp_lsp_max_diagnostics_per_file")]
223    pub max_diagnostics_per_file: usize,
224    /// Maximum files in `DiagnosticsCache` (LRU eviction). Default: 5.
225    #[serde(default = "default_acp_lsp_max_diagnostic_files")]
226    pub max_diagnostic_files: usize,
227    /// Maximum reference locations returned. Default: 100.
228    #[serde(default = "default_acp_lsp_max_references")]
229    pub max_references: usize,
230    /// Maximum workspace symbol search results. Default: 50.
231    #[serde(default = "default_acp_lsp_max_workspace_symbols")]
232    pub max_workspace_symbols: usize,
233    /// Timeout in seconds for LSP `ext_method` calls. Default: 10.
234    #[serde(default = "default_acp_lsp_request_timeout_secs")]
235    pub request_timeout_secs: u64,
236}
237
238impl Default for AcpLspConfig {
239    fn default() -> Self {
240        Self {
241            enabled: true,
242            auto_diagnostics_on_save: true,
243            max_diagnostics_per_file: default_acp_lsp_max_diagnostics_per_file(),
244            max_diagnostic_files: default_acp_lsp_max_diagnostic_files(),
245            max_references: default_acp_lsp_max_references(),
246            max_workspace_symbols: default_acp_lsp_max_workspace_symbols(),
247            request_timeout_secs: default_acp_lsp_request_timeout_secs(),
248        }
249    }
250}
251
252// ── LSP context injection ─────────────────────────────────────────────────────
253
254/// Minimum diagnostic severity to include in LSP context injection.
255#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
256#[serde(rename_all = "lowercase")]
257pub enum DiagnosticSeverity {
258    #[default]
259    Error,
260    Warning,
261    Info,
262    Hint,
263}
264
265/// Configuration for the diagnostics-on-save hook (`[agent.lsp.diagnostics]`).
266///
267/// Flood control relies on `token_budget` in [`LspConfig`], not a per-file count.
268#[derive(Debug, Clone, Deserialize, Serialize)]
269#[serde(default)]
270pub struct DiagnosticsConfig {
271    /// Enable automatic diagnostics fetching after the `write` tool.
272    pub enabled: bool,
273    /// Maximum diagnostics entries per file.
274    #[serde(default = "default_lsp_max_per_file")]
275    pub max_per_file: usize,
276    /// Minimum severity to include.
277    #[serde(default)]
278    pub min_severity: DiagnosticSeverity,
279}
280impl Default for DiagnosticsConfig {
281    fn default() -> Self {
282        Self {
283            enabled: true,
284            max_per_file: default_lsp_max_per_file(),
285            min_severity: DiagnosticSeverity::default(),
286        }
287    }
288}
289
290/// Configuration for the hover-on-read hook (`[agent.lsp.hover]`).
291#[derive(Debug, Clone, Deserialize, Serialize)]
292#[serde(default)]
293pub struct HoverConfig {
294    /// Enable hover info pre-fetch after the `read` tool. Disabled by default.
295    pub enabled: bool,
296    /// Maximum hover entries per file (Rust-only for MVP).
297    #[serde(default = "default_lsp_max_symbols")]
298    pub max_symbols: usize,
299}
300impl Default for HoverConfig {
301    fn default() -> Self {
302        Self {
303            enabled: false,
304            max_symbols: default_lsp_max_symbols(),
305        }
306    }
307}
308
309/// Top-level LSP context injection configuration (`[agent.lsp]` TOML section).
310#[derive(Debug, Clone, Deserialize, Serialize)]
311#[serde(default)]
312pub struct LspConfig {
313    /// Enable LSP context injection hooks.
314    pub enabled: bool,
315    /// MCP server ID to route LSP calls through (default: "mcpls").
316    #[serde(default = "default_lsp_mcp_server_id")]
317    pub mcp_server_id: String,
318    /// Maximum tokens to spend on injected LSP context per turn.
319    #[serde(default = "default_lsp_token_budget")]
320    pub token_budget: usize,
321    /// Timeout in seconds for each MCP LSP call.
322    #[serde(default = "default_lsp_call_timeout_secs")]
323    pub call_timeout_secs: u64,
324    /// Diagnostics-on-save hook configuration.
325    #[serde(default)]
326    pub diagnostics: DiagnosticsConfig,
327    /// Hover-on-read hook configuration.
328    #[serde(default)]
329    pub hover: HoverConfig,
330}
331impl Default for LspConfig {
332    fn default() -> Self {
333        Self {
334            enabled: false,
335            mcp_server_id: default_lsp_mcp_server_id(),
336            token_budget: default_lsp_token_budget(),
337            call_timeout_secs: default_lsp_call_timeout_secs(),
338            diagnostics: DiagnosticsConfig::default(),
339            hover: HoverConfig::default(),
340        }
341    }
342}