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/// Build the display preview for one queued message: the full text when it
21/// fits, otherwise the first [`MAX_QUEUE_PREVIEW_CHARS`] characters plus an
22/// ellipsis. Truncation is char-boundary safe; newlines are preserved for the
23/// client to render.
24#[must_use]
25pub fn queue_preview_label(text: &str) -> String {
26 let mut preview = String::new();
27 for (index, ch) in text.chars().enumerate() {
28 if index >= MAX_QUEUE_PREVIEW_CHARS {
29 preview.push('\u{2026}');
30 return preview;
31 }
32 preview.push(ch);
33 }
34 preview
35}
36
37/// Parameters for submitting a prompt to the selected session.
38#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
39#[serde(rename_all = "camelCase")]
40pub struct TurnSubmitParams {
41 /// Session that owns the new turn.
42 pub session_id: String,
43 /// User prompt.
44 pub prompt: String,
45 /// Inline text attachments read by the client, applied as content parts.
46 #[serde(default)]
47 pub attachments: Vec<TurnAttachment>,
48 /// Whether a busy-time submission may merge with adjacent queued text-only
49 /// submissions into one turn. Mirrors the CLI Ctrl+Enter batchable flag;
50 /// submissions carrying attachments always dispatch alone.
51 #[serde(default)]
52 pub batchable: bool,
53}
54
55/// One client-read text attachment for a submitted turn.
56#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
57#[serde(rename_all = "camelCase")]
58pub struct TurnAttachment {
59 /// Display path used for labeling: workspace-relative for workspace
60 /// files, absolute for files attached from anywhere else.
61 pub path: String,
62 /// Bounded file content.
63 pub text: String,
64}
65
66/// Accepted turn response.
67#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
68#[serde(rename_all = "camelCase")]
69pub struct TurnAccepted {
70 /// Session identifier.
71 pub session_id: String,
72 /// Runtime turn identifier. Empty when `queued` is set: a queued prompt
73 /// owns no turn until its batch dispatches.
74 pub turn_id: String,
75 /// Whether the prompt was queued behind a running turn instead of
76 /// starting immediately. Absent (legacy services) means immediate.
77 #[serde(default)]
78 pub queued: bool,
79 /// Queue depth after this acceptance.
80 #[serde(default)]
81 pub queue_depth: usize,
82 /// Oldest messages discarded by the queue cap while accepting this one.
83 #[serde(default)]
84 pub dropped: usize,
85}
86
87/// Parameters for cancelling an active turn.
88#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
89#[serde(rename_all = "camelCase")]
90pub struct TurnCancelParams {
91 /// Owning session.
92 pub session_id: String,
93 /// Exact turn identity to cancel.
94 pub turn_id: String,
95 /// Discard queued turns behind the cancelled one (running-switch flow:
96 /// the terminal pump auto-dispatches the queue, so a deferred session
97 /// switch can only land on an empty queue). Absent (legacy clients)
98 /// means keep the queue: a plain stop lets queued prompts run
99 /// afterwards (TUI parity).
100 #[serde(default)]
101 pub clear_queue: bool,
102}
103
104/// Parameters for popping the newest queued message back for editing.
105#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
106#[serde(rename_all = "camelCase")]
107pub struct TurnQueueTakeLatestParams {
108 /// Session that owns the queue.
109 pub session_id: String,
110}
111
112/// Result of popping the newest queued message.
113#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
114#[serde(rename_all = "camelCase")]
115pub struct TurnQueueTakeLatestResult {
116 /// Session that owns the queue.
117 pub session_id: String,
118 /// Popped message text, or `None` when the queue was empty.
119 #[serde(default)]
120 pub text: Option<String>,
121 /// Queue depth after the pop.
122 #[serde(default)]
123 pub queue_depth: usize,
124}
125
126/// Authoritative queued-message snapshot published by the service.
127#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
128#[serde(rename_all = "camelCase")]
129pub struct TurnQueueStatus {
130 /// Session that owns the queue.
131 pub session_id: String,
132 /// Number of messages waiting for dispatch.
133 pub queue_depth: usize,
134 /// Display previews for the oldest queued messages, oldest first, up to
135 /// [`MAX_QUEUE_STATUS_PREVIEWS`].
136 #[serde(default)]
137 pub previews: Vec<String>,
138 /// Total oldest messages discarded by the queue cap so far.
139 #[serde(default)]
140 pub dropped_total: usize,
141}