Skip to main content

vtcode_desktop_protocol/
session.rs

1//! Session lifecycle, history paging, and idle-session option DTOs.
2
3use serde::{Deserialize, Serialize};
4use vtcode_commons::history_scope::HistoryScope;
5
6/// Session execution and command confirmation policy.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
8#[serde(rename_all = "camelCase")]
9pub enum PermissionMode {
10    /// Preserve interactive confirmation for protected commands.
11    Ask,
12    /// Use the CLI's skip-confirmations behavior for commands.
13    AutoApproveCommands,
14    /// Run without the process sandbox or interactive approval prompts.
15    FullAccess,
16}
17
18/// Runtime options exposed without credentials or complete configuration documents.
19#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
20#[serde(rename_all = "camelCase")]
21pub struct SessionOptionsSnapshot {
22    /// Effective command confirmation policy.
23    pub permission_mode: PermissionMode,
24    /// Selected primary agent, such as build or plan.
25    pub mode: String,
26    /// Primary agents discovered by the CLI for this workspace.
27    #[serde(default)]
28    pub available_modes: Vec<AgentModeInfo>,
29    /// CLI reasoning effort identifier.
30    pub reasoning_effort: String,
31    /// Efforts supported by this exact provider/model route.
32    pub supported_efforts: Vec<String>,
33    /// Effective provider speed badge, when supported by this route.
34    pub speed: Option<String>,
35    /// Whether this exact provider/model route supports the CLI's fast setting.
36    #[serde(default)]
37    pub fast_mode_supported: bool,
38    /// Whether fast processing is currently enabled for this route.
39    #[serde(default)]
40    pub fast_mode_enabled: bool,
41    /// Label used by the CLI for the selected fast tier on this route.
42    #[serde(default, skip_serializing_if = "Option::is_none")]
43    pub fast_mode_label: Option<String>,
44}
45
46/// Non-secret metadata for a selectable CLI primary agent.
47#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
48#[serde(rename_all = "camelCase")]
49pub struct AgentModeInfo {
50    /// Canonical CLI primary-agent name.
51    pub name: String,
52    /// CLI display name.
53    pub display_name: String,
54    /// Short description of the agent's role.
55    pub description: String,
56}
57
58/// An explicit idle-session option change. Omitted values preserve existing choices.
59#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
60#[serde(rename_all = "camelCase", deny_unknown_fields)]
61pub struct SessionOptionsSelectParams {
62    /// Change command confirmations, retaining sandbox and tool authorization.
63    pub permission_mode: Option<PermissionMode>,
64    /// Select a built-in build or plan agent.
65    pub mode: Option<String>,
66    /// Select a capability-validated reasoning effort.
67    pub reasoning_effort: Option<String>,
68    /// Select the CLI's fast processing tier for a compatible route.
69    pub fast_mode: Option<bool>,
70    /// One-shot attestation that the user completed the Full access
71    /// confirmation dialog (risk disclosure + explicit acknowledge) for this
72    /// request. Required when `permission_mode` is `FullAccess`; never
73    /// persisted, never merged into stored session options.
74    pub full_access_confirmed: Option<bool>,
75}
76
77/// Persist a built-in CLI theme selected in Desktop.
78#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
79#[serde(rename_all = "camelCase", deny_unknown_fields)]
80pub struct ThemePreferenceSelectParams {
81    /// Built-in VT Code theme identifier.
82    pub theme: String,
83}
84
85/// A persisted session available for reload.
86#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
87#[serde(rename_all = "camelCase")]
88pub struct SessionSummary {
89    /// Stable durable session identifier.
90    pub session_id: String,
91    /// Human-readable title when known.
92    pub title: Option<String>,
93    /// Session workspace path.
94    pub workspace: String,
95    /// Whether the last observed state was terminal.
96    pub completed: bool,
97    /// Whether this session can be continued using existing archive data.
98    pub resumable: bool,
99    /// Archive size in bytes, zero when unknown.
100    #[serde(default)]
101    pub size_bytes: u64,
102    /// Snapshot end time as unix seconds, absent when unknown.
103    #[serde(default, skip_serializing_if = "Option::is_none")]
104    pub updated_at: Option<u64>,
105}
106
107/// Text restored from the durable message snapshot for a loaded session.
108#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
109#[serde(rename_all = "camelCase")]
110pub struct SessionHistoryMessage {
111    /// Model-only context retained for archive indexing but hidden from the transcript.
112    #[serde(default)]
113    pub hidden: bool,
114    /// Stable zero-based index in the archived session snapshot.
115    #[serde(default, skip_serializing_if = "Option::is_none")]
116    pub archive_index: Option<usize>,
117    /// Message role such as `user`, `assistant`, or `tool`.
118    pub role: String,
119    /// Recorded assistant phase, absent for older archives or non-assistant messages.
120    #[serde(default, skip_serializing_if = "Option::is_none")]
121    pub phase: Option<String>,
122    /// Canonical event item identity, when retained in the archive.
123    #[serde(default, skip_serializing_if = "Option::is_none")]
124    pub event_item_id: Option<String>,
125    /// Text projection of the persisted message content.
126    pub text: String,
127}
128
129/// Loaded durable session metadata and its bounded transcript projection.
130#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
131#[serde(rename_all = "camelCase")]
132pub struct SessionLoadResult {
133    /// Selected session metadata.
134    pub session: SessionSummary,
135    /// Recent messages restored from the archive snapshot.
136    pub messages: Vec<SessionHistoryMessage>,
137    /// Index of the earliest restored message in the archive snapshot.
138    #[serde(default)]
139    pub history_start_index: usize,
140    /// Whether earlier archived messages can be requested.
141    #[serde(default)]
142    pub history_has_earlier: bool,
143    /// Recent file-change snapshots from canonical history, in persisted order.
144    #[serde(default, skip_serializing_if = "Vec::is_empty")]
145    pub review_items: Vec<vtcode_exec_events::ThreadItem>,
146    /// Whether bounded history projection omitted review evidence.
147    #[serde(default)]
148    pub review_history_incomplete: bool,
149    /// Recent canonical tool, command, and supporting activity snapshots.
150    #[serde(default, skip_serializing_if = "Vec::is_empty")]
151    pub activity_items: Vec<vtcode_exec_events::ThreadItem>,
152    /// Bounded cursors for archived tool output that can be loaded on demand.
153    #[serde(default, skip_serializing_if = "Vec::is_empty")]
154    pub activity_output_cursors: Vec<SessionOutputCursor>,
155    /// Whether bounded history projection omitted activity or output evidence.
156    #[serde(default)]
157    pub activity_history_incomplete: bool,
158    /// Ordered canonical event suffix for reconstructing transcript turns.
159    #[serde(default, skip_serializing_if = "Vec::is_empty")]
160    pub timeline_events: Vec<vtcode_exec_events::VersionedThreadEvent>,
161    /// Whether the event suffix omits earlier events.
162    #[serde(default)]
163    pub timeline_incomplete: bool,
164}
165
166/// Request an earlier bounded page of the selected archived conversation.
167#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
168#[serde(rename_all = "camelCase")]
169pub struct SessionHistoryParams {
170    /// Durable session identifier.
171    pub session_id: String,
172    /// Request identity echoed by the service to reject stale pages.
173    pub request_id: String,
174    /// Exclusive end index for the page; use the earliest loaded archive index.
175    pub before_index: usize,
176    /// Number of messages requested, capped by [`MAX_SESSION_HISTORY_PAGE_MESSAGES`].
177    pub limit: usize,
178}
179
180/// One bounded earlier page from a durable conversation.
181#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
182#[serde(rename_all = "camelCase")]
183pub struct SessionHistoryPage {
184    /// Durable session identifier that owns the page.
185    pub session_id: String,
186    /// Request identity echoed from [`SessionHistoryParams`].
187    pub request_id: String,
188    /// Index of the earliest message included in this page.
189    pub start_index: usize,
190    /// Messages in chronological order.
191    pub messages: Vec<SessionHistoryMessage>,
192    /// Whether the archive contains messages before this page.
193    pub has_earlier: bool,
194}
195
196/// One genuine user-typed prompt from a workspace session archive.
197///
198/// Mirrors the CLI history picker's archived entries: harness-generated
199/// follow-ups and enrichment wrappers are stripped server-side, so the client
200/// can merge these with the live thread without re-cleaning.
201#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
202#[serde(rename_all = "camelCase")]
203pub struct RecentPrompt {
204    /// Cleaned user-typed prompt text.
205    pub content: String,
206    /// Durable session identifier that owns the prompt.
207    pub session_id: String,
208}
209
210/// Cursor for reading more of a restored tool-output capture.
211#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
212#[serde(rename_all = "camelCase")]
213pub struct SessionOutputCursor {
214    /// Stable canonical activity item identity.
215    pub item_id: String,
216    /// Byte offset to request next; always a UTF-8 boundary for text output.
217    pub next_offset: u64,
218    /// Captured spool size observed during session restore.
219    pub total_bytes: u64,
220}
221
222/// Request one bounded page from a restored tool-output capture.
223#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
224#[serde(rename_all = "camelCase")]
225pub struct SessionOutputReadParams {
226    /// Durable session that owns the output item.
227    pub session_id: String,
228    /// Request identity echoed by the service to reject stale pages.
229    pub request_id: String,
230    /// Stable canonical activity item identity.
231    pub item_id: String,
232    /// UTF-8 byte offset supplied by the current cursor.
233    pub offset: u64,
234}
235
236/// One bounded page from an archived tool-output capture.
237#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
238#[serde(rename_all = "camelCase")]
239pub struct SessionOutputPage {
240    /// Durable session that owns the output item.
241    pub session_id: String,
242    /// Request identity echoed from [`SessionOutputReadParams`].
243    pub request_id: String,
244    /// Stable canonical activity item identity.
245    pub item_id: String,
246    /// Requested starting offset.
247    pub offset: u64,
248    /// Next UTF-8 boundary after the returned text.
249    pub next_offset: u64,
250    /// Captured spool size observed during this read.
251    pub total_bytes: u64,
252    /// Text captured in this page.
253    pub text: String,
254    /// Whether more captured bytes remain.
255    pub has_more: bool,
256}
257
258/// Parameters for creating an empty session.
259#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
260#[serde(rename_all = "camelCase")]
261pub struct SessionCreateParams {
262    /// Optional title shown in history.
263    pub title: Option<String>,
264}
265
266/// Parameters for loading a durable session.
267#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
268#[serde(rename_all = "camelCase")]
269pub struct SessionIdParams {
270    /// Durable session identifier.
271    pub session_id: String,
272}
273
274/// Parameters for listing recent sessions. The limit is a soft page size:
275/// omitted values keep the historical full-list behavior.
276#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
277#[serde(rename_all = "camelCase")]
278pub struct SessionListParams {
279    /// Maximum sessions returned, newest first.
280    #[serde(default)]
281    pub limit: Option<usize>,
282}
283
284impl SessionListParams {
285    /// Clamp the requested limit into `1..=MAX_SESSION_LIST_LIMIT`.
286    #[must_use]
287    pub fn effective_limit(&self) -> usize {
288        self.limit.unwrap_or(MAX_SESSION_LIST_LIMIT).clamp(1, MAX_SESSION_LIST_LIMIT)
289    }
290}
291
292/// Maximum sessions returned by one `session/list` request.
293pub const MAX_SESSION_LIST_LIMIT: usize = 200;
294
295/// Parameters for listing recent cross-session prompts.
296#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
297#[serde(rename_all = "camelCase", deny_unknown_fields)]
298pub struct RecentPromptsParams {
299    /// Which sessions contribute; omitted scopes to the current project.
300    #[serde(default = "default_recent_prompts_scope")]
301    pub scope: HistoryScope,
302}
303
304fn default_recent_prompts_scope() -> HistoryScope {
305    HistoryScope::Project
306}
307
308/// Maximum message count accepted by one desktop archive-history page request.
309pub const MAX_SESSION_HISTORY_PAGE_MESSAGES: usize = 160;
310
311/// Recent-prompts result. Echoes the requested scope so the client can
312/// match out-of-order responses when the scope changes mid-fetch.
313#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
314#[serde(rename_all = "camelCase", deny_unknown_fields)]
315pub struct RecentPromptsResult {
316    /// Echo of the requested scope.
317    pub scope: HistoryScope,
318    /// Cleaned prompts, newest first, bounded by [`MAX_RECENT_PROMPTS`].
319    pub prompts: Vec<RecentPrompt>,
320}
321
322/// Maximum prompts returned by one recent-prompts request.
323pub const MAX_RECENT_PROMPTS: usize = 100;
324
325/// Maximum captured output returned by one on-demand desktop page.
326pub const MAX_SESSION_OUTPUT_PAGE_BYTES: usize = 32 * 1024;
327
328/// Marker appended while more archived tool output is available.
329pub const RESTORED_OUTPUT_TRUNCATION_SUFFIX: &str = "\n[Output truncated in restored history.]";