1use 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#[derive(Debug, Clone)]
71pub enum Notification {
72 ThreadStarted(ThreadStartedNotification),
74 ThreadStatusChanged(ThreadStatusChangedNotification),
76 ThreadTokenUsageUpdated(ThreadTokenUsageUpdatedNotification),
78 TurnStarted(TurnStartedNotification),
80 TurnCompleted(TurnCompletedNotification),
82 ItemStarted(ItemStartedNotification),
84 ItemCompleted(ItemCompletedNotification),
86 AgentMessageDelta(AgentMessageDeltaNotification),
88 CmdOutputDelta(CommandExecutionOutputDeltaNotification),
90 FileChangeOutputDelta(FileChangeOutputDeltaNotification),
92 ReasoningDelta(ReasoningSummaryTextDeltaNotification),
94 Error(ErrorNotification),
96 AccountRateLimitsUpdated(AccountRateLimitsUpdatedNotification),
98 McpServerStartupStatusUpdated(McpServerStatusUpdatedNotification),
100 RemoteControlStatusChanged(RemoteControlStatusChangedNotification),
102 McpServerOauthLoginCompleted(McpServerOauthLoginCompletedNotification),
104 FileChangePatchUpdated(FileChangePatchUpdatedNotification),
106 PlanDelta(PlanDeltaNotification),
108 TurnPlanUpdated(TurnPlanUpdatedNotification),
110 TurnDiffUpdated(TurnDiffUpdatedNotification),
112 ReasoningSummaryPartAdded(ReasoningSummaryPartAddedNotification),
114 ReasoningTextDelta(ReasoningTextDeltaNotification),
116 AccountLoginCompleted(AccountLoginCompletedNotification),
118 DeprecationNotice(DeprecationNoticeNotification),
120 GuardianWarning(GuardianWarningNotification),
122 Warning(WarningNotification),
124 ThreadArchived(ThreadArchivedNotification),
126 ThreadClosed(ThreadClosedNotification),
128 ThreadDeleted(ThreadDeletedNotification),
130 ThreadUnarchived(ThreadUnarchivedNotification),
132 ThreadGoalCleared(ThreadGoalClearedNotification),
134 ThreadNameUpdated(ThreadNameUpdatedNotification),
136 SkillsChanged(SkillsChangedNotification),
138 FsChanged(FsChangedNotification),
140 ConfigWarning(ConfigWarningNotification),
142 AccountUpdated(AccountUpdatedNotification),
144 AppListUpdated(AppListUpdatedNotification),
146 CommandExecOutputDelta(CommandExecOutputDeltaNotification),
148 ExternalAgentConfigImportCompleted(ExternalAgentConfigImportCompletedNotification),
150 FuzzyFileSearchSessionCompleted(FuzzyFileSearchSessionCompletedNotification),
152 FuzzyFileSearchSessionUpdated(FuzzyFileSearchSessionUpdatedNotification),
154 HookCompleted(HookCompletedNotification),
156 HookStarted(HookStartedNotification),
158 ItemGuardianApprovalReviewCompleted(ItemGuardianApprovalReviewCompletedNotification),
160 ItemGuardianApprovalReviewStarted(ItemGuardianApprovalReviewStartedNotification),
162 TerminalInteraction(TerminalInteractionNotification),
164 McpToolCallProgress(McpToolCallProgressNotification),
166 ModelRerouted(ModelReroutedNotification),
168 ModelVerification(ModelVerificationNotification),
170 ProcessExited(ProcessExitedNotification),
172 ProcessOutputDelta(ProcessOutputDeltaNotification),
174 ServerRequestResolved(ServerRequestResolvedNotification),
176 ContextCompacted(ContextCompactedNotification),
178 ThreadGoalUpdated(ThreadGoalUpdatedNotification),
180 ThreadRealtimeClosed(ThreadRealtimeClosedNotification),
182 ThreadRealtimeError(ThreadRealtimeErrorNotification),
184 ThreadRealtimeItemAdded(ThreadRealtimeItemAddedNotification),
186 ThreadRealtimeOutputAudioDelta(ThreadRealtimeOutputAudioDeltaNotification),
188 ThreadRealtimeSdp(ThreadRealtimeSdpNotification),
190 ThreadRealtimeStarted(ThreadRealtimeStartedNotification),
192 ThreadRealtimeTranscriptDelta(ThreadRealtimeTranscriptDeltaNotification),
194 ThreadRealtimeTranscriptDone(ThreadRealtimeTranscriptDoneNotification),
196 WindowsWorldWritableWarning(WindowsWorldWritableWarningNotification),
198 WindowsSandboxSetupCompleted(WindowsSandboxSetupCompletedNotification),
200 ThreadSettingsUpdated(ThreadSettingsUpdatedNotification),
202 TurnModerationMetadata(TurnModerationMetadataNotification),
204 ExternalAgentConfigImportProgress(ExternalAgentConfigImportProgressNotification),
206 ModelSafetyBufferingUpdated(ModelSafetyBufferingUpdatedNotification),
208 ThreadEnvironmentConnected(EnvironmentConnectionNotification),
210 ThreadEnvironmentDisconnected(EnvironmentConnectionNotification),
212 StrictReviewRequired(StrictReviewRequiredNotification),
214 ProjectChanged(ProjectChangedNotification),
216 ThreadProjectUpdated(ThreadProjectUpdatedNotification),
218 ThreadQueueChanged(ThreadQueueChangedNotification),
220 ThreadReverted(ThreadRevertedNotification),
222 Unknown {
226 method: String,
227 params: Option<Value>,
228 },
229}
230
231impl Notification {
232 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 pub fn is_unknown(&self) -> bool {
326 matches!(self, Self::Unknown { .. })
327 }
328
329 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 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 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 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#[derive(Debug, Clone)]
765pub enum ServerRequest {
766 CmdExecApproval(CommandExecutionRequestApprovalParams),
768 FileChangeApproval(FileChangeRequestApprovalParams),
770 ToolRequestUserInput(crate::protocol::ToolRequestUserInputParams),
772 McpServerElicitationRequest(crate::protocol::McpServerElicitationRequestParams),
774 PermissionsRequestApproval(crate::protocol::PermissionsRequestApprovalParams),
776 ItemToolCall(crate::protocol::DynamicToolCallParams),
778 ChatgptAuthTokensRefresh(crate::protocol::ChatgptAuthTokensRefreshParams),
780 AttestationGenerate(crate::protocol::AttestationGenerateParams),
782 ApplyPatchApproval(crate::protocol::ApplyPatchApprovalParams),
784 ExecCommandApproval(crate::protocol::ExecCommandApprovalParams),
786 Unknown {
788 method: String,
789 params: Option<Value>,
790 },
791}
792
793impl ServerRequest {
794 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 pub fn is_unknown(&self) -> bool {
813 matches!(self, Self::Unknown { .. })
814 }
815
816 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#[allow(clippy::large_enum_variant)]
865#[derive(Debug, Clone)]
866pub enum ServerMessage {
867 Notification(Notification),
869 Request {
871 id: RequestId,
872 request: ServerRequest,
873 },
874}
875
876impl ServerMessage {
877 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 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 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 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 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 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 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 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}