Skip to main content

codex_codes/
protocol.rs

1//! App-server v2 protocol types for the Codex CLI.
2//!
3//! These types represent the JSON-RPC request parameters, response payloads,
4//! and notification bodies used by `codex app-server`. All wire types use
5//! camelCase field names via `#[serde(rename_all = "camelCase")]`.
6//!
7//! # Organization
8//!
9//! - **Request/Response pairs** — [`ThreadStartParams`]/[`ThreadStartResponse`],
10//!   [`TurnStartParams`]/[`TurnStartResponse`], etc.
11//! - **Server notifications** — Structs like [`TurnCompletedNotification`],
12//!   [`AgentMessageDeltaNotification`] that can be deserialized from the `params`
13//!   field of a [`ServerMessage::Notification`]
14//! - **Approval flow types** — [`CommandExecutionApprovalParams`] and
15//!   [`FileChangeApprovalParams`] for server-to-client requests that need a response
16//! - **Method constants** — The [`methods`] module contains all JSON-RPC method
17//!   name strings
18//!
19//! # Parsing notifications
20//!
21//! Prefer the typed dispatch in [`crate::messages`] over manual `method` checks:
22//!
23//! ```
24//! use codex_codes::{Notification, ServerMessage};
25//!
26//! fn handle(msg: ServerMessage) {
27//!     if let ServerMessage::Notification(Notification::TurnCompleted(c)) = msg {
28//!         println!("Turn {} on thread {} completed", c.turn.id, c.thread_id);
29//!     }
30//! }
31//! ```
32
33use crate::io::items::ThreadItem;
34use serde::{Deserialize, Serialize};
35use serde_json::Value;
36
37// ---------------------------------------------------------------------------
38// User input
39// ---------------------------------------------------------------------------
40
41/// User input sent as part of a [`TurnStartParams`].
42///
43/// # Example
44///
45/// ```
46/// use codex_codes::UserInput;
47///
48/// let text = UserInput::Text { text: "What is 2+2?".into() };
49/// let json = serde_json::to_string(&text).unwrap();
50/// assert!(json.contains(r#""type":"text""#));
51/// ```
52#[derive(Debug, Clone, Serialize, Deserialize)]
53#[serde(tag = "type", rename_all = "camelCase")]
54pub enum UserInput {
55    /// Text input from the user.
56    Text { text: String },
57    /// Pre-encoded image as a data URI (e.g., `data:image/png;base64,...`).
58    Image { data: String },
59}
60
61// ---------------------------------------------------------------------------
62// Initialization handshake
63// ---------------------------------------------------------------------------
64
65/// Client info sent during the `initialize` handshake.
66///
67/// Identifies the connecting client to the app-server.
68#[derive(Debug, Clone, Serialize, Deserialize)]
69#[serde(rename_all = "camelCase")]
70pub struct ClientInfo {
71    /// Client application name (e.g., `"my-codex-app"`).
72    pub name: String,
73    /// Client version string (e.g., `"1.0.0"`).
74    pub version: String,
75    /// Human-readable display name.
76    #[serde(skip_serializing_if = "Option::is_none")]
77    pub title: Option<String>,
78}
79
80/// Client capabilities negotiated during `initialize`.
81#[derive(Debug, Clone, Default, Serialize, Deserialize)]
82#[serde(rename_all = "camelCase")]
83pub struct InitializeCapabilities {
84    /// Opt into receiving experimental API methods and fields.
85    #[serde(default)]
86    pub experimental_api: bool,
87    /// Notification method names to suppress for this connection.
88    #[serde(skip_serializing_if = "Option::is_none")]
89    pub opt_out_notification_methods: Option<Vec<String>>,
90}
91
92/// Parameters for the `initialize` request.
93///
94/// Must be the first request sent after connecting to the app-server.
95#[derive(Debug, Clone, Serialize, Deserialize)]
96#[serde(rename_all = "camelCase")]
97pub struct InitializeParams {
98    /// Information about the connecting client.
99    pub client_info: ClientInfo,
100    /// Optional client capabilities.
101    #[serde(skip_serializing_if = "Option::is_none")]
102    pub capabilities: Option<InitializeCapabilities>,
103}
104
105/// Response from the `initialize` request.
106#[derive(Debug, Clone, Serialize, Deserialize)]
107#[serde(rename_all = "camelCase")]
108pub struct InitializeResponse {
109    /// The server's user-agent string.
110    pub user_agent: String,
111}
112
113// ---------------------------------------------------------------------------
114// Thread lifecycle requests
115// ---------------------------------------------------------------------------
116
117/// Parameters for `thread/start`.
118///
119/// Use `ThreadStartParams::default()` for a basic thread with no custom instructions.
120#[derive(Debug, Clone, Default, Serialize, Deserialize)]
121#[serde(rename_all = "camelCase")]
122pub struct ThreadStartParams {
123    /// Optional system instructions for the agent.
124    #[serde(skip_serializing_if = "Option::is_none")]
125    pub instructions: Option<String>,
126    /// Optional tool definitions to make available to the agent.
127    #[serde(skip_serializing_if = "Option::is_none")]
128    pub tools: Option<Vec<Value>>,
129}
130
131/// Thread metadata returned inside a [`ThreadStartResponse`].
132#[derive(Debug, Clone, Serialize, Deserialize)]
133#[serde(rename_all = "camelCase")]
134pub struct ThreadInfo {
135    /// Unique thread identifier.
136    pub id: String,
137    /// All other fields are captured but not typed.
138    #[serde(flatten)]
139    pub extra: Value,
140}
141
142/// Response from `thread/start`.
143#[derive(Debug, Clone, Serialize, Deserialize)]
144#[serde(rename_all = "camelCase")]
145pub struct ThreadStartResponse {
146    /// The created thread.
147    pub thread: ThreadInfo,
148    /// The model assigned to this thread.
149    #[serde(default)]
150    pub model: Option<String>,
151    /// All other fields are captured but not typed.
152    #[serde(flatten)]
153    pub extra: Value,
154}
155
156impl ThreadStartResponse {
157    /// Convenience accessor for the thread ID.
158    pub fn thread_id(&self) -> &str {
159        &self.thread.id
160    }
161}
162
163/// Parameters for `thread/archive`.
164#[derive(Debug, Clone, Serialize, Deserialize)]
165#[serde(rename_all = "camelCase")]
166pub struct ThreadArchiveParams {
167    pub thread_id: String,
168}
169
170/// Response from `thread/archive`.
171#[derive(Debug, Clone, Default, Serialize, Deserialize)]
172#[serde(rename_all = "camelCase")]
173pub struct ThreadArchiveResponse {}
174
175// ---------------------------------------------------------------------------
176// Turn lifecycle requests
177// ---------------------------------------------------------------------------
178
179/// Parameters for `turn/start`.
180///
181/// Starts a new agent turn within an existing thread. The agent processes the
182/// input and streams notifications until the turn completes.
183#[derive(Debug, Clone, Serialize, Deserialize)]
184#[serde(rename_all = "camelCase")]
185pub struct TurnStartParams {
186    /// The thread ID from [`ThreadStartResponse`].
187    pub thread_id: String,
188    /// One or more user inputs (text and/or images).
189    pub input: Vec<UserInput>,
190    /// Override the model for this turn (e.g., `"o4-mini"`).
191    #[serde(skip_serializing_if = "Option::is_none")]
192    pub model: Option<String>,
193    /// Override reasoning effort for this turn (e.g., `"low"`, `"medium"`, `"high"`).
194    #[serde(skip_serializing_if = "Option::is_none")]
195    pub reasoning_effort: Option<String>,
196    /// Override sandbox policy for this turn.
197    #[serde(skip_serializing_if = "Option::is_none")]
198    pub sandbox_policy: Option<Value>,
199}
200
201/// Response from `turn/start`.
202#[derive(Debug, Clone, Default, Serialize, Deserialize)]
203#[serde(rename_all = "camelCase")]
204pub struct TurnStartResponse {}
205
206/// Parameters for `turn/interrupt`.
207#[derive(Debug, Clone, Serialize, Deserialize)]
208#[serde(rename_all = "camelCase")]
209pub struct TurnInterruptParams {
210    pub thread_id: String,
211}
212
213/// Response from `turn/interrupt`.
214#[derive(Debug, Clone, Default, Serialize, Deserialize)]
215#[serde(rename_all = "camelCase")]
216pub struct TurnInterruptResponse {}
217
218// ---------------------------------------------------------------------------
219// Turn status & data types
220// ---------------------------------------------------------------------------
221
222/// Status of a turn within a [`Turn`].
223#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
224#[serde(rename_all = "camelCase")]
225pub enum TurnStatus {
226    /// The agent finished normally.
227    Completed,
228    /// The turn was interrupted by the client via `turn/interrupt`.
229    Interrupted,
230    /// The turn failed with an error (see [`Turn::error`]).
231    Failed,
232    /// The turn is still being processed.
233    InProgress,
234}
235
236/// Error information from a failed turn.
237#[derive(Debug, Clone, Serialize, Deserialize)]
238#[serde(rename_all = "camelCase")]
239pub struct TurnError {
240    pub message: String,
241    #[serde(skip_serializing_if = "Option::is_none")]
242    pub codex_error_info: Option<Value>,
243}
244
245/// A completed turn with its items and final status.
246///
247/// Included in [`TurnCompletedNotification`] when a turn finishes.
248#[derive(Debug, Clone, Serialize, Deserialize)]
249#[serde(rename_all = "camelCase")]
250pub struct Turn {
251    /// Unique turn identifier.
252    pub id: String,
253    /// All items produced during this turn (messages, commands, file changes, etc.).
254    #[serde(default)]
255    pub items: Vec<ThreadItem>,
256    /// Final status of the turn.
257    pub status: TurnStatus,
258    /// Error details if `status` is [`TurnStatus::Failed`].
259    #[serde(skip_serializing_if = "Option::is_none")]
260    pub error: Option<TurnError>,
261}
262
263// ---------------------------------------------------------------------------
264// Token usage
265// ---------------------------------------------------------------------------
266
267/// A snapshot of token counts within a single turn or aggregated across a
268/// thread. Sub-field of [`TokenUsage`].
269#[derive(Debug, Clone, Default, Serialize, Deserialize)]
270#[serde(rename_all = "camelCase")]
271pub struct TokenCounts {
272    /// Input tokens consumed.
273    #[serde(default)]
274    pub input_tokens: u64,
275    /// Output tokens generated.
276    #[serde(default)]
277    pub output_tokens: u64,
278    /// Input tokens served from cache.
279    #[serde(default)]
280    pub cached_input_tokens: u64,
281    /// Output tokens spent on chain-of-thought reasoning (model-dependent).
282    #[serde(default)]
283    pub reasoning_output_tokens: u64,
284    /// Sum total — may be redundant with the other counts.
285    #[serde(default)]
286    pub total_tokens: u64,
287}
288
289/// Cumulative token usage for a thread.
290///
291/// Sent via [`ThreadTokenUsageUpdatedNotification`] after each turn. Carries
292/// per-turn (`last`) and lifetime (`total`) counts plus the model's context
293/// window for client-side budget tracking.
294#[derive(Debug, Clone, Serialize, Deserialize)]
295#[serde(rename_all = "camelCase")]
296pub struct TokenUsage {
297    /// Counts for the most recently completed turn.
298    pub last: TokenCounts,
299    /// Cumulative counts for the entire thread.
300    pub total: TokenCounts,
301    /// The model's maximum context window in tokens.
302    pub model_context_window: u64,
303}
304
305// ---------------------------------------------------------------------------
306// Thread status
307// ---------------------------------------------------------------------------
308
309/// Status of a thread, sent via [`ThreadStatusChangedNotification`].
310///
311/// Wire format is internally tagged on `"type"`, with the `Active` variant
312/// carrying an `activeFlags` array of in-progress markers.
313#[derive(Debug, Clone, Serialize, Deserialize)]
314#[serde(tag = "type", rename_all = "camelCase")]
315pub enum ThreadStatus {
316    /// Thread is not yet loaded.
317    NotLoaded,
318    /// Thread is idle (no active turn).
319    Idle,
320    /// Thread has an active turn being processed.
321    Active {
322        /// Tags identifying what is in flight (e.g. running tools).
323        /// Shape is codex-version-dependent; preserved as raw JSON.
324        #[serde(default, skip_serializing_if = "Vec::is_empty")]
325        active_flags: Vec<Value>,
326    },
327    /// Thread encountered an unrecoverable error.
328    SystemError,
329}
330
331// ---------------------------------------------------------------------------
332// Server notifications
333// ---------------------------------------------------------------------------
334
335/// `thread/started` notification.
336///
337/// Sent once when a thread is created. Carries the full [`ThreadInfo`] for
338/// the new thread.
339#[derive(Debug, Clone, Serialize, Deserialize)]
340#[serde(rename_all = "camelCase")]
341pub struct ThreadStartedNotification {
342    pub thread: ThreadInfo,
343}
344
345/// `thread/status/changed` notification.
346#[derive(Debug, Clone, Serialize, Deserialize)]
347#[serde(rename_all = "camelCase")]
348pub struct ThreadStatusChangedNotification {
349    pub thread_id: String,
350    pub status: ThreadStatus,
351}
352
353/// `turn/started` notification.
354///
355/// Carries the freshly-created [`Turn`] (with `status: in_progress`).
356#[derive(Debug, Clone, Serialize, Deserialize)]
357#[serde(rename_all = "camelCase")]
358pub struct TurnStartedNotification {
359    pub thread_id: String,
360    pub turn: Turn,
361}
362
363/// `turn/completed` notification.
364///
365/// Carries the final [`Turn`] state with its full item list.
366#[derive(Debug, Clone, Serialize, Deserialize)]
367#[serde(rename_all = "camelCase")]
368pub struct TurnCompletedNotification {
369    pub thread_id: String,
370    pub turn: Turn,
371}
372
373/// `item/started` notification.
374#[derive(Debug, Clone, Serialize, Deserialize)]
375#[serde(rename_all = "camelCase")]
376pub struct ItemStartedNotification {
377    pub thread_id: String,
378    pub turn_id: String,
379    /// Server-side timestamp (milliseconds since Unix epoch) when the item
380    /// began. Optional — older codex builds omit it.
381    #[serde(default, skip_serializing_if = "Option::is_none")]
382    pub started_at_ms: Option<i64>,
383    pub item: ThreadItem,
384}
385
386/// `item/completed` notification.
387#[derive(Debug, Clone, Serialize, Deserialize)]
388#[serde(rename_all = "camelCase")]
389pub struct ItemCompletedNotification {
390    pub thread_id: String,
391    pub turn_id: String,
392    /// Server-side timestamp (milliseconds since Unix epoch) when the item
393    /// finished. Optional — older codex builds omit it.
394    #[serde(default, skip_serializing_if = "Option::is_none")]
395    pub completed_at_ms: Option<i64>,
396    pub item: ThreadItem,
397}
398
399/// `item/agentMessage/delta` notification.
400#[derive(Debug, Clone, Serialize, Deserialize)]
401#[serde(rename_all = "camelCase")]
402pub struct AgentMessageDeltaNotification {
403    pub thread_id: String,
404    pub item_id: String,
405    pub delta: String,
406}
407
408/// `item/commandExecution/outputDelta` notification.
409#[derive(Debug, Clone, Serialize, Deserialize)]
410#[serde(rename_all = "camelCase")]
411pub struct CmdOutputDeltaNotification {
412    pub thread_id: String,
413    pub item_id: String,
414    pub delta: String,
415}
416
417/// `item/fileChange/outputDelta` notification.
418#[derive(Debug, Clone, Serialize, Deserialize)]
419#[serde(rename_all = "camelCase")]
420pub struct FileChangeOutputDeltaNotification {
421    pub thread_id: String,
422    pub item_id: String,
423    pub delta: String,
424}
425
426/// `item/reasoning/summaryTextDelta` notification.
427#[derive(Debug, Clone, Serialize, Deserialize)]
428#[serde(rename_all = "camelCase")]
429pub struct ReasoningDeltaNotification {
430    pub thread_id: String,
431    pub item_id: String,
432    pub delta: String,
433}
434
435/// `error` notification.
436#[derive(Debug, Clone, Serialize, Deserialize)]
437#[serde(rename_all = "camelCase")]
438pub struct ErrorNotification {
439    pub error: String,
440    #[serde(skip_serializing_if = "Option::is_none")]
441    pub thread_id: Option<String>,
442    #[serde(skip_serializing_if = "Option::is_none")]
443    pub turn_id: Option<String>,
444    #[serde(default)]
445    pub will_retry: bool,
446}
447
448/// `thread/tokenUsage/updated` notification.
449///
450/// Emitted after each turn with cumulative and per-turn token counts.
451#[derive(Debug, Clone, Serialize, Deserialize)]
452#[serde(rename_all = "camelCase")]
453pub struct ThreadTokenUsageUpdatedNotification {
454    pub thread_id: String,
455    /// The turn that triggered this usage update. May be absent for
456    /// thread-level updates that aren't tied to a specific turn.
457    #[serde(default, skip_serializing_if = "Option::is_none")]
458    pub turn_id: Option<String>,
459    pub token_usage: TokenUsage,
460}
461
462/// A rate-limit window descriptor used inside [`RateLimits`].
463#[derive(Debug, Clone, Serialize, Deserialize)]
464#[serde(rename_all = "camelCase")]
465pub struct RateLimitWindow {
466    /// Unix timestamp (seconds) at which this rate-limit window resets.
467    pub resets_at: i64,
468    /// Percentage of the window already consumed (0-100).
469    pub used_percent: i32,
470    /// Length of the rate-limit window, in minutes.
471    pub window_duration_mins: i64,
472}
473
474/// Rate-limit envelope sent in [`AccountRateLimitsUpdatedNotification`].
475#[derive(Debug, Clone, Serialize, Deserialize)]
476#[serde(rename_all = "camelCase")]
477pub struct RateLimits {
478    /// Credit balance, if applicable for this plan. Shape is plan-dependent
479    /// so the payload is preserved as raw JSON.
480    #[serde(default, skip_serializing_if = "Option::is_none")]
481    pub credits: Option<Value>,
482    /// Stable machine identifier for the limit (e.g. `"codex"`).
483    pub limit_id: String,
484    /// Human-readable label, if the server provides one.
485    #[serde(default, skip_serializing_if = "Option::is_none")]
486    pub limit_name: Option<String>,
487    /// Plan tier (e.g. `"free"`, `"plus"`, `"team"`).
488    pub plan_type: String,
489    /// Primary (short-term) rate-limit window, if active.
490    #[serde(default, skip_serializing_if = "Option::is_none")]
491    pub primary: Option<RateLimitWindow>,
492    /// Secondary (longer-term) rate-limit window, if active.
493    #[serde(default, skip_serializing_if = "Option::is_none")]
494    pub secondary: Option<RateLimitWindow>,
495    /// Which window (if any) the account has already hit.
496    #[serde(default, skip_serializing_if = "Option::is_none")]
497    pub rate_limit_reached_type: Option<String>,
498}
499
500/// `account/rateLimits/updated` notification.
501#[derive(Debug, Clone, Serialize, Deserialize)]
502#[serde(rename_all = "camelCase")]
503pub struct AccountRateLimitsUpdatedNotification {
504    pub rate_limits: RateLimits,
505}
506
507/// `mcpServer/startupStatus/updated` notification.
508///
509/// Emitted by the app-server as each managed MCP server transitions through
510/// its startup lifecycle (e.g. `starting` → `ready` or `starting` → `failed`).
511#[derive(Debug, Clone, Serialize, Deserialize)]
512#[serde(rename_all = "camelCase")]
513pub struct McpServerStartupStatusUpdatedNotification {
514    /// MCP server identifier.
515    pub name: String,
516    /// Current lifecycle status string (e.g. `"starting"`, `"ready"`,
517    /// `"failed"`). Kept as `String` so new status values don't break parsing.
518    pub status: String,
519    /// Error message if startup failed.
520    #[serde(default, skip_serializing_if = "Option::is_none")]
521    pub error: Option<String>,
522}
523
524/// `remoteControl/status/changed` notification.
525#[derive(Debug, Clone, Serialize, Deserialize)]
526#[serde(rename_all = "camelCase")]
527pub struct RemoteControlStatusChangedNotification {
528    /// Status string (e.g. `"disabled"`, `"enabled"`).
529    pub status: String,
530    /// Connected environment id, if any.
531    #[serde(default, skip_serializing_if = "Option::is_none")]
532    pub environment_id: Option<String>,
533}
534
535/// `item/fileChange/patchUpdated` notification.
536///
537/// Emitted as the agent's tentative patch evolves during a turn. The
538/// `changes` array carries the current file-by-file diff snapshot.
539#[derive(Debug, Clone, Serialize, Deserialize)]
540#[serde(rename_all = "camelCase")]
541pub struct FileChangePatchUpdatedNotification {
542    pub thread_id: String,
543    pub turn_id: String,
544    pub item_id: String,
545    pub changes: Vec<crate::FileUpdateChange>,
546}
547
548/// `item/plan/delta` notification (EXPERIMENTAL).
549///
550/// Proposed plan streaming deltas for plan items. Clients should not assume
551/// concatenated deltas match the completed plan item content.
552#[derive(Debug, Clone, Serialize, Deserialize)]
553#[serde(rename_all = "camelCase")]
554pub struct PlanDeltaNotification {
555    pub thread_id: String,
556    pub turn_id: String,
557    pub item_id: String,
558    pub delta: String,
559}
560
561/// One step in a turn-level plan.
562#[derive(Debug, Clone, Serialize, Deserialize)]
563#[serde(rename_all = "camelCase")]
564pub struct TurnPlanStep {
565    pub step: String,
566    pub status: TurnPlanStepStatus,
567}
568
569/// Lifecycle state of a [`TurnPlanStep`].
570#[derive(Debug, Clone, Serialize, Deserialize)]
571#[serde(rename_all = "camelCase")]
572pub enum TurnPlanStepStatus {
573    Pending,
574    InProgress,
575    Completed,
576}
577
578/// `turn/plan/updated` notification.
579///
580/// Emitted when the agent updates its turn-level plan; the full plan is
581/// resent on each update.
582#[derive(Debug, Clone, Serialize, Deserialize)]
583#[serde(rename_all = "camelCase")]
584pub struct TurnPlanUpdatedNotification {
585    pub thread_id: String,
586    pub turn_id: String,
587    pub plan: Vec<TurnPlanStep>,
588    #[serde(default, skip_serializing_if = "Option::is_none")]
589    pub explanation: Option<String>,
590}
591
592/// `turn/diff/updated` notification.
593///
594/// Aggregated unified diff across all file changes in the current turn.
595/// Sent whenever the diff materially changes.
596#[derive(Debug, Clone, Serialize, Deserialize)]
597#[serde(rename_all = "camelCase")]
598pub struct TurnDiffUpdatedNotification {
599    pub thread_id: String,
600    pub turn_id: String,
601    pub diff: String,
602}
603
604/// `item/reasoning/summaryPartAdded` notification.
605///
606/// Signals that a new summary block is starting in the agent's reasoning
607/// stream. `summary_index` is the 0-based index of the new block.
608#[derive(Debug, Clone, Serialize, Deserialize)]
609#[serde(rename_all = "camelCase")]
610pub struct ReasoningSummaryPartAddedNotification {
611    pub thread_id: String,
612    pub turn_id: String,
613    pub item_id: String,
614    pub summary_index: i64,
615}
616
617/// `item/reasoning/textDelta` notification.
618///
619/// Streaming delta for the agent's verbose reasoning text (separate from
620/// the summary stream).
621#[derive(Debug, Clone, Serialize, Deserialize)]
622#[serde(rename_all = "camelCase")]
623pub struct ReasoningTextDeltaNotification {
624    pub thread_id: String,
625    pub turn_id: String,
626    pub item_id: String,
627    pub content_index: i64,
628    pub delta: String,
629}
630
631/// `mcpServer/oauthLogin/completed` notification.
632///
633/// Emitted when an MCP server's OAuth login flow completes (or fails).
634#[derive(Debug, Clone, Serialize, Deserialize)]
635#[serde(rename_all = "camelCase")]
636pub struct McpServerOauthLoginCompletedNotification {
637    pub name: String,
638    pub success: bool,
639    #[serde(default, skip_serializing_if = "Option::is_none")]
640    pub error: Option<String>,
641}
642
643/// `account/login/completed` notification.
644#[derive(Debug, Clone, Serialize, Deserialize)]
645#[serde(rename_all = "camelCase")]
646pub struct AccountLoginCompletedNotification {
647    pub success: bool,
648    #[serde(default, skip_serializing_if = "Option::is_none")]
649    pub login_id: Option<String>,
650    #[serde(default, skip_serializing_if = "Option::is_none")]
651    pub error: Option<String>,
652}
653
654/// `deprecationNotice` notification.
655#[derive(Debug, Clone, Serialize, Deserialize)]
656#[serde(rename_all = "camelCase")]
657pub struct DeprecationNoticeNotification {
658    pub summary: String,
659    #[serde(default, skip_serializing_if = "Option::is_none")]
660    pub details: Option<String>,
661}
662
663/// `guardianWarning` notification.
664#[derive(Debug, Clone, Serialize, Deserialize)]
665#[serde(rename_all = "camelCase")]
666pub struct GuardianWarningNotification {
667    pub thread_id: String,
668    pub message: String,
669}
670
671/// `warning` notification (general advisory).
672#[derive(Debug, Clone, Serialize, Deserialize)]
673#[serde(rename_all = "camelCase")]
674pub struct WarningNotification {
675    pub message: String,
676    #[serde(default, skip_serializing_if = "Option::is_none")]
677    pub thread_id: Option<String>,
678}
679
680/// `thread/archived` notification.
681#[derive(Debug, Clone, Serialize, Deserialize)]
682#[serde(rename_all = "camelCase")]
683pub struct ThreadArchivedNotification {
684    pub thread_id: String,
685}
686
687/// `thread/closed` notification.
688#[derive(Debug, Clone, Serialize, Deserialize)]
689#[serde(rename_all = "camelCase")]
690pub struct ThreadClosedNotification {
691    pub thread_id: String,
692}
693
694/// `thread/unarchived` notification.
695#[derive(Debug, Clone, Serialize, Deserialize)]
696#[serde(rename_all = "camelCase")]
697pub struct ThreadUnarchivedNotification {
698    pub thread_id: String,
699}
700
701/// `thread/goal/cleared` notification.
702#[derive(Debug, Clone, Serialize, Deserialize)]
703#[serde(rename_all = "camelCase")]
704pub struct ThreadGoalClearedNotification {
705    pub thread_id: String,
706}
707
708/// `thread/name/updated` notification.
709#[derive(Debug, Clone, Serialize, Deserialize)]
710#[serde(rename_all = "camelCase")]
711pub struct ThreadNameUpdatedNotification {
712    pub thread_id: String,
713    #[serde(default, skip_serializing_if = "Option::is_none")]
714    pub thread_name: Option<String>,
715}
716
717/// `skills/changed` notification (no payload).
718#[derive(Debug, Clone, Default, Serialize, Deserialize)]
719#[serde(rename_all = "camelCase")]
720pub struct SkillsChangedNotification {}
721
722/// `fs/changed` notification.
723#[derive(Debug, Clone, Serialize, Deserialize)]
724#[serde(rename_all = "camelCase")]
725pub struct FsChangedNotification {
726    pub watch_id: String,
727    pub changed_paths: Vec<String>,
728}
729
730/// `configWarning` notification.
731///
732/// The `range` field's shape is left as raw JSON for now; expand to a typed
733/// `SourceRange` if/when callers need it.
734#[derive(Debug, Clone, Serialize, Deserialize)]
735#[serde(rename_all = "camelCase")]
736pub struct ConfigWarningNotification {
737    pub summary: String,
738    #[serde(default, skip_serializing_if = "Option::is_none")]
739    pub path: Option<String>,
740    #[serde(default, skip_serializing_if = "Option::is_none")]
741    pub details: Option<String>,
742    #[serde(default, skip_serializing_if = "Option::is_none")]
743    pub range: Option<Value>,
744}
745
746// ---------------------------------------------------------------------------
747// Notification stubs — these wrap the wire `Value` unchanged so that wire
748// shape is preserved and the typed `Notification` enum can dispatch every
749// method the schema enumerates. Each can be promoted to a fully-typed
750// struct later without changing the dispatch surface.
751// ---------------------------------------------------------------------------
752
753/// `account/updated` notification (stub — accepts the wire shape unchanged).
754#[derive(Debug, Clone, Serialize, Deserialize, Default)]
755#[serde(transparent)]
756pub struct AccountUpdatedNotification(pub Value);
757
758/// `app/list/updated` notification (stub).
759#[derive(Debug, Clone, Serialize, Deserialize, Default)]
760#[serde(transparent)]
761pub struct AppListUpdatedNotification(pub Value);
762
763/// `command/exec/outputDelta` notification (stub).
764#[derive(Debug, Clone, Serialize, Deserialize, Default)]
765#[serde(transparent)]
766pub struct CommandExecOutputDeltaNotification(pub Value);
767
768/// `externalAgentConfig/import/completed` notification (stub).
769#[derive(Debug, Clone, Serialize, Deserialize, Default)]
770#[serde(transparent)]
771pub struct ExternalAgentConfigImportCompletedNotification(pub Value);
772
773/// `fuzzyFileSearch/sessionCompleted` notification (stub).
774#[derive(Debug, Clone, Serialize, Deserialize, Default)]
775#[serde(transparent)]
776pub struct FuzzyFileSearchSessionCompletedNotification(pub Value);
777
778/// `fuzzyFileSearch/sessionUpdated` notification (stub).
779#[derive(Debug, Clone, Serialize, Deserialize, Default)]
780#[serde(transparent)]
781pub struct FuzzyFileSearchSessionUpdatedNotification(pub Value);
782
783/// `hook/completed` notification (stub).
784#[derive(Debug, Clone, Serialize, Deserialize, Default)]
785#[serde(transparent)]
786pub struct HookCompletedNotification(pub Value);
787
788/// `hook/started` notification (stub).
789#[derive(Debug, Clone, Serialize, Deserialize, Default)]
790#[serde(transparent)]
791pub struct HookStartedNotification(pub Value);
792
793/// `item/autoApprovalReview/completed` notification (stub).
794#[derive(Debug, Clone, Serialize, Deserialize, Default)]
795#[serde(transparent)]
796pub struct ItemGuardianApprovalReviewCompletedNotification(pub Value);
797
798/// `item/autoApprovalReview/started` notification (stub).
799#[derive(Debug, Clone, Serialize, Deserialize, Default)]
800#[serde(transparent)]
801pub struct ItemGuardianApprovalReviewStartedNotification(pub Value);
802
803/// `item/commandExecution/terminalInteraction` notification (stub).
804#[derive(Debug, Clone, Serialize, Deserialize, Default)]
805#[serde(transparent)]
806pub struct TerminalInteractionNotification(pub Value);
807
808/// `item/mcpToolCall/progress` notification (stub).
809#[derive(Debug, Clone, Serialize, Deserialize, Default)]
810#[serde(transparent)]
811pub struct McpToolCallProgressNotification(pub Value);
812
813/// `model/rerouted` notification (stub).
814#[derive(Debug, Clone, Serialize, Deserialize, Default)]
815#[serde(transparent)]
816pub struct ModelReroutedNotification(pub Value);
817
818/// `model/verification` notification (stub).
819#[derive(Debug, Clone, Serialize, Deserialize, Default)]
820#[serde(transparent)]
821pub struct ModelVerificationNotification(pub Value);
822
823/// `process/exited` notification (stub).
824#[derive(Debug, Clone, Serialize, Deserialize, Default)]
825#[serde(transparent)]
826pub struct ProcessExitedNotification(pub Value);
827
828/// `process/outputDelta` notification (stub).
829#[derive(Debug, Clone, Serialize, Deserialize, Default)]
830#[serde(transparent)]
831pub struct ProcessOutputDeltaNotification(pub Value);
832
833/// `serverRequest/resolved` notification (stub).
834#[derive(Debug, Clone, Serialize, Deserialize, Default)]
835#[serde(transparent)]
836pub struct ServerRequestResolvedNotification(pub Value);
837
838/// `thread/compacted` notification (stub).
839#[derive(Debug, Clone, Serialize, Deserialize, Default)]
840#[serde(transparent)]
841pub struct ContextCompactedNotification(pub Value);
842
843/// `thread/goal/updated` notification (stub).
844#[derive(Debug, Clone, Serialize, Deserialize, Default)]
845#[serde(transparent)]
846pub struct ThreadGoalUpdatedNotification(pub Value);
847
848/// `thread/realtime/closed` notification (stub).
849#[derive(Debug, Clone, Serialize, Deserialize, Default)]
850#[serde(transparent)]
851pub struct ThreadRealtimeClosedNotification(pub Value);
852
853/// `thread/realtime/error` notification (stub).
854#[derive(Debug, Clone, Serialize, Deserialize, Default)]
855#[serde(transparent)]
856pub struct ThreadRealtimeErrorNotification(pub Value);
857
858/// `thread/realtime/itemAdded` notification (stub).
859#[derive(Debug, Clone, Serialize, Deserialize, Default)]
860#[serde(transparent)]
861pub struct ThreadRealtimeItemAddedNotification(pub Value);
862
863/// `thread/realtime/outputAudio/delta` notification (stub).
864#[derive(Debug, Clone, Serialize, Deserialize, Default)]
865#[serde(transparent)]
866pub struct ThreadRealtimeOutputAudioDeltaNotification(pub Value);
867
868/// `thread/realtime/sdp` notification (stub).
869#[derive(Debug, Clone, Serialize, Deserialize, Default)]
870#[serde(transparent)]
871pub struct ThreadRealtimeSdpNotification(pub Value);
872
873/// `thread/realtime/started` notification (stub).
874#[derive(Debug, Clone, Serialize, Deserialize, Default)]
875#[serde(transparent)]
876pub struct ThreadRealtimeStartedNotification(pub Value);
877
878/// `thread/realtime/transcript/delta` notification (stub).
879#[derive(Debug, Clone, Serialize, Deserialize, Default)]
880#[serde(transparent)]
881pub struct ThreadRealtimeTranscriptDeltaNotification(pub Value);
882
883/// `thread/realtime/transcript/done` notification (stub).
884#[derive(Debug, Clone, Serialize, Deserialize, Default)]
885#[serde(transparent)]
886pub struct ThreadRealtimeTranscriptDoneNotification(pub Value);
887
888/// `windows/worldWritableWarning` notification (stub).
889#[derive(Debug, Clone, Serialize, Deserialize, Default)]
890#[serde(transparent)]
891pub struct WindowsWorldWritableWarningNotification(pub Value);
892
893/// `windowsSandbox/setupCompleted` notification (stub).
894#[derive(Debug, Clone, Serialize, Deserialize, Default)]
895#[serde(transparent)]
896pub struct WindowsSandboxSetupCompletedNotification(pub Value);
897
898// ---------------------------------------------------------------------------
899// Approval flow types (server-to-client requests)
900// ---------------------------------------------------------------------------
901
902/// Decision for command execution approval.
903///
904/// Sent as part of [`CommandExecutionApprovalResponse`] when responding to
905/// a command approval request from the server.
906#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
907#[serde(rename_all = "camelCase")]
908pub enum CommandApprovalDecision {
909    /// Allow this specific command to execute.
910    Accept,
911    /// Allow this command and similar future commands in this session.
912    AcceptForSession,
913    /// Reject this command.
914    Decline,
915    /// Cancel the entire turn.
916    Cancel,
917}
918
919/// Parameters for `item/commandExecution/requestApproval` (server → client).
920///
921/// The server sends this as a [`ServerMessage::Request`] when the agent wants
922/// to execute a command that requires user approval. Respond with
923/// [`CommandExecutionApprovalResponse`].
924#[derive(Debug, Clone, Serialize, Deserialize)]
925#[serde(rename_all = "camelCase")]
926pub struct CommandExecutionApprovalParams {
927    pub thread_id: String,
928    pub turn_id: String,
929    /// Unique identifier for this tool call.
930    pub call_id: String,
931    /// The shell command the agent wants to run.
932    pub command: String,
933    /// Working directory for the command.
934    pub cwd: String,
935    /// Human-readable explanation of why the command is needed.
936    #[serde(skip_serializing_if = "Option::is_none")]
937    pub reason: Option<String>,
938}
939
940/// Response for `item/commandExecution/requestApproval`.
941#[derive(Debug, Clone, Serialize, Deserialize)]
942#[serde(rename_all = "camelCase")]
943pub struct CommandExecutionApprovalResponse {
944    pub decision: CommandApprovalDecision,
945}
946
947/// Decision for file change approval.
948///
949/// Sent as part of [`FileChangeApprovalResponse`] when responding to
950/// a file change approval request from the server.
951#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
952#[serde(rename_all = "camelCase")]
953pub enum FileChangeApprovalDecision {
954    /// Allow this specific file change.
955    Accept,
956    /// Allow this and similar future file changes in this session.
957    AcceptForSession,
958    /// Reject this file change.
959    Decline,
960    /// Cancel the entire turn.
961    Cancel,
962}
963
964/// Parameters for `item/fileChange/requestApproval` (server → client).
965///
966/// The server sends this as a [`ServerMessage::Request`] when the agent wants
967/// to modify files and requires user approval. Respond with
968/// [`FileChangeApprovalResponse`].
969#[derive(Debug, Clone, Serialize, Deserialize)]
970#[serde(rename_all = "camelCase")]
971pub struct FileChangeApprovalParams {
972    pub thread_id: String,
973    pub turn_id: String,
974    /// Unique identifier for this tool call.
975    pub call_id: String,
976    /// The proposed file changes (structure varies by patch format).
977    pub changes: Value,
978    /// Human-readable explanation of why the changes are needed.
979    #[serde(skip_serializing_if = "Option::is_none")]
980    pub reason: Option<String>,
981}
982
983/// Response for `item/fileChange/requestApproval`.
984#[derive(Debug, Clone, Serialize, Deserialize)]
985#[serde(rename_all = "camelCase")]
986pub struct FileChangeApprovalResponse {
987    pub decision: FileChangeApprovalDecision,
988}
989
990// The [`ServerMessage`] enum that clients return now lives in
991// [`crate::messages`] alongside the typed [`crate::Notification`] and
992// [`crate::ServerRequest`] dispatch enums.
993
994// ---------------------------------------------------------------------------
995// Method name constants
996// ---------------------------------------------------------------------------
997
998/// JSON-RPC method names used by the app-server protocol.
999///
1000/// Use these constants when matching on [`ServerMessage::Notification`] or
1001/// [`ServerMessage::Request`] method fields to avoid typos.
1002pub mod methods {
1003    // Client → server requests
1004    pub const INITIALIZE: &str = "initialize";
1005    pub const INITIALIZED: &str = "initialized";
1006    pub const THREAD_START: &str = "thread/start";
1007    pub const THREAD_ARCHIVE: &str = "thread/archive";
1008    pub const TURN_START: &str = "turn/start";
1009    pub const TURN_INTERRUPT: &str = "turn/interrupt";
1010    pub const TURN_STEER: &str = "turn/steer";
1011    // Additional client → server requests (modeled at the method-string level
1012    // for coverage; typed *Params/*Response structs are added on demand).
1013    pub const THREAD_RESUME: &str = "thread/resume";
1014    pub const THREAD_FORK: &str = "thread/fork";
1015    pub const THREAD_UNSUBSCRIBE: &str = "thread/unsubscribe";
1016    pub const THREAD_NAME_SET: &str = "thread/name/set";
1017    pub const THREAD_METADATA_UPDATE: &str = "thread/metadata/update";
1018    pub const THREAD_UNARCHIVE: &str = "thread/unarchive";
1019    pub const THREAD_COMPACT_START: &str = "thread/compact/start";
1020    pub const THREAD_SHELLCOMMAND: &str = "thread/shellCommand";
1021    pub const THREAD_APPROVEGUARDIANDENIEDACTION: &str = "thread/approveGuardianDeniedAction";
1022    pub const THREAD_ROLLBACK: &str = "thread/rollback";
1023    pub const THREAD_LIST: &str = "thread/list";
1024    pub const THREAD_LOADED_LIST: &str = "thread/loaded/list";
1025    pub const THREAD_READ: &str = "thread/read";
1026    pub const THREAD_INJECT_ITEMS: &str = "thread/inject_items";
1027    pub const SKILLS_LIST: &str = "skills/list";
1028    pub const HOOKS_LIST: &str = "hooks/list";
1029    pub const MARKETPLACE_ADD: &str = "marketplace/add";
1030    pub const MARKETPLACE_REMOVE: &str = "marketplace/remove";
1031    pub const MARKETPLACE_UPGRADE: &str = "marketplace/upgrade";
1032    pub const PLUGIN_LIST: &str = "plugin/list";
1033    pub const PLUGIN_READ: &str = "plugin/read";
1034    pub const PLUGIN_SKILL_READ: &str = "plugin/skill/read";
1035    pub const PLUGIN_SHARE_SAVE: &str = "plugin/share/save";
1036    pub const PLUGIN_SHARE_UPDATETARGETS: &str = "plugin/share/updateTargets";
1037    pub const PLUGIN_SHARE_LIST: &str = "plugin/share/list";
1038    pub const PLUGIN_SHARE_CHECKOUT: &str = "plugin/share/checkout";
1039    pub const PLUGIN_SHARE_DELETE: &str = "plugin/share/delete";
1040    pub const APP_LIST: &str = "app/list";
1041    pub const FS_READFILE: &str = "fs/readFile";
1042    pub const FS_WRITEFILE: &str = "fs/writeFile";
1043    pub const FS_CREATEDIRECTORY: &str = "fs/createDirectory";
1044    pub const FS_GETMETADATA: &str = "fs/getMetadata";
1045    pub const FS_READDIRECTORY: &str = "fs/readDirectory";
1046    pub const FS_REMOVE: &str = "fs/remove";
1047    pub const FS_COPY: &str = "fs/copy";
1048    pub const FS_WATCH: &str = "fs/watch";
1049    pub const FS_UNWATCH: &str = "fs/unwatch";
1050    pub const SKILLS_CONFIG_WRITE: &str = "skills/config/write";
1051    pub const PLUGIN_INSTALL: &str = "plugin/install";
1052    pub const PLUGIN_UNINSTALL: &str = "plugin/uninstall";
1053    pub const REVIEW_START: &str = "review/start";
1054    pub const MODEL_LIST: &str = "model/list";
1055    pub const MODELPROVIDER_CAPABILITIES_READ: &str = "modelProvider/capabilities/read";
1056    pub const EXPERIMENTALFEATURE_LIST: &str = "experimentalFeature/list";
1057    pub const EXPERIMENTALFEATURE_ENABLEMENT_SET: &str = "experimentalFeature/enablement/set";
1058    pub const MCPSERVER_OAUTH_LOGIN: &str = "mcpServer/oauth/login";
1059    pub const CONFIG_MCPSERVER_RELOAD: &str = "config/mcpServer/reload";
1060    pub const MCPSERVERSTATUS_LIST: &str = "mcpServerStatus/list";
1061    pub const MCPSERVER_RESOURCE_READ: &str = "mcpServer/resource/read";
1062    pub const MCPSERVER_TOOL_CALL: &str = "mcpServer/tool/call";
1063    pub const WINDOWSSANDBOX_SETUPSTART: &str = "windowsSandbox/setupStart";
1064    pub const WINDOWSSANDBOX_READINESS: &str = "windowsSandbox/readiness";
1065    pub const ACCOUNT_LOGIN_START: &str = "account/login/start";
1066    pub const ACCOUNT_LOGIN_CANCEL: &str = "account/login/cancel";
1067    pub const ACCOUNT_LOGOUT: &str = "account/logout";
1068    pub const ACCOUNT_RATELIMITS_READ: &str = "account/rateLimits/read";
1069    pub const ACCOUNT_SENDADDCREDITSNUDGEEMAIL: &str = "account/sendAddCreditsNudgeEmail";
1070    pub const FEEDBACK_UPLOAD: &str = "feedback/upload";
1071    pub const COMMAND_EXEC: &str = "command/exec";
1072    pub const COMMAND_EXEC_WRITE: &str = "command/exec/write";
1073    pub const COMMAND_EXEC_TERMINATE: &str = "command/exec/terminate";
1074    pub const COMMAND_EXEC_RESIZE: &str = "command/exec/resize";
1075    pub const CONFIG_READ: &str = "config/read";
1076    pub const EXTERNALAGENTCONFIG_DETECT: &str = "externalAgentConfig/detect";
1077    pub const EXTERNALAGENTCONFIG_IMPORT: &str = "externalAgentConfig/import";
1078    pub const CONFIG_VALUE_WRITE: &str = "config/value/write";
1079    pub const CONFIG_BATCHWRITE: &str = "config/batchWrite";
1080    pub const CONFIGREQUIREMENTS_READ: &str = "configRequirements/read";
1081    pub const ACCOUNT_READ: &str = "account/read";
1082    pub const FUZZYFILESEARCH: &str = "fuzzyFileSearch";
1083
1084    // Server → client notifications
1085    pub const THREAD_STARTED: &str = "thread/started";
1086    pub const THREAD_STATUS_CHANGED: &str = "thread/status/changed";
1087    pub const THREAD_TOKEN_USAGE_UPDATED: &str = "thread/tokenUsage/updated";
1088    pub const TURN_STARTED: &str = "turn/started";
1089    pub const TURN_COMPLETED: &str = "turn/completed";
1090    pub const ITEM_STARTED: &str = "item/started";
1091    pub const ITEM_COMPLETED: &str = "item/completed";
1092    pub const AGENT_MESSAGE_DELTA: &str = "item/agentMessage/delta";
1093    pub const CMD_OUTPUT_DELTA: &str = "item/commandExecution/outputDelta";
1094    pub const FILE_CHANGE_OUTPUT_DELTA: &str = "item/fileChange/outputDelta";
1095    pub const REASONING_DELTA: &str = "item/reasoning/summaryTextDelta";
1096    pub const ERROR: &str = "error";
1097    pub const ACCOUNT_RATE_LIMITS_UPDATED: &str = "account/rateLimits/updated";
1098    pub const MCP_SERVER_STARTUP_STATUS_UPDATED: &str = "mcpServer/startupStatus/updated";
1099    pub const MCP_SERVER_OAUTH_LOGIN_COMPLETED: &str = "mcpServer/oauthLogin/completed";
1100    pub const REMOTE_CONTROL_STATUS_CHANGED: &str = "remoteControl/status/changed";
1101    pub const FILE_CHANGE_PATCH_UPDATED: &str = "item/fileChange/patchUpdated";
1102    pub const PLAN_DELTA: &str = "item/plan/delta";
1103    pub const TURN_PLAN_UPDATED: &str = "turn/plan/updated";
1104    pub const TURN_DIFF_UPDATED: &str = "turn/diff/updated";
1105    pub const REASONING_SUMMARY_PART_ADDED: &str = "item/reasoning/summaryPartAdded";
1106    pub const REASONING_TEXT_DELTA: &str = "item/reasoning/textDelta";
1107    pub const ACCOUNT_LOGIN_COMPLETED: &str = "account/login/completed";
1108    pub const DEPRECATION_NOTICE: &str = "deprecationNotice";
1109    pub const GUARDIAN_WARNING: &str = "guardianWarning";
1110    pub const WARNING: &str = "warning";
1111    pub const THREAD_ARCHIVED: &str = "thread/archived";
1112    pub const THREAD_CLOSED: &str = "thread/closed";
1113    pub const THREAD_UNARCHIVED: &str = "thread/unarchived";
1114    pub const THREAD_GOAL_CLEARED: &str = "thread/goal/cleared";
1115    pub const THREAD_NAME_UPDATED: &str = "thread/name/updated";
1116    pub const SKILLS_CHANGED: &str = "skills/changed";
1117    pub const FS_CHANGED: &str = "fs/changed";
1118    pub const CONFIG_WARNING: &str = "configWarning";
1119    pub const ACCOUNT_UPDATED: &str = "account/updated";
1120    pub const APP_LIST_UPDATED: &str = "app/list/updated";
1121    pub const COMMAND_EXEC_OUTPUT_DELTA: &str = "command/exec/outputDelta";
1122    pub const EXTERNAL_AGENT_CONFIG_IMPORT_COMPLETED: &str = "externalAgentConfig/import/completed";
1123    pub const FUZZY_FILE_SEARCH_SESSION_COMPLETED: &str = "fuzzyFileSearch/sessionCompleted";
1124    pub const FUZZY_FILE_SEARCH_SESSION_UPDATED: &str = "fuzzyFileSearch/sessionUpdated";
1125    pub const HOOK_COMPLETED: &str = "hook/completed";
1126    pub const HOOK_STARTED: &str = "hook/started";
1127    pub const ITEM_AUTO_APPROVAL_REVIEW_COMPLETED: &str = "item/autoApprovalReview/completed";
1128    pub const ITEM_AUTO_APPROVAL_REVIEW_STARTED: &str = "item/autoApprovalReview/started";
1129    pub const ITEM_COMMAND_EXEC_TERMINAL_INTERACTION: &str =
1130        "item/commandExecution/terminalInteraction";
1131    pub const ITEM_MCP_TOOL_CALL_PROGRESS: &str = "item/mcpToolCall/progress";
1132    pub const MODEL_REROUTED: &str = "model/rerouted";
1133    pub const MODEL_VERIFICATION: &str = "model/verification";
1134    pub const PROCESS_EXITED: &str = "process/exited";
1135    pub const PROCESS_OUTPUT_DELTA: &str = "process/outputDelta";
1136    pub const SERVER_REQUEST_RESOLVED: &str = "serverRequest/resolved";
1137    pub const THREAD_COMPACTED: &str = "thread/compacted";
1138    pub const THREAD_GOAL_UPDATED: &str = "thread/goal/updated";
1139    pub const THREAD_REALTIME_CLOSED: &str = "thread/realtime/closed";
1140    pub const THREAD_REALTIME_ERROR: &str = "thread/realtime/error";
1141    pub const THREAD_REALTIME_ITEM_ADDED: &str = "thread/realtime/itemAdded";
1142    pub const THREAD_REALTIME_OUTPUT_AUDIO_DELTA: &str = "thread/realtime/outputAudio/delta";
1143    pub const THREAD_REALTIME_SDP: &str = "thread/realtime/sdp";
1144    pub const THREAD_REALTIME_STARTED: &str = "thread/realtime/started";
1145    pub const THREAD_REALTIME_TRANSCRIPT_DELTA: &str = "thread/realtime/transcript/delta";
1146    pub const THREAD_REALTIME_TRANSCRIPT_DONE: &str = "thread/realtime/transcript/done";
1147    pub const WINDOWS_WORLD_WRITABLE_WARNING: &str = "windows/worldWritableWarning";
1148    pub const WINDOWS_SANDBOX_SETUP_COMPLETED: &str = "windowsSandbox/setupCompleted";
1149
1150    // Server → client requests (approval)
1151    pub const CMD_EXEC_APPROVAL: &str = "item/commandExecution/requestApproval";
1152    pub const FILE_CHANGE_APPROVAL: &str = "item/fileChange/requestApproval";
1153}
1154
1155#[cfg(test)]
1156mod tests {
1157    use super::*;
1158
1159    #[test]
1160    fn test_initialize_params() {
1161        let params = InitializeParams {
1162            client_info: ClientInfo {
1163                name: "my-app".to_string(),
1164                version: "1.0.0".to_string(),
1165                title: Some("My App".to_string()),
1166            },
1167            capabilities: None,
1168        };
1169        let json = serde_json::to_string(&params).unwrap();
1170        assert!(json.contains("clientInfo"));
1171        assert!(json.contains("my-app"));
1172        assert!(!json.contains("capabilities"));
1173    }
1174
1175    #[test]
1176    fn test_initialize_response() {
1177        let json = r#"{"userAgent":"codex-cli/0.104.0"}"#;
1178        let resp: InitializeResponse = serde_json::from_str(json).unwrap();
1179        assert_eq!(resp.user_agent, "codex-cli/0.104.0");
1180    }
1181
1182    #[test]
1183    fn test_initialize_capabilities() {
1184        let params = InitializeParams {
1185            client_info: ClientInfo {
1186                name: "test".to_string(),
1187                version: "0.1.0".to_string(),
1188                title: None,
1189            },
1190            capabilities: Some(InitializeCapabilities {
1191                experimental_api: true,
1192                opt_out_notification_methods: Some(vec!["thread/started".to_string()]),
1193            }),
1194        };
1195        let json = serde_json::to_string(&params).unwrap();
1196        assert!(json.contains("experimentalApi"));
1197        assert!(json.contains("optOutNotificationMethods"));
1198    }
1199
1200    #[test]
1201    fn test_user_input_text() {
1202        let input = UserInput::Text {
1203            text: "Hello".to_string(),
1204        };
1205        let json = serde_json::to_string(&input).unwrap();
1206        assert!(json.contains(r#""type":"text""#));
1207        let parsed: UserInput = serde_json::from_str(&json).unwrap();
1208        assert!(matches!(parsed, UserInput::Text { text } if text == "Hello"));
1209    }
1210
1211    #[test]
1212    fn test_thread_start_params() {
1213        let params = ThreadStartParams {
1214            instructions: Some("Be helpful".to_string()),
1215            tools: None,
1216        };
1217        let json = serde_json::to_string(&params).unwrap();
1218        assert!(json.contains("instructions"));
1219        assert!(!json.contains("tools"));
1220    }
1221
1222    #[test]
1223    fn test_thread_start_response() {
1224        let json = r#"{"thread":{"id":"th_abc123"},"model":"gpt-4","approvalPolicy":"never","cwd":"/tmp","modelProvider":"openai","sandbox":{}}"#;
1225        let resp: ThreadStartResponse = serde_json::from_str(json).unwrap();
1226        assert_eq!(resp.thread_id(), "th_abc123");
1227        assert_eq!(resp.model.as_deref(), Some("gpt-4"));
1228    }
1229
1230    #[test]
1231    fn test_turn_start_params() {
1232        let params = TurnStartParams {
1233            thread_id: "th_1".to_string(),
1234            input: vec![UserInput::Text {
1235                text: "What is 2+2?".to_string(),
1236            }],
1237            model: None,
1238            reasoning_effort: None,
1239            sandbox_policy: None,
1240        };
1241        let json = serde_json::to_string(&params).unwrap();
1242        assert!(json.contains("threadId"));
1243        assert!(json.contains("input"));
1244    }
1245
1246    #[test]
1247    fn test_turn_status() {
1248        let json = r#""completed""#;
1249        let status: TurnStatus = serde_json::from_str(json).unwrap();
1250        assert_eq!(status, TurnStatus::Completed);
1251    }
1252
1253    #[test]
1254    fn test_turn_completed_notification() {
1255        let json = r#"{
1256            "threadId": "th_1",
1257            "turnId": "t_1",
1258            "turn": {
1259                "id": "t_1",
1260                "items": [],
1261                "status": "completed"
1262            }
1263        }"#;
1264        let notif: TurnCompletedNotification = serde_json::from_str(json).unwrap();
1265        assert_eq!(notif.thread_id, "th_1");
1266        assert_eq!(notif.turn.status, TurnStatus::Completed);
1267    }
1268
1269    #[test]
1270    fn test_agent_message_delta() {
1271        let json = r#"{"threadId":"th_1","itemId":"msg_1","delta":"Hello "}"#;
1272        let notif: AgentMessageDeltaNotification = serde_json::from_str(json).unwrap();
1273        assert_eq!(notif.delta, "Hello ");
1274    }
1275
1276    #[test]
1277    fn test_command_approval_decision() {
1278        let json = r#""accept""#;
1279        let decision: CommandApprovalDecision = serde_json::from_str(json).unwrap();
1280        assert_eq!(decision, CommandApprovalDecision::Accept);
1281
1282        let json = r#""acceptForSession""#;
1283        let decision: CommandApprovalDecision = serde_json::from_str(json).unwrap();
1284        assert_eq!(decision, CommandApprovalDecision::AcceptForSession);
1285    }
1286
1287    #[test]
1288    fn test_command_approval_params() {
1289        let json = r#"{
1290            "threadId": "th_1",
1291            "turnId": "t_1",
1292            "callId": "call_1",
1293            "command": "rm -rf /tmp/test",
1294            "cwd": "/home/user"
1295        }"#;
1296        let params: CommandExecutionApprovalParams = serde_json::from_str(json).unwrap();
1297        assert_eq!(params.command, "rm -rf /tmp/test");
1298    }
1299
1300    #[test]
1301    fn test_error_notification() {
1302        let json = r#"{"error":"something failed","willRetry":true}"#;
1303        let notif: ErrorNotification = serde_json::from_str(json).unwrap();
1304        assert_eq!(notif.error, "something failed");
1305        assert!(notif.will_retry);
1306    }
1307
1308    #[test]
1309    fn test_thread_status_idle() {
1310        let json = r#"{"type":"idle"}"#;
1311        let status: ThreadStatus = serde_json::from_str(json).unwrap();
1312        assert!(matches!(status, ThreadStatus::Idle));
1313    }
1314
1315    #[test]
1316    fn test_thread_status_active_with_flags() {
1317        let json = r#"{"type":"active","activeFlags":[]}"#;
1318        let status: ThreadStatus = serde_json::from_str(json).unwrap();
1319        match status {
1320            ThreadStatus::Active { active_flags } => assert!(active_flags.is_empty()),
1321            other => panic!("expected Active, got {:?}", other),
1322        }
1323    }
1324
1325    #[test]
1326    fn test_token_usage() {
1327        let json = r#"{
1328            "last":{"inputTokens":100,"outputTokens":200,"cachedInputTokens":50,"reasoningOutputTokens":0,"totalTokens":300},
1329            "total":{"inputTokens":1000,"outputTokens":2000,"cachedInputTokens":500,"reasoningOutputTokens":10,"totalTokens":3000},
1330            "modelContextWindow":200000
1331        }"#;
1332        let usage: TokenUsage = serde_json::from_str(json).unwrap();
1333        assert_eq!(usage.last.input_tokens, 100);
1334        assert_eq!(usage.last.output_tokens, 200);
1335        assert_eq!(usage.last.cached_input_tokens, 50);
1336        assert_eq!(usage.total.input_tokens, 1000);
1337        assert_eq!(usage.model_context_window, 200000);
1338    }
1339}