1use serde::{Deserialize, Serialize};
11use serde_json::Value;
12use std::collections::HashMap;
13
14pub const PROTOCOL_VERSION: &str = "2025-01-01";
16
17pub const SUPPORTED_VERSIONS: &[&str] = &["2025-01-01", "2024-11-01"];
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
26#[serde(rename_all = "camelCase")]
27pub struct InitializeParams {
28 pub protocol_versions: Vec<String>,
30
31 pub capabilities: ClientCapabilities,
33
34 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#[derive(Debug, Clone, Serialize, Deserialize)]
50#[serde(rename_all = "camelCase")]
51pub struct InitializeResult {
52 pub protocol_version: String,
54
55 pub capabilities: AgentCapabilities,
57
58 pub agent_info: AgentInfo,
60
61 #[serde(skip_serializing_if = "Option::is_none")]
63 pub auth_requirements: Option<AuthRequirements>,
64}
65
66#[derive(Debug, Clone, Default, Serialize, Deserialize)]
72#[serde(rename_all = "camelCase")]
73pub struct ClientCapabilities {
74 #[serde(default)]
76 pub filesystem: FilesystemCapabilities,
77
78 #[serde(default)]
80 pub terminal: TerminalCapabilities,
81
82 #[serde(default)]
84 pub ui: UiCapabilities,
85
86 #[serde(default, skip_serializing_if = "Vec::is_empty")]
88 pub mcp_servers: Vec<McpServerCapability>,
89
90 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
92 pub extensions: HashMap<String, Value>,
93}
94
95#[derive(Debug, Clone, Default, Serialize, Deserialize)]
97#[serde(rename_all = "camelCase")]
98pub struct FilesystemCapabilities {
99 #[serde(default)]
101 pub read: bool,
102
103 #[serde(default)]
105 pub write: bool,
106
107 #[serde(default)]
109 pub list: bool,
110
111 #[serde(default)]
113 pub search: bool,
114
115 #[serde(default)]
117 pub watch: bool,
118}
119
120#[derive(Debug, Clone, Default, Serialize, Deserialize)]
122#[serde(rename_all = "camelCase")]
123pub struct TerminalCapabilities {
124 #[serde(default)]
126 pub create: bool,
127
128 #[serde(default)]
130 pub input: bool,
131
132 #[serde(default)]
134 pub output: bool,
135
136 #[serde(default)]
138 pub pty: bool,
139}
140
141#[derive(Debug, Clone, Default, Serialize, Deserialize)]
143#[serde(rename_all = "camelCase")]
144pub struct UiCapabilities {
145 #[serde(default)]
147 pub notifications: bool,
148
149 #[serde(default)]
151 pub progress: bool,
152
153 #[serde(default)]
155 pub input_prompt: bool,
156
157 #[serde(default)]
159 pub diff_view: bool,
160}
161
162#[derive(Debug, Clone, Serialize, Deserialize)]
164#[serde(rename_all = "camelCase")]
165pub struct McpServerCapability {
166 pub name: String,
168
169 pub transport: String,
171
172 #[serde(default, skip_serializing_if = "Vec::is_empty")]
174 pub tools: Vec<String>,
175}
176
177#[derive(Debug, Clone, Default, Serialize, Deserialize)]
183#[serde(rename_all = "camelCase")]
184pub struct AgentCapabilities {
185 #[serde(default, skip_serializing_if = "Vec::is_empty")]
187 pub tools: Vec<ToolCapability>,
188
189 #[serde(default)]
191 pub features: AgentFeatures,
192
193 #[serde(skip_serializing_if = "Option::is_none")]
195 pub model: Option<ModelInfo>,
196
197 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
199 pub extensions: HashMap<String, Value>,
200}
201
202#[derive(Debug, Clone, Serialize, Deserialize)]
204#[serde(rename_all = "camelCase")]
205pub struct ToolCapability {
206 pub name: String,
208
209 #[serde(skip_serializing_if = "Option::is_none")]
211 pub description: Option<String>,
212
213 #[serde(skip_serializing_if = "Option::is_none")]
215 pub input_schema: Option<Value>,
216
217 #[serde(default)]
219 pub requires_confirmation: bool,
220}
221
222#[derive(Debug, Clone, Default, Serialize, Deserialize)]
224#[serde(rename_all = "camelCase")]
225pub struct AgentFeatures {
226 #[serde(default)]
228 pub streaming: bool,
229
230 #[serde(default)]
232 pub multi_turn: bool,
233
234 #[serde(default)]
236 pub session_persistence: bool,
237
238 #[serde(default)]
240 pub vision: bool,
241
242 #[serde(default)]
244 pub code_execution: bool,
245
246 #[serde(default)]
248 pub subagents: bool,
249}
250
251#[derive(Debug, Clone, Serialize, Deserialize)]
253#[serde(rename_all = "camelCase")]
254pub struct ModelInfo {
255 pub id: String,
257
258 #[serde(skip_serializing_if = "Option::is_none")]
260 pub name: Option<String>,
261
262 #[serde(skip_serializing_if = "Option::is_none")]
264 pub provider: Option<String>,
265
266 #[serde(skip_serializing_if = "Option::is_none")]
268 pub context_window: Option<u32>,
269}
270
271#[derive(Debug, Clone, Serialize, Deserialize)]
277pub struct ClientInfo {
278 pub name: String,
280
281 pub version: String,
283
284 #[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#[derive(Debug, Clone, Serialize, Deserialize)]
301pub struct AgentInfo {
302 pub name: String,
304
305 pub version: String,
307
308 #[serde(skip_serializing_if = "Option::is_none")]
310 pub description: Option<String>,
311
312 #[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#[derive(Debug, Clone, Serialize, Deserialize)]
334#[serde(rename_all = "camelCase")]
335pub struct AuthRequirements {
336 pub required: bool,
338
339 #[serde(default, skip_serializing_if = "Vec::is_empty")]
341 pub methods: Vec<AuthMethod>,
342}
343
344#[derive(Debug, Clone, Serialize, Deserialize)]
349#[serde(tag = "type", rename_all = "snake_case")]
350pub enum AuthMethod {
351 #[serde(rename = "agent")]
353 Agent {
354 id: String,
356 name: String,
358 #[serde(skip_serializing_if = "Option::is_none")]
360 description: Option<String>,
361 },
362
363 #[serde(rename = "env_var")]
366 EnvVar {
367 id: String,
369 name: String,
371 #[serde(skip_serializing_if = "Option::is_none")]
373 description: Option<String>,
374 var_name: String,
376 #[serde(skip_serializing_if = "Option::is_none")]
378 link: Option<String>,
379 },
380
381 #[serde(rename = "terminal")]
384 Terminal {
385 id: String,
387 name: String,
389 #[serde(skip_serializing_if = "Option::is_none")]
391 description: Option<String>,
392 #[serde(default, skip_serializing_if = "Vec::is_empty")]
394 args: Vec<String>,
395 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
397 env: HashMap<String, String>,
398 },
399
400 #[serde(rename = "api_key")]
402 ApiKey,
403
404 #[serde(rename = "oauth2")]
406 OAuth2,
407
408 #[serde(rename = "bearer")]
410 Bearer,
411
412 #[serde(rename = "custom")]
414 Custom(String),
415}
416
417#[derive(Debug, Clone, Serialize, Deserialize)]
419#[serde(rename_all = "camelCase")]
420pub struct AuthenticateParams {
421 pub method: AuthMethod,
423
424 pub credentials: AuthCredentials,
426}
427
428#[derive(Debug, Clone, Serialize, Deserialize)]
430#[serde(tag = "type", rename_all = "snake_case")]
431pub enum AuthCredentials {
432 ApiKey { key: String },
434
435 Bearer { token: String },
437
438 OAuth2 {
440 access_token: String,
441 #[serde(skip_serializing_if = "Option::is_none")]
442 refresh_token: Option<String>,
443 },
444}
445
446#[derive(Debug, Clone, Serialize, Deserialize)]
448#[serde(rename_all = "camelCase")]
449pub struct AuthenticateResult {
450 pub authenticated: bool,
452
453 #[serde(skip_serializing_if = "Option::is_none")]
455 pub session_token: Option<String>,
456
457 #[serde(skip_serializing_if = "Option::is_none")]
459 pub expires_at: Option<String>,
460}
461
462#[cfg(test)]
463mod tests {
464 use super::*;
465
466 #[test]
467 fn test_initialize_params_default() {
468 let params = InitializeParams::default();
469 assert!(!params.protocol_versions.is_empty());
470 assert!(
471 params
472 .protocol_versions
473 .contains(&PROTOCOL_VERSION.to_string())
474 );
475 }
476
477 #[test]
478 fn test_client_info_default() {
479 let info = ClientInfo::default();
480 assert_eq!(info.name, "vtcode");
481 assert!(!info.version.is_empty());
482 }
483
484 #[test]
485 fn test_capabilities_serialization() {
486 let caps = ClientCapabilities {
487 filesystem: FilesystemCapabilities {
488 read: true,
489 write: true,
490 list: true,
491 search: true,
492 watch: false,
493 },
494 terminal: TerminalCapabilities {
495 create: true,
496 input: true,
497 output: true,
498 pty: true,
499 },
500 ..Default::default()
501 };
502
503 let json = serde_json::to_value(&caps).unwrap();
504 assert_eq!(json["filesystem"]["read"], true);
505 assert_eq!(json["terminal"]["pty"], true);
506 }
507
508 #[test]
509 fn test_auth_credentials() {
510 let creds = AuthCredentials::ApiKey {
511 key: "sk-test123".to_string(),
512 };
513 let json = serde_json::to_value(&creds).unwrap();
514 assert_eq!(json["type"], "api_key");
515 assert_eq!(json["key"], "sk-test123");
516 }
517
518 #[test]
519 fn test_auth_method_agent() {
520 let method = AuthMethod::Agent {
521 id: "agent_auth".to_string(),
522 name: "Agent Authentication".to_string(),
523 description: Some("Let agent handle authentication".to_string()),
524 };
525 let json = serde_json::to_value(&method).unwrap();
526 assert_eq!(json["type"], "agent");
527 assert_eq!(json["id"], "agent_auth");
528 assert_eq!(json["name"], "Agent Authentication");
529 }
530
531 #[test]
532 fn test_auth_method_env_var() {
533 let method = AuthMethod::EnvVar {
534 id: "openai_key".to_string(),
535 name: "OpenAI API Key".to_string(),
536 description: Some("Provide your OpenAI API key".to_string()),
537 var_name: "OPENAI_API_KEY".to_string(),
538 link: Some("https://platform.openai.com/api-keys".to_string()),
539 };
540 let json = serde_json::to_value(&method).unwrap();
541 assert_eq!(json["type"], "env_var");
542 assert_eq!(json["id"], "openai_key");
543 assert_eq!(json["name"], "OpenAI API Key");
544 assert_eq!(json["var_name"], "OPENAI_API_KEY");
545 assert_eq!(json["link"], "https://platform.openai.com/api-keys");
546 }
547
548 #[test]
549 fn test_auth_method_terminal() {
550 let mut env = HashMap::new();
551 env.insert("VAR1".to_string(), "value1".to_string());
552
553 let method = AuthMethod::Terminal {
554 id: "terminal_login".to_string(),
555 name: "Terminal Login".to_string(),
556 description: Some("Login via interactive terminal".to_string()),
557 args: vec!["--login".to_string(), "--interactive".to_string()],
558 env,
559 };
560 let json = serde_json::to_value(&method).unwrap();
561 assert_eq!(json["type"], "terminal");
562 assert_eq!(json["args"][0], "--login");
563 assert_eq!(json["env"]["VAR1"], "value1");
564 }
565
566 #[test]
567 fn test_auth_method_serialization_roundtrip() {
568 let method = AuthMethod::EnvVar {
569 id: "test_id".to_string(),
570 name: "Test".to_string(),
571 description: None,
572 var_name: "TEST_VAR".to_string(),
573 link: None,
574 };
575
576 let json = serde_json::to_value(&method).unwrap();
577 let deserialized: AuthMethod = serde_json::from_value(json).unwrap();
578
579 match deserialized {
580 AuthMethod::EnvVar { id, name, var_name, .. } => {
581 assert_eq!(id, "test_id");
582 assert_eq!(name, "Test");
583 assert_eq!(var_name, "TEST_VAR");
584 }
585 _ => panic!("Unexpected auth method variant"),
586 }
587 }
588
589 #[test]
590 fn test_legacy_auth_methods() {
591 let json = serde_json::json!({"type": "api_key"});
593 let method: AuthMethod = serde_json::from_value(json).unwrap();
594 matches!(method, AuthMethod::ApiKey);
595
596 let json = serde_json::json!({"type": "oauth2"});
597 let method: AuthMethod = serde_json::from_value(json).unwrap();
598 matches!(method, AuthMethod::OAuth2);
599
600 let json = serde_json::json!({"type": "bearer"});
601 let method: AuthMethod = serde_json::from_value(json).unwrap();
602 matches!(method, AuthMethod::Bearer);
603 }
604}