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 McpServerEventStreamNotification, McpServerOauthLoginCompletedNotification,
42 McpServerStatusUpdatedNotification, 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 ThreadRealtimeItemCompletedNotification, ThreadRealtimeItemStartedNotification,
54 ThreadRealtimeItemTranscriptDeltaNotification, ThreadRealtimeOutputAudioDeltaNotification,
55 ThreadRealtimeSdpNotification, ThreadRealtimeStartedNotification,
56 ThreadRealtimeTranscriptDeltaNotification, ThreadRealtimeTranscriptDoneNotification,
57 ThreadRevertedNotification, ThreadSettingsUpdatedNotification, ThreadStartedNotification,
58 ThreadStatusChangedNotification, ThreadTokenUsageUpdatedNotification,
59 ThreadUnarchivedNotification, TurnCompletedNotification, TurnDiffUpdatedNotification,
60 TurnModerationMetadataNotification, TurnPlanUpdatedNotification, TurnStartedNotification,
61 WarningNotification, WindowsSandboxSetupCompletedNotification,
62 WindowsWorldWritableWarningNotification,
63};
64use serde::{Deserialize, Deserializer, Serialize, Serializer};
65use serde_json::Value;
66
67#[derive(Debug, Clone)]
73pub enum Notification {
74 ThreadStarted(ThreadStartedNotification),
76 ThreadStatusChanged(ThreadStatusChangedNotification),
78 ThreadTokenUsageUpdated(ThreadTokenUsageUpdatedNotification),
80 TurnStarted(TurnStartedNotification),
82 TurnCompleted(TurnCompletedNotification),
84 ItemStarted(ItemStartedNotification),
86 ItemCompleted(ItemCompletedNotification),
88 AgentMessageDelta(AgentMessageDeltaNotification),
90 CmdOutputDelta(CommandExecutionOutputDeltaNotification),
92 FileChangeOutputDelta(FileChangeOutputDeltaNotification),
94 ReasoningDelta(ReasoningSummaryTextDeltaNotification),
96 Error(ErrorNotification),
98 AccountRateLimitsUpdated(AccountRateLimitsUpdatedNotification),
100 McpServerStartupStatusUpdated(McpServerStatusUpdatedNotification),
102 RemoteControlStatusChanged(RemoteControlStatusChangedNotification),
104 McpServerOauthLoginCompleted(McpServerOauthLoginCompletedNotification),
106 FileChangePatchUpdated(FileChangePatchUpdatedNotification),
108 PlanDelta(PlanDeltaNotification),
110 TurnPlanUpdated(TurnPlanUpdatedNotification),
112 TurnDiffUpdated(TurnDiffUpdatedNotification),
114 ReasoningSummaryPartAdded(ReasoningSummaryPartAddedNotification),
116 ReasoningTextDelta(ReasoningTextDeltaNotification),
118 AccountLoginCompleted(AccountLoginCompletedNotification),
120 DeprecationNotice(DeprecationNoticeNotification),
122 GuardianWarning(GuardianWarningNotification),
124 Warning(WarningNotification),
126 ThreadArchived(ThreadArchivedNotification),
128 ThreadClosed(ThreadClosedNotification),
130 ThreadDeleted(ThreadDeletedNotification),
132 ThreadUnarchived(ThreadUnarchivedNotification),
134 ThreadGoalCleared(ThreadGoalClearedNotification),
136 ThreadNameUpdated(ThreadNameUpdatedNotification),
138 SkillsChanged(SkillsChangedNotification),
140 FsChanged(FsChangedNotification),
142 ConfigWarning(ConfigWarningNotification),
144 AccountUpdated(AccountUpdatedNotification),
146 AppListUpdated(AppListUpdatedNotification),
148 CommandExecOutputDelta(CommandExecOutputDeltaNotification),
150 ExternalAgentConfigImportCompleted(ExternalAgentConfigImportCompletedNotification),
152 FuzzyFileSearchSessionCompleted(FuzzyFileSearchSessionCompletedNotification),
154 FuzzyFileSearchSessionUpdated(FuzzyFileSearchSessionUpdatedNotification),
156 HookCompleted(HookCompletedNotification),
158 HookStarted(HookStartedNotification),
160 ItemGuardianApprovalReviewCompleted(ItemGuardianApprovalReviewCompletedNotification),
162 ItemGuardianApprovalReviewStarted(ItemGuardianApprovalReviewStartedNotification),
164 TerminalInteraction(TerminalInteractionNotification),
166 McpToolCallProgress(McpToolCallProgressNotification),
168 ModelRerouted(ModelReroutedNotification),
170 ModelVerification(ModelVerificationNotification),
172 ProcessExited(ProcessExitedNotification),
174 ProcessOutputDelta(ProcessOutputDeltaNotification),
176 ServerRequestResolved(ServerRequestResolvedNotification),
178 ContextCompacted(ContextCompactedNotification),
180 ThreadGoalUpdated(ThreadGoalUpdatedNotification),
182 ThreadRealtimeClosed(ThreadRealtimeClosedNotification),
184 ThreadRealtimeError(ThreadRealtimeErrorNotification),
186 ThreadRealtimeItemAdded(ThreadRealtimeItemAddedNotification),
188 ThreadRealtimeOutputAudioDelta(ThreadRealtimeOutputAudioDeltaNotification),
190 ThreadRealtimeSdp(ThreadRealtimeSdpNotification),
192 ThreadRealtimeStarted(ThreadRealtimeStartedNotification),
194 ThreadRealtimeTranscriptDelta(ThreadRealtimeTranscriptDeltaNotification),
196 ThreadRealtimeTranscriptDone(ThreadRealtimeTranscriptDoneNotification),
198 WindowsWorldWritableWarning(WindowsWorldWritableWarningNotification),
200 WindowsSandboxSetupCompleted(WindowsSandboxSetupCompletedNotification),
202 ThreadSettingsUpdated(ThreadSettingsUpdatedNotification),
204 TurnModerationMetadata(TurnModerationMetadataNotification),
206 ExternalAgentConfigImportProgress(ExternalAgentConfigImportProgressNotification),
208 ModelSafetyBufferingUpdated(ModelSafetyBufferingUpdatedNotification),
210 ThreadEnvironmentConnected(EnvironmentConnectionNotification),
212 ThreadEnvironmentDisconnected(EnvironmentConnectionNotification),
214 StrictReviewRequired(StrictReviewRequiredNotification),
216 ProjectChanged(ProjectChangedNotification),
218 ThreadProjectUpdated(ThreadProjectUpdatedNotification),
220 ThreadQueueChanged(ThreadQueueChangedNotification),
222 ThreadReverted(ThreadRevertedNotification),
224 McpServerEventStream(McpServerEventStreamNotification),
226 ThreadRealtimeItemStarted(ThreadRealtimeItemStartedNotification),
228 ThreadRealtimeItemCompleted(ThreadRealtimeItemCompletedNotification),
230 ThreadRealtimeItemTranscriptDelta(ThreadRealtimeItemTranscriptDeltaNotification),
232 Unknown {
236 method: String,
237 params: Option<Value>,
238 },
239}
240
241impl Notification {
242 pub fn method(&self) -> &str {
244 match self {
245 Self::ThreadStarted(_) => methods::THREAD_STARTED,
246 Self::StrictReviewRequired(_) => methods::STRICT_REVIEW_REQUIRED,
247 Self::ProjectChanged(_) => methods::PROJECT_CHANGED,
248 Self::ThreadProjectUpdated(_) => methods::THREAD_PROJECT_UPDATED,
249 Self::ThreadQueueChanged(_) => methods::THREAD_QUEUE_CHANGED,
250 Self::ThreadReverted(_) => methods::THREAD_REVERTED,
251 Self::McpServerEventStream(_) => methods::MCP_SERVER_EVENT_STREAM,
252 Self::ThreadRealtimeItemStarted(_) => methods::THREAD_REALTIME_ITEM_STARTED,
253 Self::ThreadRealtimeItemCompleted(_) => methods::THREAD_REALTIME_ITEM_COMPLETED,
254 Self::ThreadRealtimeItemTranscriptDelta(_) => {
255 methods::THREAD_REALTIME_ITEM_TRANSCRIPT_DELTA
256 }
257 Self::ThreadStatusChanged(_) => methods::THREAD_STATUS_CHANGED,
258 Self::ThreadTokenUsageUpdated(_) => methods::THREAD_TOKEN_USAGE_UPDATED,
259 Self::TurnStarted(_) => methods::TURN_STARTED,
260 Self::TurnCompleted(_) => methods::TURN_COMPLETED,
261 Self::ItemStarted(_) => methods::ITEM_STARTED,
262 Self::ItemCompleted(_) => methods::ITEM_COMPLETED,
263 Self::AgentMessageDelta(_) => methods::AGENT_MESSAGE_DELTA,
264 Self::CmdOutputDelta(_) => methods::CMD_OUTPUT_DELTA,
265 Self::FileChangeOutputDelta(_) => methods::FILE_CHANGE_OUTPUT_DELTA,
266 Self::ReasoningDelta(_) => methods::REASONING_DELTA,
267 Self::Error(_) => methods::ERROR,
268 Self::AccountRateLimitsUpdated(_) => methods::ACCOUNT_RATE_LIMITS_UPDATED,
269 Self::McpServerStartupStatusUpdated(_) => methods::MCP_SERVER_STARTUP_STATUS_UPDATED,
270 Self::RemoteControlStatusChanged(_) => methods::REMOTE_CONTROL_STATUS_CHANGED,
271 Self::McpServerOauthLoginCompleted(_) => methods::MCP_SERVER_OAUTH_LOGIN_COMPLETED,
272 Self::FileChangePatchUpdated(_) => methods::FILE_CHANGE_PATCH_UPDATED,
273 Self::PlanDelta(_) => methods::PLAN_DELTA,
274 Self::TurnPlanUpdated(_) => methods::TURN_PLAN_UPDATED,
275 Self::TurnDiffUpdated(_) => methods::TURN_DIFF_UPDATED,
276 Self::ReasoningSummaryPartAdded(_) => methods::REASONING_SUMMARY_PART_ADDED,
277 Self::ReasoningTextDelta(_) => methods::REASONING_TEXT_DELTA,
278 Self::AccountLoginCompleted(_) => methods::ACCOUNT_LOGIN_COMPLETED,
279 Self::DeprecationNotice(_) => methods::DEPRECATION_NOTICE,
280 Self::GuardianWarning(_) => methods::GUARDIAN_WARNING,
281 Self::Warning(_) => methods::WARNING,
282 Self::ThreadArchived(_) => methods::THREAD_ARCHIVED,
283 Self::ThreadClosed(_) => methods::THREAD_CLOSED,
284 Self::ThreadDeleted(_) => methods::THREAD_DELETED,
285 Self::ThreadUnarchived(_) => methods::THREAD_UNARCHIVED,
286 Self::ThreadGoalCleared(_) => methods::THREAD_GOAL_CLEARED,
287 Self::ThreadNameUpdated(_) => methods::THREAD_NAME_UPDATED,
288 Self::SkillsChanged(_) => methods::SKILLS_CHANGED,
289 Self::FsChanged(_) => methods::FS_CHANGED,
290 Self::ConfigWarning(_) => methods::CONFIG_WARNING,
291 Self::AccountUpdated(_) => methods::ACCOUNT_UPDATED,
292 Self::AppListUpdated(_) => methods::APP_LIST_UPDATED,
293 Self::CommandExecOutputDelta(_) => methods::COMMAND_EXEC_OUTPUT_DELTA,
294 Self::ExternalAgentConfigImportCompleted(_) => {
295 methods::EXTERNAL_AGENT_CONFIG_IMPORT_COMPLETED
296 }
297 Self::FuzzyFileSearchSessionCompleted(_) => {
298 methods::FUZZY_FILE_SEARCH_SESSION_COMPLETED
299 }
300 Self::FuzzyFileSearchSessionUpdated(_) => methods::FUZZY_FILE_SEARCH_SESSION_UPDATED,
301 Self::HookCompleted(_) => methods::HOOK_COMPLETED,
302 Self::HookStarted(_) => methods::HOOK_STARTED,
303 Self::ItemGuardianApprovalReviewCompleted(_) => {
304 methods::ITEM_AUTO_APPROVAL_REVIEW_COMPLETED
305 }
306 Self::ItemGuardianApprovalReviewStarted(_) => {
307 methods::ITEM_AUTO_APPROVAL_REVIEW_STARTED
308 }
309 Self::TerminalInteraction(_) => methods::ITEM_COMMAND_EXEC_TERMINAL_INTERACTION,
310 Self::McpToolCallProgress(_) => methods::ITEM_MCP_TOOL_CALL_PROGRESS,
311 Self::ModelRerouted(_) => methods::MODEL_REROUTED,
312 Self::ModelVerification(_) => methods::MODEL_VERIFICATION,
313 Self::ProcessExited(_) => methods::PROCESS_EXITED,
314 Self::ProcessOutputDelta(_) => methods::PROCESS_OUTPUT_DELTA,
315 Self::ServerRequestResolved(_) => methods::SERVER_REQUEST_RESOLVED,
316 Self::ContextCompacted(_) => methods::THREAD_COMPACTED,
317 Self::ThreadGoalUpdated(_) => methods::THREAD_GOAL_UPDATED,
318 Self::ThreadRealtimeClosed(_) => methods::THREAD_REALTIME_CLOSED,
319 Self::ThreadRealtimeError(_) => methods::THREAD_REALTIME_ERROR,
320 Self::ThreadRealtimeItemAdded(_) => methods::THREAD_REALTIME_ITEM_ADDED,
321 Self::ThreadRealtimeOutputAudioDelta(_) => methods::THREAD_REALTIME_OUTPUT_AUDIO_DELTA,
322 Self::ThreadRealtimeSdp(_) => methods::THREAD_REALTIME_SDP,
323 Self::ThreadRealtimeStarted(_) => methods::THREAD_REALTIME_STARTED,
324 Self::ThreadRealtimeTranscriptDelta(_) => methods::THREAD_REALTIME_TRANSCRIPT_DELTA,
325 Self::ThreadRealtimeTranscriptDone(_) => methods::THREAD_REALTIME_TRANSCRIPT_DONE,
326 Self::WindowsWorldWritableWarning(_) => methods::WINDOWS_WORLD_WRITABLE_WARNING,
327 Self::WindowsSandboxSetupCompleted(_) => methods::WINDOWS_SANDBOX_SETUP_COMPLETED,
328 Self::ThreadSettingsUpdated(_) => methods::THREAD_SETTINGS_UPDATED,
329 Self::TurnModerationMetadata(_) => methods::TURN_MODERATION_METADATA,
330 Self::ExternalAgentConfigImportProgress(_) => {
331 methods::EXTERNAL_AGENT_CONFIG_IMPORT_PROGRESS
332 }
333 Self::ModelSafetyBufferingUpdated(_) => methods::MODEL_SAFETY_BUFFERING_UPDATED,
334 Self::ThreadEnvironmentConnected(_) => methods::THREAD_ENVIRONMENT_CONNECTED,
335 Self::ThreadEnvironmentDisconnected(_) => methods::THREAD_ENVIRONMENT_DISCONNECTED,
336 Self::Unknown { method, .. } => method,
337 }
338 }
339
340 pub fn is_unknown(&self) -> bool {
342 matches!(self, Self::Unknown { .. })
343 }
344
345 pub fn turn_id(&self) -> Option<&str> {
357 let id = match self {
358 Self::TurnStarted(n) => n.turn.id.as_str(),
359 Self::TurnCompleted(n) => n.turn.id.as_str(),
360 Self::AgentMessageDelta(n) => n.turn_id.as_str(),
361 Self::CmdOutputDelta(n) => n.turn_id.as_str(),
362 Self::FileChangeOutputDelta(n) => n.turn_id.as_str(),
363 Self::ReasoningDelta(n) => n.turn_id.as_str(),
364 Self::Error(n) => n.turn_id.as_str(),
365 Self::FileChangePatchUpdated(n) => n.turn_id.as_str(),
366 Self::PlanDelta(n) => n.turn_id.as_str(),
367 Self::TurnPlanUpdated(n) => n.turn_id.as_str(),
368 Self::TurnDiffUpdated(n) => n.turn_id.as_str(),
369 Self::TurnModerationMetadata(n) => n.turn_id.as_str(),
370 Self::ReasoningSummaryPartAdded(n) => n.turn_id.as_str(),
371 Self::ReasoningTextDelta(n) => n.turn_id.as_str(),
372 Self::ItemStarted(n) => n.turn_id.as_str(),
373 Self::ItemCompleted(n) => n.turn_id.as_str(),
374 Self::ContextCompacted(n) => n.turn_id.as_str(),
375 Self::McpToolCallProgress(n) => n.turn_id.as_str(),
376 Self::ModelRerouted(n) => n.turn_id.as_str(),
377 Self::ModelVerification(n) => n.turn_id.as_str(),
378 Self::ModelSafetyBufferingUpdated(n) => n.turn_id.as_str(),
379 Self::TerminalInteraction(n) => n.turn_id.as_str(),
380 Self::ThreadTokenUsageUpdated(n) => n.turn_id.as_str(),
381 Self::ItemGuardianApprovalReviewStarted(n) => n.turn_id.as_str(),
382 Self::ItemGuardianApprovalReviewCompleted(n) => n.turn_id.as_str(),
383 _ => return None,
384 };
385 (!id.is_empty()).then_some(id)
386 }
387
388 pub fn thread_item(&self) -> Option<&crate::protocol::ThreadItem> {
394 match self {
395 Self::ItemStarted(n) => Some(&n.item),
396 Self::ItemCompleted(n) => Some(&n.item),
397 _ => None,
398 }
399 }
400
401 pub fn from_envelope(method: &str, params: Option<Value>) -> Result<Self, serde_json::Error> {
407 let params_value = params.clone().unwrap_or(Value::Null);
408 match method {
409 methods::THREAD_STARTED => {
410 serde_json::from_value(params_value).map(Self::ThreadStarted)
411 }
412 methods::STRICT_REVIEW_REQUIRED => {
413 serde_json::from_value(params_value).map(Self::StrictReviewRequired)
414 }
415 methods::PROJECT_CHANGED => {
416 serde_json::from_value(params_value).map(Self::ProjectChanged)
417 }
418 methods::THREAD_PROJECT_UPDATED => {
419 serde_json::from_value(params_value).map(Self::ThreadProjectUpdated)
420 }
421 methods::THREAD_QUEUE_CHANGED => {
422 serde_json::from_value(params_value).map(Self::ThreadQueueChanged)
423 }
424 methods::THREAD_REVERTED => {
425 serde_json::from_value(params_value).map(Self::ThreadReverted)
426 }
427 methods::MCP_SERVER_EVENT_STREAM => {
428 serde_json::from_value(params_value).map(Self::McpServerEventStream)
429 }
430 methods::THREAD_REALTIME_ITEM_STARTED => {
431 serde_json::from_value(params_value).map(Self::ThreadRealtimeItemStarted)
432 }
433 methods::THREAD_REALTIME_ITEM_COMPLETED => {
434 serde_json::from_value(params_value).map(Self::ThreadRealtimeItemCompleted)
435 }
436 methods::THREAD_REALTIME_ITEM_TRANSCRIPT_DELTA => {
437 serde_json::from_value(params_value).map(Self::ThreadRealtimeItemTranscriptDelta)
438 }
439 methods::THREAD_STATUS_CHANGED => {
440 serde_json::from_value(params_value).map(Self::ThreadStatusChanged)
441 }
442 methods::THREAD_TOKEN_USAGE_UPDATED => {
443 serde_json::from_value(params_value).map(Self::ThreadTokenUsageUpdated)
444 }
445 methods::TURN_STARTED => serde_json::from_value(params_value).map(Self::TurnStarted),
446 methods::TURN_COMPLETED => {
447 serde_json::from_value(params_value).map(Self::TurnCompleted)
448 }
449 methods::ITEM_STARTED => serde_json::from_value(params_value).map(Self::ItemStarted),
450 methods::ITEM_COMPLETED => {
451 serde_json::from_value(params_value).map(Self::ItemCompleted)
452 }
453 methods::AGENT_MESSAGE_DELTA => {
454 serde_json::from_value(params_value).map(Self::AgentMessageDelta)
455 }
456 methods::CMD_OUTPUT_DELTA => {
457 serde_json::from_value(params_value).map(Self::CmdOutputDelta)
458 }
459 methods::FILE_CHANGE_OUTPUT_DELTA => {
460 serde_json::from_value(params_value).map(Self::FileChangeOutputDelta)
461 }
462 methods::REASONING_DELTA => {
463 serde_json::from_value(params_value).map(Self::ReasoningDelta)
464 }
465 methods::ERROR => serde_json::from_value(params_value).map(Self::Error),
466 methods::ACCOUNT_RATE_LIMITS_UPDATED => {
467 serde_json::from_value(params_value).map(Self::AccountRateLimitsUpdated)
468 }
469 methods::MCP_SERVER_STARTUP_STATUS_UPDATED => {
470 serde_json::from_value(params_value).map(Self::McpServerStartupStatusUpdated)
471 }
472 methods::REMOTE_CONTROL_STATUS_CHANGED => {
473 serde_json::from_value(params_value).map(Self::RemoteControlStatusChanged)
474 }
475 methods::MCP_SERVER_OAUTH_LOGIN_COMPLETED => {
476 serde_json::from_value(params_value).map(Self::McpServerOauthLoginCompleted)
477 }
478 methods::FILE_CHANGE_PATCH_UPDATED => {
479 serde_json::from_value(params_value).map(Self::FileChangePatchUpdated)
480 }
481 methods::PLAN_DELTA => serde_json::from_value(params_value).map(Self::PlanDelta),
482 methods::TURN_PLAN_UPDATED => {
483 serde_json::from_value(params_value).map(Self::TurnPlanUpdated)
484 }
485 methods::TURN_DIFF_UPDATED => {
486 serde_json::from_value(params_value).map(Self::TurnDiffUpdated)
487 }
488 methods::REASONING_SUMMARY_PART_ADDED => {
489 serde_json::from_value(params_value).map(Self::ReasoningSummaryPartAdded)
490 }
491 methods::REASONING_TEXT_DELTA => {
492 serde_json::from_value(params_value).map(Self::ReasoningTextDelta)
493 }
494 methods::ACCOUNT_LOGIN_COMPLETED => {
495 serde_json::from_value(params_value).map(Self::AccountLoginCompleted)
496 }
497 methods::DEPRECATION_NOTICE => {
498 serde_json::from_value(params_value).map(Self::DeprecationNotice)
499 }
500 methods::GUARDIAN_WARNING => {
501 serde_json::from_value(params_value).map(Self::GuardianWarning)
502 }
503 methods::WARNING => serde_json::from_value(params_value).map(Self::Warning),
504 methods::THREAD_ARCHIVED => {
505 serde_json::from_value(params_value).map(Self::ThreadArchived)
506 }
507 methods::THREAD_CLOSED => serde_json::from_value(params_value).map(Self::ThreadClosed),
508 methods::THREAD_DELETED => {
509 serde_json::from_value(params_value).map(Self::ThreadDeleted)
510 }
511 methods::THREAD_UNARCHIVED => {
512 serde_json::from_value(params_value).map(Self::ThreadUnarchived)
513 }
514 methods::THREAD_GOAL_CLEARED => {
515 serde_json::from_value(params_value).map(Self::ThreadGoalCleared)
516 }
517 methods::THREAD_NAME_UPDATED => {
518 serde_json::from_value(params_value).map(Self::ThreadNameUpdated)
519 }
520 methods::SKILLS_CHANGED => {
521 serde_json::from_value(params_value).map(Self::SkillsChanged)
522 }
523 methods::FS_CHANGED => serde_json::from_value(params_value).map(Self::FsChanged),
524 methods::CONFIG_WARNING => {
525 serde_json::from_value(params_value).map(Self::ConfigWarning)
526 }
527 methods::ACCOUNT_UPDATED => {
528 serde_json::from_value(params_value).map(Self::AccountUpdated)
529 }
530 methods::APP_LIST_UPDATED => {
531 serde_json::from_value(params_value).map(Self::AppListUpdated)
532 }
533 methods::COMMAND_EXEC_OUTPUT_DELTA => {
534 serde_json::from_value(params_value).map(Self::CommandExecOutputDelta)
535 }
536 methods::EXTERNAL_AGENT_CONFIG_IMPORT_COMPLETED => {
537 serde_json::from_value(params_value).map(Self::ExternalAgentConfigImportCompleted)
538 }
539 methods::FUZZY_FILE_SEARCH_SESSION_COMPLETED => {
540 serde_json::from_value(params_value).map(Self::FuzzyFileSearchSessionCompleted)
541 }
542 methods::FUZZY_FILE_SEARCH_SESSION_UPDATED => {
543 serde_json::from_value(params_value).map(Self::FuzzyFileSearchSessionUpdated)
544 }
545 methods::HOOK_COMPLETED => {
546 serde_json::from_value(params_value).map(Self::HookCompleted)
547 }
548 methods::HOOK_STARTED => serde_json::from_value(params_value).map(Self::HookStarted),
549 methods::ITEM_AUTO_APPROVAL_REVIEW_COMPLETED => {
550 serde_json::from_value(params_value).map(Self::ItemGuardianApprovalReviewCompleted)
551 }
552 methods::ITEM_AUTO_APPROVAL_REVIEW_STARTED => {
553 serde_json::from_value(params_value).map(Self::ItemGuardianApprovalReviewStarted)
554 }
555 methods::ITEM_COMMAND_EXEC_TERMINAL_INTERACTION => {
556 serde_json::from_value(params_value).map(Self::TerminalInteraction)
557 }
558 methods::ITEM_MCP_TOOL_CALL_PROGRESS => {
559 serde_json::from_value(params_value).map(Self::McpToolCallProgress)
560 }
561 methods::MODEL_REROUTED => {
562 serde_json::from_value(params_value).map(Self::ModelRerouted)
563 }
564 methods::MODEL_VERIFICATION => {
565 serde_json::from_value(params_value).map(Self::ModelVerification)
566 }
567 methods::PROCESS_EXITED => {
568 serde_json::from_value(params_value).map(Self::ProcessExited)
569 }
570 methods::PROCESS_OUTPUT_DELTA => {
571 serde_json::from_value(params_value).map(Self::ProcessOutputDelta)
572 }
573 methods::SERVER_REQUEST_RESOLVED => {
574 serde_json::from_value(params_value).map(Self::ServerRequestResolved)
575 }
576 methods::THREAD_COMPACTED => {
577 serde_json::from_value(params_value).map(Self::ContextCompacted)
578 }
579 methods::THREAD_GOAL_UPDATED => {
580 serde_json::from_value(params_value).map(Self::ThreadGoalUpdated)
581 }
582 methods::THREAD_REALTIME_CLOSED => {
583 serde_json::from_value(params_value).map(Self::ThreadRealtimeClosed)
584 }
585 methods::THREAD_REALTIME_ERROR => {
586 serde_json::from_value(params_value).map(Self::ThreadRealtimeError)
587 }
588 methods::THREAD_REALTIME_ITEM_ADDED => {
589 serde_json::from_value(params_value).map(Self::ThreadRealtimeItemAdded)
590 }
591 methods::THREAD_REALTIME_OUTPUT_AUDIO_DELTA => {
592 serde_json::from_value(params_value).map(Self::ThreadRealtimeOutputAudioDelta)
593 }
594 methods::THREAD_REALTIME_SDP => {
595 serde_json::from_value(params_value).map(Self::ThreadRealtimeSdp)
596 }
597 methods::THREAD_REALTIME_STARTED => {
598 serde_json::from_value(params_value).map(Self::ThreadRealtimeStarted)
599 }
600 methods::THREAD_REALTIME_TRANSCRIPT_DELTA => {
601 serde_json::from_value(params_value).map(Self::ThreadRealtimeTranscriptDelta)
602 }
603 methods::THREAD_REALTIME_TRANSCRIPT_DONE => {
604 serde_json::from_value(params_value).map(Self::ThreadRealtimeTranscriptDone)
605 }
606 methods::WINDOWS_WORLD_WRITABLE_WARNING => {
607 serde_json::from_value(params_value).map(Self::WindowsWorldWritableWarning)
608 }
609 methods::WINDOWS_SANDBOX_SETUP_COMPLETED => {
610 serde_json::from_value(params_value).map(Self::WindowsSandboxSetupCompleted)
611 }
612 methods::THREAD_SETTINGS_UPDATED => {
613 serde_json::from_value(params_value).map(Self::ThreadSettingsUpdated)
614 }
615 methods::TURN_MODERATION_METADATA => {
616 serde_json::from_value(params_value).map(Self::TurnModerationMetadata)
617 }
618 methods::EXTERNAL_AGENT_CONFIG_IMPORT_PROGRESS => {
619 serde_json::from_value(params_value).map(Self::ExternalAgentConfigImportProgress)
620 }
621 methods::MODEL_SAFETY_BUFFERING_UPDATED => {
622 serde_json::from_value(params_value).map(Self::ModelSafetyBufferingUpdated)
623 }
624 methods::THREAD_ENVIRONMENT_CONNECTED => {
625 serde_json::from_value(params_value).map(Self::ThreadEnvironmentConnected)
626 }
627 methods::THREAD_ENVIRONMENT_DISCONNECTED => {
628 serde_json::from_value(params_value).map(Self::ThreadEnvironmentDisconnected)
629 }
630 _ => Ok(Self::Unknown {
631 method: method.to_string(),
632 params,
633 }),
634 }
635 }
636
637 pub fn into_envelope(self) -> Result<(String, Option<Value>), serde_json::Error> {
639 fn pack<T: Serialize>(
640 method: &str,
641 v: &T,
642 ) -> Result<(String, Option<Value>), serde_json::Error> {
643 Ok((method.to_string(), Some(serde_json::to_value(v)?)))
644 }
645 match &self {
646 Self::ThreadStarted(v) => pack(methods::THREAD_STARTED, v),
647 Self::StrictReviewRequired(v) => pack(methods::STRICT_REVIEW_REQUIRED, v),
648 Self::ProjectChanged(v) => pack(methods::PROJECT_CHANGED, v),
649 Self::ThreadProjectUpdated(v) => pack(methods::THREAD_PROJECT_UPDATED, v),
650 Self::ThreadQueueChanged(v) => pack(methods::THREAD_QUEUE_CHANGED, v),
651 Self::ThreadReverted(v) => pack(methods::THREAD_REVERTED, v),
652 Self::McpServerEventStream(v) => pack(methods::MCP_SERVER_EVENT_STREAM, v),
653 Self::ThreadRealtimeItemStarted(v) => pack(methods::THREAD_REALTIME_ITEM_STARTED, v),
654 Self::ThreadRealtimeItemCompleted(v) => {
655 pack(methods::THREAD_REALTIME_ITEM_COMPLETED, v)
656 }
657 Self::ThreadRealtimeItemTranscriptDelta(v) => {
658 pack(methods::THREAD_REALTIME_ITEM_TRANSCRIPT_DELTA, v)
659 }
660 Self::ThreadStatusChanged(v) => pack(methods::THREAD_STATUS_CHANGED, v),
661 Self::ThreadTokenUsageUpdated(v) => pack(methods::THREAD_TOKEN_USAGE_UPDATED, v),
662 Self::TurnStarted(v) => pack(methods::TURN_STARTED, v),
663 Self::TurnCompleted(v) => pack(methods::TURN_COMPLETED, v),
664 Self::ItemStarted(v) => pack(methods::ITEM_STARTED, v),
665 Self::ItemCompleted(v) => pack(methods::ITEM_COMPLETED, v),
666 Self::AgentMessageDelta(v) => pack(methods::AGENT_MESSAGE_DELTA, v),
667 Self::CmdOutputDelta(v) => pack(methods::CMD_OUTPUT_DELTA, v),
668 Self::FileChangeOutputDelta(v) => pack(methods::FILE_CHANGE_OUTPUT_DELTA, v),
669 Self::ReasoningDelta(v) => pack(methods::REASONING_DELTA, v),
670 Self::Error(v) => pack(methods::ERROR, v),
671 Self::AccountRateLimitsUpdated(v) => pack(methods::ACCOUNT_RATE_LIMITS_UPDATED, v),
672 Self::McpServerStartupStatusUpdated(v) => {
673 pack(methods::MCP_SERVER_STARTUP_STATUS_UPDATED, v)
674 }
675 Self::RemoteControlStatusChanged(v) => pack(methods::REMOTE_CONTROL_STATUS_CHANGED, v),
676 Self::McpServerOauthLoginCompleted(v) => {
677 pack(methods::MCP_SERVER_OAUTH_LOGIN_COMPLETED, v)
678 }
679 Self::FileChangePatchUpdated(v) => pack(methods::FILE_CHANGE_PATCH_UPDATED, v),
680 Self::PlanDelta(v) => pack(methods::PLAN_DELTA, v),
681 Self::TurnPlanUpdated(v) => pack(methods::TURN_PLAN_UPDATED, v),
682 Self::TurnDiffUpdated(v) => pack(methods::TURN_DIFF_UPDATED, v),
683 Self::ReasoningSummaryPartAdded(v) => pack(methods::REASONING_SUMMARY_PART_ADDED, v),
684 Self::ReasoningTextDelta(v) => pack(methods::REASONING_TEXT_DELTA, v),
685 Self::AccountLoginCompleted(v) => pack(methods::ACCOUNT_LOGIN_COMPLETED, v),
686 Self::DeprecationNotice(v) => pack(methods::DEPRECATION_NOTICE, v),
687 Self::GuardianWarning(v) => pack(methods::GUARDIAN_WARNING, v),
688 Self::Warning(v) => pack(methods::WARNING, v),
689 Self::ThreadArchived(v) => pack(methods::THREAD_ARCHIVED, v),
690 Self::ThreadClosed(v) => pack(methods::THREAD_CLOSED, v),
691 Self::ThreadDeleted(v) => pack(methods::THREAD_DELETED, v),
692 Self::ThreadUnarchived(v) => pack(methods::THREAD_UNARCHIVED, v),
693 Self::ThreadGoalCleared(v) => pack(methods::THREAD_GOAL_CLEARED, v),
694 Self::ThreadNameUpdated(v) => pack(methods::THREAD_NAME_UPDATED, v),
695 Self::SkillsChanged(v) => pack(methods::SKILLS_CHANGED, v),
696 Self::FsChanged(v) => pack(methods::FS_CHANGED, v),
697 Self::ConfigWarning(v) => pack(methods::CONFIG_WARNING, v),
698 Self::AccountUpdated(v) => pack(methods::ACCOUNT_UPDATED, v),
699 Self::AppListUpdated(v) => pack(methods::APP_LIST_UPDATED, v),
700 Self::CommandExecOutputDelta(v) => pack(methods::COMMAND_EXEC_OUTPUT_DELTA, v),
701 Self::ExternalAgentConfigImportCompleted(v) => {
702 pack(methods::EXTERNAL_AGENT_CONFIG_IMPORT_COMPLETED, v)
703 }
704 Self::FuzzyFileSearchSessionCompleted(v) => {
705 pack(methods::FUZZY_FILE_SEARCH_SESSION_COMPLETED, v)
706 }
707 Self::FuzzyFileSearchSessionUpdated(v) => {
708 pack(methods::FUZZY_FILE_SEARCH_SESSION_UPDATED, v)
709 }
710 Self::HookCompleted(v) => pack(methods::HOOK_COMPLETED, v),
711 Self::HookStarted(v) => pack(methods::HOOK_STARTED, v),
712 Self::ItemGuardianApprovalReviewCompleted(v) => {
713 pack(methods::ITEM_AUTO_APPROVAL_REVIEW_COMPLETED, v)
714 }
715 Self::ItemGuardianApprovalReviewStarted(v) => {
716 pack(methods::ITEM_AUTO_APPROVAL_REVIEW_STARTED, v)
717 }
718 Self::TerminalInteraction(v) => {
719 pack(methods::ITEM_COMMAND_EXEC_TERMINAL_INTERACTION, v)
720 }
721 Self::McpToolCallProgress(v) => pack(methods::ITEM_MCP_TOOL_CALL_PROGRESS, v),
722 Self::ModelRerouted(v) => pack(methods::MODEL_REROUTED, v),
723 Self::ModelVerification(v) => pack(methods::MODEL_VERIFICATION, v),
724 Self::ProcessExited(v) => pack(methods::PROCESS_EXITED, v),
725 Self::ProcessOutputDelta(v) => pack(methods::PROCESS_OUTPUT_DELTA, v),
726 Self::ServerRequestResolved(v) => pack(methods::SERVER_REQUEST_RESOLVED, v),
727 Self::ContextCompacted(v) => pack(methods::THREAD_COMPACTED, v),
728 Self::ThreadGoalUpdated(v) => pack(methods::THREAD_GOAL_UPDATED, v),
729 Self::ThreadRealtimeClosed(v) => pack(methods::THREAD_REALTIME_CLOSED, v),
730 Self::ThreadRealtimeError(v) => pack(methods::THREAD_REALTIME_ERROR, v),
731 Self::ThreadRealtimeItemAdded(v) => pack(methods::THREAD_REALTIME_ITEM_ADDED, v),
732 Self::ThreadRealtimeOutputAudioDelta(v) => {
733 pack(methods::THREAD_REALTIME_OUTPUT_AUDIO_DELTA, v)
734 }
735 Self::ThreadRealtimeSdp(v) => pack(methods::THREAD_REALTIME_SDP, v),
736 Self::ThreadRealtimeStarted(v) => pack(methods::THREAD_REALTIME_STARTED, v),
737 Self::ThreadRealtimeTranscriptDelta(v) => {
738 pack(methods::THREAD_REALTIME_TRANSCRIPT_DELTA, v)
739 }
740 Self::ThreadRealtimeTranscriptDone(v) => {
741 pack(methods::THREAD_REALTIME_TRANSCRIPT_DONE, v)
742 }
743 Self::WindowsWorldWritableWarning(v) => {
744 pack(methods::WINDOWS_WORLD_WRITABLE_WARNING, v)
745 }
746 Self::WindowsSandboxSetupCompleted(v) => {
747 pack(methods::WINDOWS_SANDBOX_SETUP_COMPLETED, v)
748 }
749 Self::ThreadSettingsUpdated(v) => pack(methods::THREAD_SETTINGS_UPDATED, v),
750 Self::TurnModerationMetadata(v) => pack(methods::TURN_MODERATION_METADATA, v),
751 Self::ExternalAgentConfigImportProgress(v) => {
752 pack(methods::EXTERNAL_AGENT_CONFIG_IMPORT_PROGRESS, v)
753 }
754 Self::ModelSafetyBufferingUpdated(v) => {
755 pack(methods::MODEL_SAFETY_BUFFERING_UPDATED, v)
756 }
757 Self::ThreadEnvironmentConnected(v) => pack(methods::THREAD_ENVIRONMENT_CONNECTED, v),
758 Self::ThreadEnvironmentDisconnected(v) => {
759 pack(methods::THREAD_ENVIRONMENT_DISCONNECTED, v)
760 }
761 Self::Unknown { method, params } => Ok((method.clone(), params.clone())),
762 }
763 }
764}
765
766impl Serialize for Notification {
767 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
768 let (method, params) = self
769 .clone()
770 .into_envelope()
771 .map_err(serde::ser::Error::custom)?;
772 let mut env = serde_json::Map::new();
773 env.insert("method".to_string(), Value::String(method));
774 if let Some(p) = params {
775 env.insert("params".to_string(), p);
776 }
777 Value::Object(env).serialize(serializer)
778 }
779}
780
781impl<'de> Deserialize<'de> for Notification {
782 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
783 let value = Value::deserialize(deserializer)?;
784 let method = value
785 .get("method")
786 .and_then(|v| v.as_str())
787 .ok_or_else(|| serde::de::Error::missing_field("method"))?
788 .to_string();
789 let params = value.get("params").cloned();
790 Self::from_envelope(&method, params).map_err(serde::de::Error::custom)
791 }
792}
793
794#[derive(Debug, Clone)]
801pub enum ServerRequest {
802 CmdExecApproval(CommandExecutionRequestApprovalParams),
804 FileChangeApproval(FileChangeRequestApprovalParams),
806 ToolRequestUserInput(crate::protocol::ToolRequestUserInputParams),
808 McpServerElicitationRequest(crate::protocol::McpServerElicitationRequestParams),
810 PermissionsRequestApproval(crate::protocol::PermissionsRequestApprovalParams),
812 ItemToolCall(crate::protocol::DynamicToolCallParams),
814 ChatgptAuthTokensRefresh(crate::protocol::ChatgptAuthTokensRefreshParams),
816 AttestationGenerate(crate::protocol::AttestationGenerateParams),
818 ApplyPatchApproval(crate::protocol::ApplyPatchApprovalParams),
820 ExecCommandApproval(crate::protocol::ExecCommandApprovalParams),
822 Unknown {
824 method: String,
825 params: Option<Value>,
826 },
827}
828
829impl ServerRequest {
830 pub fn method(&self) -> &str {
832 match self {
833 Self::CmdExecApproval(_) => methods::CMD_EXEC_APPROVAL,
834 Self::FileChangeApproval(_) => methods::FILE_CHANGE_APPROVAL,
835 Self::ToolRequestUserInput(_) => methods::TOOL_REQUEST_USER_INPUT,
836 Self::McpServerElicitationRequest(_) => methods::MCP_SERVER_ELICITATION_REQUEST,
837 Self::PermissionsRequestApproval(_) => methods::PERMISSIONS_REQUEST_APPROVAL,
838 Self::ItemToolCall(_) => methods::ITEM_TOOL_CALL,
839 Self::ChatgptAuthTokensRefresh(_) => methods::CHATGPT_AUTH_TOKENS_REFRESH,
840 Self::AttestationGenerate(_) => methods::ATTESTATION_GENERATE,
841 Self::ApplyPatchApproval(_) => methods::APPLY_PATCH_APPROVAL,
842 Self::ExecCommandApproval(_) => methods::EXEC_COMMAND_APPROVAL,
843 Self::Unknown { method, .. } => method,
844 }
845 }
846
847 pub fn is_unknown(&self) -> bool {
849 matches!(self, Self::Unknown { .. })
850 }
851
852 pub fn from_envelope(method: &str, params: Option<Value>) -> Result<Self, serde_json::Error> {
854 let params_value = params.clone().unwrap_or(Value::Null);
855 match method {
856 methods::CMD_EXEC_APPROVAL => {
857 serde_json::from_value(params_value).map(Self::CmdExecApproval)
858 }
859 methods::FILE_CHANGE_APPROVAL => {
860 serde_json::from_value(params_value).map(Self::FileChangeApproval)
861 }
862 methods::TOOL_REQUEST_USER_INPUT => {
863 serde_json::from_value(params_value).map(Self::ToolRequestUserInput)
864 }
865 methods::MCP_SERVER_ELICITATION_REQUEST => {
866 serde_json::from_value(params_value).map(Self::McpServerElicitationRequest)
867 }
868 methods::PERMISSIONS_REQUEST_APPROVAL => {
869 serde_json::from_value(params_value).map(Self::PermissionsRequestApproval)
870 }
871 methods::ITEM_TOOL_CALL => serde_json::from_value(params_value).map(Self::ItemToolCall),
872 methods::CHATGPT_AUTH_TOKENS_REFRESH => {
873 serde_json::from_value(params_value).map(Self::ChatgptAuthTokensRefresh)
874 }
875 methods::ATTESTATION_GENERATE => {
876 serde_json::from_value(params_value).map(Self::AttestationGenerate)
877 }
878 methods::APPLY_PATCH_APPROVAL => {
879 serde_json::from_value(params_value).map(Self::ApplyPatchApproval)
880 }
881 methods::EXEC_COMMAND_APPROVAL => {
882 serde_json::from_value(params_value).map(Self::ExecCommandApproval)
883 }
884 _ => Ok(Self::Unknown {
885 method: method.to_string(),
886 params,
887 }),
888 }
889 }
890}
891
892#[allow(clippy::large_enum_variant)]
901#[derive(Debug, Clone)]
902pub enum ServerMessage {
903 Notification(Notification),
905 Request {
907 id: RequestId,
908 request: ServerRequest,
909 },
910}
911
912impl ServerMessage {
913 pub fn is_unknown(&self) -> bool {
915 match self {
916 Self::Notification(n) => n.is_unknown(),
917 Self::Request { request, .. } => request.is_unknown(),
918 }
919 }
920
921 pub fn from_json_str(s: &str) -> Result<Self, Error> {
936 let msg: JsonRpcMessage = serde_json::from_str(s)
937 .map_err(|e| Error::Deserialization(ParseError::from_line(s, e)))?;
938 Self::from_jsonrpc(msg)
939 }
940
941 pub fn from_value(value: Value) -> Result<Self, Error> {
945 let msg: JsonRpcMessage = serde_json::from_value(value.clone())
946 .map_err(|e| Error::Deserialization(ParseError::from_line(value.to_string(), e)))?;
947 Self::from_jsonrpc(msg)
948 }
949
950 fn from_jsonrpc(msg: JsonRpcMessage) -> Result<Self, Error> {
951 match msg {
952 JsonRpcMessage::Notification(JsonRpcNotification { method, params }) => {
953 Notification::from_envelope(&method, params.clone())
954 .map(ServerMessage::Notification)
955 .map_err(|e| Error::Deserialization(ParseError::from_envelope(method, params, e)))
956 }
957 JsonRpcMessage::Request(JsonRpcRequest { id, method, params }) => {
958 ServerRequest::from_envelope(&method, params.clone())
959 .map(|request| ServerMessage::Request { id, request })
960 .map_err(|e| Error::Deserialization(ParseError::from_envelope(method, params, e)))
961 }
962 JsonRpcMessage::Response(resp) => Err(Error::Protocol(format!(
963 "frame is a JSON-RPC response (id={}), not a server-initiated message",
964 resp.id
965 ))),
966 JsonRpcMessage::Error(err) => Err(Error::Protocol(format!(
967 "frame is a JSON-RPC error response (id={}, code={}), not a server-initiated message",
968 err.id, err.error.code
969 ))),
970 }
971 }
972}
973
974#[cfg(test)]
975mod tests {
976 use super::*;
977
978 #[test]
979 fn test_notification_unknown_method_routes_to_unknown_variant() {
980 let n = Notification::from_envelope("foo/bar", Some(serde_json::json!({"x": 1})))
981 .expect("unknown methods do not error");
982 match n {
983 Notification::Unknown { method, params } => {
984 assert_eq!(method, "foo/bar");
985 assert_eq!(params, Some(serde_json::json!({"x": 1})));
986 }
987 other => panic!("expected Unknown, got {:?}", other),
988 }
989 }
990
991 #[test]
992 fn test_notification_known_method_with_bad_params_errors() {
993 let err = Notification::from_envelope("thread/started", Some(serde_json::json!({})));
995 assert!(err.is_err());
996 }
997
998 #[test]
999 fn test_notification_round_trip_envelope() {
1000 let wire = serde_json::json!({
1001 "method": "item/agentMessage/delta",
1002 "params": {"threadId": "t1", "turnId": "u1", "itemId": "i1", "delta": "hi"},
1003 });
1004 let n: Notification = serde_json::from_value(wire.clone()).unwrap();
1005 assert!(matches!(n, Notification::AgentMessageDelta(_)));
1006 let back = serde_json::to_value(&n).unwrap();
1007 assert_eq!(back, wire);
1008 }
1009
1010 #[test]
1011 fn test_turn_id_from_turn_started() {
1012 let wire = serde_json::json!({
1014 "method": "turn/started",
1015 "params": {"threadId": "t1", "turn": {"id": "turn_42", "status": "inProgress"}},
1016 });
1017 let n: Notification = serde_json::from_value(wire).unwrap();
1018 assert!(matches!(n, Notification::TurnStarted(_)));
1019 assert_eq!(n.turn_id(), Some("turn_42"));
1020 }
1021
1022 #[test]
1023 fn test_turn_id_from_delta_field() {
1024 let wire = serde_json::json!({
1026 "method": "item/agentMessage/delta",
1027 "params": {"threadId": "t1", "turnId": "turn_7", "itemId": "i1", "delta": "hi"},
1028 });
1029 let n: Notification = serde_json::from_value(wire).unwrap();
1030 assert_eq!(n.turn_id(), Some("turn_7"));
1031 }
1032
1033 #[test]
1034 fn test_turn_id_none_for_non_turn_notification() {
1035 let n = Notification::from_envelope(
1036 "thread/deleted",
1037 Some(serde_json::json!({"threadId": "t1"})),
1038 )
1039 .unwrap();
1040 assert_eq!(n.turn_id(), None);
1041 }
1042
1043 #[test]
1044 fn test_thread_item_accessor() {
1045 let wire = serde_json::json!({
1046 "method": "item/started",
1047 "params": {
1048 "threadId": "t1",
1049 "turnId": "turn_1",
1050 "startedAtMs": 0,
1051 "item": {"type": "agentMessage", "id": "i1", "text": "hi"},
1052 },
1053 });
1054 let n: Notification = serde_json::from_value(wire).unwrap();
1055 assert!(n.thread_item().is_some());
1056 let other = Notification::from_envelope(
1058 "thread/deleted",
1059 Some(serde_json::json!({"threadId": "t1"})),
1060 )
1061 .unwrap();
1062 assert!(other.thread_item().is_none());
1063 }
1064
1065 #[test]
1066 fn test_server_message_from_json_str_notification() {
1067 let line = r#"{"method":"turn/started","params":{"threadId":"t1","turn":{"id":"turn_9","status":"inProgress"}}}"#;
1068 let msg = ServerMessage::from_json_str(line).expect("parses a notification frame");
1069 match msg {
1070 ServerMessage::Notification(n) => assert_eq!(n.turn_id(), Some("turn_9")),
1071 other => panic!("expected Notification, got {other:?}"),
1072 }
1073 }
1074
1075 #[test]
1076 fn test_server_message_from_value_request() {
1077 let frame = serde_json::json!({
1078 "id": 7,
1079 "method": "execCommandApproval",
1080 "params": {
1081 "callId": "c1",
1082 "command": ["ls"],
1083 "conversationId": "th_1",
1084 "cwd": "/tmp",
1085 "parsedCmd": [],
1086 },
1087 });
1088 let msg = ServerMessage::from_value(frame).expect("parses a request frame");
1089 match msg {
1090 ServerMessage::Request { id, request } => {
1091 assert_eq!(id, RequestId::Integer(7));
1092 assert!(matches!(request, ServerRequest::ExecCommandApproval(_)));
1093 }
1094 other => panic!("expected Request, got {other:?}"),
1095 }
1096 }
1097
1098 #[test]
1099 fn test_server_message_from_json_str_rejects_response() {
1100 let line = r#"{"id":1,"result":{"threadId":"th_abc"}}"#;
1102 let err = ServerMessage::from_json_str(line).unwrap_err();
1103 assert!(matches!(err, Error::Protocol(_)), "got: {err:?}");
1104 }
1105
1106 #[test]
1107 fn test_server_message_from_json_str_unknown_method_ok() {
1108 let line = r#"{"method":"some/future/notification","params":{"x":1}}"#;
1109 let msg = ServerMessage::from_json_str(line).unwrap();
1110 assert!(msg.is_unknown());
1111 }
1112}