vtcode_desktop_protocol/turn.rs
1//! Turn submission, acceptance, cancellation, and queue DTOs.
2
3use serde::{Deserialize, Serialize};
4
5/// A turn was rejected before delivery because a startup interaction is pending.
6pub const STARTUP_INTERACTION_PENDING_ERROR: i32 = -32001;
7
8/// Fallback turn id for interactions raised with no active turn (startup
9/// decisions). Produced by the service, matched by the desktop client.
10pub const SESSION_START_TURN_ID: &str = "session-start";
11
12/// Maximum queued-message previews carried by one queue-status notification.
13/// Mirrors the CLI sidebar, which renders the first few queued inputs.
14pub const MAX_QUEUE_STATUS_PREVIEWS: usize = 5;
15
16/// Maximum characters per queued-message preview before truncation.
17/// Mirrors the CLI sidebar preview width.
18pub const MAX_QUEUE_PREVIEW_CHARS: usize = 50;
19
20/// Bounded identity token for one queue entry, minted by the service so the
21/// client can address one message for take-latest and remove without relying
22/// on positional order (which races against concurrent submits).
23pub const MAX_QUEUE_ENTRY_ID_LEN: usize = 64;
24
25/// Build the display preview for one queued message: the full text when it
26/// fits, otherwise the first [`MAX_QUEUE_PREVIEW_CHARS`] characters plus an
27/// ellipsis. Truncation is char-boundary safe; newlines are preserved for the
28/// client to render.
29#[must_use]
30pub fn queue_preview_label(text: &str) -> String {
31 let mut preview = String::new();
32 for (index, ch) in text.chars().enumerate() {
33 if index >= MAX_QUEUE_PREVIEW_CHARS {
34 preview.push('\u{2026}');
35 return preview;
36 }
37 preview.push(ch);
38 }
39 preview
40}
41
42/// Parameters for submitting a prompt to the selected session.
43#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
44#[serde(rename_all = "camelCase")]
45pub struct TurnSubmitParams {
46 /// Session that owns the new turn.
47 pub session_id: String,
48 /// User prompt.
49 pub prompt: String,
50 /// Inline text attachments read by the client, applied as content parts.
51 #[serde(default)]
52 pub attachments: Vec<TurnAttachment>,
53 /// Whether a busy-time submission may merge with adjacent queued text-only
54 /// submissions into one turn. Mirrors the CLI Ctrl+Enter batchable flag;
55 /// submissions carrying attachments always dispatch alone.
56 #[serde(default)]
57 pub batchable: bool,
58}
59
60/// One client-read text attachment for a submitted turn.
61#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
62#[serde(rename_all = "camelCase")]
63pub struct TurnAttachment {
64 /// Display path used for labeling: workspace-relative for workspace
65 /// files, absolute for files attached from anywhere else.
66 pub path: String,
67 /// Bounded file content.
68 pub text: String,
69}
70
71/// Accepted turn response.
72#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
73#[serde(rename_all = "camelCase")]
74pub struct TurnAccepted {
75 /// Session identifier.
76 pub session_id: String,
77 /// Runtime turn identifier. Empty when `queued` is set: a queued prompt
78 /// owns no turn until its batch dispatches.
79 pub turn_id: String,
80 /// Whether the prompt was queued behind a running turn instead of
81 /// starting immediately. Absent (legacy services) means immediate.
82 #[serde(default)]
83 pub queued: bool,
84 /// Queue depth after this acceptance.
85 #[serde(default)]
86 pub queue_depth: usize,
87 /// Oldest messages discarded by the queue cap while accepting this one.
88 #[serde(default)]
89 pub dropped: usize,
90}
91
92/// Parameters for cancelling an active turn.
93#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
94#[serde(rename_all = "camelCase")]
95pub struct TurnCancelParams {
96 /// Owning session.
97 pub session_id: String,
98 /// Exact turn identity to cancel.
99 pub turn_id: String,
100 /// Discard queued turns behind the cancelled one (running-switch flow:
101 /// the terminal pump auto-dispatches the queue, so a deferred session
102 /// switch can only land on an empty queue). Absent (legacy clients)
103 /// means keep the queue: a plain stop lets queued prompts run
104 /// afterwards (TUI parity).
105 #[serde(default)]
106 pub clear_queue: bool,
107}
108
109/// Parameters for reversing one turn's exact file changes.
110#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
111#[serde(rename_all = "camelCase")]
112pub struct TurnUndoParams {
113 /// Session that owns the turn.
114 pub session_id: String,
115 /// Canonical turn identity (event `context.turn_id`).
116 pub turn_id: String,
117}
118
119/// One reversed file change.
120#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
121#[serde(rename_all = "camelCase")]
122pub struct TurnUndoFile {
123 /// Workspace-relative path.
124 pub path: String,
125 /// Kind of the reversed change.
126 pub kind: vtcode_exec_events::PatchChangeKind,
127 /// Reversed added-line count.
128 #[serde(default)]
129 pub additions: u64,
130 /// Reversed deleted-line count.
131 #[serde(default)]
132 pub deletions: u64,
133}
134
135/// Outcome of a turn undo request.
136#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
137#[serde(rename_all = "camelCase")]
138pub struct TurnUndoResult {
139 /// Session that owns the turn.
140 pub session_id: String,
141 /// Canonical turn identity.
142 pub turn_id: String,
143 /// Whether every recorded change was reversed. `false` carries the
144 /// refusal reason; nothing is partially reversed without disclosure.
145 pub undone: bool,
146 /// Reversed changes in recorded order: empty on clean refusals, holding
147 /// the applied prefix when a later file aborts the flush.
148 #[serde(default)]
149 pub files: Vec<TurnUndoFile>,
150 /// Refusal or failure explanation; empty on success.
151 #[serde(default)]
152 pub reason: String,
153}
154
155/// Parameters for popping the newest queued message back for editing.
156#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
157#[serde(rename_all = "camelCase")]
158pub struct TurnQueueTakeLatestParams {
159 /// Session that owns the queue.
160 pub session_id: String,
161}
162
163/// Result of popping the newest queued message.
164#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
165#[serde(rename_all = "camelCase")]
166pub struct TurnQueueTakeLatestResult {
167 /// Session that owns the queue.
168 pub session_id: String,
169 /// Popped message text, or `None` when the queue was empty.
170 #[serde(default)]
171 pub text: Option<String>,
172 /// Queue depth after the pop.
173 #[serde(default)]
174 pub queue_depth: usize,
175}
176
177/// Authoritative queued-message snapshot published by the service.
178#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
179#[serde(rename_all = "camelCase")]
180pub struct TurnQueueStatus {
181 /// Session that owns the queue.
182 pub session_id: String,
183 /// Number of messages waiting for dispatch.
184 pub queue_depth: usize,
185 /// Display previews for the oldest queued messages, oldest first, up to
186 /// [`MAX_QUEUE_STATUS_PREVIEWS`].
187 #[serde(default)]
188 pub previews: Vec<String>,
189 /// Entry identity tokens matching `previews` one-to-one (when the service
190 /// supplies them). Absent or shorter (legacy services) means the client
191 /// falls back to newest-first take-latest and hides per-row actions.
192 #[serde(default)]
193 pub entry_ids: Vec<String>,
194 /// Seconds since each entry was queued, oldest first, matching `previews`
195 /// one-to-one. Absent (legacy services) hides the elapsed-time column.
196 #[serde(default)]
197 pub queued_for_secs: Vec<u64>,
198 /// Total oldest messages discarded by the queue cap so far.
199 #[serde(default)]
200 pub dropped_total: usize,
201}
202
203/// Parameters for removing one queued message by entry id.
204#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
205#[serde(rename_all = "camelCase")]
206pub struct TurnQueueRemoveParams {
207 /// Session that owns the queue.
208 pub session_id: String,
209 /// Entry identity token from the latest `turn/queue-status` snapshot.
210 pub entry_id: String,
211}
212
213/// Outcome of a remove-one-queued-message request.
214#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
215#[serde(rename_all = "camelCase")]
216pub struct TurnQueueRemoveResult {
217 /// Session that owns the queue.
218 pub session_id: String,
219 /// Whether the entry id matched a queued message and it was removed.
220 pub removed: bool,
221 /// Queue depth after the removal.
222 pub queue_depth: usize,
223}