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")]
18pub enum SessionState {
19 Created,
21 Active,
23 AwaitingInput,
25 Completed,
27 Cancelled,
29 Failed,
31}
32
33impl Default for SessionState {
34 fn default() -> Self {
35 Self::Created
36 }
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct AcpSession {
42 pub session_id: String,
44
45 pub state: SessionState,
47
48 pub created_at: String,
50
51 #[serde(skip_serializing_if = "Option::is_none")]
53 pub last_activity_at: Option<String>,
54
55 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
57 pub metadata: HashMap<String, Value>,
58
59 #[serde(default)]
61 pub turn_count: u32,
62}
63
64impl AcpSession {
65 pub fn new(session_id: impl Into<String>) -> Self {
67 Self {
68 session_id: session_id.into(),
69 state: SessionState::Created,
70 created_at: chrono::Utc::now().to_rfc3339(),
71 last_activity_at: None,
72 metadata: HashMap::new(),
73 turn_count: 0,
74 }
75 }
76
77 pub fn set_state(&mut self, state: SessionState) {
79 self.state = state;
80 self.last_activity_at = Some(chrono::Utc::now().to_rfc3339());
81 }
82
83 pub fn increment_turn(&mut self) {
85 self.turn_count += 1;
86 self.last_activity_at = Some(chrono::Utc::now().to_rfc3339());
87 }
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct SessionNewParams {
97 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
99 pub metadata: HashMap<String, Value>,
100
101 #[serde(skip_serializing_if = "Option::is_none")]
103 pub workspace: Option<WorkspaceContext>,
104
105 #[serde(skip_serializing_if = "Option::is_none")]
107 pub model_preferences: Option<ModelPreferences>,
108}
109
110impl Default for SessionNewParams {
111 fn default() -> Self {
112 Self {
113 metadata: HashMap::new(),
114 workspace: None,
115 model_preferences: None,
116 }
117 }
118}
119
120#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct SessionNewResult {
123 pub session_id: String,
125
126 #[serde(default)]
128 pub state: SessionState,
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize)]
137pub struct SessionLoadParams {
138 pub session_id: String,
140}
141
142#[derive(Debug, Clone, Serialize, Deserialize)]
144pub struct SessionLoadResult {
145 pub session: AcpSession,
147
148 #[serde(default, skip_serializing_if = "Vec::is_empty")]
150 pub history: Vec<ConversationTurn>,
151}
152
153#[derive(Debug, Clone, Serialize, Deserialize)]
159pub struct SessionPromptParams {
160 pub session_id: String,
162
163 pub content: Vec<PromptContent>,
165
166 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
168 pub metadata: HashMap<String, Value>,
169}
170
171#[derive(Debug, Clone, Serialize, Deserialize)]
173#[serde(tag = "type", rename_all = "snake_case")]
174pub enum PromptContent {
175 Text {
177 text: String,
179 },
180
181 Image {
183 data: String,
185 mime_type: String,
187 #[serde(default)]
189 is_url: bool,
190 },
191
192 Context {
194 path: String,
196 content: String,
198 #[serde(skip_serializing_if = "Option::is_none")]
200 language: Option<String>,
201 },
202}
203
204impl PromptContent {
205 pub fn text(text: impl Into<String>) -> Self {
207 Self::Text { text: text.into() }
208 }
209
210 pub fn context(path: impl Into<String>, content: impl Into<String>) -> Self {
212 Self::Context {
213 path: path.into(),
214 content: content.into(),
215 language: None,
216 }
217 }
218}
219
220#[derive(Debug, Clone, Serialize, Deserialize)]
222pub struct SessionPromptResult {
223 pub turn_id: String,
225
226 #[serde(skip_serializing_if = "Option::is_none")]
228 pub response: Option<String>,
229
230 #[serde(default, skip_serializing_if = "Vec::is_empty")]
232 pub tool_calls: Vec<ToolCallRecord>,
233
234 pub status: TurnStatus,
236}
237
238#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
240#[serde(rename_all = "snake_case")]
241pub enum TurnStatus {
242 Completed,
244 Cancelled,
246 Failed,
248 AwaitingInput,
250}
251
252#[derive(Debug, Clone, Serialize, Deserialize)]
258#[serde(rename_all = "camelCase")]
259pub struct RequestPermissionParams {
260 pub session_id: String,
262
263 pub tool_call: ToolCallRecord,
265
266 pub options: Vec<PermissionOption>,
268}
269
270#[derive(Debug, Clone, Serialize, Deserialize)]
272pub struct PermissionOption {
273 pub id: String,
275
276 pub label: String,
278
279 #[serde(skip_serializing_if = "Option::is_none")]
281 pub description: Option<String>,
282}
283
284#[derive(Debug, Clone, Serialize, Deserialize)]
286#[serde(tag = "outcome", rename_all = "snake_case")]
287pub enum RequestPermissionResult {
288 Selected {
290 option_id: String,
292 },
293 Cancelled,
295}
296
297#[derive(Debug, Clone, Serialize, Deserialize)]
303pub struct SessionCancelParams {
304 pub session_id: String,
306
307 #[serde(skip_serializing_if = "Option::is_none")]
309 pub turn_id: Option<String>,
310}
311
312#[derive(Debug, Clone, Serialize, Deserialize)]
318pub struct SessionUpdateNotification {
319 pub session_id: String,
321
322 pub turn_id: String,
324
325 #[serde(flatten)]
327 pub update: SessionUpdate,
328}
329
330#[derive(Debug, Clone, Serialize, Deserialize)]
332#[serde(tag = "update_type", rename_all = "snake_case")]
333pub enum SessionUpdate {
334 MessageDelta {
336 delta: String,
338 },
339
340 ToolCallStart {
342 tool_call: ToolCallRecord,
344 },
345
346 ToolCallEnd {
348 tool_call_id: String,
350 result: Value,
352 },
353
354 TurnComplete {
356 status: TurnStatus,
358 },
359
360 Error {
362 code: String,
364 message: String,
366 },
367}
368
369#[derive(Debug, Clone, Serialize, Deserialize)]
375pub struct WorkspaceContext {
376 pub root_path: String,
378
379 #[serde(skip_serializing_if = "Option::is_none")]
381 pub name: Option<String>,
382
383 #[serde(default, skip_serializing_if = "Vec::is_empty")]
385 pub active_files: Vec<String>,
386}
387
388#[derive(Debug, Clone, Serialize, Deserialize)]
390pub struct ModelPreferences {
391 #[serde(skip_serializing_if = "Option::is_none")]
393 pub model_id: Option<String>,
394
395 #[serde(skip_serializing_if = "Option::is_none")]
397 pub temperature: Option<f32>,
398
399 #[serde(skip_serializing_if = "Option::is_none")]
401 pub max_tokens: Option<u32>,
402}
403
404#[derive(Debug, Clone, Serialize, Deserialize)]
406pub struct ToolCallRecord {
407 pub id: String,
409
410 pub name: String,
412
413 pub arguments: Value,
415
416 #[serde(skip_serializing_if = "Option::is_none")]
418 pub result: Option<Value>,
419
420 pub timestamp: String,
422}
423
424#[derive(Debug, Clone, Serialize, Deserialize)]
426pub struct ConversationTurn {
427 pub turn_id: String,
429
430 pub prompt: Vec<PromptContent>,
432
433 #[serde(skip_serializing_if = "Option::is_none")]
435 pub response: Option<String>,
436
437 #[serde(default, skip_serializing_if = "Vec::is_empty")]
439 pub tool_calls: Vec<ToolCallRecord>,
440
441 pub timestamp: String,
443}
444
445#[cfg(test)]
446mod tests {
447 use super::*;
448 use serde_json::json;
449
450 #[test]
451 fn test_session_new_params() {
452 let params = SessionNewParams::default();
453 let json = serde_json::to_value(¶ms).unwrap();
454 assert_eq!(json, json!({}));
455 }
456
457 #[test]
458 fn test_prompt_content_text() {
459 let content = PromptContent::text("Hello, world!");
460 let json = serde_json::to_value(&content).unwrap();
461 assert_eq!(json["type"], "text");
462 assert_eq!(json["text"], "Hello, world!");
463 }
464
465 #[test]
466 fn test_session_update_message_delta() {
467 let update = SessionUpdate::MessageDelta {
468 delta: "Hello".to_string(),
469 };
470 let json = serde_json::to_value(&update).unwrap();
471 assert_eq!(json["update_type"], "message_delta");
472 assert_eq!(json["delta"], "Hello");
473 }
474
475 #[test]
476 fn test_session_state_transitions() {
477 let mut session = AcpSession::new("test-session");
478 assert_eq!(session.state, SessionState::Created);
479
480 session.set_state(SessionState::Active);
481 assert_eq!(session.state, SessionState::Active);
482 assert!(session.last_activity_at.is_some());
483 }
484}