vtcode_acp_client/
capabilities.rs

1//! ACP capabilities and initialization types
2//!
3//! This module implements the capability negotiation as defined by ACP:
4//! - Protocol version negotiation
5//! - Feature capability exchange
6//! - Agent information structures
7//!
8//! Reference: https://agentclientprotocol.com/llms.txt
9
10use serde::{Deserialize, Serialize};
11use serde_json::Value;
12use std::collections::HashMap;
13
14/// Current ACP protocol version supported by this implementation
15pub const PROTOCOL_VERSION: &str = "2025-01-01";
16
17/// Supported protocol versions (newest first)
18pub const SUPPORTED_VERSIONS: &[&str] = &["2025-01-01", "2024-11-01"];
19
20// ============================================================================
21// Initialize Request/Response
22// ============================================================================
23
24/// Parameters for the initialize method
25#[derive(Debug, Clone, Serialize, Deserialize)]
26#[serde(rename_all = "camelCase")]
27pub struct InitializeParams {
28    /// Protocol versions the client supports (newest first)
29    pub protocol_versions: Vec<String>,
30
31    /// Client capabilities
32    pub capabilities: ClientCapabilities,
33
34    /// Client information
35    pub client_info: ClientInfo,
36}
37
38impl Default for InitializeParams {
39    fn default() -> Self {
40        Self {
41            protocol_versions: SUPPORTED_VERSIONS.iter().map(|s| s.to_string()).collect(),
42            capabilities: ClientCapabilities::default(),
43            client_info: ClientInfo::default(),
44        }
45    }
46}
47
48/// Result of the initialize method
49#[derive(Debug, Clone, Serialize, Deserialize)]
50#[serde(rename_all = "camelCase")]
51pub struct InitializeResult {
52    /// Negotiated protocol version
53    pub protocol_version: String,
54
55    /// Agent capabilities
56    pub capabilities: AgentCapabilities,
57
58    /// Agent information
59    pub agent_info: AgentInfo,
60
61    /// Authentication requirements (if any)
62    #[serde(skip_serializing_if = "Option::is_none")]
63    pub auth_requirements: Option<AuthRequirements>,
64}
65
66// ============================================================================
67// Client Capabilities
68// ============================================================================
69
70/// Capabilities the client (IDE/host) provides to the agent
71#[derive(Debug, Clone, Default, Serialize, Deserialize)]
72#[serde(rename_all = "camelCase")]
73pub struct ClientCapabilities {
74    /// File system operations
75    #[serde(default)]
76    pub filesystem: FilesystemCapabilities,
77
78    /// Terminal/shell capabilities
79    #[serde(default)]
80    pub terminal: TerminalCapabilities,
81
82    /// UI/notification capabilities
83    #[serde(default)]
84    pub ui: UiCapabilities,
85
86    /// MCP server connections the client can provide
87    #[serde(default, skip_serializing_if = "Vec::is_empty")]
88    pub mcp_servers: Vec<McpServerCapability>,
89
90    /// Extension points for custom capabilities
91    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
92    pub extensions: HashMap<String, Value>,
93}
94
95/// File system operation capabilities
96#[derive(Debug, Clone, Default, Serialize, Deserialize)]
97#[serde(rename_all = "camelCase")]
98pub struct FilesystemCapabilities {
99    /// Can read files
100    #[serde(default)]
101    pub read: bool,
102
103    /// Can write files
104    #[serde(default)]
105    pub write: bool,
106
107    /// Can list directories
108    #[serde(default)]
109    pub list: bool,
110
111    /// Can search files (grep/find)
112    #[serde(default)]
113    pub search: bool,
114
115    /// Can watch for file changes
116    #[serde(default)]
117    pub watch: bool,
118}
119
120/// Terminal operation capabilities
121#[derive(Debug, Clone, Default, Serialize, Deserialize)]
122#[serde(rename_all = "camelCase")]
123pub struct TerminalCapabilities {
124    /// Can create terminal sessions
125    #[serde(default)]
126    pub create: bool,
127
128    /// Can send input to terminals
129    #[serde(default)]
130    pub input: bool,
131
132    /// Can read terminal output
133    #[serde(default)]
134    pub output: bool,
135
136    /// Supports PTY (pseudo-terminal)
137    #[serde(default)]
138    pub pty: bool,
139}
140
141/// UI/notification capabilities
142#[derive(Debug, Clone, Default, Serialize, Deserialize)]
143#[serde(rename_all = "camelCase")]
144pub struct UiCapabilities {
145    /// Can show notifications
146    #[serde(default)]
147    pub notifications: bool,
148
149    /// Can show progress indicators
150    #[serde(default)]
151    pub progress: bool,
152
153    /// Can prompt for user input
154    #[serde(default)]
155    pub input_prompt: bool,
156
157    /// Can show file diffs
158    #[serde(default)]
159    pub diff_view: bool,
160}
161
162/// MCP server connection capability
163#[derive(Debug, Clone, Serialize, Deserialize)]
164#[serde(rename_all = "camelCase")]
165pub struct McpServerCapability {
166    /// Server name/identifier
167    pub name: String,
168
169    /// Server transport type (stdio, http, sse)
170    pub transport: String,
171
172    /// Tools this server provides
173    #[serde(default, skip_serializing_if = "Vec::is_empty")]
174    pub tools: Vec<String>,
175}
176
177// ============================================================================
178// Agent Capabilities
179// ============================================================================
180
181/// Capabilities the agent provides
182#[derive(Debug, Clone, Default, Serialize, Deserialize)]
183#[serde(rename_all = "camelCase")]
184pub struct AgentCapabilities {
185    /// Available tools
186    #[serde(default, skip_serializing_if = "Vec::is_empty")]
187    pub tools: Vec<ToolCapability>,
188
189    /// Supported features
190    #[serde(default)]
191    pub features: AgentFeatures,
192
193    /// Model information
194    #[serde(skip_serializing_if = "Option::is_none")]
195    pub model: Option<ModelInfo>,
196
197    /// Extension points
198    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
199    pub extensions: HashMap<String, Value>,
200}
201
202/// A tool the agent can execute
203#[derive(Debug, Clone, Serialize, Deserialize)]
204#[serde(rename_all = "camelCase")]
205pub struct ToolCapability {
206    /// Tool name
207    pub name: String,
208
209    /// Tool description
210    #[serde(skip_serializing_if = "Option::is_none")]
211    pub description: Option<String>,
212
213    /// Input schema (JSON Schema)
214    #[serde(skip_serializing_if = "Option::is_none")]
215    pub input_schema: Option<Value>,
216
217    /// Whether tool requires user confirmation
218    #[serde(default)]
219    pub requires_confirmation: bool,
220}
221
222/// Agent feature flags
223#[derive(Debug, Clone, Default, Serialize, Deserialize)]
224#[serde(rename_all = "camelCase")]
225pub struct AgentFeatures {
226    /// Supports streaming responses
227    #[serde(default)]
228    pub streaming: bool,
229
230    /// Supports multi-turn conversations
231    #[serde(default)]
232    pub multi_turn: bool,
233
234    /// Supports session persistence
235    #[serde(default)]
236    pub session_persistence: bool,
237
238    /// Supports image/vision input
239    #[serde(default)]
240    pub vision: bool,
241
242    /// Supports code execution
243    #[serde(default)]
244    pub code_execution: bool,
245
246    /// Supports subagent spawning
247    #[serde(default)]
248    pub subagents: bool,
249}
250
251/// Model information
252#[derive(Debug, Clone, Serialize, Deserialize)]
253#[serde(rename_all = "camelCase")]
254pub struct ModelInfo {
255    /// Model identifier
256    pub id: String,
257
258    /// Model name
259    #[serde(skip_serializing_if = "Option::is_none")]
260    pub name: Option<String>,
261
262    /// Provider name
263    #[serde(skip_serializing_if = "Option::is_none")]
264    pub provider: Option<String>,
265
266    /// Context window size
267    #[serde(skip_serializing_if = "Option::is_none")]
268    pub context_window: Option<u32>,
269}
270
271// ============================================================================
272// Client/Agent Info
273// ============================================================================
274
275/// Information about the client (IDE/host)
276#[derive(Debug, Clone, Serialize, Deserialize)]
277pub struct ClientInfo {
278    /// Client name
279    pub name: String,
280
281    /// Client version
282    pub version: String,
283
284    /// Additional metadata
285    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
286    pub metadata: HashMap<String, Value>,
287}
288
289impl Default for ClientInfo {
290    fn default() -> Self {
291        Self {
292            name: "vtcode".to_string(),
293            version: env!("CARGO_PKG_VERSION").to_string(),
294            metadata: HashMap::new(),
295        }
296    }
297}
298
299/// Information about the agent
300#[derive(Debug, Clone, Serialize, Deserialize)]
301pub struct AgentInfo {
302    /// Agent name
303    pub name: String,
304
305    /// Agent version
306    pub version: String,
307
308    /// Agent description
309    #[serde(skip_serializing_if = "Option::is_none")]
310    pub description: Option<String>,
311
312    /// Additional metadata
313    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
314    pub metadata: HashMap<String, Value>,
315}
316
317impl Default for AgentInfo {
318    fn default() -> Self {
319        Self {
320            name: "vtcode-agent".to_string(),
321            version: env!("CARGO_PKG_VERSION").to_string(),
322            description: Some("VT Code AI coding agent".to_string()),
323            metadata: HashMap::new(),
324        }
325    }
326}
327
328// ============================================================================
329// Authentication
330// ============================================================================
331
332/// Authentication requirements
333#[derive(Debug, Clone, Serialize, Deserialize)]
334#[serde(rename_all = "camelCase")]
335pub struct AuthRequirements {
336    /// Whether authentication is required
337    pub required: bool,
338
339    /// Supported authentication methods
340    #[serde(default, skip_serializing_if = "Vec::is_empty")]
341    pub methods: Vec<AuthMethod>,
342}
343
344/// Supported authentication methods
345#[derive(Debug, Clone, Serialize, Deserialize)]
346#[serde(rename_all = "snake_case")]
347pub enum AuthMethod {
348    /// API key authentication
349    ApiKey,
350    /// OAuth 2.0
351    OAuth2,
352    /// Bearer token
353    Bearer,
354    /// Custom authentication
355    Custom(String),
356}
357
358/// Parameters for authenticate method
359#[derive(Debug, Clone, Serialize, Deserialize)]
360#[serde(rename_all = "camelCase")]
361pub struct AuthenticateParams {
362    /// Authentication method being used
363    pub method: AuthMethod,
364
365    /// Authentication credentials
366    pub credentials: AuthCredentials,
367}
368
369/// Authentication credentials
370#[derive(Debug, Clone, Serialize, Deserialize)]
371#[serde(tag = "type", rename_all = "snake_case")]
372pub enum AuthCredentials {
373    /// API key
374    ApiKey { key: String },
375
376    /// Bearer token
377    Bearer { token: String },
378
379    /// OAuth2 token
380    OAuth2 {
381        access_token: String,
382        #[serde(skip_serializing_if = "Option::is_none")]
383        refresh_token: Option<String>,
384    },
385}
386
387/// Result of authenticate method
388#[derive(Debug, Clone, Serialize, Deserialize)]
389#[serde(rename_all = "camelCase")]
390pub struct AuthenticateResult {
391    /// Whether authentication succeeded
392    pub authenticated: bool,
393
394    /// Session token (if applicable)
395    #[serde(skip_serializing_if = "Option::is_none")]
396    pub session_token: Option<String>,
397
398    /// Token expiration (ISO 8601)
399    #[serde(skip_serializing_if = "Option::is_none")]
400    pub expires_at: Option<String>,
401}
402
403#[cfg(test)]
404mod tests {
405    use super::*;
406
407    #[test]
408    fn test_initialize_params_default() {
409        let params = InitializeParams::default();
410        assert!(!params.protocol_versions.is_empty());
411        assert!(
412            params
413                .protocol_versions
414                .contains(&PROTOCOL_VERSION.to_string())
415        );
416    }
417
418    #[test]
419    fn test_client_info_default() {
420        let info = ClientInfo::default();
421        assert_eq!(info.name, "vtcode");
422        assert!(!info.version.is_empty());
423    }
424
425    #[test]
426    fn test_capabilities_serialization() {
427        let caps = ClientCapabilities {
428            filesystem: FilesystemCapabilities {
429                read: true,
430                write: true,
431                list: true,
432                search: true,
433                watch: false,
434            },
435            terminal: TerminalCapabilities {
436                create: true,
437                input: true,
438                output: true,
439                pty: true,
440            },
441            ..Default::default()
442        };
443
444        let json = serde_json::to_value(&caps).unwrap();
445        assert_eq!(json["filesystem"]["read"], true);
446        assert_eq!(json["terminal"]["pty"], true);
447    }
448
449    #[test]
450    fn test_auth_credentials() {
451        let creds = AuthCredentials::ApiKey {
452            key: "sk-test123".to_string(),
453        };
454        let json = serde_json::to_value(&creds).unwrap();
455        assert_eq!(json["type"], "api_key");
456        assert_eq!(json["key"], "sk-test123");
457    }
458}