Skip to main content

codex_codes/
messages.rs

1//! Typed dispatch for app-server notifications and server-to-client requests.
2//!
3//! The Codex app-server speaks JSON-RPC where every message carries a
4//! `method` discriminant alongside a free-form `params` blob. This module
5//! lifts that loose envelope into closed enums — [`Notification`] for
6//! server-initiated notifications and [`ServerRequest`] for server-initiated
7//! requests (the approval flow). Each variant wraps a typed param struct
8//! from [`crate::protocol`].
9//!
10//! The pattern mirrors the [`ContentBlock`] dispatch in the sibling
11//! `claude-codes` crate: hand-written [`Serialize`]/[`Deserialize`] impls
12//! inspect the discriminant, route known cases through `serde_json::from_value`
13//! into the typed struct, and route unknown methods into an `Unknown`
14//! variant — preserving the raw payload for forward compatibility with
15//! future codex versions.
16//!
17//! ## Typing contract
18//!
19//! - Unknown methods route to [`Notification::Unknown`] / [`ServerRequest::Unknown`]
20//!   without error. Encountering one in production typically means the
21//!   installed Codex CLI is newer than the bindings.
22//! - Known methods whose payload fails to deserialize **do** cause an error.
23//!   If you see one, the typed binding in [`crate::protocol`] is out of
24//!   sync with the wire format and needs to be updated.
25
26use crate::jsonrpc::RequestId;
27use crate::protocol::{
28    methods, AccountLoginCompletedNotification, AccountRateLimitsUpdatedNotification,
29    AccountUpdatedNotification, AgentMessageDeltaNotification, AppListUpdatedNotification,
30    CmdOutputDeltaNotification, CommandExecOutputDeltaNotification, CommandExecutionApprovalParams,
31    ConfigWarningNotification, ContextCompactedNotification, DeprecationNoticeNotification,
32    ErrorNotification, ExternalAgentConfigImportCompletedNotification, FileChangeApprovalParams,
33    FileChangeOutputDeltaNotification, FileChangePatchUpdatedNotification, FsChangedNotification,
34    FuzzyFileSearchSessionCompletedNotification, FuzzyFileSearchSessionUpdatedNotification,
35    GuardianWarningNotification, HookCompletedNotification, HookStartedNotification,
36    ItemCompletedNotification, ItemGuardianApprovalReviewCompletedNotification,
37    ItemGuardianApprovalReviewStartedNotification, ItemStartedNotification,
38    McpServerOauthLoginCompletedNotification, McpServerStartupStatusUpdatedNotification,
39    McpToolCallProgressNotification, ModelReroutedNotification, ModelVerificationNotification,
40    PlanDeltaNotification, ProcessExitedNotification, ProcessOutputDeltaNotification,
41    ReasoningDeltaNotification, ReasoningSummaryPartAddedNotification,
42    ReasoningTextDeltaNotification, RemoteControlStatusChangedNotification,
43    ServerRequestResolvedNotification, SkillsChangedNotification, TerminalInteractionNotification,
44    ThreadArchivedNotification, ThreadClosedNotification, ThreadGoalClearedNotification,
45    ThreadGoalUpdatedNotification, ThreadNameUpdatedNotification, ThreadRealtimeClosedNotification,
46    ThreadRealtimeErrorNotification, ThreadRealtimeItemAddedNotification,
47    ThreadRealtimeOutputAudioDeltaNotification, ThreadRealtimeSdpNotification,
48    ThreadRealtimeStartedNotification, ThreadRealtimeTranscriptDeltaNotification,
49    ThreadRealtimeTranscriptDoneNotification, ThreadStartedNotification,
50    ThreadStatusChangedNotification, ThreadTokenUsageUpdatedNotification,
51    ThreadUnarchivedNotification, TurnCompletedNotification, TurnDiffUpdatedNotification,
52    TurnPlanUpdatedNotification, TurnStartedNotification, WarningNotification,
53    WindowsSandboxSetupCompletedNotification, WindowsWorldWritableWarningNotification,
54};
55use serde::{Deserialize, Deserializer, Serialize, Serializer};
56use serde_json::Value;
57
58/// A server-to-client notification.
59///
60/// Each variant maps to a single `method` string on the wire. The `Unknown`
61/// variant captures methods this crate version doesn't model yet, preserving
62/// the raw payload for inspection.
63#[derive(Debug, Clone)]
64pub enum Notification {
65    /// `thread/started`
66    ThreadStarted(ThreadStartedNotification),
67    /// `thread/status/changed`
68    ThreadStatusChanged(ThreadStatusChangedNotification),
69    /// `thread/tokenUsage/updated`
70    ThreadTokenUsageUpdated(ThreadTokenUsageUpdatedNotification),
71    /// `turn/started`
72    TurnStarted(TurnStartedNotification),
73    /// `turn/completed`
74    TurnCompleted(TurnCompletedNotification),
75    /// `item/started`
76    ItemStarted(ItemStartedNotification),
77    /// `item/completed`
78    ItemCompleted(ItemCompletedNotification),
79    /// `item/agentMessage/delta`
80    AgentMessageDelta(AgentMessageDeltaNotification),
81    /// `item/commandExecution/outputDelta`
82    CmdOutputDelta(CmdOutputDeltaNotification),
83    /// `item/fileChange/outputDelta`
84    FileChangeOutputDelta(FileChangeOutputDeltaNotification),
85    /// `item/reasoning/summaryTextDelta`
86    ReasoningDelta(ReasoningDeltaNotification),
87    /// `error`
88    Error(ErrorNotification),
89    /// `account/rateLimits/updated`
90    AccountRateLimitsUpdated(AccountRateLimitsUpdatedNotification),
91    /// `mcpServer/startupStatus/updated`
92    McpServerStartupStatusUpdated(McpServerStartupStatusUpdatedNotification),
93    /// `remoteControl/status/changed`
94    RemoteControlStatusChanged(RemoteControlStatusChangedNotification),
95    /// `mcpServer/oauthLogin/completed`
96    McpServerOauthLoginCompleted(McpServerOauthLoginCompletedNotification),
97    /// `item/fileChange/patchUpdated`
98    FileChangePatchUpdated(FileChangePatchUpdatedNotification),
99    /// `item/plan/delta` (EXPERIMENTAL)
100    PlanDelta(PlanDeltaNotification),
101    /// `turn/plan/updated`
102    TurnPlanUpdated(TurnPlanUpdatedNotification),
103    /// `turn/diff/updated`
104    TurnDiffUpdated(TurnDiffUpdatedNotification),
105    /// `item/reasoning/summaryPartAdded`
106    ReasoningSummaryPartAdded(ReasoningSummaryPartAddedNotification),
107    /// `item/reasoning/textDelta`
108    ReasoningTextDelta(ReasoningTextDeltaNotification),
109    /// `account/login/completed`
110    AccountLoginCompleted(AccountLoginCompletedNotification),
111    /// `deprecationNotice`
112    DeprecationNotice(DeprecationNoticeNotification),
113    /// `guardianWarning`
114    GuardianWarning(GuardianWarningNotification),
115    /// `warning`
116    Warning(WarningNotification),
117    /// `thread/archived`
118    ThreadArchived(ThreadArchivedNotification),
119    /// `thread/closed`
120    ThreadClosed(ThreadClosedNotification),
121    /// `thread/unarchived`
122    ThreadUnarchived(ThreadUnarchivedNotification),
123    /// `thread/goal/cleared`
124    ThreadGoalCleared(ThreadGoalClearedNotification),
125    /// `thread/name/updated`
126    ThreadNameUpdated(ThreadNameUpdatedNotification),
127    /// `skills/changed`
128    SkillsChanged(SkillsChangedNotification),
129    /// `fs/changed`
130    FsChanged(FsChangedNotification),
131    /// `configWarning`
132    ConfigWarning(ConfigWarningNotification),
133    /// `account/updated`
134    AccountUpdated(AccountUpdatedNotification),
135    /// `app/list/updated`
136    AppListUpdated(AppListUpdatedNotification),
137    /// `command/exec/outputDelta`
138    CommandExecOutputDelta(CommandExecOutputDeltaNotification),
139    /// `externalAgentConfig/import/completed`
140    ExternalAgentConfigImportCompleted(ExternalAgentConfigImportCompletedNotification),
141    /// `fuzzyFileSearch/sessionCompleted`
142    FuzzyFileSearchSessionCompleted(FuzzyFileSearchSessionCompletedNotification),
143    /// `fuzzyFileSearch/sessionUpdated`
144    FuzzyFileSearchSessionUpdated(FuzzyFileSearchSessionUpdatedNotification),
145    /// `hook/completed`
146    HookCompleted(HookCompletedNotification),
147    /// `hook/started`
148    HookStarted(HookStartedNotification),
149    /// `item/autoApprovalReview/completed`
150    ItemGuardianApprovalReviewCompleted(ItemGuardianApprovalReviewCompletedNotification),
151    /// `item/autoApprovalReview/started`
152    ItemGuardianApprovalReviewStarted(ItemGuardianApprovalReviewStartedNotification),
153    /// `item/commandExecution/terminalInteraction`
154    TerminalInteraction(TerminalInteractionNotification),
155    /// `item/mcpToolCall/progress`
156    McpToolCallProgress(McpToolCallProgressNotification),
157    /// `model/rerouted`
158    ModelRerouted(ModelReroutedNotification),
159    /// `model/verification`
160    ModelVerification(ModelVerificationNotification),
161    /// `process/exited`
162    ProcessExited(ProcessExitedNotification),
163    /// `process/outputDelta`
164    ProcessOutputDelta(ProcessOutputDeltaNotification),
165    /// `serverRequest/resolved`
166    ServerRequestResolved(ServerRequestResolvedNotification),
167    /// `thread/compacted`
168    ContextCompacted(ContextCompactedNotification),
169    /// `thread/goal/updated`
170    ThreadGoalUpdated(ThreadGoalUpdatedNotification),
171    /// `thread/realtime/closed`
172    ThreadRealtimeClosed(ThreadRealtimeClosedNotification),
173    /// `thread/realtime/error`
174    ThreadRealtimeError(ThreadRealtimeErrorNotification),
175    /// `thread/realtime/itemAdded`
176    ThreadRealtimeItemAdded(ThreadRealtimeItemAddedNotification),
177    /// `thread/realtime/outputAudio/delta`
178    ThreadRealtimeOutputAudioDelta(ThreadRealtimeOutputAudioDeltaNotification),
179    /// `thread/realtime/sdp`
180    ThreadRealtimeSdp(ThreadRealtimeSdpNotification),
181    /// `thread/realtime/started`
182    ThreadRealtimeStarted(ThreadRealtimeStartedNotification),
183    /// `thread/realtime/transcript/delta`
184    ThreadRealtimeTranscriptDelta(ThreadRealtimeTranscriptDeltaNotification),
185    /// `thread/realtime/transcript/done`
186    ThreadRealtimeTranscriptDone(ThreadRealtimeTranscriptDoneNotification),
187    /// `windows/worldWritableWarning`
188    WindowsWorldWritableWarning(WindowsWorldWritableWarningNotification),
189    /// `windowsSandbox/setupCompleted`
190    WindowsSandboxSetupCompleted(WindowsSandboxSetupCompletedNotification),
191    /// A method this crate version does not yet model. The raw params are
192    /// preserved for caller inspection. Encountering this typically means
193    /// the installed codex CLI is newer than the bindings.
194    Unknown {
195        method: String,
196        params: Option<Value>,
197    },
198}
199
200impl Notification {
201    /// Return the wire `method` string for this notification.
202    pub fn method(&self) -> &str {
203        match self {
204            Self::ThreadStarted(_) => methods::THREAD_STARTED,
205            Self::ThreadStatusChanged(_) => methods::THREAD_STATUS_CHANGED,
206            Self::ThreadTokenUsageUpdated(_) => methods::THREAD_TOKEN_USAGE_UPDATED,
207            Self::TurnStarted(_) => methods::TURN_STARTED,
208            Self::TurnCompleted(_) => methods::TURN_COMPLETED,
209            Self::ItemStarted(_) => methods::ITEM_STARTED,
210            Self::ItemCompleted(_) => methods::ITEM_COMPLETED,
211            Self::AgentMessageDelta(_) => methods::AGENT_MESSAGE_DELTA,
212            Self::CmdOutputDelta(_) => methods::CMD_OUTPUT_DELTA,
213            Self::FileChangeOutputDelta(_) => methods::FILE_CHANGE_OUTPUT_DELTA,
214            Self::ReasoningDelta(_) => methods::REASONING_DELTA,
215            Self::Error(_) => methods::ERROR,
216            Self::AccountRateLimitsUpdated(_) => methods::ACCOUNT_RATE_LIMITS_UPDATED,
217            Self::McpServerStartupStatusUpdated(_) => methods::MCP_SERVER_STARTUP_STATUS_UPDATED,
218            Self::RemoteControlStatusChanged(_) => methods::REMOTE_CONTROL_STATUS_CHANGED,
219            Self::McpServerOauthLoginCompleted(_) => methods::MCP_SERVER_OAUTH_LOGIN_COMPLETED,
220            Self::FileChangePatchUpdated(_) => methods::FILE_CHANGE_PATCH_UPDATED,
221            Self::PlanDelta(_) => methods::PLAN_DELTA,
222            Self::TurnPlanUpdated(_) => methods::TURN_PLAN_UPDATED,
223            Self::TurnDiffUpdated(_) => methods::TURN_DIFF_UPDATED,
224            Self::ReasoningSummaryPartAdded(_) => methods::REASONING_SUMMARY_PART_ADDED,
225            Self::ReasoningTextDelta(_) => methods::REASONING_TEXT_DELTA,
226            Self::AccountLoginCompleted(_) => methods::ACCOUNT_LOGIN_COMPLETED,
227            Self::DeprecationNotice(_) => methods::DEPRECATION_NOTICE,
228            Self::GuardianWarning(_) => methods::GUARDIAN_WARNING,
229            Self::Warning(_) => methods::WARNING,
230            Self::ThreadArchived(_) => methods::THREAD_ARCHIVED,
231            Self::ThreadClosed(_) => methods::THREAD_CLOSED,
232            Self::ThreadUnarchived(_) => methods::THREAD_UNARCHIVED,
233            Self::ThreadGoalCleared(_) => methods::THREAD_GOAL_CLEARED,
234            Self::ThreadNameUpdated(_) => methods::THREAD_NAME_UPDATED,
235            Self::SkillsChanged(_) => methods::SKILLS_CHANGED,
236            Self::FsChanged(_) => methods::FS_CHANGED,
237            Self::ConfigWarning(_) => methods::CONFIG_WARNING,
238            Self::AccountUpdated(_) => methods::ACCOUNT_UPDATED,
239            Self::AppListUpdated(_) => methods::APP_LIST_UPDATED,
240            Self::CommandExecOutputDelta(_) => methods::COMMAND_EXEC_OUTPUT_DELTA,
241            Self::ExternalAgentConfigImportCompleted(_) => {
242                methods::EXTERNAL_AGENT_CONFIG_IMPORT_COMPLETED
243            }
244            Self::FuzzyFileSearchSessionCompleted(_) => {
245                methods::FUZZY_FILE_SEARCH_SESSION_COMPLETED
246            }
247            Self::FuzzyFileSearchSessionUpdated(_) => methods::FUZZY_FILE_SEARCH_SESSION_UPDATED,
248            Self::HookCompleted(_) => methods::HOOK_COMPLETED,
249            Self::HookStarted(_) => methods::HOOK_STARTED,
250            Self::ItemGuardianApprovalReviewCompleted(_) => {
251                methods::ITEM_AUTO_APPROVAL_REVIEW_COMPLETED
252            }
253            Self::ItemGuardianApprovalReviewStarted(_) => {
254                methods::ITEM_AUTO_APPROVAL_REVIEW_STARTED
255            }
256            Self::TerminalInteraction(_) => methods::ITEM_COMMAND_EXEC_TERMINAL_INTERACTION,
257            Self::McpToolCallProgress(_) => methods::ITEM_MCP_TOOL_CALL_PROGRESS,
258            Self::ModelRerouted(_) => methods::MODEL_REROUTED,
259            Self::ModelVerification(_) => methods::MODEL_VERIFICATION,
260            Self::ProcessExited(_) => methods::PROCESS_EXITED,
261            Self::ProcessOutputDelta(_) => methods::PROCESS_OUTPUT_DELTA,
262            Self::ServerRequestResolved(_) => methods::SERVER_REQUEST_RESOLVED,
263            Self::ContextCompacted(_) => methods::THREAD_COMPACTED,
264            Self::ThreadGoalUpdated(_) => methods::THREAD_GOAL_UPDATED,
265            Self::ThreadRealtimeClosed(_) => methods::THREAD_REALTIME_CLOSED,
266            Self::ThreadRealtimeError(_) => methods::THREAD_REALTIME_ERROR,
267            Self::ThreadRealtimeItemAdded(_) => methods::THREAD_REALTIME_ITEM_ADDED,
268            Self::ThreadRealtimeOutputAudioDelta(_) => methods::THREAD_REALTIME_OUTPUT_AUDIO_DELTA,
269            Self::ThreadRealtimeSdp(_) => methods::THREAD_REALTIME_SDP,
270            Self::ThreadRealtimeStarted(_) => methods::THREAD_REALTIME_STARTED,
271            Self::ThreadRealtimeTranscriptDelta(_) => methods::THREAD_REALTIME_TRANSCRIPT_DELTA,
272            Self::ThreadRealtimeTranscriptDone(_) => methods::THREAD_REALTIME_TRANSCRIPT_DONE,
273            Self::WindowsWorldWritableWarning(_) => methods::WINDOWS_WORLD_WRITABLE_WARNING,
274            Self::WindowsSandboxSetupCompleted(_) => methods::WINDOWS_SANDBOX_SETUP_COMPLETED,
275            Self::Unknown { method, .. } => method,
276        }
277    }
278
279    /// `true` if this notification's method isn't modeled by the crate.
280    pub fn is_unknown(&self) -> bool {
281        matches!(self, Self::Unknown { .. })
282    }
283
284    /// Construct a [`Notification`] from a `method` + `params` envelope.
285    ///
286    /// Returns an error if `method` is recognized but `params` doesn't
287    /// deserialize into the typed struct. Unknown methods route to
288    /// [`Notification::Unknown`] without error.
289    pub fn from_envelope(method: &str, params: Option<Value>) -> Result<Self, serde_json::Error> {
290        let params_value = params.clone().unwrap_or(Value::Null);
291        match method {
292            methods::THREAD_STARTED => {
293                serde_json::from_value(params_value).map(Self::ThreadStarted)
294            }
295            methods::THREAD_STATUS_CHANGED => {
296                serde_json::from_value(params_value).map(Self::ThreadStatusChanged)
297            }
298            methods::THREAD_TOKEN_USAGE_UPDATED => {
299                serde_json::from_value(params_value).map(Self::ThreadTokenUsageUpdated)
300            }
301            methods::TURN_STARTED => serde_json::from_value(params_value).map(Self::TurnStarted),
302            methods::TURN_COMPLETED => {
303                serde_json::from_value(params_value).map(Self::TurnCompleted)
304            }
305            methods::ITEM_STARTED => serde_json::from_value(params_value).map(Self::ItemStarted),
306            methods::ITEM_COMPLETED => {
307                serde_json::from_value(params_value).map(Self::ItemCompleted)
308            }
309            methods::AGENT_MESSAGE_DELTA => {
310                serde_json::from_value(params_value).map(Self::AgentMessageDelta)
311            }
312            methods::CMD_OUTPUT_DELTA => {
313                serde_json::from_value(params_value).map(Self::CmdOutputDelta)
314            }
315            methods::FILE_CHANGE_OUTPUT_DELTA => {
316                serde_json::from_value(params_value).map(Self::FileChangeOutputDelta)
317            }
318            methods::REASONING_DELTA => {
319                serde_json::from_value(params_value).map(Self::ReasoningDelta)
320            }
321            methods::ERROR => serde_json::from_value(params_value).map(Self::Error),
322            methods::ACCOUNT_RATE_LIMITS_UPDATED => {
323                serde_json::from_value(params_value).map(Self::AccountRateLimitsUpdated)
324            }
325            methods::MCP_SERVER_STARTUP_STATUS_UPDATED => {
326                serde_json::from_value(params_value).map(Self::McpServerStartupStatusUpdated)
327            }
328            methods::REMOTE_CONTROL_STATUS_CHANGED => {
329                serde_json::from_value(params_value).map(Self::RemoteControlStatusChanged)
330            }
331            methods::MCP_SERVER_OAUTH_LOGIN_COMPLETED => {
332                serde_json::from_value(params_value).map(Self::McpServerOauthLoginCompleted)
333            }
334            methods::FILE_CHANGE_PATCH_UPDATED => {
335                serde_json::from_value(params_value).map(Self::FileChangePatchUpdated)
336            }
337            methods::PLAN_DELTA => serde_json::from_value(params_value).map(Self::PlanDelta),
338            methods::TURN_PLAN_UPDATED => {
339                serde_json::from_value(params_value).map(Self::TurnPlanUpdated)
340            }
341            methods::TURN_DIFF_UPDATED => {
342                serde_json::from_value(params_value).map(Self::TurnDiffUpdated)
343            }
344            methods::REASONING_SUMMARY_PART_ADDED => {
345                serde_json::from_value(params_value).map(Self::ReasoningSummaryPartAdded)
346            }
347            methods::REASONING_TEXT_DELTA => {
348                serde_json::from_value(params_value).map(Self::ReasoningTextDelta)
349            }
350            methods::ACCOUNT_LOGIN_COMPLETED => {
351                serde_json::from_value(params_value).map(Self::AccountLoginCompleted)
352            }
353            methods::DEPRECATION_NOTICE => {
354                serde_json::from_value(params_value).map(Self::DeprecationNotice)
355            }
356            methods::GUARDIAN_WARNING => {
357                serde_json::from_value(params_value).map(Self::GuardianWarning)
358            }
359            methods::WARNING => serde_json::from_value(params_value).map(Self::Warning),
360            methods::THREAD_ARCHIVED => {
361                serde_json::from_value(params_value).map(Self::ThreadArchived)
362            }
363            methods::THREAD_CLOSED => serde_json::from_value(params_value).map(Self::ThreadClosed),
364            methods::THREAD_UNARCHIVED => {
365                serde_json::from_value(params_value).map(Self::ThreadUnarchived)
366            }
367            methods::THREAD_GOAL_CLEARED => {
368                serde_json::from_value(params_value).map(Self::ThreadGoalCleared)
369            }
370            methods::THREAD_NAME_UPDATED => {
371                serde_json::from_value(params_value).map(Self::ThreadNameUpdated)
372            }
373            methods::SKILLS_CHANGED => {
374                serde_json::from_value(params_value).map(Self::SkillsChanged)
375            }
376            methods::FS_CHANGED => serde_json::from_value(params_value).map(Self::FsChanged),
377            methods::CONFIG_WARNING => {
378                serde_json::from_value(params_value).map(Self::ConfigWarning)
379            }
380            methods::ACCOUNT_UPDATED => {
381                serde_json::from_value(params_value).map(Self::AccountUpdated)
382            }
383            methods::APP_LIST_UPDATED => {
384                serde_json::from_value(params_value).map(Self::AppListUpdated)
385            }
386            methods::COMMAND_EXEC_OUTPUT_DELTA => {
387                serde_json::from_value(params_value).map(Self::CommandExecOutputDelta)
388            }
389            methods::EXTERNAL_AGENT_CONFIG_IMPORT_COMPLETED => {
390                serde_json::from_value(params_value).map(Self::ExternalAgentConfigImportCompleted)
391            }
392            methods::FUZZY_FILE_SEARCH_SESSION_COMPLETED => {
393                serde_json::from_value(params_value).map(Self::FuzzyFileSearchSessionCompleted)
394            }
395            methods::FUZZY_FILE_SEARCH_SESSION_UPDATED => {
396                serde_json::from_value(params_value).map(Self::FuzzyFileSearchSessionUpdated)
397            }
398            methods::HOOK_COMPLETED => {
399                serde_json::from_value(params_value).map(Self::HookCompleted)
400            }
401            methods::HOOK_STARTED => serde_json::from_value(params_value).map(Self::HookStarted),
402            methods::ITEM_AUTO_APPROVAL_REVIEW_COMPLETED => {
403                serde_json::from_value(params_value).map(Self::ItemGuardianApprovalReviewCompleted)
404            }
405            methods::ITEM_AUTO_APPROVAL_REVIEW_STARTED => {
406                serde_json::from_value(params_value).map(Self::ItemGuardianApprovalReviewStarted)
407            }
408            methods::ITEM_COMMAND_EXEC_TERMINAL_INTERACTION => {
409                serde_json::from_value(params_value).map(Self::TerminalInteraction)
410            }
411            methods::ITEM_MCP_TOOL_CALL_PROGRESS => {
412                serde_json::from_value(params_value).map(Self::McpToolCallProgress)
413            }
414            methods::MODEL_REROUTED => {
415                serde_json::from_value(params_value).map(Self::ModelRerouted)
416            }
417            methods::MODEL_VERIFICATION => {
418                serde_json::from_value(params_value).map(Self::ModelVerification)
419            }
420            methods::PROCESS_EXITED => {
421                serde_json::from_value(params_value).map(Self::ProcessExited)
422            }
423            methods::PROCESS_OUTPUT_DELTA => {
424                serde_json::from_value(params_value).map(Self::ProcessOutputDelta)
425            }
426            methods::SERVER_REQUEST_RESOLVED => {
427                serde_json::from_value(params_value).map(Self::ServerRequestResolved)
428            }
429            methods::THREAD_COMPACTED => {
430                serde_json::from_value(params_value).map(Self::ContextCompacted)
431            }
432            methods::THREAD_GOAL_UPDATED => {
433                serde_json::from_value(params_value).map(Self::ThreadGoalUpdated)
434            }
435            methods::THREAD_REALTIME_CLOSED => {
436                serde_json::from_value(params_value).map(Self::ThreadRealtimeClosed)
437            }
438            methods::THREAD_REALTIME_ERROR => {
439                serde_json::from_value(params_value).map(Self::ThreadRealtimeError)
440            }
441            methods::THREAD_REALTIME_ITEM_ADDED => {
442                serde_json::from_value(params_value).map(Self::ThreadRealtimeItemAdded)
443            }
444            methods::THREAD_REALTIME_OUTPUT_AUDIO_DELTA => {
445                serde_json::from_value(params_value).map(Self::ThreadRealtimeOutputAudioDelta)
446            }
447            methods::THREAD_REALTIME_SDP => {
448                serde_json::from_value(params_value).map(Self::ThreadRealtimeSdp)
449            }
450            methods::THREAD_REALTIME_STARTED => {
451                serde_json::from_value(params_value).map(Self::ThreadRealtimeStarted)
452            }
453            methods::THREAD_REALTIME_TRANSCRIPT_DELTA => {
454                serde_json::from_value(params_value).map(Self::ThreadRealtimeTranscriptDelta)
455            }
456            methods::THREAD_REALTIME_TRANSCRIPT_DONE => {
457                serde_json::from_value(params_value).map(Self::ThreadRealtimeTranscriptDone)
458            }
459            methods::WINDOWS_WORLD_WRITABLE_WARNING => {
460                serde_json::from_value(params_value).map(Self::WindowsWorldWritableWarning)
461            }
462            methods::WINDOWS_SANDBOX_SETUP_COMPLETED => {
463                serde_json::from_value(params_value).map(Self::WindowsSandboxSetupCompleted)
464            }
465            _ => Ok(Self::Unknown {
466                method: method.to_string(),
467                params,
468            }),
469        }
470    }
471
472    /// Decompose this notification back into a `(method, params)` pair.
473    pub fn into_envelope(self) -> Result<(String, Option<Value>), serde_json::Error> {
474        fn pack<T: Serialize>(
475            method: &str,
476            v: &T,
477        ) -> Result<(String, Option<Value>), serde_json::Error> {
478            Ok((method.to_string(), Some(serde_json::to_value(v)?)))
479        }
480        match &self {
481            Self::ThreadStarted(v) => pack(methods::THREAD_STARTED, v),
482            Self::ThreadStatusChanged(v) => pack(methods::THREAD_STATUS_CHANGED, v),
483            Self::ThreadTokenUsageUpdated(v) => pack(methods::THREAD_TOKEN_USAGE_UPDATED, v),
484            Self::TurnStarted(v) => pack(methods::TURN_STARTED, v),
485            Self::TurnCompleted(v) => pack(methods::TURN_COMPLETED, v),
486            Self::ItemStarted(v) => pack(methods::ITEM_STARTED, v),
487            Self::ItemCompleted(v) => pack(methods::ITEM_COMPLETED, v),
488            Self::AgentMessageDelta(v) => pack(methods::AGENT_MESSAGE_DELTA, v),
489            Self::CmdOutputDelta(v) => pack(methods::CMD_OUTPUT_DELTA, v),
490            Self::FileChangeOutputDelta(v) => pack(methods::FILE_CHANGE_OUTPUT_DELTA, v),
491            Self::ReasoningDelta(v) => pack(methods::REASONING_DELTA, v),
492            Self::Error(v) => pack(methods::ERROR, v),
493            Self::AccountRateLimitsUpdated(v) => pack(methods::ACCOUNT_RATE_LIMITS_UPDATED, v),
494            Self::McpServerStartupStatusUpdated(v) => {
495                pack(methods::MCP_SERVER_STARTUP_STATUS_UPDATED, v)
496            }
497            Self::RemoteControlStatusChanged(v) => pack(methods::REMOTE_CONTROL_STATUS_CHANGED, v),
498            Self::McpServerOauthLoginCompleted(v) => {
499                pack(methods::MCP_SERVER_OAUTH_LOGIN_COMPLETED, v)
500            }
501            Self::FileChangePatchUpdated(v) => pack(methods::FILE_CHANGE_PATCH_UPDATED, v),
502            Self::PlanDelta(v) => pack(methods::PLAN_DELTA, v),
503            Self::TurnPlanUpdated(v) => pack(methods::TURN_PLAN_UPDATED, v),
504            Self::TurnDiffUpdated(v) => pack(methods::TURN_DIFF_UPDATED, v),
505            Self::ReasoningSummaryPartAdded(v) => pack(methods::REASONING_SUMMARY_PART_ADDED, v),
506            Self::ReasoningTextDelta(v) => pack(methods::REASONING_TEXT_DELTA, v),
507            Self::AccountLoginCompleted(v) => pack(methods::ACCOUNT_LOGIN_COMPLETED, v),
508            Self::DeprecationNotice(v) => pack(methods::DEPRECATION_NOTICE, v),
509            Self::GuardianWarning(v) => pack(methods::GUARDIAN_WARNING, v),
510            Self::Warning(v) => pack(methods::WARNING, v),
511            Self::ThreadArchived(v) => pack(methods::THREAD_ARCHIVED, v),
512            Self::ThreadClosed(v) => pack(methods::THREAD_CLOSED, v),
513            Self::ThreadUnarchived(v) => pack(methods::THREAD_UNARCHIVED, v),
514            Self::ThreadGoalCleared(v) => pack(methods::THREAD_GOAL_CLEARED, v),
515            Self::ThreadNameUpdated(v) => pack(methods::THREAD_NAME_UPDATED, v),
516            Self::SkillsChanged(v) => pack(methods::SKILLS_CHANGED, v),
517            Self::FsChanged(v) => pack(methods::FS_CHANGED, v),
518            Self::ConfigWarning(v) => pack(methods::CONFIG_WARNING, v),
519            Self::AccountUpdated(v) => pack(methods::ACCOUNT_UPDATED, v),
520            Self::AppListUpdated(v) => pack(methods::APP_LIST_UPDATED, v),
521            Self::CommandExecOutputDelta(v) => pack(methods::COMMAND_EXEC_OUTPUT_DELTA, v),
522            Self::ExternalAgentConfigImportCompleted(v) => {
523                pack(methods::EXTERNAL_AGENT_CONFIG_IMPORT_COMPLETED, v)
524            }
525            Self::FuzzyFileSearchSessionCompleted(v) => {
526                pack(methods::FUZZY_FILE_SEARCH_SESSION_COMPLETED, v)
527            }
528            Self::FuzzyFileSearchSessionUpdated(v) => {
529                pack(methods::FUZZY_FILE_SEARCH_SESSION_UPDATED, v)
530            }
531            Self::HookCompleted(v) => pack(methods::HOOK_COMPLETED, v),
532            Self::HookStarted(v) => pack(methods::HOOK_STARTED, v),
533            Self::ItemGuardianApprovalReviewCompleted(v) => {
534                pack(methods::ITEM_AUTO_APPROVAL_REVIEW_COMPLETED, v)
535            }
536            Self::ItemGuardianApprovalReviewStarted(v) => {
537                pack(methods::ITEM_AUTO_APPROVAL_REVIEW_STARTED, v)
538            }
539            Self::TerminalInteraction(v) => {
540                pack(methods::ITEM_COMMAND_EXEC_TERMINAL_INTERACTION, v)
541            }
542            Self::McpToolCallProgress(v) => pack(methods::ITEM_MCP_TOOL_CALL_PROGRESS, v),
543            Self::ModelRerouted(v) => pack(methods::MODEL_REROUTED, v),
544            Self::ModelVerification(v) => pack(methods::MODEL_VERIFICATION, v),
545            Self::ProcessExited(v) => pack(methods::PROCESS_EXITED, v),
546            Self::ProcessOutputDelta(v) => pack(methods::PROCESS_OUTPUT_DELTA, v),
547            Self::ServerRequestResolved(v) => pack(methods::SERVER_REQUEST_RESOLVED, v),
548            Self::ContextCompacted(v) => pack(methods::THREAD_COMPACTED, v),
549            Self::ThreadGoalUpdated(v) => pack(methods::THREAD_GOAL_UPDATED, v),
550            Self::ThreadRealtimeClosed(v) => pack(methods::THREAD_REALTIME_CLOSED, v),
551            Self::ThreadRealtimeError(v) => pack(methods::THREAD_REALTIME_ERROR, v),
552            Self::ThreadRealtimeItemAdded(v) => pack(methods::THREAD_REALTIME_ITEM_ADDED, v),
553            Self::ThreadRealtimeOutputAudioDelta(v) => {
554                pack(methods::THREAD_REALTIME_OUTPUT_AUDIO_DELTA, v)
555            }
556            Self::ThreadRealtimeSdp(v) => pack(methods::THREAD_REALTIME_SDP, v),
557            Self::ThreadRealtimeStarted(v) => pack(methods::THREAD_REALTIME_STARTED, v),
558            Self::ThreadRealtimeTranscriptDelta(v) => {
559                pack(methods::THREAD_REALTIME_TRANSCRIPT_DELTA, v)
560            }
561            Self::ThreadRealtimeTranscriptDone(v) => {
562                pack(methods::THREAD_REALTIME_TRANSCRIPT_DONE, v)
563            }
564            Self::WindowsWorldWritableWarning(v) => {
565                pack(methods::WINDOWS_WORLD_WRITABLE_WARNING, v)
566            }
567            Self::WindowsSandboxSetupCompleted(v) => {
568                pack(methods::WINDOWS_SANDBOX_SETUP_COMPLETED, v)
569            }
570            Self::Unknown { method, params } => Ok((method.clone(), params.clone())),
571        }
572    }
573}
574
575impl Serialize for Notification {
576    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
577        let (method, params) = self
578            .clone()
579            .into_envelope()
580            .map_err(serde::ser::Error::custom)?;
581        let mut env = serde_json::Map::new();
582        env.insert("method".to_string(), Value::String(method));
583        if let Some(p) = params {
584            env.insert("params".to_string(), p);
585        }
586        Value::Object(env).serialize(serializer)
587    }
588}
589
590impl<'de> Deserialize<'de> for Notification {
591    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
592        let value = Value::deserialize(deserializer)?;
593        let method = value
594            .get("method")
595            .and_then(|v| v.as_str())
596            .ok_or_else(|| serde::de::Error::missing_field("method"))?
597            .to_string();
598        let params = value.get("params").cloned();
599        Self::from_envelope(&method, params).map_err(serde::de::Error::custom)
600    }
601}
602
603/// A server-to-client request that requires a response (approval flow).
604///
605/// The wire envelope carries an `id` for response correlation; that `id` is
606/// held alongside this enum in [`ServerMessage::Request`] rather than embedded
607/// inside the variant, since responding doesn't depend on which approval-type
608/// was requested.
609#[derive(Debug, Clone)]
610pub enum ServerRequest {
611    /// `item/commandExecution/requestApproval`
612    CmdExecApproval(CommandExecutionApprovalParams),
613    /// `item/fileChange/requestApproval`
614    FileChangeApproval(FileChangeApprovalParams),
615    /// `item/tool/requestUserInput`
616    ToolRequestUserInput(crate::protocol::ToolRequestUserInputParams),
617    /// `mcpServer/elicitation/request`
618    McpServerElicitationRequest(crate::protocol::McpServerElicitationRequestParams),
619    /// `item/permissions/requestApproval`
620    PermissionsRequestApproval(crate::protocol::PermissionsRequestApprovalParams),
621    /// `item/tool/call`
622    ItemToolCall(crate::protocol::DynamicToolCallParams),
623    /// `account/chatgptAuthTokens/refresh`
624    ChatgptAuthTokensRefresh(crate::protocol::ChatgptAuthTokensRefreshParams),
625    /// `attestation/generate`
626    AttestationGenerate(crate::protocol::AttestationGenerateParams),
627    /// `applyPatchApproval`
628    ApplyPatchApproval(crate::protocol::ApplyPatchApprovalParams),
629    /// `execCommandApproval`
630    ExecCommandApproval(crate::protocol::ExecCommandApprovalParams),
631    /// A request method this crate version does not yet model.
632    Unknown {
633        method: String,
634        params: Option<Value>,
635    },
636}
637
638impl ServerRequest {
639    /// Return the wire `method` string for this request.
640    pub fn method(&self) -> &str {
641        match self {
642            Self::CmdExecApproval(_) => methods::CMD_EXEC_APPROVAL,
643            Self::FileChangeApproval(_) => methods::FILE_CHANGE_APPROVAL,
644            Self::ToolRequestUserInput(_) => methods::TOOL_REQUEST_USER_INPUT,
645            Self::McpServerElicitationRequest(_) => methods::MCP_SERVER_ELICITATION_REQUEST,
646            Self::PermissionsRequestApproval(_) => methods::PERMISSIONS_REQUEST_APPROVAL,
647            Self::ItemToolCall(_) => methods::ITEM_TOOL_CALL,
648            Self::ChatgptAuthTokensRefresh(_) => methods::CHATGPT_AUTH_TOKENS_REFRESH,
649            Self::AttestationGenerate(_) => methods::ATTESTATION_GENERATE,
650            Self::ApplyPatchApproval(_) => methods::APPLY_PATCH_APPROVAL,
651            Self::ExecCommandApproval(_) => methods::EXEC_COMMAND_APPROVAL,
652            Self::Unknown { method, .. } => method,
653        }
654    }
655
656    /// `true` if this request's method isn't modeled by the crate.
657    pub fn is_unknown(&self) -> bool {
658        matches!(self, Self::Unknown { .. })
659    }
660
661    /// Construct a [`ServerRequest`] from a `method` + `params` envelope.
662    pub fn from_envelope(method: &str, params: Option<Value>) -> Result<Self, serde_json::Error> {
663        let params_value = params.clone().unwrap_or(Value::Null);
664        match method {
665            methods::CMD_EXEC_APPROVAL => {
666                serde_json::from_value(params_value).map(Self::CmdExecApproval)
667            }
668            methods::FILE_CHANGE_APPROVAL => {
669                serde_json::from_value(params_value).map(Self::FileChangeApproval)
670            }
671            methods::TOOL_REQUEST_USER_INPUT => {
672                serde_json::from_value(params_value).map(Self::ToolRequestUserInput)
673            }
674            methods::MCP_SERVER_ELICITATION_REQUEST => {
675                serde_json::from_value(params_value).map(Self::McpServerElicitationRequest)
676            }
677            methods::PERMISSIONS_REQUEST_APPROVAL => {
678                serde_json::from_value(params_value).map(Self::PermissionsRequestApproval)
679            }
680            methods::ITEM_TOOL_CALL => serde_json::from_value(params_value).map(Self::ItemToolCall),
681            methods::CHATGPT_AUTH_TOKENS_REFRESH => {
682                serde_json::from_value(params_value).map(Self::ChatgptAuthTokensRefresh)
683            }
684            methods::ATTESTATION_GENERATE => {
685                serde_json::from_value(params_value).map(Self::AttestationGenerate)
686            }
687            methods::APPLY_PATCH_APPROVAL => {
688                serde_json::from_value(params_value).map(Self::ApplyPatchApproval)
689            }
690            methods::EXEC_COMMAND_APPROVAL => {
691                serde_json::from_value(params_value).map(Self::ExecCommandApproval)
692            }
693            _ => Ok(Self::Unknown {
694                method: method.to_string(),
695                params,
696            }),
697        }
698    }
699}
700
701/// A message coming from the app-server.
702///
703/// Replaces the previous loose `{ method, params }` shape with typed enums.
704/// Match on the outer variant first to distinguish notifications (no response)
705/// from requests (need [`crate::AsyncClient::respond`] /
706/// [`crate::SyncClient::respond`]).
707#[derive(Debug, Clone)]
708pub enum ServerMessage {
709    /// A notification — no response required.
710    Notification(Notification),
711    /// A request — call `respond(id, ...)` on the client with the matching id.
712    Request {
713        id: RequestId,
714        request: ServerRequest,
715    },
716}
717
718impl ServerMessage {
719    /// `true` if this message is an unmodeled method (notification or request).
720    pub fn is_unknown(&self) -> bool {
721        match self {
722            Self::Notification(n) => n.is_unknown(),
723            Self::Request { request, .. } => request.is_unknown(),
724        }
725    }
726}
727
728#[cfg(test)]
729mod tests {
730    use super::*;
731
732    #[test]
733    fn test_notification_unknown_method_routes_to_unknown_variant() {
734        let n = Notification::from_envelope("foo/bar", Some(serde_json::json!({"x": 1})))
735            .expect("unknown methods do not error");
736        match n {
737            Notification::Unknown { method, params } => {
738                assert_eq!(method, "foo/bar");
739                assert_eq!(params, Some(serde_json::json!({"x": 1})));
740            }
741            other => panic!("expected Unknown, got {:?}", other),
742        }
743    }
744
745    #[test]
746    fn test_notification_known_method_with_bad_params_errors() {
747        // thread/started expects a `thread` field — wrong shape should error.
748        let err = Notification::from_envelope("thread/started", Some(serde_json::json!({})));
749        assert!(err.is_err());
750    }
751
752    #[test]
753    fn test_notification_round_trip_envelope() {
754        let wire = serde_json::json!({
755            "method": "item/agentMessage/delta",
756            "params": {"threadId": "t1", "itemId": "i1", "delta": "hi"},
757        });
758        let n: Notification = serde_json::from_value(wire.clone()).unwrap();
759        assert!(matches!(n, Notification::AgentMessageDelta(_)));
760        let back = serde_json::to_value(&n).unwrap();
761        assert_eq!(back, wire);
762    }
763}