Skip to main content

skiff_cli/session/
protocol.rs

1//! NDJSON IPC protocol for session daemons.
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
7#[serde(rename_all = "snake_case")]
8pub enum SessionMethod {
9    ListTools,
10    ListToolsLight,
11    GetTool,
12    CallTool,
13    ListResources,
14    ReadResource,
15    ListResourceTemplates,
16    ListPrompts,
17    GetPrompt,
18}
19
20impl SessionMethod {
21    pub fn as_str(self) -> &'static str {
22        match self {
23            Self::ListTools => "list_tools",
24            Self::ListToolsLight => "list_tools_light",
25            Self::GetTool => "get_tool",
26            Self::CallTool => "call_tool",
27            Self::ListResources => "list_resources",
28            Self::ReadResource => "read_resource",
29            Self::ListResourceTemplates => "list_resource_templates",
30            Self::ListPrompts => "list_prompts",
31            Self::GetPrompt => "get_prompt",
32        }
33    }
34
35    pub fn parse(s: &str) -> Option<Self> {
36        Some(match s {
37            "list_tools" => Self::ListTools,
38            "list_tools_light" => Self::ListToolsLight,
39            "get_tool" => Self::GetTool,
40            "call_tool" => Self::CallTool,
41            "list_resources" => Self::ListResources,
42            "read_resource" => Self::ReadResource,
43            "list_resource_templates" => Self::ListResourceTemplates,
44            "list_prompts" => Self::ListPrompts,
45            "get_prompt" => Self::GetPrompt,
46            _ => return None,
47        })
48    }
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct SessionRequest {
53    pub id: u64,
54    pub method: String,
55    #[serde(default)]
56    pub params: Value,
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct SessionResponse {
61    pub id: u64,
62    #[serde(skip_serializing_if = "Option::is_none")]
63    pub result: Option<Value>,
64    #[serde(skip_serializing_if = "Option::is_none")]
65    pub error: Option<String>,
66}
67
68impl SessionResponse {
69    pub fn ok(id: u64, result: Value) -> Self {
70        Self {
71            id,
72            result: Some(result),
73            error: None,
74        }
75    }
76
77    pub fn err(id: u64, error: impl Into<String>) -> Self {
78        Self {
79            id,
80            result: None,
81            error: Some(error.into()),
82        }
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89    use serde_json::json;
90
91    #[test]
92    fn roundtrip() {
93        let req = SessionRequest {
94            id: 1,
95            method: "list_tools".into(),
96            params: json!({"refresh": true}),
97        };
98        let line = serde_json::to_string(&req).unwrap();
99        let back: SessionRequest = serde_json::from_str(&line).unwrap();
100        assert_eq!(back.method, "list_tools");
101        assert_eq!(
102            SessionMethod::parse(&back.method),
103            Some(SessionMethod::ListTools)
104        );
105    }
106}