use hashbrown::HashMap;
use serde::{Deserialize, Serialize};
use serde_json::Value;
pub const PROTOCOL_VERSION: &str = "2025-01-01";
pub const SUPPORTED_VERSIONS: &[&str] = &["2025-01-01", "2024-11-01"];
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct InitializeParams {
pub protocol_versions: Vec<String>,
pub capabilities: ClientCapabilities,
pub client_info: ClientInfo,
}
impl Default for InitializeParams {
fn default() -> Self {
Self {
protocol_versions: SUPPORTED_VERSIONS.iter().map(|s| s.to_string()).collect(),
capabilities: ClientCapabilities::default(),
client_info: ClientInfo::default(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct InitializeResult {
pub protocol_version: String,
pub capabilities: AgentCapabilities,
pub agent_info: AgentInfo,
#[serde(skip_serializing_if = "Option::is_none")]
pub auth_requirements: Option<AuthRequirements>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ClientCapabilities {
#[serde(default)]
pub filesystem: FilesystemCapabilities,
#[serde(default)]
pub terminal: TerminalCapabilities,
#[serde(default)]
pub ui: UiCapabilities,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub mcp_servers: Vec<McpServerCapability>,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub extensions: HashMap<String, Value>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FilesystemCapabilities {
#[serde(default)]
pub read: bool,
#[serde(default)]
pub write: bool,
#[serde(default)]
pub list: bool,
#[serde(default)]
pub search: bool,
#[serde(default)]
pub watch: bool,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TerminalCapabilities {
#[serde(default)]
pub create: bool,
#[serde(default)]
pub input: bool,
#[serde(default)]
pub output: bool,
#[serde(default)]
pub pty: bool,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UiCapabilities {
#[serde(default)]
pub notifications: bool,
#[serde(default)]
pub progress: bool,
#[serde(default)]
pub input_prompt: bool,
#[serde(default)]
pub diff_view: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct McpServerCapability {
pub name: String,
pub transport: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tools: Vec<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AgentCapabilities {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tools: Vec<ToolCapability>,
#[serde(default)]
pub features: AgentFeatures,
#[serde(skip_serializing_if = "Option::is_none")]
pub model: Option<ModelInfo>,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub extensions: HashMap<String, Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ToolCapability {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub input_schema: Option<Value>,
#[serde(default)]
pub requires_confirmation: bool,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AgentFeatures {
#[serde(default)]
pub streaming: bool,
#[serde(default)]
pub multi_turn: bool,
#[serde(default)]
pub session_persistence: bool,
#[serde(default)]
pub vision: bool,
#[serde(default)]
pub code_execution: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ModelInfo {
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub provider: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub context_window: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClientInfo {
pub name: String,
pub version: String,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub metadata: HashMap<String, Value>,
}
impl Default for ClientInfo {
fn default() -> Self {
Self {
name: "vtcode".to_string(),
version: env!("CARGO_PKG_VERSION").to_string(),
metadata: HashMap::new(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentInfo {
pub name: String,
pub version: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub metadata: HashMap<String, Value>,
}
impl Default for AgentInfo {
fn default() -> Self {
Self {
name: "vtcode-agent".to_string(),
version: env!("CARGO_PKG_VERSION").to_string(),
description: Some("VT Code AI coding agent".to_string()),
metadata: HashMap::new(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AuthRequirements {
pub required: bool,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub methods: Vec<AuthMethod>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum AuthMethod {
#[serde(rename = "agent")]
Agent {
id: String,
name: String,
#[serde(skip_serializing_if = "Option::is_none")]
description: Option<String>,
},
#[serde(rename = "env_var")]
EnvVar {
id: String,
name: String,
#[serde(skip_serializing_if = "Option::is_none")]
description: Option<String>,
var_name: String,
#[serde(skip_serializing_if = "Option::is_none")]
link: Option<String>,
},
#[serde(rename = "terminal")]
Terminal {
id: String,
name: String,
#[serde(skip_serializing_if = "Option::is_none")]
description: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
args: Vec<String>,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
env: HashMap<String, String>,
},
#[serde(rename = "api_key")]
ApiKey,
#[serde(rename = "oauth2")]
OAuth2,
#[serde(rename = "bearer")]
Bearer,
#[serde(rename = "custom")]
Custom(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AuthenticateParams {
pub method: AuthMethod,
pub credentials: AuthCredentials,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum AuthCredentials {
ApiKey { key: String },
Bearer { token: String },
OAuth2 {
access_token: String,
#[serde(skip_serializing_if = "Option::is_none")]
refresh_token: Option<String>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AuthenticateResult {
pub authenticated: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub session_token: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expires_at: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_initialize_params_default() {
let params = InitializeParams::default();
assert!(!params.protocol_versions.is_empty());
assert!(
params
.protocol_versions
.contains(&PROTOCOL_VERSION.to_string())
);
}
#[test]
fn test_client_info_default() {
let info = ClientInfo::default();
assert_eq!(info.name, "vtcode");
assert!(!info.version.is_empty());
}
#[test]
fn test_capabilities_serialization() {
let caps = ClientCapabilities {
filesystem: FilesystemCapabilities {
read: true,
write: true,
list: true,
search: true,
watch: false,
},
terminal: TerminalCapabilities {
create: true,
input: true,
output: true,
pty: true,
},
..Default::default()
};
let json = serde_json::to_value(&caps).unwrap();
assert_eq!(json["filesystem"]["read"], true);
assert_eq!(json["terminal"]["pty"], true);
}
#[test]
fn test_auth_credentials() {
let creds = AuthCredentials::ApiKey {
key: "sk-test123".to_string(),
};
let json = serde_json::to_value(&creds).unwrap();
assert_eq!(json["type"], "api_key");
assert_eq!(json["key"], "sk-test123");
}
#[test]
fn test_auth_method_agent() {
let method = AuthMethod::Agent {
id: "agent_auth".to_string(),
name: "Agent Authentication".to_string(),
description: Some("Let agent handle authentication".to_string()),
};
let json = serde_json::to_value(&method).unwrap();
assert_eq!(json["type"], "agent");
assert_eq!(json["id"], "agent_auth");
assert_eq!(json["name"], "Agent Authentication");
}
#[test]
fn test_auth_method_env_var() {
let method = AuthMethod::EnvVar {
id: "openai_key".to_string(),
name: "OpenAI API Key".to_string(),
description: Some("Provide your OpenAI API key".to_string()),
var_name: "OPENAI_API_KEY".to_string(),
link: Some("https://platform.openai.com/api-keys".to_string()),
};
let json = serde_json::to_value(&method).unwrap();
assert_eq!(json["type"], "env_var");
assert_eq!(json["id"], "openai_key");
assert_eq!(json["name"], "OpenAI API Key");
assert_eq!(json["var_name"], "OPENAI_API_KEY");
assert_eq!(json["link"], "https://platform.openai.com/api-keys");
}
#[test]
fn test_auth_method_terminal() {
let mut env = HashMap::new();
env.insert("VAR1".to_string(), "value1".to_string());
let method = AuthMethod::Terminal {
id: "terminal_login".to_string(),
name: "Terminal Login".to_string(),
description: Some("Login via interactive terminal".to_string()),
args: vec!["--login".to_string(), "--interactive".to_string()],
env,
};
let json = serde_json::to_value(&method).unwrap();
assert_eq!(json["type"], "terminal");
assert_eq!(json["args"][0], "--login");
assert_eq!(json["env"]["VAR1"], "value1");
}
#[test]
fn test_auth_method_serialization_roundtrip() {
let method = AuthMethod::EnvVar {
id: "test_id".to_string(),
name: "Test".to_string(),
description: None,
var_name: "TEST_VAR".to_string(),
link: None,
};
let json = serde_json::to_value(&method).unwrap();
let deserialized: AuthMethod = serde_json::from_value(json).unwrap();
match deserialized {
AuthMethod::EnvVar {
id, name, var_name, ..
} => {
assert_eq!(id, "test_id");
assert_eq!(name, "Test");
assert_eq!(var_name, "TEST_VAR");
}
_ => panic!("Unexpected auth method variant"),
}
}
#[test]
fn test_legacy_auth_methods() {
let json = serde_json::json!({"type": "api_key"});
let method: AuthMethod = serde_json::from_value(json).unwrap();
matches!(method, AuthMethod::ApiKey);
let json = serde_json::json!({"type": "oauth2"});
let method: AuthMethod = serde_json::from_value(json).unwrap();
matches!(method, AuthMethod::OAuth2);
let json = serde_json::json!({"type": "bearer"});
let method: AuthMethod = serde_json::from_value(json).unwrap();
matches!(method, AuthMethod::Bearer);
}
}