1use std::{path::PathBuf, sync::Arc};
7
8use derive_more::{Display, From};
9#[cfg(all(feature = "schemars", feature = "unstable_subagents"))]
10use schemars::Schema;
11use serde::{Deserialize, Serialize};
12use serde_with::{DefaultOnError, DefaultOnNull, VecSkipError, serde_as, skip_serializing_none};
13#[cfg(feature = "unstable_subagents")]
14use std::collections::BTreeMap;
15
16#[cfg(feature = "unstable_subagents")]
17use super::Error;
18#[cfg(all(
19 feature = "unstable_subagents",
20 feature = "unstable_end_turn_token_usage"
21))]
22use super::Usage;
23use super::{
24 CompleteElicitationNotification, CreateElicitationRequest, CreateElicitationResponse,
25 ElicitationCapabilities,
26};
27use crate::{IntoMaybeUndefined, IntoOption, MaybeUndefined};
28
29use super::{
30 ContentBlock, EnvVariable, ExtNotification, ExtRequest, ExtResponse, Meta, Plan,
31 SessionConfigOption, SessionId, SessionModeId, ToolCall, ToolCallUpdate,
32};
33#[cfg(feature = "unstable_plan_operations")]
34use super::{PlanCapabilities, PlanRemoved, PlanUpdate};
35
36#[cfg(feature = "unstable_mcp_over_acp")]
37use super::mcp::{MCP_MESSAGE_METHOD_NAME, MessageMcpRequest, MessageMcpResponse};
38
39#[serde_as]
47#[skip_serializing_none]
48#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
49#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
50#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = SESSION_UPDATE_NOTIFICATION)))]
51#[serde(rename_all = "camelCase")]
52#[non_exhaustive]
53pub struct SessionNotification {
54 pub session_id: SessionId,
56 pub update: SessionUpdate,
58 #[serde_as(deserialize_as = "DefaultOnError")]
64 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
65 #[serde(default)]
66 #[serde(rename = "_meta")]
67 pub meta: Option<Meta>,
68}
69
70impl SessionNotification {
71 #[must_use]
73 pub fn new(session_id: impl Into<SessionId>, update: SessionUpdate) -> Self {
74 Self {
75 session_id: session_id.into(),
76 update,
77 meta: None,
78 }
79 }
80
81 #[must_use]
87 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
88 self.meta = meta.into_option();
89 self
90 }
91}
92
93#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
99#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
100#[serde(tag = "sessionUpdate", rename_all = "snake_case")]
101#[cfg_attr(feature = "schemars", schemars(extend("discriminator" = {"propertyName": "sessionUpdate"})))]
102#[non_exhaustive]
103pub enum SessionUpdate {
104 UserMessageChunk(ContentChunk),
106 AgentMessageChunk(ContentChunk),
108 AgentThoughtChunk(ContentChunk),
110 ToolCall(ToolCall),
112 ToolCallUpdate(ToolCallUpdate),
114 Plan(Plan),
117 #[cfg(feature = "unstable_plan_operations")]
123 PlanUpdate(PlanUpdate),
124 #[cfg(feature = "unstable_plan_operations")]
130 PlanRemoved(PlanRemoved),
131 AvailableCommandsUpdate(AvailableCommandsUpdate),
133 CurrentModeUpdate(CurrentModeUpdate),
137 ConfigOptionUpdate(ConfigOptionUpdate),
139 SessionInfoUpdate(SessionInfoUpdate),
141 UsageUpdate(UsageUpdate),
143 Notice(Notice),
148 CompactionUpdate(CompactionUpdate),
153 CompactionSummaryChunk(CompactionSummaryChunk),
158 #[cfg(feature = "unstable_subagents")]
165 SubagentUpdate(SubagentUpdate),
166 #[cfg(feature = "unstable_subagents")]
173 SessionMessage(SessionMessage),
174 #[cfg(feature = "unstable_subagents")]
180 SessionMessageChunk(SessionMessageChunk),
181}
182
183#[cfg(feature = "unstable_subagents")]
192#[serde_as]
193#[skip_serializing_none]
194#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
195#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
196#[serde(rename_all = "camelCase")]
197#[non_exhaustive]
198pub struct SessionMessageChunk {
199 pub message_id: MessageId,
201 pub content: ContentBlock,
203 #[serde_as(deserialize_as = "DefaultOnError")]
207 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
208 #[serde(default, rename = "_meta")]
209 pub meta: Option<Meta>,
210}
211
212#[cfg(feature = "unstable_subagents")]
213impl SessionMessageChunk {
214 #[must_use]
216 pub fn new(message_id: impl Into<MessageId>, content: ContentBlock) -> Self {
217 Self {
218 message_id: message_id.into(),
219 content,
220 meta: None,
221 }
222 }
223
224 #[must_use]
226 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
227 self.meta = meta.into_option();
228 self
229 }
230}
231
232#[cfg(feature = "unstable_subagents")]
238#[serde_as]
239#[skip_serializing_none]
240#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
241#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
242#[serde(rename_all = "camelCase")]
243#[non_exhaustive]
244pub struct SessionMessage {
245 pub message_id: MessageId,
247 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
251 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
252 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
253 pub sender_session_id: MaybeUndefined<SessionId>,
254 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
258 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
259 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
260 pub recipient_session_id: MaybeUndefined<SessionId>,
261 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
267 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
268 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
269 pub sender_message_id: MaybeUndefined<MessageId>,
270 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
276 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
277 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
278 pub recipient_message_id: MaybeUndefined<MessageId>,
279 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<VecSkipError<_>>>")]
282 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
283 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
284 pub content: MaybeUndefined<Vec<ContentBlock>>,
285 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
289 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
290 #[serde(
291 default,
292 rename = "_meta",
293 skip_serializing_if = "MaybeUndefined::is_undefined"
294 )]
295 pub meta: MaybeUndefined<Meta>,
296}
297
298#[cfg(feature = "unstable_subagents")]
299impl SessionMessage {
300 #[must_use]
302 pub fn new(message_id: impl Into<MessageId>) -> Self {
303 Self {
304 message_id: message_id.into(),
305 sender_session_id: MaybeUndefined::Undefined,
306 recipient_session_id: MaybeUndefined::Undefined,
307 sender_message_id: MaybeUndefined::Undefined,
308 recipient_message_id: MaybeUndefined::Undefined,
309 content: MaybeUndefined::Undefined,
310 meta: MaybeUndefined::Undefined,
311 }
312 }
313
314 #[must_use]
316 pub fn sender_session_id(
317 mut self,
318 sender_session_id: impl IntoMaybeUndefined<SessionId>,
319 ) -> Self {
320 self.sender_session_id = sender_session_id.into_maybe_undefined();
321 self
322 }
323
324 #[must_use]
326 pub fn recipient_session_id(
327 mut self,
328 recipient_session_id: impl IntoMaybeUndefined<SessionId>,
329 ) -> Self {
330 self.recipient_session_id = recipient_session_id.into_maybe_undefined();
331 self
332 }
333
334 #[must_use]
336 pub fn sender_message_id(
337 mut self,
338 sender_message_id: impl IntoMaybeUndefined<MessageId>,
339 ) -> Self {
340 self.sender_message_id = sender_message_id.into_maybe_undefined();
341 self
342 }
343
344 #[must_use]
346 pub fn recipient_message_id(
347 mut self,
348 recipient_message_id: impl IntoMaybeUndefined<MessageId>,
349 ) -> Self {
350 self.recipient_message_id = recipient_message_id.into_maybe_undefined();
351 self
352 }
353
354 #[must_use]
356 pub fn content(mut self, content: impl IntoMaybeUndefined<Vec<ContentBlock>>) -> Self {
357 self.content = content.into_maybe_undefined();
358 self
359 }
360
361 #[must_use]
363 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
364 self.meta = meta.into_maybe_undefined();
365 self
366 }
367}
368
369#[cfg(all(test, feature = "unstable_subagents"))]
370mod session_message_tests {
371 use super::*;
372 use serde_json::{Value, json};
373
374 #[test]
375 fn envelopes_preserve_participants_and_multimodal_content() {
376 let content = json!([
377 {"type": "text", "text": "Please inspect this"},
378 {"type": "image", "data": "aGVsbG8=", "mimeType": "image/png"}
379 ]);
380 for (transcript, sender, recipient, id) in [
381 ("parent", "parent", "child", "sent-1"),
382 ("child", "parent", "child", "received-9"),
383 ("child", "child", "parent", "sent-2"),
384 ] {
385 let wire = json!({"sessionId": transcript, "update": {
386 "sessionUpdate": "session_message", "messageId": id,
387 "senderSessionId": sender, "recipientSessionId": recipient, "content": content
388 }});
389 let decoded: SessionNotification = serde_json::from_value(wire.clone()).unwrap();
390 assert_eq!(serde_json::to_value(&decoded).unwrap(), wire);
391 assert!(matches!(decoded.update, SessionUpdate::SessionMessage(_)));
392 let message = SessionMessage::new(id)
393 .sender_session_id(SessionId::new(sender))
394 .recipient_session_id(SessionId::new(recipient))
395 .content(vec![]);
396 assert_eq!(serde_json::to_value(message).unwrap()["content"], json!([]));
397 }
398 }
399
400 #[test]
401 fn message_upserts_validate_ids_and_patch_fields() {
402 let base = json!({"sessionUpdate": "session_message", "messageId": "m1",
403 "senderSessionId": "parent", "recipientSessionId": "child", "content": []});
404 {
405 let key = "messageId";
406 let mut missing = base.clone();
407 missing.as_object_mut().unwrap().remove(key);
408 assert!(
409 serde_json::from_value::<SessionUpdate>(missing).is_err(),
410 "{key}"
411 );
412 let mut null = base.clone();
413 null[key] = Value::Null;
414 assert!(
415 serde_json::from_value::<SessionUpdate>(null).is_err(),
416 "{key}"
417 );
418 let mut non_string = base.clone();
419 non_string[key] = json!(42);
420 assert!(
421 serde_json::from_value::<SessionUpdate>(non_string).is_err(),
422 "{key}"
423 );
424 }
425 for content in [None, Some(json!({}))] {
426 let mut wire = base.clone();
427 wire.as_object_mut().unwrap().remove("content");
428 if let Some(content) = content {
429 wire["content"] = content;
430 }
431 let decoded: SessionUpdate = serde_json::from_value(wire).unwrap();
432 let encoded = serde_json::to_value(decoded).unwrap();
433 let mut unchanged = base.clone();
434 unchanged.as_object_mut().unwrap().remove("content");
435 assert_eq!(encoded, unchanged);
436 }
437 let clear = SessionMessage::new("m1")
438 .sender_session_id(SessionId::new("parent"))
439 .recipient_session_id(SessionId::new("child"))
440 .content(None)
441 .meta(None);
442 let mut clear_wire = base.clone();
443 clear_wire["content"] = Value::Null;
444 clear_wire["_meta"] = Value::Null;
445 assert_eq!(
446 serde_json::to_value(&clear).unwrap()["content"],
447 Value::Null
448 );
449 let clear_update: SessionUpdate = serde_json::from_value(clear_wire.clone()).unwrap();
450 assert_eq!(serde_json::to_value(clear_update).unwrap(), clear_wire);
451 assert!(clear.content.is_null());
452 assert!(clear.meta.is_null());
453 for meta in [None, Some(Value::Null), Some(json!({"tag": "value"}))] {
454 let mut wire = base.clone();
455 if let Some(meta) = meta {
456 wire["_meta"] = meta;
457 }
458 let decoded: SessionUpdate = serde_json::from_value(wire.clone()).unwrap();
459 let encoded = serde_json::to_value(decoded).unwrap();
460 assert_eq!(encoded, wire);
461 }
462 let metadata_only = json!({"sessionUpdate": "session_message",
463 "messageId": "m1", "senderSessionId": "parent",
464 "recipientSessionId": "child", "_meta": {"tag": "value"}});
465 let decoded: SessionUpdate = serde_json::from_value(metadata_only.clone()).unwrap();
466 assert_eq!(serde_json::to_value(decoded).unwrap(), metadata_only);
467 let unset = SessionMessage::new("m1");
468 assert_eq!(
469 serde_json::to_value(&unset).unwrap(),
470 json!({"messageId": "m1"})
471 );
472 assert!(unset.content.is_undefined());
473 assert!(unset.meta.is_undefined());
474 let reset = SessionMessage::new("m1").content(vec![]);
475 assert_eq!(serde_json::to_value(reset).unwrap()["content"], json!([]));
476 let malformed: SessionMessage = serde_json::from_value(json!({
477 "messageId": "m1", "senderSessionId": "parent", "recipientSessionId": "child",
478 "content": false, "_meta": false
479 }))
480 .unwrap();
481 assert!(malformed.content.is_undefined());
482 assert!(malformed.meta.is_undefined());
483 }
484
485 #[test]
486 fn first_chunk_and_upsert_share_transcript_local_identity() {
487 for (transcript, id) in [("parent", "sent-1"), ("child", "received-9")] {
488 let wire = json!({"sessionId": transcript, "update": {
489 "sessionUpdate": "session_message_chunk", "messageId": id,
490 "content": {"type": "text", "text": "first"}
491 }});
492 let decoded: SessionNotification = serde_json::from_value(wire.clone()).unwrap();
493 assert_eq!(serde_json::to_value(decoded).unwrap(), wire);
494 let upsert = SessionMessage::new(id)
495 .sender_session_id(SessionId::new("parent"))
496 .recipient_session_id(SessionId::new("child"));
497 let chunk = SessionMessageChunk::new(
498 id,
499 ContentBlock::Text(crate::v1::TextContent::new("first")),
500 );
501 assert_eq!(chunk.message_id, upsert.message_id);
502 assert_eq!(
503 serde_json::to_value(SessionUpdate::SessionMessageChunk(chunk)).unwrap(),
504 wire["update"]
505 );
506 }
507 let wire = json!({"sessionId": "child", "update": {
508 "sessionUpdate": "session_message_chunk", "messageId": "received-9",
509 "content": {"type": "text", "text": "first"}
510 }});
511 let base = wire["update"].clone();
512 for key in ["messageId", "content"] {
513 let mut missing = base.clone();
514 missing.as_object_mut().unwrap().remove(key);
515 assert!(
516 serde_json::from_value::<SessionUpdate>(missing).is_err(),
517 "{key}"
518 );
519 let mut null = base.clone();
520 null[key] = Value::Null;
521 assert!(
522 serde_json::from_value::<SessionUpdate>(null).is_err(),
523 "{key}"
524 );
525 let mut non_string = base.clone();
526 non_string[key] = json!(42);
527 assert!(
528 serde_json::from_value::<SessionUpdate>(non_string).is_err(),
529 "{key}"
530 );
531 }
532 for meta in [None, Some(Value::Null), Some(json!({"chunk": true}))] {
533 let mut value = base.clone();
534 if let Some(meta) = meta {
535 value["_meta"] = meta;
536 }
537 let decoded: SessionUpdate = serde_json::from_value(value.clone()).unwrap();
538 let encoded = serde_json::to_value(decoded).unwrap();
539 if value["_meta"].is_null() {
540 assert_eq!(encoded, base);
541 } else {
542 assert_eq!(encoded, value);
543 }
544 }
545 let with_identity = json!({"sessionUpdate": "session_message_chunk", "messageId": "m1",
547 "senderSessionId": "parent", "recipientSessionId": "child",
548 "senderMessageId": "sent-1", "recipientMessageId": "received-1",
549 "content": {"type": "text", "text": "hello"}});
550 let decoded: SessionUpdate = serde_json::from_value(with_identity).unwrap();
551 assert_eq!(
552 serde_json::to_value(decoded).unwrap(),
553 json!({"sessionUpdate": "session_message_chunk", "messageId": "m1",
554 "content": {"type": "text", "text": "hello"}})
555 );
556 }
557
558 #[cfg(feature = "schemars")]
559 #[test]
560 fn schema_requires_ids_and_chunk_content() {
561 let schema = serde_json::to_value(schemars::schema_for!(SessionMessage)).unwrap();
562 let required = schema["required"].as_array().unwrap();
563 assert_eq!(required, &vec![json!("messageId")]);
564 let chunk = serde_json::to_value(schemars::schema_for!(SessionMessageChunk)).unwrap();
565 let required = chunk["required"].as_array().unwrap();
566 assert_eq!(required.len(), 2);
567 assert!(required.contains(&json!("messageId")));
568 assert!(required.contains(&json!("content")));
569 }
570
571 #[test]
572 fn endpoints_can_arrive_late_or_be_omitted_from_later_events() {
573 let block = ContentBlock::Text(crate::v1::TextContent::new("hello"));
574 let minimal = SessionMessage::new("m1");
575 let first = SessionMessageChunk::new("m1", block.clone());
576 assert_eq!(
577 serde_json::to_value(&minimal).unwrap(),
578 json!({"messageId": "m1"})
579 );
580 assert_eq!(
581 serde_json::to_value(&first).unwrap(),
582 json!({"messageId": "m1", "content": {"type": "text", "text": "hello"}})
583 );
584 let enriched = SessionMessage::new("m1")
585 .sender_session_id(SessionId::new("parent"))
586 .recipient_session_id(SessionId::new("child"));
587 assert_eq!(
588 enriched.sender_session_id,
589 MaybeUndefined::Value(SessionId::new("parent"))
590 );
591 assert_eq!(
592 enriched.recipient_session_id,
593 MaybeUndefined::Value(SessionId::new("child"))
594 );
595 let later = SessionMessage::new("m1").sender_session_id(SessionId::new("parent"));
596 assert_eq!(
597 later.sender_session_id,
598 MaybeUndefined::Value(SessionId::new("parent"))
599 );
600 assert_eq!(later.recipient_session_id, MaybeUndefined::Undefined);
601 let more = SessionMessageChunk::new("m1", block);
602 for update in [
603 SessionUpdate::SessionMessage(minimal),
604 SessionUpdate::SessionMessageChunk(first),
605 SessionUpdate::SessionMessage(enriched),
606 SessionUpdate::SessionMessage(later),
607 SessionUpdate::SessionMessageChunk(more),
608 ] {
609 let wire = serde_json::to_value(&update).unwrap();
610 let decoded: SessionUpdate = serde_json::from_value(wire.clone()).unwrap();
611 assert_eq!(serde_json::to_value(decoded).unwrap(), wire);
612 }
613 }
614
615 #[test]
616 fn null_endpoints_clear_invalid_are_absent_but_message_id_is_required() {
617 let mut base = json!({"sessionUpdate": "session_message", "messageId": "m1",
618 "senderSessionId": null, "recipientSessionId": 42});
619 let decoded: SessionUpdate = serde_json::from_value(base.clone()).unwrap();
620 let encoded = serde_json::to_value(decoded).unwrap();
621 base.as_object_mut().unwrap().remove("recipientSessionId");
622 assert_eq!(encoded, base);
623 for bad in [Value::Null, json!(42)] {
624 let mut invalid = base.clone();
625 invalid["messageId"] = bad;
626 assert!(serde_json::from_value::<SessionUpdate>(invalid).is_err());
627 }
628 }
629
630 #[test]
631 fn message_references_link_views_and_can_arrive_late() {
632 let block = ContentBlock::Text(crate::v1::TextContent::new("hello"));
633 let incoming = SessionMessage::new("received-1")
634 .sender_session_id(SessionId::new("parent"))
635 .recipient_session_id(SessionId::new("child"))
636 .sender_message_id(MessageId::new("sent-1"))
637 .content(vec![block]);
638 let outgoing =
640 SessionMessage::new("sent-1").recipient_message_id(MessageId::new("received-1"));
641 for (update, wire) in [
642 (
643 SessionUpdate::SessionMessage(incoming),
644 json!({"sessionUpdate": "session_message", "messageId": "received-1",
645 "senderSessionId": "parent", "recipientSessionId": "child",
646 "senderMessageId": "sent-1", "content": [{"type": "text", "text": "hello"}]}),
647 ),
648 (
649 SessionUpdate::SessionMessage(outgoing),
650 json!({"sessionUpdate": "session_message", "messageId": "sent-1",
651 "recipientMessageId": "received-1"}),
652 ),
653 ] {
654 assert_eq!(serde_json::to_value(&update).unwrap(), wire);
655 let decoded: SessionUpdate = serde_json::from_value(wire.clone()).unwrap();
656 assert_eq!(decoded, update);
657 }
658
659 let mut wire = json!({"sessionUpdate": "session_message", "messageId": "m1",
660 "senderMessageId": null, "recipientMessageId": 42});
661 let decoded: SessionUpdate = serde_json::from_value(wire.clone()).unwrap();
662 wire.as_object_mut().unwrap().remove("recipientMessageId");
663 assert_eq!(
664 serde_json::to_value(decoded).unwrap(),
665 wire,
666 "null clears a reference and a malformed one is not supplied"
667 );
668 }
669}
670
671#[cfg(all(test, not(feature = "unstable_subagents")))]
672mod disabled_session_message_tests {
673 use super::*;
674 use serde_json::json;
675
676 #[test]
677 fn message_updates_require_subagents_gate() {
678 for (kind, content) in [
679 ("session_message", json!([])),
680 (
681 "session_message_chunk",
682 json!({"type": "text", "text": "hello"}),
683 ),
684 ] {
685 let wire = json!({"sessionUpdate": kind, "messageId": "m1", "content": content});
686 assert!(serde_json::from_value::<SessionUpdate>(wire).is_err());
687 }
688 }
689}
690
691#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
693#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
694#[serde(rename_all = "snake_case")]
695#[non_exhaustive]
696pub enum NoticeSeverity {
697 Info,
699 Warning,
701 Error,
703 #[serde(untagged)]
708 Other(String),
709}
710
711#[serde_as]
721#[skip_serializing_none]
722#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
723#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
724#[serde(rename_all = "camelCase")]
725#[non_exhaustive]
726pub struct Notice {
727 pub severity: NoticeSeverity,
729 #[cfg_attr(feature = "schemars", schemars(length(min = 1)))]
731 pub title: String,
732 #[serde_as(deserialize_as = "DefaultOnError")]
736 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
737 #[serde(default)]
738 pub description: Option<String>,
739 #[serde_as(deserialize_as = "DefaultOnError")]
743 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
744 #[serde(default, rename = "_meta")]
745 pub meta: Option<Meta>,
746}
747
748impl Notice {
749 #[must_use]
751 pub fn new(severity: NoticeSeverity, title: impl Into<String>) -> Self {
752 Self {
753 severity,
754 title: title.into(),
755 description: None,
756 meta: None,
757 }
758 }
759
760 #[must_use]
762 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
763 self.description = description.into_option();
764 self
765 }
766
767 #[must_use]
769 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
770 self.meta = meta.into_option();
771 self
772 }
773}
774
775#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
777#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
778#[serde(transparent)]
779#[from(Arc<str>, String, &'static str)]
780#[non_exhaustive]
781pub struct CompactionId(pub Arc<str>);
782
783impl CompactionId {
784 #[must_use]
786 pub fn new(id: impl Into<Arc<str>>) -> Self {
787 Self(id.into())
788 }
789}
790
791#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
793#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
794#[serde(rename_all = "snake_case")]
795#[non_exhaustive]
796pub enum CompactionStatus {
797 #[default]
799 InProgress,
800 Completed,
802 Failed,
804 Cancelled,
806 #[serde(untagged)]
811 Other(String),
812}
813
814#[serde_as]
823#[skip_serializing_none]
824#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
825#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
826#[serde(rename_all = "camelCase")]
827#[non_exhaustive]
828pub struct CompactionUpdate {
829 pub compaction_id: CompactionId,
831 pub status: CompactionStatus,
833 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<VecSkipError<_>>>")]
835 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
836 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
837 pub summary: MaybeUndefined<Vec<ContentBlock>>,
838 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
840 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
841 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
842 pub error: MaybeUndefined<String>,
843 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
845 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
846 #[serde(
847 rename = "_meta",
848 default,
849 skip_serializing_if = "MaybeUndefined::is_undefined"
850 )]
851 pub meta: MaybeUndefined<Meta>,
852}
853
854impl CompactionUpdate {
855 #[must_use]
857 pub fn new(compaction_id: impl Into<CompactionId>, status: CompactionStatus) -> Self {
858 Self {
859 compaction_id: compaction_id.into(),
860 status,
861 summary: MaybeUndefined::Undefined,
862 error: MaybeUndefined::Undefined,
863 meta: MaybeUndefined::Undefined,
864 }
865 }
866
867 #[must_use]
869 pub fn summary(mut self, summary: impl IntoMaybeUndefined<Vec<ContentBlock>>) -> Self {
870 self.summary = summary.into_maybe_undefined();
871 self
872 }
873
874 #[must_use]
876 pub fn error(mut self, error: impl IntoMaybeUndefined<String>) -> Self {
877 self.error = error.into_maybe_undefined();
878 self
879 }
880
881 #[must_use]
883 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
884 self.meta = meta.into_maybe_undefined();
885 self
886 }
887}
888
889#[serde_as]
894#[skip_serializing_none]
895#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
896#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
897#[serde(rename_all = "camelCase")]
898#[non_exhaustive]
899pub struct CompactionSummaryChunk {
900 pub compaction_id: CompactionId,
902 pub content: ContentBlock,
904 #[serde_as(deserialize_as = "DefaultOnError")]
906 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
907 #[serde(default, rename = "_meta")]
908 pub meta: Option<Meta>,
909}
910
911impl CompactionSummaryChunk {
912 #[must_use]
914 pub fn new(compaction_id: impl Into<CompactionId>, content: ContentBlock) -> Self {
915 Self {
916 compaction_id: compaction_id.into(),
917 content,
918 meta: None,
919 }
920 }
921
922 #[must_use]
924 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
925 self.meta = meta.into_option();
926 self
927 }
928}
929
930#[cfg(feature = "unstable_subagents")]
954#[serde_as]
955#[skip_serializing_none]
956#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
957#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
958#[serde(rename_all = "camelCase")]
959#[non_exhaustive]
960pub struct SubagentUpdate {
961 pub session_id: SessionId,
963 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
968 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
969 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
970 pub title: MaybeUndefined<String>,
971 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
977 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
978 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
979 pub description: MaybeUndefined<String>,
980 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
987 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
988 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
989 pub capabilities: MaybeUndefined<SubagentSessionCapabilities>,
990 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
997 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
998 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
999 pub state: MaybeUndefined<StateUpdate>,
1000 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
1007 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1008 #[serde(
1009 default,
1010 rename = "_meta",
1011 skip_serializing_if = "MaybeUndefined::is_undefined"
1012 )]
1013 pub meta: MaybeUndefined<Meta>,
1014}
1015
1016#[cfg(feature = "unstable_subagents")]
1017impl SubagentUpdate {
1018 #[must_use]
1020 pub fn new(session_id: impl Into<SessionId>) -> Self {
1021 Self {
1022 session_id: session_id.into(),
1023 title: MaybeUndefined::Undefined,
1024 description: MaybeUndefined::Undefined,
1025 capabilities: MaybeUndefined::Undefined,
1026 state: MaybeUndefined::Undefined,
1027 meta: MaybeUndefined::Undefined,
1028 }
1029 }
1030
1031 #[must_use]
1033 pub fn title(mut self, title: impl IntoMaybeUndefined<String>) -> Self {
1034 self.title = title.into_maybe_undefined();
1035 self
1036 }
1037
1038 #[must_use]
1040 pub fn description(mut self, description: impl IntoMaybeUndefined<String>) -> Self {
1041 self.description = description.into_maybe_undefined();
1042 self
1043 }
1044
1045 #[must_use]
1047 pub fn capabilities(
1048 mut self,
1049 capabilities: impl IntoMaybeUndefined<SubagentSessionCapabilities>,
1050 ) -> Self {
1051 self.capabilities = capabilities.into_maybe_undefined();
1052 self
1053 }
1054
1055 #[must_use]
1057 pub fn state(mut self, state: impl IntoMaybeUndefined<StateUpdate>) -> Self {
1058 self.state = state.into_maybe_undefined();
1059 self
1060 }
1061
1062 #[must_use]
1066 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
1067 self.meta = meta.into_maybe_undefined();
1068 self
1069 }
1070}
1071
1072#[cfg(feature = "unstable_subagents")]
1081#[serde_as]
1082#[skip_serializing_none]
1083#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1084#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1085#[serde(rename_all = "camelCase")]
1086#[non_exhaustive]
1087pub struct SubagentSessionCapabilities {
1088 #[serde_as(deserialize_as = "DefaultOnError")]
1092 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1093 #[serde(default)]
1094 pub cancel: Option<SessionCancelCapabilities>,
1095 #[serde_as(deserialize_as = "DefaultOnError")]
1099 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1100 #[serde(default)]
1101 #[serde(rename = "_meta")]
1102 pub meta: Option<Meta>,
1103}
1104
1105#[cfg(feature = "unstable_subagents")]
1106impl SubagentSessionCapabilities {
1107 #[must_use]
1109 pub fn new() -> Self {
1110 Self::default()
1111 }
1112
1113 #[must_use]
1115 pub fn cancel(mut self, cancel: impl IntoOption<SessionCancelCapabilities>) -> Self {
1116 self.cancel = cancel.into_option();
1117 self
1118 }
1119
1120 #[must_use]
1124 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1125 self.meta = meta.into_option();
1126 self
1127 }
1128}
1129
1130#[cfg(feature = "unstable_subagents")]
1138#[serde_as]
1139#[skip_serializing_none]
1140#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1141#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1142#[non_exhaustive]
1143pub struct SessionCancelCapabilities {
1144 #[serde_as(deserialize_as = "DefaultOnError")]
1150 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1151 #[serde(default)]
1152 #[serde(rename = "_meta")]
1153 pub meta: Option<Meta>,
1154}
1155
1156#[cfg(feature = "unstable_subagents")]
1157impl SessionCancelCapabilities {
1158 #[must_use]
1160 pub fn new() -> Self {
1161 Self::default()
1162 }
1163
1164 #[must_use]
1170 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1171 self.meta = meta.into_option();
1172 self
1173 }
1174}
1175
1176#[cfg(feature = "unstable_subagents")]
1186#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1187#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1188#[serde(tag = "state", rename_all = "snake_case")]
1189#[non_exhaustive]
1190pub enum StateUpdate {
1191 Running(RunningStateUpdate),
1193 Idle(IdleStateUpdate),
1195 RequiresAction(RequiresActionStateUpdate),
1197 Unknown(UnknownStateUpdate),
1201 #[serde(untagged)]
1206 Other(OtherStateUpdate),
1207}
1208
1209#[cfg(feature = "unstable_subagents")]
1211#[serde_as]
1212#[skip_serializing_none]
1213#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1214#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1215#[serde(rename_all = "camelCase")]
1216#[non_exhaustive]
1217pub struct RunningStateUpdate {
1218 #[serde_as(deserialize_as = "DefaultOnError")]
1222 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1223 #[serde(default, rename = "_meta")]
1224 pub meta: Option<Meta>,
1225}
1226
1227#[cfg(feature = "unstable_subagents")]
1228impl RunningStateUpdate {
1229 #[must_use]
1231 pub fn new() -> Self {
1232 Self::default()
1233 }
1234
1235 #[must_use]
1237 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1238 self.meta = meta.into_option();
1239 self
1240 }
1241}
1242
1243#[cfg(feature = "unstable_subagents")]
1247#[serde_as]
1248#[skip_serializing_none]
1249#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1250#[cfg_attr(feature = "schemars", schemars(transform = idle_state_update_schema))]
1251#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1252#[serde(rename_all = "camelCase")]
1253#[non_exhaustive]
1254pub struct IdleStateUpdate {
1255 #[serde(flatten)]
1257 pub stop_reason: Option<IdleStopReason>,
1258 #[cfg(feature = "unstable_end_turn_token_usage")]
1262 #[serde_as(deserialize_as = "DefaultOnError")]
1263 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1264 #[serde(default)]
1265 pub usage: Option<Box<Usage>>,
1266 #[serde_as(deserialize_as = "DefaultOnError")]
1270 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1271 #[serde(default, rename = "_meta")]
1272 pub meta: Option<Meta>,
1273}
1274
1275#[cfg(feature = "unstable_subagents")]
1276impl IdleStateUpdate {
1277 #[must_use]
1279 pub fn new() -> Self {
1280 Self::default()
1281 }
1282
1283 #[must_use]
1285 pub fn stop_reason(mut self, stop_reason: impl IntoOption<IdleStopReason>) -> Self {
1286 self.stop_reason = stop_reason.into_option();
1287 self
1288 }
1289
1290 #[cfg(feature = "unstable_end_turn_token_usage")]
1292 #[must_use]
1293 pub fn usage(mut self, usage: impl IntoOption<Usage>) -> Self {
1294 self.usage = usage.into_option().map(Box::new);
1295 self
1296 }
1297
1298 #[must_use]
1300 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1301 self.meta = meta.into_option();
1302 self
1303 }
1304}
1305
1306#[cfg(all(feature = "unstable_subagents", feature = "schemars"))]
1315fn idle_state_update_schema(schema: &mut Schema) {
1316 if let Some(properties) = schema
1317 .get_mut("properties")
1318 .and_then(serde_json::Value::as_object_mut)
1319 {
1320 properties.insert(
1321 "stopReason".into(),
1322 serde_json::json!({
1323 "description": "Why foreground work stopped. The value selects one of this type's variants, which may add fields of their own.\n\nOptional. Omitted or `null` both mean the agent is not reporting a stop reason; a malformed value is treated the same way.\n\nSee protocol docs: [Current work state](https://agentclientprotocol.com/rfds/subagents#current-work-state)",
1324 "type": ["string", "null"],
1325 "x-deserialize-default-on-error": true
1326 }),
1327 );
1328 }
1329 let Some(variants) = schema
1330 .get_mut("anyOf")
1331 .and_then(serde_json::Value::as_array_mut)
1332 else {
1333 return;
1334 };
1335 for variant in variants {
1336 if variant.as_object().is_some_and(serde_json::Map::is_empty) {
1337 *variant = serde_json::json!({
1338 "title": "none",
1339 "description": "No stop reason: `stopReason` is omitted or `null`.",
1340 "type": "object",
1341 "properties": {
1342 "stopReason": { "type": "null" }
1343 }
1344 });
1345 }
1346 }
1347}
1348
1349#[cfg(feature = "unstable_subagents")]
1362#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1363#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1364#[serde(tag = "stopReason", rename_all = "snake_case")]
1365#[non_exhaustive]
1366pub enum IdleStopReason {
1367 EndTurn,
1369 MaxTokens,
1371 MaxTurnRequests,
1374 Refusal,
1376 Cancelled,
1378 Error(ErrorStopReason),
1380 #[serde(untagged)]
1385 Other(OtherStopReason),
1386}
1387
1388#[cfg(feature = "unstable_subagents")]
1389impl From<ErrorStopReason> for IdleStopReason {
1390 fn from(value: ErrorStopReason) -> Self {
1391 Self::Error(value)
1392 }
1393}
1394
1395#[cfg(feature = "unstable_subagents")]
1401#[serde_as]
1402#[skip_serializing_none]
1403#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1404#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1405#[serde(rename_all = "camelCase")]
1406#[non_exhaustive]
1407pub struct ErrorStopReason {
1408 #[serde_as(deserialize_as = "DefaultOnError")]
1413 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1414 #[serde(default)]
1415 pub error: Option<Box<Error>>,
1416}
1417
1418#[cfg(feature = "unstable_subagents")]
1419impl ErrorStopReason {
1420 #[must_use]
1422 pub fn new() -> Self {
1423 Self::default()
1424 }
1425
1426 #[must_use]
1428 pub fn error(mut self, error: impl IntoOption<Error>) -> Self {
1429 self.error = error.into_option().map(Box::new);
1430 self
1431 }
1432}
1433
1434#[cfg(feature = "unstable_subagents")]
1440#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1441#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1442#[cfg_attr(feature = "schemars", schemars(inline))]
1443#[cfg_attr(feature = "schemars", schemars(transform = other_stop_reason_schema))]
1444#[serde(rename_all = "camelCase")]
1445#[non_exhaustive]
1446pub struct OtherStopReason {
1447 pub stop_reason: String,
1449 #[serde(flatten)]
1451 pub fields: BTreeMap<String, serde_json::Value>,
1452}
1453
1454#[cfg(feature = "unstable_subagents")]
1455impl OtherStopReason {
1456 #[must_use]
1458 pub fn new(
1459 stop_reason: impl Into<String>,
1460 mut fields: BTreeMap<String, serde_json::Value>,
1461 ) -> Self {
1462 remove_idle_state_update_fields(&mut fields);
1463 Self {
1464 stop_reason: stop_reason.into(),
1465 fields,
1466 }
1467 }
1468}
1469
1470#[cfg(feature = "unstable_subagents")]
1471impl<'de> Deserialize<'de> for OtherStopReason {
1472 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1473 where
1474 D: serde::Deserializer<'de>,
1475 {
1476 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1477 let stop_reason = fields
1478 .remove("stopReason")
1479 .ok_or_else(|| serde::de::Error::missing_field("stopReason"))?;
1480 let serde_json::Value::String(stop_reason) = stop_reason else {
1481 return Err(serde::de::Error::custom("`stopReason` must be a string"));
1482 };
1483 if KNOWN_STOP_REASONS.contains(&stop_reason.as_str()) {
1484 return Err(serde::de::Error::custom(format!(
1485 "known stop reason `{stop_reason}` did not match its schema"
1486 )));
1487 }
1488 remove_idle_state_update_fields(&mut fields);
1489 Ok(Self {
1490 stop_reason,
1491 fields,
1492 })
1493 }
1494}
1495
1496#[cfg(feature = "unstable_subagents")]
1497const KNOWN_STOP_REASONS: &[&str] = &[
1498 "end_turn",
1499 "max_tokens",
1500 "max_turn_requests",
1501 "refusal",
1502 "cancelled",
1503 "error",
1504];
1505
1506#[cfg(feature = "unstable_subagents")]
1508fn remove_idle_state_update_fields(fields: &mut BTreeMap<String, serde_json::Value>) {
1509 fields.remove("stopReason");
1510 fields.remove("usage");
1511 fields.remove("_meta");
1512}
1513
1514#[cfg(all(feature = "unstable_subagents", feature = "schemars"))]
1515fn other_stop_reason_schema(schema: &mut Schema) {
1516 let known = KNOWN_STOP_REASONS
1517 .iter()
1518 .map(|stop_reason| {
1519 serde_json::json!({
1520 "properties": { "stopReason": { "const": stop_reason, "type": "string" } },
1521 "required": ["stopReason"],
1522 "type": "object"
1523 })
1524 })
1525 .collect::<Vec<_>>();
1526 schema.insert("not".into(), serde_json::json!({ "anyOf": known }));
1527}
1528
1529#[cfg(feature = "unstable_subagents")]
1531#[serde_as]
1532#[skip_serializing_none]
1533#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1534#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1535#[serde(rename_all = "camelCase")]
1536#[non_exhaustive]
1537pub struct RequiresActionStateUpdate {
1538 #[serde_as(deserialize_as = "DefaultOnError")]
1542 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1543 #[serde(default, rename = "_meta")]
1544 pub meta: Option<Meta>,
1545}
1546
1547#[cfg(feature = "unstable_subagents")]
1548impl RequiresActionStateUpdate {
1549 #[must_use]
1551 pub fn new() -> Self {
1552 Self::default()
1553 }
1554
1555 #[must_use]
1557 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1558 self.meta = meta.into_option();
1559 self
1560 }
1561}
1562
1563#[cfg(feature = "unstable_subagents")]
1577#[serde_as]
1578#[skip_serializing_none]
1579#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1580#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1581#[serde(rename_all = "camelCase")]
1582#[non_exhaustive]
1583pub struct UnknownStateUpdate {
1584 #[serde_as(deserialize_as = "DefaultOnError")]
1588 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1589 #[serde(default, rename = "_meta")]
1590 pub meta: Option<Meta>,
1591}
1592
1593#[cfg(feature = "unstable_subagents")]
1594impl UnknownStateUpdate {
1595 #[must_use]
1597 pub fn new() -> Self {
1598 Self::default()
1599 }
1600
1601 #[must_use]
1603 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1604 self.meta = meta.into_option();
1605 self
1606 }
1607}
1608
1609#[cfg(feature = "unstable_subagents")]
1611#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1612#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
1613#[cfg_attr(feature = "schemars", schemars(inline))]
1614#[cfg_attr(feature = "schemars", schemars(transform = other_state_update_schema))]
1615#[serde(rename_all = "camelCase")]
1616#[non_exhaustive]
1617pub struct OtherStateUpdate {
1618 pub state: String,
1620 #[serde(flatten)]
1622 pub fields: BTreeMap<String, serde_json::Value>,
1623}
1624
1625#[cfg(feature = "unstable_subagents")]
1626impl OtherStateUpdate {
1627 #[must_use]
1629 pub fn new(state: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
1630 fields.remove("state");
1631 Self {
1632 state: state.into(),
1633 fields,
1634 }
1635 }
1636}
1637
1638#[cfg(feature = "unstable_subagents")]
1639impl<'de> Deserialize<'de> for OtherStateUpdate {
1640 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1641 where
1642 D: serde::Deserializer<'de>,
1643 {
1644 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1645 let state = fields
1646 .remove("state")
1647 .ok_or_else(|| serde::de::Error::missing_field("state"))?;
1648 let serde_json::Value::String(state) = state else {
1649 return Err(serde::de::Error::custom("`state` must be a string"));
1650 };
1651 if is_known_state_update(&state) {
1652 return Err(serde::de::Error::custom(format!(
1653 "known state update `{state}` did not match its schema"
1654 )));
1655 }
1656 Ok(Self { state, fields })
1657 }
1658}
1659
1660#[cfg(feature = "unstable_subagents")]
1661const KNOWN_STATE_UPDATE_STATES: &[&str] = &["running", "idle", "requires_action", "unknown"];
1662
1663#[cfg(feature = "unstable_subagents")]
1664fn is_known_state_update(state: &str) -> bool {
1665 KNOWN_STATE_UPDATE_STATES.contains(&state)
1666}
1667
1668#[cfg(all(feature = "unstable_subagents", feature = "schemars"))]
1669fn other_state_update_schema(schema: &mut Schema) {
1670 let known = KNOWN_STATE_UPDATE_STATES
1671 .iter()
1672 .map(|state| {
1673 serde_json::json!({
1674 "properties": { "state": { "const": state, "type": "string" } },
1675 "required": ["state"],
1676 "type": "object"
1677 })
1678 })
1679 .collect::<Vec<_>>();
1680 schema.insert("not".into(), serde_json::json!({ "anyOf": known }));
1681}
1682
1683#[serde_as]
1687#[skip_serializing_none]
1688#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1689#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1690#[serde(rename_all = "camelCase")]
1691#[non_exhaustive]
1692pub struct CurrentModeUpdate {
1693 pub current_mode_id: SessionModeId,
1695 #[serde_as(deserialize_as = "DefaultOnError")]
1701 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1702 #[serde(default)]
1703 #[serde(rename = "_meta")]
1704 pub meta: Option<Meta>,
1705}
1706
1707impl CurrentModeUpdate {
1708 #[must_use]
1710 pub fn new(current_mode_id: impl Into<SessionModeId>) -> Self {
1711 Self {
1712 current_mode_id: current_mode_id.into(),
1713 meta: None,
1714 }
1715 }
1716
1717 #[must_use]
1723 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1724 self.meta = meta.into_option();
1725 self
1726 }
1727}
1728
1729#[serde_as]
1731#[skip_serializing_none]
1732#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1733#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1734#[serde(rename_all = "camelCase")]
1735#[non_exhaustive]
1736pub struct ConfigOptionUpdate {
1737 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
1739 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
1740 pub config_options: Vec<SessionConfigOption>,
1741 #[serde_as(deserialize_as = "DefaultOnError")]
1747 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1748 #[serde(default)]
1749 #[serde(rename = "_meta")]
1750 pub meta: Option<Meta>,
1751}
1752
1753impl ConfigOptionUpdate {
1754 #[must_use]
1756 pub fn new(config_options: Vec<SessionConfigOption>) -> Self {
1757 Self {
1758 config_options,
1759 meta: None,
1760 }
1761 }
1762
1763 #[must_use]
1769 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1770 self.meta = meta.into_option();
1771 self
1772 }
1773}
1774
1775#[serde_as]
1780#[skip_serializing_none]
1781#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1782#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1783#[serde(rename_all = "camelCase")]
1784#[non_exhaustive]
1785pub struct SessionInfoUpdate {
1786 #[serde_as(deserialize_as = "DefaultOnError")]
1788 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1789 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
1790 pub title: MaybeUndefined<String>,
1791 #[serde_as(deserialize_as = "DefaultOnError")]
1793 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1794 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
1795 pub updated_at: MaybeUndefined<String>,
1796 #[serde_as(deserialize_as = "DefaultOnError")]
1802 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1803 #[serde(default)]
1804 #[serde(rename = "_meta")]
1805 pub meta: Option<Meta>,
1806}
1807
1808impl SessionInfoUpdate {
1809 #[must_use]
1811 pub fn new() -> Self {
1812 Self::default()
1813 }
1814
1815 #[must_use]
1817 pub fn title(mut self, title: impl IntoMaybeUndefined<String>) -> Self {
1818 self.title = title.into_maybe_undefined();
1819 self
1820 }
1821
1822 #[must_use]
1824 pub fn updated_at(mut self, updated_at: impl IntoMaybeUndefined<String>) -> Self {
1825 self.updated_at = updated_at.into_maybe_undefined();
1826 self
1827 }
1828
1829 #[must_use]
1835 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1836 self.meta = meta.into_option();
1837 self
1838 }
1839}
1840
1841#[serde_as]
1843#[skip_serializing_none]
1844#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1845#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1846#[serde(rename_all = "camelCase")]
1847#[non_exhaustive]
1848pub struct UsageUpdate {
1849 pub used: u64,
1851 pub size: u64,
1853 #[serde_as(deserialize_as = "DefaultOnError")]
1855 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1856 #[serde(default)]
1857 pub cost: Option<Cost>,
1858 #[serde_as(deserialize_as = "DefaultOnError")]
1864 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1865 #[serde(default)]
1866 #[serde(rename = "_meta")]
1867 pub meta: Option<Meta>,
1868}
1869
1870impl UsageUpdate {
1871 #[must_use]
1873 pub fn new(used: u64, size: u64) -> Self {
1874 Self {
1875 used,
1876 size,
1877 cost: None,
1878 meta: None,
1879 }
1880 }
1881
1882 #[must_use]
1884 pub fn cost(mut self, cost: impl IntoOption<Cost>) -> Self {
1885 self.cost = cost.into_option();
1886 self
1887 }
1888
1889 #[must_use]
1895 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1896 self.meta = meta.into_option();
1897 self
1898 }
1899}
1900
1901#[serde_as]
1903#[skip_serializing_none]
1904#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1905#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1906#[serde(rename_all = "camelCase")]
1907#[non_exhaustive]
1908pub struct Cost {
1909 pub amount: f64,
1911 pub currency: String,
1913 #[serde_as(deserialize_as = "DefaultOnError")]
1919 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1920 #[serde(default)]
1921 #[serde(rename = "_meta")]
1922 pub meta: Option<Meta>,
1923}
1924
1925impl Cost {
1926 #[must_use]
1928 pub fn new(amount: f64, currency: impl Into<String>) -> Self {
1929 Self {
1930 amount,
1931 currency: currency.into(),
1932 meta: None,
1933 }
1934 }
1935
1936 #[must_use]
1942 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1943 self.meta = meta.into_option();
1944 self
1945 }
1946}
1947
1948#[serde_as]
1950#[skip_serializing_none]
1951#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1952#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1953#[serde(rename_all = "camelCase")]
1954#[non_exhaustive]
1955pub struct ContentChunk {
1956 pub content: ContentBlock,
1958 #[serde_as(deserialize_as = "DefaultOnError")]
1963 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1964 #[serde(default)]
1965 pub message_id: Option<MessageId>,
1966 #[serde_as(deserialize_as = "DefaultOnError")]
1972 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1973 #[serde(default)]
1974 #[serde(rename = "_meta")]
1975 pub meta: Option<Meta>,
1976}
1977
1978impl ContentChunk {
1979 #[must_use]
1981 pub fn new(content: ContentBlock) -> Self {
1982 Self {
1983 content,
1984 message_id: None,
1985 meta: None,
1986 }
1987 }
1988
1989 #[must_use]
1994 pub fn message_id(mut self, message_id: impl IntoOption<MessageId>) -> Self {
1995 self.message_id = message_id.into_option();
1996 self
1997 }
1998
1999 #[must_use]
2005 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2006 self.meta = meta.into_option();
2007 self
2008 }
2009}
2010
2011#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2013#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
2014#[serde(transparent)]
2015#[from(Arc<str>, String, &'static str)]
2016#[non_exhaustive]
2017pub struct MessageId(pub Arc<str>);
2018
2019impl MessageId {
2020 #[must_use]
2022 pub fn new(id: impl Into<Arc<str>>) -> Self {
2023 Self(id.into())
2024 }
2025}
2026
2027impl IntoOption<MessageId> for &str {
2028 fn into_option(self) -> Option<MessageId> {
2029 Some(MessageId::new(self))
2030 }
2031}
2032
2033#[serde_as]
2035#[skip_serializing_none]
2036#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2037#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2038#[serde(rename_all = "camelCase")]
2039#[non_exhaustive]
2040pub struct AvailableCommandsUpdate {
2041 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
2043 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
2044 pub available_commands: Vec<AvailableCommand>,
2045 #[serde_as(deserialize_as = "DefaultOnError")]
2051 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2052 #[serde(default)]
2053 #[serde(rename = "_meta")]
2054 pub meta: Option<Meta>,
2055}
2056
2057impl AvailableCommandsUpdate {
2058 #[must_use]
2060 pub fn new(available_commands: Vec<AvailableCommand>) -> Self {
2061 Self {
2062 available_commands,
2063 meta: None,
2064 }
2065 }
2066
2067 #[must_use]
2073 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2074 self.meta = meta.into_option();
2075 self
2076 }
2077}
2078
2079#[serde_as]
2081#[skip_serializing_none]
2082#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2083#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2084#[serde(rename_all = "camelCase")]
2085#[non_exhaustive]
2086pub struct AvailableCommand {
2087 pub name: String,
2089 pub description: String,
2091 #[serde_as(deserialize_as = "DefaultOnError")]
2093 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2094 #[serde(default)]
2095 pub input: Option<AvailableCommandInput>,
2096 #[serde_as(deserialize_as = "DefaultOnError")]
2102 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2103 #[serde(default)]
2104 #[serde(rename = "_meta")]
2105 pub meta: Option<Meta>,
2106}
2107
2108impl AvailableCommand {
2109 #[must_use]
2111 pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
2112 Self {
2113 name: name.into(),
2114 description: description.into(),
2115 input: None,
2116 meta: None,
2117 }
2118 }
2119
2120 #[must_use]
2122 pub fn input(mut self, input: impl IntoOption<AvailableCommandInput>) -> Self {
2123 self.input = input.into_option();
2124 self
2125 }
2126
2127 #[must_use]
2133 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2134 self.meta = meta.into_option();
2135 self
2136 }
2137}
2138
2139#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2141#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2142#[serde(untagged, rename_all = "camelCase")]
2143#[non_exhaustive]
2144pub enum AvailableCommandInput {
2145 Unstructured(UnstructuredCommandInput),
2147}
2148
2149#[serde_as]
2151#[skip_serializing_none]
2152#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2153#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2154#[serde(rename_all = "camelCase")]
2155#[non_exhaustive]
2156pub struct UnstructuredCommandInput {
2157 pub hint: String,
2159 #[serde_as(deserialize_as = "DefaultOnError")]
2165 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2166 #[serde(default)]
2167 #[serde(rename = "_meta")]
2168 pub meta: Option<Meta>,
2169}
2170
2171impl UnstructuredCommandInput {
2172 #[must_use]
2174 pub fn new(hint: impl Into<String>) -> Self {
2175 Self {
2176 hint: hint.into(),
2177 meta: None,
2178 }
2179 }
2180
2181 #[must_use]
2187 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2188 self.meta = meta.into_option();
2189 self
2190 }
2191}
2192
2193#[serde_as]
2201#[skip_serializing_none]
2202#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2203#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2204#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = SESSION_REQUEST_PERMISSION_METHOD_NAME)))]
2205#[serde(rename_all = "camelCase")]
2206#[non_exhaustive]
2207pub struct RequestPermissionRequest {
2208 pub session_id: SessionId,
2210 pub tool_call: ToolCallUpdate,
2212 pub options: Vec<PermissionOption>,
2214 #[serde_as(deserialize_as = "DefaultOnError")]
2220 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2221 #[serde(default)]
2222 #[serde(rename = "_meta")]
2223 pub meta: Option<Meta>,
2224}
2225
2226impl RequestPermissionRequest {
2227 #[must_use]
2229 pub fn new(
2230 session_id: impl Into<SessionId>,
2231 tool_call: ToolCallUpdate,
2232 options: Vec<PermissionOption>,
2233 ) -> Self {
2234 Self {
2235 session_id: session_id.into(),
2236 tool_call,
2237 options,
2238 meta: None,
2239 }
2240 }
2241
2242 #[must_use]
2248 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2249 self.meta = meta.into_option();
2250 self
2251 }
2252}
2253
2254#[serde_as]
2256#[skip_serializing_none]
2257#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2258#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2259#[serde(rename_all = "camelCase")]
2260#[non_exhaustive]
2261pub struct PermissionOption {
2262 pub option_id: PermissionOptionId,
2264 pub name: String,
2266 pub kind: PermissionOptionKind,
2268 #[serde_as(deserialize_as = "DefaultOnError")]
2274 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2275 #[serde(default)]
2276 #[serde(rename = "_meta")]
2277 pub meta: Option<Meta>,
2278}
2279
2280impl PermissionOption {
2281 #[must_use]
2283 pub fn new(
2284 option_id: impl Into<PermissionOptionId>,
2285 name: impl Into<String>,
2286 kind: PermissionOptionKind,
2287 ) -> Self {
2288 Self {
2289 option_id: option_id.into(),
2290 name: name.into(),
2291 kind,
2292 meta: None,
2293 }
2294 }
2295
2296 #[must_use]
2302 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2303 self.meta = meta.into_option();
2304 self
2305 }
2306}
2307
2308#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2310#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
2311#[serde(transparent)]
2312#[from(Arc<str>, String, &'static str)]
2313#[non_exhaustive]
2314pub struct PermissionOptionId(pub Arc<str>);
2315
2316impl PermissionOptionId {
2317 #[must_use]
2319 pub fn new(id: impl Into<Arc<str>>) -> Self {
2320 Self(id.into())
2321 }
2322}
2323
2324#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2328#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
2329#[serde(rename_all = "snake_case")]
2330#[non_exhaustive]
2331pub enum PermissionOptionKind {
2332 AllowOnce,
2334 AllowAlways,
2336 RejectOnce,
2338 RejectAlways,
2340}
2341
2342#[serde_as]
2344#[skip_serializing_none]
2345#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2346#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2347#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = SESSION_REQUEST_PERMISSION_METHOD_NAME)))]
2348#[serde(rename_all = "camelCase")]
2349#[non_exhaustive]
2350pub struct RequestPermissionResponse {
2351 pub outcome: RequestPermissionOutcome,
2354 #[serde_as(deserialize_as = "DefaultOnError")]
2360 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2361 #[serde(default)]
2362 #[serde(rename = "_meta")]
2363 pub meta: Option<Meta>,
2364}
2365
2366impl RequestPermissionResponse {
2367 #[must_use]
2369 pub fn new(outcome: RequestPermissionOutcome) -> Self {
2370 Self {
2371 outcome,
2372 meta: None,
2373 }
2374 }
2375
2376 #[must_use]
2382 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2383 self.meta = meta.into_option();
2384 self
2385 }
2386}
2387
2388#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2390#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2391#[serde(tag = "outcome", rename_all = "snake_case")]
2392#[cfg_attr(feature = "schemars", schemars(extend("discriminator" = {"propertyName": "outcome"})))]
2393#[non_exhaustive]
2394pub enum RequestPermissionOutcome {
2395 Cancelled,
2403 #[serde(rename_all = "camelCase")]
2405 Selected(SelectedPermissionOutcome),
2406}
2407
2408#[serde_as]
2410#[skip_serializing_none]
2411#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2412#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2413#[serde(rename_all = "camelCase")]
2414#[non_exhaustive]
2415pub struct SelectedPermissionOutcome {
2416 pub option_id: PermissionOptionId,
2418 #[serde_as(deserialize_as = "DefaultOnError")]
2424 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2425 #[serde(default)]
2426 #[serde(rename = "_meta")]
2427 pub meta: Option<Meta>,
2428}
2429
2430impl SelectedPermissionOutcome {
2431 #[must_use]
2433 pub fn new(option_id: impl Into<PermissionOptionId>) -> Self {
2434 Self {
2435 option_id: option_id.into(),
2436 meta: None,
2437 }
2438 }
2439
2440 #[must_use]
2446 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2447 self.meta = meta.into_option();
2448 self
2449 }
2450}
2451
2452#[serde_as]
2458#[skip_serializing_none]
2459#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2460#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2461#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = FS_WRITE_TEXT_FILE_METHOD_NAME)))]
2462#[serde(rename_all = "camelCase")]
2463#[non_exhaustive]
2464pub struct WriteTextFileRequest {
2465 pub session_id: SessionId,
2467 pub path: PathBuf,
2469 pub content: String,
2471 #[serde_as(deserialize_as = "DefaultOnError")]
2477 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2478 #[serde(default)]
2479 #[serde(rename = "_meta")]
2480 pub meta: Option<Meta>,
2481}
2482
2483impl WriteTextFileRequest {
2484 #[must_use]
2486 pub fn new(
2487 session_id: impl Into<SessionId>,
2488 path: impl Into<PathBuf>,
2489 content: impl Into<String>,
2490 ) -> Self {
2491 Self {
2492 session_id: session_id.into(),
2493 path: path.into(),
2494 content: content.into(),
2495 meta: None,
2496 }
2497 }
2498
2499 #[must_use]
2505 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2506 self.meta = meta.into_option();
2507 self
2508 }
2509}
2510
2511crate::serde_util::default_on_null! {
2512 #[serde_as]
2514 #[skip_serializing_none]
2515 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2516 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
2517 #[serde(rename_all = "camelCase")]
2518 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = FS_WRITE_TEXT_FILE_METHOD_NAME)))]
2519 #[non_exhaustive]
2520 pub struct WriteTextFileResponse {
2521 #[serde_as(deserialize_as = "DefaultOnError")]
2527 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2528 #[serde(default)]
2529 #[serde(rename = "_meta")]
2530 pub meta: Option<Meta>,
2531 }
2532}
2533
2534impl WriteTextFileResponse {
2535 #[must_use]
2537 pub fn new() -> Self {
2538 Self::default()
2539 }
2540
2541 #[must_use]
2547 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2548 self.meta = meta.into_option();
2549 self
2550 }
2551}
2552
2553#[serde_as]
2559#[skip_serializing_none]
2560#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2561#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2562#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = FS_READ_TEXT_FILE_METHOD_NAME)))]
2563#[serde(rename_all = "camelCase")]
2564#[non_exhaustive]
2565pub struct ReadTextFileRequest {
2566 pub session_id: SessionId,
2568 pub path: PathBuf,
2570 #[serde(default)]
2572 pub line: Option<u32>,
2573 #[serde(default)]
2575 pub limit: Option<u32>,
2576 #[serde_as(deserialize_as = "DefaultOnError")]
2582 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2583 #[serde(default)]
2584 #[serde(rename = "_meta")]
2585 pub meta: Option<Meta>,
2586}
2587
2588impl ReadTextFileRequest {
2589 #[must_use]
2591 pub fn new(session_id: impl Into<SessionId>, path: impl Into<PathBuf>) -> Self {
2592 Self {
2593 session_id: session_id.into(),
2594 path: path.into(),
2595 line: None,
2596 limit: None,
2597 meta: None,
2598 }
2599 }
2600
2601 #[must_use]
2603 pub fn line(mut self, line: impl IntoOption<u32>) -> Self {
2604 self.line = line.into_option();
2605 self
2606 }
2607
2608 #[must_use]
2610 pub fn limit(mut self, limit: impl IntoOption<u32>) -> Self {
2611 self.limit = limit.into_option();
2612 self
2613 }
2614
2615 #[must_use]
2621 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2622 self.meta = meta.into_option();
2623 self
2624 }
2625}
2626
2627#[serde_as]
2629#[skip_serializing_none]
2630#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2631#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2632#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = FS_READ_TEXT_FILE_METHOD_NAME)))]
2633#[serde(rename_all = "camelCase")]
2634#[non_exhaustive]
2635pub struct ReadTextFileResponse {
2636 pub content: String,
2638 #[serde_as(deserialize_as = "DefaultOnError")]
2644 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2645 #[serde(default)]
2646 #[serde(rename = "_meta")]
2647 pub meta: Option<Meta>,
2648}
2649
2650impl ReadTextFileResponse {
2651 #[must_use]
2653 pub fn new(content: impl Into<String>) -> Self {
2654 Self {
2655 content: content.into(),
2656 meta: None,
2657 }
2658 }
2659
2660 #[must_use]
2666 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2667 self.meta = meta.into_option();
2668 self
2669 }
2670}
2671
2672#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2676#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
2677#[serde(transparent)]
2678#[from(Arc<str>, String, &'static str)]
2679#[non_exhaustive]
2680pub struct TerminalId(pub Arc<str>);
2681
2682impl TerminalId {
2683 #[must_use]
2685 pub fn new(id: impl Into<Arc<str>>) -> Self {
2686 Self(id.into())
2687 }
2688}
2689
2690#[serde_as]
2692#[skip_serializing_none]
2693#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2694#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2695#[serde(rename_all = "camelCase")]
2696#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_CREATE_METHOD_NAME)))]
2697#[non_exhaustive]
2698pub struct CreateTerminalRequest {
2699 pub session_id: SessionId,
2701 pub command: String,
2703 #[serde_as(deserialize_as = "DefaultOnNull")]
2705 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-null" = true)))]
2706 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2707 pub args: Vec<String>,
2708 #[serde_as(deserialize_as = "DefaultOnNull")]
2710 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-null" = true)))]
2711 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2712 pub env: Vec<EnvVariable>,
2713 #[serde(default)]
2715 pub cwd: Option<PathBuf>,
2716 #[serde(default)]
2725 pub output_byte_limit: Option<u64>,
2726 #[serde_as(deserialize_as = "DefaultOnError")]
2732 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2733 #[serde(default)]
2734 #[serde(rename = "_meta")]
2735 pub meta: Option<Meta>,
2736}
2737
2738impl CreateTerminalRequest {
2739 #[must_use]
2741 pub fn new(session_id: impl Into<SessionId>, command: impl Into<String>) -> Self {
2742 Self {
2743 session_id: session_id.into(),
2744 command: command.into(),
2745 args: Vec::new(),
2746 env: Vec::new(),
2747 cwd: None,
2748 output_byte_limit: None,
2749 meta: None,
2750 }
2751 }
2752
2753 #[must_use]
2755 pub fn args(mut self, args: Vec<String>) -> Self {
2756 self.args = args;
2757 self
2758 }
2759
2760 #[must_use]
2762 pub fn env(mut self, env: Vec<EnvVariable>) -> Self {
2763 self.env = env;
2764 self
2765 }
2766
2767 #[must_use]
2769 pub fn cwd(mut self, cwd: impl IntoOption<PathBuf>) -> Self {
2770 self.cwd = cwd.into_option();
2771 self
2772 }
2773
2774 #[must_use]
2783 pub fn output_byte_limit(mut self, output_byte_limit: impl IntoOption<u64>) -> Self {
2784 self.output_byte_limit = output_byte_limit.into_option();
2785 self
2786 }
2787
2788 #[must_use]
2794 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2795 self.meta = meta.into_option();
2796 self
2797 }
2798}
2799
2800#[serde_as]
2802#[skip_serializing_none]
2803#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2804#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2805#[serde(rename_all = "camelCase")]
2806#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_CREATE_METHOD_NAME)))]
2807#[non_exhaustive]
2808pub struct CreateTerminalResponse {
2809 pub terminal_id: TerminalId,
2811 #[serde_as(deserialize_as = "DefaultOnError")]
2817 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2818 #[serde(default)]
2819 #[serde(rename = "_meta")]
2820 pub meta: Option<Meta>,
2821}
2822
2823impl CreateTerminalResponse {
2824 #[must_use]
2826 pub fn new(terminal_id: impl Into<TerminalId>) -> Self {
2827 Self {
2828 terminal_id: terminal_id.into(),
2829 meta: None,
2830 }
2831 }
2832
2833 #[must_use]
2839 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2840 self.meta = meta.into_option();
2841 self
2842 }
2843}
2844
2845#[serde_as]
2847#[skip_serializing_none]
2848#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2849#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2850#[serde(rename_all = "camelCase")]
2851#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_OUTPUT_METHOD_NAME)))]
2852#[non_exhaustive]
2853pub struct TerminalOutputRequest {
2854 pub session_id: SessionId,
2856 pub terminal_id: TerminalId,
2858 #[serde_as(deserialize_as = "DefaultOnError")]
2864 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2865 #[serde(default)]
2866 #[serde(rename = "_meta")]
2867 pub meta: Option<Meta>,
2868}
2869
2870impl TerminalOutputRequest {
2871 #[must_use]
2873 pub fn new(session_id: impl Into<SessionId>, terminal_id: impl Into<TerminalId>) -> Self {
2874 Self {
2875 session_id: session_id.into(),
2876 terminal_id: terminal_id.into(),
2877 meta: None,
2878 }
2879 }
2880
2881 #[must_use]
2887 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2888 self.meta = meta.into_option();
2889 self
2890 }
2891}
2892
2893#[serde_as]
2895#[skip_serializing_none]
2896#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2897#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2898#[serde(rename_all = "camelCase")]
2899#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_OUTPUT_METHOD_NAME)))]
2900#[non_exhaustive]
2901pub struct TerminalOutputResponse {
2902 pub output: String,
2904 pub truncated: bool,
2906 #[serde_as(deserialize_as = "DefaultOnError")]
2908 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2909 #[serde(default)]
2910 pub exit_status: Option<TerminalExitStatus>,
2911 #[serde_as(deserialize_as = "DefaultOnError")]
2917 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2918 #[serde(default)]
2919 #[serde(rename = "_meta")]
2920 pub meta: Option<Meta>,
2921}
2922
2923impl TerminalOutputResponse {
2924 #[must_use]
2926 pub fn new(output: impl Into<String>, truncated: bool) -> Self {
2927 Self {
2928 output: output.into(),
2929 truncated,
2930 exit_status: None,
2931 meta: None,
2932 }
2933 }
2934
2935 #[must_use]
2937 pub fn exit_status(mut self, exit_status: impl IntoOption<TerminalExitStatus>) -> Self {
2938 self.exit_status = exit_status.into_option();
2939 self
2940 }
2941
2942 #[must_use]
2948 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2949 self.meta = meta.into_option();
2950 self
2951 }
2952}
2953
2954#[serde_as]
2956#[skip_serializing_none]
2957#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2958#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2959#[serde(rename_all = "camelCase")]
2960#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_RELEASE_METHOD_NAME)))]
2961#[non_exhaustive]
2962pub struct ReleaseTerminalRequest {
2963 pub session_id: SessionId,
2965 pub terminal_id: TerminalId,
2967 #[serde_as(deserialize_as = "DefaultOnError")]
2973 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2974 #[serde(default)]
2975 #[serde(rename = "_meta")]
2976 pub meta: Option<Meta>,
2977}
2978
2979impl ReleaseTerminalRequest {
2980 #[must_use]
2982 pub fn new(session_id: impl Into<SessionId>, terminal_id: impl Into<TerminalId>) -> Self {
2983 Self {
2984 session_id: session_id.into(),
2985 terminal_id: terminal_id.into(),
2986 meta: None,
2987 }
2988 }
2989
2990 #[must_use]
2996 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2997 self.meta = meta.into_option();
2998 self
2999 }
3000}
3001
3002crate::serde_util::default_on_null! {
3003 #[serde_as]
3005 #[skip_serializing_none]
3006 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3007 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
3008 #[serde(rename_all = "camelCase")]
3009 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_RELEASE_METHOD_NAME)))]
3010 #[non_exhaustive]
3011 pub struct ReleaseTerminalResponse {
3012 #[serde_as(deserialize_as = "DefaultOnError")]
3018 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3019 #[serde(default)]
3020 #[serde(rename = "_meta")]
3021 pub meta: Option<Meta>,
3022 }
3023}
3024
3025impl ReleaseTerminalResponse {
3026 #[must_use]
3028 pub fn new() -> Self {
3029 Self::default()
3030 }
3031
3032 #[must_use]
3038 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3039 self.meta = meta.into_option();
3040 self
3041 }
3042}
3043
3044#[serde_as]
3046#[skip_serializing_none]
3047#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3048#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3049#[serde(rename_all = "camelCase")]
3050#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_KILL_METHOD_NAME)))]
3051#[non_exhaustive]
3052pub struct KillTerminalRequest {
3053 pub session_id: SessionId,
3055 pub terminal_id: TerminalId,
3057 #[serde_as(deserialize_as = "DefaultOnError")]
3063 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3064 #[serde(default)]
3065 #[serde(rename = "_meta")]
3066 pub meta: Option<Meta>,
3067}
3068
3069impl KillTerminalRequest {
3070 #[must_use]
3072 pub fn new(session_id: impl Into<SessionId>, terminal_id: impl Into<TerminalId>) -> Self {
3073 Self {
3074 session_id: session_id.into(),
3075 terminal_id: terminal_id.into(),
3076 meta: None,
3077 }
3078 }
3079
3080 #[must_use]
3086 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3087 self.meta = meta.into_option();
3088 self
3089 }
3090}
3091
3092crate::serde_util::default_on_null! {
3093 #[serde_as]
3095 #[skip_serializing_none]
3096 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3097 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
3098 #[serde(rename_all = "camelCase")]
3099 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_KILL_METHOD_NAME)))]
3100 #[non_exhaustive]
3101 pub struct KillTerminalResponse {
3102 #[serde_as(deserialize_as = "DefaultOnError")]
3108 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3109 #[serde(default)]
3110 #[serde(rename = "_meta")]
3111 pub meta: Option<Meta>,
3112 }
3113}
3114
3115impl KillTerminalResponse {
3116 #[must_use]
3118 pub fn new() -> Self {
3119 Self::default()
3120 }
3121
3122 #[must_use]
3128 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3129 self.meta = meta.into_option();
3130 self
3131 }
3132}
3133
3134#[serde_as]
3136#[skip_serializing_none]
3137#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3138#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3139#[serde(rename_all = "camelCase")]
3140#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_WAIT_FOR_EXIT_METHOD_NAME)))]
3141#[non_exhaustive]
3142pub struct WaitForTerminalExitRequest {
3143 pub session_id: SessionId,
3145 pub terminal_id: TerminalId,
3147 #[serde_as(deserialize_as = "DefaultOnError")]
3153 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3154 #[serde(default)]
3155 #[serde(rename = "_meta")]
3156 pub meta: Option<Meta>,
3157}
3158
3159impl WaitForTerminalExitRequest {
3160 #[must_use]
3162 pub fn new(session_id: impl Into<SessionId>, terminal_id: impl Into<TerminalId>) -> Self {
3163 Self {
3164 session_id: session_id.into(),
3165 terminal_id: terminal_id.into(),
3166 meta: None,
3167 }
3168 }
3169
3170 #[must_use]
3176 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3177 self.meta = meta.into_option();
3178 self
3179 }
3180}
3181
3182crate::serde_util::default_on_null! {
3183 #[serde_as]
3185 #[skip_serializing_none]
3186 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3187 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
3188 #[serde(rename_all = "camelCase")]
3189 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_WAIT_FOR_EXIT_METHOD_NAME)))]
3190 #[non_exhaustive]
3191 pub struct WaitForTerminalExitResponse {
3192 #[serde(flatten)]
3194 pub exit_status: TerminalExitStatus,
3195 #[serde_as(deserialize_as = "DefaultOnError")]
3201 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3202 #[serde(default)]
3203 #[serde(rename = "_meta")]
3204 pub meta: Option<Meta>,
3205 }
3206}
3207
3208impl WaitForTerminalExitResponse {
3209 #[must_use]
3211 pub fn new(exit_status: TerminalExitStatus) -> Self {
3212 Self {
3213 exit_status,
3214 meta: None,
3215 }
3216 }
3217
3218 #[must_use]
3224 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3225 self.meta = meta.into_option();
3226 self
3227 }
3228}
3229
3230#[serde_as]
3232#[skip_serializing_none]
3233#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3234#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3235#[serde(rename_all = "camelCase")]
3236#[non_exhaustive]
3237pub struct TerminalExitStatus {
3238 #[serde_as(deserialize_as = "DefaultOnError")]
3240 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3241 #[serde(default)]
3242 pub exit_code: Option<u32>,
3243 #[serde_as(deserialize_as = "DefaultOnError")]
3245 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3246 #[serde(default)]
3247 pub signal: Option<String>,
3248 #[serde_as(deserialize_as = "DefaultOnError")]
3254 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3255 #[serde(default)]
3256 #[serde(rename = "_meta")]
3257 pub meta: Option<Meta>,
3258}
3259
3260impl TerminalExitStatus {
3261 #[must_use]
3263 pub fn new() -> Self {
3264 Self::default()
3265 }
3266
3267 #[must_use]
3269 pub fn exit_code(mut self, exit_code: impl IntoOption<u32>) -> Self {
3270 self.exit_code = exit_code.into_option();
3271 self
3272 }
3273
3274 #[must_use]
3276 pub fn signal(mut self, signal: impl IntoOption<String>) -> Self {
3277 self.signal = signal.into_option();
3278 self
3279 }
3280
3281 #[must_use]
3287 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3288 self.meta = meta.into_option();
3289 self
3290 }
3291}
3292
3293#[serde_as]
3302#[skip_serializing_none]
3303#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3304#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3305#[serde(rename_all = "camelCase")]
3306#[non_exhaustive]
3307pub struct ClientCapabilities {
3308 #[serde_as(deserialize_as = "DefaultOnError")]
3311 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3312 #[serde(default)]
3313 pub fs: FileSystemCapabilities,
3314 #[serde_as(deserialize_as = "DefaultOnError")]
3316 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3317 #[serde(default)]
3318 pub terminal: bool,
3319 #[serde_as(deserialize_as = "DefaultOnError")]
3324 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3325 #[serde(default)]
3326 pub session: Option<ClientSessionCapabilities>,
3327 #[cfg(feature = "unstable_subagents")]
3338 #[serde_as(deserialize_as = "DefaultOnError")]
3339 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3340 #[serde(default)]
3341 pub subagents: Option<SubagentCapabilities>,
3342 #[cfg(feature = "unstable_plan_operations")]
3351 #[serde_as(deserialize_as = "DefaultOnError")]
3352 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3353 #[serde(default)]
3354 pub plan: Option<PlanCapabilities>,
3355 #[serde_as(deserialize_as = "DefaultOnError")]
3359 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3360 #[serde(default)]
3361 pub auth: AuthCapabilities,
3362 #[serde_as(deserialize_as = "DefaultOnError")]
3368 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3369 #[serde(default)]
3370 pub elicitation: Option<ElicitationCapabilities>,
3371 #[serde_as(deserialize_as = "DefaultOnError")]
3377 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3378 #[serde(default)]
3379 #[serde(rename = "_meta")]
3380 pub meta: Option<Meta>,
3381}
3382
3383impl ClientCapabilities {
3384 #[must_use]
3386 pub fn new() -> Self {
3387 Self::default()
3388 }
3389
3390 #[must_use]
3393 pub fn fs(mut self, fs: FileSystemCapabilities) -> Self {
3394 self.fs = fs;
3395 self
3396 }
3397
3398 #[must_use]
3400 pub fn terminal(mut self, terminal: bool) -> Self {
3401 self.terminal = terminal;
3402 self
3403 }
3404
3405 #[must_use]
3407 pub fn session(mut self, session: impl IntoOption<ClientSessionCapabilities>) -> Self {
3408 self.session = session.into_option();
3409 self
3410 }
3411
3412 #[cfg(feature = "unstable_subagents")]
3418 #[must_use]
3419 pub fn subagents(mut self, subagents: impl IntoOption<SubagentCapabilities>) -> Self {
3420 self.subagents = subagents.into_option();
3421 self
3422 }
3423
3424 #[cfg(feature = "unstable_plan_operations")]
3433 #[must_use]
3434 pub fn plan(mut self, plan: impl IntoOption<PlanCapabilities>) -> Self {
3435 self.plan = plan.into_option();
3436 self
3437 }
3438
3439 #[must_use]
3443 pub fn auth(mut self, auth: AuthCapabilities) -> Self {
3444 self.auth = auth;
3445 self
3446 }
3447
3448 #[must_use]
3451 pub fn elicitation(mut self, elicitation: impl IntoOption<ElicitationCapabilities>) -> Self {
3452 self.elicitation = elicitation.into_option();
3453 self
3454 }
3455
3456 #[must_use]
3462 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3463 self.meta = meta.into_option();
3464 self
3465 }
3466}
3467
3468#[cfg(feature = "unstable_subagents")]
3479#[serde_as]
3480#[skip_serializing_none]
3481#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3482#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3483#[non_exhaustive]
3484pub struct SubagentCapabilities {
3485 #[serde_as(deserialize_as = "DefaultOnError")]
3489 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3490 #[serde(default)]
3491 #[serde(rename = "_meta")]
3492 pub meta: Option<Meta>,
3493}
3494
3495#[cfg(feature = "unstable_subagents")]
3496impl SubagentCapabilities {
3497 #[must_use]
3499 pub fn new() -> Self {
3500 Self::default()
3501 }
3502
3503 #[must_use]
3507 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3508 self.meta = meta.into_option();
3509 self
3510 }
3511}
3512
3513#[serde_as]
3515#[skip_serializing_none]
3516#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3517#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3518#[serde(rename_all = "camelCase")]
3519#[non_exhaustive]
3520pub struct ClientSessionCapabilities {
3521 #[serde_as(deserialize_as = "DefaultOnError")]
3524 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3525 #[serde(default)]
3526 pub compaction: Option<CompactionCapabilities>,
3527 #[serde_as(deserialize_as = "DefaultOnError")]
3532 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3533 #[serde(default)]
3534 pub config_options: Option<SessionConfigOptionsCapabilities>,
3535 #[serde_as(deserialize_as = "DefaultOnError")]
3540 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3541 #[serde(default)]
3542 pub notices: Option<NoticeCapabilities>,
3543 #[serde_as(deserialize_as = "DefaultOnError")]
3549 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3550 #[serde(default)]
3551 #[serde(rename = "_meta")]
3552 pub meta: Option<Meta>,
3553}
3554
3555impl ClientSessionCapabilities {
3556 #[must_use]
3558 pub fn new() -> Self {
3559 Self::default()
3560 }
3561
3562 #[must_use]
3564 pub fn compaction(mut self, compaction: impl IntoOption<CompactionCapabilities>) -> Self {
3565 self.compaction = compaction.into_option();
3566 self
3567 }
3568
3569 #[must_use]
3574 pub fn config_options(
3575 mut self,
3576 config_options: impl IntoOption<SessionConfigOptionsCapabilities>,
3577 ) -> Self {
3578 self.config_options = config_options.into_option();
3579 self
3580 }
3581
3582 #[must_use]
3584 pub fn notices(mut self, notices: impl IntoOption<NoticeCapabilities>) -> Self {
3585 self.notices = notices.into_option();
3586 self
3587 }
3588
3589 #[must_use]
3595 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3596 self.meta = meta.into_option();
3597 self
3598 }
3599}
3600
3601#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3603#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3604#[serde(rename_all = "camelCase")]
3605#[non_exhaustive]
3606pub struct CompactionCapabilities {}
3607
3608impl CompactionCapabilities {
3609 #[must_use]
3611 pub fn new() -> Self {
3612 Self {}
3613 }
3614}
3615
3616#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3618#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3619#[serde(rename_all = "camelCase")]
3620#[non_exhaustive]
3621pub struct NoticeCapabilities {}
3622
3623impl NoticeCapabilities {
3624 #[must_use]
3626 pub fn new() -> Self {
3627 Self {}
3628 }
3629}
3630
3631#[serde_as]
3633#[skip_serializing_none]
3634#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3635#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3636#[serde(rename_all = "camelCase")]
3637#[non_exhaustive]
3638pub struct SessionConfigOptionsCapabilities {
3639 #[serde_as(deserialize_as = "DefaultOnError")]
3646 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3647 #[serde(default)]
3648 pub boolean: Option<BooleanConfigOptionCapabilities>,
3649 #[serde_as(deserialize_as = "DefaultOnError")]
3655 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3656 #[serde(default)]
3657 #[serde(rename = "_meta")]
3658 pub meta: Option<Meta>,
3659}
3660
3661impl SessionConfigOptionsCapabilities {
3662 #[must_use]
3664 pub fn new() -> Self {
3665 Self::default()
3666 }
3667
3668 #[must_use]
3675 pub fn boolean(mut self, boolean: impl IntoOption<BooleanConfigOptionCapabilities>) -> Self {
3676 self.boolean = boolean.into_option();
3677 self
3678 }
3679
3680 #[must_use]
3686 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3687 self.meta = meta.into_option();
3688 self
3689 }
3690}
3691
3692#[serde_as]
3696#[skip_serializing_none]
3697#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3698#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3699#[non_exhaustive]
3700pub struct BooleanConfigOptionCapabilities {
3701 #[serde_as(deserialize_as = "DefaultOnError")]
3707 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3708 #[serde(default)]
3709 #[serde(rename = "_meta")]
3710 pub meta: Option<Meta>,
3711}
3712
3713impl BooleanConfigOptionCapabilities {
3714 #[must_use]
3716 pub fn new() -> Self {
3717 Self::default()
3718 }
3719
3720 #[must_use]
3726 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3727 self.meta = meta.into_option();
3728 self
3729 }
3730}
3731
3732#[serde_as]
3738#[skip_serializing_none]
3739#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3740#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3741#[serde(rename_all = "camelCase")]
3742#[non_exhaustive]
3743pub struct AuthCapabilities {
3744 #[serde_as(deserialize_as = "DefaultOnError")]
3750 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3751 #[serde(default)]
3752 pub terminal: bool,
3753 #[serde_as(deserialize_as = "DefaultOnError")]
3759 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3760 #[serde(default)]
3761 #[serde(rename = "_meta")]
3762 pub meta: Option<Meta>,
3763}
3764
3765impl AuthCapabilities {
3766 #[must_use]
3768 pub fn new() -> Self {
3769 Self::default()
3770 }
3771
3772 #[must_use]
3779 pub fn terminal(mut self, terminal: bool) -> Self {
3780 self.terminal = terminal;
3781 self
3782 }
3783
3784 #[must_use]
3790 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3791 self.meta = meta.into_option();
3792 self
3793 }
3794}
3795
3796#[serde_as]
3800#[skip_serializing_none]
3801#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3802#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3803#[serde(rename_all = "camelCase")]
3804#[non_exhaustive]
3805pub struct FileSystemCapabilities {
3806 #[serde_as(deserialize_as = "DefaultOnError")]
3808 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3809 #[serde(default)]
3810 pub read_text_file: bool,
3811 #[serde_as(deserialize_as = "DefaultOnError")]
3813 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3814 #[serde(default)]
3815 pub write_text_file: bool,
3816 #[serde_as(deserialize_as = "DefaultOnError")]
3822 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3823 #[serde(default)]
3824 #[serde(rename = "_meta")]
3825 pub meta: Option<Meta>,
3826}
3827
3828impl FileSystemCapabilities {
3829 #[must_use]
3831 pub fn new() -> Self {
3832 Self::default()
3833 }
3834
3835 #[must_use]
3837 pub fn read_text_file(mut self, read_text_file: bool) -> Self {
3838 self.read_text_file = read_text_file;
3839 self
3840 }
3841
3842 #[must_use]
3844 pub fn write_text_file(mut self, write_text_file: bool) -> Self {
3845 self.write_text_file = write_text_file;
3846 self
3847 }
3848
3849 #[must_use]
3855 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3856 self.meta = meta.into_option();
3857 self
3858 }
3859}
3860
3861#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3867#[non_exhaustive]
3868pub struct ClientMethodNames {
3869 pub session_request_permission: &'static str,
3871 pub session_update: &'static str,
3873 pub fs_write_text_file: &'static str,
3875 pub fs_read_text_file: &'static str,
3877 pub terminal_create: &'static str,
3879 pub terminal_output: &'static str,
3881 pub terminal_release: &'static str,
3883 pub terminal_wait_for_exit: &'static str,
3885 pub terminal_kill: &'static str,
3887 #[cfg(feature = "unstable_mcp_over_acp")]
3889 pub mcp_message: &'static str,
3890 pub elicitation_create: &'static str,
3892 pub elicitation_complete: &'static str,
3894}
3895
3896pub const CLIENT_METHOD_NAMES: ClientMethodNames = ClientMethodNames {
3898 session_update: SESSION_UPDATE_NOTIFICATION,
3899 session_request_permission: SESSION_REQUEST_PERMISSION_METHOD_NAME,
3900 fs_write_text_file: FS_WRITE_TEXT_FILE_METHOD_NAME,
3901 fs_read_text_file: FS_READ_TEXT_FILE_METHOD_NAME,
3902 terminal_create: TERMINAL_CREATE_METHOD_NAME,
3903 terminal_output: TERMINAL_OUTPUT_METHOD_NAME,
3904 terminal_release: TERMINAL_RELEASE_METHOD_NAME,
3905 terminal_wait_for_exit: TERMINAL_WAIT_FOR_EXIT_METHOD_NAME,
3906 terminal_kill: TERMINAL_KILL_METHOD_NAME,
3907 #[cfg(feature = "unstable_mcp_over_acp")]
3908 mcp_message: MCP_MESSAGE_METHOD_NAME,
3909 elicitation_create: ELICITATION_CREATE_METHOD_NAME,
3910 elicitation_complete: ELICITATION_COMPLETE_NOTIFICATION,
3911};
3912
3913pub(crate) const SESSION_UPDATE_NOTIFICATION: &str = "session/update";
3915pub(crate) const SESSION_REQUEST_PERMISSION_METHOD_NAME: &str = "session/request_permission";
3917pub(crate) const FS_WRITE_TEXT_FILE_METHOD_NAME: &str = "fs/write_text_file";
3919pub(crate) const FS_READ_TEXT_FILE_METHOD_NAME: &str = "fs/read_text_file";
3921pub(crate) const TERMINAL_CREATE_METHOD_NAME: &str = "terminal/create";
3923pub(crate) const TERMINAL_OUTPUT_METHOD_NAME: &str = "terminal/output";
3925pub(crate) const TERMINAL_RELEASE_METHOD_NAME: &str = "terminal/release";
3927pub(crate) const TERMINAL_WAIT_FOR_EXIT_METHOD_NAME: &str = "terminal/wait_for_exit";
3929pub(crate) const TERMINAL_KILL_METHOD_NAME: &str = "terminal/kill";
3931pub(crate) const ELICITATION_CREATE_METHOD_NAME: &str = "elicitation/create";
3933pub(crate) const ELICITATION_COMPLETE_NOTIFICATION: &str = "elicitation/complete";
3935
3936#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3943#[derive(Clone, Debug, Serialize, Deserialize)]
3944#[serde(untagged)]
3945#[cfg_attr(feature = "schemars", schemars(inline))]
3946#[non_exhaustive]
3947#[allow(clippy::large_enum_variant)]
3948pub enum AgentRequest {
3949 WriteTextFileRequest(WriteTextFileRequest),
3956 ReadTextFileRequest(ReadTextFileRequest),
3963 RequestPermissionRequest(RequestPermissionRequest),
3974 CreateTerminalRequest(CreateTerminalRequest),
3989 TerminalOutputRequest(TerminalOutputRequest),
3996 ReleaseTerminalRequest(ReleaseTerminalRequest),
4009 WaitForTerminalExitRequest(WaitForTerminalExitRequest),
4013 KillTerminalRequest(KillTerminalRequest),
4026 CreateElicitationRequest(CreateElicitationRequest),
4030 #[cfg(feature = "unstable_mcp_over_acp")]
4036 MessageMcpRequest(MessageMcpRequest),
4037 ExtMethodRequest(ExtRequest),
4045}
4046
4047impl AgentRequest {
4048 #[must_use]
4050 pub fn method(&self) -> &str {
4051 match self {
4052 Self::WriteTextFileRequest(_) => CLIENT_METHOD_NAMES.fs_write_text_file,
4053 Self::ReadTextFileRequest(_) => CLIENT_METHOD_NAMES.fs_read_text_file,
4054 Self::RequestPermissionRequest(_) => CLIENT_METHOD_NAMES.session_request_permission,
4055 Self::CreateTerminalRequest(_) => CLIENT_METHOD_NAMES.terminal_create,
4056 Self::TerminalOutputRequest(_) => CLIENT_METHOD_NAMES.terminal_output,
4057 Self::ReleaseTerminalRequest(_) => CLIENT_METHOD_NAMES.terminal_release,
4058 Self::WaitForTerminalExitRequest(_) => CLIENT_METHOD_NAMES.terminal_wait_for_exit,
4059 Self::KillTerminalRequest(_) => CLIENT_METHOD_NAMES.terminal_kill,
4060 Self::CreateElicitationRequest(_) => CLIENT_METHOD_NAMES.elicitation_create,
4061 #[cfg(feature = "unstable_mcp_over_acp")]
4062 Self::MessageMcpRequest(_) => CLIENT_METHOD_NAMES.mcp_message,
4063 Self::ExtMethodRequest(ext_request) => &ext_request.method,
4064 }
4065 }
4066}
4067
4068#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4075#[derive(Clone, Debug, Serialize, Deserialize)]
4076#[serde(untagged)]
4077#[cfg_attr(feature = "schemars", schemars(inline))]
4078#[non_exhaustive]
4079pub enum ClientResponse {
4080 WriteTextFileResponse(#[serde(default)] WriteTextFileResponse),
4082 ReadTextFileResponse(ReadTextFileResponse),
4084 RequestPermissionResponse(RequestPermissionResponse),
4086 CreateTerminalResponse(CreateTerminalResponse),
4088 TerminalOutputResponse(TerminalOutputResponse),
4090 ReleaseTerminalResponse(#[serde(default)] ReleaseTerminalResponse),
4092 WaitForTerminalExitResponse(WaitForTerminalExitResponse),
4094 KillTerminalResponse(#[serde(default)] KillTerminalResponse),
4096 CreateElicitationResponse(CreateElicitationResponse),
4098 #[cfg(feature = "unstable_mcp_over_acp")]
4100 MessageMcpResponse(MessageMcpResponse),
4101 ExtMethodResponse(ExtResponse),
4103}
4104
4105#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4112#[derive(Clone, Debug, Serialize, Deserialize)]
4113#[serde(untagged)]
4114#[expect(clippy::large_enum_variant)]
4115#[cfg_attr(feature = "schemars", schemars(inline))]
4116#[non_exhaustive]
4117pub enum AgentNotification {
4118 SessionNotification(SessionNotification),
4130 CompleteElicitationNotification(CompleteElicitationNotification),
4134 ExtNotification(ExtNotification),
4142}
4143
4144impl AgentNotification {
4145 #[must_use]
4147 pub fn method(&self) -> &str {
4148 match self {
4149 Self::SessionNotification(_) => CLIENT_METHOD_NAMES.session_update,
4150 Self::CompleteElicitationNotification(_) => CLIENT_METHOD_NAMES.elicitation_complete,
4151 Self::ExtNotification(ext_notification) => &ext_notification.method,
4152 }
4153 }
4154}
4155
4156#[cfg(test)]
4157mod tests {
4158 use super::*;
4159
4160 #[test]
4161 fn notice_preserves_wire_shape_nullable_fields_and_open_severity() {
4162 use serde_json::json;
4163
4164 let mut meta = Meta::new();
4165 meta.insert("source".into(), json!("fallback"));
4166 assert_eq!(
4167 serde_json::to_value(SessionUpdate::Notice(
4168 Notice::new(NoticeSeverity::Warning, "MCP server unavailable")
4169 .description("Continuing without it.")
4170 .meta(meta),
4171 ))
4172 .unwrap(),
4173 json!({
4174 "sessionUpdate": "notice",
4175 "severity": "warning",
4176 "title": "MCP server unavailable",
4177 "description": "Continuing without it.",
4178 "_meta": { "source": "fallback" }
4179 })
4180 );
4181
4182 assert_eq!(
4183 serde_json::to_value(SessionUpdate::Notice(Notice::new(
4184 NoticeSeverity::Info,
4185 "Indexing workspace",
4186 )))
4187 .unwrap(),
4188 json!({
4189 "sessionUpdate": "notice",
4190 "severity": "info",
4191 "title": "Indexing workspace"
4192 })
4193 );
4194
4195 let SessionUpdate::Notice(notice) = serde_json::from_value(json!({
4196 "sessionUpdate": "notice",
4197 "severity": "critical",
4198 "title": "Provider degraded",
4199 "description": null,
4200 "_meta": null
4201 }))
4202 .unwrap() else {
4203 panic!("expected notice");
4204 };
4205
4206 assert_eq!(
4207 notice.severity,
4208 NoticeSeverity::Other("critical".to_string())
4209 );
4210 assert_eq!(notice.description, None);
4211 assert_eq!(notice.meta, None);
4212 assert_eq!(
4213 serde_json::to_value(SessionUpdate::Notice(notice)).unwrap(),
4214 json!({
4215 "sessionUpdate": "notice",
4216 "severity": "critical",
4217 "title": "Provider degraded"
4218 })
4219 );
4220 }
4221
4222 #[test]
4223 fn notice_requires_string_severity_and_title() {
4224 use serde_json::json;
4225
4226 for malformed in [
4227 json!({
4228 "sessionUpdate": "notice",
4229 "severity": "warning",
4230 "title": 42
4231 }),
4232 json!({
4233 "sessionUpdate": "notice",
4234 "severity": 42,
4235 "title": "MCP server unavailable"
4236 }),
4237 json!({
4238 "sessionUpdate": "notice",
4239 "severity": "warning"
4240 }),
4241 json!({
4242 "sessionUpdate": "notice",
4243 "severity": "warning",
4244 "title": null
4245 }),
4246 json!({
4247 "sessionUpdate": "notice",
4248 "title": "MCP server unavailable"
4249 }),
4250 json!({
4251 "sessionUpdate": "notice",
4252 "severity": null,
4253 "title": "MCP server unavailable"
4254 }),
4255 ] {
4256 assert!(serde_json::from_str::<SessionUpdate>(&malformed.to_string()).is_err());
4257 assert!(serde_json::from_value::<SessionUpdate>(malformed).is_err());
4258 }
4259 }
4260
4261 #[test]
4262 fn notice_tolerates_invalid_optional_display_fields_and_custom_severity() {
4263 use serde_json::json;
4264
4265 let wire = json!({
4266 "sessionUpdate": "notice",
4267 "severity": "_custom",
4268 "title": "Provider degraded",
4269 "description": 42,
4270 "_meta": false
4271 });
4272 let SessionUpdate::Notice(notice) = serde_json::from_value(wire.clone()).unwrap() else {
4273 panic!("expected notice");
4274 };
4275 assert_eq!(
4277 serde_json::from_str::<SessionUpdate>(&wire.to_string()).unwrap(),
4278 SessionUpdate::Notice(notice.clone())
4279 );
4280 assert_eq!(notice.severity, NoticeSeverity::Other("_custom".into()));
4281 assert_eq!(notice.description, None);
4282 assert_eq!(notice.meta, None);
4283 assert_eq!(
4284 serde_json::to_value(notice).unwrap(),
4285 json!({"severity": "_custom", "title": "Provider degraded"})
4286 );
4287 }
4288
4289 #[cfg(feature = "schemars")]
4290 #[test]
4291 fn notice_schema_preserves_required_and_nullable_fields() {
4292 use serde_json::json;
4293
4294 let schema = serde_json::to_value(schemars::schema_for!(Notice)).unwrap();
4295 assert_eq!(schema["required"], json!(["severity", "title"]));
4296 assert_eq!(schema["properties"]["title"]["minLength"], 1);
4297 for field in ["description", "_meta"] {
4298 assert!(
4299 schema["properties"][field]["type"]
4300 .as_array()
4301 .unwrap()
4302 .contains(&json!("null"))
4303 );
4304 }
4305 let capabilities =
4306 serde_json::to_value(schemars::schema_for!(ClientSessionCapabilities)).unwrap();
4307 assert!(
4308 capabilities["properties"]["notices"]["anyOf"]
4309 .as_array()
4310 .unwrap()
4311 .contains(&json!({"type": "null"}))
4312 );
4313 assert!(
4314 !capabilities["required"]
4315 .as_array()
4316 .is_some_and(|required| required.contains(&json!("notices")))
4317 );
4318 }
4319
4320 #[test]
4321 fn notice_capability_advertises_support_only_when_present() {
4322 use serde_json::json;
4323
4324 let capabilities = ClientCapabilities::new()
4325 .session(ClientSessionCapabilities::new().notices(NoticeCapabilities::new()));
4326 let value = serde_json::to_value(&capabilities).unwrap();
4327 assert_eq!(value["session"], json!({ "notices": {} }));
4328 assert_eq!(
4329 serde_json::from_value::<ClientCapabilities>(value).unwrap(),
4330 capabilities
4331 );
4332
4333 for unsupported in [
4334 json!({}),
4335 json!({ "session": null }),
4336 json!({ "session": {} }),
4337 json!({ "session": { "notices": null } }),
4338 json!({ "session": { "notices": false } }),
4339 json!({ "session": { "notices": true } }),
4340 json!({ "session": { "notices": "supported" } }),
4341 ] {
4342 let capabilities: ClientCapabilities = serde_json::from_value(unsupported).unwrap();
4343 assert!(
4344 capabilities
4345 .session
4346 .and_then(|session| session.notices)
4347 .is_none()
4348 );
4349 }
4350
4351 assert_eq!(
4352 serde_json::to_value(
4353 ClientSessionCapabilities::new()
4354 .notices(NoticeCapabilities::new())
4355 .notices(None)
4356 )
4357 .unwrap(),
4358 json!({})
4359 );
4360 }
4361
4362 #[test]
4363 fn compaction_updates_preserve_patch_and_open_status_semantics() {
4364 use serde_json::json;
4365
4366 assert_eq!(
4367 serde_json::to_value(SessionUpdate::CompactionUpdate(CompactionUpdate::new(
4368 "cmp_001",
4369 CompactionStatus::InProgress,
4370 )))
4371 .unwrap(),
4372 json!({
4373 "sessionUpdate": "compaction_update",
4374 "compactionId": "cmp_001",
4375 "status": "in_progress"
4376 })
4377 );
4378
4379 let SessionUpdate::CompactionUpdate(update) = serde_json::from_value(json!({
4380 "sessionUpdate": "compaction_update",
4381 "compactionId": "cmp_001",
4382 "status": "paused",
4383 "summary": null,
4384 "error": null
4385 }))
4386 .unwrap() else {
4387 panic!("expected compaction update");
4388 };
4389 assert_eq!(update.status, CompactionStatus::Other("paused".into()));
4390 assert!(update.summary.is_null());
4391 assert!(update.error.is_null());
4392 assert!(update.meta.is_undefined());
4393 }
4394
4395 #[test]
4396 fn compaction_chunk_and_v1_capability_serialize() {
4397 use serde_json::json;
4398
4399 assert_eq!(
4400 serde_json::to_value(SessionUpdate::CompactionSummaryChunk(
4401 CompactionSummaryChunk::new(
4402 "cmp_001",
4403 ContentBlock::Text(crate::v1::TextContent::new("retained")),
4404 ),
4405 ))
4406 .unwrap(),
4407 json!({
4408 "sessionUpdate": "compaction_summary_chunk",
4409 "compactionId": "cmp_001",
4410 "content": { "type": "text", "text": "retained" }
4411 })
4412 );
4413 assert_eq!(
4414 serde_json::to_value(
4415 ClientSessionCapabilities::new().compaction(CompactionCapabilities::new())
4416 )
4417 .unwrap(),
4418 json!({ "compaction": {} })
4419 );
4420 let absent: ClientSessionCapabilities = serde_json::from_value(json!({})).unwrap();
4421 let null: ClientSessionCapabilities =
4422 serde_json::from_value(json!({ "compaction": null })).unwrap();
4423 assert!(absent.compaction.is_none());
4424 assert!(null.compaction.is_none());
4425 }
4426
4427 #[cfg(feature = "unstable_subagents")]
4428 #[test]
4429 fn test_subagent_updates_serialization() {
4430 use serde_json::json;
4431
4432 let announced = SessionUpdate::SubagentUpdate(SubagentUpdate::new("sess_child_1"));
4433 assert_eq!(
4434 serde_json::to_value(&announced).unwrap(),
4435 json!({
4436 "sessionUpdate": "subagent_update",
4437 "sessionId": "sess_child_1"
4438 })
4439 );
4440 assert_eq!(
4441 serde_json::from_value::<SessionUpdate>(serde_json::to_value(&announced).unwrap())
4442 .unwrap(),
4443 announced
4444 );
4445 let capable = SubagentUpdate::new("sess_child_1").capabilities(
4446 SubagentSessionCapabilities::new().cancel(SessionCancelCapabilities::new()),
4447 );
4448 assert_eq!(
4449 serde_json::to_value(capable).unwrap(),
4450 json!({
4451 "sessionId": "sess_child_1",
4452 "capabilities": {"cancel": {}}
4453 })
4454 );
4455 let minimal: SubagentUpdate =
4456 serde_json::from_value(json!({ "sessionId": "sess_child_3" })).unwrap();
4457 assert!(minimal.capabilities.is_undefined());
4458 assert!(minimal.state.is_undefined());
4459
4460 let nulls: SubagentUpdate = serde_json::from_value(json!({
4461 "sessionId": "sess_child_3",
4462 "capabilities": null,
4463 "state": null
4464 }))
4465 .unwrap();
4466 assert!(nulls.capabilities.is_null());
4467 assert!(nulls.state.is_null());
4468 assert_eq!(
4469 serde_json::to_value(nulls).unwrap(),
4470 json!({"sessionId": "sess_child_3", "capabilities": null, "state": null})
4471 );
4472 }
4473
4474 #[cfg(feature = "unstable_subagents")]
4475 #[test]
4476 fn test_subagent_display_metadata() {
4477 use serde_json::json;
4478
4479 let update = SubagentUpdate::new("child")
4480 .title("Test investigator".to_string())
4481 .description("Investigates platform-specific test failures.".to_string());
4482 let wire = json!({
4483 "sessionId": "child",
4484 "title": "Test investigator",
4485 "description": "Investigates platform-specific test failures."
4486 });
4487 assert_eq!(serde_json::to_value(&update).unwrap(), wire);
4488 assert_eq!(
4489 serde_json::from_value::<SubagentUpdate>(wire).unwrap(),
4490 update
4491 );
4492
4493 let minimal = SubagentUpdate::new("child");
4494 assert_eq!(
4495 serde_json::to_value(&minimal).unwrap(),
4496 json!({"sessionId": "child"})
4497 );
4498 let cleared: SubagentUpdate = serde_json::from_value(json!({
4499 "sessionId": "child", "title": null, "description": null
4500 }))
4501 .unwrap();
4502 assert!(cleared.title.is_null());
4503 assert!(cleared.description.is_null());
4504 assert_eq!(
4505 serde_json::to_value(cleared).unwrap(),
4506 json!({"sessionId": "child", "title": null, "description": null})
4507 );
4508 let invalid: SubagentUpdate = serde_json::from_value(json!({
4509 "sessionId": "child", "title": false, "description": false
4510 }))
4511 .unwrap();
4512 assert_eq!(invalid, minimal);
4513
4514 let title_only: SubagentUpdate = serde_json::from_value(json!({
4515 "sessionId": "child", "title": "Updated title"
4516 }))
4517 .unwrap();
4518 assert_eq!(
4519 title_only.title.value().map(String::as_str),
4520 Some("Updated title")
4521 );
4522 assert!(title_only.description.is_undefined());
4523 }
4524
4525 #[cfg(feature = "unstable_subagents")]
4526 #[test]
4527 fn subagent_patch_fields_preserve_omitted_null_and_concrete() {
4528 use serde_json::json;
4529
4530 let omitted = SubagentUpdate::new("child");
4531 for field in ["title", "description", "capabilities", "state", "_meta"] {
4532 let wire = json!({"sessionId": "child", field: null});
4533 let decoded: SubagentUpdate = serde_json::from_value(wire.clone()).unwrap();
4534 assert_eq!(serde_json::to_value(decoded).unwrap(), wire, "{field}");
4535 let malformed = json!({"sessionId": "child", field: false});
4536 let decoded: SubagentUpdate = serde_json::from_value(malformed).unwrap();
4537 assert_eq!(decoded, omitted, "{field}");
4538 }
4539 let concrete = json!({
4540 "sessionId": "child", "title": "Investigator",
4541 "description": "Tests", "capabilities": {"cancel": {}},
4542 "state": {"state": "running"}, "_meta": {"source": "parent"}
4543 });
4544 let decoded: SubagentUpdate = serde_json::from_value(concrete.clone()).unwrap();
4545 assert!(decoded.title.value().is_some());
4546 assert!(decoded.description.value().is_some());
4547 assert!(decoded.capabilities.value().is_some());
4548 assert!(decoded.state.value().is_some());
4549 assert!(decoded.meta.value().is_some());
4550 assert_eq!(serde_json::to_value(decoded).unwrap(), concrete);
4551 let cleared = SubagentUpdate::new("child")
4552 .title(None)
4553 .description(None)
4554 .capabilities(None)
4555 .state(None)
4556 .meta(None);
4557 assert_eq!(
4558 serde_json::to_value(cleared).unwrap(),
4559 json!({
4560 "sessionId": "child", "title": null, "description": null,
4561 "capabilities": null, "state": null, "_meta": null
4562 })
4563 );
4564 }
4565
4566 #[cfg(feature = "unstable_subagents")]
4567 #[test]
4568 fn test_subagent_notification_keeps_parent_and_child_ids_nested() {
4569 use serde_json::json;
4570
4571 let notification = SessionNotification::new(
4572 "parent",
4573 SessionUpdate::SubagentUpdate(SubagentUpdate::new("child")),
4574 );
4575 let wire = json!({
4576 "sessionId": "parent",
4577 "update": {
4578 "sessionUpdate": "subagent_update",
4579 "sessionId": "child"
4580 }
4581 });
4582 assert_eq!(serde_json::to_value(¬ification).unwrap(), wire);
4583 assert_eq!(
4584 serde_json::from_value::<SessionNotification>(wire).unwrap(),
4585 notification
4586 );
4587 for malformed in [
4589 json!({"sessionId": "parent", "update": {"sessionUpdate": "subagent_update"}}),
4590 json!({"update": {"sessionUpdate": "subagent_update", "sessionId": "child"}}),
4591 ] {
4592 assert!(serde_json::from_value::<SessionNotification>(malformed).is_err());
4593 }
4594 }
4595
4596 #[cfg(feature = "unstable_subagents")]
4597 #[test]
4598 fn test_subagent_states_round_trip() {
4599 use serde_json::json;
4600
4601 for (wire_state, expected) in [
4602 (
4603 json!({"state": "running"}),
4604 StateUpdate::Running(RunningStateUpdate::new()),
4605 ),
4606 (
4607 json!({"state": "idle", "stopReason": "end_turn"}),
4608 StateUpdate::Idle(IdleStateUpdate::new().stop_reason(IdleStopReason::EndTurn)),
4609 ),
4610 (
4611 json!({"state": "requires_action"}),
4612 StateUpdate::RequiresAction(RequiresActionStateUpdate::new()),
4613 ),
4614 (
4615 json!({"state": "unknown"}),
4616 StateUpdate::Unknown(UnknownStateUpdate::new()),
4617 ),
4618 (
4619 json!({"state": "_waiting", "detail": {"ticket": 7}}),
4620 StateUpdate::Other(OtherStateUpdate::new(
4621 "_waiting",
4622 [("detail".into(), json!({"ticket": 7}))].into(),
4623 )),
4624 ),
4625 (
4626 json!({"state": "paused", "retryAt": 42}),
4627 StateUpdate::Other(OtherStateUpdate::new(
4628 "paused",
4629 [("retryAt".into(), json!(42))].into(),
4630 )),
4631 ),
4632 ] {
4633 let wire = json!({
4634 "sessionUpdate": "subagent_update",
4635 "sessionId": "sess_child",
4636 "state": wire_state
4637 });
4638 let parsed: SessionUpdate = serde_json::from_value(wire.clone()).unwrap();
4639 let SessionUpdate::SubagentUpdate(update) = &parsed else {
4640 panic!("expected subagent update");
4641 };
4642 assert_eq!(update.state.value(), Some(&expected));
4643 assert_eq!(serde_json::to_value(parsed).unwrap(), wire);
4644 }
4645
4646 for reason in [
4647 "end_turn",
4648 "max_tokens",
4649 "max_turn_requests",
4650 "refusal",
4651 "cancelled",
4652 ] {
4653 let state: StateUpdate = serde_json::from_value(json!({
4654 "state": "idle", "stopReason": reason
4655 }))
4656 .unwrap();
4657 assert_eq!(serde_json::to_value(state).unwrap()["stopReason"], reason);
4658 }
4659
4660 for state in [
4663 StateUpdate::Running(RunningStateUpdate::new()),
4664 StateUpdate::RequiresAction(RequiresActionStateUpdate::new()),
4665 StateUpdate::Unknown(UnknownStateUpdate::new()),
4666 StateUpdate::Running(RunningStateUpdate::new()),
4667 StateUpdate::Idle(IdleStateUpdate::new().stop_reason(IdleStopReason::EndTurn)),
4668 StateUpdate::Unknown(UnknownStateUpdate::new()),
4669 StateUpdate::Running(RunningStateUpdate::new()),
4670 ] {
4671 let update = SubagentUpdate::new("sess_child").state(state.clone());
4672 let wire = serde_json::to_value(&update).unwrap();
4673 assert_eq!(wire["sessionId"], "sess_child");
4674 assert_eq!(
4675 serde_json::from_value::<SubagentUpdate>(wire)
4676 .unwrap()
4677 .state,
4678 MaybeUndefined::Value(state)
4679 );
4680 }
4681 assert_eq!(
4682 serde_json::to_value(
4683 SubagentUpdate::new("sess_child").state(StateUpdate::Idle(IdleStateUpdate::new()))
4684 )
4685 .unwrap()["state"],
4686 json!({"state": "idle"})
4687 );
4688 }
4689
4690 #[cfg(feature = "unstable_subagents")]
4691 #[test]
4692 fn subagent_idle_snapshot_reports_child_failure() {
4693 use serde_json::json;
4694
4695 let update = SubagentUpdate::new("sess_child").state(StateUpdate::Idle(
4696 IdleStateUpdate::new().stop_reason(IdleStopReason::from(
4697 ErrorStopReason::new().error(Error::auth_required()),
4698 )),
4699 ));
4700 let wire = json!({
4701 "sessionId": "sess_child",
4702 "state": {
4703 "state": "idle",
4704 "stopReason": "error",
4705 "error": { "code": -32000, "message": "Authentication required" }
4706 }
4707 });
4708 assert_eq!(serde_json::to_value(&update).unwrap(), wire);
4709 assert_eq!(
4710 serde_json::from_value::<SubagentUpdate>(wire).unwrap(),
4711 update
4712 );
4713
4714 let without_details = StateUpdate::Idle(
4716 IdleStateUpdate::new().stop_reason(IdleStopReason::from(ErrorStopReason::new())),
4717 );
4718 for error in [None, Some(json!(null)), Some(json!("failed"))] {
4719 let mut wire = json!({ "state": "idle", "stopReason": "error" });
4720 if let Some(error) = error {
4721 wire["error"] = error;
4722 }
4723 assert_eq!(
4724 serde_json::from_value::<StateUpdate>(wire).unwrap(),
4725 without_details
4726 );
4727 }
4728
4729 let parsed: StateUpdate = serde_json::from_value(json!({
4732 "state": "idle",
4733 "stopReason": "end_turn",
4734 "error": { "code": -32603, "message": "Internal error" }
4735 }))
4736 .unwrap();
4737 assert_eq!(
4738 parsed,
4739 StateUpdate::Idle(IdleStateUpdate::new().stop_reason(IdleStopReason::EndTurn))
4740 );
4741
4742 let meta: Meta = [("source".into(), json!("worker"))].into_iter().collect();
4745 for stop_reason in [None, Some(json!(null)), Some(json!(true))] {
4746 let mut wire = json!({ "state": "idle", "_meta": { "source": "worker" } });
4747 if let Some(stop_reason) = stop_reason {
4748 wire["stopReason"] = stop_reason;
4749 }
4750 assert_eq!(
4751 serde_json::from_value::<StateUpdate>(wire).unwrap(),
4752 StateUpdate::Idle(IdleStateUpdate::new().meta(meta.clone()))
4753 );
4754 }
4755
4756 for stop_reason in ["_paused", "not_a_reason"] {
4759 let wire = json!({
4760 "state": "idle",
4761 "stopReason": stop_reason,
4762 "resumeAfter": 30,
4763 "_meta": { "source": "worker" }
4764 });
4765 let parsed: StateUpdate = serde_json::from_value(wire.clone()).unwrap();
4766 assert_eq!(
4767 parsed,
4768 StateUpdate::Idle(
4769 IdleStateUpdate::new()
4770 .stop_reason(IdleStopReason::Other(OtherStopReason::new(
4771 stop_reason,
4772 [("resumeAfter".into(), json!(30))].into_iter().collect(),
4773 )))
4774 .meta(meta.clone())
4775 )
4776 );
4777 assert_eq!(serde_json::to_value(&parsed).unwrap(), wire);
4778 }
4779 }
4780
4781 #[cfg(feature = "unstable_subagents")]
4782 #[test]
4783 fn subagent_known_stop_reasons_are_never_read_as_other() {
4784 use serde_json::json;
4785
4786 let known = [
4787 IdleStopReason::EndTurn,
4788 IdleStopReason::MaxTokens,
4789 IdleStopReason::MaxTurnRequests,
4790 IdleStopReason::Refusal,
4791 IdleStopReason::Cancelled,
4792 IdleStopReason::Error(ErrorStopReason::new()),
4793 ];
4794 for stop_reason in &known {
4796 match stop_reason {
4797 IdleStopReason::EndTurn
4798 | IdleStopReason::MaxTokens
4799 | IdleStopReason::MaxTurnRequests
4800 | IdleStopReason::Refusal
4801 | IdleStopReason::Cancelled
4802 | IdleStopReason::Error(_)
4803 | IdleStopReason::Other(_) => {}
4804 }
4805 }
4806
4807 for stop_reason in known {
4808 let state = StateUpdate::Idle(IdleStateUpdate::new().stop_reason(stop_reason));
4809 let wire = serde_json::to_value(&state).unwrap();
4810 assert_eq!(
4811 serde_json::from_value::<StateUpdate>(wire.clone()).unwrap(),
4812 state
4813 );
4814 assert!(
4815 serde_json::from_value::<OtherStopReason>(
4816 json!({ "stopReason": wire["stopReason"] })
4817 )
4818 .is_err(),
4819 "{} must not be accepted as a custom stop reason",
4820 wire["stopReason"]
4821 );
4822 }
4823 }
4824
4825 #[cfg(feature = "unstable_subagents")]
4826 #[test]
4827 fn subagent_unknown_stop_reason_leaves_usage_on_the_snapshot() {
4828 use serde_json::json;
4829
4830 let usage = json!({ "totalTokens": 10, "inputTokens": 6, "outputTokens": 4 });
4831 let wire = json!({ "state": "idle", "stopReason": "_paused", "usage": usage });
4832 let StateUpdate::Idle(idle) = serde_json::from_value(wire).unwrap() else {
4833 panic!("expected idle state snapshot");
4834 };
4835 let Some(IdleStopReason::Other(other)) = &idle.stop_reason else {
4836 panic!("expected a custom stop reason");
4837 };
4838 assert!(other.fields.is_empty());
4839
4840 #[cfg(feature = "unstable_end_turn_token_usage")]
4841 {
4842 assert!(idle.usage.is_some());
4843 assert_eq!(serde_json::to_value(&idle).unwrap()["usage"], usage);
4844 }
4845 #[cfg(not(feature = "unstable_end_turn_token_usage"))]
4848 assert!(serde_json::to_value(&idle).unwrap().get("usage").is_none());
4849 }
4850
4851 #[cfg(feature = "unstable_subagents")]
4852 #[test]
4853 fn test_subagent_unknown_activity_metadata_and_capabilities() {
4854 use serde_json::json;
4855
4856 let update = SubagentUpdate::new("child").state(StateUpdate::Unknown(
4857 UnknownStateUpdate::new().meta(
4858 [("source".into(), json!("worker"))]
4859 .into_iter()
4860 .collect::<Meta>(),
4861 ),
4862 ));
4863 let wire = json!({
4864 "sessionId": "child",
4865 "state": {
4866 "state": "unknown",
4867 "_meta": { "source": "worker" }
4868 }
4869 });
4870 assert_eq!(serde_json::to_value(&update).unwrap(), wire);
4871 assert_eq!(
4872 serde_json::from_value::<SubagentUpdate>(wire).unwrap(),
4873 update
4874 );
4875 assert!(update.capabilities.is_undefined());
4877
4878 for meta in [json!(null), json!(false)] {
4879 let state: StateUpdate = serde_json::from_value(json!({
4880 "state": "unknown",
4881 "_meta": meta
4882 }))
4883 .unwrap();
4884 assert_eq!(state, StateUpdate::Unknown(UnknownStateUpdate::new()));
4885 assert_eq!(
4886 serde_json::to_value(state).unwrap(),
4887 json!({"state": "unknown"})
4888 );
4889 }
4890 assert!(serde_json::from_value::<OtherStateUpdate>(json!({"state": "unknown"})).is_err());
4892 }
4893
4894 #[cfg(feature = "unstable_subagents")]
4895 #[test]
4896 fn test_subagent_malformed_optional_state() {
4897 use serde_json::json;
4898
4899 for malformed in [json!("running"), json!({}), json!({"state": 4})] {
4900 assert!(serde_json::from_value::<StateUpdate>(malformed.clone()).is_err());
4901 let update: SubagentUpdate = serde_json::from_value(json!({
4902 "sessionId": "child", "state": malformed
4903 }))
4904 .unwrap();
4905 assert!(update.state.is_undefined());
4906 }
4907 let bad_reason: StateUpdate =
4908 serde_json::from_value(json!({"state": "idle", "stopReason": false})).unwrap();
4909 assert_eq!(bad_reason, StateUpdate::Idle(IdleStateUpdate::new()));
4910 let null_reason: StateUpdate =
4911 serde_json::from_value(json!({"state": "idle", "stopReason": null})).unwrap();
4912 assert_eq!(null_reason, StateUpdate::Idle(IdleStateUpdate::new()));
4913 }
4914
4915 #[cfg(all(feature = "unstable_subagents", feature = "unstable_protocol_v2"))]
4916 #[test]
4917 fn test_subagent_state_v2_wire_parity() {
4918 use serde_json::json;
4919
4920 for wire in [
4921 json!({"state": "running"}),
4922 json!({"state": "idle", "stopReason": "cancelled"}),
4923 json!({"state": "requires_action"}),
4924 json!({"state": "unknown"}),
4925 json!({"state": "unknown", "_meta": {"source": "worker"}}),
4926 json!({"state": "_waiting", "detail": {"ticket": 7}}),
4927 ] {
4928 let v1: StateUpdate = serde_json::from_value(wire.clone()).unwrap();
4929 let v2: crate::v2::StateUpdate = serde_json::from_value(wire.clone()).unwrap();
4930 assert_eq!(serde_json::to_value(v1).unwrap(), wire);
4931 assert_eq!(serde_json::to_value(v2).unwrap(), wire);
4932 }
4933 }
4934
4935 #[cfg(feature = "unstable_subagents")]
4936 #[test]
4937 fn test_subagent_capability_semantics() {
4938 use serde_json::json;
4939
4940 let capabilities =
4941 serde_json::to_value(ClientCapabilities::new().subagents(SubagentCapabilities::new()))
4942 .unwrap();
4943 assert_eq!(capabilities["subagents"], json!({}));
4944
4945 let omitted: ClientCapabilities = serde_json::from_value(json!({})).unwrap();
4946 assert!(omitted.subagents.is_none());
4947
4948 let null: ClientCapabilities =
4949 serde_json::from_value(json!({ "subagents": null })).unwrap();
4950 assert!(null.subagents.is_none());
4951 assert_eq!(null, omitted);
4952 let serialized = serde_json::to_value(null).unwrap();
4953 assert_eq!(serialized, serde_json::to_value(omitted).unwrap());
4954 assert!(serialized.get("subagents").is_none());
4955
4956 for wire in [
4957 json!({}),
4958 json!({"cancel": null}),
4959 json!({"cancel": true}),
4960 json!({"cancel": false}),
4961 ] {
4962 let child: SubagentSessionCapabilities = serde_json::from_value(wire).unwrap();
4963 assert!(child.cancel.is_none());
4964 assert_eq!(serde_json::to_value(child).unwrap(), json!({}));
4965 }
4966 let enabled = SubagentSessionCapabilities::new().cancel(SessionCancelCapabilities::new());
4967 assert_eq!(
4968 serde_json::to_value(&enabled).unwrap(),
4969 json!({"cancel": {}})
4970 );
4971 assert_eq!(
4972 serde_json::from_value::<SubagentSessionCapabilities>(json!({"cancel": {}})).unwrap(),
4973 enabled
4974 );
4975 let meta: Meta = [("source".into(), json!("worker"))].into_iter().collect();
4976 let with_meta =
4977 SubagentSessionCapabilities::new().cancel(SessionCancelCapabilities::new().meta(meta));
4978 let wire = json!({"cancel": {"_meta": {"source": "worker"}}});
4979 assert_eq!(serde_json::to_value(&with_meta).unwrap(), wire);
4980 assert_eq!(
4981 serde_json::from_value::<SubagentSessionCapabilities>(wire).unwrap(),
4982 with_meta
4983 );
4984 let removed = SubagentSessionCapabilities::new();
4986 assert_eq!(serde_json::to_value(&removed).unwrap(), json!({}));
4987 assert!(removed.cancel.is_none());
4988 assert!(enabled.cancel.is_some());
4989 assert!(enabled.cancel(None).cancel.is_none());
4990 let replacement = SubagentUpdate::new("child").capabilities(removed);
4991 assert_eq!(
4992 serde_json::to_value(replacement).unwrap(),
4993 json!({"sessionId": "child", "capabilities": {}})
4994 );
4995 }
4996
4997 #[cfg(all(feature = "unstable_subagents", feature = "schemars"))]
4998 #[test]
4999 fn test_subagent_capability_schema_is_optional_and_nullable() {
5000 use serde_json::json;
5001
5002 let schema = serde_json::to_value(schemars::schema_for!(ClientCapabilities)).unwrap();
5003 let variants = schema["properties"]["subagents"]["anyOf"]
5004 .as_array()
5005 .expect("subagents must allow the capability object or null");
5006 assert!(variants.contains(&json!({"type": "null"})));
5007 assert!(
5008 !schema["required"]
5009 .as_array()
5010 .is_some_and(|required| required.contains(&json!("subagents")))
5011 );
5012 let child =
5013 serde_json::to_value(schemars::schema_for!(SubagentSessionCapabilities)).unwrap();
5014 let variants = child["properties"]["cancel"]["anyOf"]
5015 .as_array()
5016 .expect("cancel must allow the capability object or null");
5017 assert!(variants.contains(&json!({"type": "null"})));
5018 assert!(
5019 variants
5020 .iter()
5021 .any(|variant| variant["type"] == "object" || variant.get("$ref").is_some())
5022 );
5023 assert!(!variants.iter().any(|variant| variant["type"] == "boolean"));
5024 let cancel =
5025 serde_json::to_value(schemars::schema_for!(SessionCancelCapabilities)).unwrap();
5026 assert_eq!(cancel["type"], "object");
5027 assert!(
5028 !child["required"]
5029 .as_array()
5030 .is_some_and(|required| required.contains(&json!("cancel")))
5031 );
5032 }
5033
5034 #[test]
5035 fn test_elicitation_capability_semantics() {
5036 use serde_json::json;
5037
5038 let unsupported: ClientCapabilities = serde_json::from_value(json!({})).unwrap();
5039 assert!(unsupported.elicitation.is_none());
5040
5041 let null: ClientCapabilities =
5042 serde_json::from_value(json!({ "elicitation": null })).unwrap();
5043 assert!(null.elicitation.is_none());
5044
5045 let malformed: ClientCapabilities =
5046 serde_json::from_value(json!({ "elicitation": false })).unwrap();
5047 assert!(malformed.elicitation.is_none());
5048
5049 let empty: ClientCapabilities =
5050 serde_json::from_value(json!({ "elicitation": {} })).unwrap();
5051 let empty = empty.elicitation.expect("present capability");
5052 assert!(!empty.supports_form());
5053 assert!(!empty.supports_url());
5054
5055 let form_only: ClientCapabilities = serde_json::from_value(json!({
5056 "elicitation": { "form": {} }
5057 }))
5058 .unwrap();
5059 let form_only = form_only.elicitation.expect("advertised capability");
5060 assert!(form_only.supports_form());
5061 assert!(!form_only.supports_url());
5062
5063 let url_only: ClientCapabilities = serde_json::from_value(json!({
5064 "elicitation": { "url": {} }
5065 }))
5066 .unwrap();
5067 let url_only = url_only.elicitation.expect("advertised capability");
5068 assert!(!url_only.supports_form());
5069 assert!(url_only.supports_url());
5070
5071 let both: ClientCapabilities = serde_json::from_value(json!({
5072 "elicitation": { "form": {}, "url": {} }
5073 }))
5074 .unwrap();
5075 let both = both.elicitation.expect("advertised capability");
5076 assert!(both.supports_form());
5077 assert!(both.supports_url());
5078 }
5079
5080 #[test]
5081 fn test_elicitation_method_routing_and_envelopes() {
5082 use serde_json::json;
5083
5084 assert_eq!(CLIENT_METHOD_NAMES.elicitation_create, "elicitation/create");
5085 assert_eq!(
5086 CLIENT_METHOD_NAMES.elicitation_complete,
5087 "elicitation/complete"
5088 );
5089
5090 let request = AgentRequest::CreateElicitationRequest(CreateElicitationRequest::new(
5091 crate::v1::ElicitationFormMode::new(
5092 crate::v1::ElicitationSessionScope::new("sess_1"),
5093 crate::v1::ElicitationSchema::new(),
5094 ),
5095 "Choose a value",
5096 ));
5097 assert_eq!(request.method(), "elicitation/create");
5098 let method = Arc::from(request.method());
5099 let request = crate::v1::JsonRpcMessage::wrap(crate::v1::Request {
5100 id: crate::v1::RequestId::Number(7),
5101 method,
5102 params: Some(request),
5103 });
5104 assert_eq!(
5105 serde_json::to_value(request).unwrap(),
5106 json!({
5107 "jsonrpc": "2.0",
5108 "id": 7,
5109 "method": "elicitation/create",
5110 "params": {
5111 "mode": "form",
5112 "sessionId": "sess_1",
5113 "message": "Choose a value",
5114 "requestedSchema": { "type": "object", "properties": {} }
5115 }
5116 })
5117 );
5118
5119 let notification = AgentNotification::CompleteElicitationNotification(
5120 CompleteElicitationNotification::new("elic_1"),
5121 );
5122 assert_eq!(notification.method(), "elicitation/complete");
5123 let method = Arc::from(notification.method());
5124 let notification = crate::v1::JsonRpcMessage::wrap(crate::v1::Notification {
5125 method,
5126 params: Some(notification),
5127 });
5128 assert_eq!(
5129 serde_json::to_value(notification).unwrap(),
5130 json!({
5131 "jsonrpc": "2.0",
5132 "method": "elicitation/complete",
5133 "params": { "elicitationId": "elic_1" }
5134 })
5135 );
5136 }
5137
5138 #[test]
5139 fn test_client_capabilities_default_on_malformed_values() {
5140 use serde_json::json;
5141
5142 let capabilities: ClientCapabilities = serde_json::from_value(json!({
5143 "fs": {
5144 "readTextFile": "yes",
5145 "writeTextFile": true
5146 },
5147 "terminal": {}
5148 }))
5149 .unwrap();
5150
5151 assert!(!capabilities.fs.read_text_file);
5152 assert!(capabilities.fs.write_text_file);
5153 assert!(!capabilities.terminal);
5154
5155 let capabilities: ClientCapabilities = serde_json::from_value(json!({
5156 "fs": false
5157 }))
5158 .unwrap();
5159 assert_eq!(capabilities.fs, FileSystemCapabilities::default());
5160
5161 {
5162 let capabilities: ClientCapabilities = serde_json::from_value(json!({
5163 "auth": false
5164 }))
5165 .unwrap();
5166 assert_eq!(capabilities.auth, AuthCapabilities::default());
5167
5168 let capabilities: AuthCapabilities = serde_json::from_value(json!({
5169 "terminal": {}
5170 }))
5171 .unwrap();
5172 assert!(!capabilities.terminal);
5173 }
5174 }
5175
5176 #[test]
5177 fn test_serialization_behavior() {
5178 use serde_json::json;
5179
5180 assert_eq!(
5181 serde_json::from_value::<SessionInfoUpdate>(json!({})).unwrap(),
5182 SessionInfoUpdate {
5183 title: MaybeUndefined::Undefined,
5184 updated_at: MaybeUndefined::Undefined,
5185 meta: None
5186 }
5187 );
5188 assert_eq!(
5189 serde_json::from_value::<SessionInfoUpdate>(json!({"title": null, "updatedAt": null}))
5190 .unwrap(),
5191 SessionInfoUpdate {
5192 title: MaybeUndefined::Null,
5193 updated_at: MaybeUndefined::Null,
5194 meta: None
5195 }
5196 );
5197 assert_eq!(
5198 serde_json::from_value::<SessionInfoUpdate>(
5199 json!({"title": "title", "updatedAt": "timestamp"})
5200 )
5201 .unwrap(),
5202 SessionInfoUpdate {
5203 title: MaybeUndefined::Value("title".to_string()),
5204 updated_at: MaybeUndefined::Value("timestamp".to_string()),
5205 meta: None
5206 }
5207 );
5208
5209 assert_eq!(
5210 serde_json::to_value(SessionInfoUpdate::new()).unwrap(),
5211 json!({})
5212 );
5213 assert_eq!(
5214 serde_json::to_value(SessionInfoUpdate::new().title("title")).unwrap(),
5215 json!({"title": "title"})
5216 );
5217 assert_eq!(
5218 serde_json::to_value(SessionInfoUpdate::new().title(None)).unwrap(),
5219 json!({"title": null})
5220 );
5221 assert_eq!(
5222 serde_json::to_value(
5223 SessionInfoUpdate::new()
5224 .title("title")
5225 .title(MaybeUndefined::Undefined)
5226 )
5227 .unwrap(),
5228 json!({})
5229 );
5230 }
5231
5232 #[test]
5233 fn test_content_chunk_message_id_serialization() {
5234 use serde_json::json;
5235
5236 assert_eq!(
5237 serde_json::to_value(SessionUpdate::AgentMessageChunk(ContentChunk::new(
5238 ContentBlock::Text(crate::v1::TextContent::new("Hello"))
5239 )))
5240 .unwrap(),
5241 json!({
5242 "sessionUpdate": "agent_message_chunk",
5243 "content": {
5244 "type": "text",
5245 "text": "Hello"
5246 }
5247 })
5248 );
5249
5250 assert_eq!(
5251 serde_json::to_value(SessionUpdate::AgentMessageChunk(
5252 ContentChunk::new(ContentBlock::Text(crate::v1::TextContent::new("Hello")))
5253 .message_id("msg_agent_c42b9")
5254 ))
5255 .unwrap(),
5256 json!({
5257 "sessionUpdate": "agent_message_chunk",
5258 "messageId": "msg_agent_c42b9",
5259 "content": {
5260 "type": "text",
5261 "text": "Hello"
5262 }
5263 })
5264 );
5265
5266 let SessionUpdate::AgentMessageChunk(chunk) = serde_json::from_value(json!({
5267 "sessionUpdate": "agent_message_chunk",
5268 "messageId": null,
5269 "content": {
5270 "type": "text",
5271 "text": "Hello"
5272 }
5273 }))
5274 .unwrap() else {
5275 panic!("expected agent message chunk");
5276 };
5277
5278 assert_eq!(chunk.message_id, None);
5279 }
5280
5281 #[test]
5282 fn test_usage_update_serialization() {
5283 use serde_json::json;
5284
5285 assert_eq!(
5286 serde_json::to_value(SessionUpdate::UsageUpdate(UsageUpdate::new(
5287 53_000, 200_000
5288 )))
5289 .unwrap(),
5290 json!({
5291 "sessionUpdate": "usage_update",
5292 "used": 53000,
5293 "size": 200_000
5294 })
5295 );
5296
5297 assert_eq!(
5298 serde_json::to_value(SessionUpdate::UsageUpdate(
5299 UsageUpdate::new(53_000, 200_000).cost(Cost::new(0.045, "USD"))
5300 ))
5301 .unwrap(),
5302 json!({
5303 "sessionUpdate": "usage_update",
5304 "used": 53000,
5305 "size": 200_000,
5306 "cost": {
5307 "amount": 0.045,
5308 "currency": "USD"
5309 }
5310 })
5311 );
5312
5313 let SessionUpdate::UsageUpdate(update) = serde_json::from_value(json!({
5314 "sessionUpdate": "usage_update",
5315 "used": 53000,
5316 "size": 200_000,
5317 "cost": null
5318 }))
5319 .unwrap() else {
5320 panic!("expected usage update");
5321 };
5322
5323 assert_eq!(update.cost, None);
5324 }
5325
5326 #[test]
5327 fn test_client_capabilities_boolean_config_options_serialization() {
5328 use serde_json::json;
5329
5330 let capabilities = ClientCapabilities::new().session(
5331 ClientSessionCapabilities::new().config_options(
5332 SessionConfigOptionsCapabilities::new()
5333 .boolean(BooleanConfigOptionCapabilities::new()),
5334 ),
5335 );
5336 let json = serde_json::to_value(&capabilities).unwrap();
5337
5338 assert_eq!(json["session"]["configOptions"]["boolean"], json!({}));
5339
5340 let omitted: ClientCapabilities = serde_json::from_value(json!({})).unwrap();
5341 assert!(omitted.session.is_none());
5342
5343 let null_session: ClientCapabilities = serde_json::from_value(json!({
5344 "session": null
5345 }))
5346 .unwrap();
5347 assert!(null_session.session.is_none());
5348
5349 let null_config_options: ClientCapabilities = serde_json::from_value(json!({
5350 "session": {
5351 "configOptions": null
5352 }
5353 }))
5354 .unwrap();
5355 assert!(
5356 null_config_options
5357 .session
5358 .and_then(|session| session.config_options)
5359 .is_none()
5360 );
5361
5362 let null_boolean: ClientCapabilities = serde_json::from_value(json!({
5363 "session": {
5364 "configOptions": {
5365 "boolean": null
5366 }
5367 }
5368 }))
5369 .unwrap();
5370 assert!(
5371 null_boolean
5372 .session
5373 .and_then(|session| session.config_options)
5374 .and_then(|config_options| config_options.boolean)
5375 .is_none()
5376 );
5377 }
5378
5379 #[cfg(feature = "unstable_plan_operations")]
5380 #[test]
5381 fn test_plan_operations_serialization() {
5382 use serde_json::json;
5383
5384 use crate::v1::{PlanEntry, PlanEntryPriority, PlanEntryStatus, PlanUpdateContent};
5385
5386 let plan_update = SessionUpdate::PlanUpdate(PlanUpdate::new(PlanUpdateContent::items(
5387 "plan-1",
5388 vec![PlanEntry::new(
5389 "Step 1",
5390 PlanEntryPriority::High,
5391 PlanEntryStatus::Pending,
5392 )],
5393 )));
5394
5395 assert_eq!(
5396 serde_json::to_value(plan_update).unwrap(),
5397 json!({
5398 "sessionUpdate": "plan_update",
5399 "plan": {
5400 "type": "items",
5401 "planId": "plan-1",
5402 "entries": [
5403 {
5404 "content": "Step 1",
5405 "priority": "high",
5406 "status": "pending"
5407 }
5408 ]
5409 }
5410 })
5411 );
5412
5413 assert_eq!(
5414 serde_json::to_value(SessionUpdate::PlanRemoved(PlanRemoved::new("plan-1"))).unwrap(),
5415 json!({
5416 "sessionUpdate": "plan_removed",
5417 "planId": "plan-1"
5418 })
5419 );
5420
5421 let capabilities = ClientCapabilities::new().plan(PlanCapabilities::new());
5422 let json = serde_json::to_value(&capabilities).unwrap();
5423 assert_eq!(json["plan"], json!({}));
5424
5425 assert_eq!(
5426 serde_json::from_value::<ClientCapabilities>(json!({ "plan": null }))
5427 .unwrap()
5428 .plan,
5429 None
5430 );
5431 }
5432
5433 #[cfg(feature = "unstable_mcp_over_acp")]
5434 #[test]
5435 fn test_agent_mcp_request_method_names() {
5436 use serde_json::json;
5437
5438 let params: serde_json::Map<String, serde_json::Value> =
5439 [("cursor".to_string(), json!("abc"))].into_iter().collect();
5440
5441 assert_eq!(CLIENT_METHOD_NAMES.mcp_message, "mcp/message");
5442 assert_eq!(
5443 AgentRequest::MessageMcpRequest(MessageMcpRequest::new(
5444 "server-1",
5445 "req-1",
5446 "tools/list"
5447 ))
5448 .method(),
5449 "mcp/message"
5450 );
5451 assert_eq!(
5452 serde_json::to_value(
5453 MessageMcpRequest::new("server-1", "req-1", "tools/list").params(params)
5454 )
5455 .unwrap(),
5456 json!({
5457 "serverId": "server-1",
5458 "requestId": "req-1",
5459 "method": "tools/list",
5460 "params": { "cursor": "abc" }
5461 })
5462 );
5463
5464 let request_with_null_params: MessageMcpRequest = serde_json::from_value(json!({
5465 "serverId": "server-1",
5466 "requestId": "req-1",
5467 "method": "tools/list",
5468 "params": null,
5469 "_meta": null
5470 }))
5471 .unwrap();
5472 assert_eq!(request_with_null_params.params, None);
5473 assert_eq!(request_with_null_params.meta, None);
5474 for key in ["serverId", "requestId", "method"] {
5475 let mut value =
5476 json!({"serverId":"server-1", "requestId":"req-1", "method":"tools/list"});
5477 value.as_object_mut().unwrap().remove(key);
5478 assert!(serde_json::from_value::<MessageMcpRequest>(value).is_err());
5479 }
5480 for key in ["serverId", "requestId", "method"] {
5481 let mut value =
5482 json!({"serverId":"server-1", "requestId":"req-1", "method":"tools/list"});
5483 value[key] = serde_json::Value::Null;
5484 assert!(serde_json::from_value::<MessageMcpRequest>(value).is_err());
5485 }
5486 }
5487
5488 #[test]
5489 fn request_permission_request_rejects_malformed_options() {
5490 use serde_json::json;
5491
5492 assert!(
5493 serde_json::from_value::<RequestPermissionRequest>(json!({
5494 "sessionId": "sess-1",
5495 "toolCall": {"toolCallId": "tc-1"},
5496 "options": "not-an-array"
5497 }))
5498 .is_err()
5499 );
5500 assert!(
5501 serde_json::from_value::<RequestPermissionRequest>(json!({
5502 "sessionId": "sess-1",
5503 "toolCall": {"toolCallId": "tc-1"},
5504 "options": [{"optionId": "allow"}]
5505 }))
5506 .is_err()
5507 );
5508 }
5509
5510 #[test]
5511 fn create_terminal_request_rejects_malformed_fields() {
5512 use serde_json::json;
5513
5514 let request = |field: &str, value: serde_json::Value| {
5515 let mut request = json!({"sessionId": "sess-1", "command": "npx"});
5516 request[field] = value;
5517 serde_json::from_value::<CreateTerminalRequest>(request)
5518 };
5519
5520 for (field, value) in [
5522 ("args", json!(["serve", "--port", 3000])),
5523 ("args", json!("serve --port 3000")),
5524 ("env", json!([{"name": "PORT", "value": 3000}])),
5525 ("env", json!({"PORT": "3000"})),
5526 ("cwd", json!({"path": "/repo"})),
5527 ("outputByteLimit", json!("1048576")),
5528 ] {
5529 assert!(request(field, value.clone()).is_err(), "{field}: {value}");
5530 }
5531
5532 for field in ["args", "env", "cwd", "outputByteLimit"] {
5533 assert_eq!(
5534 request(field, serde_json::Value::Null).unwrap(),
5535 CreateTerminalRequest::new("sess-1", "npx"),
5536 "{field}"
5537 );
5538 }
5539 }
5540
5541 #[test]
5542 fn read_text_file_request_rejects_malformed_range() {
5543 use serde_json::json;
5544
5545 let request = |field: &str, value: serde_json::Value| {
5546 let mut request = json!({"sessionId": "sess-1", "path": "/repo/src/main.rs"});
5547 request[field] = value;
5548 serde_json::from_value::<ReadTextFileRequest>(request)
5549 };
5550
5551 for field in ["line", "limit"] {
5553 assert!(request(field, json!("120")).is_err(), "{field}");
5554 assert!(request(field, json!(-1)).is_err(), "{field}");
5555 assert_eq!(
5556 request(field, serde_json::Value::Null).unwrap(),
5557 ReadTextFileRequest::new("sess-1", "/repo/src/main.rs"),
5558 "{field}"
5559 );
5560 }
5561 }
5562}