1use serde::{Deserialize, Serialize};
7use serde_json::Value;
8use vtcode_exec_events::VersionedThreadEvent;
9
10pub mod slash_command;
11
12pub const PROTOCOL_VERSION: ProtocolVersion = ProtocolVersion { major: 1, minor: 24 };
14
15pub const MAX_FRAME_BYTES: usize = 1024 * 1024;
17
18pub const DEFAULT_QUEUE_CAPACITY: usize = 128;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
23pub struct ProtocolVersion {
24 pub major: u16,
26 pub minor: u16,
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
32#[serde(rename_all = "camelCase")]
33pub struct ProtocolRange {
34 pub minimum: ProtocolVersion,
36 pub maximum: ProtocolVersion,
38}
39
40impl ProtocolRange {
41 pub fn negotiate(self, server: ProtocolVersion) -> Result<ProtocolVersion, ProtocolError> {
43 if self.minimum.major != self.maximum.major || server.major != self.minimum.major {
44 return Err(ProtocolError::IncompatibleVersion { client: self, server });
45 }
46
47 if server < self.minimum || server > self.maximum {
48 return Err(ProtocolError::IncompatibleVersion { client: self, server });
49 }
50
51 Ok(server)
52 }
53}
54
55#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
57#[serde(untagged)]
58pub enum RequestId {
59 String(String),
61 Number(i64),
63}
64
65#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
67pub struct Request {
68 pub jsonrpc: String,
70 pub id: RequestId,
72 pub method: String,
74 #[serde(default)]
76 pub params: Value,
77}
78
79#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
81pub struct Notification {
82 pub jsonrpc: String,
84 pub method: String,
86 #[serde(default)]
88 pub params: Value,
89}
90
91#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
93pub struct Response {
94 pub jsonrpc: String,
96 pub id: RequestId,
98 pub result: Value,
100}
101
102#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
104pub struct ErrorResponse {
105 pub jsonrpc: String,
107 pub id: Option<RequestId>,
109 pub error: RpcError,
111}
112
113#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
115pub struct RpcError {
116 pub code: i32,
118 pub message: String,
120 #[serde(skip_serializing_if = "Option::is_none")]
122 pub data: Option<Value>,
123}
124
125#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
127#[serde(rename_all = "camelCase")]
128pub struct InitializeParams {
129 pub protocol: ProtocolRange,
131 pub client_name: String,
133 pub client_version: String,
135}
136
137#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
139#[serde(rename_all = "camelCase")]
140pub struct InitializeResult {
141 pub protocol: ProtocolVersion,
143 pub workspace: String,
145 pub server_version: String,
147 #[serde(default)]
149 pub slash_commands: Vec<SlashCommandInfo>,
150}
151
152#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
154#[serde(rename_all = "camelCase")]
155pub struct SlashCommandInfo {
156 pub name: String,
158 pub description: String,
160}
161
162#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
164#[serde(rename_all = "camelCase")]
165pub struct ThreadEventNotification {
166 pub session_id: String,
168 pub turn_id: Option<String>,
170 pub event: VersionedThreadEvent,
172}
173
174#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
176#[serde(rename_all = "camelCase")]
177pub struct PrimaryAgentChanged {
178 pub session_id: String,
180 pub display_name: Option<String>,
182}
183
184pub mod method;
185
186#[derive(Debug, Clone, PartialEq, Eq)]
188pub enum ProtocolError {
189 FrameTooLarge {
191 limit: usize,
193 },
194 IncompatibleVersion {
196 client: ProtocolRange,
198 server: ProtocolVersion,
200 },
201 StaleInteraction,
203 UnknownInteraction,
205}
206
207impl std::fmt::Display for ProtocolError {
208 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
209 match self {
210 Self::FrameTooLarge { limit } => write!(formatter, "JSON-RPC frame exceeds {limit} bytes"),
211 Self::IncompatibleVersion { client, server } => write!(
212 formatter,
213 "incompatible desktop protocol versions: client {}.{}..={}.{}, server {}.{}",
214 client.minimum.major,
215 client.minimum.minor,
216 client.maximum.major,
217 client.maximum.minor,
218 server.major,
219 server.minor
220 ),
221 Self::StaleInteraction => {
222 formatter.write_str("interaction response does not match the active session and turn")
223 }
224 Self::UnknownInteraction => formatter.write_str("interaction is no longer pending"),
225 }
226 }
227}
228
229impl std::error::Error for ProtocolError {}
230
231pub mod interaction_registry;
232pub use interaction_registry::InteractionRegistry;
233
234pub mod frames;
235pub use frames::{read_frame, write_frame};
236
237pub fn success_response(id: RequestId, result: impl Serialize) -> Result<Response, serde_json::Error> {
239 Ok(Response {
240 jsonrpc: "2.0".to_string(),
241 id,
242 result: serde_json::to_value(result)?,
243 })
244}
245
246pub fn error_response(id: Option<RequestId>, code: i32, message: impl Into<String>) -> ErrorResponse {
248 ErrorResponse {
249 jsonrpc: "2.0".to_string(),
250 id,
251 error: RpcError { code, message: message.into(), data: None },
252 }
253}
254
255pub mod live_events;
256pub use live_events::{LiveEventReceiver, LiveEventSendError, LiveEventSender, live_event_channel};
257
258pub mod secret;
259pub use secret::{SecretStoreInfo, SecretStoreParams, SecretStoreResult};
260
261pub mod storage;
262pub use storage::{SessionDeleteResult, StorageSummary, WorktreeEntry, WorktreeRemoveParams, WorktreeRemoveResult};
263
264pub mod composer;
265pub use composer::{ComposerResource, ComposerResourceKind};
266
267pub mod config;
268pub use config::{
269 ConfigOpenFileParams, ConfigSetting, ConfigSettingsSnapshot, ConfigSettingsWriteParams, ConfigSettingsWriteResult,
270 ConfigTarget, ConfigTargetVersion, ConfigurationSnapshot, ModelCapabilities, ModelInfo, ModelSelectParams,
271 SharedUiPreferences,
272};
273
274pub mod interaction;
275pub use interaction::{InteractionChoice, InteractionKind, InteractionRequest, InteractionResponse, InteractionStep};
276
277pub mod review;
278pub use review::{
279 MAX_MCP_APP_BODY_BYTES, McpAppReadParams, McpAppReadResult, WorkspaceReviewParams, WorkspaceReviewResult,
280};
281
282pub mod session;
283pub use session::{
284 AgentModeInfo, MAX_SESSION_HISTORY_PAGE_MESSAGES, MAX_SESSION_OUTPUT_PAGE_BYTES, PermissionMode,
285 RESTORED_OUTPUT_TRUNCATION_SUFFIX, SessionCreateParams, SessionHistoryMessage, SessionHistoryPage,
286 SessionHistoryParams, SessionIdParams, SessionLoadResult, SessionOptionsSelectParams, SessionOptionsSnapshot,
287 SessionOutputCursor, SessionOutputPage, SessionOutputReadParams, SessionSummary, ThemePreferenceSelectParams,
288};
289
290pub mod turn;
291pub use turn::{
292 MAX_QUEUE_PREVIEW_CHARS, MAX_QUEUE_STATUS_PREVIEWS, SESSION_START_TURN_ID, STARTUP_INTERACTION_PENDING_ERROR,
293 TurnAccepted, TurnAttachment, TurnCancelParams, TurnQueueStatus, TurnQueueTakeLatestParams,
294 TurnQueueTakeLatestResult, TurnSubmitParams, queue_preview_label,
295};
296
297pub mod update;
298pub use update::UpdateStatusSnapshot;
299
300pub mod webmcp;
301pub use webmcp::{WebmcpBridgeStatus, WebmcpPairParams, WebmcpPairResult};
302
303#[cfg(test)]
304mod tests;