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