1use serde::{Deserialize, Serialize};
12use serde_json::Value;
13use std::collections::HashMap;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
17#[serde(rename_all = "snake_case")]
18#[derive(Default)]
19pub enum SessionState {
20 #[default]
22 Created,
23 Active,
25 AwaitingInput,
27 Completed,
29 Cancelled,
31 Failed,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct AcpSession {
38 pub session_id: String,
40
41 pub state: SessionState,
43
44 pub created_at: String,
46
47 #[serde(skip_serializing_if = "Option::is_none")]
49 pub last_activity_at: Option<String>,
50
51 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
53 pub metadata: HashMap<String, Value>,
54
55 #[serde(default)]
57 pub turn_count: u32,
58}
59
60impl AcpSession {
61 pub fn new(session_id: impl Into<String>) -> Self {
63 Self {
64 session_id: session_id.into(),
65 state: SessionState::Created,
66 created_at: chrono::Utc::now().to_rfc3339(),
67 last_activity_at: None,
68 metadata: HashMap::new(),
69 turn_count: 0,
70 }
71 }
72
73 pub fn set_state(&mut self, state: SessionState) {
75 self.state = state;
76 self.last_activity_at = Some(chrono::Utc::now().to_rfc3339());
77 }
78
79 pub fn increment_turn(&mut self) {
81 self.turn_count += 1;
82 self.last_activity_at = Some(chrono::Utc::now().to_rfc3339());
83 }
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize, Default)]
92pub struct SessionNewParams {
93 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
95 pub metadata: HashMap<String, Value>,
96
97 #[serde(skip_serializing_if = "Option::is_none")]
99 pub workspace: Option<WorkspaceContext>,
100
101 #[serde(skip_serializing_if = "Option::is_none")]
103 pub model_preferences: Option<ModelPreferences>,
104}
105
106#[derive(Debug, Clone, Serialize, Deserialize)]
108pub struct SessionNewResult {
109 pub session_id: String,
111
112 #[serde(default)]
114 pub state: SessionState,
115}
116
117#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct SessionLoadParams {
124 pub session_id: String,
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct SessionLoadResult {
131 pub session: AcpSession,
133
134 #[serde(default, skip_serializing_if = "Vec::is_empty")]
136 pub history: Vec<ConversationTurn>,
137}
138
139#[derive(Debug, Clone, Serialize, Deserialize)]
145pub struct SessionPromptParams {
146 pub session_id: String,
148
149 pub content: Vec<PromptContent>,
151
152 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
154 pub metadata: HashMap<String, Value>,
155}
156
157#[derive(Debug, Clone, Serialize, Deserialize)]
159#[serde(tag = "type", rename_all = "snake_case")]
160pub enum PromptContent {
161 Text {
163 text: String,
165 },
166
167 Image {
169 data: String,
171 mime_type: String,
173 #[serde(default)]
175 is_url: bool,
176 },
177
178 Context {
180 path: String,
182 content: String,
184 #[serde(skip_serializing_if = "Option::is_none")]
186 language: Option<String>,
187 },
188}
189
190impl PromptContent {
191 pub fn text(text: impl Into<String>) -> Self {
193 Self::Text { text: text.into() }
194 }
195
196 pub fn context(path: impl Into<String>, content: impl Into<String>) -> Self {
198 Self::Context {
199 path: path.into(),
200 content: content.into(),
201 language: None,
202 }
203 }
204}
205
206#[derive(Debug, Clone, Serialize, Deserialize)]
208pub struct SessionPromptResult {
209 pub turn_id: String,
211
212 #[serde(skip_serializing_if = "Option::is_none")]
214 pub response: Option<String>,
215
216 #[serde(default, skip_serializing_if = "Vec::is_empty")]
218 pub tool_calls: Vec<ToolCallRecord>,
219
220 pub status: TurnStatus,
222}
223
224#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
226#[serde(rename_all = "snake_case")]
227pub enum TurnStatus {
228 Completed,
230 Cancelled,
232 Failed,
234 AwaitingInput,
236}
237
238#[derive(Debug, Clone, Serialize, Deserialize)]
244#[serde(rename_all = "camelCase")]
245pub struct RequestPermissionParams {
246 pub session_id: String,
248
249 pub tool_call: ToolCallRecord,
251
252 pub options: Vec<PermissionOption>,
254}
255
256#[derive(Debug, Clone, Serialize, Deserialize)]
258pub struct PermissionOption {
259 pub id: String,
261
262 pub label: String,
264
265 #[serde(skip_serializing_if = "Option::is_none")]
267 pub description: Option<String>,
268}
269
270#[derive(Debug, Clone, Serialize, Deserialize)]
272#[serde(tag = "outcome", rename_all = "snake_case")]
273pub enum RequestPermissionResult {
274 Selected {
276 option_id: String,
278 },
279 Cancelled,
281}
282
283#[derive(Debug, Clone, Serialize, Deserialize)]
289pub struct SessionCancelParams {
290 pub session_id: String,
292
293 #[serde(skip_serializing_if = "Option::is_none")]
295 pub turn_id: Option<String>,
296}
297
298#[derive(Debug, Clone, Serialize, Deserialize)]
304pub struct SessionUpdateNotification {
305 pub session_id: String,
307
308 pub turn_id: String,
310
311 #[serde(flatten)]
313 pub update: SessionUpdate,
314}
315
316#[derive(Debug, Clone, Serialize, Deserialize)]
318#[serde(tag = "update_type", rename_all = "snake_case")]
319pub enum SessionUpdate {
320 MessageDelta {
322 delta: String,
324 },
325
326 ToolCallStart {
328 tool_call: ToolCallRecord,
330 },
331
332 ToolCallEnd {
334 tool_call_id: String,
336 result: Value,
338 },
339
340 TurnComplete {
342 status: TurnStatus,
344 },
345
346 Error {
348 code: String,
350 message: String,
352 },
353}
354
355#[derive(Debug, Clone, Serialize, Deserialize)]
361pub struct WorkspaceContext {
362 pub root_path: String,
364
365 #[serde(skip_serializing_if = "Option::is_none")]
367 pub name: Option<String>,
368
369 #[serde(default, skip_serializing_if = "Vec::is_empty")]
371 pub active_files: Vec<String>,
372}
373
374#[derive(Debug, Clone, Serialize, Deserialize)]
376pub struct ModelPreferences {
377 #[serde(skip_serializing_if = "Option::is_none")]
379 pub model_id: Option<String>,
380
381 #[serde(skip_serializing_if = "Option::is_none")]
383 pub temperature: Option<f32>,
384
385 #[serde(skip_serializing_if = "Option::is_none")]
387 pub max_tokens: Option<u32>,
388}
389
390#[derive(Debug, Clone, Serialize, Deserialize)]
392pub struct ToolCallRecord {
393 pub id: String,
395
396 pub name: String,
398
399 pub arguments: Value,
401
402 #[serde(skip_serializing_if = "Option::is_none")]
404 pub result: Option<Value>,
405
406 pub timestamp: String,
408}
409
410#[derive(Debug, Clone, Serialize, Deserialize)]
412pub struct ConversationTurn {
413 pub turn_id: String,
415
416 pub prompt: Vec<PromptContent>,
418
419 #[serde(skip_serializing_if = "Option::is_none")]
421 pub response: Option<String>,
422
423 #[serde(default, skip_serializing_if = "Vec::is_empty")]
425 pub tool_calls: Vec<ToolCallRecord>,
426
427 pub timestamp: String,
429}
430
431#[cfg(test)]
432mod tests {
433 use super::*;
434 use serde_json::json;
435
436 #[test]
437 fn test_session_new_params() {
438 let params = SessionNewParams::default();
439 let json = serde_json::to_value(¶ms).unwrap();
440 assert_eq!(json, json!({}));
441 }
442
443 #[test]
444 fn test_prompt_content_text() {
445 let content = PromptContent::text("Hello, world!");
446 let json = serde_json::to_value(&content).unwrap();
447 assert_eq!(json["type"], "text");
448 assert_eq!(json["text"], "Hello, world!");
449 }
450
451 #[test]
452 fn test_session_update_message_delta() {
453 let update = SessionUpdate::MessageDelta {
454 delta: "Hello".to_string(),
455 };
456 let json = serde_json::to_value(&update).unwrap();
457 assert_eq!(json["update_type"], "message_delta");
458 assert_eq!(json["delta"], "Hello");
459 }
460
461 #[test]
462 fn test_session_state_transitions() {
463 let mut session = AcpSession::new("test-session");
464 assert_eq!(session.state, SessionState::Created);
465
466 session.set_state(SessionState::Active);
467 assert_eq!(session.state, SessionState::Active);
468 assert!(session.last_activity_at.is_some());
469 }
470}