Skip to main content

par_term_acp/protocol/
session.rs

1//! Session management types for the ACP protocol.
2//!
3//! Covers `session/new`, `session/load`, `session/prompt`, and `session/update`
4//! messages including all session-update discriminated variants.
5
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8
9/// Parameters for the `session/new` request.
10#[derive(Debug, Clone, Serialize, Deserialize)]
11#[serde(rename_all = "camelCase")]
12pub struct SessionNewParams {
13    pub cwd: String,
14    #[serde(skip_serializing_if = "Option::is_none")]
15    pub mcp_servers: Option<Vec<Value>>,
16    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
17    pub meta: Option<Value>,
18}
19
20/// Parameters for the `session/load` request.
21#[derive(Debug, Clone, Serialize, Deserialize)]
22#[serde(rename_all = "camelCase")]
23pub struct SessionLoadParams {
24    pub cwd: String,
25    pub session_id: String,
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub mcp_servers: Option<Vec<Value>>,
28}
29
30/// Result returned after creating or loading a session.
31#[derive(Debug, Clone, Serialize, Deserialize)]
32#[serde(rename_all = "camelCase")]
33pub struct SessionResult {
34    pub session_id: String,
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub modes: Option<ModesInfo>,
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub models: Option<ModelsInfo>,
39}
40
41/// Available interaction modes reported by the agent.
42#[derive(Debug, Clone, Serialize, Deserialize)]
43#[serde(rename_all = "camelCase")]
44pub struct ModesInfo {
45    pub available_modes: Vec<ModeEntry>,
46    pub current_mode_id: String,
47}
48
49/// A single interaction mode.
50#[derive(Debug, Clone, Serialize, Deserialize)]
51#[serde(rename_all = "camelCase")]
52pub struct ModeEntry {
53    pub id: String,
54    pub name: String,
55    #[serde(skip_serializing_if = "Option::is_none")]
56    pub description: Option<String>,
57}
58
59/// Available models reported by the agent.
60#[derive(Debug, Clone, Serialize, Deserialize)]
61#[serde(rename_all = "camelCase")]
62pub struct ModelsInfo {
63    pub available_models: Vec<ModelEntry>,
64    pub current_model_id: String,
65}
66
67/// A single model the agent can use.
68#[derive(Debug, Clone, Serialize, Deserialize)]
69#[serde(rename_all = "camelCase")]
70pub struct ModelEntry {
71    pub model_id: String,
72    pub name: String,
73    #[serde(skip_serializing_if = "Option::is_none")]
74    pub description: Option<String>,
75}
76
77/// Parameters for the `session/prompt` request.
78#[derive(Debug, Clone, Serialize, Deserialize)]
79#[serde(rename_all = "camelCase")]
80pub struct SessionPromptParams {
81    pub session_id: String,
82    pub prompt: Vec<super::content::ContentBlock>,
83}
84
85/// Result returned after a prompt completes.
86#[derive(Debug, Clone, Serialize, Deserialize)]
87#[serde(rename_all = "camelCase")]
88pub struct SessionPromptResult {
89    #[serde(skip_serializing_if = "Option::is_none")]
90    pub stop_reason: Option<String>,
91}
92
93/// Parameters for the `session/update` notification.
94#[derive(Debug, Clone, Serialize, Deserialize)]
95#[serde(rename_all = "camelCase")]
96pub struct SessionUpdateParams {
97    pub session_id: String,
98    pub update: Value,
99}
100
101/// A parsed session update. These are **not** serde-derived because the
102/// `sessionUpdate` discriminator field requires manual dispatch.
103#[derive(Debug, Clone)]
104pub enum SessionUpdate {
105    /// A chunk of the agent's response text.
106    AgentMessageChunk { text: String },
107    /// A chunk of the agent's internal reasoning.
108    AgentThoughtChunk { text: String },
109    /// A chunk echoing the user's message.
110    UserMessageChunk { text: String },
111    /// A new or updated tool call.
112    ToolCall(ToolCallInfo),
113    /// An incremental update to an existing tool call.
114    ToolCallUpdate(ToolCallUpdateInfo),
115    /// The agent's current plan.
116    Plan(PlanInfo),
117    /// Updated list of available slash commands.
118    AvailableCommandsUpdate(Vec<AgentCommand>),
119    /// The agent switched interaction mode.
120    CurrentModeUpdate { mode_id: String },
121    /// Unrecognized update type — preserved as raw JSON.
122    Unknown(Value),
123}
124
125impl SessionUpdate {
126    /// Parse a session update from its raw JSON [`Value`].
127    ///
128    /// The value is expected to have a `"sessionUpdate"` string field that
129    /// acts as a type discriminator.
130    pub fn from_value(value: &Value) -> Self {
131        let update_type = value
132            .get("sessionUpdate")
133            .and_then(|v| v.as_str())
134            .unwrap_or("");
135
136        match update_type {
137            "agent_message_chunk" => {
138                let text = value
139                    .get("content")
140                    .and_then(|c| c.get("text"))
141                    .and_then(|t| t.as_str())
142                    .unwrap_or("")
143                    .to_string();
144                Self::AgentMessageChunk { text }
145            }
146            "agent_thought_chunk" => {
147                let text = value
148                    .get("content")
149                    .and_then(|c| c.get("text"))
150                    .and_then(|t| t.as_str())
151                    .unwrap_or("")
152                    .to_string();
153                Self::AgentThoughtChunk { text }
154            }
155            "user_message_chunk" => {
156                let text = value
157                    .get("content")
158                    .and_then(|c| c.get("text"))
159                    .and_then(|t| t.as_str())
160                    .unwrap_or("")
161                    .to_string();
162                Self::UserMessageChunk { text }
163            }
164            "tool_call" => {
165                let tool_call_id = value
166                    .get("toolCallId")
167                    .and_then(|v| v.as_str())
168                    .unwrap_or("")
169                    .to_string();
170                let title = value
171                    .get("title")
172                    .and_then(|v| v.as_str())
173                    .unwrap_or("")
174                    .to_string();
175                let kind = value
176                    .get("kind")
177                    .and_then(|v| v.as_str())
178                    .unwrap_or("")
179                    .to_string();
180                let status = value
181                    .get("status")
182                    .and_then(|v| v.as_str())
183                    .unwrap_or("")
184                    .to_string();
185                let content = value.get("content").cloned();
186                Self::ToolCall(ToolCallInfo {
187                    tool_call_id,
188                    title,
189                    kind,
190                    status,
191                    content,
192                })
193            }
194            "tool_call_update" => {
195                let tool_call_id = value
196                    .get("toolCallId")
197                    .and_then(|v| v.as_str())
198                    .unwrap_or("")
199                    .to_string();
200                let status = value
201                    .get("status")
202                    .and_then(|v| v.as_str())
203                    .map(String::from);
204                let title = value
205                    .get("title")
206                    .and_then(|v| v.as_str())
207                    .map(String::from);
208                let content = value.get("content").cloned();
209                Self::ToolCallUpdate(ToolCallUpdateInfo {
210                    tool_call_id,
211                    status,
212                    title,
213                    content,
214                })
215            }
216            "plan" => {
217                let entries = value
218                    .get("entries")
219                    .and_then(|v| v.as_array())
220                    .map(|arr| {
221                        arr.iter()
222                            .map(|entry| PlanEntry {
223                                content: entry
224                                    .get("content")
225                                    .and_then(|v| v.as_str())
226                                    .unwrap_or("")
227                                    .to_string(),
228                                status: entry
229                                    .get("status")
230                                    .and_then(|v| v.as_str())
231                                    .unwrap_or("")
232                                    .to_string(),
233                            })
234                            .collect()
235                    })
236                    .unwrap_or_default();
237                Self::Plan(PlanInfo { entries })
238            }
239            "available_commands_update" => {
240                let commands = value
241                    .get("commands")
242                    .and_then(|v| v.as_array())
243                    .map(|arr| {
244                        arr.iter()
245                            .map(|cmd| AgentCommand {
246                                name: cmd
247                                    .get("name")
248                                    .and_then(|v| v.as_str())
249                                    .unwrap_or("")
250                                    .to_string(),
251                                description: cmd
252                                    .get("description")
253                                    .and_then(|v| v.as_str())
254                                    .map(String::from),
255                            })
256                            .collect()
257                    })
258                    .unwrap_or_default();
259                Self::AvailableCommandsUpdate(commands)
260            }
261            "current_mode_update" => {
262                let mode_id = value
263                    .get("modeId")
264                    .and_then(|v| v.as_str())
265                    .unwrap_or("")
266                    .to_string();
267                Self::CurrentModeUpdate { mode_id }
268            }
269            _ => Self::Unknown(value.clone()),
270        }
271    }
272}
273
274/// Information about a tool call initiated by the agent.
275#[derive(Debug, Clone)]
276pub struct ToolCallInfo {
277    pub tool_call_id: String,
278    pub title: String,
279    pub kind: String,
280    pub status: String,
281    pub content: Option<Value>,
282}
283
284/// An incremental update to an in-progress tool call.
285#[derive(Debug, Clone)]
286pub struct ToolCallUpdateInfo {
287    pub tool_call_id: String,
288    pub status: Option<String>,
289    pub title: Option<String>,
290    pub content: Option<Value>,
291}
292
293/// The agent's current execution plan.
294#[derive(Debug, Clone)]
295pub struct PlanInfo {
296    pub entries: Vec<PlanEntry>,
297}
298
299/// A single step in the agent's plan.
300#[derive(Debug, Clone)]
301pub struct PlanEntry {
302    pub content: String,
303    pub status: String,
304}
305
306/// A slash-command or action the agent exposes.
307#[derive(Debug, Clone)]
308pub struct AgentCommand {
309    pub name: String,
310    pub description: Option<String>,
311}