Skip to main content

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}