use serde::{Deserialize, Serialize};
use serde_json::Value;
pub const METHOD_SESSION_INITIALIZE: &str = "session/initialize";
pub const METHOD_SESSION_NEW: &str = "session/new";
pub const METHOD_SESSION_PROMPT: &str = "session/prompt";
pub const METHOD_SESSION_UPDATE: &str = "session/update";
pub const METHOD_SESSION_CANCEL: &str = "session/cancel";
pub const METHOD_SESSION_CLOSE: &str = "session/close";
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InitializeParams {
#[serde(default)]
pub client_name: String,
#[serde(default)]
pub client_version: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AcpCapabilities {
pub text_prompts: bool,
pub tool_calls: bool,
pub file_operations: bool,
pub session_resume: bool,
pub modes: Vec<String>,
}
impl Default for AcpCapabilities {
fn default() -> Self {
Self {
text_prompts: true,
tool_calls: true,
file_operations: true,
session_resume: true,
modes: vec![
"code".to_string(),
"ask".to_string(),
"architect".to_string(),
],
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InitializeResult {
pub name: String,
pub version: String,
pub capabilities: AcpCapabilities,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionNewParams {
pub cwd: String,
#[serde(default)]
pub mcp_servers: Vec<McpServerConfig>,
#[serde(default = "default_mode")]
pub mode: String,
}
fn default_mode() -> String {
"code".to_string()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpServerConfig {
pub name: String,
#[serde(default)]
pub command: Option<String>,
#[serde(default)]
pub args: Vec<String>,
#[serde(default)]
pub env: std::collections::HashMap<String, String>,
#[serde(default)]
pub url: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionNewResult {
pub session_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionPromptParams {
pub session_id: String,
pub text: String,
#[serde(default)]
pub mode: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionPromptResult {
pub accepted: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum UpdatePayload {
Token {
text: String,
},
Response {
text: String,
},
ToolCall {
name: String,
args: String,
},
ToolResult {
name: String,
result: String,
success: bool,
},
FileModified {
path: String,
},
Status {
iteration: u32,
elapsed_secs: u64,
},
Phase {
label: String,
},
Plan {
text: String,
},
Error {
message: String,
},
Done,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionUpdateNotification {
pub session_id: String,
pub payload: UpdatePayload,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionCancelParams {
pub session_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionCancelResult {
pub cancelled: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionCloseParams {
pub session_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionCloseResult {
pub closed: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonRpcNotification {
pub jsonrpc: String,
pub method: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub params: Option<Value>,
}
impl JsonRpcNotification {
pub fn new(method: &str, params: Value) -> Self {
Self {
jsonrpc: "2.0".to_string(),
method: method.to_string(),
params: Some(params),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_initialize_params_roundtrip() {
let params = InitializeParams {
client_name: "IntelliJ IDEA".to_string(),
client_version: "2026.1".to_string(),
};
let json = serde_json::to_value(¶ms).unwrap();
let decoded: InitializeParams = serde_json::from_value(json).unwrap();
assert_eq!(decoded.client_name, "IntelliJ IDEA");
}
#[test]
fn test_capabilities_default() {
let caps = AcpCapabilities::default();
assert!(caps.text_prompts);
assert!(caps.tool_calls);
assert!(caps.session_resume);
assert_eq!(caps.modes.len(), 3);
}
#[test]
fn test_session_new_params_roundtrip() {
let json = json!({
"cwd": "/home/user/project",
"mode": "ask"
});
let params: SessionNewParams = serde_json::from_value(json).unwrap();
assert_eq!(params.cwd, "/home/user/project");
assert_eq!(params.mode, "ask");
assert!(params.mcp_servers.is_empty());
}
#[test]
fn test_update_payload_tagged() {
let payload = UpdatePayload::Token {
text: "hello".to_string(),
};
let json = serde_json::to_value(&payload).unwrap();
assert_eq!(json["type"], "token");
assert_eq!(json["text"], "hello");
}
#[test]
fn test_update_payload_done() {
let payload = UpdatePayload::Done;
let json = serde_json::to_value(&payload).unwrap();
assert_eq!(json["type"], "done");
}
#[test]
fn test_notification_no_id() {
let notif = JsonRpcNotification::new(
"session/update",
json!({"session_id": "abc", "payload": {"type": "done"}}),
);
let serialized = serde_json::to_string(¬if).unwrap();
assert!(!serialized.contains("\"id\""));
assert!(serialized.contains("session/update"));
}
}