use serde::{Deserialize, Serialize};
use serde_json::Value;
pub(crate) mod method {
pub const INITIALIZE: &str = "initialize";
#[allow(dead_code)]
pub const AUTHENTICATE: &str = "authenticate";
pub const SESSION_NEW: &str = "session/new";
pub const SESSION_LOAD: &str = "session/load";
pub const SESSION_PROMPT: &str = "session/prompt";
pub const SESSION_CANCEL: &str = "session/cancel";
pub const SESSION_UPDATE: &str = "session/update";
#[allow(dead_code)]
pub const REQUEST_PERMISSION: &str = "session/request_permission";
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct InitializeParams {
pub protocol_version: u32,
#[serde(default)]
pub client_capabilities: super::capabilities::ClientCapabilities,
#[serde(default)]
pub client_info: ClientInfo,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ClientInfo {
#[serde(default)]
pub name: String,
#[serde(default)]
pub version: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct WireAuthMethod {
pub id: String,
#[serde(default)]
pub name: String,
#[serde(default)]
pub description: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct InitializeResult {
#[serde(default)]
pub protocol_version: u32,
#[serde(default)]
pub agent_capabilities: super::capabilities::AgentCapabilities,
#[serde(default)]
pub auth_methods: Vec<WireAuthMethod>,
#[serde(flatten)]
pub extra: Value,
}
#[allow(dead_code)]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct AuthenticateParams {
pub auth_method: String,
}
#[allow(dead_code)]
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct AuthenticateResult {
#[serde(default)]
pub success: bool,
#[serde(flatten)]
pub extra: Value,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct SessionNewParams {
#[serde(default)]
pub cwd: String,
#[serde(default)]
pub mcp_servers: Vec<Value>,
#[serde(flatten)]
pub extra: Value,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct SessionNewResult {
#[serde(default)]
pub session_id: String,
#[serde(flatten)]
pub extra: Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct SessionLoadParams {
pub session_id: String,
#[serde(flatten)]
pub extra: Value,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct SessionLoadResult {
#[serde(default)]
pub session_id: String,
#[serde(flatten)]
pub extra: Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct SessionPromptParams {
pub session_id: String,
pub prompt: Vec<super::content::WireContentBlock>,
#[serde(flatten)]
pub extra: Value,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct SessionPromptResult {
#[serde(default)]
pub stop_reason: String,
#[serde(flatten)]
pub extra: Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct SessionCancelParams {
pub session_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct RequestPermissionParams {
pub session_id: String,
pub tool_call: PermissionToolCall,
#[serde(default)]
pub options: Vec<PermissionOption>,
#[serde(flatten)]
pub extra: Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct PermissionToolCall {
pub tool_call_id: String,
#[serde(default)]
pub title: String,
#[serde(default)]
pub kind: String,
#[serde(default)]
pub raw_input: Value,
#[serde(default)]
pub locations: Vec<ToolLocation>,
#[serde(flatten)]
pub extra: Value,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ToolLocation {
#[serde(default)]
pub uri: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub range: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct PermissionOption {
#[serde(alias = "optionId")]
pub id: String,
#[serde(default, alias = "name")]
pub label: String,
#[serde(default)]
pub description: String,
#[serde(flatten)]
pub extra: Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct RequestPermissionResponse {
pub outcome: RequestPermissionOutcome,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct RequestPermissionOutcome {
pub outcome: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub option_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub updated_input: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub interrupt: Option<bool>,
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_initialize_params_roundtrip() {
let raw = json!({
"protocolVersion": 1,
"clientCapabilities": {},
"clientInfo": {
"name": "pom-desktop",
"version": "0.1.0"
}
});
let params: InitializeParams =
serde_json::from_value(raw.clone()).expect("deserialize InitializeParams");
assert_eq!(params.protocol_version, 1);
assert_eq!(params.client_info.name, "pom-desktop");
assert_eq!(params.client_info.version, "0.1.0");
let back = serde_json::to_value(¶ms).expect("re-serialize InitializeParams");
assert_eq!(back["protocolVersion"], 1);
assert_eq!(back["clientInfo"]["name"], "pom-desktop");
assert_eq!(back["clientInfo"]["version"], "0.1.0");
}
#[test]
fn test_initialize_result_extra_fields_preserved() {
let raw = json!({
"protocolVersion": 1,
"agentCapabilities": { "streaming": true },
"authMethods": [
{ "id": "oauth-personal", "name": "Log in with Google", "description": null },
{ "id": "gemini-api-key", "name": "Use Gemini API key" }
],
"serverName": "gemini-cli/1.0.0"
});
let result: InitializeResult =
serde_json::from_value(raw.clone()).expect("deserialize InitializeResult");
assert_eq!(result.protocol_version, 1);
assert_eq!(result.auth_methods.len(), 2);
assert_eq!(result.auth_methods[0].id, "oauth-personal");
assert_eq!(result.auth_methods[1].id, "gemini-api-key");
assert_eq!(result.auth_methods[1].description, None);
let back = serde_json::to_value(&result).expect("re-serialize InitializeResult");
assert_eq!(back["serverName"], "gemini-cli/1.0.0", "extra field must survive roundtrip");
}
#[test]
fn test_permission_request_roundtrip() {
let raw = json!({
"sessionId": "sess-abc",
"toolCall": {
"toolCallId": "tc-001",
"title": "Run shell command",
"kind": "shell",
"rawInput": { "command": "ls -la" },
"locations": [{ "uri": "file:///home/user/project" }]
},
"options": [
{ "id": "allow_once", "label": "Allow once", "description": "" },
{ "id": "deny", "label": "Deny", "description": "" }
]
});
let params: RequestPermissionParams =
serde_json::from_value(raw.clone()).expect("deserialize RequestPermissionParams");
assert_eq!(params.session_id, "sess-abc");
assert_eq!(params.tool_call.tool_call_id, "tc-001");
assert_eq!(params.tool_call.kind, "shell");
assert_eq!(params.options.len(), 2);
assert_eq!(params.options[0].id, "allow_once");
assert_eq!(params.options[1].id, "deny");
assert_eq!(params.tool_call.locations[0].uri, "file:///home/user/project");
let back = serde_json::to_value(¶ms).expect("re-serialize RequestPermissionParams");
assert_eq!(back["sessionId"], "sess-abc");
assert_eq!(back["toolCall"]["toolCallId"], "tc-001");
}
#[test]
fn test_permission_response_selected() {
let resp = RequestPermissionResponse {
outcome: RequestPermissionOutcome {
outcome: "selected".to_string(),
option_id: Some("allow_once".to_string()),
updated_input: None,
interrupt: None,
},
};
let v = serde_json::to_value(&resp).expect("serialize");
let outcome = &v["outcome"];
assert_eq!(outcome["outcome"], "selected");
assert_eq!(outcome["optionId"], "allow_once");
assert!(outcome.get("interrupt").is_none(), "interrupt must be absent when None");
}
#[test]
fn test_permission_response_cancelled_omits_option_id() {
let resp = RequestPermissionResponse {
outcome: RequestPermissionOutcome {
outcome: "cancelled".to_string(),
option_id: None,
updated_input: None,
interrupt: None,
},
};
let v = serde_json::to_value(&resp).expect("serialize");
let outcome = &v["outcome"];
assert_eq!(outcome["outcome"], "cancelled");
assert!(outcome.get("optionId").is_none(), "optionId must be absent when cancelled");
assert!(outcome.get("interrupt").is_none(), "interrupt must be absent when None");
}
#[test]
fn test_permission_response_interrupt_true_serialized() {
let resp = RequestPermissionResponse {
outcome: RequestPermissionOutcome {
outcome: "cancelled".to_string(),
option_id: None,
updated_input: None,
interrupt: Some(true),
},
};
let v = serde_json::to_value(&resp).expect("serialize");
let outcome = &v["outcome"];
assert_eq!(outcome["outcome"], "cancelled");
assert_eq!(outcome["interrupt"], true, "interrupt: Some(true) must serialize as true");
}
#[test]
fn test_permission_response_interrupt_none_omitted() {
let resp = RequestPermissionResponse {
outcome: RequestPermissionOutcome {
outcome: "cancelled".to_string(),
option_id: None,
updated_input: None,
interrupt: None,
},
};
let v = serde_json::to_value(&resp).expect("serialize");
let outcome = &v["outcome"];
assert!(outcome.get("interrupt").is_none(), "interrupt: None must be omitted from JSON");
}
#[test]
fn test_session_prompt_params_roundtrip() {
let raw = json!({
"sessionId": "sess-xyz",
"prompt": [
{ "type": "text", "text": "Hello" }
]
});
let params: SessionPromptParams =
serde_json::from_value(raw).expect("deserialize SessionPromptParams");
assert_eq!(params.session_id, "sess-xyz");
assert_eq!(params.prompt.len(), 1);
assert_eq!(params.prompt[0].content_type, "text");
assert_eq!(params.prompt[0].text.as_deref(), Some("Hello"));
}
}