1use std::{collections::BTreeMap, sync::Arc};
7
8use derive_more::{Display, From};
9#[cfg(feature = "schemars")]
10use schemars::Schema;
11use serde::{Deserialize, Serialize};
12use serde_with::{DefaultOnError, VecSkipError, serde_as, skip_serializing_none};
13
14#[cfg(feature = "unstable_plan_operations")]
15use super::PlanRemoved;
16#[cfg(feature = "unstable_end_turn_token_usage")]
17use super::Usage;
18use super::{
19 AbsolutePath, ContentBlock, ExtNotification, ExtRequest, ExtResponse, Meta, PlanUpdate,
20 SessionConfigOption, SessionId, StopReason, TerminalId, TerminalOutputChunk, TerminalUpdate,
21 ToolCallContentChunk, ToolCallId, ToolCallUpdate,
22};
23use super::{
24 CompleteElicitationNotification, CreateElicitationRequest, CreateElicitationResponse,
25 ElicitationCapabilities,
26};
27use crate::{IntoMaybeUndefined, IntoOption, MaybeUndefined};
28
29#[cfg(feature = "unstable_mcp_over_acp")]
30use super::mcp::{MCP_MESSAGE_METHOD_NAME, MessageMcpRequest, MessageMcpResponse};
31
32#[cfg(feature = "unstable_nes")]
33use super::{ClientNesCapabilities, PositionEncodingKind};
34
35#[serde_as]
43#[skip_serializing_none]
44#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
45#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
46#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = SESSION_UPDATE_NOTIFICATION)))]
47#[serde(rename_all = "camelCase")]
48#[non_exhaustive]
49pub struct UpdateSessionNotification {
50 pub session_id: SessionId,
52 pub update: SessionUpdate,
54 #[serde_as(deserialize_as = "DefaultOnError")]
60 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
61 #[serde(default)]
62 #[serde(rename = "_meta")]
63 pub meta: Option<Meta>,
64}
65
66impl UpdateSessionNotification {
67 #[must_use]
69 pub fn new(session_id: impl Into<SessionId>, update: SessionUpdate) -> Self {
70 Self {
71 session_id: session_id.into(),
72 update,
73 meta: None,
74 }
75 }
76
77 #[must_use]
83 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
84 self.meta = meta.into_option();
85 self
86 }
87}
88
89#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
95#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
96#[serde(tag = "sessionUpdate", rename_all = "snake_case")]
97#[non_exhaustive]
98pub enum SessionUpdate {
99 UserMessageChunk(ContentChunk),
101 UserMessage(UserMessage),
107 AgentMessageChunk(ContentChunk),
109 AgentMessage(AgentMessage),
115 AgentThoughtChunk(ContentChunk),
117 AgentThought(AgentThought),
123 StateUpdate(StateUpdate),
125 ToolCallContentChunk(ToolCallContentChunk),
127 ToolCallUpdate(ToolCallUpdate),
129 TerminalUpdate(TerminalUpdate),
131 TerminalOutputChunk(TerminalOutputChunk),
133 PlanUpdate(PlanUpdate),
136 #[cfg(feature = "unstable_plan_operations")]
142 PlanRemoved(PlanRemoved),
143 AvailableCommandsUpdate(AvailableCommandsUpdate),
145 ConfigOptionUpdate(ConfigOptionUpdate),
147 SessionInfoUpdate(SessionInfoUpdate),
149 UsageUpdate(UsageUpdate),
151 Notice(Notice),
156 CompactionUpdate(CompactionUpdate),
158 CompactionSummaryChunk(CompactionSummaryChunk),
160 #[cfg(feature = "unstable_subagents")]
167 SubagentUpdate(SubagentUpdate),
168 #[cfg(feature = "unstable_subagents")]
175 SessionMessage(SessionMessage),
176 #[cfg(feature = "unstable_subagents")]
182 SessionMessageChunk(SessionMessageChunk),
183 #[serde(untagged)]
193 Other(OtherSessionUpdate),
194}
195
196#[cfg(feature = "unstable_subagents")]
202#[serde_as]
203#[skip_serializing_none]
204#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
205#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
206#[serde(rename_all = "camelCase")]
207#[non_exhaustive]
208pub struct SessionMessageChunk {
209 pub message_id: MessageId,
211 #[serde_as(deserialize_as = "DefaultOnError")]
213 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
214 #[serde(default)]
215 pub sender_session_id: Option<SessionId>,
216 #[serde_as(deserialize_as = "DefaultOnError")]
218 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
219 #[serde(default)]
220 pub recipient_session_id: Option<SessionId>,
221 pub content: ContentBlock,
223 #[serde_as(deserialize_as = "DefaultOnError")]
227 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
228 #[serde(default, rename = "_meta")]
229 pub meta: Option<Meta>,
230}
231
232#[cfg(feature = "unstable_subagents")]
233impl SessionMessageChunk {
234 #[must_use]
236 pub fn new(message_id: impl Into<MessageId>, content: ContentBlock) -> Self {
237 Self {
238 message_id: message_id.into(),
239 sender_session_id: None,
240 recipient_session_id: None,
241 content,
242 meta: None,
243 }
244 }
245
246 #[must_use]
248 pub fn sender_session_id(mut self, sender_session_id: impl IntoOption<SessionId>) -> Self {
249 self.sender_session_id = sender_session_id.into_option();
250 self
251 }
252
253 #[must_use]
255 pub fn recipient_session_id(
256 mut self,
257 recipient_session_id: impl IntoOption<SessionId>,
258 ) -> Self {
259 self.recipient_session_id = recipient_session_id.into_option();
260 self
261 }
262
263 #[must_use]
265 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
266 self.meta = meta.into_option();
267 self
268 }
269}
270
271#[cfg(feature = "unstable_subagents")]
277#[serde_as]
278#[skip_serializing_none]
279#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
280#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
281#[serde(rename_all = "camelCase")]
282#[non_exhaustive]
283pub struct SessionMessage {
284 pub message_id: MessageId,
286 #[serde_as(deserialize_as = "DefaultOnError")]
288 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
289 #[serde(default)]
290 pub sender_session_id: Option<SessionId>,
291 #[serde_as(deserialize_as = "DefaultOnError")]
293 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
294 #[serde(default)]
295 pub recipient_session_id: Option<SessionId>,
296 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<VecSkipError<_>>>")]
299 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
300 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
301 pub content: MaybeUndefined<Vec<ContentBlock>>,
302 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
306 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
307 #[serde(
308 default,
309 rename = "_meta",
310 skip_serializing_if = "MaybeUndefined::is_undefined"
311 )]
312 pub meta: MaybeUndefined<Meta>,
313}
314
315#[cfg(feature = "unstable_subagents")]
316impl SessionMessage {
317 #[must_use]
319 pub fn new(message_id: impl Into<MessageId>) -> Self {
320 Self {
321 message_id: message_id.into(),
322 sender_session_id: None,
323 recipient_session_id: None,
324 content: MaybeUndefined::Undefined,
325 meta: MaybeUndefined::Undefined,
326 }
327 }
328
329 #[must_use]
331 pub fn sender_session_id(mut self, sender_session_id: impl IntoOption<SessionId>) -> Self {
332 self.sender_session_id = sender_session_id.into_option();
333 self
334 }
335
336 #[must_use]
338 pub fn recipient_session_id(
339 mut self,
340 recipient_session_id: impl IntoOption<SessionId>,
341 ) -> Self {
342 self.recipient_session_id = recipient_session_id.into_option();
343 self
344 }
345
346 #[must_use]
348 pub fn content(mut self, content: impl IntoMaybeUndefined<Vec<ContentBlock>>) -> Self {
349 self.content = content.into_maybe_undefined();
350 self
351 }
352
353 #[must_use]
355 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
356 self.meta = meta.into_maybe_undefined();
357 self
358 }
359}
360
361#[cfg(all(test, feature = "unstable_subagents"))]
362mod session_message_tests {
363 use super::*;
364 use serde_json::{Value, json};
365
366 #[test]
367 fn transcript_envelopes_and_multimodal_content() {
368 for (transcript, sender, recipient, id) in [
369 ("parent", "parent", "child", "sent-1"),
370 ("child", "parent", "child", "received-9"),
371 ("child", "child", "parent", "sent-2"),
372 ] {
373 let wire = json!({"sessionId": transcript, "update": {
374 "sessionUpdate": "session_message", "messageId": id,
375 "senderSessionId": sender, "recipientSessionId": recipient, "content": [
376 {"type": "text", "text": "hello"},
377 {"type": "image", "data": "aGVsbG8=", "mimeType": "image/png"}
378 ]
379 }});
380 let decoded: UpdateSessionNotification = serde_json::from_value(wire.clone()).unwrap();
381 assert_eq!(serde_json::to_value(decoded).unwrap(), wire);
382 }
383 }
384
385 #[test]
386 fn chunks_and_upserts_share_transcript_local_identity() {
387 for (transcript, id) in [("parent", "sent-1"), ("child", "received-9")] {
388 let wire = json!({"sessionId": transcript, "update": {
389 "sessionUpdate": "session_message_chunk", "messageId": id,
390 "senderSessionId": "parent", "recipientSessionId": "child",
391 "content": {"type": "text", "text": "hello"}
392 }});
393 let decoded: UpdateSessionNotification = serde_json::from_value(wire.clone()).unwrap();
394 assert_eq!(serde_json::to_value(decoded).unwrap(), wire);
395 let upsert = SessionMessage::new(id)
396 .sender_session_id(SessionId::new("parent"))
397 .recipient_session_id(SessionId::new("child"))
398 .content(vec![]);
399 let chunk = SessionMessageChunk::new(
400 id,
401 ContentBlock::Text(crate::v2::TextContent::new("hello")),
402 )
403 .sender_session_id(SessionId::new("parent"))
404 .recipient_session_id(SessionId::new("child"));
405 assert_eq!(chunk.message_id, upsert.message_id);
406 assert_eq!(chunk.sender_session_id, upsert.sender_session_id);
407 assert_eq!(chunk.recipient_session_id, upsert.recipient_session_id);
408 assert_eq!(
409 serde_json::to_value(SessionUpdate::SessionMessageChunk(chunk)).unwrap(),
410 wire["update"]
411 );
412 assert_eq!(serde_json::to_value(upsert).unwrap()["content"], json!([]));
413 }
414 }
415
416 #[test]
417 fn required_ids_and_chunk_content_with_upsert_patch_semantics() {
418 for (kind, content) in [
419 ("session_message", json!([])),
420 (
421 "session_message_chunk",
422 json!({"type": "text", "text": "hello"}),
423 ),
424 ] {
425 let base = json!({"sessionUpdate": kind, "messageId": "m1",
426 "senderSessionId": "parent", "recipientSessionId": "child", "content": content});
427 {
428 let key = "messageId";
429 let mut missing = base.clone();
430 missing.as_object_mut().unwrap().remove(key);
431 assert!(
432 serde_json::from_value::<SessionUpdate>(missing).is_err(),
433 "{kind} {key}"
434 );
435 let mut null = base.clone();
436 null[key] = Value::Null;
437 assert!(
438 serde_json::from_value::<SessionUpdate>(null).is_err(),
439 "{kind} {key}"
440 );
441 let mut non_string = base.clone();
442 non_string[key] = json!(42);
443 assert!(
444 serde_json::from_value::<SessionUpdate>(non_string).is_err(),
445 "{kind} {key}"
446 );
447 }
448 if kind == "session_message_chunk" {
449 for content in [None, Some(Value::Null), Some(json!([]))] {
450 let mut invalid = base.clone();
451 invalid.as_object_mut().unwrap().remove("content");
452 if let Some(content) = content {
453 invalid["content"] = content;
454 }
455 assert!(serde_json::from_value::<SessionUpdate>(invalid).is_err());
456 }
457 } else {
458 for content in [None, Some(Value::Null), Some(json!([])), Some(json!({}))] {
459 let mut wire = base.clone();
460 wire.as_object_mut().unwrap().remove("content");
461 if let Some(content) = content {
462 wire["content"] = content;
463 }
464 let decoded: SessionUpdate = serde_json::from_value(wire.clone()).unwrap();
465 let encoded = serde_json::to_value(decoded).unwrap();
466 if wire["content"].is_object() {
467 wire.as_object_mut().unwrap().remove("content");
468 }
469 assert_eq!(encoded, wire);
470 }
471 }
472 for meta in [None, Some(Value::Null), Some(json!({"tag": "value"}))] {
473 let mut wire = base.clone();
474 if let Some(meta) = meta {
475 wire["_meta"] = meta;
476 }
477 let decoded: SessionUpdate = serde_json::from_value(wire.clone()).unwrap();
478 let encoded = serde_json::to_value(decoded).unwrap();
479 if kind == "session_message" {
480 assert_eq!(encoded, wire);
481 } else if wire["_meta"].is_null() {
482 assert_eq!(encoded, base);
483 } else {
484 assert_eq!(encoded, wire);
485 }
486 }
487 }
488 let metadata_only = json!({"sessionUpdate": "session_message",
489 "messageId": "m1", "senderSessionId": "parent",
490 "recipientSessionId": "child", "_meta": {"tag": "value"}});
491 let decoded: SessionUpdate = serde_json::from_value(metadata_only.clone()).unwrap();
492 assert_eq!(serde_json::to_value(decoded).unwrap(), metadata_only);
493 let cleared = SessionMessage::new("m1")
494 .sender_session_id(SessionId::new("parent"))
495 .recipient_session_id(SessionId::new("child"))
496 .content(None)
497 .meta(None);
498 assert_eq!(
499 serde_json::to_value(cleared).unwrap(),
500 json!({"messageId": "m1", "senderSessionId": "parent",
501 "recipientSessionId": "child", "content": null, "_meta": null})
502 );
503 }
504
505 #[cfg(feature = "schemars")]
506 #[test]
507 fn schemas_require_ids_and_chunk_content_but_not_upsert_content() {
508 let upsert = serde_json::to_value(schemars::schema_for!(SessionMessage)).unwrap();
509 let required = upsert["required"].as_array().unwrap();
510 assert_eq!(required, &vec![json!("messageId")]);
511 let chunk = serde_json::to_value(schemars::schema_for!(SessionMessageChunk)).unwrap();
512 let required = chunk["required"].as_array().unwrap();
513 assert_eq!(required.len(), 2);
514 assert!(required.contains(&json!("messageId")));
515 assert!(required.contains(&json!("content")));
516 }
517
518 #[test]
519 fn endpoints_can_arrive_late_or_be_omitted_from_later_events() {
520 let block = ContentBlock::Text(crate::v2::TextContent::new("hello"));
521 let minimal = SessionMessage::new("m1");
522 let first = SessionMessageChunk::new("m1", block.clone());
523 assert_eq!(
524 serde_json::to_value(&minimal).unwrap(),
525 json!({"messageId": "m1"})
526 );
527 assert_eq!(
528 serde_json::to_value(&first).unwrap(),
529 json!({"messageId": "m1", "content": {"type": "text", "text": "hello"}})
530 );
531 let enriched = SessionMessage::new("m1")
532 .sender_session_id(SessionId::new("parent"))
533 .recipient_session_id(SessionId::new("child"));
534 assert_eq!(enriched.sender_session_id, Some(SessionId::new("parent")));
535 assert_eq!(enriched.recipient_session_id, Some(SessionId::new("child")));
536 let later =
537 SessionMessageChunk::new("m1", block).sender_session_id(SessionId::new("parent"));
538 assert_eq!(later.sender_session_id, Some(SessionId::new("parent")));
539 assert_eq!(later.recipient_session_id, None);
540 for update in [
541 SessionUpdate::SessionMessage(minimal),
542 SessionUpdate::SessionMessageChunk(first),
543 SessionUpdate::SessionMessage(enriched),
544 SessionUpdate::SessionMessageChunk(later),
545 ] {
546 let wire = serde_json::to_value(&update).unwrap();
547 let decoded: SessionUpdate = serde_json::from_value(wire.clone()).unwrap();
548 assert_eq!(serde_json::to_value(decoded).unwrap(), wire);
549 }
550 }
551
552 #[test]
553 fn invalid_or_null_endpoints_are_absent_but_message_id_is_required() {
554 for (kind, content) in [
555 ("session_message", None),
556 (
557 "session_message_chunk",
558 Some(json!({"type": "text", "text": "hello"})),
559 ),
560 ] {
561 let mut base = json!({"sessionUpdate": kind, "messageId": "m1",
562 "senderSessionId": null, "recipientSessionId": 42});
563 if let Some(content) = content {
564 base["content"] = content;
565 }
566 let decoded: SessionUpdate = serde_json::from_value(base.clone()).unwrap();
567 let encoded = serde_json::to_value(decoded).unwrap();
568 base.as_object_mut().unwrap().remove("senderSessionId");
569 base.as_object_mut().unwrap().remove("recipientSessionId");
570 assert_eq!(encoded, base);
571 for bad in [Value::Null, json!(42)] {
572 let mut invalid = base.clone();
573 invalid["messageId"] = bad;
574 assert!(serde_json::from_value::<SessionUpdate>(invalid).is_err());
575 }
576 }
577 }
578}
579
580#[cfg(all(test, not(feature = "unstable_subagents")))]
581mod disabled_session_message_tests {
582 use super::*;
583 use serde_json::json;
584
585 #[test]
586 fn preserves_both_unknown_message_discriminators() {
587 for (kind, content) in [
588 ("session_message", json!([])),
589 (
590 "session_message_chunk",
591 json!({"type": "text", "text": "hello"}),
592 ),
593 ] {
594 let wire = json!({"sessionUpdate": kind, "messageId": "m1",
595 "senderSessionId": "parent", "recipientSessionId": "child", "content": content});
596 let decoded: SessionUpdate = serde_json::from_value(wire.clone()).unwrap();
597 assert!(matches!(decoded, SessionUpdate::Other(_)));
598 assert_eq!(serde_json::to_value(decoded).unwrap(), wire);
599 }
600 }
601}
602
603#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
605#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
606#[serde(rename_all = "snake_case")]
607#[non_exhaustive]
608pub enum NoticeSeverity {
609 Info,
611 Warning,
613 Error,
615 #[serde(untagged)]
620 Other(String),
621}
622
623#[serde_as]
631#[skip_serializing_none]
632#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
633#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
634#[serde(rename_all = "camelCase")]
635#[non_exhaustive]
636pub struct Notice {
637 pub severity: NoticeSeverity,
639 #[cfg_attr(feature = "schemars", schemars(length(min = 1)))]
641 pub title: String,
642 #[serde_as(deserialize_as = "DefaultOnError")]
646 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
647 #[serde(default)]
648 pub description: Option<String>,
649 #[serde_as(deserialize_as = "DefaultOnError")]
653 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
654 #[serde(default, rename = "_meta")]
655 pub meta: Option<Meta>,
656}
657
658impl Notice {
659 #[must_use]
661 pub fn new(severity: NoticeSeverity, title: impl Into<String>) -> Self {
662 Self {
663 severity,
664 title: title.into(),
665 description: None,
666 meta: None,
667 }
668 }
669
670 #[must_use]
672 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
673 self.description = description.into_option();
674 self
675 }
676
677 #[must_use]
679 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
680 self.meta = meta.into_option();
681 self
682 }
683}
684
685#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
687#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
688#[serde(transparent)]
689#[from(forward)]
690#[non_exhaustive]
691pub struct CompactionId(pub Arc<str>);
692
693impl CompactionId {
694 #[must_use]
696 pub fn new(id: impl Into<Self>) -> Self {
697 id.into()
698 }
699}
700
701#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
703#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
704#[serde(rename_all = "snake_case")]
705#[non_exhaustive]
706pub enum CompactionStatus {
707 #[default]
709 InProgress,
710 Completed,
712 Failed,
714 Cancelled,
716 #[serde(untagged)]
721 Other(String),
722}
723
724#[serde_as]
731#[skip_serializing_none]
732#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
733#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
734#[serde(rename_all = "camelCase")]
735#[non_exhaustive]
736pub struct CompactionUpdate {
737 pub compaction_id: CompactionId,
739 pub status: CompactionStatus,
741 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<VecSkipError<_>>>")]
743 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
744 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
745 pub summary: MaybeUndefined<Vec<ContentBlock>>,
746 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
748 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
749 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
750 pub error: MaybeUndefined<String>,
751 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
753 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
754 #[serde(
755 rename = "_meta",
756 default,
757 skip_serializing_if = "MaybeUndefined::is_undefined"
758 )]
759 pub meta: MaybeUndefined<Meta>,
760}
761
762impl CompactionUpdate {
763 #[must_use]
765 pub fn new(compaction_id: impl Into<CompactionId>, status: CompactionStatus) -> Self {
766 Self {
767 compaction_id: compaction_id.into(),
768 status,
769 summary: MaybeUndefined::Undefined,
770 error: MaybeUndefined::Undefined,
771 meta: MaybeUndefined::Undefined,
772 }
773 }
774
775 #[must_use]
777 pub fn summary(mut self, summary: impl IntoMaybeUndefined<Vec<ContentBlock>>) -> Self {
778 self.summary = summary.into_maybe_undefined();
779 self
780 }
781
782 #[must_use]
784 pub fn error(mut self, error: impl IntoMaybeUndefined<String>) -> Self {
785 self.error = error.into_maybe_undefined();
786 self
787 }
788
789 #[must_use]
791 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
792 self.meta = meta.into_maybe_undefined();
793 self
794 }
795}
796
797#[serde_as]
800#[skip_serializing_none]
801#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
802#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
803#[serde(rename_all = "camelCase")]
804#[non_exhaustive]
805pub struct CompactionSummaryChunk {
806 pub compaction_id: CompactionId,
808 pub content: ContentBlock,
810 #[serde_as(deserialize_as = "DefaultOnError")]
812 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
813 #[serde(default, rename = "_meta")]
814 pub meta: Option<Meta>,
815}
816
817impl CompactionSummaryChunk {
818 #[must_use]
820 pub fn new(compaction_id: impl Into<CompactionId>, content: ContentBlock) -> Self {
821 Self {
822 compaction_id: compaction_id.into(),
823 content,
824 meta: None,
825 }
826 }
827
828 #[must_use]
830 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
831 self.meta = meta.into_option();
832 self
833 }
834}
835
836#[cfg(feature = "unstable_subagents")]
870#[serde_as]
871#[skip_serializing_none]
872#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
873#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
874#[serde(rename_all = "camelCase")]
875#[non_exhaustive]
876pub struct SubagentUpdate {
877 pub session_id: SessionId,
879 #[serde_as(deserialize_as = "DefaultOnError")]
884 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
885 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
886 pub title: MaybeUndefined<String>,
887 #[serde_as(deserialize_as = "DefaultOnError")]
892 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
893 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
894 pub description: MaybeUndefined<String>,
895 #[serde_as(deserialize_as = "DefaultOnError")]
900 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
901 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
902 pub capabilities: MaybeUndefined<SubagentSessionCapabilities>,
903 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
909 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
910 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
911 pub state: MaybeUndefined<StateUpdate>,
912 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
918 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
919 #[serde(
920 rename = "_meta",
921 default,
922 skip_serializing_if = "MaybeUndefined::is_undefined"
923 )]
924 pub meta: MaybeUndefined<Meta>,
925}
926
927#[cfg(feature = "unstable_subagents")]
928impl SubagentUpdate {
929 #[must_use]
931 pub fn new(session_id: impl Into<SessionId>) -> Self {
932 Self {
933 session_id: session_id.into(),
934 title: MaybeUndefined::Undefined,
935 description: MaybeUndefined::Undefined,
936 capabilities: MaybeUndefined::Undefined,
937 state: MaybeUndefined::Undefined,
938 meta: MaybeUndefined::Undefined,
939 }
940 }
941
942 #[must_use]
944 pub fn title(mut self, title: impl IntoMaybeUndefined<String>) -> Self {
945 self.title = title.into_maybe_undefined();
946 self
947 }
948
949 #[must_use]
951 pub fn description(mut self, description: impl IntoMaybeUndefined<String>) -> Self {
952 self.description = description.into_maybe_undefined();
953 self
954 }
955
956 #[must_use]
958 pub fn capabilities(
959 mut self,
960 capabilities: impl IntoMaybeUndefined<SubagentSessionCapabilities>,
961 ) -> Self {
962 self.capabilities = capabilities.into_maybe_undefined();
963 self
964 }
965
966 #[must_use]
968 pub fn state(mut self, state: impl IntoMaybeUndefined<StateUpdate>) -> Self {
969 self.state = state.into_maybe_undefined();
970 self
971 }
972
973 #[must_use]
975 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
976 self.meta = meta.into_maybe_undefined();
977 self
978 }
979}
980
981#[cfg(feature = "unstable_subagents")]
990#[serde_as]
991#[skip_serializing_none]
992#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
993#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
994#[serde(rename_all = "camelCase")]
995#[non_exhaustive]
996pub struct SubagentSessionCapabilities {
997 #[serde_as(deserialize_as = "DefaultOnError")]
1001 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1002 #[serde(default)]
1003 pub cancel: Option<SessionCancelCapabilities>,
1004 #[serde_as(deserialize_as = "DefaultOnError")]
1008 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1009 #[serde(default)]
1010 #[serde(rename = "_meta")]
1011 pub meta: Option<Meta>,
1012}
1013
1014#[cfg(feature = "unstable_subagents")]
1015impl SubagentSessionCapabilities {
1016 #[must_use]
1018 pub fn new() -> Self {
1019 Self::default()
1020 }
1021
1022 #[must_use]
1024 pub fn cancel(mut self, cancel: impl IntoOption<SessionCancelCapabilities>) -> Self {
1025 self.cancel = cancel.into_option();
1026 self
1027 }
1028
1029 #[must_use]
1033 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1034 self.meta = meta.into_option();
1035 self
1036 }
1037}
1038
1039#[cfg(feature = "unstable_subagents")]
1047#[serde_as]
1048#[skip_serializing_none]
1049#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1050#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1051#[non_exhaustive]
1052pub struct SessionCancelCapabilities {
1053 #[serde_as(deserialize_as = "DefaultOnError")]
1059 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1060 #[serde(default)]
1061 #[serde(rename = "_meta")]
1062 pub meta: Option<Meta>,
1063}
1064
1065#[cfg(feature = "unstable_subagents")]
1066impl SessionCancelCapabilities {
1067 #[must_use]
1069 pub fn new() -> Self {
1070 Self::default()
1071 }
1072
1073 #[must_use]
1079 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1080 self.meta = meta.into_option();
1081 self
1082 }
1083}
1084
1085#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1091#[derive(Debug, Clone, Serialize, PartialEq)]
1092#[cfg_attr(feature = "schemars", schemars(inline))]
1093#[cfg_attr(feature = "schemars", schemars(transform = other_session_update_schema))]
1094#[serde(rename_all = "camelCase")]
1095#[non_exhaustive]
1096pub struct OtherSessionUpdate {
1097 #[serde(rename = "sessionUpdate")]
1103 pub session_update: String,
1104 #[serde(flatten)]
1106 pub fields: BTreeMap<String, serde_json::Value>,
1107}
1108
1109impl OtherSessionUpdate {
1110 #[must_use]
1112 pub fn new(
1113 session_update: impl Into<String>,
1114 mut fields: BTreeMap<String, serde_json::Value>,
1115 ) -> Self {
1116 fields.remove("sessionUpdate");
1117 Self {
1118 session_update: session_update.into(),
1119 fields,
1120 }
1121 }
1122}
1123
1124impl<'de> Deserialize<'de> for OtherSessionUpdate {
1125 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1126 where
1127 D: serde::Deserializer<'de>,
1128 {
1129 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1130 let session_update = fields
1131 .remove("sessionUpdate")
1132 .ok_or_else(|| serde::de::Error::missing_field("sessionUpdate"))?;
1133 let serde_json::Value::String(session_update) = session_update else {
1134 return Err(serde::de::Error::custom("`sessionUpdate` must be a string"));
1135 };
1136
1137 if is_known_session_update(&session_update) {
1138 return Err(serde::de::Error::custom(format!(
1139 "known session update `{session_update}` did not match its schema"
1140 )));
1141 }
1142
1143 Ok(Self {
1144 session_update,
1145 fields,
1146 })
1147 }
1148}
1149
1150fn is_known_session_update(session_update: &str) -> bool {
1151 if session_update == "notice" {
1152 return true;
1153 }
1154 if matches!(
1155 session_update,
1156 "compaction_update" | "compaction_summary_chunk"
1157 ) {
1158 return true;
1159 }
1160 #[cfg(feature = "unstable_plan_operations")]
1161 if session_update == "plan_removed" {
1162 return true;
1163 }
1164 #[cfg(feature = "unstable_subagents")]
1165 if matches!(
1166 session_update,
1167 "subagent_update" | "session_message" | "session_message_chunk"
1168 ) {
1169 return true;
1170 }
1171 matches!(
1172 session_update,
1173 "user_message_chunk"
1174 | "user_message"
1175 | "agent_message_chunk"
1176 | "agent_message"
1177 | "agent_thought_chunk"
1178 | "agent_thought"
1179 | "state_update"
1180 | "tool_call_content_chunk"
1181 | "tool_call_update"
1182 | "terminal_update"
1183 | "terminal_output_chunk"
1184 | "plan_update"
1185 | "available_commands_update"
1186 | "config_option_update"
1187 | "session_info_update"
1188 | "usage_update"
1189 )
1190}
1191
1192#[cfg(feature = "schemars")]
1193fn other_session_update_schema(schema: &mut Schema) {
1194 super::schema_util::reject_known_string_discriminators(
1195 schema,
1196 "sessionUpdate",
1197 &[
1198 "user_message_chunk",
1199 "user_message",
1200 "agent_message_chunk",
1201 "agent_message",
1202 "agent_thought_chunk",
1203 "agent_thought",
1204 "state_update",
1205 "tool_call_content_chunk",
1206 "tool_call_update",
1207 "terminal_update",
1208 "terminal_output_chunk",
1209 "plan_update",
1210 "available_commands_update",
1211 "config_option_update",
1212 "session_info_update",
1213 #[cfg(feature = "unstable_plan_operations")]
1214 "plan_removed",
1215 "usage_update",
1216 "notice",
1217 "compaction_update",
1218 "compaction_summary_chunk",
1219 #[cfg(feature = "unstable_subagents")]
1220 "subagent_update",
1221 #[cfg(feature = "unstable_subagents")]
1222 "session_message",
1223 #[cfg(feature = "unstable_subagents")]
1224 "session_message_chunk",
1225 ],
1226 );
1227}
1228
1229#[serde_as]
1231#[skip_serializing_none]
1232#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1233#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1234#[serde(rename_all = "camelCase")]
1235#[non_exhaustive]
1236pub struct ConfigOptionUpdate {
1237 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
1239 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
1240 pub config_options: Vec<SessionConfigOption>,
1241 #[serde_as(deserialize_as = "DefaultOnError")]
1247 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1248 #[serde(default)]
1249 #[serde(rename = "_meta")]
1250 pub meta: Option<Meta>,
1251}
1252
1253impl ConfigOptionUpdate {
1254 #[must_use]
1256 pub fn new(config_options: Vec<SessionConfigOption>) -> Self {
1257 Self {
1258 config_options,
1259 meta: None,
1260 }
1261 }
1262
1263 #[must_use]
1269 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1270 self.meta = meta.into_option();
1271 self
1272 }
1273}
1274
1275#[serde_as]
1283#[skip_serializing_none]
1284#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1285#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1286#[serde(rename_all = "camelCase")]
1287#[non_exhaustive]
1288pub struct SessionInfoUpdate {
1289 #[serde_as(deserialize_as = "DefaultOnError")]
1291 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1292 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
1293 pub title: MaybeUndefined<String>,
1294 #[serde_as(deserialize_as = "DefaultOnError")]
1296 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "format" = "date-time")))]
1297 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
1298 pub updated_at: MaybeUndefined<String>,
1299 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
1305 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1306 #[serde(
1307 rename = "_meta",
1308 default,
1309 skip_serializing_if = "MaybeUndefined::is_undefined"
1310 )]
1311 pub meta: MaybeUndefined<Meta>,
1312}
1313
1314impl SessionInfoUpdate {
1315 #[must_use]
1317 pub fn new() -> Self {
1318 Self::default()
1319 }
1320
1321 #[must_use]
1323 pub fn title(mut self, title: impl IntoMaybeUndefined<String>) -> Self {
1324 self.title = title.into_maybe_undefined();
1325 self
1326 }
1327
1328 #[must_use]
1330 pub fn updated_at(mut self, updated_at: impl IntoMaybeUndefined<String>) -> Self {
1331 self.updated_at = updated_at.into_maybe_undefined();
1332 self
1333 }
1334
1335 #[must_use]
1341 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
1342 self.meta = meta.into_maybe_undefined();
1343 self
1344 }
1345}
1346
1347#[serde_as]
1349#[skip_serializing_none]
1350#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1351#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1352#[serde(rename_all = "camelCase")]
1353#[non_exhaustive]
1354pub struct UsageUpdate {
1355 pub used: u64,
1357 pub size: u64,
1359 #[serde_as(deserialize_as = "DefaultOnError")]
1361 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1362 #[serde(default)]
1363 pub cost: Option<Cost>,
1364 #[serde_as(deserialize_as = "DefaultOnError")]
1370 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1371 #[serde(default)]
1372 #[serde(rename = "_meta")]
1373 pub meta: Option<Meta>,
1374}
1375
1376impl UsageUpdate {
1377 #[must_use]
1379 pub fn new(used: u64, size: u64) -> Self {
1380 Self {
1381 used,
1382 size,
1383 cost: None,
1384 meta: None,
1385 }
1386 }
1387
1388 #[must_use]
1390 pub fn cost(mut self, cost: impl IntoOption<Cost>) -> Self {
1391 self.cost = cost.into_option();
1392 self
1393 }
1394
1395 #[must_use]
1401 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1402 self.meta = meta.into_option();
1403 self
1404 }
1405}
1406
1407#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1412#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1413#[serde(tag = "state", rename_all = "snake_case")]
1414#[non_exhaustive]
1415pub enum StateUpdate {
1416 Running(RunningStateUpdate),
1418 Idle(IdleStateUpdate),
1420 RequiresAction(RequiresActionStateUpdate),
1422 #[cfg(feature = "unstable_subagents")]
1429 Unknown(UnknownStateUpdate),
1430 #[serde(untagged)]
1436 Other(OtherStateUpdate),
1437}
1438
1439#[serde_as]
1441#[skip_serializing_none]
1442#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1443#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1444#[serde(rename_all = "camelCase")]
1445#[non_exhaustive]
1446pub struct RunningStateUpdate {
1447 #[serde_as(deserialize_as = "DefaultOnError")]
1453 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1454 #[serde(default)]
1455 #[serde(rename = "_meta")]
1456 pub meta: Option<Meta>,
1457}
1458
1459impl RunningStateUpdate {
1460 #[must_use]
1462 pub fn new() -> Self {
1463 Self::default()
1464 }
1465
1466 #[must_use]
1472 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1473 self.meta = meta.into_option();
1474 self
1475 }
1476}
1477
1478#[serde_as]
1484#[skip_serializing_none]
1485#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1486#[cfg_attr(feature = "schemars", schemars(transform = idle_state_update_schema))]
1487#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
1488#[serde(rename_all = "camelCase")]
1489#[non_exhaustive]
1490pub struct IdleStateUpdate {
1491 #[serde(flatten)]
1493 pub stop_reason: Option<StopReason>,
1494 #[cfg(feature = "unstable_end_turn_token_usage")]
1503 #[serde_as(deserialize_as = "DefaultOnError")]
1504 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1505 #[serde(default)]
1506 pub usage: Option<Box<Usage>>,
1507 #[serde_as(deserialize_as = "DefaultOnError")]
1513 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1514 #[serde(default)]
1515 #[serde(rename = "_meta")]
1516 pub meta: Option<Meta>,
1517}
1518
1519impl IdleStateUpdate {
1520 #[must_use]
1522 pub fn new() -> Self {
1523 Self::default()
1524 }
1525
1526 #[must_use]
1528 pub fn stop_reason(mut self, stop_reason: impl IntoOption<StopReason>) -> Self {
1529 self.stop_reason = stop_reason.into_option();
1530 self
1531 }
1532
1533 #[cfg(feature = "unstable_end_turn_token_usage")]
1539 #[must_use]
1540 pub fn usage(mut self, usage: impl IntoOption<Usage>) -> Self {
1541 self.usage = usage.into_option().map(Box::new);
1542 self
1543 }
1544
1545 #[must_use]
1551 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1552 self.meta = meta.into_option();
1553 self
1554 }
1555}
1556
1557#[cfg(feature = "schemars")]
1566fn idle_state_update_schema(schema: &mut Schema) {
1567 if let Some(properties) = schema
1568 .get_mut("properties")
1569 .and_then(serde_json::Value::as_object_mut)
1570 {
1571 properties.insert(
1572 "stopReason".into(),
1573 serde_json::json!({
1574 "description": "Why foreground work stopped. The value selects one of this type's variants, which may add fields of their own.\n\nOptional. Omitted or `null` both mean the agent is not reporting a stop reason; a malformed value is treated the same way.\n\nSee protocol docs: [Stop Reasons](https://agentclientprotocol.com/protocol/prompt-lifecycle#stop-reasons)",
1575 "type": ["string", "null"],
1576 "x-deserialize-default-on-error": true
1577 }),
1578 );
1579 }
1580 let Some(variants) = schema
1581 .get_mut("anyOf")
1582 .and_then(serde_json::Value::as_array_mut)
1583 else {
1584 return;
1585 };
1586 for variant in variants {
1587 if variant.as_object().is_some_and(serde_json::Map::is_empty) {
1588 *variant = serde_json::json!({
1589 "title": "none",
1590 "description": "No stop reason: `stopReason` is omitted or `null`.",
1591 "type": "object",
1592 "properties": {
1593 "stopReason": { "type": "null" }
1594 }
1595 });
1596 }
1597 }
1598}
1599
1600#[serde_as]
1602#[skip_serializing_none]
1603#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1604#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1605#[serde(rename_all = "camelCase")]
1606#[non_exhaustive]
1607pub struct RequiresActionStateUpdate {
1608 #[serde_as(deserialize_as = "DefaultOnError")]
1614 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1615 #[serde(default)]
1616 #[serde(rename = "_meta")]
1617 pub meta: Option<Meta>,
1618}
1619
1620impl RequiresActionStateUpdate {
1621 #[must_use]
1623 pub fn new() -> Self {
1624 Self::default()
1625 }
1626
1627 #[must_use]
1633 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1634 self.meta = meta.into_option();
1635 self
1636 }
1637}
1638
1639#[cfg(feature = "unstable_subagents")]
1653#[serde_as]
1654#[skip_serializing_none]
1655#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1656#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1657#[serde(rename_all = "camelCase")]
1658#[non_exhaustive]
1659pub struct UnknownStateUpdate {
1660 #[serde_as(deserialize_as = "DefaultOnError")]
1664 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1665 #[serde(default, rename = "_meta")]
1666 pub meta: Option<Meta>,
1667}
1668
1669#[cfg(feature = "unstable_subagents")]
1670impl UnknownStateUpdate {
1671 #[must_use]
1673 pub fn new() -> Self {
1674 Self::default()
1675 }
1676
1677 #[must_use]
1679 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1680 self.meta = meta.into_option();
1681 self
1682 }
1683}
1684
1685#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1690#[derive(Debug, Clone, Serialize, PartialEq)]
1691#[cfg_attr(feature = "schemars", schemars(inline))]
1692#[cfg_attr(feature = "schemars", schemars(transform = other_state_update_schema))]
1693#[serde(rename_all = "camelCase")]
1694#[non_exhaustive]
1695pub struct OtherStateUpdate {
1696 #[serde(rename = "state")]
1702 pub state: String,
1703 #[serde(flatten)]
1705 pub fields: BTreeMap<String, serde_json::Value>,
1706}
1707
1708impl OtherStateUpdate {
1709 #[must_use]
1711 pub fn new(state: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
1712 fields.remove("state");
1713 Self {
1714 state: state.into(),
1715 fields,
1716 }
1717 }
1718}
1719
1720impl<'de> Deserialize<'de> for OtherStateUpdate {
1721 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1722 where
1723 D: serde::Deserializer<'de>,
1724 {
1725 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1726 let state = fields
1727 .remove("state")
1728 .ok_or_else(|| serde::de::Error::missing_field("state"))?;
1729 let serde_json::Value::String(state) = state else {
1730 return Err(serde::de::Error::custom("`state` must be a string"));
1731 };
1732
1733 if is_known_state_update(&state) {
1734 return Err(serde::de::Error::custom(format!(
1735 "known state update `{state}` did not match its schema"
1736 )));
1737 }
1738
1739 Ok(Self { state, fields })
1740 }
1741}
1742
1743const KNOWN_STATE_UPDATE_STATES: &[&str] = &[
1744 "running",
1745 "idle",
1746 "requires_action",
1747 #[cfg(feature = "unstable_subagents")]
1748 "unknown",
1749];
1750
1751fn is_known_state_update(state: &str) -> bool {
1752 KNOWN_STATE_UPDATE_STATES.contains(&state)
1753}
1754
1755#[cfg(feature = "schemars")]
1756fn other_state_update_schema(schema: &mut Schema) {
1757 super::schema_util::reject_known_string_discriminators(
1758 schema,
1759 "state",
1760 KNOWN_STATE_UPDATE_STATES,
1761 );
1762}
1763
1764#[serde_as]
1766#[skip_serializing_none]
1767#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1768#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1769#[serde(rename_all = "camelCase")]
1770#[non_exhaustive]
1771pub struct Cost {
1772 pub amount: f64,
1774 #[cfg_attr(feature = "schemars", schemars(pattern(r"^[A-Z]{3}$")))]
1776 pub currency: String,
1777 #[serde_as(deserialize_as = "DefaultOnError")]
1783 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1784 #[serde(default)]
1785 #[serde(rename = "_meta")]
1786 pub meta: Option<Meta>,
1787}
1788
1789impl Cost {
1790 #[must_use]
1792 pub fn new(amount: f64, currency: impl Into<String>) -> Self {
1793 Self {
1794 amount,
1795 currency: currency.into(),
1796 meta: None,
1797 }
1798 }
1799
1800 #[must_use]
1806 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1807 self.meta = meta.into_option();
1808 self
1809 }
1810}
1811
1812#[serde_as]
1814#[skip_serializing_none]
1815#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1816#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1817#[serde(rename_all = "camelCase")]
1818#[non_exhaustive]
1819pub struct ContentChunk {
1820 pub message_id: MessageId,
1825 pub content: ContentBlock,
1827 #[serde_as(deserialize_as = "DefaultOnError")]
1833 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1834 #[serde(default)]
1835 #[serde(rename = "_meta")]
1836 pub meta: Option<Meta>,
1837}
1838
1839impl ContentChunk {
1840 #[must_use]
1842 pub fn new(content: ContentBlock, message_id: impl Into<MessageId>) -> Self {
1843 Self {
1844 content,
1845 message_id: message_id.into(),
1846 meta: None,
1847 }
1848 }
1849
1850 #[must_use]
1856 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1857 self.meta = meta.into_option();
1858 self
1859 }
1860}
1861
1862#[serde_as]
1876#[skip_serializing_none]
1877#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1878#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1879#[serde(rename_all = "camelCase")]
1880#[non_exhaustive]
1881pub struct UserMessage {
1882 pub message_id: MessageId,
1884 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<VecSkipError<_>>>")]
1886 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
1887 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
1888 pub content: MaybeUndefined<Vec<ContentBlock>>,
1889 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
1895 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1896 #[serde(
1897 rename = "_meta",
1898 default,
1899 skip_serializing_if = "MaybeUndefined::is_undefined"
1900 )]
1901 pub meta: MaybeUndefined<Meta>,
1902}
1903
1904impl UserMessage {
1905 #[must_use]
1907 pub fn new(message_id: impl Into<MessageId>) -> Self {
1908 Self {
1909 message_id: message_id.into(),
1910 content: MaybeUndefined::Undefined,
1911 meta: MaybeUndefined::Undefined,
1912 }
1913 }
1914
1915 #[must_use]
1917 pub fn content(mut self, content: impl IntoMaybeUndefined<Vec<ContentBlock>>) -> Self {
1918 self.content = content.into_maybe_undefined();
1919 self
1920 }
1921
1922 #[must_use]
1928 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
1929 self.meta = meta.into_maybe_undefined();
1930 self
1931 }
1932}
1933
1934#[serde_as]
1948#[skip_serializing_none]
1949#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1950#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1951#[serde(rename_all = "camelCase")]
1952#[non_exhaustive]
1953pub struct AgentMessage {
1954 pub message_id: MessageId,
1956 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<VecSkipError<_>>>")]
1958 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
1959 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
1960 pub content: MaybeUndefined<Vec<ContentBlock>>,
1961 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
1967 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1968 #[serde(
1969 rename = "_meta",
1970 default,
1971 skip_serializing_if = "MaybeUndefined::is_undefined"
1972 )]
1973 pub meta: MaybeUndefined<Meta>,
1974}
1975
1976impl AgentMessage {
1977 #[must_use]
1979 pub fn new(message_id: impl Into<MessageId>) -> Self {
1980 Self {
1981 message_id: message_id.into(),
1982 content: MaybeUndefined::Undefined,
1983 meta: MaybeUndefined::Undefined,
1984 }
1985 }
1986
1987 #[must_use]
1989 pub fn content(mut self, content: impl IntoMaybeUndefined<Vec<ContentBlock>>) -> Self {
1990 self.content = content.into_maybe_undefined();
1991 self
1992 }
1993
1994 #[must_use]
2000 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
2001 self.meta = meta.into_maybe_undefined();
2002 self
2003 }
2004}
2005
2006#[serde_as]
2020#[skip_serializing_none]
2021#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2022#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2023#[serde(rename_all = "camelCase")]
2024#[non_exhaustive]
2025pub struct AgentThought {
2026 pub message_id: MessageId,
2028 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<VecSkipError<_>>>")]
2030 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
2031 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
2032 pub content: MaybeUndefined<Vec<ContentBlock>>,
2033 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
2039 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2040 #[serde(
2041 rename = "_meta",
2042 default,
2043 skip_serializing_if = "MaybeUndefined::is_undefined"
2044 )]
2045 pub meta: MaybeUndefined<Meta>,
2046}
2047
2048impl AgentThought {
2049 #[must_use]
2051 pub fn new(message_id: impl Into<MessageId>) -> Self {
2052 Self {
2053 message_id: message_id.into(),
2054 content: MaybeUndefined::Undefined,
2055 meta: MaybeUndefined::Undefined,
2056 }
2057 }
2058
2059 #[must_use]
2061 pub fn content(mut self, content: impl IntoMaybeUndefined<Vec<ContentBlock>>) -> Self {
2062 self.content = content.into_maybe_undefined();
2063 self
2064 }
2065
2066 #[must_use]
2072 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
2073 self.meta = meta.into_maybe_undefined();
2074 self
2075 }
2076}
2077
2078#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2084#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
2085#[serde(transparent)]
2086#[from(forward)]
2087#[non_exhaustive]
2088pub struct MessageId(pub Arc<str>);
2089
2090impl MessageId {
2091 #[must_use]
2093 pub fn new(id: impl Into<Self>) -> Self {
2094 id.into()
2095 }
2096}
2097
2098#[serde_as]
2100#[skip_serializing_none]
2101#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2102#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2103#[serde(rename_all = "camelCase")]
2104#[non_exhaustive]
2105pub struct AvailableCommandsUpdate {
2106 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
2108 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
2109 pub available_commands: Vec<AvailableCommand>,
2110 #[serde_as(deserialize_as = "DefaultOnError")]
2116 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2117 #[serde(default)]
2118 #[serde(rename = "_meta")]
2119 pub meta: Option<Meta>,
2120}
2121
2122impl AvailableCommandsUpdate {
2123 #[must_use]
2125 pub fn new(available_commands: Vec<AvailableCommand>) -> Self {
2126 Self {
2127 available_commands,
2128 meta: None,
2129 }
2130 }
2131
2132 #[must_use]
2138 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2139 self.meta = meta.into_option();
2140 self
2141 }
2142}
2143
2144#[serde_as]
2146#[skip_serializing_none]
2147#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2148#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2149#[serde(rename_all = "camelCase")]
2150#[non_exhaustive]
2151pub struct AvailableCommand {
2152 pub name: String,
2154 pub description: String,
2156 #[serde_as(deserialize_as = "DefaultOnError")]
2158 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2159 #[serde(default)]
2160 pub input: Option<AvailableCommandInput>,
2161 #[serde_as(deserialize_as = "DefaultOnError")]
2167 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2168 #[serde(default)]
2169 #[serde(rename = "_meta")]
2170 pub meta: Option<Meta>,
2171}
2172
2173impl AvailableCommand {
2174 #[must_use]
2176 pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
2177 Self {
2178 name: name.into(),
2179 description: description.into(),
2180 input: None,
2181 meta: None,
2182 }
2183 }
2184
2185 #[must_use]
2187 pub fn input(mut self, input: impl IntoOption<AvailableCommandInput>) -> Self {
2188 self.input = input.into_option();
2189 self
2190 }
2191
2192 #[must_use]
2198 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2199 self.meta = meta.into_option();
2200 self
2201 }
2202}
2203
2204#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2206#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2207#[serde(tag = "type", rename_all = "snake_case")]
2208#[non_exhaustive]
2209pub enum AvailableCommandInput {
2210 #[serde(rename = "text")]
2212 Text(TextCommandInput),
2213 #[serde(untagged)]
2224 Other(OtherAvailableCommandInput),
2225}
2226
2227#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2229#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
2230#[cfg_attr(feature = "schemars", schemars(inline))]
2231#[cfg_attr(feature = "schemars", schemars(transform = other_available_command_input_schema))]
2232#[serde(rename_all = "camelCase")]
2233#[non_exhaustive]
2234pub struct OtherAvailableCommandInput {
2235 #[serde(rename = "type")]
2241 pub type_: String,
2242 #[serde(flatten)]
2244 pub fields: BTreeMap<String, serde_json::Value>,
2245}
2246
2247impl OtherAvailableCommandInput {
2248 #[must_use]
2250 pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
2251 fields.remove("type");
2252 Self {
2253 type_: type_.into(),
2254 fields,
2255 }
2256 }
2257}
2258
2259impl<'de> Deserialize<'de> for OtherAvailableCommandInput {
2260 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2261 where
2262 D: serde::Deserializer<'de>,
2263 {
2264 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
2265 let type_ = fields
2266 .remove("type")
2267 .ok_or_else(|| serde::de::Error::missing_field("type"))?;
2268 let serde_json::Value::String(type_) = type_ else {
2269 return Err(serde::de::Error::custom("`type` must be a string"));
2270 };
2271
2272 if is_known_available_command_input_type(&type_) {
2273 return Err(serde::de::Error::custom(format!(
2274 "known available command input type `{type_}` did not match its schema"
2275 )));
2276 }
2277
2278 Ok(Self { type_, fields })
2279 }
2280}
2281
2282const KNOWN_AVAILABLE_COMMAND_INPUT_TYPES: &[&str] = &["text"];
2283
2284fn is_known_available_command_input_type(type_: &str) -> bool {
2285 KNOWN_AVAILABLE_COMMAND_INPUT_TYPES.contains(&type_)
2286}
2287
2288#[cfg(feature = "schemars")]
2289fn other_available_command_input_schema(schema: &mut Schema) {
2290 super::schema_util::reject_known_string_discriminators(
2291 schema,
2292 "type",
2293 KNOWN_AVAILABLE_COMMAND_INPUT_TYPES,
2294 );
2295}
2296
2297#[serde_as]
2299#[skip_serializing_none]
2300#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2301#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2302#[serde(rename_all = "camelCase")]
2303#[non_exhaustive]
2304pub struct TextCommandInput {
2305 pub hint: String,
2307 #[serde_as(deserialize_as = "DefaultOnError")]
2313 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2314 #[serde(default)]
2315 #[serde(rename = "_meta")]
2316 pub meta: Option<Meta>,
2317}
2318
2319impl TextCommandInput {
2320 #[must_use]
2322 pub fn new(hint: impl Into<String>) -> Self {
2323 Self {
2324 hint: hint.into(),
2325 meta: None,
2326 }
2327 }
2328
2329 #[must_use]
2335 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2336 self.meta = meta.into_option();
2337 self
2338 }
2339}
2340
2341#[serde_as]
2349#[skip_serializing_none]
2350#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2351#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2352#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = SESSION_REQUEST_PERMISSION_METHOD_NAME)))]
2353#[serde(rename_all = "camelCase")]
2354#[non_exhaustive]
2355pub struct RequestPermissionRequest {
2356 pub session_id: SessionId,
2358 pub title: String,
2363 #[serde_as(deserialize_as = "DefaultOnError")]
2369 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2370 #[serde(default)]
2371 pub description: Option<String>,
2372 #[serde(default)]
2376 pub subject: Option<RequestPermissionSubject>,
2377 #[cfg_attr(feature = "schemars", schemars(length(min = 1)))]
2380 pub options: Vec<PermissionOption>,
2381 #[serde_as(deserialize_as = "DefaultOnError")]
2387 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2388 #[serde(default)]
2389 #[serde(rename = "_meta")]
2390 pub meta: Option<Meta>,
2391}
2392
2393impl RequestPermissionRequest {
2394 #[must_use]
2396 pub fn new(
2397 session_id: impl Into<SessionId>,
2398 title: impl Into<String>,
2399 options: Vec<PermissionOption>,
2400 ) -> Self {
2401 Self {
2402 session_id: session_id.into(),
2403 title: title.into(),
2404 description: None,
2405 subject: None,
2406 options,
2407 meta: None,
2408 }
2409 }
2410
2411 #[must_use]
2413 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
2414 self.description = description.into_option();
2415 self
2416 }
2417
2418 #[must_use]
2420 pub fn subject(mut self, subject: impl IntoOption<RequestPermissionSubject>) -> Self {
2421 self.subject = subject.into_option();
2422 self
2423 }
2424
2425 #[must_use]
2431 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2432 self.meta = meta.into_option();
2433 self
2434 }
2435}
2436
2437#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2439#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2440#[serde(tag = "type", rename_all = "snake_case")]
2441#[non_exhaustive]
2442pub enum RequestPermissionSubject {
2443 ToolCall(Box<ToolCallPermissionSubject>),
2445 Command(CommandPermissionSubject),
2447 #[serde(untagged)]
2458 Other(OtherRequestPermissionSubject),
2459}
2460
2461impl From<ToolCallPermissionSubject> for RequestPermissionSubject {
2462 fn from(subject: ToolCallPermissionSubject) -> Self {
2463 Self::ToolCall(Box::new(subject))
2464 }
2465}
2466
2467impl From<ToolCallUpdate> for RequestPermissionSubject {
2468 fn from(tool_call: ToolCallUpdate) -> Self {
2469 ToolCallPermissionSubject::new(tool_call).into()
2470 }
2471}
2472
2473impl From<CommandPermissionSubject> for RequestPermissionSubject {
2474 fn from(subject: CommandPermissionSubject) -> Self {
2475 Self::Command(subject)
2476 }
2477}
2478
2479#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2481#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2482#[serde(rename_all = "camelCase")]
2483#[non_exhaustive]
2484pub struct ToolCallPermissionSubject {
2485 pub tool_call: ToolCallUpdate,
2487}
2488
2489impl ToolCallPermissionSubject {
2490 #[must_use]
2492 pub fn new(tool_call: ToolCallUpdate) -> Self {
2493 Self { tool_call }
2494 }
2495}
2496
2497#[serde_as]
2499#[skip_serializing_none]
2500#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2501#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2502#[serde(rename_all = "camelCase")]
2503#[non_exhaustive]
2504pub struct CommandPermissionSubject {
2505 pub command: String,
2507 pub cwd: AbsolutePath,
2509 #[serde_as(deserialize_as = "DefaultOnError")]
2511 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2512 #[serde(default)]
2513 pub tool_call_id: Option<ToolCallId>,
2514 #[serde_as(deserialize_as = "DefaultOnError")]
2516 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2517 #[serde(default)]
2518 pub terminal_id: Option<TerminalId>,
2519 #[serde_as(deserialize_as = "DefaultOnError")]
2525 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2526 #[serde(default)]
2527 #[serde(rename = "_meta")]
2528 pub meta: Option<Meta>,
2529}
2530
2531impl CommandPermissionSubject {
2532 #[must_use]
2534 pub fn new(command: impl Into<String>, cwd: impl Into<AbsolutePath>) -> Self {
2535 Self {
2536 command: command.into(),
2537 cwd: cwd.into(),
2538 tool_call_id: None,
2539 terminal_id: None,
2540 meta: None,
2541 }
2542 }
2543
2544 #[must_use]
2546 pub fn tool_call_id(mut self, tool_call_id: impl IntoOption<ToolCallId>) -> Self {
2547 self.tool_call_id = tool_call_id.into_option();
2548 self
2549 }
2550
2551 #[must_use]
2553 pub fn terminal_id(mut self, terminal_id: impl IntoOption<TerminalId>) -> Self {
2554 self.terminal_id = terminal_id.into_option();
2555 self
2556 }
2557
2558 #[must_use]
2560 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2561 self.meta = meta.into_option();
2562 self
2563 }
2564}
2565
2566#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2568#[derive(Debug, Clone, Serialize, PartialEq)]
2569#[cfg_attr(feature = "schemars", schemars(inline))]
2570#[cfg_attr(feature = "schemars", schemars(transform = other_request_permission_subject_schema))]
2571#[serde(rename_all = "camelCase")]
2572#[non_exhaustive]
2573pub struct OtherRequestPermissionSubject {
2574 #[serde(rename = "type")]
2580 pub type_: String,
2581 #[serde(flatten)]
2583 pub fields: BTreeMap<String, serde_json::Value>,
2584}
2585
2586impl OtherRequestPermissionSubject {
2587 #[must_use]
2589 pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
2590 fields.remove("type");
2591 Self {
2592 type_: type_.into(),
2593 fields,
2594 }
2595 }
2596}
2597
2598impl<'de> Deserialize<'de> for OtherRequestPermissionSubject {
2599 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2600 where
2601 D: serde::Deserializer<'de>,
2602 {
2603 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
2604 let type_ = fields
2605 .remove("type")
2606 .ok_or_else(|| serde::de::Error::missing_field("type"))?;
2607 let serde_json::Value::String(type_) = type_ else {
2608 return Err(serde::de::Error::custom("`type` must be a string"));
2609 };
2610
2611 if is_known_request_permission_subject_type(&type_) {
2612 return Err(serde::de::Error::custom(format!(
2613 "known request permission subject `{type_}` did not match its schema"
2614 )));
2615 }
2616
2617 Ok(Self { type_, fields })
2618 }
2619}
2620
2621fn is_known_request_permission_subject_type(type_: &str) -> bool {
2622 matches!(type_, "tool_call" | "command")
2623}
2624
2625#[cfg(feature = "schemars")]
2626fn other_request_permission_subject_schema(schema: &mut Schema) {
2627 super::schema_util::reject_known_string_discriminators(
2628 schema,
2629 "type",
2630 &["tool_call", "command"],
2631 );
2632}
2633
2634#[serde_as]
2636#[skip_serializing_none]
2637#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2638#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2639#[serde(rename_all = "camelCase")]
2640#[non_exhaustive]
2641pub struct PermissionOption {
2642 pub option_id: PermissionOptionId,
2644 pub name: String,
2646 pub kind: PermissionOptionKind,
2648 #[serde_as(deserialize_as = "DefaultOnError")]
2654 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2655 #[serde(default)]
2656 #[serde(rename = "_meta")]
2657 pub meta: Option<Meta>,
2658}
2659
2660impl PermissionOption {
2661 #[must_use]
2663 pub fn new(
2664 option_id: impl Into<PermissionOptionId>,
2665 name: impl Into<String>,
2666 kind: PermissionOptionKind,
2667 ) -> Self {
2668 Self {
2669 option_id: option_id.into(),
2670 name: name.into(),
2671 kind,
2672 meta: None,
2673 }
2674 }
2675
2676 #[must_use]
2682 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2683 self.meta = meta.into_option();
2684 self
2685 }
2686}
2687
2688#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2690#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
2691#[serde(transparent)]
2692#[from(forward)]
2693#[non_exhaustive]
2694pub struct PermissionOptionId(pub Arc<str>);
2695
2696impl PermissionOptionId {
2697 #[must_use]
2699 pub fn new(id: impl Into<Self>) -> Self {
2700 id.into()
2701 }
2702}
2703
2704#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2708#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2709#[serde(rename_all = "snake_case")]
2710#[non_exhaustive]
2711pub enum PermissionOptionKind {
2712 AllowOnce,
2714 AllowAlways,
2716 RejectOnce,
2718 RejectAlways,
2720 #[serde(untagged)]
2726 Other(String),
2727}
2728
2729#[serde_as]
2731#[skip_serializing_none]
2732#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2733#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2734#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = SESSION_REQUEST_PERMISSION_METHOD_NAME)))]
2735#[serde(rename_all = "camelCase")]
2736#[non_exhaustive]
2737pub struct RequestPermissionResponse {
2738 pub outcome: RequestPermissionOutcome,
2740 #[serde_as(deserialize_as = "DefaultOnError")]
2746 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2747 #[serde(default)]
2748 #[serde(rename = "_meta")]
2749 pub meta: Option<Meta>,
2750}
2751
2752impl RequestPermissionResponse {
2753 #[must_use]
2755 pub fn new(outcome: RequestPermissionOutcome) -> Self {
2756 Self {
2757 outcome,
2758 meta: None,
2759 }
2760 }
2761
2762 #[must_use]
2768 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2769 self.meta = meta.into_option();
2770 self
2771 }
2772}
2773
2774#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2776#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2777#[serde(tag = "outcome", rename_all = "snake_case")]
2778#[non_exhaustive]
2779pub enum RequestPermissionOutcome {
2780 Cancelled,
2788 #[serde(rename_all = "camelCase")]
2790 Selected(SelectedPermissionOutcome),
2791 #[serde(untagged)]
2802 Other(OtherRequestPermissionOutcome),
2803}
2804
2805#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2811#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
2812#[cfg_attr(feature = "schemars", schemars(inline))]
2813#[cfg_attr(feature = "schemars", schemars(transform = other_request_permission_outcome_schema))]
2814#[serde(rename_all = "camelCase")]
2815#[non_exhaustive]
2816pub struct OtherRequestPermissionOutcome {
2817 pub outcome: String,
2823 #[serde(flatten)]
2825 pub fields: BTreeMap<String, serde_json::Value>,
2826}
2827
2828impl OtherRequestPermissionOutcome {
2829 #[must_use]
2831 pub fn new(
2832 outcome: impl Into<String>,
2833 mut fields: BTreeMap<String, serde_json::Value>,
2834 ) -> Self {
2835 fields.remove("outcome");
2836 Self {
2837 outcome: outcome.into(),
2838 fields,
2839 }
2840 }
2841}
2842
2843impl<'de> Deserialize<'de> for OtherRequestPermissionOutcome {
2844 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2845 where
2846 D: serde::Deserializer<'de>,
2847 {
2848 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
2849 let outcome = fields
2850 .remove("outcome")
2851 .ok_or_else(|| serde::de::Error::missing_field("outcome"))?;
2852 let serde_json::Value::String(outcome) = outcome else {
2853 return Err(serde::de::Error::custom("`outcome` must be a string"));
2854 };
2855
2856 if is_known_request_permission_outcome(&outcome) {
2857 return Err(serde::de::Error::custom(format!(
2858 "known request permission outcome `{outcome}` did not match its schema"
2859 )));
2860 }
2861
2862 Ok(Self { outcome, fields })
2863 }
2864}
2865
2866fn is_known_request_permission_outcome(outcome: &str) -> bool {
2867 matches!(outcome, "cancelled" | "selected")
2868}
2869
2870#[cfg(feature = "schemars")]
2871fn other_request_permission_outcome_schema(schema: &mut Schema) {
2872 super::schema_util::reject_known_string_discriminators(
2873 schema,
2874 "outcome",
2875 &["cancelled", "selected"],
2876 );
2877}
2878
2879#[serde_as]
2881#[skip_serializing_none]
2882#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2883#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2884#[serde(rename_all = "camelCase")]
2885#[non_exhaustive]
2886pub struct SelectedPermissionOutcome {
2887 pub option_id: PermissionOptionId,
2889 #[serde_as(deserialize_as = "DefaultOnError")]
2895 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2896 #[serde(default)]
2897 #[serde(rename = "_meta")]
2898 pub meta: Option<Meta>,
2899}
2900
2901impl SelectedPermissionOutcome {
2902 #[must_use]
2904 pub fn new(option_id: impl Into<PermissionOptionId>) -> Self {
2905 Self {
2906 option_id: option_id.into(),
2907 meta: None,
2908 }
2909 }
2910
2911 #[must_use]
2917 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2918 self.meta = meta.into_option();
2919 self
2920 }
2921}
2922
2923#[serde_as]
2932#[skip_serializing_none]
2933#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2934#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2935#[serde(rename_all = "camelCase")]
2936#[non_exhaustive]
2937pub struct ClientCapabilities {
2938 #[serde_as(deserialize_as = "DefaultOnError")]
2945 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2946 #[serde(default)]
2947 pub auth: Option<AuthCapabilities>,
2948 #[serde_as(deserialize_as = "DefaultOnError")]
2954 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2955 #[serde(default)]
2956 pub elicitation: Option<ElicitationCapabilities>,
2957 #[cfg(feature = "unstable_nes")]
2966 #[serde_as(deserialize_as = "DefaultOnError")]
2967 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2968 #[serde(default)]
2969 pub nes: Option<ClientNesCapabilities>,
2970 #[cfg(feature = "unstable_nes")]
2976 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
2977 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
2978 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2979 pub position_encodings: Vec<PositionEncodingKind>,
2980
2981 #[serde_as(deserialize_as = "DefaultOnError")]
2987 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2988 #[serde(default)]
2989 #[serde(rename = "_meta")]
2990 pub meta: Option<Meta>,
2991}
2992
2993impl ClientCapabilities {
2994 #[must_use]
2996 pub fn new() -> Self {
2997 Self::default()
2998 }
2999
3000 #[must_use]
3004 pub fn auth(mut self, auth: impl IntoOption<AuthCapabilities>) -> Self {
3005 self.auth = auth.into_option();
3006 self
3007 }
3008
3009 #[must_use]
3012 pub fn elicitation(mut self, elicitation: impl IntoOption<ElicitationCapabilities>) -> Self {
3013 self.elicitation = elicitation.into_option();
3014 self
3015 }
3016
3017 #[cfg(feature = "unstable_nes")]
3021 #[must_use]
3022 pub fn nes(mut self, nes: impl IntoOption<ClientNesCapabilities>) -> Self {
3023 self.nes = nes.into_option();
3024 self
3025 }
3026
3027 #[cfg(feature = "unstable_nes")]
3031 #[must_use]
3032 pub fn position_encodings(mut self, position_encodings: Vec<PositionEncodingKind>) -> Self {
3033 self.position_encodings = position_encodings;
3034 self
3035 }
3036
3037 #[must_use]
3043 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3044 self.meta = meta.into_option();
3045 self
3046 }
3047}
3048
3049#[serde_as]
3055#[skip_serializing_none]
3056#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3057#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3058#[serde(rename_all = "camelCase")]
3059#[non_exhaustive]
3060pub struct AuthCapabilities {
3061 #[serde_as(deserialize_as = "DefaultOnError")]
3068 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3069 #[serde(default)]
3070 pub terminal: Option<TerminalAuthCapabilities>,
3071 #[serde_as(deserialize_as = "DefaultOnError")]
3077 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3078 #[serde(default)]
3079 #[serde(rename = "_meta")]
3080 pub meta: Option<Meta>,
3081}
3082
3083impl AuthCapabilities {
3084 #[must_use]
3086 pub fn new() -> Self {
3087 Self::default()
3088 }
3089
3090 #[must_use]
3098 pub fn terminal(mut self, terminal: impl IntoOption<TerminalAuthCapabilities>) -> Self {
3099 self.terminal = terminal.into_option();
3100 self
3101 }
3102
3103 #[must_use]
3109 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3110 self.meta = meta.into_option();
3111 self
3112 }
3113}
3114
3115#[serde_as]
3121#[skip_serializing_none]
3122#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3123#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3124#[non_exhaustive]
3125pub struct TerminalAuthCapabilities {
3126 #[serde_as(deserialize_as = "DefaultOnError")]
3132 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3133 #[serde(default)]
3134 #[serde(rename = "_meta")]
3135 pub meta: Option<Meta>,
3136}
3137
3138impl TerminalAuthCapabilities {
3139 #[must_use]
3141 pub fn new() -> Self {
3142 Self::default()
3143 }
3144
3145 #[must_use]
3151 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3152 self.meta = meta.into_option();
3153 self
3154 }
3155}
3156
3157#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3163#[non_exhaustive]
3164pub struct ClientMethodNames {
3165 pub session_request_permission: &'static str,
3167 pub session_update: &'static str,
3169 #[cfg(feature = "unstable_mcp_over_acp")]
3171 pub mcp_message: &'static str,
3172 pub elicitation_create: &'static str,
3174 pub elicitation_complete: &'static str,
3176}
3177
3178pub const CLIENT_METHOD_NAMES: ClientMethodNames = ClientMethodNames {
3180 session_update: SESSION_UPDATE_NOTIFICATION,
3181 session_request_permission: SESSION_REQUEST_PERMISSION_METHOD_NAME,
3182 #[cfg(feature = "unstable_mcp_over_acp")]
3183 mcp_message: MCP_MESSAGE_METHOD_NAME,
3184 elicitation_create: ELICITATION_CREATE_METHOD_NAME,
3185 elicitation_complete: ELICITATION_COMPLETE_NOTIFICATION,
3186};
3187
3188pub(crate) const SESSION_UPDATE_NOTIFICATION: &str = "session/update";
3190pub(crate) const SESSION_REQUEST_PERMISSION_METHOD_NAME: &str = "session/request_permission";
3192pub(crate) const ELICITATION_CREATE_METHOD_NAME: &str = "elicitation/create";
3194pub(crate) const ELICITATION_COMPLETE_NOTIFICATION: &str = "elicitation/complete";
3196
3197#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3204#[derive(Clone, Debug, Serialize, Deserialize)]
3205#[serde(untagged)]
3206#[cfg_attr(feature = "schemars", schemars(inline))]
3207#[non_exhaustive]
3208pub enum AgentRequest {
3209 RequestPermissionRequest(Box<RequestPermissionRequest>),
3220 CreateElicitationRequest(Box<CreateElicitationRequest>),
3224 #[cfg(feature = "unstable_mcp_over_acp")]
3230 MessageMcpRequest(Box<MessageMcpRequest>),
3231 ExtMethodRequest(Box<ExtRequest>),
3239}
3240
3241impl AgentRequest {
3242 #[must_use]
3244 pub fn method(&self) -> &str {
3245 match self {
3246 Self::RequestPermissionRequest(_) => CLIENT_METHOD_NAMES.session_request_permission,
3247 Self::CreateElicitationRequest(_) => CLIENT_METHOD_NAMES.elicitation_create,
3248 #[cfg(feature = "unstable_mcp_over_acp")]
3249 Self::MessageMcpRequest(_) => CLIENT_METHOD_NAMES.mcp_message,
3250 Self::ExtMethodRequest(ext_request) => &ext_request.method,
3251 }
3252 }
3253}
3254
3255#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3262#[derive(Clone, Debug, Serialize, Deserialize)]
3263#[serde(untagged)]
3264#[cfg_attr(feature = "schemars", schemars(inline))]
3265#[non_exhaustive]
3266pub enum ClientResponse {
3267 RequestPermissionResponse(Box<RequestPermissionResponse>),
3269 CreateElicitationResponse(Box<CreateElicitationResponse>),
3271 #[cfg(feature = "unstable_mcp_over_acp")]
3273 MessageMcpResponse(Box<MessageMcpResponse>),
3274 ExtMethodResponse(Box<ExtResponse>),
3276}
3277
3278#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3285#[derive(Clone, Debug, Serialize, Deserialize)]
3286#[serde(untagged)]
3287#[cfg_attr(feature = "schemars", schemars(inline))]
3288#[non_exhaustive]
3289pub enum AgentNotification {
3290 UpdateSessionNotification(Box<UpdateSessionNotification>),
3303 CompleteElicitationNotification(Box<CompleteElicitationNotification>),
3307 ExtNotification(Box<ExtNotification>),
3315}
3316
3317impl AgentNotification {
3318 #[must_use]
3320 pub fn method(&self) -> &str {
3321 match self {
3322 Self::UpdateSessionNotification(_) => CLIENT_METHOD_NAMES.session_update,
3323 Self::CompleteElicitationNotification(_) => CLIENT_METHOD_NAMES.elicitation_complete,
3324 Self::ExtNotification(ext_notification) => &ext_notification.method,
3325 }
3326 }
3327}
3328
3329#[cfg(test)]
3330mod tests {
3331 use super::*;
3332
3333 #[cfg(feature = "unstable_subagents")]
3334 #[test]
3335 fn subagent_update_serializes_as_upsert() {
3336 use serde_json::json;
3337
3338 let announced =
3339 SessionUpdate::SubagentUpdate(SubagentUpdate::new("sess_child_1").capabilities(
3340 SubagentSessionCapabilities::new().cancel(SessionCancelCapabilities::new()),
3341 ));
3342 let wire = json!({
3343 "sessionUpdate": "subagent_update",
3344 "sessionId": "sess_child_1",
3345 "capabilities": { "cancel": {} }
3346 });
3347 assert_eq!(serde_json::to_value(&announced).unwrap(), wire);
3348 assert_eq!(
3349 serde_json::from_value::<SessionUpdate>(wire).unwrap(),
3350 announced
3351 );
3352
3353 let minimal = SubagentUpdate::new("sess_child_1");
3354 assert_eq!(
3355 serde_json::to_value(&minimal).unwrap(),
3356 json!({
3357 "sessionId": "sess_child_1"
3358 })
3359 );
3360 assert!(minimal.capabilities.is_undefined());
3361 assert!(minimal.state.is_undefined());
3362 assert!(minimal.meta.is_undefined());
3363
3364 let cleared_wire = json!({
3366 "sessionId": "sess_child_1",
3367 "capabilities": null,
3368 "state": null,
3369 "_meta": null
3370 });
3371 let cleared: SubagentUpdate = serde_json::from_value(cleared_wire.clone()).unwrap();
3372 assert!(cleared.capabilities.is_null());
3373 assert!(cleared.state.is_null());
3374 assert!(cleared.meta.is_null());
3375 assert_eq!(serde_json::to_value(cleared).unwrap(), cleared_wire);
3376
3377 let revoked: SubagentUpdate = serde_json::from_value(json!({
3378 "sessionId": "sess_child_1",
3379 "capabilities": {}
3380 }))
3381 .unwrap();
3382 assert_eq!(
3383 revoked.capabilities,
3384 MaybeUndefined::Value(SubagentSessionCapabilities::new())
3385 );
3386 assert!(
3387 matches!(revoked.capabilities, MaybeUndefined::Value(ref child) if child.cancel.is_none())
3388 );
3389
3390 assert!(
3391 serde_json::from_value::<SessionUpdate>(json!({
3392 "sessionUpdate": "subagent_update"
3393 }))
3394 .is_err()
3395 );
3396 }
3397
3398 #[cfg(feature = "unstable_subagents")]
3399 #[test]
3400 fn subagent_cancel_capability_is_optional_object() {
3401 use serde_json::json;
3402
3403 for wire in [
3404 json!({}),
3405 json!({"cancel": null}),
3406 json!({"cancel": true}),
3407 json!({"cancel": false}),
3408 ] {
3409 let child: SubagentSessionCapabilities = serde_json::from_value(wire).unwrap();
3410 assert!(child.cancel.is_none());
3411 assert_eq!(serde_json::to_value(child).unwrap(), json!({}));
3412 }
3413 let enabled = SubagentSessionCapabilities::new().cancel(SessionCancelCapabilities::new());
3414 assert_eq!(
3415 serde_json::to_value(&enabled).unwrap(),
3416 json!({"cancel": {}})
3417 );
3418 assert_eq!(
3419 serde_json::from_value::<SubagentSessionCapabilities>(json!({"cancel": {}})).unwrap(),
3420 enabled
3421 );
3422 let meta: Meta = [("source".into(), json!("worker"))].into_iter().collect();
3423 let with_meta =
3424 SubagentSessionCapabilities::new().cancel(SessionCancelCapabilities::new().meta(meta));
3425 let wire = json!({"cancel": {"_meta": {"source": "worker"}}});
3426 assert_eq!(serde_json::to_value(&with_meta).unwrap(), wire);
3427 assert_eq!(
3428 serde_json::from_value::<SubagentSessionCapabilities>(wire).unwrap(),
3429 with_meta
3430 );
3431 assert!(enabled.cancel.is_some());
3432 assert!(enabled.cancel(None).cancel.is_none());
3433 let removed = SubagentUpdate::new("child").capabilities(SubagentSessionCapabilities::new());
3434 assert_eq!(
3435 serde_json::to_value(removed).unwrap(),
3436 json!({"sessionId": "child", "capabilities": {}})
3437 );
3438 }
3439
3440 #[cfg(feature = "unstable_subagents")]
3441 #[test]
3442 fn subagent_display_metadata_uses_patch_semantics() {
3443 use serde_json::json;
3444
3445 let update = SubagentUpdate::new("child")
3446 .title("Test investigator".to_string())
3447 .description("Investigates platform-specific test failures.".to_string());
3448 let wire = json!({
3449 "sessionId": "child",
3450 "title": "Test investigator",
3451 "description": "Investigates platform-specific test failures."
3452 });
3453 assert_eq!(serde_json::to_value(&update).unwrap(), wire);
3454 assert_eq!(
3455 serde_json::from_value::<SubagentUpdate>(wire).unwrap(),
3456 update
3457 );
3458
3459 let minimal = SubagentUpdate::new("child");
3460 assert!(minimal.title.is_undefined());
3461 assert!(minimal.description.is_undefined());
3462 assert_eq!(
3463 serde_json::to_value(&minimal).unwrap(),
3464 json!({"sessionId": "child"})
3465 );
3466 let cleared_wire = json!({
3467 "sessionId": "child", "title": null, "description": null
3468 });
3469 let cleared: SubagentUpdate = serde_json::from_value(cleared_wire.clone()).unwrap();
3470 assert!(cleared.title.is_null());
3471 assert!(cleared.description.is_null());
3472 assert_eq!(serde_json::to_value(cleared).unwrap(), cleared_wire);
3473
3474 let title_only: SubagentUpdate = serde_json::from_value(json!({
3475 "sessionId": "child", "title": "Updated title"
3476 }))
3477 .unwrap();
3478 assert_eq!(
3479 title_only.title,
3480 MaybeUndefined::Value("Updated title".to_string())
3481 );
3482 assert!(title_only.description.is_undefined());
3483 let malformed: SubagentUpdate = serde_json::from_value(json!({
3484 "sessionId": "child", "title": false, "description": false
3485 }))
3486 .unwrap();
3487 assert_eq!(malformed, minimal);
3488 }
3489
3490 #[cfg(feature = "unstable_subagents")]
3491 #[test]
3492 fn subagent_notification_keeps_parent_and_child_ids_nested() {
3493 use serde_json::json;
3494
3495 let notification = UpdateSessionNotification::new(
3496 "parent",
3497 SessionUpdate::SubagentUpdate(SubagentUpdate::new("child")),
3498 );
3499 let wire = json!({
3500 "sessionId": "parent",
3501 "update": {
3502 "sessionUpdate": "subagent_update",
3503 "sessionId": "child"
3504 }
3505 });
3506 assert_eq!(serde_json::to_value(¬ification).unwrap(), wire);
3507 assert_eq!(
3508 serde_json::from_value::<UpdateSessionNotification>(wire).unwrap(),
3509 notification
3510 );
3511 for malformed in [
3512 json!({"sessionId": "parent", "update": {"sessionUpdate": "subagent_update"}}),
3513 json!({"update": {"sessionUpdate": "subagent_update", "sessionId": "child"}}),
3514 ] {
3515 assert!(serde_json::from_value::<UpdateSessionNotification>(malformed).is_err());
3516 }
3517 }
3518
3519 #[cfg(feature = "unstable_subagents")]
3520 #[test]
3521 fn subagent_mirrors_normal_child_state_notifications() {
3522 use serde_json::json;
3523
3524 let association = SubagentUpdate::new("sess_child");
3525 for state in [
3526 StateUpdate::Running(RunningStateUpdate::new()),
3527 StateUpdate::RequiresAction(RequiresActionStateUpdate::new()),
3528 StateUpdate::Unknown(UnknownStateUpdate::new()),
3529 StateUpdate::Running(RunningStateUpdate::new()),
3530 StateUpdate::Idle(IdleStateUpdate::new().stop_reason(StopReason::EndTurn)),
3531 StateUpdate::Running(RunningStateUpdate::new()),
3532 StateUpdate::Idle(IdleStateUpdate::new().stop_reason(StopReason::Cancelled)),
3533 ] {
3534 let child_notification = UpdateSessionNotification::new(
3535 association.session_id.clone(),
3536 SessionUpdate::StateUpdate(state.clone()),
3537 );
3538 let parent_notification = UpdateSessionNotification::new(
3539 "sess_parent",
3540 SessionUpdate::SubagentUpdate(association.clone().state(state)),
3541 );
3542 let child_wire = serde_json::to_value(&child_notification).unwrap();
3543 let parent_wire = serde_json::to_value(&parent_notification).unwrap();
3544 assert_eq!(child_wire["sessionId"], json!("sess_child"));
3545 assert_eq!(child_wire["update"]["sessionUpdate"], json!("state_update"));
3546 assert_eq!(parent_wire["sessionId"], json!("sess_parent"));
3547 assert_eq!(parent_wire["update"]["sessionId"], json!("sess_child"));
3548 let mut child_snapshot = child_wire["update"].clone();
3549 child_snapshot
3550 .as_object_mut()
3551 .unwrap()
3552 .remove("sessionUpdate");
3553 assert_eq!(parent_wire["update"]["state"], child_snapshot);
3554 assert_eq!(
3555 serde_json::from_value::<UpdateSessionNotification>(child_wire).unwrap(),
3556 child_notification
3557 );
3558 assert_eq!(
3559 serde_json::from_value::<UpdateSessionNotification>(parent_wire).unwrap(),
3560 parent_notification
3561 );
3562 }
3563 }
3564
3565 #[cfg(feature = "unstable_subagents")]
3566 #[test]
3567 fn subagent_state_uses_whole_snapshot_patch_semantics() {
3568 use serde_json::json;
3569
3570 let minimal = SubagentUpdate::new("child");
3571 assert!(minimal.state.is_undefined());
3572 assert_eq!(
3573 serde_json::to_value(&minimal).unwrap(),
3574 json!({"sessionId": "child"})
3575 );
3576
3577 let cleared = minimal.clone().state(None::<StateUpdate>);
3578 assert!(cleared.state.is_null());
3579 assert_eq!(
3580 serde_json::to_value(&cleared).unwrap(),
3581 json!({"sessionId": "child", "state": null})
3582 );
3583 assert_eq!(
3584 serde_json::from_value::<SubagentUpdate>(json!({"sessionId": "child", "state": null}))
3585 .unwrap(),
3586 cleared
3587 );
3588
3589 for snapshot in [
3590 json!({"state": "running", "_meta": {"source": "worker"}}),
3591 json!({"state": "idle", "stopReason": "end_turn"}),
3592 json!({"state": "requires_action"}),
3593 json!({"state": "unknown"}),
3594 json!({"state": "_custom", "detail": {"phase": 2}}),
3595 ] {
3596 let state: StateUpdate = serde_json::from_value(snapshot.clone()).unwrap();
3597 let update = minimal.clone().state(state.clone());
3598 let wire = json!({"sessionId": "child", "state": snapshot});
3599 assert_eq!(serde_json::to_value(&update).unwrap(), wire);
3600 assert_eq!(
3601 serde_json::from_value::<SubagentUpdate>(wire).unwrap(),
3602 update
3603 );
3604 assert_eq!(update.state, MaybeUndefined::Value(state));
3605 }
3606
3607 let idle = SubagentUpdate::new("child").state(StateUpdate::Idle(
3610 IdleStateUpdate::new().stop_reason(StopReason::Cancelled),
3611 ));
3612 let running = idle.state(StateUpdate::Running(RunningStateUpdate::new()));
3613 assert_eq!(
3614 serde_json::to_value(running).unwrap(),
3615 json!({"sessionId": "child", "state": {"state": "running"}})
3616 );
3617 for invalid in [json!(false), json!(42), json!({}), json!({"state": 7})] {
3618 let parsed: SubagentUpdate =
3619 serde_json::from_value(json!({"sessionId": "child", "state": invalid})).unwrap();
3620 assert_eq!(parsed, minimal);
3621 }
3622 }
3623
3624 #[cfg(feature = "unstable_subagents")]
3625 #[test]
3626 fn unknown_activity_is_a_known_state_update() {
3627 use serde_json::json;
3628
3629 let update = SessionUpdate::StateUpdate(StateUpdate::Unknown(
3630 UnknownStateUpdate::new().meta(
3631 [("source".into(), json!("worker"))]
3632 .into_iter()
3633 .collect::<Meta>(),
3634 ),
3635 ));
3636 let wire = json!({
3637 "sessionUpdate": "state_update",
3638 "state": "unknown",
3639 "_meta": { "source": "worker" }
3640 });
3641 assert_eq!(serde_json::to_value(&update).unwrap(), wire);
3642 assert_eq!(
3643 serde_json::from_value::<SessionUpdate>(wire).unwrap(),
3644 update
3645 );
3646
3647 for meta in [json!(null), json!(false)] {
3648 let state: StateUpdate = serde_json::from_value(json!({
3649 "state": "unknown",
3650 "_meta": meta
3651 }))
3652 .unwrap();
3653 assert_eq!(state, StateUpdate::Unknown(UnknownStateUpdate::new()));
3654 assert_eq!(
3655 serde_json::to_value(state).unwrap(),
3656 json!({"state": "unknown"})
3657 );
3658 }
3659 assert!(serde_json::from_value::<OtherStateUpdate>(json!({"state": "unknown"})).is_err());
3660 }
3661
3662 #[cfg(not(feature = "unstable_subagents"))]
3663 #[test]
3664 fn unknown_activity_is_preserved_without_subagents_feature() {
3665 let wire = serde_json::json!({
3666 "sessionUpdate": "state_update",
3667 "state": "unknown",
3668 "_meta": { "source": "worker" }
3669 });
3670 let parsed: SessionUpdate = serde_json::from_value(wire.clone()).unwrap();
3671 let SessionUpdate::StateUpdate(StateUpdate::Other(state)) = &parsed else {
3672 panic!("expected unrecognized state payload");
3673 };
3674 assert_eq!(state.state, "unknown");
3675 assert_eq!(serde_json::to_value(parsed).unwrap(), wire);
3676 }
3677
3678 #[cfg(all(feature = "unstable_subagents", feature = "schemars"))]
3679 #[test]
3680 fn subagent_schema_carries_optional_nullable_state_snapshot() {
3681 use serde_json::json;
3682
3683 let schema = serde_json::to_value(schemars::schema_for!(SubagentUpdate)).unwrap();
3684 let properties = schema["properties"].as_object().unwrap();
3685 assert!(properties.contains_key("sessionId"));
3686 assert!(properties.contains_key("title"));
3687 assert!(properties.contains_key("description"));
3688 assert!(properties.contains_key("capabilities"));
3689 assert!(properties.contains_key("state"));
3690 assert!(properties.contains_key("_meta"));
3691 assert!(!properties.contains_key("name"));
3692 assert!(!properties.contains_key("task"));
3693 assert_eq!(schema["required"], json!(["sessionId"]));
3694 assert_eq!(
3695 properties["state"]["x-deserialize-default-on-error"],
3696 json!(true)
3697 );
3698 let state_variants = properties["state"]["anyOf"].as_array().unwrap();
3699 assert!(state_variants.contains(&json!({"type": "null"})));
3700 assert!(
3701 state_variants
3702 .iter()
3703 .any(|variant| variant["$ref"] == "#/$defs/StateUpdate")
3704 );
3705 let child =
3706 serde_json::to_value(schemars::schema_for!(SubagentSessionCapabilities)).unwrap();
3707 let variants = child["properties"]["cancel"]["anyOf"]
3708 .as_array()
3709 .expect("cancel must allow the capability object or null");
3710 assert!(variants.contains(&json!({"type": "null"})));
3711 assert!(
3712 variants
3713 .iter()
3714 .any(|variant| variant["type"] == "object" || variant.get("$ref").is_some())
3715 );
3716 assert!(!variants.iter().any(|variant| variant["type"] == "boolean"));
3717 let cancel =
3718 serde_json::to_value(schemars::schema_for!(SessionCancelCapabilities)).unwrap();
3719 assert_eq!(cancel["type"], "object");
3720 assert!(
3721 !child["required"]
3722 .as_array()
3723 .is_some_and(|required| required.contains(&json!("cancel")))
3724 );
3725 }
3726
3727 #[cfg(not(feature = "unstable_subagents"))]
3728 #[test]
3729 fn unsupported_subagent_update_is_preserved() {
3730 let wire = serde_json::json!({
3731 "sessionUpdate": "subagent_update",
3732 "sessionId": "sess_child",
3733 "capabilities": { "cancel": {} }
3734 });
3735 let parsed: SessionUpdate = serde_json::from_value(wire.clone()).unwrap();
3736 assert!(matches!(&parsed, SessionUpdate::Other(_)));
3737 assert_eq!(serde_json::to_value(parsed).unwrap(), wire);
3738 }
3739
3740 #[test]
3741 fn notice_preserves_wire_shape_nullable_fields_and_open_severity() {
3742 use serde_json::json;
3743
3744 let mut meta = Meta::new();
3745 meta.insert("source".into(), json!("fallback"));
3746 let v2_notice = SessionUpdate::Notice(
3747 Notice::new(NoticeSeverity::Error, "Provider degraded")
3748 .description("Requests may take longer than usual.")
3749 .meta(meta.clone()),
3750 );
3751 let expected = json!({
3752 "sessionUpdate": "notice",
3753 "severity": "error",
3754 "title": "Provider degraded",
3755 "description": "Requests may take longer than usual.",
3756 "_meta": { "source": "fallback" }
3757 });
3758 assert_eq!(serde_json::to_value(&v2_notice).unwrap(), expected);
3759
3760 let v1_notice = crate::v1::SessionUpdate::Notice(
3761 crate::v1::Notice::new(crate::v1::NoticeSeverity::Error, "Provider degraded")
3762 .description("Requests may take longer than usual.")
3763 .meta(meta),
3764 );
3765 assert_eq!(
3766 serde_json::to_value(v2_notice).unwrap(),
3767 serde_json::to_value(v1_notice).unwrap()
3768 );
3769
3770 let SessionUpdate::Notice(notice) = serde_json::from_value(json!({
3771 "sessionUpdate": "notice",
3772 "severity": "critical",
3773 "title": "Provider degraded",
3774 "description": null,
3775 "_meta": null
3776 }))
3777 .unwrap() else {
3778 panic!("expected notice");
3779 };
3780
3781 assert_eq!(
3782 notice.severity,
3783 NoticeSeverity::Other("critical".to_string())
3784 );
3785 assert_eq!(notice.description, None);
3786 assert_eq!(notice.meta, None);
3787 assert_eq!(
3788 serde_json::to_value(SessionUpdate::Notice(notice)).unwrap(),
3789 json!({
3790 "sessionUpdate": "notice",
3791 "severity": "critical",
3792 "title": "Provider degraded"
3793 })
3794 );
3795 }
3796
3797 #[test]
3798 fn malformed_known_notice_is_not_hidden_as_unknown() {
3799 use serde_json::json;
3800
3801 for malformed in [
3802 json!({
3803 "sessionUpdate": "notice",
3804 "severity": "warning",
3805 "title": 42
3806 }),
3807 json!({
3808 "sessionUpdate": "notice",
3809 "severity": 42,
3810 "title": "MCP server unavailable"
3811 }),
3812 json!({
3813 "sessionUpdate": "notice",
3814 "severity": "warning"
3815 }),
3816 json!({
3817 "sessionUpdate": "notice",
3818 "severity": "warning",
3819 "title": null
3820 }),
3821 json!({
3822 "sessionUpdate": "notice",
3823 "title": "MCP server unavailable"
3824 }),
3825 json!({
3826 "sessionUpdate": "notice",
3827 "severity": null,
3828 "title": "MCP server unavailable"
3829 }),
3830 ] {
3831 assert!(serde_json::from_str::<SessionUpdate>(&malformed.to_string()).is_err());
3832 assert!(serde_json::from_value::<SessionUpdate>(malformed).is_err());
3833 }
3834 }
3835
3836 #[test]
3837 fn notice_tolerates_invalid_optional_display_fields_and_custom_severity() {
3838 use serde_json::json;
3839
3840 let wire = json!({
3841 "sessionUpdate": "notice",
3842 "severity": "_custom",
3843 "title": "Provider degraded",
3844 "description": 42,
3845 "_meta": false
3846 });
3847 let SessionUpdate::Notice(notice) = serde_json::from_value(wire.clone()).unwrap() else {
3848 panic!("expected notice");
3849 };
3850 assert_eq!(
3851 serde_json::from_str::<SessionUpdate>(&wire.to_string()).unwrap(),
3852 SessionUpdate::Notice(notice.clone())
3853 );
3854 assert_eq!(notice.severity, NoticeSeverity::Other("_custom".into()));
3855 assert_eq!(notice.description, None);
3856 assert_eq!(notice.meta, None);
3857 assert_eq!(
3858 serde_json::to_value(notice).unwrap(),
3859 json!({"severity": "_custom", "title": "Provider degraded"})
3860 );
3861 }
3862
3863 #[cfg(feature = "schemars")]
3864 #[test]
3865 fn notice_schema_preserves_required_fields_and_excludes_unknown_fallback() {
3866 use serde_json::json;
3867
3868 let schema = serde_json::to_value(schemars::schema_for!(Notice)).unwrap();
3869 assert_eq!(schema["required"], json!(["severity", "title"]));
3870 assert_eq!(schema["properties"]["title"]["minLength"], 1);
3871 for field in ["description", "_meta"] {
3872 assert!(
3873 schema["properties"][field]["type"]
3874 .as_array()
3875 .unwrap()
3876 .contains(&json!("null"))
3877 );
3878 }
3879 let fallback = serde_json::to_value(schemars::schema_for!(OtherSessionUpdate)).unwrap();
3880 assert!(
3881 fallback["not"]["anyOf"]
3882 .as_array()
3883 .unwrap()
3884 .iter()
3885 .any(|variant| variant["properties"]["sessionUpdate"]["const"] == "notice")
3886 );
3887 }
3888
3889 #[test]
3890 fn compaction_updates_preserve_patch_and_open_status_semantics() {
3891 use serde_json::json;
3892
3893 assert_eq!(
3894 serde_json::to_value(SessionUpdate::CompactionUpdate(
3895 CompactionUpdate::new("cmp_001", CompactionStatus::Completed).summary(vec![
3896 ContentBlock::Text(crate::v2::TextContent::new("retained")),
3897 ]),
3898 ))
3899 .unwrap(),
3900 json!({
3901 "sessionUpdate": "compaction_update",
3902 "compactionId": "cmp_001",
3903 "status": "completed",
3904 "summary": [{ "type": "text", "text": "retained" }]
3905 })
3906 );
3907
3908 let SessionUpdate::CompactionUpdate(update) = serde_json::from_value(json!({
3909 "sessionUpdate": "compaction_update",
3910 "compactionId": "cmp_001",
3911 "status": "paused",
3912 "summary": null
3913 }))
3914 .unwrap() else {
3915 panic!("expected compaction update");
3916 };
3917 assert_eq!(update.status, CompactionStatus::Other("paused".into()));
3918 assert!(update.summary.is_null());
3919 assert!(update.error.is_undefined());
3920 }
3921
3922 #[test]
3923 fn malformed_known_compaction_update_is_not_hidden_as_unknown() {
3924 use serde_json::json;
3925
3926 assert!(
3927 serde_json::from_value::<SessionUpdate>(json!({
3928 "sessionUpdate": "compaction_update",
3929 "status": "completed"
3930 }))
3931 .is_err()
3932 );
3933 assert!(
3934 serde_json::from_value::<SessionUpdate>(json!({
3935 "sessionUpdate": "compaction_summary_chunk",
3936 "compactionId": "cmp_001"
3937 }))
3938 .is_err()
3939 );
3940 }
3941
3942 #[test]
3943 fn test_elicitation_capability_semantics() {
3944 use serde_json::json;
3945
3946 let unsupported: ClientCapabilities = serde_json::from_value(json!({})).unwrap();
3947 assert!(unsupported.elicitation.is_none());
3948
3949 let null: ClientCapabilities =
3950 serde_json::from_value(json!({ "elicitation": null })).unwrap();
3951 assert!(null.elicitation.is_none());
3952
3953 let malformed: ClientCapabilities =
3954 serde_json::from_value(json!({ "elicitation": false })).unwrap();
3955 assert!(malformed.elicitation.is_none());
3956
3957 let empty: ClientCapabilities =
3958 serde_json::from_value(json!({ "elicitation": {} })).unwrap();
3959 let empty = empty.elicitation.expect("present capability");
3960 assert!(!empty.supports_form());
3961 assert!(!empty.supports_url());
3962
3963 let form_only: ClientCapabilities = serde_json::from_value(json!({
3964 "elicitation": { "form": {} }
3965 }))
3966 .unwrap();
3967 let form_only = form_only.elicitation.expect("advertised capability");
3968 assert!(form_only.supports_form());
3969 assert!(!form_only.supports_url());
3970
3971 let url_only: ClientCapabilities = serde_json::from_value(json!({
3972 "elicitation": { "url": {} }
3973 }))
3974 .unwrap();
3975 let url_only = url_only.elicitation.expect("advertised capability");
3976 assert!(!url_only.supports_form());
3977 assert!(url_only.supports_url());
3978
3979 let both: ClientCapabilities = serde_json::from_value(json!({
3980 "elicitation": { "form": {}, "url": {} }
3981 }))
3982 .unwrap();
3983 let both = both.elicitation.expect("advertised capability");
3984 assert!(both.supports_form());
3985 assert!(both.supports_url());
3986 }
3987
3988 #[test]
3989 fn test_elicitation_method_routing_and_envelopes() {
3990 use serde_json::json;
3991
3992 assert_eq!(CLIENT_METHOD_NAMES.elicitation_create, "elicitation/create");
3993 assert_eq!(
3994 CLIENT_METHOD_NAMES.elicitation_complete,
3995 "elicitation/complete"
3996 );
3997
3998 let request =
3999 AgentRequest::CreateElicitationRequest(Box::new(CreateElicitationRequest::new(
4000 crate::v2::ElicitationFormMode::new(
4001 crate::v2::ElicitationSessionScope::new("sess_1"),
4002 crate::v2::ElicitationSchema::new(),
4003 ),
4004 "Choose a value",
4005 )));
4006 assert_eq!(request.method(), "elicitation/create");
4007 let method = Arc::from(request.method());
4008 let request = crate::v2::JsonRpcMessage::wrap(crate::v2::Request {
4009 id: crate::v2::RequestId::Number(7),
4010 method,
4011 params: Some(request),
4012 });
4013 assert_eq!(
4014 serde_json::to_value(request).unwrap(),
4015 json!({
4016 "jsonrpc": "2.0",
4017 "id": 7,
4018 "method": "elicitation/create",
4019 "params": {
4020 "mode": "form",
4021 "sessionId": "sess_1",
4022 "message": "Choose a value",
4023 "requestedSchema": { "type": "object", "properties": {} }
4024 }
4025 })
4026 );
4027
4028 let notification = AgentNotification::CompleteElicitationNotification(Box::new(
4029 CompleteElicitationNotification::new("elic_1"),
4030 ));
4031 assert_eq!(notification.method(), "elicitation/complete");
4032 let method = Arc::from(notification.method());
4033 let notification = crate::v2::JsonRpcMessage::wrap(crate::v2::Notification {
4034 method,
4035 params: Some(notification),
4036 });
4037 assert_eq!(
4038 serde_json::to_value(notification).unwrap(),
4039 json!({
4040 "jsonrpc": "2.0",
4041 "method": "elicitation/complete",
4042 "params": { "elicitationId": "elic_1" }
4043 })
4044 );
4045 }
4046
4047 #[test]
4048 fn test_client_capabilities_auth_defaults_on_malformed_value() {
4049 use serde_json::json;
4050
4051 let capabilities: ClientCapabilities = serde_json::from_value(json!({
4052 "auth": false
4053 }))
4054 .unwrap();
4055
4056 assert_eq!(capabilities.auth, None);
4057 }
4058
4059 #[test]
4060 fn test_serialization_behavior() {
4061 use serde_json::json;
4062
4063 assert_eq!(
4064 serde_json::from_value::<SessionInfoUpdate>(json!({})).unwrap(),
4065 SessionInfoUpdate {
4066 title: MaybeUndefined::Undefined,
4067 updated_at: MaybeUndefined::Undefined,
4068 meta: MaybeUndefined::Undefined
4069 }
4070 );
4071 assert_eq!(
4072 serde_json::from_value::<SessionInfoUpdate>(json!({"title": null, "updatedAt": null}))
4073 .unwrap(),
4074 SessionInfoUpdate {
4075 title: MaybeUndefined::Null,
4076 updated_at: MaybeUndefined::Null,
4077 meta: MaybeUndefined::Undefined
4078 }
4079 );
4080 assert_eq!(
4081 serde_json::from_value::<SessionInfoUpdate>(
4082 json!({"title": "title", "updatedAt": "timestamp"})
4083 )
4084 .unwrap(),
4085 SessionInfoUpdate {
4086 title: MaybeUndefined::Value("title".to_string()),
4087 updated_at: MaybeUndefined::Value("timestamp".to_string()),
4088 meta: MaybeUndefined::Undefined
4089 }
4090 );
4091
4092 let clear_meta =
4093 serde_json::from_value::<SessionInfoUpdate>(json!({"_meta": null})).unwrap();
4094 assert_eq!(clear_meta.meta, MaybeUndefined::Null);
4095
4096 let mut meta = Meta::new();
4097 meta.insert("source".to_string(), json!("session-info"));
4098
4099 assert_eq!(
4100 serde_json::from_value::<SessionInfoUpdate>(json!({"_meta": {
4101 "source": "session-info"
4102 }}))
4103 .unwrap()
4104 .meta,
4105 MaybeUndefined::Value(meta.clone())
4106 );
4107
4108 assert_eq!(
4109 serde_json::to_value(SessionInfoUpdate::new()).unwrap(),
4110 json!({})
4111 );
4112
4113 assert_eq!(
4114 serde_json::to_value(SessionInfoUpdate::new().meta(None::<Meta>)).unwrap(),
4115 json!({"_meta": null})
4116 );
4117
4118 assert_eq!(
4119 serde_json::to_value(SessionInfoUpdate::new().meta(meta)).unwrap(),
4120 json!({"_meta": {
4121 "source": "session-info"
4122 }})
4123 );
4124 assert_eq!(
4125 serde_json::to_value(SessionInfoUpdate::new().title("title")).unwrap(),
4126 json!({"title": "title"})
4127 );
4128 assert_eq!(
4129 serde_json::to_value(SessionInfoUpdate::new().title(None)).unwrap(),
4130 json!({"title": null})
4131 );
4132 assert_eq!(
4133 serde_json::to_value(
4134 SessionInfoUpdate::new()
4135 .title("title")
4136 .title(MaybeUndefined::Undefined)
4137 )
4138 .unwrap(),
4139 json!({})
4140 );
4141 }
4142
4143 #[test]
4144 fn test_content_chunk_message_id_serialization() {
4145 use serde_json::json;
4146
4147 assert_eq!(
4148 serde_json::to_value(SessionUpdate::AgentMessageChunk(ContentChunk::new(
4149 ContentBlock::Text(crate::v2::TextContent::new("Hello")),
4150 "msg_agent_c42b9",
4151 )))
4152 .unwrap(),
4153 json!({
4154 "sessionUpdate": "agent_message_chunk",
4155 "messageId": "msg_agent_c42b9",
4156 "content": {
4157 "type": "text",
4158 "text": "Hello"
4159 }
4160 })
4161 );
4162
4163 let err = serde_json::from_value::<ContentChunk>(json!({
4164 "content": {
4165 "type": "text",
4166 "text": "Hello"
4167 }
4168 }))
4169 .unwrap_err();
4170
4171 assert!(err.to_string().contains("messageId"), "{err}");
4172 }
4173
4174 #[test]
4175 fn test_tool_call_content_chunk_serialization() {
4176 use serde_json::json;
4177
4178 assert_eq!(
4179 serde_json::to_value(SessionUpdate::ToolCallContentChunk(
4180 ToolCallContentChunk::new(
4181 "call_001",
4182 crate::v2::ContentBlock::Text(crate::v2::TextContent::new("partial output")),
4183 )
4184 ))
4185 .unwrap(),
4186 json!({
4187 "sessionUpdate": "tool_call_content_chunk",
4188 "toolCallId": "call_001",
4189 "content": {
4190 "type": "content",
4191 "content": {
4192 "type": "text",
4193 "text": "partial output"
4194 }
4195 }
4196 })
4197 );
4198
4199 let err = serde_json::from_value::<ToolCallContentChunk>(json!({
4200 "content": {
4201 "type": "content",
4202 "content": {
4203 "type": "text",
4204 "text": "partial output"
4205 }
4206 }
4207 }))
4208 .unwrap_err();
4209
4210 assert!(err.to_string().contains("toolCallId"), "{err}");
4211 }
4212
4213 #[test]
4214 fn test_full_message_serialization() {
4215 use serde_json::json;
4216
4217 assert_eq!(
4218 serde_json::to_value(SessionUpdate::UserMessage(
4219 UserMessage::new("msg_user_8f7a1").content(vec![ContentBlock::Text(
4220 crate::v2::TextContent::new("Hello")
4221 )])
4222 ))
4223 .unwrap(),
4224 json!({
4225 "sessionUpdate": "user_message",
4226 "messageId": "msg_user_8f7a1",
4227 "content": [
4228 {
4229 "type": "text",
4230 "text": "Hello"
4231 }
4232 ]
4233 })
4234 );
4235
4236 assert_eq!(
4237 serde_json::to_value(SessionUpdate::AgentMessage(
4238 AgentMessage::new("msg_agent_c42b9").content(vec![ContentBlock::Text(
4239 crate::v2::TextContent::new("Hello")
4240 )])
4241 ))
4242 .unwrap(),
4243 json!({
4244 "sessionUpdate": "agent_message",
4245 "messageId": "msg_agent_c42b9",
4246 "content": [
4247 {
4248 "type": "text",
4249 "text": "Hello"
4250 }
4251 ]
4252 })
4253 );
4254
4255 assert_eq!(
4256 serde_json::to_value(SessionUpdate::AgentThought(
4257 AgentThought::new("msg_thought_a12").content(vec![ContentBlock::Text(
4258 crate::v2::TextContent::new("Need to inspect the call sites first.")
4259 )])
4260 ))
4261 .unwrap(),
4262 json!({
4263 "sessionUpdate": "agent_thought",
4264 "messageId": "msg_thought_a12",
4265 "content": [
4266 {
4267 "type": "text",
4268 "text": "Need to inspect the call sites first."
4269 }
4270 ]
4271 })
4272 );
4273 }
4274
4275 #[test]
4276 fn test_message_upsert_serialization() {
4277 use serde_json::json;
4278
4279 assert_eq!(
4280 serde_json::to_value(SessionUpdate::UserMessage(
4281 UserMessage::new("msg_empty").content(Vec::<ContentBlock>::new())
4282 ))
4283 .unwrap(),
4284 json!({
4285 "sessionUpdate": "user_message",
4286 "messageId": "msg_empty",
4287 "content": []
4288 })
4289 );
4290
4291 let empty = serde_json::from_value::<UserMessage>(json!({
4292 "messageId": "msg_empty",
4293 "content": []
4294 }))
4295 .unwrap();
4296 assert!(matches!(
4297 empty.content,
4298 MaybeUndefined::Value(ref content) if content.is_empty()
4299 ));
4300
4301 let patch = serde_json::from_value::<AgentMessage>(json!({
4302 "messageId": "msg_agent_c42b9"
4303 }))
4304 .unwrap();
4305 assert_eq!(patch.content, MaybeUndefined::Undefined);
4306 assert_eq!(patch.meta, MaybeUndefined::Undefined);
4307
4308 let malformed_meta = serde_json::from_value::<AgentMessage>(json!({
4309 "messageId": "msg_agent_c42b9",
4310 "_meta": false
4311 }))
4312 .unwrap();
4313 assert_eq!(malformed_meta.meta, MaybeUndefined::Undefined);
4314
4315 let patch = serde_json::from_value::<AgentThought>(json!({
4316 "messageId": "msg_thought_a12"
4317 }))
4318 .unwrap();
4319 assert_eq!(patch.content, MaybeUndefined::Undefined);
4320
4321 let clear = serde_json::from_value::<UserMessage>(json!({
4322 "messageId": "msg_user_8f7a1",
4323 "content": null
4324 }))
4325 .unwrap();
4326 assert_eq!(clear.content, MaybeUndefined::Null);
4327
4328 let clear_meta = serde_json::from_value::<UserMessage>(json!({
4329 "messageId": "msg_user_8f7a1",
4330 "_meta": null
4331 }))
4332 .unwrap();
4333 assert_eq!(clear_meta.meta, MaybeUndefined::Null);
4334
4335 let mut meta = Meta::new();
4336 meta.insert("source".to_string(), json!("replay"));
4337
4338 assert_eq!(
4339 serde_json::to_value(SessionUpdate::UserMessage(
4340 UserMessage::new("msg_user_8f7a1").meta(meta)
4341 ))
4342 .unwrap(),
4343 json!({
4344 "sessionUpdate": "user_message",
4345 "messageId": "msg_user_8f7a1",
4346 "_meta": {
4347 "source": "replay"
4348 }
4349 })
4350 );
4351
4352 assert_eq!(
4353 serde_json::to_value(SessionUpdate::UserMessage(
4354 UserMessage::new("msg_user_8f7a1").meta(None::<Meta>)
4355 ))
4356 .unwrap(),
4357 json!({
4358 "sessionUpdate": "user_message",
4359 "messageId": "msg_user_8f7a1",
4360 "_meta": null
4361 })
4362 );
4363 }
4364
4365 #[test]
4366 fn test_usage_update_serialization() {
4367 use serde_json::json;
4368
4369 assert_eq!(
4370 serde_json::to_value(SessionUpdate::UsageUpdate(UsageUpdate::new(
4371 53_000, 200_000
4372 )))
4373 .unwrap(),
4374 json!({
4375 "sessionUpdate": "usage_update",
4376 "used": 53000,
4377 "size": 200_000
4378 })
4379 );
4380
4381 assert_eq!(
4382 serde_json::to_value(SessionUpdate::UsageUpdate(
4383 UsageUpdate::new(53_000, 200_000).cost(Cost::new(0.045, "USD"))
4384 ))
4385 .unwrap(),
4386 json!({
4387 "sessionUpdate": "usage_update",
4388 "used": 53000,
4389 "size": 200_000,
4390 "cost": {
4391 "amount": 0.045,
4392 "currency": "USD"
4393 }
4394 })
4395 );
4396
4397 let SessionUpdate::UsageUpdate(update) = serde_json::from_value(json!({
4398 "sessionUpdate": "usage_update",
4399 "used": 53000,
4400 "size": 200_000,
4401 "cost": null
4402 }))
4403 .unwrap() else {
4404 panic!("expected usage update");
4405 };
4406
4407 assert_eq!(update.cost, None);
4408 }
4409
4410 #[test]
4411 fn error_stop_reason_carries_failure_beside_discriminator() {
4412 use crate::v2::{Error, ErrorStopReason};
4413 use serde_json::json;
4414
4415 let update =
4416 SessionUpdate::StateUpdate(StateUpdate::Idle(IdleStateUpdate::new().stop_reason(
4417 StopReason::from(ErrorStopReason::new().error(Error::auth_required())),
4418 )));
4419 let wire = json!({
4420 "sessionUpdate": "state_update",
4421 "state": "idle",
4422 "stopReason": "error",
4423 "error": { "code": -32000, "message": "Authentication required" }
4424 });
4425 assert_eq!(serde_json::to_value(&update).unwrap(), wire);
4426 assert_eq!(
4427 serde_json::from_value::<SessionUpdate>(wire).unwrap(),
4428 update
4429 );
4430
4431 let with_data =
4432 StateUpdate::Idle(IdleStateUpdate::new().stop_reason(StopReason::from(
4433 ErrorStopReason::new().error(
4434 Error::new(-32099, "Provider unavailable").data(json!({ "status": 503 })),
4435 ),
4436 )));
4437 let wire = json!({
4438 "state": "idle",
4439 "stopReason": "error",
4440 "error": {
4441 "code": -32099,
4442 "message": "Provider unavailable",
4443 "data": { "status": 503 }
4444 }
4445 });
4446 assert_eq!(serde_json::to_value(&with_data).unwrap(), wire);
4447 assert_eq!(
4448 serde_json::from_value::<StateUpdate>(wire).unwrap(),
4449 with_data
4450 );
4451
4452 let without_details = StateUpdate::Idle(
4455 IdleStateUpdate::new().stop_reason(StopReason::from(ErrorStopReason::new())),
4456 );
4457 for error in [
4458 None,
4459 Some(json!(null)),
4460 Some(json!("failed")),
4461 Some(json!({ "message": "missing code" })),
4462 ] {
4463 let mut wire = json!({ "state": "idle", "stopReason": "error" });
4464 if let Some(error) = error {
4465 wire["error"] = error;
4466 }
4467 assert_eq!(
4468 serde_json::from_value::<StateUpdate>(wire).unwrap(),
4469 without_details
4470 );
4471 }
4472 assert_eq!(
4473 serde_json::to_value(&without_details).unwrap(),
4474 json!({ "state": "idle", "stopReason": "error" })
4475 );
4476
4477 let parsed: StateUpdate = serde_json::from_value(json!({
4481 "state": "idle",
4482 "stopReason": "end_turn",
4483 "error": { "code": -32603, "message": "Internal error" }
4484 }))
4485 .unwrap();
4486 assert_eq!(
4487 parsed,
4488 StateUpdate::Idle(IdleStateUpdate::new().stop_reason(StopReason::EndTurn))
4489 );
4490 }
4491
4492 #[test]
4493 fn flattened_stop_reason_keeps_open_enum_tolerance() {
4494 use crate::v2::OtherStopReason;
4495 use serde_json::json;
4496
4497 let meta: Meta = [("source".into(), json!("test"))].into_iter().collect();
4498
4499 for stop_reason in [None, Some(json!(null)), Some(json!(true)), Some(json!({}))] {
4502 let mut wire = json!({ "state": "idle", "_meta": { "source": "test" } });
4503 if let Some(stop_reason) = stop_reason {
4504 wire["stopReason"] = stop_reason;
4505 }
4506 let StateUpdate::Idle(idle) = serde_json::from_value(wire).unwrap() else {
4507 panic!("expected idle state update");
4508 };
4509 assert_eq!(idle.stop_reason, None);
4510 assert_eq!(idle.meta, Some(meta.clone()));
4511 }
4512
4513 let wire = json!({
4516 "state": "idle",
4517 "stopReason": "_paused",
4518 "resumeAfter": 30,
4519 "_meta": { "source": "test" }
4520 });
4521 let parsed: StateUpdate = serde_json::from_value(wire.clone()).unwrap();
4522 let expected = StateUpdate::Idle(
4523 IdleStateUpdate::new()
4524 .stop_reason(StopReason::Other(OtherStopReason::new(
4525 "_paused",
4526 [("resumeAfter".into(), json!(30))].into_iter().collect(),
4527 )))
4528 .meta(meta),
4529 );
4530 assert_eq!(parsed, expected);
4531 assert_eq!(serde_json::to_value(&parsed).unwrap(), wire);
4532 }
4533
4534 #[test]
4535 fn known_stop_reasons_are_never_read_as_other() {
4536 use crate::v2::{ErrorStopReason, OtherStopReason};
4537 use serde_json::json;
4538
4539 let known = [
4540 StopReason::EndTurn,
4541 StopReason::MaxTokens,
4542 StopReason::MaxTurnRequests,
4543 StopReason::Refusal,
4544 StopReason::Cancelled,
4545 StopReason::Error(ErrorStopReason::new()),
4546 ];
4547 for stop_reason in &known {
4549 match stop_reason {
4550 StopReason::EndTurn
4551 | StopReason::MaxTokens
4552 | StopReason::MaxTurnRequests
4553 | StopReason::Refusal
4554 | StopReason::Cancelled
4555 | StopReason::Error(_)
4556 | StopReason::Other(_) => {}
4557 }
4558 }
4559
4560 for stop_reason in known {
4561 let update = StateUpdate::Idle(IdleStateUpdate::new().stop_reason(stop_reason));
4562 let wire = serde_json::to_value(&update).unwrap();
4563 assert_eq!(
4564 serde_json::from_value::<StateUpdate>(wire.clone()).unwrap(),
4565 update
4566 );
4567 assert!(
4568 serde_json::from_value::<OtherStopReason>(
4569 json!({ "stopReason": wire["stopReason"] })
4570 )
4571 .is_err(),
4572 "{} must not be accepted as a custom stop reason",
4573 wire["stopReason"]
4574 );
4575 }
4576 }
4577
4578 #[test]
4579 fn unknown_stop_reason_leaves_usage_on_the_idle_update() {
4580 use serde_json::json;
4581
4582 let usage = json!({ "totalTokens": 10, "inputTokens": 6, "outputTokens": 4 });
4583 let wire = json!({ "state": "idle", "stopReason": "_paused", "usage": usage });
4584 let StateUpdate::Idle(idle) = serde_json::from_value(wire.clone()).unwrap() else {
4585 panic!("expected idle state update");
4586 };
4587 let Some(StopReason::Other(other)) = &idle.stop_reason else {
4588 panic!("expected a custom stop reason");
4589 };
4590 assert!(other.fields.is_empty());
4591
4592 #[cfg(feature = "unstable_end_turn_token_usage")]
4593 {
4594 assert!(idle.usage.is_some());
4595 assert_eq!(serde_json::to_value(&idle).unwrap()["usage"], usage);
4596 }
4597 #[cfg(not(feature = "unstable_end_turn_token_usage"))]
4600 assert!(serde_json::to_value(&idle).unwrap().get("usage").is_none());
4601 }
4602
4603 #[test]
4604 fn test_state_update_serialization() {
4605 use serde_json::json;
4606
4607 assert_eq!(
4608 serde_json::to_value(SessionUpdate::StateUpdate(StateUpdate::Running(
4609 RunningStateUpdate::new()
4610 )))
4611 .unwrap(),
4612 json!({
4613 "sessionUpdate": "state_update",
4614 "state": "running"
4615 })
4616 );
4617
4618 assert_eq!(
4619 serde_json::to_value(SessionUpdate::StateUpdate(StateUpdate::Idle(
4620 IdleStateUpdate::new().stop_reason(StopReason::EndTurn)
4621 )))
4622 .unwrap(),
4623 json!({
4624 "sessionUpdate": "state_update",
4625 "state": "idle",
4626 "stopReason": "end_turn"
4627 })
4628 );
4629
4630 let SessionUpdate::StateUpdate(update) = serde_json::from_value(json!({
4631 "sessionUpdate": "state_update",
4632 "state": "requires_action"
4633 }))
4634 .unwrap() else {
4635 panic!("expected state update");
4636 };
4637
4638 assert!(matches!(update, StateUpdate::RequiresAction(_)));
4639
4640 let SessionUpdate::StateUpdate(StateUpdate::Idle(update)) = serde_json::from_value(json!({
4641 "sessionUpdate": "state_update",
4642 "state": "idle",
4643 "stopReason": null
4644 }))
4645 .unwrap() else {
4646 panic!("expected idle state update");
4647 };
4648
4649 assert_eq!(update.stop_reason, None);
4650
4651 let SessionUpdate::StateUpdate(StateUpdate::Other(update)) =
4652 serde_json::from_value(json!({
4653 "sessionUpdate": "state_update",
4654 "state": "_paused",
4655 "label": "Paused"
4656 }))
4657 .unwrap()
4658 else {
4659 panic!("expected unknown state update");
4660 };
4661
4662 assert_eq!(update.state, "_paused");
4663 assert_eq!(update.fields["label"], json!("Paused"));
4664 }
4665
4666 #[test]
4667 fn session_update_preserves_unknown_variant() {
4668 use serde_json::json;
4669
4670 let update: SessionUpdate = serde_json::from_value(json!({
4671 "sessionUpdate": "_status_badge",
4672 "label": "Indexing",
4673 "progress": 0.5
4674 }))
4675 .unwrap();
4676
4677 let SessionUpdate::Other(unknown) = update else {
4678 panic!("expected unknown session update");
4679 };
4680
4681 assert_eq!(unknown.session_update, "_status_badge");
4682 assert_eq!(unknown.fields.get("label"), Some(&json!("Indexing")));
4683 assert_eq!(unknown.fields.get("progress"), Some(&json!(0.5)));
4684
4685 assert_eq!(
4686 serde_json::to_value(SessionUpdate::Other(unknown)).unwrap(),
4687 json!({
4688 "sessionUpdate": "_status_badge",
4689 "label": "Indexing",
4690 "progress": 0.5
4691 })
4692 );
4693 }
4694
4695 #[test]
4696 fn terminal_session_updates_use_known_discriminators() {
4697 use serde_json::json;
4698
4699 assert_eq!(
4700 serde_json::to_value(SessionUpdate::TerminalUpdate(
4701 TerminalUpdate::new("term_1").command("cargo test")
4702 ))
4703 .unwrap(),
4704 json!({
4705 "sessionUpdate": "terminal_update",
4706 "terminalId": "term_1",
4707 "command": "cargo test"
4708 })
4709 );
4710 assert_eq!(
4711 serde_json::to_value(SessionUpdate::TerminalOutputChunk(
4712 TerminalOutputChunk::new("term_1", "dGVzdAo=")
4713 ))
4714 .unwrap(),
4715 json!({
4716 "sessionUpdate": "terminal_output_chunk",
4717 "terminalId": "term_1",
4718 "data": "dGVzdAo="
4719 })
4720 );
4721 }
4722
4723 #[test]
4724 fn session_update_does_not_hide_malformed_known_terminal_variants() {
4725 use serde_json::json;
4726
4727 assert!(
4728 serde_json::from_value::<SessionUpdate>(json!({
4729 "sessionUpdate": "terminal_update"
4730 }))
4731 .is_err()
4732 );
4733 assert!(
4734 serde_json::from_value::<SessionUpdate>(json!({
4735 "sessionUpdate": "terminal_output_chunk",
4736 "terminalId": "term_1"
4737 }))
4738 .is_err()
4739 );
4740 }
4741
4742 #[test]
4743 fn test_plan_update_serialization() {
4744 use serde_json::json;
4745
4746 let plan_update =
4747 SessionUpdate::PlanUpdate(PlanUpdate::new(crate::v2::PlanUpdateContent::items(
4748 "plan-1",
4749 vec![crate::v2::PlanEntry::new(
4750 "Step 1",
4751 crate::v2::PlanEntryPriority::High,
4752 crate::v2::PlanEntryStatus::Pending,
4753 )],
4754 )));
4755
4756 assert_eq!(
4757 serde_json::to_value(plan_update).unwrap(),
4758 json!({
4759 "sessionUpdate": "plan_update",
4760 "plan": {
4761 "type": "items",
4762 "planId": "plan-1",
4763 "entries": [
4764 {
4765 "content": "Step 1",
4766 "priority": "high",
4767 "status": "pending"
4768 }
4769 ]
4770 }
4771 })
4772 );
4773 }
4774
4775 #[cfg(feature = "unstable_plan_operations")]
4776 #[test]
4777 fn test_plan_removed_serialization() {
4778 use serde_json::json;
4779
4780 assert_eq!(
4781 serde_json::to_value(SessionUpdate::PlanRemoved(PlanRemoved::new("plan-1"))).unwrap(),
4782 json!({
4783 "sessionUpdate": "plan_removed",
4784 "planId": "plan-1"
4785 })
4786 );
4787 }
4788
4789 #[test]
4790 fn available_command_input_preserves_unknown_typed_variant() {
4791 use serde_json::json;
4792
4793 let input: AvailableCommandInput = serde_json::from_value(json!({
4794 "type": "_choices",
4795 "hint": "Pick one",
4796 "options": ["fast", "careful"]
4797 }))
4798 .unwrap();
4799
4800 let AvailableCommandInput::Other(unknown) = input else {
4801 panic!("expected unknown command input");
4802 };
4803
4804 assert_eq!(unknown.type_, "_choices");
4805 assert_eq!(unknown.fields.get("hint"), Some(&json!("Pick one")));
4806 assert_eq!(
4807 unknown.fields.get("options"),
4808 Some(&json!(["fast", "careful"]))
4809 );
4810 assert_eq!(
4811 serde_json::to_value(AvailableCommandInput::Other(unknown)).unwrap(),
4812 json!({
4813 "type": "_choices",
4814 "hint": "Pick one",
4815 "options": ["fast", "careful"]
4816 })
4817 );
4818 }
4819
4820 #[test]
4821 fn available_command_input_text_uses_type_discriminator() {
4822 use serde_json::json;
4823
4824 let input = AvailableCommandInput::Text(TextCommandInput::new("Describe changes"));
4825
4826 let json = serde_json::to_value(&input).unwrap();
4827 assert_eq!(
4828 json,
4829 json!({
4830 "type": "text",
4831 "hint": "Describe changes"
4832 })
4833 );
4834
4835 let roundtripped: AvailableCommandInput = serde_json::from_value(json).unwrap();
4836 assert!(matches!(roundtripped, AvailableCommandInput::Text(_)));
4837 }
4838
4839 #[test]
4840 fn request_permission_subject_tool_call_uses_type_discriminator() {
4841 use serde_json::json;
4842
4843 let subject = RequestPermissionSubject::from(ToolCallUpdate::new("call_001"));
4844
4845 let json = serde_json::to_value(&subject).unwrap();
4846 assert_eq!(
4847 json,
4848 json!({
4849 "type": "tool_call",
4850 "toolCall": {
4851 "toolCallId": "call_001"
4852 }
4853 })
4854 );
4855
4856 let roundtripped: RequestPermissionSubject = serde_json::from_value(json).unwrap();
4857 assert!(matches!(
4858 roundtripped,
4859 RequestPermissionSubject::ToolCall(_)
4860 ));
4861 }
4862
4863 #[test]
4864 fn request_permission_subject_command_uses_type_discriminator() {
4865 use serde_json::json;
4866
4867 let mut meta = Meta::new();
4868 meta.insert("source".to_string(), json!("shell"));
4869 let subject = RequestPermissionSubject::from(
4870 CommandPermissionSubject::new("cargo test", "/workspace/project")
4871 .tool_call_id("call_001")
4872 .terminal_id("term_1")
4873 .meta(meta),
4874 );
4875
4876 let json = serde_json::to_value(&subject).unwrap();
4877 assert_eq!(
4878 json,
4879 json!({
4880 "type": "command",
4881 "command": "cargo test",
4882 "cwd": "/workspace/project",
4883 "toolCallId": "call_001",
4884 "terminalId": "term_1",
4885 "_meta": {
4886 "source": "shell"
4887 }
4888 })
4889 );
4890
4891 let roundtripped: RequestPermissionSubject = serde_json::from_value(json).unwrap();
4892 assert!(matches!(roundtripped, RequestPermissionSubject::Command(_)));
4893 }
4894
4895 #[test]
4896 fn command_permission_subject_treats_optional_association_nulls_as_omitted() {
4897 use serde_json::json;
4898
4899 let subject: RequestPermissionSubject = serde_json::from_value(json!({
4900 "type": "command",
4901 "command": "cargo test",
4902 "cwd": "/workspace/project",
4903 "toolCallId": null,
4904 "terminalId": null,
4905 "_meta": null
4906 }))
4907 .unwrap();
4908
4909 let RequestPermissionSubject::Command(subject) = subject else {
4910 panic!("expected command permission subject");
4911 };
4912 assert_eq!(subject.cwd, AbsolutePath::new("/workspace/project"));
4913 assert_eq!(subject.tool_call_id, None);
4914 assert_eq!(subject.terminal_id, None);
4915 assert_eq!(subject.meta, None);
4916 assert_eq!(
4917 serde_json::to_value(RequestPermissionSubject::Command(subject)).unwrap(),
4918 json!({
4919 "type": "command",
4920 "command": "cargo test",
4921 "cwd": "/workspace/project"
4922 })
4923 );
4924 }
4925
4926 #[test]
4927 fn request_permission_subject_preserves_unknown_variant() {
4928 use serde_json::json;
4929
4930 let subject: RequestPermissionSubject = serde_json::from_value(json!({
4931 "type": "_review",
4932 "reason": "needs-review",
4933 "retryAfterSeconds": 30
4934 }))
4935 .unwrap();
4936
4937 let RequestPermissionSubject::Other(unknown) = subject else {
4938 panic!("expected unknown permission subject");
4939 };
4940
4941 assert_eq!(unknown.type_, "_review");
4942 assert_eq!(unknown.fields.get("reason"), Some(&json!("needs-review")));
4943 assert_eq!(unknown.fields.get("retryAfterSeconds"), Some(&json!(30)));
4944 assert_eq!(
4945 serde_json::to_value(RequestPermissionSubject::Other(unknown)).unwrap(),
4946 json!({
4947 "type": "_review",
4948 "reason": "needs-review",
4949 "retryAfterSeconds": 30
4950 })
4951 );
4952 }
4953
4954 #[test]
4955 fn request_permission_subject_unknown_does_not_hide_malformed_known_variant() {
4956 use serde_json::json;
4957
4958 assert!(
4959 serde_json::from_value::<RequestPermissionSubject>(json!({
4960 "type": "tool_call"
4961 }))
4962 .is_err()
4963 );
4964 assert!(
4965 serde_json::from_value::<RequestPermissionSubject>(json!({
4966 "type": 1
4967 }))
4968 .is_err()
4969 );
4970 assert!(
4971 serde_json::from_value::<RequestPermissionSubject>(json!({
4972 "type": "command",
4973 "cwd": "/workspace/project"
4974 }))
4975 .is_err()
4976 );
4977 assert!(
4978 serde_json::from_value::<RequestPermissionSubject>(json!({
4979 "type": "command",
4980 "command": "cargo test"
4981 }))
4982 .is_err()
4983 );
4984 assert!(
4985 serde_json::from_value::<RequestPermissionSubject>(json!({
4986 "type": "command",
4987 "command": "cargo test",
4988 "cwd": null
4989 }))
4990 .is_err()
4991 );
4992 }
4993
4994 #[test]
4995 fn request_permission_title_and_description_are_separate_from_tool_call_content() {
4996 use serde_json::json;
4997
4998 let request =
4999 RequestPermissionRequest::new("sess_abc123def456", "Approve file edit?", Vec::new())
5000 .description("Allow this tool to edit src/main.rs?")
5001 .subject(RequestPermissionSubject::from(ToolCallUpdate::new(
5002 "call_001",
5003 )));
5004
5005 assert_eq!(
5006 serde_json::to_value(request).unwrap(),
5007 json!({
5008 "sessionId": "sess_abc123def456",
5009 "title": "Approve file edit?",
5010 "description": "Allow this tool to edit src/main.rs?",
5011 "subject": {
5012 "type": "tool_call",
5013 "toolCall": {
5014 "toolCallId": "call_001"
5015 }
5016 },
5017 "options": []
5018 })
5019 );
5020 }
5021
5022 #[test]
5023 fn request_permission_requires_title_and_allows_missing_subject() {
5024 use serde_json::json;
5025
5026 let request = RequestPermissionRequest::new(
5027 "sess_abc123def456",
5028 "Approve elevated permissions?",
5029 Vec::new(),
5030 );
5031
5032 assert_eq!(
5033 serde_json::to_value(request).unwrap(),
5034 json!({
5035 "sessionId": "sess_abc123def456",
5036 "title": "Approve elevated permissions?",
5037 "options": []
5038 })
5039 );
5040
5041 let missing_subject: RequestPermissionRequest = serde_json::from_value(json!({
5042 "sessionId": "sess_abc123def456",
5043 "title": "Approve elevated permissions?",
5044 "options": []
5045 }))
5046 .unwrap();
5047 assert!(missing_subject.subject.is_none());
5048
5049 let null_subject: RequestPermissionRequest = serde_json::from_value(json!({
5050 "sessionId": "sess_abc123def456",
5051 "title": "Approve elevated permissions?",
5052 "subject": null,
5053 "options": []
5054 }))
5055 .unwrap();
5056 assert!(null_subject.subject.is_none());
5057
5058 assert!(
5059 serde_json::from_value::<RequestPermissionRequest>(json!({
5060 "sessionId": "sess_abc123def456",
5061 "options": []
5062 }))
5063 .is_err()
5064 );
5065 }
5066
5067 #[test]
5068 fn request_permission_outcome_preserves_unknown_variant() {
5069 use serde_json::json;
5070
5071 let outcome: RequestPermissionOutcome = serde_json::from_value(json!({
5072 "outcome": "_defer",
5073 "reason": "needs-review",
5074 "retryAfterSeconds": 30
5075 }))
5076 .unwrap();
5077
5078 let RequestPermissionOutcome::Other(unknown) = outcome else {
5079 panic!("expected unknown permission outcome");
5080 };
5081
5082 assert_eq!(unknown.outcome, "_defer");
5083 assert_eq!(unknown.fields.get("reason"), Some(&json!("needs-review")));
5084 assert_eq!(unknown.fields.get("retryAfterSeconds"), Some(&json!(30)));
5085 assert_eq!(
5086 serde_json::to_value(RequestPermissionOutcome::Other(unknown)).unwrap(),
5087 json!({
5088 "outcome": "_defer",
5089 "reason": "needs-review",
5090 "retryAfterSeconds": 30
5091 })
5092 );
5093 }
5094
5095 #[test]
5096 fn request_permission_outcome_unknown_does_not_hide_malformed_known_variant() {
5097 use serde_json::json;
5098
5099 assert!(
5100 serde_json::from_value::<RequestPermissionOutcome>(json!({
5101 "outcome": "selected"
5102 }))
5103 .is_err()
5104 );
5105 assert!(
5106 serde_json::from_value::<RequestPermissionOutcome>(json!({
5107 "outcome": 1
5108 }))
5109 .is_err()
5110 );
5111 }
5112
5113 #[test]
5114 fn available_command_input_unknown_does_not_hide_malformed_text_variant() {
5115 use serde_json::json;
5116
5117 assert!(serde_json::from_value::<AvailableCommandInput>(json!({})).is_err());
5118 assert!(
5119 serde_json::from_value::<AvailableCommandInput>(json!({
5120 "hint": "Pick one"
5121 }))
5122 .is_err()
5123 );
5124 assert!(
5125 serde_json::from_value::<AvailableCommandInput>(json!({
5126 "type": 1,
5127 "hint": "Pick one"
5128 }))
5129 .is_err()
5130 );
5131 assert!(
5132 serde_json::from_value::<OtherAvailableCommandInput>(json!({
5133 "type": "text",
5134 "hint": "Pick one"
5135 }))
5136 .is_err()
5137 );
5138 }
5139
5140 #[cfg(feature = "unstable_nes")]
5141 #[test]
5142 fn test_client_capabilities_position_encodings_serialization() {
5143 use serde_json::json;
5144
5145 let capabilities = ClientCapabilities::new().position_encodings(vec![
5146 PositionEncodingKind::Utf32,
5147 PositionEncodingKind::Utf16,
5148 ]);
5149 let json = serde_json::to_value(&capabilities).unwrap();
5150
5151 assert_eq!(json["positionEncodings"], json!(["utf-32", "utf-16"]));
5152 }
5153
5154 #[cfg(feature = "unstable_mcp_over_acp")]
5155 #[test]
5156 fn test_agent_mcp_request_method_names() {
5157 use serde_json::json;
5158
5159 let params: serde_json::Map<String, serde_json::Value> =
5160 [("cursor".to_string(), json!("abc"))].into_iter().collect();
5161
5162 assert_eq!(CLIENT_METHOD_NAMES.mcp_message, "mcp/message");
5163 assert_eq!(
5164 AgentRequest::MessageMcpRequest(Box::new(MessageMcpRequest::new(
5165 "server-1",
5166 "req-1",
5167 "tools/list"
5168 )))
5169 .method(),
5170 "mcp/message"
5171 );
5172 assert_eq!(
5173 serde_json::to_value(
5174 MessageMcpRequest::new("server-1", "req-1", "tools/list").params(params)
5175 )
5176 .unwrap(),
5177 json!({
5178 "serverId": "server-1",
5179 "requestId": "req-1",
5180 "method": "tools/list",
5181 "params": { "cursor": "abc" }
5182 })
5183 );
5184
5185 let request_with_null_params: MessageMcpRequest = serde_json::from_value(json!({
5186 "serverId": "server-1",
5187 "requestId": "req-1",
5188 "method": "tools/list",
5189 "params": null,
5190 "_meta": null
5191 }))
5192 .unwrap();
5193 assert_eq!(request_with_null_params.params, None);
5194 assert_eq!(request_with_null_params.meta, None);
5195 for key in ["serverId", "requestId", "method"] {
5196 let mut value =
5197 json!({"serverId":"server-1", "requestId":"req-1", "method":"tools/list"});
5198 value.as_object_mut().unwrap().remove(key);
5199 assert!(serde_json::from_value::<MessageMcpRequest>(value).is_err());
5200 }
5201 for key in ["serverId", "requestId", "method"] {
5202 let mut value =
5203 json!({"serverId":"server-1", "requestId":"req-1", "method":"tools/list"});
5204 value[key] = serde_json::Value::Null;
5205 assert!(serde_json::from_value::<MessageMcpRequest>(value).is_err());
5206 }
5207 }
5208
5209 #[test]
5210 fn test_auth_capabilities_serialize_terminal_support_as_object() {
5211 use serde_json::json;
5212
5213 let capabilities = AuthCapabilities::new().terminal(TerminalAuthCapabilities::new());
5214
5215 assert_eq!(
5216 serde_json::to_value(&capabilities).unwrap(),
5217 json!({
5218 "terminal": {}
5219 })
5220 );
5221
5222 let deserialized: AuthCapabilities = serde_json::from_value(json!({
5223 "terminal": false
5224 }))
5225 .unwrap();
5226 assert!(deserialized.terminal.is_none());
5227 }
5228
5229 #[test]
5230 fn request_permission_request_rejects_malformed_options() {
5231 use serde_json::json;
5232
5233 assert!(
5234 serde_json::from_value::<RequestPermissionRequest>(json!({
5235 "sessionId": "sess-1",
5236 "title": "Run tool?",
5237 "options": "not-an-array"
5238 }))
5239 .is_err()
5240 );
5241 assert!(
5242 serde_json::from_value::<RequestPermissionRequest>(json!({
5243 "sessionId": "sess-1",
5244 "title": "Run tool?",
5245 "options": [{"optionId": "allow"}]
5246 }))
5247 .is_err()
5248 );
5249 }
5250
5251 #[cfg(feature = "unstable_plan_operations")]
5252 #[test]
5253 fn malformed_plan_removed_is_not_hidden_as_unknown_update() {
5254 use serde_json::json;
5255
5256 assert!(
5257 serde_json::from_value::<SessionUpdate>(json!({
5258 "sessionUpdate": "plan_removed"
5259 }))
5260 .is_err()
5261 );
5262 }
5263}