1use serde::{Deserialize, Serialize};
7use serde_json::Value;
8use vtcode_exec_events::VersionedThreadEvent;
9
10pub mod handoff_url;
11pub mod slash_command;
12
13pub const PROTOCOL_VERSION: ProtocolVersion = ProtocolVersion { major: 1, minor: 32 };
15
16pub const MAX_FRAME_BYTES: usize = 1024 * 1024;
18
19pub const DEFAULT_QUEUE_CAPACITY: usize = 128;
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
24pub struct ProtocolVersion {
25 pub major: u16,
27 pub minor: u16,
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
33#[serde(rename_all = "camelCase")]
34pub struct ProtocolRange {
35 pub minimum: ProtocolVersion,
37 pub maximum: ProtocolVersion,
39}
40
41impl ProtocolRange {
42 pub fn negotiate(self, server: ProtocolVersion) -> Result<ProtocolVersion, ProtocolError> {
44 if self.minimum.major != self.maximum.major || server.major != self.minimum.major {
45 return Err(ProtocolError::IncompatibleVersion { client: self, server });
46 }
47
48 if server < self.minimum || server > self.maximum {
49 return Err(ProtocolError::IncompatibleVersion { client: self, server });
50 }
51
52 Ok(server)
53 }
54}
55
56#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
58#[serde(untagged)]
59pub enum RequestId {
60 String(String),
62 Number(i64),
64}
65
66#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
68pub struct Request {
69 pub jsonrpc: String,
71 pub id: RequestId,
73 pub method: String,
75 #[serde(default)]
77 pub params: Value,
78}
79
80#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
82pub struct Notification {
83 pub jsonrpc: String,
85 pub method: String,
87 #[serde(default)]
89 pub params: Value,
90}
91
92#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
94pub struct Response {
95 pub jsonrpc: String,
97 pub id: RequestId,
99 pub result: Value,
101}
102
103#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
105pub struct ErrorResponse {
106 pub jsonrpc: String,
108 pub id: Option<RequestId>,
110 pub error: RpcError,
112}
113
114#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
116pub struct RpcError {
117 pub code: i32,
119 pub message: String,
121 #[serde(skip_serializing_if = "Option::is_none")]
123 pub data: Option<Value>,
124}
125
126#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
128#[serde(rename_all = "camelCase")]
129pub struct InitializeParams {
130 pub protocol: ProtocolRange,
132 pub client_name: String,
134 pub client_version: String,
136}
137
138#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
140#[serde(rename_all = "camelCase")]
141pub struct InitializeResult {
142 pub protocol: ProtocolVersion,
144 pub workspace: String,
146 pub server_version: String,
148 #[serde(default)]
150 pub slash_commands: Vec<SlashCommandInfo>,
151}
152
153#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
155#[serde(rename_all = "camelCase")]
156pub struct SlashCommandInfo {
157 pub name: String,
159 pub description: String,
161}
162
163#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
165#[serde(rename_all = "camelCase")]
166pub struct ThreadEventNotification {
167 pub session_id: String,
169 pub turn_id: Option<String>,
171 pub event: VersionedThreadEvent,
173}
174
175#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
177#[serde(rename_all = "camelCase")]
178pub struct PrimaryAgentChanged {
179 pub session_id: String,
181 pub display_name: Option<String>,
183}
184
185pub mod method;
186
187#[derive(Debug, Clone, PartialEq, Eq)]
189pub enum ProtocolError {
190 FrameTooLarge {
192 limit: usize,
194 },
195 IncompatibleVersion {
197 client: ProtocolRange,
199 server: ProtocolVersion,
201 },
202 StaleInteraction,
204 UnknownInteraction,
206}
207
208impl std::fmt::Display for ProtocolError {
209 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
210 match self {
211 Self::FrameTooLarge { limit } => write!(formatter, "JSON-RPC frame exceeds {limit} bytes"),
212 Self::IncompatibleVersion { client, server } => write!(
213 formatter,
214 "incompatible desktop protocol versions: client {}.{}..={}.{}, server {}.{}",
215 client.minimum.major,
216 client.minimum.minor,
217 client.maximum.major,
218 client.maximum.minor,
219 server.major,
220 server.minor
221 ),
222 Self::StaleInteraction => {
223 formatter.write_str("interaction response does not match the active session and turn")
224 }
225 Self::UnknownInteraction => formatter.write_str("interaction is no longer pending"),
226 }
227 }
228}
229
230impl std::error::Error for ProtocolError {}
231
232pub mod interaction_registry;
233pub use interaction_registry::InteractionRegistry;
234
235pub mod frames;
236pub use frames::{read_frame, write_frame};
237
238pub fn success_response(id: RequestId, result: impl Serialize) -> Result<Response, serde_json::Error> {
240 Ok(Response {
241 jsonrpc: "2.0".to_string(),
242 id,
243 result: serde_json::to_value(result)?,
244 })
245}
246
247pub fn error_response(id: Option<RequestId>, code: i32, message: impl Into<String>) -> ErrorResponse {
249 ErrorResponse {
250 jsonrpc: "2.0".to_string(),
251 id,
252 error: RpcError { code, message: message.into(), data: None },
253 }
254}
255
256pub mod live_events;
257pub use live_events::{LiveEventReceiver, LiveEventSendError, LiveEventSender, live_event_channel};
258
259pub mod secret;
260pub use secret::{SecretStoreInfo, SecretStoreParams, SecretStoreResult};
261
262pub mod storage;
263pub use storage::{SessionDeleteResult, StorageSummary, WorktreeEntry, WorktreeRemoveParams, WorktreeRemoveResult};
264
265pub mod composer;
266pub use composer::{ComposerResource, ComposerResourceKind};
267
268pub mod config;
269pub use config::{
270 ConfigOpenFileParams, ConfigSetting, ConfigSettingsSnapshot, ConfigSettingsWriteParams, ConfigSettingsWriteResult,
271 ConfigTarget, ConfigTargetVersion, ConfigurationSnapshot, ModelCapabilities, ModelInfo, ModelSelectParams,
272 SharedUiPreferences,
273};
274
275pub mod interaction;
276pub use interaction::{
277 InteractionChoice, InteractionHotkey, InteractionKind, InteractionRequest, InteractionResponse, InteractionStep,
278};
279
280pub mod review;
281pub use review::{
282 MAX_MCP_APP_BODY_BYTES, McpAppReadParams, McpAppReadResult, WorkspaceReviewParams, WorkspaceReviewResult,
283};
284
285pub mod session;
286pub use session::{
287 AgentModeInfo, MAX_RECENT_PROMPTS, MAX_SESSION_HISTORY_PAGE_MESSAGES, MAX_SESSION_LIST_LIMIT,
288 MAX_SESSION_OUTPUT_PAGE_BYTES, PermissionMode, RESTORED_OUTPUT_TRUNCATION_SUFFIX, RecentPrompt,
289 RecentPromptsParams, RecentPromptsResult, SessionCreateParams, SessionHistoryMessage, SessionHistoryPage,
290 SessionHistoryParams, SessionIdParams, SessionListParams, SessionLoadResult, SessionOptionsSelectParams,
291 SessionOptionsSnapshot, SessionOutputCursor, SessionOutputPage, SessionOutputReadParams, SessionSummary,
292 ThemePreferenceSelectParams,
293};
294
295pub mod turn;
296pub use turn::{
297 MAX_QUEUE_ENTRY_ID_LEN, MAX_QUEUE_PREVIEW_CHARS, MAX_QUEUE_STATUS_PREVIEWS, SESSION_START_TURN_ID,
298 STARTUP_INTERACTION_PENDING_ERROR, TurnAccepted, TurnAttachment, TurnCancelParams, TurnQueueRemoveParams,
299 TurnQueueRemoveResult, TurnQueueStatus, TurnQueueTakeLatestParams, TurnQueueTakeLatestResult, TurnSubmitParams,
300 TurnUndoFile, TurnUndoParams, TurnUndoResult, queue_preview_label,
301};
302
303pub mod update;
304pub use update::UpdateStatusSnapshot;
305
306pub mod webmcp;
307pub use webmcp::{WebmcpBridgeStatus, WebmcpPairParams, WebmcpPairResult};
308
309#[cfg(test)]
310mod tests;