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::error::{Error, ParseError};
27use crate::jsonrpc::{JsonRpcMessage, JsonRpcNotification, JsonRpcRequest, RequestId};
28use crate::protocol::{
29    methods, AccountLoginCompletedNotification, AccountRateLimitsUpdatedNotification,
30    AccountUpdatedNotification, AgentMessageDeltaNotification, AppListUpdatedNotification,
31    CommandExecOutputDeltaNotification, CommandExecutionOutputDeltaNotification,
32    CommandExecutionRequestApprovalParams, ConfigWarningNotification, ContextCompactedNotification,
33    DeprecationNoticeNotification, EnvironmentConnectionNotification, ErrorNotification,
34    ExternalAgentConfigImportCompletedNotification, ExternalAgentConfigImportProgressNotification,
35    FileChangeOutputDeltaNotification, FileChangePatchUpdatedNotification,
36    FileChangeRequestApprovalParams, FsChangedNotification,
37    FuzzyFileSearchSessionCompletedNotification, FuzzyFileSearchSessionUpdatedNotification,
38    GuardianWarningNotification, HookCompletedNotification, HookStartedNotification,
39    ItemCompletedNotification, ItemGuardianApprovalReviewCompletedNotification,
40    ItemGuardianApprovalReviewStartedNotification, ItemStartedNotification,
41    McpServerOauthLoginCompletedNotification, McpServerStatusUpdatedNotification,
42    McpToolCallProgressNotification, ModelReroutedNotification,
43    ModelSafetyBufferingUpdatedNotification, ModelVerificationNotification, PlanDeltaNotification,
44    ProcessExitedNotification, ProcessOutputDeltaNotification, ProjectChangedNotification,
45    ReasoningSummaryPartAddedNotification, ReasoningSummaryTextDeltaNotification,
46    ReasoningTextDeltaNotification, RemoteControlStatusChangedNotification,
47    ServerRequestResolvedNotification, SkillsChangedNotification, StrictReviewRequiredNotification,
48    TerminalInteractionNotification, ThreadArchivedNotification, ThreadClosedNotification,
49    ThreadDeletedNotification, ThreadGoalClearedNotification, ThreadGoalUpdatedNotification,
50    ThreadNameUpdatedNotification, ThreadProjectUpdatedNotification,
51    ThreadQueueChangedNotification, ThreadRealtimeClosedNotification,
52    ThreadRealtimeErrorNotification, ThreadRealtimeItemAddedNotification,
53    ThreadRealtimeOutputAudioDeltaNotification, ThreadRealtimeSdpNotification,
54    ThreadRealtimeStartedNotification, ThreadRealtimeTranscriptDeltaNotification,
55    ThreadRealtimeTranscriptDoneNotification, ThreadRevertedNotification,
56    ThreadSettingsUpdatedNotification, ThreadStartedNotification, ThreadStatusChangedNotification,
57    ThreadTokenUsageUpdatedNotification, ThreadUnarchivedNotification, TurnCompletedNotification,
58    TurnDiffUpdatedNotification, TurnModerationMetadataNotification, TurnPlanUpdatedNotification,
59    TurnStartedNotification, WarningNotification, WindowsSandboxSetupCompletedNotification,
60    WindowsWorldWritableWarningNotification,
61};
62use serde::{Deserialize, Deserializer, Serialize, Serializer};
63use serde_json::Value;
64
65/// A server-to-client notification.
66///
67/// Each variant maps to a single `method` string on the wire. The `Unknown`
68/// variant captures methods this crate version doesn't model yet, preserving
69/// the raw payload for inspection.
70#[derive(Debug, Clone)]
71pub enum Notification {
72    /// `thread/started`
73    ThreadStarted(ThreadStartedNotification),
74    /// `thread/status/changed`
75    ThreadStatusChanged(ThreadStatusChangedNotification),
76    /// `thread/tokenUsage/updated`
77    ThreadTokenUsageUpdated(ThreadTokenUsageUpdatedNotification),
78    /// `turn/started`
79    TurnStarted(TurnStartedNotification),
80    /// `turn/completed`
81    TurnCompleted(TurnCompletedNotification),
82    /// `item/started`
83    ItemStarted(ItemStartedNotification),
84    /// `item/completed`
85    ItemCompleted(ItemCompletedNotification),
86    /// `item/agentMessage/delta`
87    AgentMessageDelta(AgentMessageDeltaNotification),
88    /// `item/commandExecution/outputDelta`
89    CmdOutputDelta(CommandExecutionOutputDeltaNotification),
90    /// `item/fileChange/outputDelta`
91    FileChangeOutputDelta(FileChangeOutputDeltaNotification),
92    /// `item/reasoning/summaryTextDelta`
93    ReasoningDelta(ReasoningSummaryTextDeltaNotification),
94    /// `error`
95    Error(ErrorNotification),
96    /// `account/rateLimits/updated`
97    AccountRateLimitsUpdated(AccountRateLimitsUpdatedNotification),
98    /// `mcpServer/startupStatus/updated`
99    McpServerStartupStatusUpdated(McpServerStatusUpdatedNotification),
100    /// `remoteControl/status/changed`
101    RemoteControlStatusChanged(RemoteControlStatusChangedNotification),
102    /// `mcpServer/oauthLogin/completed`
103    McpServerOauthLoginCompleted(McpServerOauthLoginCompletedNotification),
104    /// `item/fileChange/patchUpdated`
105    FileChangePatchUpdated(FileChangePatchUpdatedNotification),
106    /// `item/plan/delta` (EXPERIMENTAL)
107    PlanDelta(PlanDeltaNotification),
108    /// `turn/plan/updated`
109    TurnPlanUpdated(TurnPlanUpdatedNotification),
110    /// `turn/diff/updated`
111    TurnDiffUpdated(TurnDiffUpdatedNotification),
112    /// `item/reasoning/summaryPartAdded`
113    ReasoningSummaryPartAdded(ReasoningSummaryPartAddedNotification),
114    /// `item/reasoning/textDelta`
115    ReasoningTextDelta(ReasoningTextDeltaNotification),
116    /// `account/login/completed`
117    AccountLoginCompleted(AccountLoginCompletedNotification),
118    /// `deprecationNotice`
119    DeprecationNotice(DeprecationNoticeNotification),
120    /// `guardianWarning`
121    GuardianWarning(GuardianWarningNotification),
122    /// `warning`
123    Warning(WarningNotification),
124    /// `thread/archived`
125    ThreadArchived(ThreadArchivedNotification),
126    /// `thread/closed`
127    ThreadClosed(ThreadClosedNotification),
128    /// `thread/deleted`
129    ThreadDeleted(ThreadDeletedNotification),
130    /// `thread/unarchived`
131    ThreadUnarchived(ThreadUnarchivedNotification),
132    /// `thread/goal/cleared`
133    ThreadGoalCleared(ThreadGoalClearedNotification),
134    /// `thread/name/updated`
135    ThreadNameUpdated(ThreadNameUpdatedNotification),
136    /// `skills/changed`
137    SkillsChanged(SkillsChangedNotification),
138    /// `fs/changed`
139    FsChanged(FsChangedNotification),
140    /// `configWarning`
141    ConfigWarning(ConfigWarningNotification),
142    /// `account/updated`
143    AccountUpdated(AccountUpdatedNotification),
144    /// `app/list/updated`
145    AppListUpdated(AppListUpdatedNotification),
146    /// `command/exec/outputDelta`
147    CommandExecOutputDelta(CommandExecOutputDeltaNotification),
148    /// `externalAgentConfig/import/completed`
149    ExternalAgentConfigImportCompleted(ExternalAgentConfigImportCompletedNotification),
150    /// `fuzzyFileSearch/sessionCompleted`
151    FuzzyFileSearchSessionCompleted(FuzzyFileSearchSessionCompletedNotification),
152    /// `fuzzyFileSearch/sessionUpdated`
153    FuzzyFileSearchSessionUpdated(FuzzyFileSearchSessionUpdatedNotification),
154    /// `hook/completed`
155    HookCompleted(HookCompletedNotification),
156    /// `hook/started`
157    HookStarted(HookStartedNotification),
158    /// `item/autoApprovalReview/completed`
159    ItemGuardianApprovalReviewCompleted(ItemGuardianApprovalReviewCompletedNotification),
160    /// `item/autoApprovalReview/started`
161    ItemGuardianApprovalReviewStarted(ItemGuardianApprovalReviewStartedNotification),
162    /// `item/commandExecution/terminalInteraction`
163    TerminalInteraction(TerminalInteractionNotification),
164    /// `item/mcpToolCall/progress`
165    McpToolCallProgress(McpToolCallProgressNotification),
166    /// `model/rerouted`
167    ModelRerouted(ModelReroutedNotification),
168    /// `model/verification`
169    ModelVerification(ModelVerificationNotification),
170    /// `process/exited`
171    ProcessExited(ProcessExitedNotification),
172    /// `process/outputDelta`
173    ProcessOutputDelta(ProcessOutputDeltaNotification),
174    /// `serverRequest/resolved`
175    ServerRequestResolved(ServerRequestResolvedNotification),
176    /// `thread/compacted`
177    ContextCompacted(ContextCompactedNotification),
178    /// `thread/goal/updated`
179    ThreadGoalUpdated(ThreadGoalUpdatedNotification),
180    /// `thread/realtime/closed`
181    ThreadRealtimeClosed(ThreadRealtimeClosedNotification),
182    /// `thread/realtime/error`
183    ThreadRealtimeError(ThreadRealtimeErrorNotification),
184    /// `thread/realtime/itemAdded`
185    ThreadRealtimeItemAdded(ThreadRealtimeItemAddedNotification),
186    /// `thread/realtime/outputAudio/delta`
187    ThreadRealtimeOutputAudioDelta(ThreadRealtimeOutputAudioDeltaNotification),
188    /// `thread/realtime/sdp`
189    ThreadRealtimeSdp(ThreadRealtimeSdpNotification),
190    /// `thread/realtime/started`
191    ThreadRealtimeStarted(ThreadRealtimeStartedNotification),
192    /// `thread/realtime/transcript/delta`
193    ThreadRealtimeTranscriptDelta(ThreadRealtimeTranscriptDeltaNotification),
194    /// `thread/realtime/transcript/done`
195    ThreadRealtimeTranscriptDone(ThreadRealtimeTranscriptDoneNotification),
196    /// `windows/worldWritableWarning`
197    WindowsWorldWritableWarning(WindowsWorldWritableWarningNotification),
198    /// `windowsSandbox/setupCompleted`
199    WindowsSandboxSetupCompleted(WindowsSandboxSetupCompletedNotification),
200    /// `thread/settings/updated`
201    ThreadSettingsUpdated(ThreadSettingsUpdatedNotification),
202    /// `turn/moderationMetadata`
203    TurnModerationMetadata(TurnModerationMetadataNotification),
204    /// `externalAgentConfig/import/progress`
205    ExternalAgentConfigImportProgress(ExternalAgentConfigImportProgressNotification),
206    /// `model/safetyBuffering/updated`
207    ModelSafetyBufferingUpdated(ModelSafetyBufferingUpdatedNotification),
208    /// `thread/environment/connected`
209    ThreadEnvironmentConnected(EnvironmentConnectionNotification),
210    /// `thread/environment/disconnected`
211    ThreadEnvironmentDisconnected(EnvironmentConnectionNotification),
212    /// `autoApprovalReview/strictReviewRequired` (0.147)
213    StrictReviewRequired(StrictReviewRequiredNotification),
214    /// `project/changed` (0.147)
215    ProjectChanged(ProjectChangedNotification),
216    /// `thread/project/updated` (0.147)
217    ThreadProjectUpdated(ThreadProjectUpdatedNotification),
218    /// `thread/queue/changed` (0.147)
219    ThreadQueueChanged(ThreadQueueChangedNotification),
220    /// `thread/reverted` (0.147)
221    ThreadReverted(ThreadRevertedNotification),
222    /// A method this crate version does not yet model. The raw params are
223    /// preserved for caller inspection. Encountering this typically means
224    /// the installed codex CLI is newer than the bindings.
225    Unknown {
226        method: String,
227        params: Option<Value>,
228    },
229}
230
231impl Notification {
232    /// Return the wire `method` string for this notification.
233    pub fn method(&self) -> &str {
234        match self {
235            Self::ThreadStarted(_) => methods::THREAD_STARTED,
236            Self::StrictReviewRequired(_) => methods::STRICT_REVIEW_REQUIRED,
237            Self::ProjectChanged(_) => methods::PROJECT_CHANGED,
238            Self::ThreadProjectUpdated(_) => methods::THREAD_PROJECT_UPDATED,
239            Self::ThreadQueueChanged(_) => methods::THREAD_QUEUE_CHANGED,
240            Self::ThreadReverted(_) => methods::THREAD_REVERTED,
241            Self::ThreadStatusChanged(_) => methods::THREAD_STATUS_CHANGED,
242            Self::ThreadTokenUsageUpdated(_) => methods::THREAD_TOKEN_USAGE_UPDATED,
243            Self::TurnStarted(_) => methods::TURN_STARTED,
244            Self::TurnCompleted(_) => methods::TURN_COMPLETED,
245            Self::ItemStarted(_) => methods::ITEM_STARTED,
246            Self::ItemCompleted(_) => methods::ITEM_COMPLETED,
247            Self::AgentMessageDelta(_) => methods::AGENT_MESSAGE_DELTA,
248            Self::CmdOutputDelta(_) => methods::CMD_OUTPUT_DELTA,
249            Self::FileChangeOutputDelta(_) => methods::FILE_CHANGE_OUTPUT_DELTA,
250            Self::ReasoningDelta(_) => methods::REASONING_DELTA,
251            Self::Error(_) => methods::ERROR,
252            Self::AccountRateLimitsUpdated(_) => methods::ACCOUNT_RATE_LIMITS_UPDATED,
253            Self::McpServerStartupStatusUpdated(_) => methods::MCP_SERVER_STARTUP_STATUS_UPDATED,
254            Self::RemoteControlStatusChanged(_) => methods::REMOTE_CONTROL_STATUS_CHANGED,
255            Self::McpServerOauthLoginCompleted(_) => methods::MCP_SERVER_OAUTH_LOGIN_COMPLETED,
256            Self::FileChangePatchUpdated(_) => methods::FILE_CHANGE_PATCH_UPDATED,
257            Self::PlanDelta(_) => methods::PLAN_DELTA,
258            Self::TurnPlanUpdated(_) => methods::TURN_PLAN_UPDATED,
259            Self::TurnDiffUpdated(_) => methods::TURN_DIFF_UPDATED,
260            Self::ReasoningSummaryPartAdded(_) => methods::REASONING_SUMMARY_PART_ADDED,
261            Self::ReasoningTextDelta(_) => methods::REASONING_TEXT_DELTA,
262            Self::AccountLoginCompleted(_) => methods::ACCOUNT_LOGIN_COMPLETED,
263            Self::DeprecationNotice(_) => methods::DEPRECATION_NOTICE,
264            Self::GuardianWarning(_) => methods::GUARDIAN_WARNING,
265            Self::Warning(_) => methods::WARNING,
266            Self::ThreadArchived(_) => methods::THREAD_ARCHIVED,
267            Self::ThreadClosed(_) => methods::THREAD_CLOSED,
268            Self::ThreadDeleted(_) => methods::THREAD_DELETED,
269            Self::ThreadUnarchived(_) => methods::THREAD_UNARCHIVED,
270            Self::ThreadGoalCleared(_) => methods::THREAD_GOAL_CLEARED,
271            Self::ThreadNameUpdated(_) => methods::THREAD_NAME_UPDATED,
272            Self::SkillsChanged(_) => methods::SKILLS_CHANGED,
273            Self::FsChanged(_) => methods::FS_CHANGED,
274            Self::ConfigWarning(_) => methods::CONFIG_WARNING,
275            Self::AccountUpdated(_) => methods::ACCOUNT_UPDATED,
276            Self::AppListUpdated(_) => methods::APP_LIST_UPDATED,
277            Self::CommandExecOutputDelta(_) => methods::COMMAND_EXEC_OUTPUT_DELTA,
278            Self::ExternalAgentConfigImportCompleted(_) => {
279                methods::EXTERNAL_AGENT_CONFIG_IMPORT_COMPLETED
280            }
281            Self::FuzzyFileSearchSessionCompleted(_) => {
282                methods::FUZZY_FILE_SEARCH_SESSION_COMPLETED
283            }
284            Self::FuzzyFileSearchSessionUpdated(_) => methods::FUZZY_FILE_SEARCH_SESSION_UPDATED,
285            Self::HookCompleted(_) => methods::HOOK_COMPLETED,
286            Self::HookStarted(_) => methods::HOOK_STARTED,
287            Self::ItemGuardianApprovalReviewCompleted(_) => {
288                methods::ITEM_AUTO_APPROVAL_REVIEW_COMPLETED
289            }
290            Self::ItemGuardianApprovalReviewStarted(_) => {
291                methods::ITEM_AUTO_APPROVAL_REVIEW_STARTED
292            }
293            Self::TerminalInteraction(_) => methods::ITEM_COMMAND_EXEC_TERMINAL_INTERACTION,
294            Self::McpToolCallProgress(_) => methods::ITEM_MCP_TOOL_CALL_PROGRESS,
295            Self::ModelRerouted(_) => methods::MODEL_REROUTED,
296            Self::ModelVerification(_) => methods::MODEL_VERIFICATION,
297            Self::ProcessExited(_) => methods::PROCESS_EXITED,
298            Self::ProcessOutputDelta(_) => methods::PROCESS_OUTPUT_DELTA,
299            Self::ServerRequestResolved(_) => methods::SERVER_REQUEST_RESOLVED,
300            Self::ContextCompacted(_) => methods::THREAD_COMPACTED,
301            Self::ThreadGoalUpdated(_) => methods::THREAD_GOAL_UPDATED,
302            Self::ThreadRealtimeClosed(_) => methods::THREAD_REALTIME_CLOSED,
303            Self::ThreadRealtimeError(_) => methods::THREAD_REALTIME_ERROR,
304            Self::ThreadRealtimeItemAdded(_) => methods::THREAD_REALTIME_ITEM_ADDED,
305            Self::ThreadRealtimeOutputAudioDelta(_) => methods::THREAD_REALTIME_OUTPUT_AUDIO_DELTA,
306            Self::ThreadRealtimeSdp(_) => methods::THREAD_REALTIME_SDP,
307            Self::ThreadRealtimeStarted(_) => methods::THREAD_REALTIME_STARTED,
308            Self::ThreadRealtimeTranscriptDelta(_) => methods::THREAD_REALTIME_TRANSCRIPT_DELTA,
309            Self::ThreadRealtimeTranscriptDone(_) => methods::THREAD_REALTIME_TRANSCRIPT_DONE,
310            Self::WindowsWorldWritableWarning(_) => methods::WINDOWS_WORLD_WRITABLE_WARNING,
311            Self::WindowsSandboxSetupCompleted(_) => methods::WINDOWS_SANDBOX_SETUP_COMPLETED,
312            Self::ThreadSettingsUpdated(_) => methods::THREAD_SETTINGS_UPDATED,
313            Self::TurnModerationMetadata(_) => methods::TURN_MODERATION_METADATA,
314            Self::ExternalAgentConfigImportProgress(_) => {
315                methods::EXTERNAL_AGENT_CONFIG_IMPORT_PROGRESS
316            }
317            Self::ModelSafetyBufferingUpdated(_) => methods::MODEL_SAFETY_BUFFERING_UPDATED,
318            Self::ThreadEnvironmentConnected(_) => methods::THREAD_ENVIRONMENT_CONNECTED,
319            Self::ThreadEnvironmentDisconnected(_) => methods::THREAD_ENVIRONMENT_DISCONNECTED,
320            Self::Unknown { method, .. } => method,
321        }
322    }
323
324    /// `true` if this notification's method isn't modeled by the crate.
325    pub fn is_unknown(&self) -> bool {
326        matches!(self, Self::Unknown { .. })
327    }
328
329    /// Return the turn id this notification is scoped to, if it carries one.
330    ///
331    /// Reads the typed `turnId` field (or `turn.id` for the turn-lifecycle
332    /// notifications) directly, so callers don't have to round-trip through
333    /// [`into_envelope`](Self::into_envelope) and poke `serde_json::Value`
334    /// fields. Returns `None` for notifications that aren't turn-scoped, and
335    /// treats an empty id the server omitted as absent.
336    ///
337    /// The turn id from `turn/started` is what
338    /// [`turn/interrupt`](crate::AsyncClient::turn_interrupt) needs to cancel
339    /// the active turn.
340    pub fn turn_id(&self) -> Option<&str> {
341        let id = match self {
342            Self::TurnStarted(n) => n.turn.id.as_str(),
343            Self::TurnCompleted(n) => n.turn.id.as_str(),
344            Self::AgentMessageDelta(n) => n.turn_id.as_str(),
345            Self::CmdOutputDelta(n) => n.turn_id.as_str(),
346            Self::FileChangeOutputDelta(n) => n.turn_id.as_str(),
347            Self::ReasoningDelta(n) => n.turn_id.as_str(),
348            Self::Error(n) => n.turn_id.as_str(),
349            Self::FileChangePatchUpdated(n) => n.turn_id.as_str(),
350            Self::PlanDelta(n) => n.turn_id.as_str(),
351            Self::TurnPlanUpdated(n) => n.turn_id.as_str(),
352            Self::TurnDiffUpdated(n) => n.turn_id.as_str(),
353            Self::TurnModerationMetadata(n) => n.turn_id.as_str(),
354            Self::ReasoningSummaryPartAdded(n) => n.turn_id.as_str(),
355            Self::ReasoningTextDelta(n) => n.turn_id.as_str(),
356            Self::ItemStarted(n) => n.turn_id.as_str(),
357            Self::ItemCompleted(n) => n.turn_id.as_str(),
358            Self::ContextCompacted(n) => n.turn_id.as_str(),
359            Self::McpToolCallProgress(n) => n.turn_id.as_str(),
360            Self::ModelRerouted(n) => n.turn_id.as_str(),
361            Self::ModelVerification(n) => n.turn_id.as_str(),
362            Self::ModelSafetyBufferingUpdated(n) => n.turn_id.as_str(),
363            Self::TerminalInteraction(n) => n.turn_id.as_str(),
364            Self::ThreadTokenUsageUpdated(n) => n.turn_id.as_str(),
365            Self::ItemGuardianApprovalReviewStarted(n) => n.turn_id.as_str(),
366            Self::ItemGuardianApprovalReviewCompleted(n) => n.turn_id.as_str(),
367            _ => return None,
368        };
369        (!id.is_empty()).then_some(id)
370    }
371
372    /// Return the typed [`ThreadItem`](crate::protocol::ThreadItem) this
373    /// notification carries, for `item/started` and `item/completed`.
374    ///
375    /// Lets downstream event adapters read the item's typed fields instead of
376    /// reserializing it back into `serde_json::Value`.
377    pub fn thread_item(&self) -> Option<&crate::protocol::ThreadItem> {
378        match self {
379            Self::ItemStarted(n) => Some(&n.item),
380            Self::ItemCompleted(n) => Some(&n.item),
381            _ => None,
382        }
383    }
384
385    /// Construct a [`Notification`] from a `method` + `params` envelope.
386    ///
387    /// Returns an error if `method` is recognized but `params` doesn't
388    /// deserialize into the typed struct. Unknown methods route to
389    /// [`Notification::Unknown`] without error.
390    pub fn from_envelope(method: &str, params: Option<Value>) -> Result<Self, serde_json::Error> {
391        let params_value = params.clone().unwrap_or(Value::Null);
392        match method {
393            methods::THREAD_STARTED => {
394                serde_json::from_value(params_value).map(Self::ThreadStarted)
395            }
396            methods::STRICT_REVIEW_REQUIRED => {
397                serde_json::from_value(params_value).map(Self::StrictReviewRequired)
398            }
399            methods::PROJECT_CHANGED => {
400                serde_json::from_value(params_value).map(Self::ProjectChanged)
401            }
402            methods::THREAD_PROJECT_UPDATED => {
403                serde_json::from_value(params_value).map(Self::ThreadProjectUpdated)
404            }
405            methods::THREAD_QUEUE_CHANGED => {
406                serde_json::from_value(params_value).map(Self::ThreadQueueChanged)
407            }
408            methods::THREAD_REVERTED => {
409                serde_json::from_value(params_value).map(Self::ThreadReverted)
410            }
411            methods::THREAD_STATUS_CHANGED => {
412                serde_json::from_value(params_value).map(Self::ThreadStatusChanged)
413            }
414            methods::THREAD_TOKEN_USAGE_UPDATED => {
415                serde_json::from_value(params_value).map(Self::ThreadTokenUsageUpdated)
416            }
417            methods::TURN_STARTED => serde_json::from_value(params_value).map(Self::TurnStarted),
418            methods::TURN_COMPLETED => {
419                serde_json::from_value(params_value).map(Self::TurnCompleted)
420            }
421            methods::ITEM_STARTED => serde_json::from_value(params_value).map(Self::ItemStarted),
422            methods::ITEM_COMPLETED => {
423                serde_json::from_value(params_value).map(Self::ItemCompleted)
424            }
425            methods::AGENT_MESSAGE_DELTA => {
426                serde_json::from_value(params_value).map(Self::AgentMessageDelta)
427            }
428            methods::CMD_OUTPUT_DELTA => {
429                serde_json::from_value(params_value).map(Self::CmdOutputDelta)
430            }
431            methods::FILE_CHANGE_OUTPUT_DELTA => {
432                serde_json::from_value(params_value).map(Self::FileChangeOutputDelta)
433            }
434            methods::REASONING_DELTA => {
435                serde_json::from_value(params_value).map(Self::ReasoningDelta)
436            }
437            methods::ERROR => serde_json::from_value(params_value).map(Self::Error),
438            methods::ACCOUNT_RATE_LIMITS_UPDATED => {
439                serde_json::from_value(params_value).map(Self::AccountRateLimitsUpdated)
440            }
441            methods::MCP_SERVER_STARTUP_STATUS_UPDATED => {
442                serde_json::from_value(params_value).map(Self::McpServerStartupStatusUpdated)
443            }
444            methods::REMOTE_CONTROL_STATUS_CHANGED => {
445                serde_json::from_value(params_value).map(Self::RemoteControlStatusChanged)
446            }
447            methods::MCP_SERVER_OAUTH_LOGIN_COMPLETED => {
448                serde_json::from_value(params_value).map(Self::McpServerOauthLoginCompleted)
449            }
450            methods::FILE_CHANGE_PATCH_UPDATED => {
451                serde_json::from_value(params_value).map(Self::FileChangePatchUpdated)
452            }
453            methods::PLAN_DELTA => serde_json::from_value(params_value).map(Self::PlanDelta),
454            methods::TURN_PLAN_UPDATED => {
455                serde_json::from_value(params_value).map(Self::TurnPlanUpdated)
456            }
457            methods::TURN_DIFF_UPDATED => {
458                serde_json::from_value(params_value).map(Self::TurnDiffUpdated)
459            }
460            methods::REASONING_SUMMARY_PART_ADDED => {
461                serde_json::from_value(params_value).map(Self::ReasoningSummaryPartAdded)
462            }
463            methods::REASONING_TEXT_DELTA => {
464                serde_json::from_value(params_value).map(Self::ReasoningTextDelta)
465            }
466            methods::ACCOUNT_LOGIN_COMPLETED => {
467                serde_json::from_value(params_value).map(Self::AccountLoginCompleted)
468            }
469            methods::DEPRECATION_NOTICE => {
470                serde_json::from_value(params_value).map(Self::DeprecationNotice)
471            }
472            methods::GUARDIAN_WARNING => {
473                serde_json::from_value(params_value).map(Self::GuardianWarning)
474            }
475            methods::WARNING => serde_json::from_value(params_value).map(Self::Warning),
476            methods::THREAD_ARCHIVED => {
477                serde_json::from_value(params_value).map(Self::ThreadArchived)
478            }
479            methods::THREAD_CLOSED => serde_json::from_value(params_value).map(Self::ThreadClosed),
480            methods::THREAD_DELETED => {
481                serde_json::from_value(params_value).map(Self::ThreadDeleted)
482            }
483            methods::THREAD_UNARCHIVED => {
484                serde_json::from_value(params_value).map(Self::ThreadUnarchived)
485            }
486            methods::THREAD_GOAL_CLEARED => {
487                serde_json::from_value(params_value).map(Self::ThreadGoalCleared)
488            }
489            methods::THREAD_NAME_UPDATED => {
490                serde_json::from_value(params_value).map(Self::ThreadNameUpdated)
491            }
492            methods::SKILLS_CHANGED => {
493                serde_json::from_value(params_value).map(Self::SkillsChanged)
494            }
495            methods::FS_CHANGED => serde_json::from_value(params_value).map(Self::FsChanged),
496            methods::CONFIG_WARNING => {
497                serde_json::from_value(params_value).map(Self::ConfigWarning)
498            }
499            methods::ACCOUNT_UPDATED => {
500                serde_json::from_value(params_value).map(Self::AccountUpdated)
501            }
502            methods::APP_LIST_UPDATED => {
503                serde_json::from_value(params_value).map(Self::AppListUpdated)
504            }
505            methods::COMMAND_EXEC_OUTPUT_DELTA => {
506                serde_json::from_value(params_value).map(Self::CommandExecOutputDelta)
507            }
508            methods::EXTERNAL_AGENT_CONFIG_IMPORT_COMPLETED => {
509                serde_json::from_value(params_value).map(Self::ExternalAgentConfigImportCompleted)
510            }
511            methods::FUZZY_FILE_SEARCH_SESSION_COMPLETED => {
512                serde_json::from_value(params_value).map(Self::FuzzyFileSearchSessionCompleted)
513            }
514            methods::FUZZY_FILE_SEARCH_SESSION_UPDATED => {
515                serde_json::from_value(params_value).map(Self::FuzzyFileSearchSessionUpdated)
516            }
517            methods::HOOK_COMPLETED => {
518                serde_json::from_value(params_value).map(Self::HookCompleted)
519            }
520            methods::HOOK_STARTED => serde_json::from_value(params_value).map(Self::HookStarted),
521            methods::ITEM_AUTO_APPROVAL_REVIEW_COMPLETED => {
522                serde_json::from_value(params_value).map(Self::ItemGuardianApprovalReviewCompleted)
523            }
524            methods::ITEM_AUTO_APPROVAL_REVIEW_STARTED => {
525                serde_json::from_value(params_value).map(Self::ItemGuardianApprovalReviewStarted)
526            }
527            methods::ITEM_COMMAND_EXEC_TERMINAL_INTERACTION => {
528                serde_json::from_value(params_value).map(Self::TerminalInteraction)
529            }
530            methods::ITEM_MCP_TOOL_CALL_PROGRESS => {
531                serde_json::from_value(params_value).map(Self::McpToolCallProgress)
532            }
533            methods::MODEL_REROUTED => {
534                serde_json::from_value(params_value).map(Self::ModelRerouted)
535            }
536            methods::MODEL_VERIFICATION => {
537                serde_json::from_value(params_value).map(Self::ModelVerification)
538            }
539            methods::PROCESS_EXITED => {
540                serde_json::from_value(params_value).map(Self::ProcessExited)
541            }
542            methods::PROCESS_OUTPUT_DELTA => {
543                serde_json::from_value(params_value).map(Self::ProcessOutputDelta)
544            }
545            methods::SERVER_REQUEST_RESOLVED => {
546                serde_json::from_value(params_value).map(Self::ServerRequestResolved)
547            }
548            methods::THREAD_COMPACTED => {
549                serde_json::from_value(params_value).map(Self::ContextCompacted)
550            }
551            methods::THREAD_GOAL_UPDATED => {
552                serde_json::from_value(params_value).map(Self::ThreadGoalUpdated)
553            }
554            methods::THREAD_REALTIME_CLOSED => {
555                serde_json::from_value(params_value).map(Self::ThreadRealtimeClosed)
556            }
557            methods::THREAD_REALTIME_ERROR => {
558                serde_json::from_value(params_value).map(Self::ThreadRealtimeError)
559            }
560            methods::THREAD_REALTIME_ITEM_ADDED => {
561                serde_json::from_value(params_value).map(Self::ThreadRealtimeItemAdded)
562            }
563            methods::THREAD_REALTIME_OUTPUT_AUDIO_DELTA => {
564                serde_json::from_value(params_value).map(Self::ThreadRealtimeOutputAudioDelta)
565            }
566            methods::THREAD_REALTIME_SDP => {
567                serde_json::from_value(params_value).map(Self::ThreadRealtimeSdp)
568            }
569            methods::THREAD_REALTIME_STARTED => {
570                serde_json::from_value(params_value).map(Self::ThreadRealtimeStarted)
571            }
572            methods::THREAD_REALTIME_TRANSCRIPT_DELTA => {
573                serde_json::from_value(params_value).map(Self::ThreadRealtimeTranscriptDelta)
574            }
575            methods::THREAD_REALTIME_TRANSCRIPT_DONE => {
576                serde_json::from_value(params_value).map(Self::ThreadRealtimeTranscriptDone)
577            }
578            methods::WINDOWS_WORLD_WRITABLE_WARNING => {
579                serde_json::from_value(params_value).map(Self::WindowsWorldWritableWarning)
580            }
581            methods::WINDOWS_SANDBOX_SETUP_COMPLETED => {
582                serde_json::from_value(params_value).map(Self::WindowsSandboxSetupCompleted)
583            }
584            methods::THREAD_SETTINGS_UPDATED => {
585                serde_json::from_value(params_value).map(Self::ThreadSettingsUpdated)
586            }
587            methods::TURN_MODERATION_METADATA => {
588                serde_json::from_value(params_value).map(Self::TurnModerationMetadata)
589            }
590            methods::EXTERNAL_AGENT_CONFIG_IMPORT_PROGRESS => {
591                serde_json::from_value(params_value).map(Self::ExternalAgentConfigImportProgress)
592            }
593            methods::MODEL_SAFETY_BUFFERING_UPDATED => {
594                serde_json::from_value(params_value).map(Self::ModelSafetyBufferingUpdated)
595            }
596            methods::THREAD_ENVIRONMENT_CONNECTED => {
597                serde_json::from_value(params_value).map(Self::ThreadEnvironmentConnected)
598            }
599            methods::THREAD_ENVIRONMENT_DISCONNECTED => {
600                serde_json::from_value(params_value).map(Self::ThreadEnvironmentDisconnected)
601            }
602            _ => Ok(Self::Unknown {
603                method: method.to_string(),
604                params,
605            }),
606        }
607    }
608
609    /// Decompose this notification back into a `(method, params)` pair.
610    pub fn into_envelope(self) -> Result<(String, Option<Value>), serde_json::Error> {
611        fn pack<T: Serialize>(
612            method: &str,
613            v: &T,
614        ) -> Result<(String, Option<Value>), serde_json::Error> {
615            Ok((method.to_string(), Some(serde_json::to_value(v)?)))
616        }
617        match &self {
618            Self::ThreadStarted(v) => pack(methods::THREAD_STARTED, v),
619            Self::StrictReviewRequired(v) => pack(methods::STRICT_REVIEW_REQUIRED, v),
620            Self::ProjectChanged(v) => pack(methods::PROJECT_CHANGED, v),
621            Self::ThreadProjectUpdated(v) => pack(methods::THREAD_PROJECT_UPDATED, v),
622            Self::ThreadQueueChanged(v) => pack(methods::THREAD_QUEUE_CHANGED, v),
623            Self::ThreadReverted(v) => pack(methods::THREAD_REVERTED, v),
624            Self::ThreadStatusChanged(v) => pack(methods::THREAD_STATUS_CHANGED, v),
625            Self::ThreadTokenUsageUpdated(v) => pack(methods::THREAD_TOKEN_USAGE_UPDATED, v),
626            Self::TurnStarted(v) => pack(methods::TURN_STARTED, v),
627            Self::TurnCompleted(v) => pack(methods::TURN_COMPLETED, v),
628            Self::ItemStarted(v) => pack(methods::ITEM_STARTED, v),
629            Self::ItemCompleted(v) => pack(methods::ITEM_COMPLETED, v),
630            Self::AgentMessageDelta(v) => pack(methods::AGENT_MESSAGE_DELTA, v),
631            Self::CmdOutputDelta(v) => pack(methods::CMD_OUTPUT_DELTA, v),
632            Self::FileChangeOutputDelta(v) => pack(methods::FILE_CHANGE_OUTPUT_DELTA, v),
633            Self::ReasoningDelta(v) => pack(methods::REASONING_DELTA, v),
634            Self::Error(v) => pack(methods::ERROR, v),
635            Self::AccountRateLimitsUpdated(v) => pack(methods::ACCOUNT_RATE_LIMITS_UPDATED, v),
636            Self::McpServerStartupStatusUpdated(v) => {
637                pack(methods::MCP_SERVER_STARTUP_STATUS_UPDATED, v)
638            }
639            Self::RemoteControlStatusChanged(v) => pack(methods::REMOTE_CONTROL_STATUS_CHANGED, v),
640            Self::McpServerOauthLoginCompleted(v) => {
641                pack(methods::MCP_SERVER_OAUTH_LOGIN_COMPLETED, v)
642            }
643            Self::FileChangePatchUpdated(v) => pack(methods::FILE_CHANGE_PATCH_UPDATED, v),
644            Self::PlanDelta(v) => pack(methods::PLAN_DELTA, v),
645            Self::TurnPlanUpdated(v) => pack(methods::TURN_PLAN_UPDATED, v),
646            Self::TurnDiffUpdated(v) => pack(methods::TURN_DIFF_UPDATED, v),
647            Self::ReasoningSummaryPartAdded(v) => pack(methods::REASONING_SUMMARY_PART_ADDED, v),
648            Self::ReasoningTextDelta(v) => pack(methods::REASONING_TEXT_DELTA, v),
649            Self::AccountLoginCompleted(v) => pack(methods::ACCOUNT_LOGIN_COMPLETED, v),
650            Self::DeprecationNotice(v) => pack(methods::DEPRECATION_NOTICE, v),
651            Self::GuardianWarning(v) => pack(methods::GUARDIAN_WARNING, v),
652            Self::Warning(v) => pack(methods::WARNING, v),
653            Self::ThreadArchived(v) => pack(methods::THREAD_ARCHIVED, v),
654            Self::ThreadClosed(v) => pack(methods::THREAD_CLOSED, v),
655            Self::ThreadDeleted(v) => pack(methods::THREAD_DELETED, v),
656            Self::ThreadUnarchived(v) => pack(methods::THREAD_UNARCHIVED, v),
657            Self::ThreadGoalCleared(v) => pack(methods::THREAD_GOAL_CLEARED, v),
658            Self::ThreadNameUpdated(v) => pack(methods::THREAD_NAME_UPDATED, v),
659            Self::SkillsChanged(v) => pack(methods::SKILLS_CHANGED, v),
660            Self::FsChanged(v) => pack(methods::FS_CHANGED, v),
661            Self::ConfigWarning(v) => pack(methods::CONFIG_WARNING, v),
662            Self::AccountUpdated(v) => pack(methods::ACCOUNT_UPDATED, v),
663            Self::AppListUpdated(v) => pack(methods::APP_LIST_UPDATED, v),
664            Self::CommandExecOutputDelta(v) => pack(methods::COMMAND_EXEC_OUTPUT_DELTA, v),
665            Self::ExternalAgentConfigImportCompleted(v) => {
666                pack(methods::EXTERNAL_AGENT_CONFIG_IMPORT_COMPLETED, v)
667            }
668            Self::FuzzyFileSearchSessionCompleted(v) => {
669                pack(methods::FUZZY_FILE_SEARCH_SESSION_COMPLETED, v)
670            }
671            Self::FuzzyFileSearchSessionUpdated(v) => {
672                pack(methods::FUZZY_FILE_SEARCH_SESSION_UPDATED, v)
673            }
674            Self::HookCompleted(v) => pack(methods::HOOK_COMPLETED, v),
675            Self::HookStarted(v) => pack(methods::HOOK_STARTED, v),
676            Self::ItemGuardianApprovalReviewCompleted(v) => {
677                pack(methods::ITEM_AUTO_APPROVAL_REVIEW_COMPLETED, v)
678            }
679            Self::ItemGuardianApprovalReviewStarted(v) => {
680                pack(methods::ITEM_AUTO_APPROVAL_REVIEW_STARTED, v)
681            }
682            Self::TerminalInteraction(v) => {
683                pack(methods::ITEM_COMMAND_EXEC_TERMINAL_INTERACTION, v)
684            }
685            Self::McpToolCallProgress(v) => pack(methods::ITEM_MCP_TOOL_CALL_PROGRESS, v),
686            Self::ModelRerouted(v) => pack(methods::MODEL_REROUTED, v),
687            Self::ModelVerification(v) => pack(methods::MODEL_VERIFICATION, v),
688            Self::ProcessExited(v) => pack(methods::PROCESS_EXITED, v),
689            Self::ProcessOutputDelta(v) => pack(methods::PROCESS_OUTPUT_DELTA, v),
690            Self::ServerRequestResolved(v) => pack(methods::SERVER_REQUEST_RESOLVED, v),
691            Self::ContextCompacted(v) => pack(methods::THREAD_COMPACTED, v),
692            Self::ThreadGoalUpdated(v) => pack(methods::THREAD_GOAL_UPDATED, v),
693            Self::ThreadRealtimeClosed(v) => pack(methods::THREAD_REALTIME_CLOSED, v),
694            Self::ThreadRealtimeError(v) => pack(methods::THREAD_REALTIME_ERROR, v),
695            Self::ThreadRealtimeItemAdded(v) => pack(methods::THREAD_REALTIME_ITEM_ADDED, v),
696            Self::ThreadRealtimeOutputAudioDelta(v) => {
697                pack(methods::THREAD_REALTIME_OUTPUT_AUDIO_DELTA, v)
698            }
699            Self::ThreadRealtimeSdp(v) => pack(methods::THREAD_REALTIME_SDP, v),
700            Self::ThreadRealtimeStarted(v) => pack(methods::THREAD_REALTIME_STARTED, v),
701            Self::ThreadRealtimeTranscriptDelta(v) => {
702                pack(methods::THREAD_REALTIME_TRANSCRIPT_DELTA, v)
703            }
704            Self::ThreadRealtimeTranscriptDone(v) => {
705                pack(methods::THREAD_REALTIME_TRANSCRIPT_DONE, v)
706            }
707            Self::WindowsWorldWritableWarning(v) => {
708                pack(methods::WINDOWS_WORLD_WRITABLE_WARNING, v)
709            }
710            Self::WindowsSandboxSetupCompleted(v) => {
711                pack(methods::WINDOWS_SANDBOX_SETUP_COMPLETED, v)
712            }
713            Self::ThreadSettingsUpdated(v) => pack(methods::THREAD_SETTINGS_UPDATED, v),
714            Self::TurnModerationMetadata(v) => pack(methods::TURN_MODERATION_METADATA, v),
715            Self::ExternalAgentConfigImportProgress(v) => {
716                pack(methods::EXTERNAL_AGENT_CONFIG_IMPORT_PROGRESS, v)
717            }
718            Self::ModelSafetyBufferingUpdated(v) => {
719                pack(methods::MODEL_SAFETY_BUFFERING_UPDATED, v)
720            }
721            Self::ThreadEnvironmentConnected(v) => pack(methods::THREAD_ENVIRONMENT_CONNECTED, v),
722            Self::ThreadEnvironmentDisconnected(v) => {
723                pack(methods::THREAD_ENVIRONMENT_DISCONNECTED, v)
724            }
725            Self::Unknown { method, params } => Ok((method.clone(), params.clone())),
726        }
727    }
728}
729
730impl Serialize for Notification {
731    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
732        let (method, params) = self
733            .clone()
734            .into_envelope()
735            .map_err(serde::ser::Error::custom)?;
736        let mut env = serde_json::Map::new();
737        env.insert("method".to_string(), Value::String(method));
738        if let Some(p) = params {
739            env.insert("params".to_string(), p);
740        }
741        Value::Object(env).serialize(serializer)
742    }
743}
744
745impl<'de> Deserialize<'de> for Notification {
746    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
747        let value = Value::deserialize(deserializer)?;
748        let method = value
749            .get("method")
750            .and_then(|v| v.as_str())
751            .ok_or_else(|| serde::de::Error::missing_field("method"))?
752            .to_string();
753        let params = value.get("params").cloned();
754        Self::from_envelope(&method, params).map_err(serde::de::Error::custom)
755    }
756}
757
758/// A server-to-client request that requires a response (approval flow).
759///
760/// The wire envelope carries an `id` for response correlation; that `id` is
761/// held alongside this enum in [`ServerMessage::Request`] rather than embedded
762/// inside the variant, since responding doesn't depend on which approval-type
763/// was requested.
764#[derive(Debug, Clone)]
765pub enum ServerRequest {
766    /// `item/commandExecution/requestApproval`
767    CmdExecApproval(CommandExecutionRequestApprovalParams),
768    /// `item/fileChange/requestApproval`
769    FileChangeApproval(FileChangeRequestApprovalParams),
770    /// `item/tool/requestUserInput`
771    ToolRequestUserInput(crate::protocol::ToolRequestUserInputParams),
772    /// `mcpServer/elicitation/request`
773    McpServerElicitationRequest(crate::protocol::McpServerElicitationRequestParams),
774    /// `item/permissions/requestApproval`
775    PermissionsRequestApproval(crate::protocol::PermissionsRequestApprovalParams),
776    /// `item/tool/call`
777    ItemToolCall(crate::protocol::DynamicToolCallParams),
778    /// `account/chatgptAuthTokens/refresh`
779    ChatgptAuthTokensRefresh(crate::protocol::ChatgptAuthTokensRefreshParams),
780    /// `attestation/generate`
781    AttestationGenerate(crate::protocol::AttestationGenerateParams),
782    /// `applyPatchApproval`
783    ApplyPatchApproval(crate::protocol::ApplyPatchApprovalParams),
784    /// `execCommandApproval`
785    ExecCommandApproval(crate::protocol::ExecCommandApprovalParams),
786    /// A request method this crate version does not yet model.
787    Unknown {
788        method: String,
789        params: Option<Value>,
790    },
791}
792
793impl ServerRequest {
794    /// Return the wire `method` string for this request.
795    pub fn method(&self) -> &str {
796        match self {
797            Self::CmdExecApproval(_) => methods::CMD_EXEC_APPROVAL,
798            Self::FileChangeApproval(_) => methods::FILE_CHANGE_APPROVAL,
799            Self::ToolRequestUserInput(_) => methods::TOOL_REQUEST_USER_INPUT,
800            Self::McpServerElicitationRequest(_) => methods::MCP_SERVER_ELICITATION_REQUEST,
801            Self::PermissionsRequestApproval(_) => methods::PERMISSIONS_REQUEST_APPROVAL,
802            Self::ItemToolCall(_) => methods::ITEM_TOOL_CALL,
803            Self::ChatgptAuthTokensRefresh(_) => methods::CHATGPT_AUTH_TOKENS_REFRESH,
804            Self::AttestationGenerate(_) => methods::ATTESTATION_GENERATE,
805            Self::ApplyPatchApproval(_) => methods::APPLY_PATCH_APPROVAL,
806            Self::ExecCommandApproval(_) => methods::EXEC_COMMAND_APPROVAL,
807            Self::Unknown { method, .. } => method,
808        }
809    }
810
811    /// `true` if this request's method isn't modeled by the crate.
812    pub fn is_unknown(&self) -> bool {
813        matches!(self, Self::Unknown { .. })
814    }
815
816    /// Construct a [`ServerRequest`] from a `method` + `params` envelope.
817    pub fn from_envelope(method: &str, params: Option<Value>) -> Result<Self, serde_json::Error> {
818        let params_value = params.clone().unwrap_or(Value::Null);
819        match method {
820            methods::CMD_EXEC_APPROVAL => {
821                serde_json::from_value(params_value).map(Self::CmdExecApproval)
822            }
823            methods::FILE_CHANGE_APPROVAL => {
824                serde_json::from_value(params_value).map(Self::FileChangeApproval)
825            }
826            methods::TOOL_REQUEST_USER_INPUT => {
827                serde_json::from_value(params_value).map(Self::ToolRequestUserInput)
828            }
829            methods::MCP_SERVER_ELICITATION_REQUEST => {
830                serde_json::from_value(params_value).map(Self::McpServerElicitationRequest)
831            }
832            methods::PERMISSIONS_REQUEST_APPROVAL => {
833                serde_json::from_value(params_value).map(Self::PermissionsRequestApproval)
834            }
835            methods::ITEM_TOOL_CALL => serde_json::from_value(params_value).map(Self::ItemToolCall),
836            methods::CHATGPT_AUTH_TOKENS_REFRESH => {
837                serde_json::from_value(params_value).map(Self::ChatgptAuthTokensRefresh)
838            }
839            methods::ATTESTATION_GENERATE => {
840                serde_json::from_value(params_value).map(Self::AttestationGenerate)
841            }
842            methods::APPLY_PATCH_APPROVAL => {
843                serde_json::from_value(params_value).map(Self::ApplyPatchApproval)
844            }
845            methods::EXEC_COMMAND_APPROVAL => {
846                serde_json::from_value(params_value).map(Self::ExecCommandApproval)
847            }
848            _ => Ok(Self::Unknown {
849                method: method.to_string(),
850                params,
851            }),
852        }
853    }
854}
855
856/// A message coming from the app-server.
857///
858/// Replaces the previous loose `{ method, params }` shape with typed enums.
859/// Match on the outer variant first to distinguish notifications (no response)
860/// from requests (need [`crate::AsyncClient::respond`] /
861/// [`crate::SyncClient::respond`]).
862/// Keep variants unboxed so pattern matches and constructors stay ergonomic;
863/// the size skew comes from the generated notification payloads.
864#[allow(clippy::large_enum_variant)]
865#[derive(Debug, Clone)]
866pub enum ServerMessage {
867    /// A notification — no response required.
868    Notification(Notification),
869    /// A request — call `respond(id, ...)` on the client with the matching id.
870    Request {
871        id: RequestId,
872        request: ServerRequest,
873    },
874}
875
876impl ServerMessage {
877    /// `true` if this message is an unmodeled method (notification or request).
878    pub fn is_unknown(&self) -> bool {
879        match self {
880            Self::Notification(n) => n.is_unknown(),
881            Self::Request { request, .. } => request.is_unknown(),
882        }
883    }
884
885    /// Parse a raw app-server frame (one JSON-RPC line) into a [`ServerMessage`].
886    ///
887    /// This is the same parse path the [`AsyncClient`](crate::AsyncClient) and
888    /// [`SyncClient`](crate::SyncClient) run on incoming lines, exposed for
889    /// replay, recovery, and test fixtures that need to decode a captured frame
890    /// without a live client.
891    ///
892    /// A frame is a [`ServerMessage`] only if it is a server-initiated
893    /// notification or request. A JSON-RPC *response* / *error* (a reply to a
894    /// client request) is not a server message and returns
895    /// [`Error::Protocol`](crate::Error::Protocol). Unknown methods decode to
896    /// the `Unknown` variants rather than erroring; a modeled method whose
897    /// `params` don't fit returns [`Error::Deserialization`](crate::Error::Deserialization)
898    /// carrying the raw frame.
899    pub fn from_json_str(s: &str) -> Result<Self, Error> {
900        let msg: JsonRpcMessage = serde_json::from_str(s)
901            .map_err(|e| Error::Deserialization(ParseError::from_line(s, e)))?;
902        Self::from_jsonrpc(msg)
903    }
904
905    /// Parse a [`serde_json::Value`] frame into a [`ServerMessage`].
906    ///
907    /// See [`from_json_str`](Self::from_json_str) for the parsing contract.
908    pub fn from_value(value: Value) -> Result<Self, Error> {
909        let msg: JsonRpcMessage = serde_json::from_value(value.clone())
910            .map_err(|e| Error::Deserialization(ParseError::from_line(value.to_string(), e)))?;
911        Self::from_jsonrpc(msg)
912    }
913
914    fn from_jsonrpc(msg: JsonRpcMessage) -> Result<Self, Error> {
915        match msg {
916            JsonRpcMessage::Notification(JsonRpcNotification { method, params }) => {
917                Notification::from_envelope(&method, params.clone())
918                    .map(ServerMessage::Notification)
919                    .map_err(|e| Error::Deserialization(ParseError::from_envelope(method, params, e)))
920            }
921            JsonRpcMessage::Request(JsonRpcRequest { id, method, params }) => {
922                ServerRequest::from_envelope(&method, params.clone())
923                    .map(|request| ServerMessage::Request { id, request })
924                    .map_err(|e| Error::Deserialization(ParseError::from_envelope(method, params, e)))
925            }
926            JsonRpcMessage::Response(resp) => Err(Error::Protocol(format!(
927                "frame is a JSON-RPC response (id={}), not a server-initiated message",
928                resp.id
929            ))),
930            JsonRpcMessage::Error(err) => Err(Error::Protocol(format!(
931                "frame is a JSON-RPC error response (id={}, code={}), not a server-initiated message",
932                err.id, err.error.code
933            ))),
934        }
935    }
936}
937
938#[cfg(test)]
939mod tests {
940    use super::*;
941
942    #[test]
943    fn test_notification_unknown_method_routes_to_unknown_variant() {
944        let n = Notification::from_envelope("foo/bar", Some(serde_json::json!({"x": 1})))
945            .expect("unknown methods do not error");
946        match n {
947            Notification::Unknown { method, params } => {
948                assert_eq!(method, "foo/bar");
949                assert_eq!(params, Some(serde_json::json!({"x": 1})));
950            }
951            other => panic!("expected Unknown, got {:?}", other),
952        }
953    }
954
955    #[test]
956    fn test_notification_known_method_with_bad_params_errors() {
957        // thread/started expects a `thread` field — wrong shape should error.
958        let err = Notification::from_envelope("thread/started", Some(serde_json::json!({})));
959        assert!(err.is_err());
960    }
961
962    #[test]
963    fn test_notification_round_trip_envelope() {
964        let wire = serde_json::json!({
965            "method": "item/agentMessage/delta",
966            "params": {"threadId": "t1", "turnId": "u1", "itemId": "i1", "delta": "hi"},
967        });
968        let n: Notification = serde_json::from_value(wire.clone()).unwrap();
969        assert!(matches!(n, Notification::AgentMessageDelta(_)));
970        let back = serde_json::to_value(&n).unwrap();
971        assert_eq!(back, wire);
972    }
973
974    #[test]
975    fn test_turn_id_from_turn_started() {
976        // turn/started carries the id under `turn.id`.
977        let wire = serde_json::json!({
978            "method": "turn/started",
979            "params": {"threadId": "t1", "turn": {"id": "turn_42", "status": "inProgress"}},
980        });
981        let n: Notification = serde_json::from_value(wire).unwrap();
982        assert!(matches!(n, Notification::TurnStarted(_)));
983        assert_eq!(n.turn_id(), Some("turn_42"));
984    }
985
986    #[test]
987    fn test_turn_id_from_delta_field() {
988        // Streaming notifications carry a flat `turnId`.
989        let wire = serde_json::json!({
990            "method": "item/agentMessage/delta",
991            "params": {"threadId": "t1", "turnId": "turn_7", "itemId": "i1", "delta": "hi"},
992        });
993        let n: Notification = serde_json::from_value(wire).unwrap();
994        assert_eq!(n.turn_id(), Some("turn_7"));
995    }
996
997    #[test]
998    fn test_turn_id_none_for_non_turn_notification() {
999        let n = Notification::from_envelope(
1000            "thread/deleted",
1001            Some(serde_json::json!({"threadId": "t1"})),
1002        )
1003        .unwrap();
1004        assert_eq!(n.turn_id(), None);
1005    }
1006
1007    #[test]
1008    fn test_thread_item_accessor() {
1009        let wire = serde_json::json!({
1010            "method": "item/started",
1011            "params": {
1012                "threadId": "t1",
1013                "turnId": "turn_1",
1014                "startedAtMs": 0,
1015                "item": {"type": "agentMessage", "id": "i1", "text": "hi"},
1016            },
1017        });
1018        let n: Notification = serde_json::from_value(wire).unwrap();
1019        assert!(n.thread_item().is_some());
1020        // A non-item notification has no thread item.
1021        let other = Notification::from_envelope(
1022            "thread/deleted",
1023            Some(serde_json::json!({"threadId": "t1"})),
1024        )
1025        .unwrap();
1026        assert!(other.thread_item().is_none());
1027    }
1028
1029    #[test]
1030    fn test_server_message_from_json_str_notification() {
1031        let line = r#"{"method":"turn/started","params":{"threadId":"t1","turn":{"id":"turn_9","status":"inProgress"}}}"#;
1032        let msg = ServerMessage::from_json_str(line).expect("parses a notification frame");
1033        match msg {
1034            ServerMessage::Notification(n) => assert_eq!(n.turn_id(), Some("turn_9")),
1035            other => panic!("expected Notification, got {other:?}"),
1036        }
1037    }
1038
1039    #[test]
1040    fn test_server_message_from_value_request() {
1041        let frame = serde_json::json!({
1042            "id": 7,
1043            "method": "execCommandApproval",
1044            "params": {
1045                "callId": "c1",
1046                "command": ["ls"],
1047                "conversationId": "th_1",
1048                "cwd": "/tmp",
1049                "parsedCmd": [],
1050            },
1051        });
1052        let msg = ServerMessage::from_value(frame).expect("parses a request frame");
1053        match msg {
1054            ServerMessage::Request { id, request } => {
1055                assert_eq!(id, RequestId::Integer(7));
1056                assert!(matches!(request, ServerRequest::ExecCommandApproval(_)));
1057            }
1058            other => panic!("expected Request, got {other:?}"),
1059        }
1060    }
1061
1062    #[test]
1063    fn test_server_message_from_json_str_rejects_response() {
1064        // A response to a client request is not a server-initiated message.
1065        let line = r#"{"id":1,"result":{"threadId":"th_abc"}}"#;
1066        let err = ServerMessage::from_json_str(line).unwrap_err();
1067        assert!(matches!(err, Error::Protocol(_)), "got: {err:?}");
1068    }
1069
1070    #[test]
1071    fn test_server_message_from_json_str_unknown_method_ok() {
1072        let line = r#"{"method":"some/future/notification","params":{"x":1}}"#;
1073        let msg = ServerMessage::from_json_str(line).unwrap();
1074        assert!(msg.is_unknown());
1075    }
1076}