Skip to main content

opencode_sdk/types/
session.rs

1//! Session types for opencode_rs.
2
3use crate::types::permission::Ruleset;
4use serde::{Deserialize, Serialize};
5
6/// A session in OpenCode.
7#[derive(Debug, Clone, Serialize, Deserialize)]
8#[serde(rename_all = "camelCase")]
9pub struct Session {
10    /// Unique session identifier.
11    pub id: String,
12    /// Project identifier (may not be present in all responses).
13    #[serde(default, skip_serializing_if = "Option::is_none")]
14    pub project_id: Option<String>,
15    /// Working directory for the session.
16    #[serde(default, skip_serializing_if = "Option::is_none")]
17    pub directory: Option<String>,
18    /// Parent session ID (for forked sessions).
19    #[serde(default, skip_serializing_if = "Option::is_none")]
20    pub parent_id: Option<String>,
21    /// Session summary.
22    #[serde(default, skip_serializing_if = "Option::is_none")]
23    pub summary: Option<SessionSummary>,
24    /// Share information.
25    #[serde(default, skip_serializing_if = "Option::is_none")]
26    pub share: Option<ShareInfo>,
27    /// Session title.
28    #[serde(default)]
29    pub title: String,
30    /// Session version.
31    #[serde(default)]
32    pub version: String,
33    /// Timestamps.
34    #[serde(default, skip_serializing_if = "Option::is_none")]
35    pub time: Option<SessionTime>,
36    /// Pending permission ruleset.
37    #[serde(default, skip_serializing_if = "Option::is_none")]
38    pub permission: Option<Ruleset>,
39    /// Revert information.
40    #[serde(default, skip_serializing_if = "Option::is_none")]
41    pub revert: Option<RevertInfo>,
42    /// Additional fields from server.
43    #[serde(flatten)]
44    pub extra: serde_json::Value,
45}
46
47/// Session summary with file changes.
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct SessionSummary {
50    /// Lines added.
51    pub additions: u64,
52    /// Lines deleted.
53    pub deletions: u64,
54    /// Number of files changed.
55    pub files: u64,
56    /// File diffs.
57    #[serde(default, skip_serializing_if = "Option::is_none")]
58    pub diffs: Option<Vec<FileDiffLite>>,
59}
60
61/// Lightweight file diff information.
62#[derive(Debug, Clone, Serialize, Deserialize)]
63#[serde(rename_all = "camelCase")]
64pub struct FileDiffLite {
65    /// File path.
66    #[serde(default, skip_serializing_if = "Option::is_none")]
67    pub file: Option<String>,
68    /// Content before changes.
69    #[serde(default, skip_serializing_if = "Option::is_none")]
70    pub before: Option<String>,
71    /// Content after changes.
72    #[serde(default, skip_serializing_if = "Option::is_none")]
73    pub after: Option<String>,
74    /// Lines added.
75    #[serde(default, skip_serializing_if = "Option::is_none")]
76    pub additions: Option<u64>,
77    /// Lines deleted.
78    #[serde(default, skip_serializing_if = "Option::is_none")]
79    pub deletions: Option<u64>,
80    /// Additional fields.
81    #[serde(flatten)]
82    pub extra: serde_json::Value,
83}
84
85/// Share information.
86#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct ShareInfo {
88    /// Share secret (for editing).
89    #[serde(default, skip_serializing_if = "Option::is_none")]
90    pub secret: Option<String>,
91    /// Share URL.
92    pub url: String,
93}
94
95/// Session timestamps.
96#[derive(Debug, Clone, Serialize, Deserialize)]
97pub struct SessionTime {
98    /// Creation timestamp.
99    pub created: i64,
100    /// Last update timestamp.
101    pub updated: i64,
102    /// Compaction timestamp.
103    #[serde(default, skip_serializing_if = "Option::is_none")]
104    pub compacting: Option<i64>,
105    /// Archive timestamp.
106    #[serde(default, skip_serializing_if = "Option::is_none")]
107    pub archived: Option<i64>,
108}
109
110/// Revert information.
111#[derive(Debug, Clone, Serialize, Deserialize)]
112#[serde(rename_all = "camelCase")]
113pub struct RevertInfo {
114    /// Message ID to revert to.
115    pub message_id: String,
116    /// Part ID to revert to.
117    #[serde(default, skip_serializing_if = "Option::is_none")]
118    pub part_id: Option<String>,
119    /// Snapshot ID.
120    #[serde(default, skip_serializing_if = "Option::is_none")]
121    pub snapshot: Option<String>,
122    /// Diff content.
123    #[serde(default, skip_serializing_if = "Option::is_none")]
124    pub diff: Option<String>,
125}
126
127/// Request to create a new session.
128#[derive(Debug, Clone, Default, Serialize, Deserialize)]
129#[serde(rename_all = "camelCase")]
130pub struct CreateSessionRequest {
131    /// Parent session ID to fork from.
132    ///
133    /// Serialized as `parentID` to match OpenCode API.
134    #[serde(rename = "parentID", default, skip_serializing_if = "Option::is_none")]
135    pub parent_id: Option<String>,
136    /// Optional title for the session.
137    #[serde(default, skip_serializing_if = "Option::is_none")]
138    pub title: Option<String>,
139    /// Initial permission ruleset.
140    #[serde(default, skip_serializing_if = "Option::is_none")]
141    pub permission: Option<Ruleset>,
142    /// Optional directory to create the session in.
143    ///
144    /// This is sent as the `directory` query parameter on `POST /session`,
145    /// not in the JSON body.
146    #[serde(skip)]
147    pub directory: Option<String>,
148}
149
150/// Convenience options for creating a new session.
151#[derive(Debug, Clone, Default)]
152pub struct SessionCreateOptions {
153    /// Parent session ID to fork from.
154    pub parent_id: Option<String>,
155    /// Optional title for the new session.
156    pub title: Option<String>,
157    /// Optional directory to create the session in.
158    pub directory: Option<String>,
159    /// Initial permission ruleset.
160    pub permission: Option<Ruleset>,
161}
162
163impl SessionCreateOptions {
164    /// Create empty options.
165    pub fn new() -> Self {
166        Self::default()
167    }
168
169    /// Set session parent ID.
170    pub fn with_parent_id(mut self, parent_id: impl Into<String>) -> Self {
171        self.parent_id = Some(parent_id.into());
172        self
173    }
174
175    /// Set session title.
176    pub fn with_title(mut self, title: impl Into<String>) -> Self {
177        self.title = Some(title.into());
178        self
179    }
180
181    /// Set session directory.
182    pub fn with_directory(mut self, directory: impl Into<String>) -> Self {
183        self.directory = Some(directory.into());
184        self
185    }
186
187    /// Set initial permission ruleset.
188    pub fn with_permission(mut self, permission: Ruleset) -> Self {
189        self.permission = Some(permission);
190        self
191    }
192}
193
194impl From<SessionCreateOptions> for CreateSessionRequest {
195    fn from(value: SessionCreateOptions) -> Self {
196        Self {
197            parent_id: value.parent_id,
198            title: value.title,
199            permission: value.permission,
200            directory: value.directory,
201        }
202    }
203}
204
205/// Request to update a session.
206#[derive(Debug, Clone, Default, Serialize, Deserialize)]
207#[serde(rename_all = "camelCase")]
208pub struct UpdateSessionRequest {
209    /// New title.
210    #[serde(default, skip_serializing_if = "Option::is_none")]
211    pub title: Option<String>,
212}
213
214/// Request to summarize a session.
215#[derive(Debug, Clone, Serialize, Deserialize)]
216#[serde(rename_all = "camelCase")]
217pub struct SummarizeRequest {
218    /// Provider ID.
219    pub provider_id: String,
220    /// Model ID.
221    pub model_id: String,
222    /// Whether this is automatic.
223    #[serde(default, skip_serializing_if = "Option::is_none")]
224    pub auto: Option<bool>,
225}
226
227/// Request to revert a session.
228#[derive(Debug, Clone, Serialize, Deserialize)]
229#[serde(rename_all = "camelCase")]
230pub struct RevertRequest {
231    /// Message ID to revert to.
232    pub message_id: String,
233    /// Part ID to revert to.
234    #[serde(default, skip_serializing_if = "Option::is_none")]
235    pub part_id: Option<String>,
236}
237
238/// Session status response.
239#[derive(Debug, Clone, Serialize, Deserialize)]
240#[serde(rename_all = "camelCase")]
241pub struct SessionStatus {
242    /// Active session ID.
243    #[serde(default, skip_serializing_if = "Option::is_none")]
244    pub active_session_id: Option<String>,
245    /// Whether any session is busy.
246    #[serde(default)]
247    pub busy: bool,
248}
249
250/// Session diff response.
251#[derive(Debug, Clone, Default, Serialize, Deserialize)]
252#[serde(rename_all = "camelCase")]
253pub struct SessionDiff {
254    /// Diff content.
255    #[serde(default)]
256    pub diff: String,
257    /// Files changed.
258    #[serde(default)]
259    pub files: Vec<String>,
260    /// Additional fields.
261    #[serde(flatten)]
262    pub extra: serde_json::Value,
263}
264
265/// Session todo item.
266#[derive(Debug, Clone, Serialize, Deserialize)]
267#[serde(rename_all = "camelCase")]
268pub struct TodoItem {
269    /// Todo ID.
270    pub id: String,
271    /// Todo content.
272    pub content: String,
273    /// Whether completed.
274    #[serde(default)]
275    pub completed: bool,
276    /// Priority.
277    #[serde(default, skip_serializing_if = "Option::is_none")]
278    pub priority: Option<String>,
279}
280
281#[cfg(test)]
282mod tests {
283    use super::*;
284
285    #[test]
286    fn test_session_deserialize() {
287        let json = r#"{
288            "id": "s1",
289            "projectId": "p1",
290            "directory": "/path/to/project",
291            "title": "Test Session",
292            "version": "1.0",
293            "time": {"created": 1234567890, "updated": 1234567890}
294        }"#;
295        let session: Session = serde_json::from_str(json).unwrap();
296        assert_eq!(session.id, "s1");
297        assert_eq!(session.title, "Test Session");
298    }
299
300    #[test]
301    fn test_session_minimal() {
302        // Session with only required field (id)
303        let json = r#"{"id": "s1"}"#;
304        let session: Session = serde_json::from_str(json).unwrap();
305        assert_eq!(session.id, "s1");
306        assert!(session.project_id.is_none());
307    }
308
309    #[test]
310    fn test_session_with_optional_fields() {
311        let json = r#"{
312            "id": "s1",
313            "projectId": "p1",
314            "directory": "/path",
315            "title": "Test",
316            "version": "1.0",
317            "time": {"created": 1234567890, "updated": 1234567890},
318            "parentId": "s0",
319            "share": {"url": "https://example.com/share/s1"}
320        }"#;
321        let session: Session = serde_json::from_str(json).unwrap();
322        assert_eq!(session.parent_id, Some("s0".to_string()));
323        assert!(session.share.is_some());
324    }
325}