1use hashbrown::HashMap;
16use serde::{Deserialize, Serialize};
17use serde_json::Value;
18
19pub(crate) const PROTOCOL_VERSION: &str = "2025-01-01";
21
22pub(crate) const SUPPORTED_VERSIONS: &[&str] = &["2025-01-01", "2024-11-01"];
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
31#[serde(rename_all = "camelCase")]
32pub struct InitializeParams {
33 pub(crate) protocol_versions: Vec<String>,
35
36 pub(crate) capabilities: ClientCapabilities,
38
39 pub(crate) client_info: ClientInfo,
41}
42
43impl Default for InitializeParams {
44 fn default() -> Self {
45 Self {
46 protocol_versions: SUPPORTED_VERSIONS.iter().map(|s| s.to_string()).collect(),
47 capabilities: ClientCapabilities::default(),
48 client_info: ClientInfo::default(),
49 }
50 }
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize)]
55#[serde(rename_all = "camelCase")]
56pub struct InitializeResult {
57 pub(crate) protocol_version: String,
59
60 pub(crate) capabilities: AgentCapabilities,
62
63 pub(crate) agent_info: AgentInfo,
65
66 #[serde(skip_serializing_if = "Option::is_none")]
68 auth_requirements: Option<AuthRequirements>,
69}
70
71#[derive(Debug, Clone, Default, Serialize, Deserialize)]
77#[serde(rename_all = "camelCase")]
78pub struct ClientCapabilities {
79 #[serde(default)]
81 filesystem: FilesystemCapabilities,
82
83 #[serde(default)]
85 terminal: TerminalCapabilities,
86
87 #[serde(default)]
89 ui: UiCapabilities,
90
91 #[serde(default, skip_serializing_if = "Vec::is_empty")]
93 mcp_servers: Vec<McpServerCapability>,
94
95 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
97 extensions: HashMap<String, Value>,
98}
99
100#[derive(Debug, Clone, Default, Serialize, Deserialize)]
102#[serde(rename_all = "camelCase")]
103pub struct FilesystemCapabilities {
104 #[serde(default)]
106 read: bool,
107
108 #[serde(default)]
110 write: bool,
111
112 #[serde(default)]
114 list: bool,
115
116 #[serde(default)]
118 search: bool,
119
120 #[serde(default)]
122 watch: bool,
123}
124
125#[derive(Debug, Clone, Default, Serialize, Deserialize)]
127#[serde(rename_all = "camelCase")]
128pub struct TerminalCapabilities {
129 #[serde(default)]
131 create: bool,
132
133 #[serde(default)]
135 input: bool,
136
137 #[serde(default)]
139 output: bool,
140
141 #[serde(default)]
143 pty: bool,
144}
145
146#[derive(Debug, Clone, Default, Serialize, Deserialize)]
148#[serde(rename_all = "camelCase")]
149pub struct UiCapabilities {
150 #[serde(default)]
152 notifications: bool,
153
154 #[serde(default)]
156 progress: bool,
157
158 #[serde(default)]
160 input_prompt: bool,
161
162 #[serde(default)]
164 diff_view: bool,
165}
166
167#[derive(Debug, Clone, Serialize, Deserialize)]
169#[serde(rename_all = "camelCase")]
170pub struct McpServerCapability {
171 name: String,
173
174 transport: String,
176
177 #[serde(default, skip_serializing_if = "Vec::is_empty")]
179 tools: Vec<String>,
180}
181
182#[derive(Debug, Clone, Default, Serialize, Deserialize)]
188#[serde(rename_all = "camelCase")]
189pub struct AgentCapabilities {
190 #[serde(default, skip_serializing_if = "Vec::is_empty")]
192 tools: Vec<ToolCapability>,
193
194 #[serde(default)]
196 features: AgentFeatures,
197
198 #[serde(skip_serializing_if = "Option::is_none")]
200 model: Option<ModelInfo>,
201
202 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
204 extensions: HashMap<String, Value>,
205}
206
207#[derive(Debug, Clone, Serialize, Deserialize)]
209#[serde(rename_all = "camelCase")]
210pub struct ToolCapability {
211 name: String,
213
214 #[serde(skip_serializing_if = "Option::is_none")]
216 description: Option<String>,
217
218 #[serde(skip_serializing_if = "Option::is_none")]
220 input_schema: Option<Value>,
221
222 #[serde(default)]
224 requires_confirmation: bool,
225}
226
227#[derive(Debug, Clone, Default, Serialize, Deserialize)]
229#[serde(rename_all = "camelCase")]
230pub struct AgentFeatures {
231 #[serde(default)]
233 streaming: bool,
234
235 #[serde(default)]
237 multi_turn: bool,
238
239 #[serde(default)]
241 session_persistence: bool,
242
243 #[serde(default)]
245 vision: bool,
246
247 #[serde(default)]
249 code_execution: bool,
250}
251
252#[derive(Debug, Clone, Serialize, Deserialize)]
254#[serde(rename_all = "camelCase")]
255pub struct ModelInfo {
256 id: String,
258
259 #[serde(skip_serializing_if = "Option::is_none")]
261 name: Option<String>,
262
263 #[serde(skip_serializing_if = "Option::is_none")]
265 provider: Option<String>,
266
267 #[serde(skip_serializing_if = "Option::is_none")]
269 context_window: Option<u32>,
270}
271
272#[derive(Debug, Clone, Serialize, Deserialize)]
278pub struct ClientInfo {
279 name: String,
281
282 version: String,
284
285 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
287 metadata: HashMap<String, Value>,
288}
289
290impl Default for ClientInfo {
291 fn default() -> Self {
292 Self {
293 name: "vtcode".to_string(),
294 version: env!("CARGO_PKG_VERSION").to_string(),
295 metadata: HashMap::new(),
296 }
297 }
298}
299
300#[derive(Debug, Clone, Serialize, Deserialize)]
302pub struct AgentInfo {
303 pub(crate) name: String,
305
306 version: String,
308
309 #[serde(skip_serializing_if = "Option::is_none")]
311 description: Option<String>,
312
313 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
315 metadata: HashMap<String, Value>,
316}
317
318impl Default for AgentInfo {
319 fn default() -> Self {
320 Self {
321 name: "vtcode-agent".to_string(),
322 version: env!("CARGO_PKG_VERSION").to_string(),
323 description: Some("VT Code AI coding agent".to_string()),
324 metadata: HashMap::new(),
325 }
326 }
327}
328
329#[derive(Debug, Clone, Serialize, Deserialize)]
335#[serde(rename_all = "camelCase")]
336pub struct AuthRequirements {
337 required: bool,
339
340 #[serde(default, skip_serializing_if = "Vec::is_empty")]
342 methods: Vec<AuthMethod>,
343}
344
345#[derive(Debug, Clone, Serialize, Deserialize)]
350#[serde(tag = "type", rename_all = "snake_case")]
351pub enum AuthMethod {
352 #[serde(rename = "agent")]
354 Agent {
355 id: String,
357 name: String,
359 #[serde(skip_serializing_if = "Option::is_none")]
361 description: Option<String>,
362 },
363
364 #[serde(rename = "env_var")]
367 EnvVar {
368 id: String,
370 name: String,
372 #[serde(skip_serializing_if = "Option::is_none")]
374 description: Option<String>,
375 var_name: String,
377 #[serde(skip_serializing_if = "Option::is_none")]
379 link: Option<String>,
380 },
381
382 #[serde(rename = "terminal")]
385 Terminal {
386 id: String,
388 name: String,
390 #[serde(skip_serializing_if = "Option::is_none")]
392 description: Option<String>,
393 #[serde(default, skip_serializing_if = "Vec::is_empty")]
395 args: Vec<String>,
396 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
398 env: HashMap<String, String>,
399 },
400
401 #[serde(rename = "api_key")]
403 ApiKey,
404
405 #[serde(rename = "oauth2")]
407 OAuth2,
408
409 #[serde(rename = "bearer")]
411 Bearer,
412
413 #[serde(rename = "custom")]
415 Custom(String),
416}
417
418#[derive(Debug, Clone, Serialize, Deserialize)]
420#[serde(rename_all = "camelCase")]
421pub struct AuthenticateParams {
422 method: AuthMethod,
424
425 credentials: AuthCredentials,
427}
428
429#[derive(Debug, Clone, Serialize, Deserialize)]
431#[serde(tag = "type", rename_all = "snake_case")]
432pub enum AuthCredentials {
433 ApiKey { key: String },
435
436 Bearer { token: String },
438
439 OAuth2 {
441 access_token: String,
442 #[serde(skip_serializing_if = "Option::is_none")]
443 refresh_token: Option<String>,
444 },
445}
446
447#[derive(Debug, Clone, Serialize, Deserialize)]
449#[serde(rename_all = "camelCase")]
450pub struct AuthenticateResult {
451 pub(crate) authenticated: bool,
453
454 #[serde(skip_serializing_if = "Option::is_none")]
456 pub(crate) session_token: Option<String>,
457
458 #[serde(skip_serializing_if = "Option::is_none")]
460 expires_at: Option<String>,
461}
462
463#[cfg(test)]
464mod tests {
465 use super::*;
466
467 #[test]
468 fn test_initialize_params_default() {
469 let params = InitializeParams::default();
470 assert!(!params.protocol_versions.is_empty());
471 assert!(params.protocol_versions.contains(&PROTOCOL_VERSION.to_string()));
472 }
473
474 #[test]
475 fn test_client_info_default() {
476 let info = ClientInfo::default();
477 assert_eq!(info.name, "vtcode");
478 assert!(!info.version.is_empty());
479 }
480
481 #[test]
482 fn test_capabilities_serialization() {
483 let caps = ClientCapabilities {
484 filesystem: FilesystemCapabilities {
485 read: true,
486 write: true,
487 list: true,
488 search: true,
489 watch: false,
490 },
491 terminal: TerminalCapabilities { create: true, input: true, output: true, pty: true },
492 ..Default::default()
493 };
494
495 let json = serde_json::to_value(&caps).unwrap();
496 assert_eq!(json["filesystem"]["read"], true);
497 assert_eq!(json["terminal"]["pty"], true);
498 }
499
500 #[test]
501 fn test_auth_credentials() {
502 let creds = AuthCredentials::ApiKey { key: "sk-test123".to_string() };
503 let json = serde_json::to_value(&creds).unwrap();
504 assert_eq!(json["type"], "api_key");
505 assert_eq!(json["key"], "sk-test123");
506 }
507
508 #[test]
509 fn test_auth_method_agent() {
510 let method = AuthMethod::Agent {
511 id: "agent_auth".to_string(),
512 name: "Agent Authentication".to_string(),
513 description: Some("Let agent handle authentication".to_string()),
514 };
515 let json = serde_json::to_value(&method).unwrap();
516 assert_eq!(json["type"], "agent");
517 assert_eq!(json["id"], "agent_auth");
518 assert_eq!(json["name"], "Agent Authentication");
519 }
520
521 #[test]
522 fn test_auth_method_env_var() {
523 let method = AuthMethod::EnvVar {
524 id: "openai_key".to_string(),
525 name: "OpenAI API Key".to_string(),
526 description: Some("Provide your OpenAI API key".to_string()),
527 var_name: "OPENAI_API_KEY".to_string(),
528 link: Some("https://platform.openai.com/api-keys".to_string()),
529 };
530 let json = serde_json::to_value(&method).unwrap();
531 assert_eq!(json["type"], "env_var");
532 assert_eq!(json["id"], "openai_key");
533 assert_eq!(json["name"], "OpenAI API Key");
534 assert_eq!(json["var_name"], "OPENAI_API_KEY");
535 assert_eq!(json["link"], "https://platform.openai.com/api-keys");
536 }
537
538 #[test]
539 fn test_auth_method_terminal() {
540 let mut env = HashMap::new();
541 drop(env.insert("VAR1".to_string(), "value1".to_string()));
542
543 let method = AuthMethod::Terminal {
544 id: "terminal_login".to_string(),
545 name: "Terminal Login".to_string(),
546 description: Some("Login via interactive terminal".to_string()),
547 args: vec!["--login".to_string(), "--interactive".to_string()],
548 env,
549 };
550 let json = serde_json::to_value(&method).unwrap();
551 assert_eq!(json["type"], "terminal");
552 assert_eq!(json["args"][0], "--login");
553 assert_eq!(json["env"]["VAR1"], "value1");
554 }
555
556 #[test]
557 fn test_auth_method_serialization_roundtrip() {
558 let method = AuthMethod::EnvVar {
559 id: "test_id".to_string(),
560 name: "Test".to_string(),
561 description: None,
562 var_name: "TEST_VAR".to_string(),
563 link: None,
564 };
565
566 let json = serde_json::to_value(&method).unwrap();
567 let deserialized: AuthMethod = serde_json::from_value(json).unwrap();
568
569 match deserialized {
570 AuthMethod::EnvVar { id, name, var_name, .. } => {
571 assert_eq!(id, "test_id");
572 assert_eq!(name, "Test");
573 assert_eq!(var_name, "TEST_VAR");
574 }
575 _ => panic!("Unexpected auth method variant"),
576 }
577 }
578
579 #[test]
580 fn test_legacy_auth_methods() {
581 let json = serde_json::json!({"type": "api_key"});
583 let method: AuthMethod = serde_json::from_value(json).unwrap();
584 matches!(method, AuthMethod::ApiKey);
585
586 let json = serde_json::json!({"type": "oauth2"});
587 let method: AuthMethod = serde_json::from_value(json).unwrap();
588 matches!(method, AuthMethod::OAuth2);
589
590 let json = serde_json::json!({"type": "bearer"});
591 let method: AuthMethod = serde_json::from_value(json).unwrap();
592 matches!(method, AuthMethod::Bearer);
593 }
594}