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 #[cfg(feature = "unstable_session_notices")]
160 Notice(Notice),
161 #[cfg(feature = "unstable_session_compaction")]
167 CompactionUpdate(CompactionUpdate),
168 #[cfg(feature = "unstable_session_compaction")]
174 CompactionSummaryChunk(CompactionSummaryChunk),
175 #[cfg(feature = "unstable_subagents")]
182 SubagentUpdate(SubagentUpdate),
183 #[cfg(feature = "unstable_subagents")]
190 SessionMessage(SessionMessage),
191 #[cfg(feature = "unstable_subagents")]
197 SessionMessageChunk(SessionMessageChunk),
198 #[serde(untagged)]
208 Other(OtherSessionUpdate),
209}
210
211#[cfg(feature = "unstable_subagents")]
217#[serde_as]
218#[skip_serializing_none]
219#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
220#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
221#[serde(rename_all = "camelCase")]
222#[non_exhaustive]
223pub struct SessionMessageChunk {
224 pub message_id: MessageId,
226 #[serde_as(deserialize_as = "DefaultOnError")]
228 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
229 #[serde(default)]
230 pub sender_session_id: Option<SessionId>,
231 #[serde_as(deserialize_as = "DefaultOnError")]
233 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
234 #[serde(default)]
235 pub recipient_session_id: Option<SessionId>,
236 pub content: ContentBlock,
238 #[serde_as(deserialize_as = "DefaultOnError")]
242 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
243 #[serde(default, rename = "_meta")]
244 pub meta: Option<Meta>,
245}
246
247#[cfg(feature = "unstable_subagents")]
248impl SessionMessageChunk {
249 #[must_use]
251 pub fn new(message_id: impl Into<MessageId>, content: ContentBlock) -> Self {
252 Self {
253 message_id: message_id.into(),
254 sender_session_id: None,
255 recipient_session_id: None,
256 content,
257 meta: None,
258 }
259 }
260
261 #[must_use]
263 pub fn sender_session_id(mut self, sender_session_id: impl IntoOption<SessionId>) -> Self {
264 self.sender_session_id = sender_session_id.into_option();
265 self
266 }
267
268 #[must_use]
270 pub fn recipient_session_id(
271 mut self,
272 recipient_session_id: impl IntoOption<SessionId>,
273 ) -> Self {
274 self.recipient_session_id = recipient_session_id.into_option();
275 self
276 }
277
278 #[must_use]
280 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
281 self.meta = meta.into_option();
282 self
283 }
284}
285
286#[cfg(feature = "unstable_subagents")]
292#[serde_as]
293#[skip_serializing_none]
294#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
295#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
296#[serde(rename_all = "camelCase")]
297#[non_exhaustive]
298pub struct SessionMessage {
299 pub message_id: MessageId,
301 #[serde_as(deserialize_as = "DefaultOnError")]
303 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
304 #[serde(default)]
305 pub sender_session_id: Option<SessionId>,
306 #[serde_as(deserialize_as = "DefaultOnError")]
308 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
309 #[serde(default)]
310 pub recipient_session_id: Option<SessionId>,
311 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<VecSkipError<_>>>")]
314 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
315 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
316 pub content: MaybeUndefined<Vec<ContentBlock>>,
317 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
321 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
322 #[serde(
323 default,
324 rename = "_meta",
325 skip_serializing_if = "MaybeUndefined::is_undefined"
326 )]
327 pub meta: MaybeUndefined<Meta>,
328}
329
330#[cfg(feature = "unstable_subagents")]
331impl SessionMessage {
332 #[must_use]
334 pub fn new(message_id: impl Into<MessageId>) -> Self {
335 Self {
336 message_id: message_id.into(),
337 sender_session_id: None,
338 recipient_session_id: None,
339 content: MaybeUndefined::Undefined,
340 meta: MaybeUndefined::Undefined,
341 }
342 }
343
344 #[must_use]
346 pub fn sender_session_id(mut self, sender_session_id: impl IntoOption<SessionId>) -> Self {
347 self.sender_session_id = sender_session_id.into_option();
348 self
349 }
350
351 #[must_use]
353 pub fn recipient_session_id(
354 mut self,
355 recipient_session_id: impl IntoOption<SessionId>,
356 ) -> Self {
357 self.recipient_session_id = recipient_session_id.into_option();
358 self
359 }
360
361 #[must_use]
363 pub fn content(mut self, content: impl IntoMaybeUndefined<Vec<ContentBlock>>) -> Self {
364 self.content = content.into_maybe_undefined();
365 self
366 }
367
368 #[must_use]
370 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
371 self.meta = meta.into_maybe_undefined();
372 self
373 }
374}
375
376#[cfg(all(test, feature = "unstable_subagents"))]
377mod session_message_tests {
378 use super::*;
379 use serde_json::{Value, json};
380
381 #[test]
382 fn transcript_envelopes_and_multimodal_content() {
383 for (transcript, sender, recipient, id) in [
384 ("parent", "parent", "child", "sent-1"),
385 ("child", "parent", "child", "received-9"),
386 ("child", "child", "parent", "sent-2"),
387 ] {
388 let wire = json!({"sessionId": transcript, "update": {
389 "sessionUpdate": "session_message", "messageId": id,
390 "senderSessionId": sender, "recipientSessionId": recipient, "content": [
391 {"type": "text", "text": "hello"},
392 {"type": "image", "data": "aGVsbG8=", "mimeType": "image/png"}
393 ]
394 }});
395 let decoded: UpdateSessionNotification = serde_json::from_value(wire.clone()).unwrap();
396 assert_eq!(serde_json::to_value(decoded).unwrap(), wire);
397 }
398 }
399
400 #[test]
401 fn chunks_and_upserts_share_transcript_local_identity() {
402 for (transcript, id) in [("parent", "sent-1"), ("child", "received-9")] {
403 let wire = json!({"sessionId": transcript, "update": {
404 "sessionUpdate": "session_message_chunk", "messageId": id,
405 "senderSessionId": "parent", "recipientSessionId": "child",
406 "content": {"type": "text", "text": "hello"}
407 }});
408 let decoded: UpdateSessionNotification = serde_json::from_value(wire.clone()).unwrap();
409 assert_eq!(serde_json::to_value(decoded).unwrap(), wire);
410 let upsert = SessionMessage::new(id)
411 .sender_session_id(SessionId::new("parent"))
412 .recipient_session_id(SessionId::new("child"))
413 .content(vec![]);
414 let chunk = SessionMessageChunk::new(
415 id,
416 ContentBlock::Text(crate::v2::TextContent::new("hello")),
417 )
418 .sender_session_id(SessionId::new("parent"))
419 .recipient_session_id(SessionId::new("child"));
420 assert_eq!(chunk.message_id, upsert.message_id);
421 assert_eq!(chunk.sender_session_id, upsert.sender_session_id);
422 assert_eq!(chunk.recipient_session_id, upsert.recipient_session_id);
423 assert_eq!(
424 serde_json::to_value(SessionUpdate::SessionMessageChunk(chunk)).unwrap(),
425 wire["update"]
426 );
427 assert_eq!(serde_json::to_value(upsert).unwrap()["content"], json!([]));
428 }
429 }
430
431 #[test]
432 fn required_ids_and_chunk_content_with_upsert_patch_semantics() {
433 for (kind, content) in [
434 ("session_message", json!([])),
435 (
436 "session_message_chunk",
437 json!({"type": "text", "text": "hello"}),
438 ),
439 ] {
440 let base = json!({"sessionUpdate": kind, "messageId": "m1",
441 "senderSessionId": "parent", "recipientSessionId": "child", "content": content});
442 {
443 let key = "messageId";
444 let mut missing = base.clone();
445 missing.as_object_mut().unwrap().remove(key);
446 assert!(
447 serde_json::from_value::<SessionUpdate>(missing).is_err(),
448 "{kind} {key}"
449 );
450 let mut null = base.clone();
451 null[key] = Value::Null;
452 assert!(
453 serde_json::from_value::<SessionUpdate>(null).is_err(),
454 "{kind} {key}"
455 );
456 let mut non_string = base.clone();
457 non_string[key] = json!(42);
458 assert!(
459 serde_json::from_value::<SessionUpdate>(non_string).is_err(),
460 "{kind} {key}"
461 );
462 }
463 if kind == "session_message_chunk" {
464 for content in [None, Some(Value::Null), Some(json!([]))] {
465 let mut invalid = base.clone();
466 invalid.as_object_mut().unwrap().remove("content");
467 if let Some(content) = content {
468 invalid["content"] = content;
469 }
470 assert!(serde_json::from_value::<SessionUpdate>(invalid).is_err());
471 }
472 } else {
473 for content in [None, Some(Value::Null), Some(json!([])), Some(json!({}))] {
474 let mut wire = base.clone();
475 wire.as_object_mut().unwrap().remove("content");
476 if let Some(content) = content {
477 wire["content"] = content;
478 }
479 let decoded: SessionUpdate = serde_json::from_value(wire.clone()).unwrap();
480 let encoded = serde_json::to_value(decoded).unwrap();
481 if wire["content"].is_object() {
482 wire.as_object_mut().unwrap().remove("content");
483 }
484 assert_eq!(encoded, wire);
485 }
486 }
487 for meta in [None, Some(Value::Null), Some(json!({"tag": "value"}))] {
488 let mut wire = base.clone();
489 if let Some(meta) = meta {
490 wire["_meta"] = meta;
491 }
492 let decoded: SessionUpdate = serde_json::from_value(wire.clone()).unwrap();
493 let encoded = serde_json::to_value(decoded).unwrap();
494 if kind == "session_message" {
495 assert_eq!(encoded, wire);
496 } else if wire["_meta"].is_null() {
497 assert_eq!(encoded, base);
498 } else {
499 assert_eq!(encoded, wire);
500 }
501 }
502 }
503 let metadata_only = json!({"sessionUpdate": "session_message",
504 "messageId": "m1", "senderSessionId": "parent",
505 "recipientSessionId": "child", "_meta": {"tag": "value"}});
506 let decoded: SessionUpdate = serde_json::from_value(metadata_only.clone()).unwrap();
507 assert_eq!(serde_json::to_value(decoded).unwrap(), metadata_only);
508 let cleared = SessionMessage::new("m1")
509 .sender_session_id(SessionId::new("parent"))
510 .recipient_session_id(SessionId::new("child"))
511 .content(None)
512 .meta(None);
513 assert_eq!(
514 serde_json::to_value(cleared).unwrap(),
515 json!({"messageId": "m1", "senderSessionId": "parent",
516 "recipientSessionId": "child", "content": null, "_meta": null})
517 );
518 }
519
520 #[cfg(feature = "schemars")]
521 #[test]
522 fn schemas_require_ids_and_chunk_content_but_not_upsert_content() {
523 let upsert = serde_json::to_value(schemars::schema_for!(SessionMessage)).unwrap();
524 let required = upsert["required"].as_array().unwrap();
525 assert_eq!(required, &vec![json!("messageId")]);
526 let chunk = serde_json::to_value(schemars::schema_for!(SessionMessageChunk)).unwrap();
527 let required = chunk["required"].as_array().unwrap();
528 assert_eq!(required.len(), 2);
529 assert!(required.contains(&json!("messageId")));
530 assert!(required.contains(&json!("content")));
531 }
532
533 #[test]
534 fn endpoints_can_arrive_late_or_be_omitted_from_later_events() {
535 let block = ContentBlock::Text(crate::v2::TextContent::new("hello"));
536 let minimal = SessionMessage::new("m1");
537 let first = SessionMessageChunk::new("m1", block.clone());
538 assert_eq!(
539 serde_json::to_value(&minimal).unwrap(),
540 json!({"messageId": "m1"})
541 );
542 assert_eq!(
543 serde_json::to_value(&first).unwrap(),
544 json!({"messageId": "m1", "content": {"type": "text", "text": "hello"}})
545 );
546 let enriched = SessionMessage::new("m1")
547 .sender_session_id(SessionId::new("parent"))
548 .recipient_session_id(SessionId::new("child"));
549 assert_eq!(enriched.sender_session_id, Some(SessionId::new("parent")));
550 assert_eq!(enriched.recipient_session_id, Some(SessionId::new("child")));
551 let later =
552 SessionMessageChunk::new("m1", block).sender_session_id(SessionId::new("parent"));
553 assert_eq!(later.sender_session_id, Some(SessionId::new("parent")));
554 assert_eq!(later.recipient_session_id, None);
555 for update in [
556 SessionUpdate::SessionMessage(minimal),
557 SessionUpdate::SessionMessageChunk(first),
558 SessionUpdate::SessionMessage(enriched),
559 SessionUpdate::SessionMessageChunk(later),
560 ] {
561 let wire = serde_json::to_value(&update).unwrap();
562 let decoded: SessionUpdate = serde_json::from_value(wire.clone()).unwrap();
563 assert_eq!(serde_json::to_value(decoded).unwrap(), wire);
564 }
565 }
566
567 #[test]
568 fn invalid_or_null_endpoints_are_absent_but_message_id_is_required() {
569 for (kind, content) in [
570 ("session_message", None),
571 (
572 "session_message_chunk",
573 Some(json!({"type": "text", "text": "hello"})),
574 ),
575 ] {
576 let mut base = json!({"sessionUpdate": kind, "messageId": "m1",
577 "senderSessionId": null, "recipientSessionId": 42});
578 if let Some(content) = content {
579 base["content"] = content;
580 }
581 let decoded: SessionUpdate = serde_json::from_value(base.clone()).unwrap();
582 let encoded = serde_json::to_value(decoded).unwrap();
583 base.as_object_mut().unwrap().remove("senderSessionId");
584 base.as_object_mut().unwrap().remove("recipientSessionId");
585 assert_eq!(encoded, base);
586 for bad in [Value::Null, json!(42)] {
587 let mut invalid = base.clone();
588 invalid["messageId"] = bad;
589 assert!(serde_json::from_value::<SessionUpdate>(invalid).is_err());
590 }
591 }
592 }
593}
594
595#[cfg(all(test, not(feature = "unstable_subagents")))]
596mod disabled_session_message_tests {
597 use super::*;
598 use serde_json::json;
599
600 #[test]
601 fn preserves_both_unknown_message_discriminators() {
602 for (kind, content) in [
603 ("session_message", json!([])),
604 (
605 "session_message_chunk",
606 json!({"type": "text", "text": "hello"}),
607 ),
608 ] {
609 let wire = json!({"sessionUpdate": kind, "messageId": "m1",
610 "senderSessionId": "parent", "recipientSessionId": "child", "content": content});
611 let decoded: SessionUpdate = serde_json::from_value(wire.clone()).unwrap();
612 assert!(matches!(decoded, SessionUpdate::Other(_)));
613 assert_eq!(serde_json::to_value(decoded).unwrap(), wire);
614 }
615 }
616}
617
618#[cfg(feature = "unstable_session_notices")]
624#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
625#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
626#[serde(rename_all = "snake_case")]
627#[non_exhaustive]
628pub enum NoticeSeverity {
629 Info,
631 Warning,
633 Error,
635 #[serde(untagged)]
640 Other(String),
641}
642
643#[cfg(feature = "unstable_session_notices")]
655#[serde_as]
656#[skip_serializing_none]
657#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
658#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
659#[serde(rename_all = "camelCase")]
660#[non_exhaustive]
661pub struct Notice {
662 pub severity: NoticeSeverity,
664 #[cfg_attr(feature = "schemars", schemars(length(min = 1)))]
666 pub title: String,
667 #[serde_as(deserialize_as = "DefaultOnError")]
671 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
672 #[serde(default)]
673 pub description: Option<String>,
674 #[serde_as(deserialize_as = "DefaultOnError")]
678 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
679 #[serde(default, rename = "_meta")]
680 pub meta: Option<Meta>,
681}
682
683#[cfg(feature = "unstable_session_notices")]
684impl Notice {
685 #[must_use]
687 pub fn new(severity: NoticeSeverity, title: impl Into<String>) -> Self {
688 Self {
689 severity,
690 title: title.into(),
691 description: None,
692 meta: None,
693 }
694 }
695
696 #[must_use]
698 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
699 self.description = description.into_option();
700 self
701 }
702
703 #[must_use]
705 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
706 self.meta = meta.into_option();
707 self
708 }
709}
710
711#[cfg(feature = "unstable_session_compaction")]
717#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
718#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
719#[serde(transparent)]
720#[from(forward)]
721#[non_exhaustive]
722pub struct CompactionId(pub Arc<str>);
723
724#[cfg(feature = "unstable_session_compaction")]
725impl CompactionId {
726 #[must_use]
728 pub fn new(id: impl Into<Self>) -> Self {
729 id.into()
730 }
731}
732
733#[cfg(feature = "unstable_session_compaction")]
739#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
740#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
741#[serde(rename_all = "snake_case")]
742#[non_exhaustive]
743pub enum CompactionStatus {
744 InProgress,
746 Completed,
748 Failed,
750 Cancelled,
752 #[serde(untagged)]
757 Other(String),
758}
759
760#[cfg(feature = "unstable_session_compaction")]
772#[serde_as]
773#[skip_serializing_none]
774#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
775#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
776#[serde(rename_all = "camelCase")]
777#[non_exhaustive]
778pub struct CompactionUpdate {
779 pub compaction_id: CompactionId,
781 pub status: CompactionStatus,
783 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<VecSkipError<_>>>")]
785 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
786 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
787 pub summary: MaybeUndefined<Vec<ContentBlock>>,
788 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
790 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
791 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
792 pub error: MaybeUndefined<String>,
793 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
795 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
796 #[serde(
797 rename = "_meta",
798 default,
799 skip_serializing_if = "MaybeUndefined::is_undefined"
800 )]
801 pub meta: MaybeUndefined<Meta>,
802}
803
804#[cfg(feature = "unstable_session_compaction")]
805impl CompactionUpdate {
806 #[must_use]
808 pub fn new(compaction_id: impl Into<CompactionId>, status: CompactionStatus) -> Self {
809 Self {
810 compaction_id: compaction_id.into(),
811 status,
812 summary: MaybeUndefined::Undefined,
813 error: MaybeUndefined::Undefined,
814 meta: MaybeUndefined::Undefined,
815 }
816 }
817
818 #[must_use]
820 pub fn summary(mut self, summary: impl IntoMaybeUndefined<Vec<ContentBlock>>) -> Self {
821 self.summary = summary.into_maybe_undefined();
822 self
823 }
824
825 #[must_use]
827 pub fn error(mut self, error: impl IntoMaybeUndefined<String>) -> Self {
828 self.error = error.into_maybe_undefined();
829 self
830 }
831
832 #[must_use]
834 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
835 self.meta = meta.into_maybe_undefined();
836 self
837 }
838}
839
840#[cfg(feature = "unstable_session_compaction")]
848#[serde_as]
849#[skip_serializing_none]
850#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
851#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
852#[serde(rename_all = "camelCase")]
853#[non_exhaustive]
854pub struct CompactionSummaryChunk {
855 pub compaction_id: CompactionId,
857 pub content: ContentBlock,
859 #[serde_as(deserialize_as = "DefaultOnError")]
861 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
862 #[serde(default, rename = "_meta")]
863 pub meta: Option<Meta>,
864}
865
866#[cfg(feature = "unstable_session_compaction")]
867impl CompactionSummaryChunk {
868 #[must_use]
870 pub fn new(compaction_id: impl Into<CompactionId>, content: ContentBlock) -> Self {
871 Self {
872 compaction_id: compaction_id.into(),
873 content,
874 meta: None,
875 }
876 }
877
878 #[must_use]
880 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
881 self.meta = meta.into_option();
882 self
883 }
884}
885
886#[cfg(feature = "unstable_subagents")]
920#[serde_as]
921#[skip_serializing_none]
922#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
923#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
924#[serde(rename_all = "camelCase")]
925#[non_exhaustive]
926pub struct SubagentUpdate {
927 pub session_id: SessionId,
929 #[serde_as(deserialize_as = "DefaultOnError")]
934 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
935 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
936 pub title: MaybeUndefined<String>,
937 #[serde_as(deserialize_as = "DefaultOnError")]
942 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
943 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
944 pub description: MaybeUndefined<String>,
945 #[serde_as(deserialize_as = "DefaultOnError")]
950 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
951 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
952 pub capabilities: MaybeUndefined<SubagentSessionCapabilities>,
953 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
959 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
960 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
961 pub state: MaybeUndefined<StateUpdate>,
962 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
968 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
969 #[serde(
970 rename = "_meta",
971 default,
972 skip_serializing_if = "MaybeUndefined::is_undefined"
973 )]
974 pub meta: MaybeUndefined<Meta>,
975}
976
977#[cfg(feature = "unstable_subagents")]
978impl SubagentUpdate {
979 #[must_use]
981 pub fn new(session_id: impl Into<SessionId>) -> Self {
982 Self {
983 session_id: session_id.into(),
984 title: MaybeUndefined::Undefined,
985 description: MaybeUndefined::Undefined,
986 capabilities: MaybeUndefined::Undefined,
987 state: MaybeUndefined::Undefined,
988 meta: MaybeUndefined::Undefined,
989 }
990 }
991
992 #[must_use]
994 pub fn title(mut self, title: impl IntoMaybeUndefined<String>) -> Self {
995 self.title = title.into_maybe_undefined();
996 self
997 }
998
999 #[must_use]
1001 pub fn description(mut self, description: impl IntoMaybeUndefined<String>) -> Self {
1002 self.description = description.into_maybe_undefined();
1003 self
1004 }
1005
1006 #[must_use]
1008 pub fn capabilities(
1009 mut self,
1010 capabilities: impl IntoMaybeUndefined<SubagentSessionCapabilities>,
1011 ) -> Self {
1012 self.capabilities = capabilities.into_maybe_undefined();
1013 self
1014 }
1015
1016 #[must_use]
1018 pub fn state(mut self, state: impl IntoMaybeUndefined<StateUpdate>) -> Self {
1019 self.state = state.into_maybe_undefined();
1020 self
1021 }
1022
1023 #[must_use]
1025 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
1026 self.meta = meta.into_maybe_undefined();
1027 self
1028 }
1029}
1030
1031#[cfg(feature = "unstable_subagents")]
1040#[serde_as]
1041#[skip_serializing_none]
1042#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1043#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1044#[serde(rename_all = "camelCase")]
1045#[non_exhaustive]
1046pub struct SubagentSessionCapabilities {
1047 #[serde_as(deserialize_as = "DefaultOnError")]
1051 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1052 #[serde(default)]
1053 pub cancel: Option<SessionCancelCapabilities>,
1054 #[serde_as(deserialize_as = "DefaultOnError")]
1058 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1059 #[serde(default)]
1060 #[serde(rename = "_meta")]
1061 pub meta: Option<Meta>,
1062}
1063
1064#[cfg(feature = "unstable_subagents")]
1065impl SubagentSessionCapabilities {
1066 #[must_use]
1068 pub fn new() -> Self {
1069 Self::default()
1070 }
1071
1072 #[must_use]
1074 pub fn cancel(mut self, cancel: impl IntoOption<SessionCancelCapabilities>) -> Self {
1075 self.cancel = cancel.into_option();
1076 self
1077 }
1078
1079 #[must_use]
1083 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1084 self.meta = meta.into_option();
1085 self
1086 }
1087}
1088
1089#[cfg(feature = "unstable_subagents")]
1097#[serde_as]
1098#[skip_serializing_none]
1099#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1100#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1101#[non_exhaustive]
1102pub struct SessionCancelCapabilities {
1103 #[serde_as(deserialize_as = "DefaultOnError")]
1109 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1110 #[serde(default)]
1111 #[serde(rename = "_meta")]
1112 pub meta: Option<Meta>,
1113}
1114
1115#[cfg(feature = "unstable_subagents")]
1116impl SessionCancelCapabilities {
1117 #[must_use]
1119 pub fn new() -> Self {
1120 Self::default()
1121 }
1122
1123 #[must_use]
1129 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1130 self.meta = meta.into_option();
1131 self
1132 }
1133}
1134
1135#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1141#[derive(Debug, Clone, Serialize, PartialEq)]
1142#[cfg_attr(feature = "schemars", schemars(inline))]
1143#[cfg_attr(feature = "schemars", schemars(transform = other_session_update_schema))]
1144#[serde(rename_all = "camelCase")]
1145#[non_exhaustive]
1146pub struct OtherSessionUpdate {
1147 #[serde(rename = "sessionUpdate")]
1153 pub session_update: String,
1154 #[serde(flatten)]
1156 pub fields: BTreeMap<String, serde_json::Value>,
1157}
1158
1159impl OtherSessionUpdate {
1160 #[must_use]
1162 pub fn new(
1163 session_update: impl Into<String>,
1164 mut fields: BTreeMap<String, serde_json::Value>,
1165 ) -> Self {
1166 fields.remove("sessionUpdate");
1167 Self {
1168 session_update: session_update.into(),
1169 fields,
1170 }
1171 }
1172}
1173
1174impl<'de> Deserialize<'de> for OtherSessionUpdate {
1175 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1176 where
1177 D: serde::Deserializer<'de>,
1178 {
1179 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1180 let session_update = fields
1181 .remove("sessionUpdate")
1182 .ok_or_else(|| serde::de::Error::missing_field("sessionUpdate"))?;
1183 let serde_json::Value::String(session_update) = session_update else {
1184 return Err(serde::de::Error::custom("`sessionUpdate` must be a string"));
1185 };
1186
1187 if is_known_session_update(&session_update) {
1188 return Err(serde::de::Error::custom(format!(
1189 "known session update `{session_update}` did not match its schema"
1190 )));
1191 }
1192
1193 Ok(Self {
1194 session_update,
1195 fields,
1196 })
1197 }
1198}
1199
1200fn is_known_session_update(session_update: &str) -> bool {
1201 #[cfg(feature = "unstable_session_notices")]
1202 if session_update == "notice" {
1203 return true;
1204 }
1205 #[cfg(feature = "unstable_session_compaction")]
1206 if matches!(
1207 session_update,
1208 "compaction_update" | "compaction_summary_chunk"
1209 ) {
1210 return true;
1211 }
1212 #[cfg(feature = "unstable_plan_operations")]
1213 if session_update == "plan_removed" {
1214 return true;
1215 }
1216 #[cfg(feature = "unstable_subagents")]
1217 if matches!(
1218 session_update,
1219 "subagent_update" | "session_message" | "session_message_chunk"
1220 ) {
1221 return true;
1222 }
1223 matches!(
1224 session_update,
1225 "user_message_chunk"
1226 | "user_message"
1227 | "agent_message_chunk"
1228 | "agent_message"
1229 | "agent_thought_chunk"
1230 | "agent_thought"
1231 | "state_update"
1232 | "tool_call_content_chunk"
1233 | "tool_call_update"
1234 | "terminal_update"
1235 | "terminal_output_chunk"
1236 | "plan_update"
1237 | "available_commands_update"
1238 | "config_option_update"
1239 | "session_info_update"
1240 | "usage_update"
1241 )
1242}
1243
1244#[cfg(feature = "schemars")]
1245fn other_session_update_schema(schema: &mut Schema) {
1246 super::schema_util::reject_known_string_discriminators(
1247 schema,
1248 "sessionUpdate",
1249 &[
1250 "user_message_chunk",
1251 "user_message",
1252 "agent_message_chunk",
1253 "agent_message",
1254 "agent_thought_chunk",
1255 "agent_thought",
1256 "state_update",
1257 "tool_call_content_chunk",
1258 "tool_call_update",
1259 "terminal_update",
1260 "terminal_output_chunk",
1261 "plan_update",
1262 "available_commands_update",
1263 "config_option_update",
1264 "session_info_update",
1265 #[cfg(feature = "unstable_plan_operations")]
1266 "plan_removed",
1267 "usage_update",
1268 #[cfg(feature = "unstable_session_notices")]
1269 "notice",
1270 #[cfg(feature = "unstable_session_compaction")]
1271 "compaction_update",
1272 #[cfg(feature = "unstable_session_compaction")]
1273 "compaction_summary_chunk",
1274 #[cfg(feature = "unstable_subagents")]
1275 "subagent_update",
1276 #[cfg(feature = "unstable_subagents")]
1277 "session_message",
1278 #[cfg(feature = "unstable_subagents")]
1279 "session_message_chunk",
1280 ],
1281 );
1282}
1283
1284#[serde_as]
1286#[skip_serializing_none]
1287#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1288#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1289#[serde(rename_all = "camelCase")]
1290#[non_exhaustive]
1291pub struct ConfigOptionUpdate {
1292 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
1294 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
1295 pub config_options: Vec<SessionConfigOption>,
1296 #[serde_as(deserialize_as = "DefaultOnError")]
1302 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1303 #[serde(default)]
1304 #[serde(rename = "_meta")]
1305 pub meta: Option<Meta>,
1306}
1307
1308impl ConfigOptionUpdate {
1309 #[must_use]
1311 pub fn new(config_options: Vec<SessionConfigOption>) -> Self {
1312 Self {
1313 config_options,
1314 meta: None,
1315 }
1316 }
1317
1318 #[must_use]
1324 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1325 self.meta = meta.into_option();
1326 self
1327 }
1328}
1329
1330#[serde_as]
1338#[skip_serializing_none]
1339#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1340#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1341#[serde(rename_all = "camelCase")]
1342#[non_exhaustive]
1343pub struct SessionInfoUpdate {
1344 #[serde_as(deserialize_as = "DefaultOnError")]
1346 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1347 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
1348 pub title: MaybeUndefined<String>,
1349 #[serde_as(deserialize_as = "DefaultOnError")]
1351 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "format" = "date-time")))]
1352 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
1353 pub updated_at: MaybeUndefined<String>,
1354 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
1360 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1361 #[serde(
1362 rename = "_meta",
1363 default,
1364 skip_serializing_if = "MaybeUndefined::is_undefined"
1365 )]
1366 pub meta: MaybeUndefined<Meta>,
1367}
1368
1369impl SessionInfoUpdate {
1370 #[must_use]
1372 pub fn new() -> Self {
1373 Self::default()
1374 }
1375
1376 #[must_use]
1378 pub fn title(mut self, title: impl IntoMaybeUndefined<String>) -> Self {
1379 self.title = title.into_maybe_undefined();
1380 self
1381 }
1382
1383 #[must_use]
1385 pub fn updated_at(mut self, updated_at: impl IntoMaybeUndefined<String>) -> Self {
1386 self.updated_at = updated_at.into_maybe_undefined();
1387 self
1388 }
1389
1390 #[must_use]
1396 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
1397 self.meta = meta.into_maybe_undefined();
1398 self
1399 }
1400}
1401
1402#[serde_as]
1404#[skip_serializing_none]
1405#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1406#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1407#[serde(rename_all = "camelCase")]
1408#[non_exhaustive]
1409pub struct UsageUpdate {
1410 pub used: u64,
1412 pub size: u64,
1414 #[serde_as(deserialize_as = "DefaultOnError")]
1416 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1417 #[serde(default)]
1418 pub cost: Option<Cost>,
1419 #[serde_as(deserialize_as = "DefaultOnError")]
1425 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1426 #[serde(default)]
1427 #[serde(rename = "_meta")]
1428 pub meta: Option<Meta>,
1429}
1430
1431impl UsageUpdate {
1432 #[must_use]
1434 pub fn new(used: u64, size: u64) -> Self {
1435 Self {
1436 used,
1437 size,
1438 cost: None,
1439 meta: None,
1440 }
1441 }
1442
1443 #[must_use]
1445 pub fn cost(mut self, cost: impl IntoOption<Cost>) -> Self {
1446 self.cost = cost.into_option();
1447 self
1448 }
1449
1450 #[must_use]
1456 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1457 self.meta = meta.into_option();
1458 self
1459 }
1460}
1461
1462#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1467#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1468#[serde(tag = "state", rename_all = "snake_case")]
1469#[non_exhaustive]
1470pub enum StateUpdate {
1471 Running(RunningStateUpdate),
1473 Idle(IdleStateUpdate),
1475 RequiresAction(RequiresActionStateUpdate),
1477 #[cfg(feature = "unstable_subagents")]
1484 Unknown(UnknownStateUpdate),
1485 #[serde(untagged)]
1491 Other(OtherStateUpdate),
1492}
1493
1494#[serde_as]
1496#[skip_serializing_none]
1497#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1498#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1499#[serde(rename_all = "camelCase")]
1500#[non_exhaustive]
1501pub struct RunningStateUpdate {
1502 #[serde_as(deserialize_as = "DefaultOnError")]
1508 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1509 #[serde(default)]
1510 #[serde(rename = "_meta")]
1511 pub meta: Option<Meta>,
1512}
1513
1514impl RunningStateUpdate {
1515 #[must_use]
1517 pub fn new() -> Self {
1518 Self::default()
1519 }
1520
1521 #[must_use]
1527 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1528 self.meta = meta.into_option();
1529 self
1530 }
1531}
1532
1533#[serde_as]
1535#[skip_serializing_none]
1536#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1537#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
1538#[serde(rename_all = "camelCase")]
1539#[non_exhaustive]
1540pub struct IdleStateUpdate {
1541 #[serde_as(deserialize_as = "DefaultOnError")]
1546 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1547 #[serde(default)]
1548 pub stop_reason: Option<StopReason>,
1549 #[cfg(feature = "unstable_end_turn_token_usage")]
1558 #[serde_as(deserialize_as = "DefaultOnError")]
1559 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1560 #[serde(default)]
1561 pub usage: Option<Usage>,
1562 #[serde_as(deserialize_as = "DefaultOnError")]
1568 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1569 #[serde(default)]
1570 #[serde(rename = "_meta")]
1571 pub meta: Option<Meta>,
1572}
1573
1574impl IdleStateUpdate {
1575 #[must_use]
1577 pub fn new() -> Self {
1578 Self::default()
1579 }
1580
1581 #[must_use]
1583 pub fn stop_reason(mut self, stop_reason: impl IntoOption<StopReason>) -> Self {
1584 self.stop_reason = stop_reason.into_option();
1585 self
1586 }
1587
1588 #[cfg(feature = "unstable_end_turn_token_usage")]
1594 #[must_use]
1595 pub fn usage(mut self, usage: impl IntoOption<Usage>) -> Self {
1596 self.usage = usage.into_option();
1597 self
1598 }
1599
1600 #[must_use]
1606 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1607 self.meta = meta.into_option();
1608 self
1609 }
1610}
1611
1612#[serde_as]
1614#[skip_serializing_none]
1615#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1616#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1617#[serde(rename_all = "camelCase")]
1618#[non_exhaustive]
1619pub struct RequiresActionStateUpdate {
1620 #[serde_as(deserialize_as = "DefaultOnError")]
1626 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1627 #[serde(default)]
1628 #[serde(rename = "_meta")]
1629 pub meta: Option<Meta>,
1630}
1631
1632impl RequiresActionStateUpdate {
1633 #[must_use]
1635 pub fn new() -> Self {
1636 Self::default()
1637 }
1638
1639 #[must_use]
1645 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1646 self.meta = meta.into_option();
1647 self
1648 }
1649}
1650
1651#[cfg(feature = "unstable_subagents")]
1665#[serde_as]
1666#[skip_serializing_none]
1667#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1668#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1669#[serde(rename_all = "camelCase")]
1670#[non_exhaustive]
1671pub struct UnknownStateUpdate {
1672 #[serde_as(deserialize_as = "DefaultOnError")]
1676 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1677 #[serde(default, rename = "_meta")]
1678 pub meta: Option<Meta>,
1679}
1680
1681#[cfg(feature = "unstable_subagents")]
1682impl UnknownStateUpdate {
1683 #[must_use]
1685 pub fn new() -> Self {
1686 Self::default()
1687 }
1688
1689 #[must_use]
1691 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1692 self.meta = meta.into_option();
1693 self
1694 }
1695}
1696
1697#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1702#[derive(Debug, Clone, Serialize, PartialEq)]
1703#[cfg_attr(feature = "schemars", schemars(inline))]
1704#[cfg_attr(feature = "schemars", schemars(transform = other_state_update_schema))]
1705#[serde(rename_all = "camelCase")]
1706#[non_exhaustive]
1707pub struct OtherStateUpdate {
1708 #[serde(rename = "state")]
1714 pub state: String,
1715 #[serde(flatten)]
1717 pub fields: BTreeMap<String, serde_json::Value>,
1718}
1719
1720impl OtherStateUpdate {
1721 #[must_use]
1723 pub fn new(state: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
1724 fields.remove("state");
1725 Self {
1726 state: state.into(),
1727 fields,
1728 }
1729 }
1730}
1731
1732impl<'de> Deserialize<'de> for OtherStateUpdate {
1733 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1734 where
1735 D: serde::Deserializer<'de>,
1736 {
1737 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1738 let state = fields
1739 .remove("state")
1740 .ok_or_else(|| serde::de::Error::missing_field("state"))?;
1741 let serde_json::Value::String(state) = state else {
1742 return Err(serde::de::Error::custom("`state` must be a string"));
1743 };
1744
1745 if is_known_state_update(&state) {
1746 return Err(serde::de::Error::custom(format!(
1747 "known state update `{state}` did not match its schema"
1748 )));
1749 }
1750
1751 Ok(Self { state, fields })
1752 }
1753}
1754
1755const KNOWN_STATE_UPDATE_STATES: &[&str] = &[
1756 "running",
1757 "idle",
1758 "requires_action",
1759 #[cfg(feature = "unstable_subagents")]
1760 "unknown",
1761];
1762
1763fn is_known_state_update(state: &str) -> bool {
1764 KNOWN_STATE_UPDATE_STATES.contains(&state)
1765}
1766
1767#[cfg(feature = "schemars")]
1768fn other_state_update_schema(schema: &mut Schema) {
1769 super::schema_util::reject_known_string_discriminators(
1770 schema,
1771 "state",
1772 KNOWN_STATE_UPDATE_STATES,
1773 );
1774}
1775
1776#[serde_as]
1778#[skip_serializing_none]
1779#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1780#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1781#[serde(rename_all = "camelCase")]
1782#[non_exhaustive]
1783pub struct Cost {
1784 pub amount: f64,
1786 #[cfg_attr(feature = "schemars", schemars(pattern(r"^[A-Z]{3}$")))]
1788 pub currency: String,
1789 #[serde_as(deserialize_as = "DefaultOnError")]
1795 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1796 #[serde(default)]
1797 #[serde(rename = "_meta")]
1798 pub meta: Option<Meta>,
1799}
1800
1801impl Cost {
1802 #[must_use]
1804 pub fn new(amount: f64, currency: impl Into<String>) -> Self {
1805 Self {
1806 amount,
1807 currency: currency.into(),
1808 meta: None,
1809 }
1810 }
1811
1812 #[must_use]
1818 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1819 self.meta = meta.into_option();
1820 self
1821 }
1822}
1823
1824#[serde_as]
1826#[skip_serializing_none]
1827#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1828#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1829#[serde(rename_all = "camelCase")]
1830#[non_exhaustive]
1831pub struct ContentChunk {
1832 pub message_id: MessageId,
1837 pub content: ContentBlock,
1839 #[serde_as(deserialize_as = "DefaultOnError")]
1845 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1846 #[serde(default)]
1847 #[serde(rename = "_meta")]
1848 pub meta: Option<Meta>,
1849}
1850
1851impl ContentChunk {
1852 #[must_use]
1854 pub fn new(content: ContentBlock, message_id: impl Into<MessageId>) -> Self {
1855 Self {
1856 content,
1857 message_id: message_id.into(),
1858 meta: None,
1859 }
1860 }
1861
1862 #[must_use]
1868 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1869 self.meta = meta.into_option();
1870 self
1871 }
1872}
1873
1874#[serde_as]
1888#[skip_serializing_none]
1889#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1890#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1891#[serde(rename_all = "camelCase")]
1892#[non_exhaustive]
1893pub struct UserMessage {
1894 pub message_id: MessageId,
1896 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<VecSkipError<_>>>")]
1898 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
1899 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
1900 pub content: MaybeUndefined<Vec<ContentBlock>>,
1901 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
1907 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1908 #[serde(
1909 rename = "_meta",
1910 default,
1911 skip_serializing_if = "MaybeUndefined::is_undefined"
1912 )]
1913 pub meta: MaybeUndefined<Meta>,
1914}
1915
1916impl UserMessage {
1917 #[must_use]
1919 pub fn new(message_id: impl Into<MessageId>) -> Self {
1920 Self {
1921 message_id: message_id.into(),
1922 content: MaybeUndefined::Undefined,
1923 meta: MaybeUndefined::Undefined,
1924 }
1925 }
1926
1927 #[must_use]
1929 pub fn content(mut self, content: impl IntoMaybeUndefined<Vec<ContentBlock>>) -> Self {
1930 self.content = content.into_maybe_undefined();
1931 self
1932 }
1933
1934 #[must_use]
1940 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
1941 self.meta = meta.into_maybe_undefined();
1942 self
1943 }
1944}
1945
1946#[serde_as]
1960#[skip_serializing_none]
1961#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1962#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1963#[serde(rename_all = "camelCase")]
1964#[non_exhaustive]
1965pub struct AgentMessage {
1966 pub message_id: MessageId,
1968 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<VecSkipError<_>>>")]
1970 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
1971 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
1972 pub content: MaybeUndefined<Vec<ContentBlock>>,
1973 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
1979 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1980 #[serde(
1981 rename = "_meta",
1982 default,
1983 skip_serializing_if = "MaybeUndefined::is_undefined"
1984 )]
1985 pub meta: MaybeUndefined<Meta>,
1986}
1987
1988impl AgentMessage {
1989 #[must_use]
1991 pub fn new(message_id: impl Into<MessageId>) -> Self {
1992 Self {
1993 message_id: message_id.into(),
1994 content: MaybeUndefined::Undefined,
1995 meta: MaybeUndefined::Undefined,
1996 }
1997 }
1998
1999 #[must_use]
2001 pub fn content(mut self, content: impl IntoMaybeUndefined<Vec<ContentBlock>>) -> Self {
2002 self.content = content.into_maybe_undefined();
2003 self
2004 }
2005
2006 #[must_use]
2012 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
2013 self.meta = meta.into_maybe_undefined();
2014 self
2015 }
2016}
2017
2018#[serde_as]
2032#[skip_serializing_none]
2033#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2034#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2035#[serde(rename_all = "camelCase")]
2036#[non_exhaustive]
2037pub struct AgentThought {
2038 pub message_id: MessageId,
2040 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<VecSkipError<_>>>")]
2042 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
2043 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
2044 pub content: MaybeUndefined<Vec<ContentBlock>>,
2045 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
2051 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2052 #[serde(
2053 rename = "_meta",
2054 default,
2055 skip_serializing_if = "MaybeUndefined::is_undefined"
2056 )]
2057 pub meta: MaybeUndefined<Meta>,
2058}
2059
2060impl AgentThought {
2061 #[must_use]
2063 pub fn new(message_id: impl Into<MessageId>) -> Self {
2064 Self {
2065 message_id: message_id.into(),
2066 content: MaybeUndefined::Undefined,
2067 meta: MaybeUndefined::Undefined,
2068 }
2069 }
2070
2071 #[must_use]
2073 pub fn content(mut self, content: impl IntoMaybeUndefined<Vec<ContentBlock>>) -> Self {
2074 self.content = content.into_maybe_undefined();
2075 self
2076 }
2077
2078 #[must_use]
2084 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
2085 self.meta = meta.into_maybe_undefined();
2086 self
2087 }
2088}
2089
2090#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2092#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
2093#[serde(transparent)]
2094#[from(forward)]
2095#[non_exhaustive]
2096pub struct MessageId(pub Arc<str>);
2097
2098impl MessageId {
2099 #[must_use]
2101 pub fn new(id: impl Into<Self>) -> Self {
2102 id.into()
2103 }
2104}
2105
2106#[serde_as]
2108#[skip_serializing_none]
2109#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2110#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2111#[serde(rename_all = "camelCase")]
2112#[non_exhaustive]
2113pub struct AvailableCommandsUpdate {
2114 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
2116 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
2117 pub available_commands: Vec<AvailableCommand>,
2118 #[serde_as(deserialize_as = "DefaultOnError")]
2124 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2125 #[serde(default)]
2126 #[serde(rename = "_meta")]
2127 pub meta: Option<Meta>,
2128}
2129
2130impl AvailableCommandsUpdate {
2131 #[must_use]
2133 pub fn new(available_commands: Vec<AvailableCommand>) -> Self {
2134 Self {
2135 available_commands,
2136 meta: None,
2137 }
2138 }
2139
2140 #[must_use]
2146 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2147 self.meta = meta.into_option();
2148 self
2149 }
2150}
2151
2152#[serde_as]
2154#[skip_serializing_none]
2155#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2156#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2157#[serde(rename_all = "camelCase")]
2158#[non_exhaustive]
2159pub struct AvailableCommand {
2160 pub name: String,
2162 pub description: String,
2164 #[serde_as(deserialize_as = "DefaultOnError")]
2166 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2167 #[serde(default)]
2168 pub input: Option<AvailableCommandInput>,
2169 #[serde_as(deserialize_as = "DefaultOnError")]
2175 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2176 #[serde(default)]
2177 #[serde(rename = "_meta")]
2178 pub meta: Option<Meta>,
2179}
2180
2181impl AvailableCommand {
2182 #[must_use]
2184 pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
2185 Self {
2186 name: name.into(),
2187 description: description.into(),
2188 input: None,
2189 meta: None,
2190 }
2191 }
2192
2193 #[must_use]
2195 pub fn input(mut self, input: impl IntoOption<AvailableCommandInput>) -> Self {
2196 self.input = input.into_option();
2197 self
2198 }
2199
2200 #[must_use]
2206 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2207 self.meta = meta.into_option();
2208 self
2209 }
2210}
2211
2212#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2214#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2215#[serde(tag = "type", rename_all = "snake_case")]
2216#[non_exhaustive]
2217pub enum AvailableCommandInput {
2218 #[serde(rename = "text")]
2220 Text(TextCommandInput),
2221 #[serde(untagged)]
2232 Other(OtherAvailableCommandInput),
2233}
2234
2235#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2237#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
2238#[cfg_attr(feature = "schemars", schemars(inline))]
2239#[cfg_attr(feature = "schemars", schemars(transform = other_available_command_input_schema))]
2240#[serde(rename_all = "camelCase")]
2241#[non_exhaustive]
2242pub struct OtherAvailableCommandInput {
2243 #[serde(rename = "type")]
2249 pub type_: String,
2250 #[serde(flatten)]
2252 pub fields: BTreeMap<String, serde_json::Value>,
2253}
2254
2255impl OtherAvailableCommandInput {
2256 #[must_use]
2258 pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
2259 fields.remove("type");
2260 Self {
2261 type_: type_.into(),
2262 fields,
2263 }
2264 }
2265}
2266
2267impl<'de> Deserialize<'de> for OtherAvailableCommandInput {
2268 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2269 where
2270 D: serde::Deserializer<'de>,
2271 {
2272 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
2273 let type_ = fields
2274 .remove("type")
2275 .ok_or_else(|| serde::de::Error::missing_field("type"))?;
2276 let serde_json::Value::String(type_) = type_ else {
2277 return Err(serde::de::Error::custom("`type` must be a string"));
2278 };
2279
2280 if is_known_available_command_input_type(&type_) {
2281 return Err(serde::de::Error::custom(format!(
2282 "known available command input type `{type_}` did not match its schema"
2283 )));
2284 }
2285
2286 Ok(Self { type_, fields })
2287 }
2288}
2289
2290const KNOWN_AVAILABLE_COMMAND_INPUT_TYPES: &[&str] = &["text"];
2291
2292fn is_known_available_command_input_type(type_: &str) -> bool {
2293 KNOWN_AVAILABLE_COMMAND_INPUT_TYPES.contains(&type_)
2294}
2295
2296#[cfg(feature = "schemars")]
2297fn other_available_command_input_schema(schema: &mut Schema) {
2298 super::schema_util::reject_known_string_discriminators(
2299 schema,
2300 "type",
2301 KNOWN_AVAILABLE_COMMAND_INPUT_TYPES,
2302 );
2303}
2304
2305#[serde_as]
2307#[skip_serializing_none]
2308#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2309#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2310#[serde(rename_all = "camelCase")]
2311#[non_exhaustive]
2312pub struct TextCommandInput {
2313 pub hint: String,
2315 #[serde_as(deserialize_as = "DefaultOnError")]
2321 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2322 #[serde(default)]
2323 #[serde(rename = "_meta")]
2324 pub meta: Option<Meta>,
2325}
2326
2327impl TextCommandInput {
2328 #[must_use]
2330 pub fn new(hint: impl Into<String>) -> Self {
2331 Self {
2332 hint: hint.into(),
2333 meta: None,
2334 }
2335 }
2336
2337 #[must_use]
2343 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2344 self.meta = meta.into_option();
2345 self
2346 }
2347}
2348
2349#[serde_as]
2357#[skip_serializing_none]
2358#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2359#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2360#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = SESSION_REQUEST_PERMISSION_METHOD_NAME)))]
2361#[serde(rename_all = "camelCase")]
2362#[non_exhaustive]
2363pub struct RequestPermissionRequest {
2364 pub session_id: SessionId,
2366 pub title: String,
2371 #[serde_as(deserialize_as = "DefaultOnError")]
2377 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2378 #[serde(default)]
2379 pub description: Option<String>,
2380 #[serde(default)]
2384 pub subject: Option<RequestPermissionSubject>,
2385 #[cfg_attr(feature = "schemars", schemars(length(min = 1)))]
2388 pub options: Vec<PermissionOption>,
2389 #[serde_as(deserialize_as = "DefaultOnError")]
2395 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2396 #[serde(default)]
2397 #[serde(rename = "_meta")]
2398 pub meta: Option<Meta>,
2399}
2400
2401impl RequestPermissionRequest {
2402 #[must_use]
2404 pub fn new(
2405 session_id: impl Into<SessionId>,
2406 title: impl Into<String>,
2407 options: Vec<PermissionOption>,
2408 ) -> Self {
2409 Self {
2410 session_id: session_id.into(),
2411 title: title.into(),
2412 description: None,
2413 subject: None,
2414 options,
2415 meta: None,
2416 }
2417 }
2418
2419 #[must_use]
2421 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
2422 self.description = description.into_option();
2423 self
2424 }
2425
2426 #[must_use]
2428 pub fn subject(mut self, subject: impl IntoOption<RequestPermissionSubject>) -> Self {
2429 self.subject = subject.into_option();
2430 self
2431 }
2432
2433 #[must_use]
2439 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2440 self.meta = meta.into_option();
2441 self
2442 }
2443}
2444
2445#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2447#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2448#[serde(tag = "type", rename_all = "snake_case")]
2449#[non_exhaustive]
2450pub enum RequestPermissionSubject {
2451 ToolCall(Box<ToolCallPermissionSubject>),
2453 Command(CommandPermissionSubject),
2455 #[serde(untagged)]
2466 Other(OtherRequestPermissionSubject),
2467}
2468
2469impl From<ToolCallPermissionSubject> for RequestPermissionSubject {
2470 fn from(subject: ToolCallPermissionSubject) -> Self {
2471 Self::ToolCall(Box::new(subject))
2472 }
2473}
2474
2475impl From<ToolCallUpdate> for RequestPermissionSubject {
2476 fn from(tool_call: ToolCallUpdate) -> Self {
2477 ToolCallPermissionSubject::new(tool_call).into()
2478 }
2479}
2480
2481impl From<CommandPermissionSubject> for RequestPermissionSubject {
2482 fn from(subject: CommandPermissionSubject) -> Self {
2483 Self::Command(subject)
2484 }
2485}
2486
2487#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2489#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2490#[serde(rename_all = "camelCase")]
2491#[non_exhaustive]
2492pub struct ToolCallPermissionSubject {
2493 pub tool_call: ToolCallUpdate,
2495}
2496
2497impl ToolCallPermissionSubject {
2498 #[must_use]
2500 pub fn new(tool_call: ToolCallUpdate) -> Self {
2501 Self { tool_call }
2502 }
2503}
2504
2505#[serde_as]
2507#[skip_serializing_none]
2508#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2509#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2510#[serde(rename_all = "camelCase")]
2511#[non_exhaustive]
2512pub struct CommandPermissionSubject {
2513 pub command: String,
2515 pub cwd: AbsolutePath,
2517 #[serde_as(deserialize_as = "DefaultOnError")]
2519 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2520 #[serde(default)]
2521 pub tool_call_id: Option<ToolCallId>,
2522 #[serde_as(deserialize_as = "DefaultOnError")]
2524 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2525 #[serde(default)]
2526 pub terminal_id: Option<TerminalId>,
2527 #[serde_as(deserialize_as = "DefaultOnError")]
2533 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2534 #[serde(default)]
2535 #[serde(rename = "_meta")]
2536 pub meta: Option<Meta>,
2537}
2538
2539impl CommandPermissionSubject {
2540 #[must_use]
2542 pub fn new(command: impl Into<String>, cwd: impl Into<AbsolutePath>) -> Self {
2543 Self {
2544 command: command.into(),
2545 cwd: cwd.into(),
2546 tool_call_id: None,
2547 terminal_id: None,
2548 meta: None,
2549 }
2550 }
2551
2552 #[must_use]
2554 pub fn tool_call_id(mut self, tool_call_id: impl IntoOption<ToolCallId>) -> Self {
2555 self.tool_call_id = tool_call_id.into_option();
2556 self
2557 }
2558
2559 #[must_use]
2561 pub fn terminal_id(mut self, terminal_id: impl IntoOption<TerminalId>) -> Self {
2562 self.terminal_id = terminal_id.into_option();
2563 self
2564 }
2565
2566 #[must_use]
2568 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2569 self.meta = meta.into_option();
2570 self
2571 }
2572}
2573
2574#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2576#[derive(Debug, Clone, Serialize, PartialEq)]
2577#[cfg_attr(feature = "schemars", schemars(inline))]
2578#[cfg_attr(feature = "schemars", schemars(transform = other_request_permission_subject_schema))]
2579#[serde(rename_all = "camelCase")]
2580#[non_exhaustive]
2581pub struct OtherRequestPermissionSubject {
2582 #[serde(rename = "type")]
2588 pub type_: String,
2589 #[serde(flatten)]
2591 pub fields: BTreeMap<String, serde_json::Value>,
2592}
2593
2594impl OtherRequestPermissionSubject {
2595 #[must_use]
2597 pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
2598 fields.remove("type");
2599 Self {
2600 type_: type_.into(),
2601 fields,
2602 }
2603 }
2604}
2605
2606impl<'de> Deserialize<'de> for OtherRequestPermissionSubject {
2607 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2608 where
2609 D: serde::Deserializer<'de>,
2610 {
2611 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
2612 let type_ = fields
2613 .remove("type")
2614 .ok_or_else(|| serde::de::Error::missing_field("type"))?;
2615 let serde_json::Value::String(type_) = type_ else {
2616 return Err(serde::de::Error::custom("`type` must be a string"));
2617 };
2618
2619 if is_known_request_permission_subject_type(&type_) {
2620 return Err(serde::de::Error::custom(format!(
2621 "known request permission subject `{type_}` did not match its schema"
2622 )));
2623 }
2624
2625 Ok(Self { type_, fields })
2626 }
2627}
2628
2629fn is_known_request_permission_subject_type(type_: &str) -> bool {
2630 matches!(type_, "tool_call" | "command")
2631}
2632
2633#[cfg(feature = "schemars")]
2634fn other_request_permission_subject_schema(schema: &mut Schema) {
2635 super::schema_util::reject_known_string_discriminators(
2636 schema,
2637 "type",
2638 &["tool_call", "command"],
2639 );
2640}
2641
2642#[serde_as]
2644#[skip_serializing_none]
2645#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2646#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2647#[serde(rename_all = "camelCase")]
2648#[non_exhaustive]
2649pub struct PermissionOption {
2650 pub option_id: PermissionOptionId,
2652 pub name: String,
2654 pub kind: PermissionOptionKind,
2656 #[serde_as(deserialize_as = "DefaultOnError")]
2662 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2663 #[serde(default)]
2664 #[serde(rename = "_meta")]
2665 pub meta: Option<Meta>,
2666}
2667
2668impl PermissionOption {
2669 #[must_use]
2671 pub fn new(
2672 option_id: impl Into<PermissionOptionId>,
2673 name: impl Into<String>,
2674 kind: PermissionOptionKind,
2675 ) -> Self {
2676 Self {
2677 option_id: option_id.into(),
2678 name: name.into(),
2679 kind,
2680 meta: None,
2681 }
2682 }
2683
2684 #[must_use]
2690 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2691 self.meta = meta.into_option();
2692 self
2693 }
2694}
2695
2696#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2698#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
2699#[serde(transparent)]
2700#[from(forward)]
2701#[non_exhaustive]
2702pub struct PermissionOptionId(pub Arc<str>);
2703
2704impl PermissionOptionId {
2705 #[must_use]
2707 pub fn new(id: impl Into<Self>) -> Self {
2708 id.into()
2709 }
2710}
2711
2712#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2716#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2717#[serde(rename_all = "snake_case")]
2718#[non_exhaustive]
2719pub enum PermissionOptionKind {
2720 AllowOnce,
2722 AllowAlways,
2724 RejectOnce,
2726 RejectAlways,
2728 #[serde(untagged)]
2734 Other(String),
2735}
2736
2737#[serde_as]
2739#[skip_serializing_none]
2740#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2741#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2742#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = SESSION_REQUEST_PERMISSION_METHOD_NAME)))]
2743#[serde(rename_all = "camelCase")]
2744#[non_exhaustive]
2745pub struct RequestPermissionResponse {
2746 pub outcome: RequestPermissionOutcome,
2748 #[serde_as(deserialize_as = "DefaultOnError")]
2754 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2755 #[serde(default)]
2756 #[serde(rename = "_meta")]
2757 pub meta: Option<Meta>,
2758}
2759
2760impl RequestPermissionResponse {
2761 #[must_use]
2763 pub fn new(outcome: RequestPermissionOutcome) -> Self {
2764 Self {
2765 outcome,
2766 meta: None,
2767 }
2768 }
2769
2770 #[must_use]
2776 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2777 self.meta = meta.into_option();
2778 self
2779 }
2780}
2781
2782#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2784#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2785#[serde(tag = "outcome", rename_all = "snake_case")]
2786#[non_exhaustive]
2787pub enum RequestPermissionOutcome {
2788 Cancelled,
2796 #[serde(rename_all = "camelCase")]
2798 Selected(SelectedPermissionOutcome),
2799 #[serde(untagged)]
2810 Other(OtherRequestPermissionOutcome),
2811}
2812
2813#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2819#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
2820#[cfg_attr(feature = "schemars", schemars(inline))]
2821#[cfg_attr(feature = "schemars", schemars(transform = other_request_permission_outcome_schema))]
2822#[serde(rename_all = "camelCase")]
2823#[non_exhaustive]
2824pub struct OtherRequestPermissionOutcome {
2825 pub outcome: String,
2831 #[serde(flatten)]
2833 pub fields: BTreeMap<String, serde_json::Value>,
2834}
2835
2836impl OtherRequestPermissionOutcome {
2837 #[must_use]
2839 pub fn new(
2840 outcome: impl Into<String>,
2841 mut fields: BTreeMap<String, serde_json::Value>,
2842 ) -> Self {
2843 fields.remove("outcome");
2844 Self {
2845 outcome: outcome.into(),
2846 fields,
2847 }
2848 }
2849}
2850
2851impl<'de> Deserialize<'de> for OtherRequestPermissionOutcome {
2852 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2853 where
2854 D: serde::Deserializer<'de>,
2855 {
2856 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
2857 let outcome = fields
2858 .remove("outcome")
2859 .ok_or_else(|| serde::de::Error::missing_field("outcome"))?;
2860 let serde_json::Value::String(outcome) = outcome else {
2861 return Err(serde::de::Error::custom("`outcome` must be a string"));
2862 };
2863
2864 if is_known_request_permission_outcome(&outcome) {
2865 return Err(serde::de::Error::custom(format!(
2866 "known request permission outcome `{outcome}` did not match its schema"
2867 )));
2868 }
2869
2870 Ok(Self { outcome, fields })
2871 }
2872}
2873
2874fn is_known_request_permission_outcome(outcome: &str) -> bool {
2875 matches!(outcome, "cancelled" | "selected")
2876}
2877
2878#[cfg(feature = "schemars")]
2879fn other_request_permission_outcome_schema(schema: &mut Schema) {
2880 super::schema_util::reject_known_string_discriminators(
2881 schema,
2882 "outcome",
2883 &["cancelled", "selected"],
2884 );
2885}
2886
2887#[serde_as]
2889#[skip_serializing_none]
2890#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2891#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2892#[serde(rename_all = "camelCase")]
2893#[non_exhaustive]
2894pub struct SelectedPermissionOutcome {
2895 pub option_id: PermissionOptionId,
2897 #[serde_as(deserialize_as = "DefaultOnError")]
2903 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2904 #[serde(default)]
2905 #[serde(rename = "_meta")]
2906 pub meta: Option<Meta>,
2907}
2908
2909impl SelectedPermissionOutcome {
2910 #[must_use]
2912 pub fn new(option_id: impl Into<PermissionOptionId>) -> Self {
2913 Self {
2914 option_id: option_id.into(),
2915 meta: None,
2916 }
2917 }
2918
2919 #[must_use]
2925 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2926 self.meta = meta.into_option();
2927 self
2928 }
2929}
2930
2931#[serde_as]
2940#[skip_serializing_none]
2941#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2942#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2943#[serde(rename_all = "camelCase")]
2944#[non_exhaustive]
2945pub struct ClientCapabilities {
2946 #[serde_as(deserialize_as = "DefaultOnError")]
2953 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2954 #[serde(default)]
2955 pub auth: Option<AuthCapabilities>,
2956 #[serde_as(deserialize_as = "DefaultOnError")]
2962 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2963 #[serde(default)]
2964 pub elicitation: Option<ElicitationCapabilities>,
2965 #[cfg(feature = "unstable_nes")]
2974 #[serde_as(deserialize_as = "DefaultOnError")]
2975 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2976 #[serde(default)]
2977 pub nes: Option<ClientNesCapabilities>,
2978 #[cfg(feature = "unstable_nes")]
2984 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
2985 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
2986 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2987 pub position_encodings: Vec<PositionEncodingKind>,
2988
2989 #[serde_as(deserialize_as = "DefaultOnError")]
2995 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2996 #[serde(default)]
2997 #[serde(rename = "_meta")]
2998 pub meta: Option<Meta>,
2999}
3000
3001impl ClientCapabilities {
3002 #[must_use]
3004 pub fn new() -> Self {
3005 Self::default()
3006 }
3007
3008 #[must_use]
3012 pub fn auth(mut self, auth: impl IntoOption<AuthCapabilities>) -> Self {
3013 self.auth = auth.into_option();
3014 self
3015 }
3016
3017 #[must_use]
3020 pub fn elicitation(mut self, elicitation: impl IntoOption<ElicitationCapabilities>) -> Self {
3021 self.elicitation = elicitation.into_option();
3022 self
3023 }
3024
3025 #[cfg(feature = "unstable_nes")]
3029 #[must_use]
3030 pub fn nes(mut self, nes: impl IntoOption<ClientNesCapabilities>) -> Self {
3031 self.nes = nes.into_option();
3032 self
3033 }
3034
3035 #[cfg(feature = "unstable_nes")]
3039 #[must_use]
3040 pub fn position_encodings(mut self, position_encodings: Vec<PositionEncodingKind>) -> Self {
3041 self.position_encodings = position_encodings;
3042 self
3043 }
3044
3045 #[must_use]
3051 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3052 self.meta = meta.into_option();
3053 self
3054 }
3055}
3056
3057#[serde_as]
3063#[skip_serializing_none]
3064#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3065#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3066#[serde(rename_all = "camelCase")]
3067#[non_exhaustive]
3068pub struct AuthCapabilities {
3069 #[serde_as(deserialize_as = "DefaultOnError")]
3076 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3077 #[serde(default)]
3078 pub terminal: Option<TerminalAuthCapabilities>,
3079 #[serde_as(deserialize_as = "DefaultOnError")]
3085 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3086 #[serde(default)]
3087 #[serde(rename = "_meta")]
3088 pub meta: Option<Meta>,
3089}
3090
3091impl AuthCapabilities {
3092 #[must_use]
3094 pub fn new() -> Self {
3095 Self::default()
3096 }
3097
3098 #[must_use]
3106 pub fn terminal(mut self, terminal: impl IntoOption<TerminalAuthCapabilities>) -> Self {
3107 self.terminal = terminal.into_option();
3108 self
3109 }
3110
3111 #[must_use]
3117 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3118 self.meta = meta.into_option();
3119 self
3120 }
3121}
3122
3123#[serde_as]
3129#[skip_serializing_none]
3130#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3131#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3132#[non_exhaustive]
3133pub struct TerminalAuthCapabilities {
3134 #[serde_as(deserialize_as = "DefaultOnError")]
3140 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3141 #[serde(default)]
3142 #[serde(rename = "_meta")]
3143 pub meta: Option<Meta>,
3144}
3145
3146impl TerminalAuthCapabilities {
3147 #[must_use]
3149 pub fn new() -> Self {
3150 Self::default()
3151 }
3152
3153 #[must_use]
3159 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3160 self.meta = meta.into_option();
3161 self
3162 }
3163}
3164
3165#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3171#[non_exhaustive]
3172pub struct ClientMethodNames {
3173 pub session_request_permission: &'static str,
3175 pub session_update: &'static str,
3177 #[cfg(feature = "unstable_mcp_over_acp")]
3179 pub mcp_message: &'static str,
3180 pub elicitation_create: &'static str,
3182 pub elicitation_complete: &'static str,
3184}
3185
3186pub const CLIENT_METHOD_NAMES: ClientMethodNames = ClientMethodNames {
3188 session_update: SESSION_UPDATE_NOTIFICATION,
3189 session_request_permission: SESSION_REQUEST_PERMISSION_METHOD_NAME,
3190 #[cfg(feature = "unstable_mcp_over_acp")]
3191 mcp_message: MCP_MESSAGE_METHOD_NAME,
3192 elicitation_create: ELICITATION_CREATE_METHOD_NAME,
3193 elicitation_complete: ELICITATION_COMPLETE_NOTIFICATION,
3194};
3195
3196pub(crate) const SESSION_UPDATE_NOTIFICATION: &str = "session/update";
3198pub(crate) const SESSION_REQUEST_PERMISSION_METHOD_NAME: &str = "session/request_permission";
3200pub(crate) const ELICITATION_CREATE_METHOD_NAME: &str = "elicitation/create";
3202pub(crate) const ELICITATION_COMPLETE_NOTIFICATION: &str = "elicitation/complete";
3204
3205#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3212#[derive(Clone, Debug, Serialize, Deserialize)]
3213#[serde(untagged)]
3214#[cfg_attr(feature = "schemars", schemars(inline))]
3215#[non_exhaustive]
3216pub enum AgentRequest {
3217 RequestPermissionRequest(Box<RequestPermissionRequest>),
3228 CreateElicitationRequest(Box<CreateElicitationRequest>),
3232 #[cfg(feature = "unstable_mcp_over_acp")]
3238 MessageMcpRequest(Box<MessageMcpRequest>),
3239 ExtMethodRequest(Box<ExtRequest>),
3247}
3248
3249impl AgentRequest {
3250 #[must_use]
3252 pub fn method(&self) -> &str {
3253 match self {
3254 Self::RequestPermissionRequest(_) => CLIENT_METHOD_NAMES.session_request_permission,
3255 Self::CreateElicitationRequest(_) => CLIENT_METHOD_NAMES.elicitation_create,
3256 #[cfg(feature = "unstable_mcp_over_acp")]
3257 Self::MessageMcpRequest(_) => CLIENT_METHOD_NAMES.mcp_message,
3258 Self::ExtMethodRequest(ext_request) => &ext_request.method,
3259 }
3260 }
3261}
3262
3263#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3270#[derive(Clone, Debug, Serialize, Deserialize)]
3271#[serde(untagged)]
3272#[cfg_attr(feature = "schemars", schemars(inline))]
3273#[non_exhaustive]
3274pub enum ClientResponse {
3275 RequestPermissionResponse(Box<RequestPermissionResponse>),
3277 CreateElicitationResponse(Box<CreateElicitationResponse>),
3279 #[cfg(feature = "unstable_mcp_over_acp")]
3281 MessageMcpResponse(Box<MessageMcpResponse>),
3282 ExtMethodResponse(Box<ExtResponse>),
3284}
3285
3286#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3293#[derive(Clone, Debug, Serialize, Deserialize)]
3294#[serde(untagged)]
3295#[cfg_attr(feature = "schemars", schemars(inline))]
3296#[non_exhaustive]
3297pub enum AgentNotification {
3298 UpdateSessionNotification(Box<UpdateSessionNotification>),
3311 CompleteElicitationNotification(Box<CompleteElicitationNotification>),
3315 ExtNotification(Box<ExtNotification>),
3323}
3324
3325impl AgentNotification {
3326 #[must_use]
3328 pub fn method(&self) -> &str {
3329 match self {
3330 Self::UpdateSessionNotification(_) => CLIENT_METHOD_NAMES.session_update,
3331 Self::CompleteElicitationNotification(_) => CLIENT_METHOD_NAMES.elicitation_complete,
3332 Self::ExtNotification(ext_notification) => &ext_notification.method,
3333 }
3334 }
3335}
3336
3337#[cfg(test)]
3338mod tests {
3339 use super::*;
3340
3341 #[cfg(feature = "unstable_subagents")]
3342 #[test]
3343 fn subagent_update_serializes_as_upsert() {
3344 use serde_json::json;
3345
3346 let announced =
3347 SessionUpdate::SubagentUpdate(SubagentUpdate::new("sess_child_1").capabilities(
3348 SubagentSessionCapabilities::new().cancel(SessionCancelCapabilities::new()),
3349 ));
3350 let wire = json!({
3351 "sessionUpdate": "subagent_update",
3352 "sessionId": "sess_child_1",
3353 "capabilities": { "cancel": {} }
3354 });
3355 assert_eq!(serde_json::to_value(&announced).unwrap(), wire);
3356 assert_eq!(
3357 serde_json::from_value::<SessionUpdate>(wire).unwrap(),
3358 announced
3359 );
3360
3361 let minimal = SubagentUpdate::new("sess_child_1");
3362 assert_eq!(
3363 serde_json::to_value(&minimal).unwrap(),
3364 json!({
3365 "sessionId": "sess_child_1"
3366 })
3367 );
3368 assert!(minimal.capabilities.is_undefined());
3369 assert!(minimal.state.is_undefined());
3370 assert!(minimal.meta.is_undefined());
3371
3372 let cleared_wire = json!({
3374 "sessionId": "sess_child_1",
3375 "capabilities": null,
3376 "state": null,
3377 "_meta": null
3378 });
3379 let cleared: SubagentUpdate = serde_json::from_value(cleared_wire.clone()).unwrap();
3380 assert!(cleared.capabilities.is_null());
3381 assert!(cleared.state.is_null());
3382 assert!(cleared.meta.is_null());
3383 assert_eq!(serde_json::to_value(cleared).unwrap(), cleared_wire);
3384
3385 let revoked: SubagentUpdate = serde_json::from_value(json!({
3386 "sessionId": "sess_child_1",
3387 "capabilities": {}
3388 }))
3389 .unwrap();
3390 assert_eq!(
3391 revoked.capabilities,
3392 MaybeUndefined::Value(SubagentSessionCapabilities::new())
3393 );
3394 assert!(
3395 matches!(revoked.capabilities, MaybeUndefined::Value(ref child) if child.cancel.is_none())
3396 );
3397
3398 assert!(
3399 serde_json::from_value::<SessionUpdate>(json!({
3400 "sessionUpdate": "subagent_update"
3401 }))
3402 .is_err()
3403 );
3404 }
3405
3406 #[cfg(feature = "unstable_subagents")]
3407 #[test]
3408 fn subagent_cancel_capability_is_optional_object() {
3409 use serde_json::json;
3410
3411 for wire in [
3412 json!({}),
3413 json!({"cancel": null}),
3414 json!({"cancel": true}),
3415 json!({"cancel": false}),
3416 ] {
3417 let child: SubagentSessionCapabilities = serde_json::from_value(wire).unwrap();
3418 assert!(child.cancel.is_none());
3419 assert_eq!(serde_json::to_value(child).unwrap(), json!({}));
3420 }
3421 let enabled = SubagentSessionCapabilities::new().cancel(SessionCancelCapabilities::new());
3422 assert_eq!(
3423 serde_json::to_value(&enabled).unwrap(),
3424 json!({"cancel": {}})
3425 );
3426 assert_eq!(
3427 serde_json::from_value::<SubagentSessionCapabilities>(json!({"cancel": {}})).unwrap(),
3428 enabled
3429 );
3430 let meta: Meta = [("source".into(), json!("worker"))].into_iter().collect();
3431 let with_meta =
3432 SubagentSessionCapabilities::new().cancel(SessionCancelCapabilities::new().meta(meta));
3433 let wire = json!({"cancel": {"_meta": {"source": "worker"}}});
3434 assert_eq!(serde_json::to_value(&with_meta).unwrap(), wire);
3435 assert_eq!(
3436 serde_json::from_value::<SubagentSessionCapabilities>(wire).unwrap(),
3437 with_meta
3438 );
3439 assert!(enabled.cancel.is_some());
3440 assert!(enabled.cancel(None).cancel.is_none());
3441 let removed = SubagentUpdate::new("child").capabilities(SubagentSessionCapabilities::new());
3442 assert_eq!(
3443 serde_json::to_value(removed).unwrap(),
3444 json!({"sessionId": "child", "capabilities": {}})
3445 );
3446 }
3447
3448 #[cfg(feature = "unstable_subagents")]
3449 #[test]
3450 fn subagent_display_metadata_uses_patch_semantics() {
3451 use serde_json::json;
3452
3453 let update = SubagentUpdate::new("child")
3454 .title("Test investigator".to_string())
3455 .description("Investigates platform-specific test failures.".to_string());
3456 let wire = json!({
3457 "sessionId": "child",
3458 "title": "Test investigator",
3459 "description": "Investigates platform-specific test failures."
3460 });
3461 assert_eq!(serde_json::to_value(&update).unwrap(), wire);
3462 assert_eq!(
3463 serde_json::from_value::<SubagentUpdate>(wire).unwrap(),
3464 update
3465 );
3466
3467 let minimal = SubagentUpdate::new("child");
3468 assert!(minimal.title.is_undefined());
3469 assert!(minimal.description.is_undefined());
3470 assert_eq!(
3471 serde_json::to_value(&minimal).unwrap(),
3472 json!({"sessionId": "child"})
3473 );
3474 let cleared_wire = json!({
3475 "sessionId": "child", "title": null, "description": null
3476 });
3477 let cleared: SubagentUpdate = serde_json::from_value(cleared_wire.clone()).unwrap();
3478 assert!(cleared.title.is_null());
3479 assert!(cleared.description.is_null());
3480 assert_eq!(serde_json::to_value(cleared).unwrap(), cleared_wire);
3481
3482 let title_only: SubagentUpdate = serde_json::from_value(json!({
3483 "sessionId": "child", "title": "Updated title"
3484 }))
3485 .unwrap();
3486 assert_eq!(
3487 title_only.title,
3488 MaybeUndefined::Value("Updated title".to_string())
3489 );
3490 assert!(title_only.description.is_undefined());
3491 let malformed: SubagentUpdate = serde_json::from_value(json!({
3492 "sessionId": "child", "title": false, "description": false
3493 }))
3494 .unwrap();
3495 assert_eq!(malformed, minimal);
3496 }
3497
3498 #[cfg(feature = "unstable_subagents")]
3499 #[test]
3500 fn subagent_notification_keeps_parent_and_child_ids_nested() {
3501 use serde_json::json;
3502
3503 let notification = UpdateSessionNotification::new(
3504 "parent",
3505 SessionUpdate::SubagentUpdate(SubagentUpdate::new("child")),
3506 );
3507 let wire = json!({
3508 "sessionId": "parent",
3509 "update": {
3510 "sessionUpdate": "subagent_update",
3511 "sessionId": "child"
3512 }
3513 });
3514 assert_eq!(serde_json::to_value(¬ification).unwrap(), wire);
3515 assert_eq!(
3516 serde_json::from_value::<UpdateSessionNotification>(wire).unwrap(),
3517 notification
3518 );
3519 for malformed in [
3520 json!({"sessionId": "parent", "update": {"sessionUpdate": "subagent_update"}}),
3521 json!({"update": {"sessionUpdate": "subagent_update", "sessionId": "child"}}),
3522 ] {
3523 assert!(serde_json::from_value::<UpdateSessionNotification>(malformed).is_err());
3524 }
3525 }
3526
3527 #[cfg(feature = "unstable_subagents")]
3528 #[test]
3529 fn subagent_mirrors_normal_child_state_notifications() {
3530 use serde_json::json;
3531
3532 let association = SubagentUpdate::new("sess_child");
3533 for state in [
3534 StateUpdate::Running(RunningStateUpdate::new()),
3535 StateUpdate::RequiresAction(RequiresActionStateUpdate::new()),
3536 StateUpdate::Unknown(UnknownStateUpdate::new()),
3537 StateUpdate::Running(RunningStateUpdate::new()),
3538 StateUpdate::Idle(IdleStateUpdate::new().stop_reason(StopReason::EndTurn)),
3539 StateUpdate::Running(RunningStateUpdate::new()),
3540 StateUpdate::Idle(IdleStateUpdate::new().stop_reason(StopReason::Cancelled)),
3541 ] {
3542 let child_notification = UpdateSessionNotification::new(
3543 association.session_id.clone(),
3544 SessionUpdate::StateUpdate(state.clone()),
3545 );
3546 let parent_notification = UpdateSessionNotification::new(
3547 "sess_parent",
3548 SessionUpdate::SubagentUpdate(association.clone().state(state)),
3549 );
3550 let child_wire = serde_json::to_value(&child_notification).unwrap();
3551 let parent_wire = serde_json::to_value(&parent_notification).unwrap();
3552 assert_eq!(child_wire["sessionId"], json!("sess_child"));
3553 assert_eq!(child_wire["update"]["sessionUpdate"], json!("state_update"));
3554 assert_eq!(parent_wire["sessionId"], json!("sess_parent"));
3555 assert_eq!(parent_wire["update"]["sessionId"], json!("sess_child"));
3556 let mut child_snapshot = child_wire["update"].clone();
3557 child_snapshot
3558 .as_object_mut()
3559 .unwrap()
3560 .remove("sessionUpdate");
3561 assert_eq!(parent_wire["update"]["state"], child_snapshot);
3562 assert_eq!(
3563 serde_json::from_value::<UpdateSessionNotification>(child_wire).unwrap(),
3564 child_notification
3565 );
3566 assert_eq!(
3567 serde_json::from_value::<UpdateSessionNotification>(parent_wire).unwrap(),
3568 parent_notification
3569 );
3570 }
3571 }
3572
3573 #[cfg(feature = "unstable_subagents")]
3574 #[test]
3575 fn subagent_state_uses_whole_snapshot_patch_semantics() {
3576 use serde_json::json;
3577
3578 let minimal = SubagentUpdate::new("child");
3579 assert!(minimal.state.is_undefined());
3580 assert_eq!(
3581 serde_json::to_value(&minimal).unwrap(),
3582 json!({"sessionId": "child"})
3583 );
3584
3585 let cleared = minimal.clone().state(None::<StateUpdate>);
3586 assert!(cleared.state.is_null());
3587 assert_eq!(
3588 serde_json::to_value(&cleared).unwrap(),
3589 json!({"sessionId": "child", "state": null})
3590 );
3591 assert_eq!(
3592 serde_json::from_value::<SubagentUpdate>(json!({"sessionId": "child", "state": null}))
3593 .unwrap(),
3594 cleared
3595 );
3596
3597 for snapshot in [
3598 json!({"state": "running", "_meta": {"source": "worker"}}),
3599 json!({"state": "idle", "stopReason": "end_turn"}),
3600 json!({"state": "requires_action"}),
3601 json!({"state": "unknown"}),
3602 json!({"state": "_custom", "detail": {"phase": 2}}),
3603 ] {
3604 let state: StateUpdate = serde_json::from_value(snapshot.clone()).unwrap();
3605 let update = minimal.clone().state(state.clone());
3606 let wire = json!({"sessionId": "child", "state": snapshot});
3607 assert_eq!(serde_json::to_value(&update).unwrap(), wire);
3608 assert_eq!(
3609 serde_json::from_value::<SubagentUpdate>(wire).unwrap(),
3610 update
3611 );
3612 assert_eq!(update.state, MaybeUndefined::Value(state));
3613 }
3614
3615 let idle = SubagentUpdate::new("child").state(StateUpdate::Idle(
3618 IdleStateUpdate::new().stop_reason(StopReason::Cancelled),
3619 ));
3620 let running = idle.state(StateUpdate::Running(RunningStateUpdate::new()));
3621 assert_eq!(
3622 serde_json::to_value(running).unwrap(),
3623 json!({"sessionId": "child", "state": {"state": "running"}})
3624 );
3625 for invalid in [json!(false), json!(42), json!({}), json!({"state": 7})] {
3626 let parsed: SubagentUpdate =
3627 serde_json::from_value(json!({"sessionId": "child", "state": invalid})).unwrap();
3628 assert_eq!(parsed, minimal);
3629 }
3630 }
3631
3632 #[cfg(feature = "unstable_subagents")]
3633 #[test]
3634 fn unknown_activity_is_a_known_state_update() {
3635 use serde_json::json;
3636
3637 let update = SessionUpdate::StateUpdate(StateUpdate::Unknown(
3638 UnknownStateUpdate::new().meta(
3639 [("source".into(), json!("worker"))]
3640 .into_iter()
3641 .collect::<Meta>(),
3642 ),
3643 ));
3644 let wire = json!({
3645 "sessionUpdate": "state_update",
3646 "state": "unknown",
3647 "_meta": { "source": "worker" }
3648 });
3649 assert_eq!(serde_json::to_value(&update).unwrap(), wire);
3650 assert_eq!(
3651 serde_json::from_value::<SessionUpdate>(wire).unwrap(),
3652 update
3653 );
3654
3655 for meta in [json!(null), json!(false)] {
3656 let state: StateUpdate = serde_json::from_value(json!({
3657 "state": "unknown",
3658 "_meta": meta
3659 }))
3660 .unwrap();
3661 assert_eq!(state, StateUpdate::Unknown(UnknownStateUpdate::new()));
3662 assert_eq!(
3663 serde_json::to_value(state).unwrap(),
3664 json!({"state": "unknown"})
3665 );
3666 }
3667 assert!(serde_json::from_value::<OtherStateUpdate>(json!({"state": "unknown"})).is_err());
3668 }
3669
3670 #[cfg(not(feature = "unstable_subagents"))]
3671 #[test]
3672 fn unknown_activity_is_preserved_without_subagents_feature() {
3673 let wire = serde_json::json!({
3674 "sessionUpdate": "state_update",
3675 "state": "unknown",
3676 "_meta": { "source": "worker" }
3677 });
3678 let parsed: SessionUpdate = serde_json::from_value(wire.clone()).unwrap();
3679 let SessionUpdate::StateUpdate(StateUpdate::Other(state)) = &parsed else {
3680 panic!("expected unrecognized state payload");
3681 };
3682 assert_eq!(state.state, "unknown");
3683 assert_eq!(serde_json::to_value(parsed).unwrap(), wire);
3684 }
3685
3686 #[cfg(all(feature = "unstable_subagents", feature = "schemars"))]
3687 #[test]
3688 fn subagent_schema_carries_optional_nullable_state_snapshot() {
3689 use serde_json::json;
3690
3691 let schema = serde_json::to_value(schemars::schema_for!(SubagentUpdate)).unwrap();
3692 let properties = schema["properties"].as_object().unwrap();
3693 assert!(properties.contains_key("sessionId"));
3694 assert!(properties.contains_key("title"));
3695 assert!(properties.contains_key("description"));
3696 assert!(properties.contains_key("capabilities"));
3697 assert!(properties.contains_key("state"));
3698 assert!(properties.contains_key("_meta"));
3699 assert!(!properties.contains_key("name"));
3700 assert!(!properties.contains_key("task"));
3701 assert_eq!(schema["required"], json!(["sessionId"]));
3702 assert_eq!(
3703 properties["state"]["x-deserialize-default-on-error"],
3704 json!(true)
3705 );
3706 let state_variants = properties["state"]["anyOf"].as_array().unwrap();
3707 assert!(state_variants.contains(&json!({"type": "null"})));
3708 assert!(
3709 state_variants
3710 .iter()
3711 .any(|variant| variant["$ref"] == "#/$defs/StateUpdate")
3712 );
3713 let child =
3714 serde_json::to_value(schemars::schema_for!(SubagentSessionCapabilities)).unwrap();
3715 let variants = child["properties"]["cancel"]["anyOf"]
3716 .as_array()
3717 .expect("cancel must allow the capability object or null");
3718 assert!(variants.contains(&json!({"type": "null"})));
3719 assert!(
3720 variants
3721 .iter()
3722 .any(|variant| variant["type"] == "object" || variant.get("$ref").is_some())
3723 );
3724 assert!(!variants.iter().any(|variant| variant["type"] == "boolean"));
3725 let cancel =
3726 serde_json::to_value(schemars::schema_for!(SessionCancelCapabilities)).unwrap();
3727 assert_eq!(cancel["type"], "object");
3728 assert!(
3729 !child["required"]
3730 .as_array()
3731 .is_some_and(|required| required.contains(&json!("cancel")))
3732 );
3733 }
3734
3735 #[cfg(not(feature = "unstable_subagents"))]
3736 #[test]
3737 fn unsupported_subagent_update_is_preserved() {
3738 let wire = serde_json::json!({
3739 "sessionUpdate": "subagent_update",
3740 "sessionId": "sess_child",
3741 "capabilities": { "cancel": {} }
3742 });
3743 let parsed: SessionUpdate = serde_json::from_value(wire.clone()).unwrap();
3744 assert!(matches!(&parsed, SessionUpdate::Other(_)));
3745 assert_eq!(serde_json::to_value(parsed).unwrap(), wire);
3746 }
3747
3748 #[cfg(feature = "unstable_session_notices")]
3749 #[test]
3750 fn notice_preserves_wire_shape_nullable_fields_and_open_severity() {
3751 use serde_json::json;
3752
3753 let mut meta = Meta::new();
3754 meta.insert("source".into(), json!("fallback"));
3755 let v2_notice = SessionUpdate::Notice(
3756 Notice::new(NoticeSeverity::Error, "Provider degraded")
3757 .description("Requests may take longer than usual.")
3758 .meta(meta.clone()),
3759 );
3760 let expected = json!({
3761 "sessionUpdate": "notice",
3762 "severity": "error",
3763 "title": "Provider degraded",
3764 "description": "Requests may take longer than usual.",
3765 "_meta": { "source": "fallback" }
3766 });
3767 assert_eq!(serde_json::to_value(&v2_notice).unwrap(), expected);
3768
3769 let v1_notice = crate::v1::SessionUpdate::Notice(
3770 crate::v1::Notice::new(crate::v1::NoticeSeverity::Error, "Provider degraded")
3771 .description("Requests may take longer than usual.")
3772 .meta(meta),
3773 );
3774 assert_eq!(
3775 serde_json::to_value(v2_notice).unwrap(),
3776 serde_json::to_value(v1_notice).unwrap()
3777 );
3778
3779 let SessionUpdate::Notice(notice) = serde_json::from_value(json!({
3780 "sessionUpdate": "notice",
3781 "severity": "critical",
3782 "title": "Provider degraded",
3783 "description": null,
3784 "_meta": null
3785 }))
3786 .unwrap() else {
3787 panic!("expected notice");
3788 };
3789
3790 assert_eq!(
3791 notice.severity,
3792 NoticeSeverity::Other("critical".to_string())
3793 );
3794 assert_eq!(notice.description, None);
3795 assert_eq!(notice.meta, None);
3796 assert_eq!(
3797 serde_json::to_value(SessionUpdate::Notice(notice)).unwrap(),
3798 json!({
3799 "sessionUpdate": "notice",
3800 "severity": "critical",
3801 "title": "Provider degraded"
3802 })
3803 );
3804 }
3805
3806 #[cfg(feature = "unstable_session_notices")]
3807 #[test]
3808 fn malformed_known_notice_is_not_hidden_as_unknown() {
3809 use serde_json::json;
3810
3811 for malformed in [
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_value::<SessionUpdate>(malformed).is_err());
3832 }
3833 }
3834
3835 #[cfg(not(feature = "unstable_session_notices"))]
3836 #[test]
3837 fn unsupported_notice_is_preserved_as_an_unknown_update() {
3838 use serde_json::json;
3839
3840 let SessionUpdate::Other(notice) = serde_json::from_value(json!({
3841 "sessionUpdate": "notice",
3842 "severity": "warning",
3843 "title": "MCP server unavailable"
3844 }))
3845 .unwrap() else {
3846 panic!("expected unknown session update");
3847 };
3848
3849 assert_eq!(notice.session_update, "notice");
3850 assert_eq!(notice.fields.get("severity"), Some(&json!("warning")));
3851 assert_eq!(
3852 notice.fields.get("title"),
3853 Some(&json!("MCP server unavailable"))
3854 );
3855 }
3856
3857 #[cfg(feature = "unstable_session_compaction")]
3858 #[test]
3859 fn compaction_updates_preserve_patch_and_open_status_semantics() {
3860 use serde_json::json;
3861
3862 assert_eq!(
3863 serde_json::to_value(SessionUpdate::CompactionUpdate(
3864 CompactionUpdate::new("cmp_001", CompactionStatus::Completed).summary(vec![
3865 ContentBlock::Text(crate::v2::TextContent::new("retained")),
3866 ]),
3867 ))
3868 .unwrap(),
3869 json!({
3870 "sessionUpdate": "compaction_update",
3871 "compactionId": "cmp_001",
3872 "status": "completed",
3873 "summary": [{ "type": "text", "text": "retained" }]
3874 })
3875 );
3876
3877 let SessionUpdate::CompactionUpdate(update) = serde_json::from_value(json!({
3878 "sessionUpdate": "compaction_update",
3879 "compactionId": "cmp_001",
3880 "status": "paused",
3881 "summary": null
3882 }))
3883 .unwrap() else {
3884 panic!("expected compaction update");
3885 };
3886 assert_eq!(update.status, CompactionStatus::Other("paused".into()));
3887 assert!(update.summary.is_null());
3888 assert!(update.error.is_undefined());
3889 }
3890
3891 #[cfg(feature = "unstable_session_compaction")]
3892 #[test]
3893 fn malformed_known_compaction_update_is_not_hidden_as_unknown() {
3894 use serde_json::json;
3895
3896 assert!(
3897 serde_json::from_value::<SessionUpdate>(json!({
3898 "sessionUpdate": "compaction_update",
3899 "status": "completed"
3900 }))
3901 .is_err()
3902 );
3903 assert!(
3904 serde_json::from_value::<SessionUpdate>(json!({
3905 "sessionUpdate": "compaction_summary_chunk",
3906 "compactionId": "cmp_001"
3907 }))
3908 .is_err()
3909 );
3910 }
3911
3912 #[test]
3913 fn test_elicitation_capability_semantics() {
3914 use serde_json::json;
3915
3916 let unsupported: ClientCapabilities = serde_json::from_value(json!({})).unwrap();
3917 assert!(unsupported.elicitation.is_none());
3918
3919 let null: ClientCapabilities =
3920 serde_json::from_value(json!({ "elicitation": null })).unwrap();
3921 assert!(null.elicitation.is_none());
3922
3923 let malformed: ClientCapabilities =
3924 serde_json::from_value(json!({ "elicitation": false })).unwrap();
3925 assert!(malformed.elicitation.is_none());
3926
3927 let empty: ClientCapabilities =
3928 serde_json::from_value(json!({ "elicitation": {} })).unwrap();
3929 let empty = empty.elicitation.expect("present capability");
3930 assert!(!empty.supports_form());
3931 assert!(!empty.supports_url());
3932
3933 let form_only: ClientCapabilities = serde_json::from_value(json!({
3934 "elicitation": { "form": {} }
3935 }))
3936 .unwrap();
3937 let form_only = form_only.elicitation.expect("advertised capability");
3938 assert!(form_only.supports_form());
3939 assert!(!form_only.supports_url());
3940
3941 let url_only: ClientCapabilities = serde_json::from_value(json!({
3942 "elicitation": { "url": {} }
3943 }))
3944 .unwrap();
3945 let url_only = url_only.elicitation.expect("advertised capability");
3946 assert!(!url_only.supports_form());
3947 assert!(url_only.supports_url());
3948
3949 let both: ClientCapabilities = serde_json::from_value(json!({
3950 "elicitation": { "form": {}, "url": {} }
3951 }))
3952 .unwrap();
3953 let both = both.elicitation.expect("advertised capability");
3954 assert!(both.supports_form());
3955 assert!(both.supports_url());
3956 }
3957
3958 #[test]
3959 fn test_elicitation_method_routing_and_envelopes() {
3960 use serde_json::json;
3961
3962 assert_eq!(CLIENT_METHOD_NAMES.elicitation_create, "elicitation/create");
3963 assert_eq!(
3964 CLIENT_METHOD_NAMES.elicitation_complete,
3965 "elicitation/complete"
3966 );
3967
3968 let request =
3969 AgentRequest::CreateElicitationRequest(Box::new(CreateElicitationRequest::new(
3970 crate::v2::ElicitationFormMode::new(
3971 crate::v2::ElicitationSessionScope::new("sess_1"),
3972 crate::v2::ElicitationSchema::new(),
3973 ),
3974 "Choose a value",
3975 )));
3976 assert_eq!(request.method(), "elicitation/create");
3977 let method = Arc::from(request.method());
3978 let request = crate::v2::JsonRpcMessage::wrap(crate::v2::Request {
3979 id: crate::v2::RequestId::Number(7),
3980 method,
3981 params: Some(request),
3982 });
3983 assert_eq!(
3984 serde_json::to_value(request).unwrap(),
3985 json!({
3986 "jsonrpc": "2.0",
3987 "id": 7,
3988 "method": "elicitation/create",
3989 "params": {
3990 "mode": "form",
3991 "sessionId": "sess_1",
3992 "message": "Choose a value",
3993 "requestedSchema": { "type": "object", "properties": {} }
3994 }
3995 })
3996 );
3997
3998 let notification = AgentNotification::CompleteElicitationNotification(Box::new(
3999 CompleteElicitationNotification::new("elic_1"),
4000 ));
4001 assert_eq!(notification.method(), "elicitation/complete");
4002 let method = Arc::from(notification.method());
4003 let notification = crate::v2::JsonRpcMessage::wrap(crate::v2::Notification {
4004 method,
4005 params: Some(notification),
4006 });
4007 assert_eq!(
4008 serde_json::to_value(notification).unwrap(),
4009 json!({
4010 "jsonrpc": "2.0",
4011 "method": "elicitation/complete",
4012 "params": { "elicitationId": "elic_1" }
4013 })
4014 );
4015 }
4016
4017 #[test]
4018 fn test_client_capabilities_auth_defaults_on_malformed_value() {
4019 use serde_json::json;
4020
4021 let capabilities: ClientCapabilities = serde_json::from_value(json!({
4022 "auth": false
4023 }))
4024 .unwrap();
4025
4026 assert_eq!(capabilities.auth, None);
4027 }
4028
4029 #[test]
4030 fn test_serialization_behavior() {
4031 use serde_json::json;
4032
4033 assert_eq!(
4034 serde_json::from_value::<SessionInfoUpdate>(json!({})).unwrap(),
4035 SessionInfoUpdate {
4036 title: MaybeUndefined::Undefined,
4037 updated_at: MaybeUndefined::Undefined,
4038 meta: MaybeUndefined::Undefined
4039 }
4040 );
4041 assert_eq!(
4042 serde_json::from_value::<SessionInfoUpdate>(json!({"title": null, "updatedAt": null}))
4043 .unwrap(),
4044 SessionInfoUpdate {
4045 title: MaybeUndefined::Null,
4046 updated_at: MaybeUndefined::Null,
4047 meta: MaybeUndefined::Undefined
4048 }
4049 );
4050 assert_eq!(
4051 serde_json::from_value::<SessionInfoUpdate>(
4052 json!({"title": "title", "updatedAt": "timestamp"})
4053 )
4054 .unwrap(),
4055 SessionInfoUpdate {
4056 title: MaybeUndefined::Value("title".to_string()),
4057 updated_at: MaybeUndefined::Value("timestamp".to_string()),
4058 meta: MaybeUndefined::Undefined
4059 }
4060 );
4061
4062 let clear_meta =
4063 serde_json::from_value::<SessionInfoUpdate>(json!({"_meta": null})).unwrap();
4064 assert_eq!(clear_meta.meta, MaybeUndefined::Null);
4065
4066 let mut meta = Meta::new();
4067 meta.insert("source".to_string(), json!("session-info"));
4068
4069 assert_eq!(
4070 serde_json::from_value::<SessionInfoUpdate>(json!({"_meta": {
4071 "source": "session-info"
4072 }}))
4073 .unwrap()
4074 .meta,
4075 MaybeUndefined::Value(meta.clone())
4076 );
4077
4078 assert_eq!(
4079 serde_json::to_value(SessionInfoUpdate::new()).unwrap(),
4080 json!({})
4081 );
4082
4083 assert_eq!(
4084 serde_json::to_value(SessionInfoUpdate::new().meta(None::<Meta>)).unwrap(),
4085 json!({"_meta": null})
4086 );
4087
4088 assert_eq!(
4089 serde_json::to_value(SessionInfoUpdate::new().meta(meta)).unwrap(),
4090 json!({"_meta": {
4091 "source": "session-info"
4092 }})
4093 );
4094 assert_eq!(
4095 serde_json::to_value(SessionInfoUpdate::new().title("title")).unwrap(),
4096 json!({"title": "title"})
4097 );
4098 assert_eq!(
4099 serde_json::to_value(SessionInfoUpdate::new().title(None)).unwrap(),
4100 json!({"title": null})
4101 );
4102 assert_eq!(
4103 serde_json::to_value(
4104 SessionInfoUpdate::new()
4105 .title("title")
4106 .title(MaybeUndefined::Undefined)
4107 )
4108 .unwrap(),
4109 json!({})
4110 );
4111 }
4112
4113 #[test]
4114 fn test_content_chunk_message_id_serialization() {
4115 use serde_json::json;
4116
4117 assert_eq!(
4118 serde_json::to_value(SessionUpdate::AgentMessageChunk(ContentChunk::new(
4119 ContentBlock::Text(crate::v2::TextContent::new("Hello")),
4120 "msg_agent_c42b9",
4121 )))
4122 .unwrap(),
4123 json!({
4124 "sessionUpdate": "agent_message_chunk",
4125 "messageId": "msg_agent_c42b9",
4126 "content": {
4127 "type": "text",
4128 "text": "Hello"
4129 }
4130 })
4131 );
4132
4133 let err = serde_json::from_value::<ContentChunk>(json!({
4134 "content": {
4135 "type": "text",
4136 "text": "Hello"
4137 }
4138 }))
4139 .unwrap_err();
4140
4141 assert!(err.to_string().contains("messageId"), "{err}");
4142 }
4143
4144 #[test]
4145 fn test_tool_call_content_chunk_serialization() {
4146 use serde_json::json;
4147
4148 assert_eq!(
4149 serde_json::to_value(SessionUpdate::ToolCallContentChunk(
4150 ToolCallContentChunk::new(
4151 "call_001",
4152 crate::v2::ContentBlock::Text(crate::v2::TextContent::new("partial output")),
4153 )
4154 ))
4155 .unwrap(),
4156 json!({
4157 "sessionUpdate": "tool_call_content_chunk",
4158 "toolCallId": "call_001",
4159 "content": {
4160 "type": "content",
4161 "content": {
4162 "type": "text",
4163 "text": "partial output"
4164 }
4165 }
4166 })
4167 );
4168
4169 let err = serde_json::from_value::<ToolCallContentChunk>(json!({
4170 "content": {
4171 "type": "content",
4172 "content": {
4173 "type": "text",
4174 "text": "partial output"
4175 }
4176 }
4177 }))
4178 .unwrap_err();
4179
4180 assert!(err.to_string().contains("toolCallId"), "{err}");
4181 }
4182
4183 #[test]
4184 fn test_full_message_serialization() {
4185 use serde_json::json;
4186
4187 assert_eq!(
4188 serde_json::to_value(SessionUpdate::UserMessage(
4189 UserMessage::new("msg_user_8f7a1").content(vec![ContentBlock::Text(
4190 crate::v2::TextContent::new("Hello")
4191 )])
4192 ))
4193 .unwrap(),
4194 json!({
4195 "sessionUpdate": "user_message",
4196 "messageId": "msg_user_8f7a1",
4197 "content": [
4198 {
4199 "type": "text",
4200 "text": "Hello"
4201 }
4202 ]
4203 })
4204 );
4205
4206 assert_eq!(
4207 serde_json::to_value(SessionUpdate::AgentMessage(
4208 AgentMessage::new("msg_agent_c42b9").content(vec![ContentBlock::Text(
4209 crate::v2::TextContent::new("Hello")
4210 )])
4211 ))
4212 .unwrap(),
4213 json!({
4214 "sessionUpdate": "agent_message",
4215 "messageId": "msg_agent_c42b9",
4216 "content": [
4217 {
4218 "type": "text",
4219 "text": "Hello"
4220 }
4221 ]
4222 })
4223 );
4224
4225 assert_eq!(
4226 serde_json::to_value(SessionUpdate::AgentThought(
4227 AgentThought::new("msg_thought_a12").content(vec![ContentBlock::Text(
4228 crate::v2::TextContent::new("Need to inspect the call sites first.")
4229 )])
4230 ))
4231 .unwrap(),
4232 json!({
4233 "sessionUpdate": "agent_thought",
4234 "messageId": "msg_thought_a12",
4235 "content": [
4236 {
4237 "type": "text",
4238 "text": "Need to inspect the call sites first."
4239 }
4240 ]
4241 })
4242 );
4243 }
4244
4245 #[test]
4246 fn test_message_upsert_serialization() {
4247 use serde_json::json;
4248
4249 assert_eq!(
4250 serde_json::to_value(SessionUpdate::UserMessage(
4251 UserMessage::new("msg_empty").content(Vec::<ContentBlock>::new())
4252 ))
4253 .unwrap(),
4254 json!({
4255 "sessionUpdate": "user_message",
4256 "messageId": "msg_empty",
4257 "content": []
4258 })
4259 );
4260
4261 let empty = serde_json::from_value::<UserMessage>(json!({
4262 "messageId": "msg_empty",
4263 "content": []
4264 }))
4265 .unwrap();
4266 assert!(matches!(
4267 empty.content,
4268 MaybeUndefined::Value(ref content) if content.is_empty()
4269 ));
4270
4271 let patch = serde_json::from_value::<AgentMessage>(json!({
4272 "messageId": "msg_agent_c42b9"
4273 }))
4274 .unwrap();
4275 assert_eq!(patch.content, MaybeUndefined::Undefined);
4276 assert_eq!(patch.meta, MaybeUndefined::Undefined);
4277
4278 let malformed_meta = serde_json::from_value::<AgentMessage>(json!({
4279 "messageId": "msg_agent_c42b9",
4280 "_meta": false
4281 }))
4282 .unwrap();
4283 assert_eq!(malformed_meta.meta, MaybeUndefined::Undefined);
4284
4285 let patch = serde_json::from_value::<AgentThought>(json!({
4286 "messageId": "msg_thought_a12"
4287 }))
4288 .unwrap();
4289 assert_eq!(patch.content, MaybeUndefined::Undefined);
4290
4291 let clear = serde_json::from_value::<UserMessage>(json!({
4292 "messageId": "msg_user_8f7a1",
4293 "content": null
4294 }))
4295 .unwrap();
4296 assert_eq!(clear.content, MaybeUndefined::Null);
4297
4298 let clear_meta = serde_json::from_value::<UserMessage>(json!({
4299 "messageId": "msg_user_8f7a1",
4300 "_meta": null
4301 }))
4302 .unwrap();
4303 assert_eq!(clear_meta.meta, MaybeUndefined::Null);
4304
4305 let mut meta = Meta::new();
4306 meta.insert("source".to_string(), json!("replay"));
4307
4308 assert_eq!(
4309 serde_json::to_value(SessionUpdate::UserMessage(
4310 UserMessage::new("msg_user_8f7a1").meta(meta)
4311 ))
4312 .unwrap(),
4313 json!({
4314 "sessionUpdate": "user_message",
4315 "messageId": "msg_user_8f7a1",
4316 "_meta": {
4317 "source": "replay"
4318 }
4319 })
4320 );
4321
4322 assert_eq!(
4323 serde_json::to_value(SessionUpdate::UserMessage(
4324 UserMessage::new("msg_user_8f7a1").meta(None::<Meta>)
4325 ))
4326 .unwrap(),
4327 json!({
4328 "sessionUpdate": "user_message",
4329 "messageId": "msg_user_8f7a1",
4330 "_meta": null
4331 })
4332 );
4333 }
4334
4335 #[test]
4336 fn test_usage_update_serialization() {
4337 use serde_json::json;
4338
4339 assert_eq!(
4340 serde_json::to_value(SessionUpdate::UsageUpdate(UsageUpdate::new(
4341 53_000, 200_000
4342 )))
4343 .unwrap(),
4344 json!({
4345 "sessionUpdate": "usage_update",
4346 "used": 53000,
4347 "size": 200_000
4348 })
4349 );
4350
4351 assert_eq!(
4352 serde_json::to_value(SessionUpdate::UsageUpdate(
4353 UsageUpdate::new(53_000, 200_000).cost(Cost::new(0.045, "USD"))
4354 ))
4355 .unwrap(),
4356 json!({
4357 "sessionUpdate": "usage_update",
4358 "used": 53000,
4359 "size": 200_000,
4360 "cost": {
4361 "amount": 0.045,
4362 "currency": "USD"
4363 }
4364 })
4365 );
4366
4367 let SessionUpdate::UsageUpdate(update) = serde_json::from_value(json!({
4368 "sessionUpdate": "usage_update",
4369 "used": 53000,
4370 "size": 200_000,
4371 "cost": null
4372 }))
4373 .unwrap() else {
4374 panic!("expected usage update");
4375 };
4376
4377 assert_eq!(update.cost, None);
4378 }
4379
4380 #[test]
4381 fn test_state_update_serialization() {
4382 use serde_json::json;
4383
4384 assert_eq!(
4385 serde_json::to_value(SessionUpdate::StateUpdate(StateUpdate::Running(
4386 RunningStateUpdate::new()
4387 )))
4388 .unwrap(),
4389 json!({
4390 "sessionUpdate": "state_update",
4391 "state": "running"
4392 })
4393 );
4394
4395 assert_eq!(
4396 serde_json::to_value(SessionUpdate::StateUpdate(StateUpdate::Idle(
4397 IdleStateUpdate::new().stop_reason(StopReason::EndTurn)
4398 )))
4399 .unwrap(),
4400 json!({
4401 "sessionUpdate": "state_update",
4402 "state": "idle",
4403 "stopReason": "end_turn"
4404 })
4405 );
4406
4407 let SessionUpdate::StateUpdate(update) = serde_json::from_value(json!({
4408 "sessionUpdate": "state_update",
4409 "state": "requires_action"
4410 }))
4411 .unwrap() else {
4412 panic!("expected state update");
4413 };
4414
4415 assert!(matches!(update, StateUpdate::RequiresAction(_)));
4416
4417 let SessionUpdate::StateUpdate(StateUpdate::Idle(update)) = serde_json::from_value(json!({
4418 "sessionUpdate": "state_update",
4419 "state": "idle",
4420 "stopReason": null
4421 }))
4422 .unwrap() else {
4423 panic!("expected idle state update");
4424 };
4425
4426 assert_eq!(update.stop_reason, None);
4427
4428 let SessionUpdate::StateUpdate(StateUpdate::Other(update)) =
4429 serde_json::from_value(json!({
4430 "sessionUpdate": "state_update",
4431 "state": "_paused",
4432 "label": "Paused"
4433 }))
4434 .unwrap()
4435 else {
4436 panic!("expected unknown state update");
4437 };
4438
4439 assert_eq!(update.state, "_paused");
4440 assert_eq!(update.fields["label"], json!("Paused"));
4441 }
4442
4443 #[test]
4444 fn session_update_preserves_unknown_variant() {
4445 use serde_json::json;
4446
4447 let update: SessionUpdate = serde_json::from_value(json!({
4448 "sessionUpdate": "_status_badge",
4449 "label": "Indexing",
4450 "progress": 0.5
4451 }))
4452 .unwrap();
4453
4454 let SessionUpdate::Other(unknown) = update else {
4455 panic!("expected unknown session update");
4456 };
4457
4458 assert_eq!(unknown.session_update, "_status_badge");
4459 assert_eq!(unknown.fields.get("label"), Some(&json!("Indexing")));
4460 assert_eq!(unknown.fields.get("progress"), Some(&json!(0.5)));
4461
4462 assert_eq!(
4463 serde_json::to_value(SessionUpdate::Other(unknown)).unwrap(),
4464 json!({
4465 "sessionUpdate": "_status_badge",
4466 "label": "Indexing",
4467 "progress": 0.5
4468 })
4469 );
4470 }
4471
4472 #[test]
4473 fn terminal_session_updates_use_known_discriminators() {
4474 use serde_json::json;
4475
4476 assert_eq!(
4477 serde_json::to_value(SessionUpdate::TerminalUpdate(
4478 TerminalUpdate::new("term_1").command("cargo test")
4479 ))
4480 .unwrap(),
4481 json!({
4482 "sessionUpdate": "terminal_update",
4483 "terminalId": "term_1",
4484 "command": "cargo test"
4485 })
4486 );
4487 assert_eq!(
4488 serde_json::to_value(SessionUpdate::TerminalOutputChunk(
4489 TerminalOutputChunk::new("term_1", "dGVzdAo=")
4490 ))
4491 .unwrap(),
4492 json!({
4493 "sessionUpdate": "terminal_output_chunk",
4494 "terminalId": "term_1",
4495 "data": "dGVzdAo="
4496 })
4497 );
4498 }
4499
4500 #[test]
4501 fn session_update_does_not_hide_malformed_known_terminal_variants() {
4502 use serde_json::json;
4503
4504 assert!(
4505 serde_json::from_value::<SessionUpdate>(json!({
4506 "sessionUpdate": "terminal_update"
4507 }))
4508 .is_err()
4509 );
4510 assert!(
4511 serde_json::from_value::<SessionUpdate>(json!({
4512 "sessionUpdate": "terminal_output_chunk",
4513 "terminalId": "term_1"
4514 }))
4515 .is_err()
4516 );
4517 }
4518
4519 #[test]
4520 fn test_plan_update_serialization() {
4521 use serde_json::json;
4522
4523 let plan_update =
4524 SessionUpdate::PlanUpdate(PlanUpdate::new(crate::v2::PlanUpdateContent::items(
4525 "plan-1",
4526 vec![crate::v2::PlanEntry::new(
4527 "Step 1",
4528 crate::v2::PlanEntryPriority::High,
4529 crate::v2::PlanEntryStatus::Pending,
4530 )],
4531 )));
4532
4533 assert_eq!(
4534 serde_json::to_value(plan_update).unwrap(),
4535 json!({
4536 "sessionUpdate": "plan_update",
4537 "plan": {
4538 "type": "items",
4539 "planId": "plan-1",
4540 "entries": [
4541 {
4542 "content": "Step 1",
4543 "priority": "high",
4544 "status": "pending"
4545 }
4546 ]
4547 }
4548 })
4549 );
4550 }
4551
4552 #[cfg(feature = "unstable_plan_operations")]
4553 #[test]
4554 fn test_plan_removed_serialization() {
4555 use serde_json::json;
4556
4557 assert_eq!(
4558 serde_json::to_value(SessionUpdate::PlanRemoved(PlanRemoved::new("plan-1"))).unwrap(),
4559 json!({
4560 "sessionUpdate": "plan_removed",
4561 "planId": "plan-1"
4562 })
4563 );
4564 }
4565
4566 #[test]
4567 fn available_command_input_preserves_unknown_typed_variant() {
4568 use serde_json::json;
4569
4570 let input: AvailableCommandInput = serde_json::from_value(json!({
4571 "type": "_choices",
4572 "hint": "Pick one",
4573 "options": ["fast", "careful"]
4574 }))
4575 .unwrap();
4576
4577 let AvailableCommandInput::Other(unknown) = input else {
4578 panic!("expected unknown command input");
4579 };
4580
4581 assert_eq!(unknown.type_, "_choices");
4582 assert_eq!(unknown.fields.get("hint"), Some(&json!("Pick one")));
4583 assert_eq!(
4584 unknown.fields.get("options"),
4585 Some(&json!(["fast", "careful"]))
4586 );
4587 assert_eq!(
4588 serde_json::to_value(AvailableCommandInput::Other(unknown)).unwrap(),
4589 json!({
4590 "type": "_choices",
4591 "hint": "Pick one",
4592 "options": ["fast", "careful"]
4593 })
4594 );
4595 }
4596
4597 #[test]
4598 fn available_command_input_text_uses_type_discriminator() {
4599 use serde_json::json;
4600
4601 let input = AvailableCommandInput::Text(TextCommandInput::new("Describe changes"));
4602
4603 let json = serde_json::to_value(&input).unwrap();
4604 assert_eq!(
4605 json,
4606 json!({
4607 "type": "text",
4608 "hint": "Describe changes"
4609 })
4610 );
4611
4612 let roundtripped: AvailableCommandInput = serde_json::from_value(json).unwrap();
4613 assert!(matches!(roundtripped, AvailableCommandInput::Text(_)));
4614 }
4615
4616 #[test]
4617 fn request_permission_subject_tool_call_uses_type_discriminator() {
4618 use serde_json::json;
4619
4620 let subject = RequestPermissionSubject::from(ToolCallUpdate::new("call_001"));
4621
4622 let json = serde_json::to_value(&subject).unwrap();
4623 assert_eq!(
4624 json,
4625 json!({
4626 "type": "tool_call",
4627 "toolCall": {
4628 "toolCallId": "call_001"
4629 }
4630 })
4631 );
4632
4633 let roundtripped: RequestPermissionSubject = serde_json::from_value(json).unwrap();
4634 assert!(matches!(
4635 roundtripped,
4636 RequestPermissionSubject::ToolCall(_)
4637 ));
4638 }
4639
4640 #[test]
4641 fn request_permission_subject_command_uses_type_discriminator() {
4642 use serde_json::json;
4643
4644 let mut meta = Meta::new();
4645 meta.insert("source".to_string(), json!("shell"));
4646 let subject = RequestPermissionSubject::from(
4647 CommandPermissionSubject::new("cargo test", "/workspace/project")
4648 .tool_call_id("call_001")
4649 .terminal_id("term_1")
4650 .meta(meta),
4651 );
4652
4653 let json = serde_json::to_value(&subject).unwrap();
4654 assert_eq!(
4655 json,
4656 json!({
4657 "type": "command",
4658 "command": "cargo test",
4659 "cwd": "/workspace/project",
4660 "toolCallId": "call_001",
4661 "terminalId": "term_1",
4662 "_meta": {
4663 "source": "shell"
4664 }
4665 })
4666 );
4667
4668 let roundtripped: RequestPermissionSubject = serde_json::from_value(json).unwrap();
4669 assert!(matches!(roundtripped, RequestPermissionSubject::Command(_)));
4670 }
4671
4672 #[test]
4673 fn command_permission_subject_treats_optional_association_nulls_as_omitted() {
4674 use serde_json::json;
4675
4676 let subject: RequestPermissionSubject = serde_json::from_value(json!({
4677 "type": "command",
4678 "command": "cargo test",
4679 "cwd": "/workspace/project",
4680 "toolCallId": null,
4681 "terminalId": null,
4682 "_meta": null
4683 }))
4684 .unwrap();
4685
4686 let RequestPermissionSubject::Command(subject) = subject else {
4687 panic!("expected command permission subject");
4688 };
4689 assert_eq!(subject.cwd, AbsolutePath::new("/workspace/project"));
4690 assert_eq!(subject.tool_call_id, None);
4691 assert_eq!(subject.terminal_id, None);
4692 assert_eq!(subject.meta, None);
4693 assert_eq!(
4694 serde_json::to_value(RequestPermissionSubject::Command(subject)).unwrap(),
4695 json!({
4696 "type": "command",
4697 "command": "cargo test",
4698 "cwd": "/workspace/project"
4699 })
4700 );
4701 }
4702
4703 #[test]
4704 fn request_permission_subject_preserves_unknown_variant() {
4705 use serde_json::json;
4706
4707 let subject: RequestPermissionSubject = serde_json::from_value(json!({
4708 "type": "_review",
4709 "reason": "needs-review",
4710 "retryAfterSeconds": 30
4711 }))
4712 .unwrap();
4713
4714 let RequestPermissionSubject::Other(unknown) = subject else {
4715 panic!("expected unknown permission subject");
4716 };
4717
4718 assert_eq!(unknown.type_, "_review");
4719 assert_eq!(unknown.fields.get("reason"), Some(&json!("needs-review")));
4720 assert_eq!(unknown.fields.get("retryAfterSeconds"), Some(&json!(30)));
4721 assert_eq!(
4722 serde_json::to_value(RequestPermissionSubject::Other(unknown)).unwrap(),
4723 json!({
4724 "type": "_review",
4725 "reason": "needs-review",
4726 "retryAfterSeconds": 30
4727 })
4728 );
4729 }
4730
4731 #[test]
4732 fn request_permission_subject_unknown_does_not_hide_malformed_known_variant() {
4733 use serde_json::json;
4734
4735 assert!(
4736 serde_json::from_value::<RequestPermissionSubject>(json!({
4737 "type": "tool_call"
4738 }))
4739 .is_err()
4740 );
4741 assert!(
4742 serde_json::from_value::<RequestPermissionSubject>(json!({
4743 "type": 1
4744 }))
4745 .is_err()
4746 );
4747 assert!(
4748 serde_json::from_value::<RequestPermissionSubject>(json!({
4749 "type": "command",
4750 "cwd": "/workspace/project"
4751 }))
4752 .is_err()
4753 );
4754 assert!(
4755 serde_json::from_value::<RequestPermissionSubject>(json!({
4756 "type": "command",
4757 "command": "cargo test"
4758 }))
4759 .is_err()
4760 );
4761 assert!(
4762 serde_json::from_value::<RequestPermissionSubject>(json!({
4763 "type": "command",
4764 "command": "cargo test",
4765 "cwd": null
4766 }))
4767 .is_err()
4768 );
4769 }
4770
4771 #[test]
4772 fn request_permission_title_and_description_are_separate_from_tool_call_content() {
4773 use serde_json::json;
4774
4775 let request =
4776 RequestPermissionRequest::new("sess_abc123def456", "Approve file edit?", Vec::new())
4777 .description("Allow this tool to edit src/main.rs?")
4778 .subject(RequestPermissionSubject::from(ToolCallUpdate::new(
4779 "call_001",
4780 )));
4781
4782 assert_eq!(
4783 serde_json::to_value(request).unwrap(),
4784 json!({
4785 "sessionId": "sess_abc123def456",
4786 "title": "Approve file edit?",
4787 "description": "Allow this tool to edit src/main.rs?",
4788 "subject": {
4789 "type": "tool_call",
4790 "toolCall": {
4791 "toolCallId": "call_001"
4792 }
4793 },
4794 "options": []
4795 })
4796 );
4797 }
4798
4799 #[test]
4800 fn request_permission_requires_title_and_allows_missing_subject() {
4801 use serde_json::json;
4802
4803 let request = RequestPermissionRequest::new(
4804 "sess_abc123def456",
4805 "Approve elevated permissions?",
4806 Vec::new(),
4807 );
4808
4809 assert_eq!(
4810 serde_json::to_value(request).unwrap(),
4811 json!({
4812 "sessionId": "sess_abc123def456",
4813 "title": "Approve elevated permissions?",
4814 "options": []
4815 })
4816 );
4817
4818 let missing_subject: RequestPermissionRequest = serde_json::from_value(json!({
4819 "sessionId": "sess_abc123def456",
4820 "title": "Approve elevated permissions?",
4821 "options": []
4822 }))
4823 .unwrap();
4824 assert!(missing_subject.subject.is_none());
4825
4826 let null_subject: RequestPermissionRequest = serde_json::from_value(json!({
4827 "sessionId": "sess_abc123def456",
4828 "title": "Approve elevated permissions?",
4829 "subject": null,
4830 "options": []
4831 }))
4832 .unwrap();
4833 assert!(null_subject.subject.is_none());
4834
4835 assert!(
4836 serde_json::from_value::<RequestPermissionRequest>(json!({
4837 "sessionId": "sess_abc123def456",
4838 "options": []
4839 }))
4840 .is_err()
4841 );
4842 }
4843
4844 #[test]
4845 fn request_permission_outcome_preserves_unknown_variant() {
4846 use serde_json::json;
4847
4848 let outcome: RequestPermissionOutcome = serde_json::from_value(json!({
4849 "outcome": "_defer",
4850 "reason": "needs-review",
4851 "retryAfterSeconds": 30
4852 }))
4853 .unwrap();
4854
4855 let RequestPermissionOutcome::Other(unknown) = outcome else {
4856 panic!("expected unknown permission outcome");
4857 };
4858
4859 assert_eq!(unknown.outcome, "_defer");
4860 assert_eq!(unknown.fields.get("reason"), Some(&json!("needs-review")));
4861 assert_eq!(unknown.fields.get("retryAfterSeconds"), Some(&json!(30)));
4862 assert_eq!(
4863 serde_json::to_value(RequestPermissionOutcome::Other(unknown)).unwrap(),
4864 json!({
4865 "outcome": "_defer",
4866 "reason": "needs-review",
4867 "retryAfterSeconds": 30
4868 })
4869 );
4870 }
4871
4872 #[test]
4873 fn request_permission_outcome_unknown_does_not_hide_malformed_known_variant() {
4874 use serde_json::json;
4875
4876 assert!(
4877 serde_json::from_value::<RequestPermissionOutcome>(json!({
4878 "outcome": "selected"
4879 }))
4880 .is_err()
4881 );
4882 assert!(
4883 serde_json::from_value::<RequestPermissionOutcome>(json!({
4884 "outcome": 1
4885 }))
4886 .is_err()
4887 );
4888 }
4889
4890 #[test]
4891 fn available_command_input_unknown_does_not_hide_malformed_text_variant() {
4892 use serde_json::json;
4893
4894 assert!(serde_json::from_value::<AvailableCommandInput>(json!({})).is_err());
4895 assert!(
4896 serde_json::from_value::<AvailableCommandInput>(json!({
4897 "hint": "Pick one"
4898 }))
4899 .is_err()
4900 );
4901 assert!(
4902 serde_json::from_value::<AvailableCommandInput>(json!({
4903 "type": 1,
4904 "hint": "Pick one"
4905 }))
4906 .is_err()
4907 );
4908 assert!(
4909 serde_json::from_value::<OtherAvailableCommandInput>(json!({
4910 "type": "text",
4911 "hint": "Pick one"
4912 }))
4913 .is_err()
4914 );
4915 }
4916
4917 #[cfg(feature = "unstable_nes")]
4918 #[test]
4919 fn test_client_capabilities_position_encodings_serialization() {
4920 use serde_json::json;
4921
4922 let capabilities = ClientCapabilities::new().position_encodings(vec![
4923 PositionEncodingKind::Utf32,
4924 PositionEncodingKind::Utf16,
4925 ]);
4926 let json = serde_json::to_value(&capabilities).unwrap();
4927
4928 assert_eq!(json["positionEncodings"], json!(["utf-32", "utf-16"]));
4929 }
4930
4931 #[cfg(feature = "unstable_mcp_over_acp")]
4932 #[test]
4933 fn test_agent_mcp_request_method_names() {
4934 use serde_json::json;
4935
4936 let params: serde_json::Map<String, serde_json::Value> =
4937 [("cursor".to_string(), json!("abc"))].into_iter().collect();
4938
4939 assert_eq!(CLIENT_METHOD_NAMES.mcp_message, "mcp/message");
4940 assert_eq!(
4941 AgentRequest::MessageMcpRequest(Box::new(MessageMcpRequest::new(
4942 "server-1",
4943 "req-1",
4944 "tools/list"
4945 )))
4946 .method(),
4947 "mcp/message"
4948 );
4949 assert_eq!(
4950 serde_json::to_value(
4951 MessageMcpRequest::new("server-1", "req-1", "tools/list").params(params)
4952 )
4953 .unwrap(),
4954 json!({
4955 "serverId": "server-1",
4956 "requestId": "req-1",
4957 "method": "tools/list",
4958 "params": { "cursor": "abc" }
4959 })
4960 );
4961
4962 let request_with_null_params: MessageMcpRequest = serde_json::from_value(json!({
4963 "serverId": "server-1",
4964 "requestId": "req-1",
4965 "method": "tools/list",
4966 "params": null,
4967 "_meta": null
4968 }))
4969 .unwrap();
4970 assert_eq!(request_with_null_params.params, None);
4971 assert_eq!(request_with_null_params.meta, None);
4972 for key in ["serverId", "requestId", "method"] {
4973 let mut value =
4974 json!({"serverId":"server-1", "requestId":"req-1", "method":"tools/list"});
4975 value.as_object_mut().unwrap().remove(key);
4976 assert!(serde_json::from_value::<MessageMcpRequest>(value).is_err());
4977 }
4978 for key in ["serverId", "requestId", "method"] {
4979 let mut value =
4980 json!({"serverId":"server-1", "requestId":"req-1", "method":"tools/list"});
4981 value[key] = serde_json::Value::Null;
4982 assert!(serde_json::from_value::<MessageMcpRequest>(value).is_err());
4983 }
4984 }
4985
4986 #[test]
4987 fn test_auth_capabilities_serialize_terminal_support_as_object() {
4988 use serde_json::json;
4989
4990 let capabilities = AuthCapabilities::new().terminal(TerminalAuthCapabilities::new());
4991
4992 assert_eq!(
4993 serde_json::to_value(&capabilities).unwrap(),
4994 json!({
4995 "terminal": {}
4996 })
4997 );
4998
4999 let deserialized: AuthCapabilities = serde_json::from_value(json!({
5000 "terminal": false
5001 }))
5002 .unwrap();
5003 assert!(deserialized.terminal.is_none());
5004 }
5005
5006 #[test]
5007 fn request_permission_request_rejects_malformed_options() {
5008 use serde_json::json;
5009
5010 assert!(
5011 serde_json::from_value::<RequestPermissionRequest>(json!({
5012 "sessionId": "sess-1",
5013 "title": "Run tool?",
5014 "options": "not-an-array"
5015 }))
5016 .is_err()
5017 );
5018 assert!(
5019 serde_json::from_value::<RequestPermissionRequest>(json!({
5020 "sessionId": "sess-1",
5021 "title": "Run tool?",
5022 "options": [{"optionId": "allow"}]
5023 }))
5024 .is_err()
5025 );
5026 }
5027
5028 #[cfg(feature = "unstable_plan_operations")]
5029 #[test]
5030 fn malformed_plan_removed_is_not_hidden_as_unknown_update() {
5031 use serde_json::json;
5032
5033 assert!(
5034 serde_json::from_value::<SessionUpdate>(json!({
5035 "sessionUpdate": "plan_removed"
5036 }))
5037 .is_err()
5038 );
5039 }
5040}