1use hashbrown::HashMap;
11use serde::{Deserialize, Serialize};
12use serde_json::Value;
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
247#[derive(Debug, Clone, Serialize, Deserialize)]
249#[serde(rename_all = "camelCase")]
250pub struct ModelInfo {
251 pub id: String,
253
254 #[serde(skip_serializing_if = "Option::is_none")]
256 pub name: Option<String>,
257
258 #[serde(skip_serializing_if = "Option::is_none")]
260 pub provider: Option<String>,
261
262 #[serde(skip_serializing_if = "Option::is_none")]
264 pub context_window: Option<u32>,
265}
266
267#[derive(Debug, Clone, Serialize, Deserialize)]
273pub struct ClientInfo {
274 pub name: String,
276
277 pub version: String,
279
280 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
282 pub metadata: HashMap<String, Value>,
283}
284
285impl Default for ClientInfo {
286 fn default() -> Self {
287 Self {
288 name: "vtcode".to_string(),
289 version: env!("CARGO_PKG_VERSION").to_string(),
290 metadata: HashMap::new(),
291 }
292 }
293}
294
295#[derive(Debug, Clone, Serialize, Deserialize)]
297pub struct AgentInfo {
298 pub name: String,
300
301 pub version: String,
303
304 #[serde(skip_serializing_if = "Option::is_none")]
306 pub description: Option<String>,
307
308 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
310 pub metadata: HashMap<String, Value>,
311}
312
313impl Default for AgentInfo {
314 fn default() -> Self {
315 Self {
316 name: "vtcode-agent".to_string(),
317 version: env!("CARGO_PKG_VERSION").to_string(),
318 description: Some("VT Code AI coding agent".to_string()),
319 metadata: HashMap::new(),
320 }
321 }
322}
323
324#[derive(Debug, Clone, Serialize, Deserialize)]
330#[serde(rename_all = "camelCase")]
331pub struct AuthRequirements {
332 pub required: bool,
334
335 #[serde(default, skip_serializing_if = "Vec::is_empty")]
337 pub methods: Vec<AuthMethod>,
338}
339
340#[derive(Debug, Clone, Serialize, Deserialize)]
345#[serde(tag = "type", rename_all = "snake_case")]
346pub enum AuthMethod {
347 #[serde(rename = "agent")]
349 Agent {
350 id: String,
352 name: String,
354 #[serde(skip_serializing_if = "Option::is_none")]
356 description: Option<String>,
357 },
358
359 #[serde(rename = "env_var")]
362 EnvVar {
363 id: String,
365 name: String,
367 #[serde(skip_serializing_if = "Option::is_none")]
369 description: Option<String>,
370 var_name: String,
372 #[serde(skip_serializing_if = "Option::is_none")]
374 link: Option<String>,
375 },
376
377 #[serde(rename = "terminal")]
380 Terminal {
381 id: String,
383 name: String,
385 #[serde(skip_serializing_if = "Option::is_none")]
387 description: Option<String>,
388 #[serde(default, skip_serializing_if = "Vec::is_empty")]
390 args: Vec<String>,
391 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
393 env: HashMap<String, String>,
394 },
395
396 #[serde(rename = "api_key")]
398 ApiKey,
399
400 #[serde(rename = "oauth2")]
402 OAuth2,
403
404 #[serde(rename = "bearer")]
406 Bearer,
407
408 #[serde(rename = "custom")]
410 Custom(String),
411}
412
413#[derive(Debug, Clone, Serialize, Deserialize)]
415#[serde(rename_all = "camelCase")]
416pub struct AuthenticateParams {
417 pub method: AuthMethod,
419
420 pub credentials: AuthCredentials,
422}
423
424#[derive(Debug, Clone, Serialize, Deserialize)]
426#[serde(tag = "type", rename_all = "snake_case")]
427pub enum AuthCredentials {
428 ApiKey { key: String },
430
431 Bearer { token: String },
433
434 OAuth2 {
436 access_token: String,
437 #[serde(skip_serializing_if = "Option::is_none")]
438 refresh_token: Option<String>,
439 },
440}
441
442#[derive(Debug, Clone, Serialize, Deserialize)]
444#[serde(rename_all = "camelCase")]
445pub struct AuthenticateResult {
446 pub authenticated: bool,
448
449 #[serde(skip_serializing_if = "Option::is_none")]
451 pub session_token: Option<String>,
452
453 #[serde(skip_serializing_if = "Option::is_none")]
455 pub expires_at: Option<String>,
456}
457
458#[cfg(test)]
459mod tests {
460 use super::*;
461
462 #[test]
463 fn test_initialize_params_default() {
464 let params = InitializeParams::default();
465 assert!(!params.protocol_versions.is_empty());
466 assert!(
467 params
468 .protocol_versions
469 .contains(&PROTOCOL_VERSION.to_string())
470 );
471 }
472
473 #[test]
474 fn test_client_info_default() {
475 let info = ClientInfo::default();
476 assert_eq!(info.name, "vtcode");
477 assert!(!info.version.is_empty());
478 }
479
480 #[test]
481 fn test_capabilities_serialization() {
482 let caps = ClientCapabilities {
483 filesystem: FilesystemCapabilities {
484 read: true,
485 write: true,
486 list: true,
487 search: true,
488 watch: false,
489 },
490 terminal: TerminalCapabilities {
491 create: true,
492 input: true,
493 output: true,
494 pty: true,
495 },
496 ..Default::default()
497 };
498
499 let json = serde_json::to_value(&caps).unwrap();
500 assert_eq!(json["filesystem"]["read"], true);
501 assert_eq!(json["terminal"]["pty"], true);
502 }
503
504 #[test]
505 fn test_auth_credentials() {
506 let creds = AuthCredentials::ApiKey {
507 key: "sk-test123".to_string(),
508 };
509 let json = serde_json::to_value(&creds).unwrap();
510 assert_eq!(json["type"], "api_key");
511 assert_eq!(json["key"], "sk-test123");
512 }
513
514 #[test]
515 fn test_auth_method_agent() {
516 let method = AuthMethod::Agent {
517 id: "agent_auth".to_string(),
518 name: "Agent Authentication".to_string(),
519 description: Some("Let agent handle authentication".to_string()),
520 };
521 let json = serde_json::to_value(&method).unwrap();
522 assert_eq!(json["type"], "agent");
523 assert_eq!(json["id"], "agent_auth");
524 assert_eq!(json["name"], "Agent Authentication");
525 }
526
527 #[test]
528 fn test_auth_method_env_var() {
529 let method = AuthMethod::EnvVar {
530 id: "openai_key".to_string(),
531 name: "OpenAI API Key".to_string(),
532 description: Some("Provide your OpenAI API key".to_string()),
533 var_name: "OPENAI_API_KEY".to_string(),
534 link: Some("https://platform.openai.com/api-keys".to_string()),
535 };
536 let json = serde_json::to_value(&method).unwrap();
537 assert_eq!(json["type"], "env_var");
538 assert_eq!(json["id"], "openai_key");
539 assert_eq!(json["name"], "OpenAI API Key");
540 assert_eq!(json["var_name"], "OPENAI_API_KEY");
541 assert_eq!(json["link"], "https://platform.openai.com/api-keys");
542 }
543
544 #[test]
545 fn test_auth_method_terminal() {
546 let mut env = HashMap::new();
547 env.insert("VAR1".to_string(), "value1".to_string());
548
549 let method = AuthMethod::Terminal {
550 id: "terminal_login".to_string(),
551 name: "Terminal Login".to_string(),
552 description: Some("Login via interactive terminal".to_string()),
553 args: vec!["--login".to_string(), "--interactive".to_string()],
554 env,
555 };
556 let json = serde_json::to_value(&method).unwrap();
557 assert_eq!(json["type"], "terminal");
558 assert_eq!(json["args"][0], "--login");
559 assert_eq!(json["env"]["VAR1"], "value1");
560 }
561
562 #[test]
563 fn test_auth_method_serialization_roundtrip() {
564 let method = AuthMethod::EnvVar {
565 id: "test_id".to_string(),
566 name: "Test".to_string(),
567 description: None,
568 var_name: "TEST_VAR".to_string(),
569 link: None,
570 };
571
572 let json = serde_json::to_value(&method).unwrap();
573 let deserialized: AuthMethod = serde_json::from_value(json).unwrap();
574
575 match deserialized {
576 AuthMethod::EnvVar {
577 id, name, var_name, ..
578 } => {
579 assert_eq!(id, "test_id");
580 assert_eq!(name, "Test");
581 assert_eq!(var_name, "TEST_VAR");
582 }
583 _ => panic!("Unexpected auth method variant"),
584 }
585 }
586
587 #[test]
588 fn test_legacy_auth_methods() {
589 let json = serde_json::json!({"type": "api_key"});
591 let method: AuthMethod = serde_json::from_value(json).unwrap();
592 matches!(method, AuthMethod::ApiKey);
593
594 let json = serde_json::json!({"type": "oauth2"});
595 let method: AuthMethod = serde_json::from_value(json).unwrap();
596 matches!(method, AuthMethod::OAuth2);
597
598 let json = serde_json::json!({"type": "bearer"});
599 let method: AuthMethod = serde_json::from_value(json).unwrap();
600 matches!(method, AuthMethod::Bearer);
601 }
602}