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")]
956#[serde_as]
957#[skip_serializing_none]
958#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
959#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
960#[serde(rename_all = "camelCase")]
961#[non_exhaustive]
962pub struct SubagentUpdate {
963 pub session_id: SessionId,
965 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
970 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
971 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
972 pub title: MaybeUndefined<String>,
973 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
979 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
980 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
981 pub description: MaybeUndefined<String>,
982 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
989 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
990 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
991 pub capabilities: MaybeUndefined<SubagentSessionCapabilities>,
992 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
999 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1000 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
1001 pub state: MaybeUndefined<StateUpdate>,
1002 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
1009 pub cwd: MaybeUndefined<PathBuf>,
1010 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
1018 pub additional_directories: MaybeUndefined<Vec<PathBuf>>,
1019 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
1026 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1027 #[serde(
1028 default,
1029 rename = "_meta",
1030 skip_serializing_if = "MaybeUndefined::is_undefined"
1031 )]
1032 pub meta: MaybeUndefined<Meta>,
1033}
1034
1035#[cfg(feature = "unstable_subagents")]
1036impl SubagentUpdate {
1037 #[must_use]
1039 pub fn new(session_id: impl Into<SessionId>) -> Self {
1040 Self {
1041 session_id: session_id.into(),
1042 title: MaybeUndefined::Undefined,
1043 description: MaybeUndefined::Undefined,
1044 capabilities: MaybeUndefined::Undefined,
1045 state: MaybeUndefined::Undefined,
1046 cwd: MaybeUndefined::Undefined,
1047 additional_directories: MaybeUndefined::Undefined,
1048 meta: MaybeUndefined::Undefined,
1049 }
1050 }
1051
1052 #[must_use]
1054 pub fn title(mut self, title: impl IntoMaybeUndefined<String>) -> Self {
1055 self.title = title.into_maybe_undefined();
1056 self
1057 }
1058
1059 #[must_use]
1061 pub fn description(mut self, description: impl IntoMaybeUndefined<String>) -> Self {
1062 self.description = description.into_maybe_undefined();
1063 self
1064 }
1065
1066 #[must_use]
1068 pub fn capabilities(
1069 mut self,
1070 capabilities: impl IntoMaybeUndefined<SubagentSessionCapabilities>,
1071 ) -> Self {
1072 self.capabilities = capabilities.into_maybe_undefined();
1073 self
1074 }
1075
1076 #[must_use]
1078 pub fn state(mut self, state: impl IntoMaybeUndefined<StateUpdate>) -> Self {
1079 self.state = state.into_maybe_undefined();
1080 self
1081 }
1082
1083 #[must_use]
1087 pub fn cwd(mut self, cwd: impl IntoMaybeUndefined<PathBuf>) -> Self {
1088 self.cwd = cwd.into_maybe_undefined();
1089 self
1090 }
1091
1092 #[must_use]
1096 pub fn additional_directories(
1097 mut self,
1098 additional_directories: impl IntoMaybeUndefined<Vec<PathBuf>>,
1099 ) -> Self {
1100 self.additional_directories = additional_directories.into_maybe_undefined();
1101 self
1102 }
1103
1104 #[must_use]
1108 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
1109 self.meta = meta.into_maybe_undefined();
1110 self
1111 }
1112}
1113
1114#[cfg(feature = "unstable_subagents")]
1123#[serde_as]
1124#[skip_serializing_none]
1125#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1126#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1127#[serde(rename_all = "camelCase")]
1128#[non_exhaustive]
1129pub struct SubagentSessionCapabilities {
1130 #[serde_as(deserialize_as = "DefaultOnError")]
1134 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1135 #[serde(default)]
1136 pub cancel: Option<SessionCancelCapabilities>,
1137 #[serde_as(deserialize_as = "DefaultOnError")]
1141 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1142 #[serde(default)]
1143 #[serde(rename = "_meta")]
1144 pub meta: Option<Meta>,
1145}
1146
1147#[cfg(feature = "unstable_subagents")]
1148impl SubagentSessionCapabilities {
1149 #[must_use]
1151 pub fn new() -> Self {
1152 Self::default()
1153 }
1154
1155 #[must_use]
1157 pub fn cancel(mut self, cancel: impl IntoOption<SessionCancelCapabilities>) -> Self {
1158 self.cancel = cancel.into_option();
1159 self
1160 }
1161
1162 #[must_use]
1166 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1167 self.meta = meta.into_option();
1168 self
1169 }
1170}
1171
1172#[cfg(feature = "unstable_subagents")]
1180#[serde_as]
1181#[skip_serializing_none]
1182#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1183#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1184#[non_exhaustive]
1185pub struct SessionCancelCapabilities {
1186 #[serde_as(deserialize_as = "DefaultOnError")]
1192 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1193 #[serde(default)]
1194 #[serde(rename = "_meta")]
1195 pub meta: Option<Meta>,
1196}
1197
1198#[cfg(feature = "unstable_subagents")]
1199impl SessionCancelCapabilities {
1200 #[must_use]
1202 pub fn new() -> Self {
1203 Self::default()
1204 }
1205
1206 #[must_use]
1212 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1213 self.meta = meta.into_option();
1214 self
1215 }
1216}
1217
1218#[cfg(feature = "unstable_subagents")]
1228#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1229#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1230#[serde(tag = "state", rename_all = "snake_case")]
1231#[non_exhaustive]
1232pub enum StateUpdate {
1233 Running(RunningStateUpdate),
1235 Idle(IdleStateUpdate),
1237 RequiresAction(RequiresActionStateUpdate),
1239 Unknown(UnknownStateUpdate),
1243 #[serde(untagged)]
1248 Other(OtherStateUpdate),
1249}
1250
1251#[cfg(feature = "unstable_subagents")]
1253#[serde_as]
1254#[skip_serializing_none]
1255#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1256#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1257#[serde(rename_all = "camelCase")]
1258#[non_exhaustive]
1259pub struct RunningStateUpdate {
1260 #[serde_as(deserialize_as = "DefaultOnError")]
1264 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1265 #[serde(default, rename = "_meta")]
1266 pub meta: Option<Meta>,
1267}
1268
1269#[cfg(feature = "unstable_subagents")]
1270impl RunningStateUpdate {
1271 #[must_use]
1273 pub fn new() -> Self {
1274 Self::default()
1275 }
1276
1277 #[must_use]
1279 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1280 self.meta = meta.into_option();
1281 self
1282 }
1283}
1284
1285#[cfg(feature = "unstable_subagents")]
1289#[serde_as]
1290#[skip_serializing_none]
1291#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1292#[cfg_attr(feature = "schemars", schemars(transform = idle_state_update_schema))]
1293#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1294#[serde(rename_all = "camelCase")]
1295#[non_exhaustive]
1296pub struct IdleStateUpdate {
1297 #[serde(flatten)]
1299 pub stop_reason: Option<IdleStopReason>,
1300 #[cfg(feature = "unstable_end_turn_token_usage")]
1304 #[serde_as(deserialize_as = "DefaultOnError")]
1305 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1306 #[serde(default)]
1307 pub usage: Option<Box<Usage>>,
1308 #[serde_as(deserialize_as = "DefaultOnError")]
1312 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1313 #[serde(default, rename = "_meta")]
1314 pub meta: Option<Meta>,
1315}
1316
1317#[cfg(feature = "unstable_subagents")]
1318impl IdleStateUpdate {
1319 #[must_use]
1321 pub fn new() -> Self {
1322 Self::default()
1323 }
1324
1325 #[must_use]
1327 pub fn stop_reason(mut self, stop_reason: impl IntoOption<IdleStopReason>) -> Self {
1328 self.stop_reason = stop_reason.into_option();
1329 self
1330 }
1331
1332 #[cfg(feature = "unstable_end_turn_token_usage")]
1334 #[must_use]
1335 pub fn usage(mut self, usage: impl IntoOption<Usage>) -> Self {
1336 self.usage = usage.into_option().map(Box::new);
1337 self
1338 }
1339
1340 #[must_use]
1342 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1343 self.meta = meta.into_option();
1344 self
1345 }
1346}
1347
1348#[cfg(all(feature = "unstable_subagents", feature = "schemars"))]
1357fn idle_state_update_schema(schema: &mut Schema) {
1358 if let Some(properties) = schema
1359 .get_mut("properties")
1360 .and_then(serde_json::Value::as_object_mut)
1361 {
1362 properties.insert(
1363 "stopReason".into(),
1364 serde_json::json!({
1365 "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)",
1366 "type": ["string", "null"],
1367 "x-deserialize-default-on-error": true
1368 }),
1369 );
1370 }
1371 let Some(variants) = schema
1372 .get_mut("anyOf")
1373 .and_then(serde_json::Value::as_array_mut)
1374 else {
1375 return;
1376 };
1377 for variant in variants {
1378 if variant.as_object().is_some_and(serde_json::Map::is_empty) {
1379 *variant = serde_json::json!({
1380 "title": "none",
1381 "description": "No stop reason: `stopReason` is omitted or `null`.",
1382 "type": "object",
1383 "properties": {
1384 "stopReason": { "type": "null" }
1385 }
1386 });
1387 }
1388 }
1389}
1390
1391#[cfg(feature = "unstable_subagents")]
1404#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1405#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1406#[serde(tag = "stopReason", rename_all = "snake_case")]
1407#[non_exhaustive]
1408pub enum IdleStopReason {
1409 EndTurn,
1411 MaxTokens,
1413 MaxTurnRequests,
1416 Refusal,
1418 Cancelled,
1420 Error(ErrorStopReason),
1422 #[serde(untagged)]
1427 Other(OtherStopReason),
1428}
1429
1430#[cfg(feature = "unstable_subagents")]
1431impl From<ErrorStopReason> for IdleStopReason {
1432 fn from(value: ErrorStopReason) -> Self {
1433 Self::Error(value)
1434 }
1435}
1436
1437#[cfg(feature = "unstable_subagents")]
1443#[serde_as]
1444#[skip_serializing_none]
1445#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1446#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1447#[serde(rename_all = "camelCase")]
1448#[non_exhaustive]
1449pub struct ErrorStopReason {
1450 #[serde_as(deserialize_as = "DefaultOnError")]
1455 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1456 #[serde(default)]
1457 pub error: Option<Box<Error>>,
1458}
1459
1460#[cfg(feature = "unstable_subagents")]
1461impl ErrorStopReason {
1462 #[must_use]
1464 pub fn new() -> Self {
1465 Self::default()
1466 }
1467
1468 #[must_use]
1470 pub fn error(mut self, error: impl IntoOption<Error>) -> Self {
1471 self.error = error.into_option().map(Box::new);
1472 self
1473 }
1474}
1475
1476#[cfg(feature = "unstable_subagents")]
1482#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1483#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1484#[cfg_attr(feature = "schemars", schemars(inline))]
1485#[cfg_attr(feature = "schemars", schemars(transform = other_stop_reason_schema))]
1486#[serde(rename_all = "camelCase")]
1487#[non_exhaustive]
1488pub struct OtherStopReason {
1489 pub stop_reason: String,
1491 #[serde(flatten)]
1493 pub fields: BTreeMap<String, serde_json::Value>,
1494}
1495
1496#[cfg(feature = "unstable_subagents")]
1497impl OtherStopReason {
1498 #[must_use]
1500 pub fn new(
1501 stop_reason: impl Into<String>,
1502 mut fields: BTreeMap<String, serde_json::Value>,
1503 ) -> Self {
1504 remove_idle_state_update_fields(&mut fields);
1505 Self {
1506 stop_reason: stop_reason.into(),
1507 fields,
1508 }
1509 }
1510}
1511
1512#[cfg(feature = "unstable_subagents")]
1513impl<'de> Deserialize<'de> for OtherStopReason {
1514 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1515 where
1516 D: serde::Deserializer<'de>,
1517 {
1518 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1519 let stop_reason = fields
1520 .remove("stopReason")
1521 .ok_or_else(|| serde::de::Error::missing_field("stopReason"))?;
1522 let serde_json::Value::String(stop_reason) = stop_reason else {
1523 return Err(serde::de::Error::custom("`stopReason` must be a string"));
1524 };
1525 if KNOWN_STOP_REASONS.contains(&stop_reason.as_str()) {
1526 return Err(serde::de::Error::custom(format!(
1527 "known stop reason `{stop_reason}` did not match its schema"
1528 )));
1529 }
1530 remove_idle_state_update_fields(&mut fields);
1531 Ok(Self {
1532 stop_reason,
1533 fields,
1534 })
1535 }
1536}
1537
1538#[cfg(feature = "unstable_subagents")]
1539const KNOWN_STOP_REASONS: &[&str] = &[
1540 "end_turn",
1541 "max_tokens",
1542 "max_turn_requests",
1543 "refusal",
1544 "cancelled",
1545 "error",
1546];
1547
1548#[cfg(feature = "unstable_subagents")]
1550fn remove_idle_state_update_fields(fields: &mut BTreeMap<String, serde_json::Value>) {
1551 fields.remove("stopReason");
1552 fields.remove("usage");
1553 fields.remove("_meta");
1554}
1555
1556#[cfg(all(feature = "unstable_subagents", feature = "schemars"))]
1557fn other_stop_reason_schema(schema: &mut Schema) {
1558 let known = KNOWN_STOP_REASONS
1559 .iter()
1560 .map(|stop_reason| {
1561 serde_json::json!({
1562 "properties": { "stopReason": { "const": stop_reason, "type": "string" } },
1563 "required": ["stopReason"],
1564 "type": "object"
1565 })
1566 })
1567 .collect::<Vec<_>>();
1568 schema.insert("not".into(), serde_json::json!({ "anyOf": known }));
1569}
1570
1571#[cfg(feature = "unstable_subagents")]
1573#[serde_as]
1574#[skip_serializing_none]
1575#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1576#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1577#[serde(rename_all = "camelCase")]
1578#[non_exhaustive]
1579pub struct RequiresActionStateUpdate {
1580 #[serde_as(deserialize_as = "DefaultOnError")]
1584 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1585 #[serde(default, rename = "_meta")]
1586 pub meta: Option<Meta>,
1587}
1588
1589#[cfg(feature = "unstable_subagents")]
1590impl RequiresActionStateUpdate {
1591 #[must_use]
1593 pub fn new() -> Self {
1594 Self::default()
1595 }
1596
1597 #[must_use]
1599 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1600 self.meta = meta.into_option();
1601 self
1602 }
1603}
1604
1605#[cfg(feature = "unstable_subagents")]
1619#[serde_as]
1620#[skip_serializing_none]
1621#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1622#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1623#[serde(rename_all = "camelCase")]
1624#[non_exhaustive]
1625pub struct UnknownStateUpdate {
1626 #[serde_as(deserialize_as = "DefaultOnError")]
1630 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1631 #[serde(default, rename = "_meta")]
1632 pub meta: Option<Meta>,
1633}
1634
1635#[cfg(feature = "unstable_subagents")]
1636impl UnknownStateUpdate {
1637 #[must_use]
1639 pub fn new() -> Self {
1640 Self::default()
1641 }
1642
1643 #[must_use]
1645 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1646 self.meta = meta.into_option();
1647 self
1648 }
1649}
1650
1651#[cfg(feature = "unstable_subagents")]
1653#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1654#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
1655#[cfg_attr(feature = "schemars", schemars(inline))]
1656#[cfg_attr(feature = "schemars", schemars(transform = other_state_update_schema))]
1657#[serde(rename_all = "camelCase")]
1658#[non_exhaustive]
1659pub struct OtherStateUpdate {
1660 pub state: String,
1662 #[serde(flatten)]
1664 pub fields: BTreeMap<String, serde_json::Value>,
1665}
1666
1667#[cfg(feature = "unstable_subagents")]
1668impl OtherStateUpdate {
1669 #[must_use]
1671 pub fn new(state: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
1672 fields.remove("state");
1673 Self {
1674 state: state.into(),
1675 fields,
1676 }
1677 }
1678}
1679
1680#[cfg(feature = "unstable_subagents")]
1681impl<'de> Deserialize<'de> for OtherStateUpdate {
1682 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1683 where
1684 D: serde::Deserializer<'de>,
1685 {
1686 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1687 let state = fields
1688 .remove("state")
1689 .ok_or_else(|| serde::de::Error::missing_field("state"))?;
1690 let serde_json::Value::String(state) = state else {
1691 return Err(serde::de::Error::custom("`state` must be a string"));
1692 };
1693 if is_known_state_update(&state) {
1694 return Err(serde::de::Error::custom(format!(
1695 "known state update `{state}` did not match its schema"
1696 )));
1697 }
1698 Ok(Self { state, fields })
1699 }
1700}
1701
1702#[cfg(feature = "unstable_subagents")]
1703const KNOWN_STATE_UPDATE_STATES: &[&str] = &["running", "idle", "requires_action", "unknown"];
1704
1705#[cfg(feature = "unstable_subagents")]
1706fn is_known_state_update(state: &str) -> bool {
1707 KNOWN_STATE_UPDATE_STATES.contains(&state)
1708}
1709
1710#[cfg(all(feature = "unstable_subagents", feature = "schemars"))]
1711fn other_state_update_schema(schema: &mut Schema) {
1712 let known = KNOWN_STATE_UPDATE_STATES
1713 .iter()
1714 .map(|state| {
1715 serde_json::json!({
1716 "properties": { "state": { "const": state, "type": "string" } },
1717 "required": ["state"],
1718 "type": "object"
1719 })
1720 })
1721 .collect::<Vec<_>>();
1722 schema.insert("not".into(), serde_json::json!({ "anyOf": known }));
1723}
1724
1725#[serde_as]
1729#[skip_serializing_none]
1730#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1731#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1732#[serde(rename_all = "camelCase")]
1733#[non_exhaustive]
1734pub struct CurrentModeUpdate {
1735 pub current_mode_id: SessionModeId,
1737 #[serde_as(deserialize_as = "DefaultOnError")]
1743 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1744 #[serde(default)]
1745 #[serde(rename = "_meta")]
1746 pub meta: Option<Meta>,
1747}
1748
1749impl CurrentModeUpdate {
1750 #[must_use]
1752 pub fn new(current_mode_id: impl Into<SessionModeId>) -> Self {
1753 Self {
1754 current_mode_id: current_mode_id.into(),
1755 meta: None,
1756 }
1757 }
1758
1759 #[must_use]
1765 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1766 self.meta = meta.into_option();
1767 self
1768 }
1769}
1770
1771#[serde_as]
1773#[skip_serializing_none]
1774#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1775#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1776#[serde(rename_all = "camelCase")]
1777#[non_exhaustive]
1778pub struct ConfigOptionUpdate {
1779 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
1781 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
1782 pub config_options: Vec<SessionConfigOption>,
1783 #[serde_as(deserialize_as = "DefaultOnError")]
1789 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1790 #[serde(default)]
1791 #[serde(rename = "_meta")]
1792 pub meta: Option<Meta>,
1793}
1794
1795impl ConfigOptionUpdate {
1796 #[must_use]
1798 pub fn new(config_options: Vec<SessionConfigOption>) -> Self {
1799 Self {
1800 config_options,
1801 meta: None,
1802 }
1803 }
1804
1805 #[must_use]
1811 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1812 self.meta = meta.into_option();
1813 self
1814 }
1815}
1816
1817#[serde_as]
1822#[skip_serializing_none]
1823#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1824#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1825#[serde(rename_all = "camelCase")]
1826#[non_exhaustive]
1827pub struct SessionInfoUpdate {
1828 #[serde_as(deserialize_as = "DefaultOnError")]
1830 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1831 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
1832 pub title: MaybeUndefined<String>,
1833 #[serde_as(deserialize_as = "DefaultOnError")]
1835 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1836 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
1837 pub updated_at: MaybeUndefined<String>,
1838 #[serde_as(deserialize_as = "DefaultOnError")]
1844 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1845 #[serde(default)]
1846 #[serde(rename = "_meta")]
1847 pub meta: Option<Meta>,
1848}
1849
1850impl SessionInfoUpdate {
1851 #[must_use]
1853 pub fn new() -> Self {
1854 Self::default()
1855 }
1856
1857 #[must_use]
1859 pub fn title(mut self, title: impl IntoMaybeUndefined<String>) -> Self {
1860 self.title = title.into_maybe_undefined();
1861 self
1862 }
1863
1864 #[must_use]
1866 pub fn updated_at(mut self, updated_at: impl IntoMaybeUndefined<String>) -> Self {
1867 self.updated_at = updated_at.into_maybe_undefined();
1868 self
1869 }
1870
1871 #[must_use]
1877 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1878 self.meta = meta.into_option();
1879 self
1880 }
1881}
1882
1883#[serde_as]
1885#[skip_serializing_none]
1886#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1887#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1888#[serde(rename_all = "camelCase")]
1889#[non_exhaustive]
1890pub struct UsageUpdate {
1891 pub used: u64,
1893 pub size: u64,
1895 #[serde_as(deserialize_as = "DefaultOnError")]
1897 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1898 #[serde(default)]
1899 pub cost: Option<Cost>,
1900 #[serde_as(deserialize_as = "DefaultOnError")]
1906 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1907 #[serde(default)]
1908 #[serde(rename = "_meta")]
1909 pub meta: Option<Meta>,
1910}
1911
1912impl UsageUpdate {
1913 #[must_use]
1915 pub fn new(used: u64, size: u64) -> Self {
1916 Self {
1917 used,
1918 size,
1919 cost: None,
1920 meta: None,
1921 }
1922 }
1923
1924 #[must_use]
1926 pub fn cost(mut self, cost: impl IntoOption<Cost>) -> Self {
1927 self.cost = cost.into_option();
1928 self
1929 }
1930
1931 #[must_use]
1937 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1938 self.meta = meta.into_option();
1939 self
1940 }
1941}
1942
1943#[serde_as]
1945#[skip_serializing_none]
1946#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1947#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1948#[serde(rename_all = "camelCase")]
1949#[non_exhaustive]
1950pub struct Cost {
1951 pub amount: f64,
1953 pub currency: String,
1956 #[serde_as(deserialize_as = "DefaultOnError")]
1962 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1963 #[serde(default)]
1964 #[serde(rename = "_meta")]
1965 pub meta: Option<Meta>,
1966}
1967
1968impl Cost {
1969 #[must_use]
1971 pub fn new(amount: f64, currency: impl Into<String>) -> Self {
1972 Self {
1973 amount,
1974 currency: currency.into(),
1975 meta: None,
1976 }
1977 }
1978
1979 #[must_use]
1985 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1986 self.meta = meta.into_option();
1987 self
1988 }
1989}
1990
1991#[serde_as]
1993#[skip_serializing_none]
1994#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1995#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1996#[serde(rename_all = "camelCase")]
1997#[non_exhaustive]
1998pub struct ContentChunk {
1999 pub content: ContentBlock,
2001 #[serde_as(deserialize_as = "DefaultOnError")]
2006 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2007 #[serde(default)]
2008 pub message_id: Option<MessageId>,
2009 #[serde_as(deserialize_as = "DefaultOnError")]
2015 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2016 #[serde(default)]
2017 #[serde(rename = "_meta")]
2018 pub meta: Option<Meta>,
2019}
2020
2021impl ContentChunk {
2022 #[must_use]
2024 pub fn new(content: ContentBlock) -> Self {
2025 Self {
2026 content,
2027 message_id: None,
2028 meta: None,
2029 }
2030 }
2031
2032 #[must_use]
2037 pub fn message_id(mut self, message_id: impl IntoOption<MessageId>) -> Self {
2038 self.message_id = message_id.into_option();
2039 self
2040 }
2041
2042 #[must_use]
2048 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2049 self.meta = meta.into_option();
2050 self
2051 }
2052}
2053
2054#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2056#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
2057#[serde(transparent)]
2058#[from(Arc<str>, String, &'static str)]
2059#[non_exhaustive]
2060pub struct MessageId(pub Arc<str>);
2061
2062impl MessageId {
2063 #[must_use]
2065 pub fn new(id: impl Into<Arc<str>>) -> Self {
2066 Self(id.into())
2067 }
2068}
2069
2070impl IntoOption<MessageId> for &str {
2071 fn into_option(self) -> Option<MessageId> {
2072 Some(MessageId::new(self))
2073 }
2074}
2075
2076#[serde_as]
2078#[skip_serializing_none]
2079#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2080#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2081#[serde(rename_all = "camelCase")]
2082#[non_exhaustive]
2083pub struct AvailableCommandsUpdate {
2084 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
2086 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
2087 pub available_commands: Vec<AvailableCommand>,
2088 #[serde_as(deserialize_as = "DefaultOnError")]
2094 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2095 #[serde(default)]
2096 #[serde(rename = "_meta")]
2097 pub meta: Option<Meta>,
2098}
2099
2100impl AvailableCommandsUpdate {
2101 #[must_use]
2103 pub fn new(available_commands: Vec<AvailableCommand>) -> Self {
2104 Self {
2105 available_commands,
2106 meta: None,
2107 }
2108 }
2109
2110 #[must_use]
2116 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2117 self.meta = meta.into_option();
2118 self
2119 }
2120}
2121
2122#[serde_as]
2124#[skip_serializing_none]
2125#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2126#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2127#[serde(rename_all = "camelCase")]
2128#[non_exhaustive]
2129pub struct AvailableCommand {
2130 pub name: String,
2132 pub description: String,
2134 #[serde_as(deserialize_as = "DefaultOnError")]
2136 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2137 #[serde(default)]
2138 pub input: Option<AvailableCommandInput>,
2139 #[serde_as(deserialize_as = "DefaultOnError")]
2145 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2146 #[serde(default)]
2147 #[serde(rename = "_meta")]
2148 pub meta: Option<Meta>,
2149}
2150
2151impl AvailableCommand {
2152 #[must_use]
2154 pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
2155 Self {
2156 name: name.into(),
2157 description: description.into(),
2158 input: None,
2159 meta: None,
2160 }
2161 }
2162
2163 #[must_use]
2165 pub fn input(mut self, input: impl IntoOption<AvailableCommandInput>) -> Self {
2166 self.input = input.into_option();
2167 self
2168 }
2169
2170 #[must_use]
2176 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2177 self.meta = meta.into_option();
2178 self
2179 }
2180}
2181
2182#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2184#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2185#[serde(untagged, rename_all = "camelCase")]
2186#[non_exhaustive]
2187pub enum AvailableCommandInput {
2188 Unstructured(UnstructuredCommandInput),
2190}
2191
2192#[serde_as]
2194#[skip_serializing_none]
2195#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2196#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2197#[serde(rename_all = "camelCase")]
2198#[non_exhaustive]
2199pub struct UnstructuredCommandInput {
2200 pub hint: String,
2202 #[serde_as(deserialize_as = "DefaultOnError")]
2208 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2209 #[serde(default)]
2210 #[serde(rename = "_meta")]
2211 pub meta: Option<Meta>,
2212}
2213
2214impl UnstructuredCommandInput {
2215 #[must_use]
2217 pub fn new(hint: impl Into<String>) -> Self {
2218 Self {
2219 hint: hint.into(),
2220 meta: None,
2221 }
2222 }
2223
2224 #[must_use]
2230 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2231 self.meta = meta.into_option();
2232 self
2233 }
2234}
2235
2236#[serde_as]
2244#[skip_serializing_none]
2245#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2246#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2247#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = SESSION_REQUEST_PERMISSION_METHOD_NAME)))]
2248#[serde(rename_all = "camelCase")]
2249#[non_exhaustive]
2250pub struct RequestPermissionRequest {
2251 pub session_id: SessionId,
2253 pub tool_call: ToolCallUpdate,
2255 pub options: Vec<PermissionOption>,
2257 #[serde_as(deserialize_as = "DefaultOnError")]
2263 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2264 #[serde(default)]
2265 #[serde(rename = "_meta")]
2266 pub meta: Option<Meta>,
2267}
2268
2269impl RequestPermissionRequest {
2270 #[must_use]
2272 pub fn new(
2273 session_id: impl Into<SessionId>,
2274 tool_call: ToolCallUpdate,
2275 options: Vec<PermissionOption>,
2276 ) -> Self {
2277 Self {
2278 session_id: session_id.into(),
2279 tool_call,
2280 options,
2281 meta: None,
2282 }
2283 }
2284
2285 #[must_use]
2291 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2292 self.meta = meta.into_option();
2293 self
2294 }
2295}
2296
2297#[serde_as]
2299#[skip_serializing_none]
2300#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2301#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2302#[serde(rename_all = "camelCase")]
2303#[non_exhaustive]
2304pub struct PermissionOption {
2305 pub option_id: PermissionOptionId,
2307 pub name: String,
2309 pub kind: PermissionOptionKind,
2311 #[serde_as(deserialize_as = "DefaultOnError")]
2317 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2318 #[serde(default)]
2319 #[serde(rename = "_meta")]
2320 pub meta: Option<Meta>,
2321}
2322
2323impl PermissionOption {
2324 #[must_use]
2326 pub fn new(
2327 option_id: impl Into<PermissionOptionId>,
2328 name: impl Into<String>,
2329 kind: PermissionOptionKind,
2330 ) -> Self {
2331 Self {
2332 option_id: option_id.into(),
2333 name: name.into(),
2334 kind,
2335 meta: None,
2336 }
2337 }
2338
2339 #[must_use]
2345 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2346 self.meta = meta.into_option();
2347 self
2348 }
2349}
2350
2351#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2353#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
2354#[serde(transparent)]
2355#[from(Arc<str>, String, &'static str)]
2356#[non_exhaustive]
2357pub struct PermissionOptionId(pub Arc<str>);
2358
2359impl PermissionOptionId {
2360 #[must_use]
2362 pub fn new(id: impl Into<Arc<str>>) -> Self {
2363 Self(id.into())
2364 }
2365}
2366
2367#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2371#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
2372#[serde(rename_all = "snake_case")]
2373#[non_exhaustive]
2374pub enum PermissionOptionKind {
2375 AllowOnce,
2377 AllowAlways,
2379 RejectOnce,
2381 RejectAlways,
2383}
2384
2385#[serde_as]
2387#[skip_serializing_none]
2388#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2389#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2390#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = SESSION_REQUEST_PERMISSION_METHOD_NAME)))]
2391#[serde(rename_all = "camelCase")]
2392#[non_exhaustive]
2393pub struct RequestPermissionResponse {
2394 pub outcome: RequestPermissionOutcome,
2397 #[serde_as(deserialize_as = "DefaultOnError")]
2403 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2404 #[serde(default)]
2405 #[serde(rename = "_meta")]
2406 pub meta: Option<Meta>,
2407}
2408
2409impl RequestPermissionResponse {
2410 #[must_use]
2412 pub fn new(outcome: RequestPermissionOutcome) -> Self {
2413 Self {
2414 outcome,
2415 meta: None,
2416 }
2417 }
2418
2419 #[must_use]
2425 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2426 self.meta = meta.into_option();
2427 self
2428 }
2429}
2430
2431#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2433#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2434#[serde(tag = "outcome", rename_all = "snake_case")]
2435#[cfg_attr(feature = "schemars", schemars(extend("discriminator" = {"propertyName": "outcome"})))]
2436#[non_exhaustive]
2437pub enum RequestPermissionOutcome {
2438 Cancelled,
2446 #[serde(rename_all = "camelCase")]
2448 Selected(SelectedPermissionOutcome),
2449}
2450
2451#[serde_as]
2453#[skip_serializing_none]
2454#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2455#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2456#[serde(rename_all = "camelCase")]
2457#[non_exhaustive]
2458pub struct SelectedPermissionOutcome {
2459 pub option_id: PermissionOptionId,
2461 #[serde_as(deserialize_as = "DefaultOnError")]
2467 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2468 #[serde(default)]
2469 #[serde(rename = "_meta")]
2470 pub meta: Option<Meta>,
2471}
2472
2473impl SelectedPermissionOutcome {
2474 #[must_use]
2476 pub fn new(option_id: impl Into<PermissionOptionId>) -> Self {
2477 Self {
2478 option_id: option_id.into(),
2479 meta: None,
2480 }
2481 }
2482
2483 #[must_use]
2489 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2490 self.meta = meta.into_option();
2491 self
2492 }
2493}
2494
2495#[serde_as]
2501#[skip_serializing_none]
2502#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2503#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2504#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = FS_WRITE_TEXT_FILE_METHOD_NAME)))]
2505#[serde(rename_all = "camelCase")]
2506#[non_exhaustive]
2507pub struct WriteTextFileRequest {
2508 pub session_id: SessionId,
2510 pub path: PathBuf,
2512 pub content: String,
2514 #[serde_as(deserialize_as = "DefaultOnError")]
2520 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2521 #[serde(default)]
2522 #[serde(rename = "_meta")]
2523 pub meta: Option<Meta>,
2524}
2525
2526impl WriteTextFileRequest {
2527 #[must_use]
2529 pub fn new(
2530 session_id: impl Into<SessionId>,
2531 path: impl Into<PathBuf>,
2532 content: impl Into<String>,
2533 ) -> Self {
2534 Self {
2535 session_id: session_id.into(),
2536 path: path.into(),
2537 content: content.into(),
2538 meta: None,
2539 }
2540 }
2541
2542 #[must_use]
2548 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2549 self.meta = meta.into_option();
2550 self
2551 }
2552}
2553
2554crate::serde_util::default_on_null! {
2555 #[serde_as]
2557 #[skip_serializing_none]
2558 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2559 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
2560 #[serde(rename_all = "camelCase")]
2561 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = FS_WRITE_TEXT_FILE_METHOD_NAME)))]
2562 #[non_exhaustive]
2563 pub struct WriteTextFileResponse {
2564 #[serde_as(deserialize_as = "DefaultOnError")]
2570 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2571 #[serde(default)]
2572 #[serde(rename = "_meta")]
2573 pub meta: Option<Meta>,
2574 }
2575}
2576
2577impl WriteTextFileResponse {
2578 #[must_use]
2580 pub fn new() -> Self {
2581 Self::default()
2582 }
2583
2584 #[must_use]
2590 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2591 self.meta = meta.into_option();
2592 self
2593 }
2594}
2595
2596#[serde_as]
2602#[skip_serializing_none]
2603#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2604#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2605#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = FS_READ_TEXT_FILE_METHOD_NAME)))]
2606#[serde(rename_all = "camelCase")]
2607#[non_exhaustive]
2608pub struct ReadTextFileRequest {
2609 pub session_id: SessionId,
2611 pub path: PathBuf,
2613 #[serde(default)]
2615 pub line: Option<u32>,
2616 #[serde(default)]
2618 pub limit: Option<u32>,
2619 #[serde_as(deserialize_as = "DefaultOnError")]
2625 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2626 #[serde(default)]
2627 #[serde(rename = "_meta")]
2628 pub meta: Option<Meta>,
2629}
2630
2631impl ReadTextFileRequest {
2632 #[must_use]
2634 pub fn new(session_id: impl Into<SessionId>, path: impl Into<PathBuf>) -> Self {
2635 Self {
2636 session_id: session_id.into(),
2637 path: path.into(),
2638 line: None,
2639 limit: None,
2640 meta: None,
2641 }
2642 }
2643
2644 #[must_use]
2646 pub fn line(mut self, line: impl IntoOption<u32>) -> Self {
2647 self.line = line.into_option();
2648 self
2649 }
2650
2651 #[must_use]
2653 pub fn limit(mut self, limit: impl IntoOption<u32>) -> Self {
2654 self.limit = limit.into_option();
2655 self
2656 }
2657
2658 #[must_use]
2664 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2665 self.meta = meta.into_option();
2666 self
2667 }
2668}
2669
2670#[serde_as]
2672#[skip_serializing_none]
2673#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2674#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2675#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = FS_READ_TEXT_FILE_METHOD_NAME)))]
2676#[serde(rename_all = "camelCase")]
2677#[non_exhaustive]
2678pub struct ReadTextFileResponse {
2679 pub content: String,
2681 #[serde_as(deserialize_as = "DefaultOnError")]
2687 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2688 #[serde(default)]
2689 #[serde(rename = "_meta")]
2690 pub meta: Option<Meta>,
2691}
2692
2693impl ReadTextFileResponse {
2694 #[must_use]
2696 pub fn new(content: impl Into<String>) -> Self {
2697 Self {
2698 content: content.into(),
2699 meta: None,
2700 }
2701 }
2702
2703 #[must_use]
2709 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2710 self.meta = meta.into_option();
2711 self
2712 }
2713}
2714
2715#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2719#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
2720#[serde(transparent)]
2721#[from(Arc<str>, String, &'static str)]
2722#[non_exhaustive]
2723pub struct TerminalId(pub Arc<str>);
2724
2725impl TerminalId {
2726 #[must_use]
2728 pub fn new(id: impl Into<Arc<str>>) -> Self {
2729 Self(id.into())
2730 }
2731}
2732
2733#[serde_as]
2735#[skip_serializing_none]
2736#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2737#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2738#[serde(rename_all = "camelCase")]
2739#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_CREATE_METHOD_NAME)))]
2740#[non_exhaustive]
2741pub struct CreateTerminalRequest {
2742 pub session_id: SessionId,
2744 pub command: String,
2746 #[serde_as(deserialize_as = "DefaultOnNull")]
2748 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-null" = true)))]
2749 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2750 pub args: Vec<String>,
2751 #[serde_as(deserialize_as = "DefaultOnNull")]
2753 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-null" = true)))]
2754 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2755 pub env: Vec<EnvVariable>,
2756 #[serde(default)]
2758 pub cwd: Option<PathBuf>,
2759 #[serde(default)]
2768 pub output_byte_limit: Option<u64>,
2769 #[serde_as(deserialize_as = "DefaultOnError")]
2775 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2776 #[serde(default)]
2777 #[serde(rename = "_meta")]
2778 pub meta: Option<Meta>,
2779}
2780
2781impl CreateTerminalRequest {
2782 #[must_use]
2784 pub fn new(session_id: impl Into<SessionId>, command: impl Into<String>) -> Self {
2785 Self {
2786 session_id: session_id.into(),
2787 command: command.into(),
2788 args: Vec::new(),
2789 env: Vec::new(),
2790 cwd: None,
2791 output_byte_limit: None,
2792 meta: None,
2793 }
2794 }
2795
2796 #[must_use]
2798 pub fn args(mut self, args: Vec<String>) -> Self {
2799 self.args = args;
2800 self
2801 }
2802
2803 #[must_use]
2805 pub fn env(mut self, env: Vec<EnvVariable>) -> Self {
2806 self.env = env;
2807 self
2808 }
2809
2810 #[must_use]
2812 pub fn cwd(mut self, cwd: impl IntoOption<PathBuf>) -> Self {
2813 self.cwd = cwd.into_option();
2814 self
2815 }
2816
2817 #[must_use]
2826 pub fn output_byte_limit(mut self, output_byte_limit: impl IntoOption<u64>) -> Self {
2827 self.output_byte_limit = output_byte_limit.into_option();
2828 self
2829 }
2830
2831 #[must_use]
2837 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2838 self.meta = meta.into_option();
2839 self
2840 }
2841}
2842
2843#[serde_as]
2845#[skip_serializing_none]
2846#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2847#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2848#[serde(rename_all = "camelCase")]
2849#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_CREATE_METHOD_NAME)))]
2850#[non_exhaustive]
2851pub struct CreateTerminalResponse {
2852 pub terminal_id: TerminalId,
2854 #[serde_as(deserialize_as = "DefaultOnError")]
2860 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2861 #[serde(default)]
2862 #[serde(rename = "_meta")]
2863 pub meta: Option<Meta>,
2864}
2865
2866impl CreateTerminalResponse {
2867 #[must_use]
2869 pub fn new(terminal_id: impl Into<TerminalId>) -> Self {
2870 Self {
2871 terminal_id: terminal_id.into(),
2872 meta: None,
2873 }
2874 }
2875
2876 #[must_use]
2882 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2883 self.meta = meta.into_option();
2884 self
2885 }
2886}
2887
2888#[serde_as]
2890#[skip_serializing_none]
2891#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2892#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2893#[serde(rename_all = "camelCase")]
2894#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_OUTPUT_METHOD_NAME)))]
2895#[non_exhaustive]
2896pub struct TerminalOutputRequest {
2897 pub session_id: SessionId,
2899 pub terminal_id: TerminalId,
2901 #[serde_as(deserialize_as = "DefaultOnError")]
2907 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2908 #[serde(default)]
2909 #[serde(rename = "_meta")]
2910 pub meta: Option<Meta>,
2911}
2912
2913impl TerminalOutputRequest {
2914 #[must_use]
2916 pub fn new(session_id: impl Into<SessionId>, terminal_id: impl Into<TerminalId>) -> Self {
2917 Self {
2918 session_id: session_id.into(),
2919 terminal_id: terminal_id.into(),
2920 meta: None,
2921 }
2922 }
2923
2924 #[must_use]
2930 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2931 self.meta = meta.into_option();
2932 self
2933 }
2934}
2935
2936#[serde_as]
2938#[skip_serializing_none]
2939#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2940#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2941#[serde(rename_all = "camelCase")]
2942#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_OUTPUT_METHOD_NAME)))]
2943#[non_exhaustive]
2944pub struct TerminalOutputResponse {
2945 pub output: String,
2947 pub truncated: bool,
2949 #[serde_as(deserialize_as = "DefaultOnError")]
2951 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2952 #[serde(default)]
2953 pub exit_status: Option<TerminalExitStatus>,
2954 #[serde_as(deserialize_as = "DefaultOnError")]
2960 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2961 #[serde(default)]
2962 #[serde(rename = "_meta")]
2963 pub meta: Option<Meta>,
2964}
2965
2966impl TerminalOutputResponse {
2967 #[must_use]
2969 pub fn new(output: impl Into<String>, truncated: bool) -> Self {
2970 Self {
2971 output: output.into(),
2972 truncated,
2973 exit_status: None,
2974 meta: None,
2975 }
2976 }
2977
2978 #[must_use]
2980 pub fn exit_status(mut self, exit_status: impl IntoOption<TerminalExitStatus>) -> Self {
2981 self.exit_status = exit_status.into_option();
2982 self
2983 }
2984
2985 #[must_use]
2991 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2992 self.meta = meta.into_option();
2993 self
2994 }
2995}
2996
2997#[serde_as]
2999#[skip_serializing_none]
3000#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3001#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3002#[serde(rename_all = "camelCase")]
3003#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_RELEASE_METHOD_NAME)))]
3004#[non_exhaustive]
3005pub struct ReleaseTerminalRequest {
3006 pub session_id: SessionId,
3008 pub terminal_id: TerminalId,
3010 #[serde_as(deserialize_as = "DefaultOnError")]
3016 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3017 #[serde(default)]
3018 #[serde(rename = "_meta")]
3019 pub meta: Option<Meta>,
3020}
3021
3022impl ReleaseTerminalRequest {
3023 #[must_use]
3025 pub fn new(session_id: impl Into<SessionId>, terminal_id: impl Into<TerminalId>) -> Self {
3026 Self {
3027 session_id: session_id.into(),
3028 terminal_id: terminal_id.into(),
3029 meta: None,
3030 }
3031 }
3032
3033 #[must_use]
3039 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3040 self.meta = meta.into_option();
3041 self
3042 }
3043}
3044
3045crate::serde_util::default_on_null! {
3046 #[serde_as]
3048 #[skip_serializing_none]
3049 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3050 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
3051 #[serde(rename_all = "camelCase")]
3052 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_RELEASE_METHOD_NAME)))]
3053 #[non_exhaustive]
3054 pub struct ReleaseTerminalResponse {
3055 #[serde_as(deserialize_as = "DefaultOnError")]
3061 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3062 #[serde(default)]
3063 #[serde(rename = "_meta")]
3064 pub meta: Option<Meta>,
3065 }
3066}
3067
3068impl ReleaseTerminalResponse {
3069 #[must_use]
3071 pub fn new() -> Self {
3072 Self::default()
3073 }
3074
3075 #[must_use]
3081 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3082 self.meta = meta.into_option();
3083 self
3084 }
3085}
3086
3087#[serde_as]
3089#[skip_serializing_none]
3090#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3091#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3092#[serde(rename_all = "camelCase")]
3093#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_KILL_METHOD_NAME)))]
3094#[non_exhaustive]
3095pub struct KillTerminalRequest {
3096 pub session_id: SessionId,
3098 pub terminal_id: TerminalId,
3100 #[serde_as(deserialize_as = "DefaultOnError")]
3106 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3107 #[serde(default)]
3108 #[serde(rename = "_meta")]
3109 pub meta: Option<Meta>,
3110}
3111
3112impl KillTerminalRequest {
3113 #[must_use]
3115 pub fn new(session_id: impl Into<SessionId>, terminal_id: impl Into<TerminalId>) -> Self {
3116 Self {
3117 session_id: session_id.into(),
3118 terminal_id: terminal_id.into(),
3119 meta: None,
3120 }
3121 }
3122
3123 #[must_use]
3129 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3130 self.meta = meta.into_option();
3131 self
3132 }
3133}
3134
3135crate::serde_util::default_on_null! {
3136 #[serde_as]
3138 #[skip_serializing_none]
3139 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3140 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
3141 #[serde(rename_all = "camelCase")]
3142 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_KILL_METHOD_NAME)))]
3143 #[non_exhaustive]
3144 pub struct KillTerminalResponse {
3145 #[serde_as(deserialize_as = "DefaultOnError")]
3151 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3152 #[serde(default)]
3153 #[serde(rename = "_meta")]
3154 pub meta: Option<Meta>,
3155 }
3156}
3157
3158impl KillTerminalResponse {
3159 #[must_use]
3161 pub fn new() -> Self {
3162 Self::default()
3163 }
3164
3165 #[must_use]
3171 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3172 self.meta = meta.into_option();
3173 self
3174 }
3175}
3176
3177#[serde_as]
3179#[skip_serializing_none]
3180#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3181#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3182#[serde(rename_all = "camelCase")]
3183#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_WAIT_FOR_EXIT_METHOD_NAME)))]
3184#[non_exhaustive]
3185pub struct WaitForTerminalExitRequest {
3186 pub session_id: SessionId,
3188 pub terminal_id: TerminalId,
3190 #[serde_as(deserialize_as = "DefaultOnError")]
3196 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3197 #[serde(default)]
3198 #[serde(rename = "_meta")]
3199 pub meta: Option<Meta>,
3200}
3201
3202impl WaitForTerminalExitRequest {
3203 #[must_use]
3205 pub fn new(session_id: impl Into<SessionId>, terminal_id: impl Into<TerminalId>) -> Self {
3206 Self {
3207 session_id: session_id.into(),
3208 terminal_id: terminal_id.into(),
3209 meta: None,
3210 }
3211 }
3212
3213 #[must_use]
3219 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3220 self.meta = meta.into_option();
3221 self
3222 }
3223}
3224
3225crate::serde_util::default_on_null! {
3226 #[serde_as]
3228 #[skip_serializing_none]
3229 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3230 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
3231 #[serde(rename_all = "camelCase")]
3232 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_WAIT_FOR_EXIT_METHOD_NAME)))]
3233 #[non_exhaustive]
3234 pub struct WaitForTerminalExitResponse {
3235 #[serde(flatten)]
3237 pub exit_status: TerminalExitStatus,
3238 #[serde_as(deserialize_as = "DefaultOnError")]
3244 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3245 #[serde(default)]
3246 #[serde(rename = "_meta")]
3247 pub meta: Option<Meta>,
3248 }
3249}
3250
3251impl WaitForTerminalExitResponse {
3252 #[must_use]
3254 pub fn new(exit_status: TerminalExitStatus) -> Self {
3255 Self {
3256 exit_status,
3257 meta: None,
3258 }
3259 }
3260
3261 #[must_use]
3267 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3268 self.meta = meta.into_option();
3269 self
3270 }
3271}
3272
3273#[serde_as]
3275#[skip_serializing_none]
3276#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3277#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3278#[serde(rename_all = "camelCase")]
3279#[non_exhaustive]
3280pub struct TerminalExitStatus {
3281 #[serde_as(deserialize_as = "DefaultOnError")]
3283 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3284 #[serde(default)]
3285 pub exit_code: Option<u32>,
3286 #[serde_as(deserialize_as = "DefaultOnError")]
3288 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3289 #[serde(default)]
3290 pub signal: Option<String>,
3291 #[serde_as(deserialize_as = "DefaultOnError")]
3297 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3298 #[serde(default)]
3299 #[serde(rename = "_meta")]
3300 pub meta: Option<Meta>,
3301}
3302
3303impl TerminalExitStatus {
3304 #[must_use]
3306 pub fn new() -> Self {
3307 Self::default()
3308 }
3309
3310 #[must_use]
3312 pub fn exit_code(mut self, exit_code: impl IntoOption<u32>) -> Self {
3313 self.exit_code = exit_code.into_option();
3314 self
3315 }
3316
3317 #[must_use]
3319 pub fn signal(mut self, signal: impl IntoOption<String>) -> Self {
3320 self.signal = signal.into_option();
3321 self
3322 }
3323
3324 #[must_use]
3330 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3331 self.meta = meta.into_option();
3332 self
3333 }
3334}
3335
3336#[serde_as]
3345#[skip_serializing_none]
3346#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3347#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3348#[serde(rename_all = "camelCase")]
3349#[non_exhaustive]
3350pub struct ClientCapabilities {
3351 #[serde_as(deserialize_as = "DefaultOnError")]
3354 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3355 #[serde(default)]
3356 pub fs: FileSystemCapabilities,
3357 #[serde_as(deserialize_as = "DefaultOnError")]
3359 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3360 #[serde(default)]
3361 pub terminal: bool,
3362 #[serde_as(deserialize_as = "DefaultOnError")]
3367 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3368 #[serde(default)]
3369 pub session: Option<ClientSessionCapabilities>,
3370 #[cfg(feature = "unstable_subagents")]
3381 #[serde_as(deserialize_as = "DefaultOnError")]
3382 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3383 #[serde(default)]
3384 pub subagents: Option<SubagentCapabilities>,
3385 #[cfg(feature = "unstable_plan_operations")]
3394 #[serde_as(deserialize_as = "DefaultOnError")]
3395 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3396 #[serde(default)]
3397 pub plan: Option<PlanCapabilities>,
3398 #[serde_as(deserialize_as = "DefaultOnError")]
3402 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3403 #[serde(default)]
3404 pub auth: AuthCapabilities,
3405 #[serde_as(deserialize_as = "DefaultOnError")]
3411 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3412 #[serde(default)]
3413 pub elicitation: Option<ElicitationCapabilities>,
3414 #[serde_as(deserialize_as = "DefaultOnError")]
3420 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3421 #[serde(default)]
3422 #[serde(rename = "_meta")]
3423 pub meta: Option<Meta>,
3424}
3425
3426impl ClientCapabilities {
3427 #[must_use]
3429 pub fn new() -> Self {
3430 Self::default()
3431 }
3432
3433 #[must_use]
3436 pub fn fs(mut self, fs: FileSystemCapabilities) -> Self {
3437 self.fs = fs;
3438 self
3439 }
3440
3441 #[must_use]
3443 pub fn terminal(mut self, terminal: bool) -> Self {
3444 self.terminal = terminal;
3445 self
3446 }
3447
3448 #[must_use]
3450 pub fn session(mut self, session: impl IntoOption<ClientSessionCapabilities>) -> Self {
3451 self.session = session.into_option();
3452 self
3453 }
3454
3455 #[cfg(feature = "unstable_subagents")]
3461 #[must_use]
3462 pub fn subagents(mut self, subagents: impl IntoOption<SubagentCapabilities>) -> Self {
3463 self.subagents = subagents.into_option();
3464 self
3465 }
3466
3467 #[cfg(feature = "unstable_plan_operations")]
3476 #[must_use]
3477 pub fn plan(mut self, plan: impl IntoOption<PlanCapabilities>) -> Self {
3478 self.plan = plan.into_option();
3479 self
3480 }
3481
3482 #[must_use]
3486 pub fn auth(mut self, auth: AuthCapabilities) -> Self {
3487 self.auth = auth;
3488 self
3489 }
3490
3491 #[must_use]
3494 pub fn elicitation(mut self, elicitation: impl IntoOption<ElicitationCapabilities>) -> Self {
3495 self.elicitation = elicitation.into_option();
3496 self
3497 }
3498
3499 #[must_use]
3505 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3506 self.meta = meta.into_option();
3507 self
3508 }
3509}
3510
3511#[cfg(feature = "unstable_subagents")]
3522#[serde_as]
3523#[skip_serializing_none]
3524#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3525#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3526#[non_exhaustive]
3527pub struct SubagentCapabilities {
3528 #[serde_as(deserialize_as = "DefaultOnError")]
3532 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3533 #[serde(default)]
3534 #[serde(rename = "_meta")]
3535 pub meta: Option<Meta>,
3536}
3537
3538#[cfg(feature = "unstable_subagents")]
3539impl SubagentCapabilities {
3540 #[must_use]
3542 pub fn new() -> Self {
3543 Self::default()
3544 }
3545
3546 #[must_use]
3550 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3551 self.meta = meta.into_option();
3552 self
3553 }
3554}
3555
3556#[serde_as]
3558#[skip_serializing_none]
3559#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3560#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3561#[serde(rename_all = "camelCase")]
3562#[non_exhaustive]
3563pub struct ClientSessionCapabilities {
3564 #[serde_as(deserialize_as = "DefaultOnError")]
3567 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3568 #[serde(default)]
3569 pub compaction: Option<CompactionCapabilities>,
3570 #[serde_as(deserialize_as = "DefaultOnError")]
3575 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3576 #[serde(default)]
3577 pub config_options: Option<SessionConfigOptionsCapabilities>,
3578 #[serde_as(deserialize_as = "DefaultOnError")]
3583 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3584 #[serde(default)]
3585 pub notices: Option<NoticeCapabilities>,
3586 #[serde_as(deserialize_as = "DefaultOnError")]
3592 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3593 #[serde(default)]
3594 #[serde(rename = "_meta")]
3595 pub meta: Option<Meta>,
3596}
3597
3598impl ClientSessionCapabilities {
3599 #[must_use]
3601 pub fn new() -> Self {
3602 Self::default()
3603 }
3604
3605 #[must_use]
3607 pub fn compaction(mut self, compaction: impl IntoOption<CompactionCapabilities>) -> Self {
3608 self.compaction = compaction.into_option();
3609 self
3610 }
3611
3612 #[must_use]
3617 pub fn config_options(
3618 mut self,
3619 config_options: impl IntoOption<SessionConfigOptionsCapabilities>,
3620 ) -> Self {
3621 self.config_options = config_options.into_option();
3622 self
3623 }
3624
3625 #[must_use]
3627 pub fn notices(mut self, notices: impl IntoOption<NoticeCapabilities>) -> Self {
3628 self.notices = notices.into_option();
3629 self
3630 }
3631
3632 #[must_use]
3638 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3639 self.meta = meta.into_option();
3640 self
3641 }
3642}
3643
3644#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3646#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3647#[serde(rename_all = "camelCase")]
3648#[non_exhaustive]
3649pub struct CompactionCapabilities {}
3650
3651impl CompactionCapabilities {
3652 #[must_use]
3654 pub fn new() -> Self {
3655 Self {}
3656 }
3657}
3658
3659#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3661#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3662#[serde(rename_all = "camelCase")]
3663#[non_exhaustive]
3664pub struct NoticeCapabilities {}
3665
3666impl NoticeCapabilities {
3667 #[must_use]
3669 pub fn new() -> Self {
3670 Self {}
3671 }
3672}
3673
3674#[serde_as]
3676#[skip_serializing_none]
3677#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3678#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3679#[serde(rename_all = "camelCase")]
3680#[non_exhaustive]
3681pub struct SessionConfigOptionsCapabilities {
3682 #[serde_as(deserialize_as = "DefaultOnError")]
3689 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3690 #[serde(default)]
3691 pub boolean: Option<BooleanConfigOptionCapabilities>,
3692 #[serde_as(deserialize_as = "DefaultOnError")]
3698 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3699 #[serde(default)]
3700 #[serde(rename = "_meta")]
3701 pub meta: Option<Meta>,
3702}
3703
3704impl SessionConfigOptionsCapabilities {
3705 #[must_use]
3707 pub fn new() -> Self {
3708 Self::default()
3709 }
3710
3711 #[must_use]
3718 pub fn boolean(mut self, boolean: impl IntoOption<BooleanConfigOptionCapabilities>) -> Self {
3719 self.boolean = boolean.into_option();
3720 self
3721 }
3722
3723 #[must_use]
3729 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3730 self.meta = meta.into_option();
3731 self
3732 }
3733}
3734
3735#[serde_as]
3739#[skip_serializing_none]
3740#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3741#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3742#[non_exhaustive]
3743pub struct BooleanConfigOptionCapabilities {
3744 #[serde_as(deserialize_as = "DefaultOnError")]
3750 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3751 #[serde(default)]
3752 #[serde(rename = "_meta")]
3753 pub meta: Option<Meta>,
3754}
3755
3756impl BooleanConfigOptionCapabilities {
3757 #[must_use]
3759 pub fn new() -> Self {
3760 Self::default()
3761 }
3762
3763 #[must_use]
3769 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3770 self.meta = meta.into_option();
3771 self
3772 }
3773}
3774
3775#[serde_as]
3781#[skip_serializing_none]
3782#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3783#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3784#[serde(rename_all = "camelCase")]
3785#[non_exhaustive]
3786pub struct AuthCapabilities {
3787 #[serde_as(deserialize_as = "DefaultOnError")]
3793 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3794 #[serde(default)]
3795 pub terminal: bool,
3796 #[serde_as(deserialize_as = "DefaultOnError")]
3802 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3803 #[serde(default)]
3804 #[serde(rename = "_meta")]
3805 pub meta: Option<Meta>,
3806}
3807
3808impl AuthCapabilities {
3809 #[must_use]
3811 pub fn new() -> Self {
3812 Self::default()
3813 }
3814
3815 #[must_use]
3822 pub fn terminal(mut self, terminal: bool) -> Self {
3823 self.terminal = terminal;
3824 self
3825 }
3826
3827 #[must_use]
3833 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3834 self.meta = meta.into_option();
3835 self
3836 }
3837}
3838
3839#[serde_as]
3843#[skip_serializing_none]
3844#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3845#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3846#[serde(rename_all = "camelCase")]
3847#[non_exhaustive]
3848pub struct FileSystemCapabilities {
3849 #[serde_as(deserialize_as = "DefaultOnError")]
3851 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3852 #[serde(default)]
3853 pub read_text_file: bool,
3854 #[serde_as(deserialize_as = "DefaultOnError")]
3856 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3857 #[serde(default)]
3858 pub write_text_file: bool,
3859 #[serde_as(deserialize_as = "DefaultOnError")]
3865 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3866 #[serde(default)]
3867 #[serde(rename = "_meta")]
3868 pub meta: Option<Meta>,
3869}
3870
3871impl FileSystemCapabilities {
3872 #[must_use]
3874 pub fn new() -> Self {
3875 Self::default()
3876 }
3877
3878 #[must_use]
3880 pub fn read_text_file(mut self, read_text_file: bool) -> Self {
3881 self.read_text_file = read_text_file;
3882 self
3883 }
3884
3885 #[must_use]
3887 pub fn write_text_file(mut self, write_text_file: bool) -> Self {
3888 self.write_text_file = write_text_file;
3889 self
3890 }
3891
3892 #[must_use]
3898 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3899 self.meta = meta.into_option();
3900 self
3901 }
3902}
3903
3904#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3910#[non_exhaustive]
3911pub struct ClientMethodNames {
3912 pub session_request_permission: &'static str,
3914 pub session_update: &'static str,
3916 pub fs_write_text_file: &'static str,
3918 pub fs_read_text_file: &'static str,
3920 pub terminal_create: &'static str,
3922 pub terminal_output: &'static str,
3924 pub terminal_release: &'static str,
3926 pub terminal_wait_for_exit: &'static str,
3928 pub terminal_kill: &'static str,
3930 #[cfg(feature = "unstable_mcp_over_acp")]
3932 pub mcp_message: &'static str,
3933 pub elicitation_create: &'static str,
3935 pub elicitation_complete: &'static str,
3937}
3938
3939pub const CLIENT_METHOD_NAMES: ClientMethodNames = ClientMethodNames {
3941 session_update: SESSION_UPDATE_NOTIFICATION,
3942 session_request_permission: SESSION_REQUEST_PERMISSION_METHOD_NAME,
3943 fs_write_text_file: FS_WRITE_TEXT_FILE_METHOD_NAME,
3944 fs_read_text_file: FS_READ_TEXT_FILE_METHOD_NAME,
3945 terminal_create: TERMINAL_CREATE_METHOD_NAME,
3946 terminal_output: TERMINAL_OUTPUT_METHOD_NAME,
3947 terminal_release: TERMINAL_RELEASE_METHOD_NAME,
3948 terminal_wait_for_exit: TERMINAL_WAIT_FOR_EXIT_METHOD_NAME,
3949 terminal_kill: TERMINAL_KILL_METHOD_NAME,
3950 #[cfg(feature = "unstable_mcp_over_acp")]
3951 mcp_message: MCP_MESSAGE_METHOD_NAME,
3952 elicitation_create: ELICITATION_CREATE_METHOD_NAME,
3953 elicitation_complete: ELICITATION_COMPLETE_NOTIFICATION,
3954};
3955
3956pub(crate) const SESSION_UPDATE_NOTIFICATION: &str = "session/update";
3958pub(crate) const SESSION_REQUEST_PERMISSION_METHOD_NAME: &str = "session/request_permission";
3960pub(crate) const FS_WRITE_TEXT_FILE_METHOD_NAME: &str = "fs/write_text_file";
3962pub(crate) const FS_READ_TEXT_FILE_METHOD_NAME: &str = "fs/read_text_file";
3964pub(crate) const TERMINAL_CREATE_METHOD_NAME: &str = "terminal/create";
3966pub(crate) const TERMINAL_OUTPUT_METHOD_NAME: &str = "terminal/output";
3968pub(crate) const TERMINAL_RELEASE_METHOD_NAME: &str = "terminal/release";
3970pub(crate) const TERMINAL_WAIT_FOR_EXIT_METHOD_NAME: &str = "terminal/wait_for_exit";
3972pub(crate) const TERMINAL_KILL_METHOD_NAME: &str = "terminal/kill";
3974pub(crate) const ELICITATION_CREATE_METHOD_NAME: &str = "elicitation/create";
3976pub(crate) const ELICITATION_COMPLETE_NOTIFICATION: &str = "elicitation/complete";
3978
3979#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3986#[derive(Clone, Debug, Serialize, Deserialize)]
3987#[serde(untagged)]
3988#[cfg_attr(feature = "schemars", schemars(inline))]
3989#[non_exhaustive]
3990#[allow(clippy::large_enum_variant)]
3991pub enum AgentRequest {
3992 WriteTextFileRequest(WriteTextFileRequest),
3999 ReadTextFileRequest(ReadTextFileRequest),
4006 RequestPermissionRequest(RequestPermissionRequest),
4017 CreateTerminalRequest(CreateTerminalRequest),
4032 TerminalOutputRequest(TerminalOutputRequest),
4039 ReleaseTerminalRequest(ReleaseTerminalRequest),
4052 WaitForTerminalExitRequest(WaitForTerminalExitRequest),
4056 KillTerminalRequest(KillTerminalRequest),
4069 CreateElicitationRequest(CreateElicitationRequest),
4073 #[cfg(feature = "unstable_mcp_over_acp")]
4079 MessageMcpRequest(MessageMcpRequest),
4080 ExtMethodRequest(ExtRequest),
4088}
4089
4090impl AgentRequest {
4091 #[must_use]
4093 pub fn method(&self) -> &str {
4094 match self {
4095 Self::WriteTextFileRequest(_) => CLIENT_METHOD_NAMES.fs_write_text_file,
4096 Self::ReadTextFileRequest(_) => CLIENT_METHOD_NAMES.fs_read_text_file,
4097 Self::RequestPermissionRequest(_) => CLIENT_METHOD_NAMES.session_request_permission,
4098 Self::CreateTerminalRequest(_) => CLIENT_METHOD_NAMES.terminal_create,
4099 Self::TerminalOutputRequest(_) => CLIENT_METHOD_NAMES.terminal_output,
4100 Self::ReleaseTerminalRequest(_) => CLIENT_METHOD_NAMES.terminal_release,
4101 Self::WaitForTerminalExitRequest(_) => CLIENT_METHOD_NAMES.terminal_wait_for_exit,
4102 Self::KillTerminalRequest(_) => CLIENT_METHOD_NAMES.terminal_kill,
4103 Self::CreateElicitationRequest(_) => CLIENT_METHOD_NAMES.elicitation_create,
4104 #[cfg(feature = "unstable_mcp_over_acp")]
4105 Self::MessageMcpRequest(_) => CLIENT_METHOD_NAMES.mcp_message,
4106 Self::ExtMethodRequest(ext_request) => &ext_request.method,
4107 }
4108 }
4109}
4110
4111#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4118#[derive(Clone, Debug, Serialize, Deserialize)]
4119#[serde(untagged)]
4120#[cfg_attr(feature = "schemars", schemars(inline))]
4121#[non_exhaustive]
4122pub enum ClientResponse {
4123 WriteTextFileResponse(#[serde(default)] WriteTextFileResponse),
4125 ReadTextFileResponse(ReadTextFileResponse),
4127 RequestPermissionResponse(RequestPermissionResponse),
4129 CreateTerminalResponse(CreateTerminalResponse),
4131 TerminalOutputResponse(TerminalOutputResponse),
4133 ReleaseTerminalResponse(#[serde(default)] ReleaseTerminalResponse),
4135 WaitForTerminalExitResponse(WaitForTerminalExitResponse),
4137 KillTerminalResponse(#[serde(default)] KillTerminalResponse),
4139 CreateElicitationResponse(CreateElicitationResponse),
4141 #[cfg(feature = "unstable_mcp_over_acp")]
4143 MessageMcpResponse(MessageMcpResponse),
4144 ExtMethodResponse(ExtResponse),
4146}
4147
4148#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4155#[derive(Clone, Debug, Serialize, Deserialize)]
4156#[serde(untagged)]
4157#[expect(clippy::large_enum_variant)]
4158#[cfg_attr(feature = "schemars", schemars(inline))]
4159#[non_exhaustive]
4160pub enum AgentNotification {
4161 SessionNotification(SessionNotification),
4173 CompleteElicitationNotification(CompleteElicitationNotification),
4177 ExtNotification(ExtNotification),
4185}
4186
4187impl AgentNotification {
4188 #[must_use]
4190 pub fn method(&self) -> &str {
4191 match self {
4192 Self::SessionNotification(_) => CLIENT_METHOD_NAMES.session_update,
4193 Self::CompleteElicitationNotification(_) => CLIENT_METHOD_NAMES.elicitation_complete,
4194 Self::ExtNotification(ext_notification) => &ext_notification.method,
4195 }
4196 }
4197}
4198
4199#[cfg(test)]
4200mod tests {
4201 use super::*;
4202
4203 #[test]
4204 fn notice_preserves_wire_shape_nullable_fields_and_open_severity() {
4205 use serde_json::json;
4206
4207 let mut meta = Meta::new();
4208 meta.insert("source".into(), json!("fallback"));
4209 assert_eq!(
4210 serde_json::to_value(SessionUpdate::Notice(
4211 Notice::new(NoticeSeverity::Warning, "MCP server unavailable")
4212 .description("Continuing without it.")
4213 .meta(meta),
4214 ))
4215 .unwrap(),
4216 json!({
4217 "sessionUpdate": "notice",
4218 "severity": "warning",
4219 "title": "MCP server unavailable",
4220 "description": "Continuing without it.",
4221 "_meta": { "source": "fallback" }
4222 })
4223 );
4224
4225 assert_eq!(
4226 serde_json::to_value(SessionUpdate::Notice(Notice::new(
4227 NoticeSeverity::Info,
4228 "Indexing workspace",
4229 )))
4230 .unwrap(),
4231 json!({
4232 "sessionUpdate": "notice",
4233 "severity": "info",
4234 "title": "Indexing workspace"
4235 })
4236 );
4237
4238 let SessionUpdate::Notice(notice) = serde_json::from_value(json!({
4239 "sessionUpdate": "notice",
4240 "severity": "critical",
4241 "title": "Provider degraded",
4242 "description": null,
4243 "_meta": null
4244 }))
4245 .unwrap() else {
4246 panic!("expected notice");
4247 };
4248
4249 assert_eq!(
4250 notice.severity,
4251 NoticeSeverity::Other("critical".to_string())
4252 );
4253 assert_eq!(notice.description, None);
4254 assert_eq!(notice.meta, None);
4255 assert_eq!(
4256 serde_json::to_value(SessionUpdate::Notice(notice)).unwrap(),
4257 json!({
4258 "sessionUpdate": "notice",
4259 "severity": "critical",
4260 "title": "Provider degraded"
4261 })
4262 );
4263 }
4264
4265 #[test]
4266 fn notice_requires_string_severity_and_title() {
4267 use serde_json::json;
4268
4269 for malformed in [
4270 json!({
4271 "sessionUpdate": "notice",
4272 "severity": "warning",
4273 "title": 42
4274 }),
4275 json!({
4276 "sessionUpdate": "notice",
4277 "severity": 42,
4278 "title": "MCP server unavailable"
4279 }),
4280 json!({
4281 "sessionUpdate": "notice",
4282 "severity": "warning"
4283 }),
4284 json!({
4285 "sessionUpdate": "notice",
4286 "severity": "warning",
4287 "title": null
4288 }),
4289 json!({
4290 "sessionUpdate": "notice",
4291 "title": "MCP server unavailable"
4292 }),
4293 json!({
4294 "sessionUpdate": "notice",
4295 "severity": null,
4296 "title": "MCP server unavailable"
4297 }),
4298 ] {
4299 assert!(serde_json::from_str::<SessionUpdate>(&malformed.to_string()).is_err());
4300 assert!(serde_json::from_value::<SessionUpdate>(malformed).is_err());
4301 }
4302 }
4303
4304 #[test]
4305 fn notice_tolerates_invalid_optional_display_fields_and_custom_severity() {
4306 use serde_json::json;
4307
4308 let wire = json!({
4309 "sessionUpdate": "notice",
4310 "severity": "_custom",
4311 "title": "Provider degraded",
4312 "description": 42,
4313 "_meta": false
4314 });
4315 let SessionUpdate::Notice(notice) = serde_json::from_value(wire.clone()).unwrap() else {
4316 panic!("expected notice");
4317 };
4318 assert_eq!(
4320 serde_json::from_str::<SessionUpdate>(&wire.to_string()).unwrap(),
4321 SessionUpdate::Notice(notice.clone())
4322 );
4323 assert_eq!(notice.severity, NoticeSeverity::Other("_custom".into()));
4324 assert_eq!(notice.description, None);
4325 assert_eq!(notice.meta, None);
4326 assert_eq!(
4327 serde_json::to_value(notice).unwrap(),
4328 json!({"severity": "_custom", "title": "Provider degraded"})
4329 );
4330 }
4331
4332 #[cfg(feature = "schemars")]
4333 #[test]
4334 fn notice_schema_preserves_required_and_nullable_fields() {
4335 use serde_json::json;
4336
4337 let schema = serde_json::to_value(schemars::schema_for!(Notice)).unwrap();
4338 assert_eq!(schema["required"], json!(["severity", "title"]));
4339 assert_eq!(schema["properties"]["title"]["minLength"], 1);
4340 for field in ["description", "_meta"] {
4341 assert!(
4342 schema["properties"][field]["type"]
4343 .as_array()
4344 .unwrap()
4345 .contains(&json!("null"))
4346 );
4347 }
4348 let capabilities =
4349 serde_json::to_value(schemars::schema_for!(ClientSessionCapabilities)).unwrap();
4350 assert!(
4351 capabilities["properties"]["notices"]["anyOf"]
4352 .as_array()
4353 .unwrap()
4354 .contains(&json!({"type": "null"}))
4355 );
4356 assert!(
4357 !capabilities["required"]
4358 .as_array()
4359 .is_some_and(|required| required.contains(&json!("notices")))
4360 );
4361 }
4362
4363 #[test]
4364 fn notice_capability_advertises_support_only_when_present() {
4365 use serde_json::json;
4366
4367 let capabilities = ClientCapabilities::new()
4368 .session(ClientSessionCapabilities::new().notices(NoticeCapabilities::new()));
4369 let value = serde_json::to_value(&capabilities).unwrap();
4370 assert_eq!(value["session"], json!({ "notices": {} }));
4371 assert_eq!(
4372 serde_json::from_value::<ClientCapabilities>(value).unwrap(),
4373 capabilities
4374 );
4375
4376 for unsupported in [
4377 json!({}),
4378 json!({ "session": null }),
4379 json!({ "session": {} }),
4380 json!({ "session": { "notices": null } }),
4381 json!({ "session": { "notices": false } }),
4382 json!({ "session": { "notices": true } }),
4383 json!({ "session": { "notices": "supported" } }),
4384 ] {
4385 let capabilities: ClientCapabilities = serde_json::from_value(unsupported).unwrap();
4386 assert!(
4387 capabilities
4388 .session
4389 .and_then(|session| session.notices)
4390 .is_none()
4391 );
4392 }
4393
4394 assert_eq!(
4395 serde_json::to_value(
4396 ClientSessionCapabilities::new()
4397 .notices(NoticeCapabilities::new())
4398 .notices(None)
4399 )
4400 .unwrap(),
4401 json!({})
4402 );
4403 }
4404
4405 #[test]
4406 fn compaction_updates_preserve_patch_and_open_status_semantics() {
4407 use serde_json::json;
4408
4409 assert_eq!(
4410 serde_json::to_value(SessionUpdate::CompactionUpdate(CompactionUpdate::new(
4411 "cmp_001",
4412 CompactionStatus::InProgress,
4413 )))
4414 .unwrap(),
4415 json!({
4416 "sessionUpdate": "compaction_update",
4417 "compactionId": "cmp_001",
4418 "status": "in_progress"
4419 })
4420 );
4421
4422 let SessionUpdate::CompactionUpdate(update) = serde_json::from_value(json!({
4423 "sessionUpdate": "compaction_update",
4424 "compactionId": "cmp_001",
4425 "status": "paused",
4426 "summary": null,
4427 "error": null
4428 }))
4429 .unwrap() else {
4430 panic!("expected compaction update");
4431 };
4432 assert_eq!(update.status, CompactionStatus::Other("paused".into()));
4433 assert!(update.summary.is_null());
4434 assert!(update.error.is_null());
4435 assert!(update.meta.is_undefined());
4436 }
4437
4438 #[test]
4439 fn compaction_chunk_and_v1_capability_serialize() {
4440 use serde_json::json;
4441
4442 assert_eq!(
4443 serde_json::to_value(SessionUpdate::CompactionSummaryChunk(
4444 CompactionSummaryChunk::new(
4445 "cmp_001",
4446 ContentBlock::Text(crate::v1::TextContent::new("retained")),
4447 ),
4448 ))
4449 .unwrap(),
4450 json!({
4451 "sessionUpdate": "compaction_summary_chunk",
4452 "compactionId": "cmp_001",
4453 "content": { "type": "text", "text": "retained" }
4454 })
4455 );
4456 assert_eq!(
4457 serde_json::to_value(
4458 ClientSessionCapabilities::new().compaction(CompactionCapabilities::new())
4459 )
4460 .unwrap(),
4461 json!({ "compaction": {} })
4462 );
4463 let absent: ClientSessionCapabilities = serde_json::from_value(json!({})).unwrap();
4464 let null: ClientSessionCapabilities =
4465 serde_json::from_value(json!({ "compaction": null })).unwrap();
4466 assert!(absent.compaction.is_none());
4467 assert!(null.compaction.is_none());
4468 }
4469
4470 #[cfg(feature = "unstable_subagents")]
4471 #[test]
4472 fn test_subagent_updates_serialization() {
4473 use serde_json::json;
4474
4475 let announced = SessionUpdate::SubagentUpdate(SubagentUpdate::new("sess_child_1"));
4476 assert_eq!(
4477 serde_json::to_value(&announced).unwrap(),
4478 json!({
4479 "sessionUpdate": "subagent_update",
4480 "sessionId": "sess_child_1"
4481 })
4482 );
4483 assert_eq!(
4484 serde_json::from_value::<SessionUpdate>(serde_json::to_value(&announced).unwrap())
4485 .unwrap(),
4486 announced
4487 );
4488 let capable = SubagentUpdate::new("sess_child_1").capabilities(
4489 SubagentSessionCapabilities::new().cancel(SessionCancelCapabilities::new()),
4490 );
4491 assert_eq!(
4492 serde_json::to_value(capable).unwrap(),
4493 json!({
4494 "sessionId": "sess_child_1",
4495 "capabilities": {"cancel": {}}
4496 })
4497 );
4498 let minimal: SubagentUpdate =
4499 serde_json::from_value(json!({ "sessionId": "sess_child_3" })).unwrap();
4500 assert!(minimal.capabilities.is_undefined());
4501 assert!(minimal.state.is_undefined());
4502
4503 let nulls: SubagentUpdate = serde_json::from_value(json!({
4504 "sessionId": "sess_child_3",
4505 "capabilities": null,
4506 "state": null
4507 }))
4508 .unwrap();
4509 assert!(nulls.capabilities.is_null());
4510 assert!(nulls.state.is_null());
4511 assert_eq!(
4512 serde_json::to_value(nulls).unwrap(),
4513 json!({"sessionId": "sess_child_3", "capabilities": null, "state": null})
4514 );
4515 }
4516
4517 #[cfg(feature = "unstable_subagents")]
4518 #[test]
4519 fn test_subagent_display_metadata() {
4520 use serde_json::json;
4521
4522 let update = SubagentUpdate::new("child")
4523 .title("Test investigator".to_string())
4524 .description("Investigates platform-specific test failures.".to_string());
4525 let wire = json!({
4526 "sessionId": "child",
4527 "title": "Test investigator",
4528 "description": "Investigates platform-specific test failures."
4529 });
4530 assert_eq!(serde_json::to_value(&update).unwrap(), wire);
4531 assert_eq!(
4532 serde_json::from_value::<SubagentUpdate>(wire).unwrap(),
4533 update
4534 );
4535
4536 let minimal = SubagentUpdate::new("child");
4537 assert_eq!(
4538 serde_json::to_value(&minimal).unwrap(),
4539 json!({"sessionId": "child"})
4540 );
4541 let cleared: SubagentUpdate = serde_json::from_value(json!({
4542 "sessionId": "child", "title": null, "description": null
4543 }))
4544 .unwrap();
4545 assert!(cleared.title.is_null());
4546 assert!(cleared.description.is_null());
4547 assert_eq!(
4548 serde_json::to_value(cleared).unwrap(),
4549 json!({"sessionId": "child", "title": null, "description": null})
4550 );
4551 let invalid: SubagentUpdate = serde_json::from_value(json!({
4552 "sessionId": "child", "title": false, "description": false
4553 }))
4554 .unwrap();
4555 assert_eq!(invalid, minimal);
4556
4557 let title_only: SubagentUpdate = serde_json::from_value(json!({
4558 "sessionId": "child", "title": "Updated title"
4559 }))
4560 .unwrap();
4561 assert_eq!(
4562 title_only.title.value().map(String::as_str),
4563 Some("Updated title")
4564 );
4565 assert!(title_only.description.is_undefined());
4566 }
4567
4568 #[cfg(feature = "unstable_subagents")]
4569 #[test]
4570 fn subagent_directories_use_strict_patch_semantics() {
4571 use serde_json::json;
4572
4573 let update = SubagentUpdate::new("child")
4574 .cwd("/home/user/project/.worktrees/investigator")
4575 .additional_directories(vec![]);
4576 let wire = json!({
4577 "sessionId": "child",
4578 "cwd": "/home/user/project/.worktrees/investigator",
4579 "additionalDirectories": []
4580 });
4581 assert_eq!(serde_json::to_value(&update).unwrap(), wire);
4582 assert_eq!(
4583 serde_json::from_value::<SubagentUpdate>(wire).unwrap(),
4584 update
4585 );
4586
4587 let inherited: SubagentUpdate =
4588 serde_json::from_value(json!({"sessionId": "child"})).unwrap();
4589 assert!(inherited.cwd.is_undefined());
4590 assert!(inherited.additional_directories.is_undefined());
4591
4592 let cleared_wire = json!({
4593 "sessionId": "child", "cwd": null, "additionalDirectories": null
4594 });
4595 let cleared: SubagentUpdate = serde_json::from_value(cleared_wire.clone()).unwrap();
4596 assert!(cleared.cwd.is_null());
4597 assert!(cleared.additional_directories.is_null());
4598 assert_eq!(serde_json::to_value(cleared).unwrap(), cleared_wire);
4599
4600 for malformed in [
4601 json!({"sessionId": "child", "cwd": false}),
4602 json!({"sessionId": "child", "additionalDirectories": "/tmp/wt"}),
4603 json!({"sessionId": "child", "additionalDirectories": ["/tmp/wt", false]}),
4604 ] {
4605 assert!(
4606 serde_json::from_value::<SubagentUpdate>(malformed.clone()).is_err(),
4607 "{malformed}"
4608 );
4609 }
4610 }
4611
4612 #[cfg(feature = "unstable_subagents")]
4613 #[test]
4614 fn subagent_patch_fields_preserve_omitted_null_and_concrete() {
4615 use serde_json::json;
4616
4617 let omitted = SubagentUpdate::new("child");
4618 for field in ["title", "description", "capabilities", "state", "_meta"] {
4619 let wire = json!({"sessionId": "child", field: null});
4620 let decoded: SubagentUpdate = serde_json::from_value(wire.clone()).unwrap();
4621 assert_eq!(serde_json::to_value(decoded).unwrap(), wire, "{field}");
4622 let malformed = json!({"sessionId": "child", field: false});
4623 let decoded: SubagentUpdate = serde_json::from_value(malformed).unwrap();
4624 assert_eq!(decoded, omitted, "{field}");
4625 }
4626 let concrete = json!({
4627 "sessionId": "child", "title": "Investigator",
4628 "description": "Tests", "capabilities": {"cancel": {}},
4629 "state": {"state": "running"}, "_meta": {"source": "parent"}
4630 });
4631 let decoded: SubagentUpdate = serde_json::from_value(concrete.clone()).unwrap();
4632 assert!(decoded.title.value().is_some());
4633 assert!(decoded.description.value().is_some());
4634 assert!(decoded.capabilities.value().is_some());
4635 assert!(decoded.state.value().is_some());
4636 assert!(decoded.meta.value().is_some());
4637 assert_eq!(serde_json::to_value(decoded).unwrap(), concrete);
4638 let cleared = SubagentUpdate::new("child")
4639 .title(None)
4640 .description(None)
4641 .capabilities(None)
4642 .state(None)
4643 .meta(None);
4644 assert_eq!(
4645 serde_json::to_value(cleared).unwrap(),
4646 json!({
4647 "sessionId": "child", "title": null, "description": null,
4648 "capabilities": null, "state": null, "_meta": null
4649 })
4650 );
4651 }
4652
4653 #[cfg(feature = "unstable_subagents")]
4654 #[test]
4655 fn test_subagent_notification_keeps_parent_and_child_ids_nested() {
4656 use serde_json::json;
4657
4658 let notification = SessionNotification::new(
4659 "parent",
4660 SessionUpdate::SubagentUpdate(SubagentUpdate::new("child")),
4661 );
4662 let wire = json!({
4663 "sessionId": "parent",
4664 "update": {
4665 "sessionUpdate": "subagent_update",
4666 "sessionId": "child"
4667 }
4668 });
4669 assert_eq!(serde_json::to_value(¬ification).unwrap(), wire);
4670 assert_eq!(
4671 serde_json::from_value::<SessionNotification>(wire).unwrap(),
4672 notification
4673 );
4674 for malformed in [
4676 json!({"sessionId": "parent", "update": {"sessionUpdate": "subagent_update"}}),
4677 json!({"update": {"sessionUpdate": "subagent_update", "sessionId": "child"}}),
4678 ] {
4679 assert!(serde_json::from_value::<SessionNotification>(malformed).is_err());
4680 }
4681 }
4682
4683 #[cfg(feature = "unstable_subagents")]
4684 #[test]
4685 fn test_subagent_states_round_trip() {
4686 use serde_json::json;
4687
4688 for (wire_state, expected) in [
4689 (
4690 json!({"state": "running"}),
4691 StateUpdate::Running(RunningStateUpdate::new()),
4692 ),
4693 (
4694 json!({"state": "idle", "stopReason": "end_turn"}),
4695 StateUpdate::Idle(IdleStateUpdate::new().stop_reason(IdleStopReason::EndTurn)),
4696 ),
4697 (
4698 json!({"state": "requires_action"}),
4699 StateUpdate::RequiresAction(RequiresActionStateUpdate::new()),
4700 ),
4701 (
4702 json!({"state": "unknown"}),
4703 StateUpdate::Unknown(UnknownStateUpdate::new()),
4704 ),
4705 (
4706 json!({"state": "_waiting", "detail": {"ticket": 7}}),
4707 StateUpdate::Other(OtherStateUpdate::new(
4708 "_waiting",
4709 [("detail".into(), json!({"ticket": 7}))].into(),
4710 )),
4711 ),
4712 (
4713 json!({"state": "paused", "retryAt": 42}),
4714 StateUpdate::Other(OtherStateUpdate::new(
4715 "paused",
4716 [("retryAt".into(), json!(42))].into(),
4717 )),
4718 ),
4719 ] {
4720 let wire = json!({
4721 "sessionUpdate": "subagent_update",
4722 "sessionId": "sess_child",
4723 "state": wire_state
4724 });
4725 let parsed: SessionUpdate = serde_json::from_value(wire.clone()).unwrap();
4726 let SessionUpdate::SubagentUpdate(update) = &parsed else {
4727 panic!("expected subagent update");
4728 };
4729 assert_eq!(update.state.value(), Some(&expected));
4730 assert_eq!(serde_json::to_value(parsed).unwrap(), wire);
4731 }
4732
4733 for reason in [
4734 "end_turn",
4735 "max_tokens",
4736 "max_turn_requests",
4737 "refusal",
4738 "cancelled",
4739 ] {
4740 let state: StateUpdate = serde_json::from_value(json!({
4741 "state": "idle", "stopReason": reason
4742 }))
4743 .unwrap();
4744 assert_eq!(serde_json::to_value(state).unwrap()["stopReason"], reason);
4745 }
4746
4747 for state in [
4750 StateUpdate::Running(RunningStateUpdate::new()),
4751 StateUpdate::RequiresAction(RequiresActionStateUpdate::new()),
4752 StateUpdate::Unknown(UnknownStateUpdate::new()),
4753 StateUpdate::Running(RunningStateUpdate::new()),
4754 StateUpdate::Idle(IdleStateUpdate::new().stop_reason(IdleStopReason::EndTurn)),
4755 StateUpdate::Unknown(UnknownStateUpdate::new()),
4756 StateUpdate::Running(RunningStateUpdate::new()),
4757 ] {
4758 let update = SubagentUpdate::new("sess_child").state(state.clone());
4759 let wire = serde_json::to_value(&update).unwrap();
4760 assert_eq!(wire["sessionId"], "sess_child");
4761 assert_eq!(
4762 serde_json::from_value::<SubagentUpdate>(wire)
4763 .unwrap()
4764 .state,
4765 MaybeUndefined::Value(state)
4766 );
4767 }
4768 assert_eq!(
4769 serde_json::to_value(
4770 SubagentUpdate::new("sess_child").state(StateUpdate::Idle(IdleStateUpdate::new()))
4771 )
4772 .unwrap()["state"],
4773 json!({"state": "idle"})
4774 );
4775 }
4776
4777 #[cfg(feature = "unstable_subagents")]
4778 #[test]
4779 fn subagent_idle_snapshot_reports_child_failure() {
4780 use serde_json::json;
4781
4782 let update = SubagentUpdate::new("sess_child").state(StateUpdate::Idle(
4783 IdleStateUpdate::new().stop_reason(IdleStopReason::from(
4784 ErrorStopReason::new().error(Error::auth_required()),
4785 )),
4786 ));
4787 let wire = json!({
4788 "sessionId": "sess_child",
4789 "state": {
4790 "state": "idle",
4791 "stopReason": "error",
4792 "error": { "code": -32000, "message": "Authentication required" }
4793 }
4794 });
4795 assert_eq!(serde_json::to_value(&update).unwrap(), wire);
4796 assert_eq!(
4797 serde_json::from_value::<SubagentUpdate>(wire).unwrap(),
4798 update
4799 );
4800
4801 let without_details = StateUpdate::Idle(
4803 IdleStateUpdate::new().stop_reason(IdleStopReason::from(ErrorStopReason::new())),
4804 );
4805 for error in [None, Some(json!(null)), Some(json!("failed"))] {
4806 let mut wire = json!({ "state": "idle", "stopReason": "error" });
4807 if let Some(error) = error {
4808 wire["error"] = error;
4809 }
4810 assert_eq!(
4811 serde_json::from_value::<StateUpdate>(wire).unwrap(),
4812 without_details
4813 );
4814 }
4815
4816 let parsed: StateUpdate = serde_json::from_value(json!({
4819 "state": "idle",
4820 "stopReason": "end_turn",
4821 "error": { "code": -32603, "message": "Internal error" }
4822 }))
4823 .unwrap();
4824 assert_eq!(
4825 parsed,
4826 StateUpdate::Idle(IdleStateUpdate::new().stop_reason(IdleStopReason::EndTurn))
4827 );
4828
4829 let meta: Meta = [("source".into(), json!("worker"))].into_iter().collect();
4832 for stop_reason in [None, Some(json!(null)), Some(json!(true))] {
4833 let mut wire = json!({ "state": "idle", "_meta": { "source": "worker" } });
4834 if let Some(stop_reason) = stop_reason {
4835 wire["stopReason"] = stop_reason;
4836 }
4837 assert_eq!(
4838 serde_json::from_value::<StateUpdate>(wire).unwrap(),
4839 StateUpdate::Idle(IdleStateUpdate::new().meta(meta.clone()))
4840 );
4841 }
4842
4843 for stop_reason in ["_paused", "not_a_reason"] {
4846 let wire = json!({
4847 "state": "idle",
4848 "stopReason": stop_reason,
4849 "resumeAfter": 30,
4850 "_meta": { "source": "worker" }
4851 });
4852 let parsed: StateUpdate = serde_json::from_value(wire.clone()).unwrap();
4853 assert_eq!(
4854 parsed,
4855 StateUpdate::Idle(
4856 IdleStateUpdate::new()
4857 .stop_reason(IdleStopReason::Other(OtherStopReason::new(
4858 stop_reason,
4859 [("resumeAfter".into(), json!(30))].into_iter().collect(),
4860 )))
4861 .meta(meta.clone())
4862 )
4863 );
4864 assert_eq!(serde_json::to_value(&parsed).unwrap(), wire);
4865 }
4866 }
4867
4868 #[cfg(feature = "unstable_subagents")]
4869 #[test]
4870 fn subagent_known_stop_reasons_are_never_read_as_other() {
4871 use serde_json::json;
4872
4873 let known = [
4874 IdleStopReason::EndTurn,
4875 IdleStopReason::MaxTokens,
4876 IdleStopReason::MaxTurnRequests,
4877 IdleStopReason::Refusal,
4878 IdleStopReason::Cancelled,
4879 IdleStopReason::Error(ErrorStopReason::new()),
4880 ];
4881 for stop_reason in &known {
4883 match stop_reason {
4884 IdleStopReason::EndTurn
4885 | IdleStopReason::MaxTokens
4886 | IdleStopReason::MaxTurnRequests
4887 | IdleStopReason::Refusal
4888 | IdleStopReason::Cancelled
4889 | IdleStopReason::Error(_)
4890 | IdleStopReason::Other(_) => {}
4891 }
4892 }
4893
4894 for stop_reason in known {
4895 let state = StateUpdate::Idle(IdleStateUpdate::new().stop_reason(stop_reason));
4896 let wire = serde_json::to_value(&state).unwrap();
4897 assert_eq!(
4898 serde_json::from_value::<StateUpdate>(wire.clone()).unwrap(),
4899 state
4900 );
4901 assert!(
4902 serde_json::from_value::<OtherStopReason>(
4903 json!({ "stopReason": wire["stopReason"] })
4904 )
4905 .is_err(),
4906 "{} must not be accepted as a custom stop reason",
4907 wire["stopReason"]
4908 );
4909 }
4910 }
4911
4912 #[cfg(feature = "unstable_subagents")]
4913 #[test]
4914 fn subagent_unknown_stop_reason_leaves_usage_on_the_snapshot() {
4915 use serde_json::json;
4916
4917 let usage = json!({ "totalTokens": 10, "inputTokens": 6, "outputTokens": 4 });
4918 let wire = json!({ "state": "idle", "stopReason": "_paused", "usage": usage });
4919 let StateUpdate::Idle(idle) = serde_json::from_value(wire).unwrap() else {
4920 panic!("expected idle state snapshot");
4921 };
4922 let Some(IdleStopReason::Other(other)) = &idle.stop_reason else {
4923 panic!("expected a custom stop reason");
4924 };
4925 assert!(other.fields.is_empty());
4926
4927 #[cfg(feature = "unstable_end_turn_token_usage")]
4928 {
4929 assert!(idle.usage.is_some());
4930 assert_eq!(serde_json::to_value(&idle).unwrap()["usage"], usage);
4931 }
4932 #[cfg(not(feature = "unstable_end_turn_token_usage"))]
4935 assert!(serde_json::to_value(&idle).unwrap().get("usage").is_none());
4936 }
4937
4938 #[cfg(feature = "unstable_subagents")]
4939 #[test]
4940 fn test_subagent_unknown_activity_metadata_and_capabilities() {
4941 use serde_json::json;
4942
4943 let update = SubagentUpdate::new("child").state(StateUpdate::Unknown(
4944 UnknownStateUpdate::new().meta(
4945 [("source".into(), json!("worker"))]
4946 .into_iter()
4947 .collect::<Meta>(),
4948 ),
4949 ));
4950 let wire = json!({
4951 "sessionId": "child",
4952 "state": {
4953 "state": "unknown",
4954 "_meta": { "source": "worker" }
4955 }
4956 });
4957 assert_eq!(serde_json::to_value(&update).unwrap(), wire);
4958 assert_eq!(
4959 serde_json::from_value::<SubagentUpdate>(wire).unwrap(),
4960 update
4961 );
4962 assert!(update.capabilities.is_undefined());
4964
4965 for meta in [json!(null), json!(false)] {
4966 let state: StateUpdate = serde_json::from_value(json!({
4967 "state": "unknown",
4968 "_meta": meta
4969 }))
4970 .unwrap();
4971 assert_eq!(state, StateUpdate::Unknown(UnknownStateUpdate::new()));
4972 assert_eq!(
4973 serde_json::to_value(state).unwrap(),
4974 json!({"state": "unknown"})
4975 );
4976 }
4977 assert!(serde_json::from_value::<OtherStateUpdate>(json!({"state": "unknown"})).is_err());
4979 }
4980
4981 #[cfg(feature = "unstable_subagents")]
4982 #[test]
4983 fn test_subagent_malformed_optional_state() {
4984 use serde_json::json;
4985
4986 for malformed in [json!("running"), json!({}), json!({"state": 4})] {
4987 assert!(serde_json::from_value::<StateUpdate>(malformed.clone()).is_err());
4988 let update: SubagentUpdate = serde_json::from_value(json!({
4989 "sessionId": "child", "state": malformed
4990 }))
4991 .unwrap();
4992 assert!(update.state.is_undefined());
4993 }
4994 let bad_reason: StateUpdate =
4995 serde_json::from_value(json!({"state": "idle", "stopReason": false})).unwrap();
4996 assert_eq!(bad_reason, StateUpdate::Idle(IdleStateUpdate::new()));
4997 let null_reason: StateUpdate =
4998 serde_json::from_value(json!({"state": "idle", "stopReason": null})).unwrap();
4999 assert_eq!(null_reason, StateUpdate::Idle(IdleStateUpdate::new()));
5000 }
5001
5002 #[cfg(all(feature = "unstable_subagents", feature = "unstable_protocol_v2"))]
5003 #[test]
5004 fn test_subagent_state_v2_wire_parity() {
5005 use serde_json::json;
5006
5007 for wire in [
5008 json!({"state": "running"}),
5009 json!({"state": "idle", "stopReason": "cancelled"}),
5010 json!({"state": "requires_action"}),
5011 json!({"state": "unknown"}),
5012 json!({"state": "unknown", "_meta": {"source": "worker"}}),
5013 json!({"state": "_waiting", "detail": {"ticket": 7}}),
5014 ] {
5015 let v1: StateUpdate = serde_json::from_value(wire.clone()).unwrap();
5016 let v2: crate::v2::StateUpdate = serde_json::from_value(wire.clone()).unwrap();
5017 assert_eq!(serde_json::to_value(v1).unwrap(), wire);
5018 assert_eq!(serde_json::to_value(v2).unwrap(), wire);
5019 }
5020 }
5021
5022 #[cfg(feature = "unstable_subagents")]
5023 #[test]
5024 fn test_subagent_capability_semantics() {
5025 use serde_json::json;
5026
5027 let capabilities =
5028 serde_json::to_value(ClientCapabilities::new().subagents(SubagentCapabilities::new()))
5029 .unwrap();
5030 assert_eq!(capabilities["subagents"], json!({}));
5031
5032 let omitted: ClientCapabilities = serde_json::from_value(json!({})).unwrap();
5033 assert!(omitted.subagents.is_none());
5034
5035 let null: ClientCapabilities =
5036 serde_json::from_value(json!({ "subagents": null })).unwrap();
5037 assert!(null.subagents.is_none());
5038 assert_eq!(null, omitted);
5039 let serialized = serde_json::to_value(null).unwrap();
5040 assert_eq!(serialized, serde_json::to_value(omitted).unwrap());
5041 assert!(serialized.get("subagents").is_none());
5042
5043 for wire in [
5044 json!({}),
5045 json!({"cancel": null}),
5046 json!({"cancel": true}),
5047 json!({"cancel": false}),
5048 ] {
5049 let child: SubagentSessionCapabilities = serde_json::from_value(wire).unwrap();
5050 assert!(child.cancel.is_none());
5051 assert_eq!(serde_json::to_value(child).unwrap(), json!({}));
5052 }
5053 let enabled = SubagentSessionCapabilities::new().cancel(SessionCancelCapabilities::new());
5054 assert_eq!(
5055 serde_json::to_value(&enabled).unwrap(),
5056 json!({"cancel": {}})
5057 );
5058 assert_eq!(
5059 serde_json::from_value::<SubagentSessionCapabilities>(json!({"cancel": {}})).unwrap(),
5060 enabled
5061 );
5062 let meta: Meta = [("source".into(), json!("worker"))].into_iter().collect();
5063 let with_meta =
5064 SubagentSessionCapabilities::new().cancel(SessionCancelCapabilities::new().meta(meta));
5065 let wire = json!({"cancel": {"_meta": {"source": "worker"}}});
5066 assert_eq!(serde_json::to_value(&with_meta).unwrap(), wire);
5067 assert_eq!(
5068 serde_json::from_value::<SubagentSessionCapabilities>(wire).unwrap(),
5069 with_meta
5070 );
5071 let removed = SubagentSessionCapabilities::new();
5073 assert_eq!(serde_json::to_value(&removed).unwrap(), json!({}));
5074 assert!(removed.cancel.is_none());
5075 assert!(enabled.cancel.is_some());
5076 assert!(enabled.cancel(None).cancel.is_none());
5077 let replacement = SubagentUpdate::new("child").capabilities(removed);
5078 assert_eq!(
5079 serde_json::to_value(replacement).unwrap(),
5080 json!({"sessionId": "child", "capabilities": {}})
5081 );
5082 }
5083
5084 #[cfg(all(feature = "unstable_subagents", feature = "schemars"))]
5085 #[test]
5086 fn test_subagent_capability_schema_is_optional_and_nullable() {
5087 use serde_json::json;
5088
5089 let schema = serde_json::to_value(schemars::schema_for!(ClientCapabilities)).unwrap();
5090 let variants = schema["properties"]["subagents"]["anyOf"]
5091 .as_array()
5092 .expect("subagents must allow the capability object or null");
5093 assert!(variants.contains(&json!({"type": "null"})));
5094 assert!(
5095 !schema["required"]
5096 .as_array()
5097 .is_some_and(|required| required.contains(&json!("subagents")))
5098 );
5099 let child =
5100 serde_json::to_value(schemars::schema_for!(SubagentSessionCapabilities)).unwrap();
5101 let variants = child["properties"]["cancel"]["anyOf"]
5102 .as_array()
5103 .expect("cancel must allow the capability object or null");
5104 assert!(variants.contains(&json!({"type": "null"})));
5105 assert!(
5106 variants
5107 .iter()
5108 .any(|variant| variant["type"] == "object" || variant.get("$ref").is_some())
5109 );
5110 assert!(!variants.iter().any(|variant| variant["type"] == "boolean"));
5111 let cancel =
5112 serde_json::to_value(schemars::schema_for!(SessionCancelCapabilities)).unwrap();
5113 assert_eq!(cancel["type"], "object");
5114 assert!(
5115 !child["required"]
5116 .as_array()
5117 .is_some_and(|required| required.contains(&json!("cancel")))
5118 );
5119 }
5120
5121 #[test]
5122 fn test_elicitation_capability_semantics() {
5123 use serde_json::json;
5124
5125 let unsupported: ClientCapabilities = serde_json::from_value(json!({})).unwrap();
5126 assert!(unsupported.elicitation.is_none());
5127
5128 let null: ClientCapabilities =
5129 serde_json::from_value(json!({ "elicitation": null })).unwrap();
5130 assert!(null.elicitation.is_none());
5131
5132 let malformed: ClientCapabilities =
5133 serde_json::from_value(json!({ "elicitation": false })).unwrap();
5134 assert!(malformed.elicitation.is_none());
5135
5136 let empty: ClientCapabilities =
5137 serde_json::from_value(json!({ "elicitation": {} })).unwrap();
5138 let empty = empty.elicitation.expect("present capability");
5139 assert!(!empty.supports_form());
5140 assert!(!empty.supports_url());
5141
5142 let form_only: ClientCapabilities = serde_json::from_value(json!({
5143 "elicitation": { "form": {} }
5144 }))
5145 .unwrap();
5146 let form_only = form_only.elicitation.expect("advertised capability");
5147 assert!(form_only.supports_form());
5148 assert!(!form_only.supports_url());
5149
5150 let url_only: ClientCapabilities = serde_json::from_value(json!({
5151 "elicitation": { "url": {} }
5152 }))
5153 .unwrap();
5154 let url_only = url_only.elicitation.expect("advertised capability");
5155 assert!(!url_only.supports_form());
5156 assert!(url_only.supports_url());
5157
5158 let both: ClientCapabilities = serde_json::from_value(json!({
5159 "elicitation": { "form": {}, "url": {} }
5160 }))
5161 .unwrap();
5162 let both = both.elicitation.expect("advertised capability");
5163 assert!(both.supports_form());
5164 assert!(both.supports_url());
5165 }
5166
5167 #[test]
5168 fn test_elicitation_method_routing_and_envelopes() {
5169 use serde_json::json;
5170
5171 assert_eq!(CLIENT_METHOD_NAMES.elicitation_create, "elicitation/create");
5172 assert_eq!(
5173 CLIENT_METHOD_NAMES.elicitation_complete,
5174 "elicitation/complete"
5175 );
5176
5177 let request = AgentRequest::CreateElicitationRequest(CreateElicitationRequest::new(
5178 crate::v1::ElicitationFormMode::new(
5179 crate::v1::ElicitationSessionScope::new("sess_1"),
5180 crate::v1::ElicitationSchema::new(),
5181 ),
5182 "Choose a value",
5183 ));
5184 assert_eq!(request.method(), "elicitation/create");
5185 let method = Arc::from(request.method());
5186 let request = crate::v1::JsonRpcMessage::wrap(crate::v1::Request {
5187 id: crate::v1::RequestId::Number(7),
5188 method,
5189 params: Some(request),
5190 });
5191 assert_eq!(
5192 serde_json::to_value(request).unwrap(),
5193 json!({
5194 "jsonrpc": "2.0",
5195 "id": 7,
5196 "method": "elicitation/create",
5197 "params": {
5198 "mode": "form",
5199 "sessionId": "sess_1",
5200 "message": "Choose a value",
5201 "requestedSchema": { "type": "object", "properties": {} }
5202 }
5203 })
5204 );
5205
5206 let notification = AgentNotification::CompleteElicitationNotification(
5207 CompleteElicitationNotification::new("elic_1"),
5208 );
5209 assert_eq!(notification.method(), "elicitation/complete");
5210 let method = Arc::from(notification.method());
5211 let notification = crate::v1::JsonRpcMessage::wrap(crate::v1::Notification {
5212 method,
5213 params: Some(notification),
5214 });
5215 assert_eq!(
5216 serde_json::to_value(notification).unwrap(),
5217 json!({
5218 "jsonrpc": "2.0",
5219 "method": "elicitation/complete",
5220 "params": { "elicitationId": "elic_1" }
5221 })
5222 );
5223 }
5224
5225 #[test]
5226 fn test_client_capabilities_default_on_malformed_values() {
5227 use serde_json::json;
5228
5229 let capabilities: ClientCapabilities = serde_json::from_value(json!({
5230 "fs": {
5231 "readTextFile": "yes",
5232 "writeTextFile": true
5233 },
5234 "terminal": {}
5235 }))
5236 .unwrap();
5237
5238 assert!(!capabilities.fs.read_text_file);
5239 assert!(capabilities.fs.write_text_file);
5240 assert!(!capabilities.terminal);
5241
5242 let capabilities: ClientCapabilities = serde_json::from_value(json!({
5243 "fs": false
5244 }))
5245 .unwrap();
5246 assert_eq!(capabilities.fs, FileSystemCapabilities::default());
5247
5248 {
5249 let capabilities: ClientCapabilities = serde_json::from_value(json!({
5250 "auth": false
5251 }))
5252 .unwrap();
5253 assert_eq!(capabilities.auth, AuthCapabilities::default());
5254
5255 let capabilities: AuthCapabilities = serde_json::from_value(json!({
5256 "terminal": {}
5257 }))
5258 .unwrap();
5259 assert!(!capabilities.terminal);
5260 }
5261 }
5262
5263 #[test]
5264 fn test_serialization_behavior() {
5265 use serde_json::json;
5266
5267 assert_eq!(
5268 serde_json::from_value::<SessionInfoUpdate>(json!({})).unwrap(),
5269 SessionInfoUpdate {
5270 title: MaybeUndefined::Undefined,
5271 updated_at: MaybeUndefined::Undefined,
5272 meta: None
5273 }
5274 );
5275 assert_eq!(
5276 serde_json::from_value::<SessionInfoUpdate>(json!({"title": null, "updatedAt": null}))
5277 .unwrap(),
5278 SessionInfoUpdate {
5279 title: MaybeUndefined::Null,
5280 updated_at: MaybeUndefined::Null,
5281 meta: None
5282 }
5283 );
5284 assert_eq!(
5285 serde_json::from_value::<SessionInfoUpdate>(
5286 json!({"title": "title", "updatedAt": "timestamp"})
5287 )
5288 .unwrap(),
5289 SessionInfoUpdate {
5290 title: MaybeUndefined::Value("title".to_string()),
5291 updated_at: MaybeUndefined::Value("timestamp".to_string()),
5292 meta: None
5293 }
5294 );
5295
5296 assert_eq!(
5297 serde_json::to_value(SessionInfoUpdate::new()).unwrap(),
5298 json!({})
5299 );
5300 assert_eq!(
5301 serde_json::to_value(SessionInfoUpdate::new().title("title")).unwrap(),
5302 json!({"title": "title"})
5303 );
5304 assert_eq!(
5305 serde_json::to_value(SessionInfoUpdate::new().title(None)).unwrap(),
5306 json!({"title": null})
5307 );
5308 assert_eq!(
5309 serde_json::to_value(
5310 SessionInfoUpdate::new()
5311 .title("title")
5312 .title(MaybeUndefined::Undefined)
5313 )
5314 .unwrap(),
5315 json!({})
5316 );
5317 }
5318
5319 #[test]
5320 fn test_content_chunk_message_id_serialization() {
5321 use serde_json::json;
5322
5323 assert_eq!(
5324 serde_json::to_value(SessionUpdate::AgentMessageChunk(ContentChunk::new(
5325 ContentBlock::Text(crate::v1::TextContent::new("Hello"))
5326 )))
5327 .unwrap(),
5328 json!({
5329 "sessionUpdate": "agent_message_chunk",
5330 "content": {
5331 "type": "text",
5332 "text": "Hello"
5333 }
5334 })
5335 );
5336
5337 assert_eq!(
5338 serde_json::to_value(SessionUpdate::AgentMessageChunk(
5339 ContentChunk::new(ContentBlock::Text(crate::v1::TextContent::new("Hello")))
5340 .message_id("msg_agent_c42b9")
5341 ))
5342 .unwrap(),
5343 json!({
5344 "sessionUpdate": "agent_message_chunk",
5345 "messageId": "msg_agent_c42b9",
5346 "content": {
5347 "type": "text",
5348 "text": "Hello"
5349 }
5350 })
5351 );
5352
5353 let SessionUpdate::AgentMessageChunk(chunk) = serde_json::from_value(json!({
5354 "sessionUpdate": "agent_message_chunk",
5355 "messageId": null,
5356 "content": {
5357 "type": "text",
5358 "text": "Hello"
5359 }
5360 }))
5361 .unwrap() else {
5362 panic!("expected agent message chunk");
5363 };
5364
5365 assert_eq!(chunk.message_id, None);
5366 }
5367
5368 #[test]
5369 fn test_usage_update_serialization() {
5370 use serde_json::json;
5371
5372 assert_eq!(
5373 serde_json::to_value(SessionUpdate::UsageUpdate(UsageUpdate::new(
5374 53_000, 200_000
5375 )))
5376 .unwrap(),
5377 json!({
5378 "sessionUpdate": "usage_update",
5379 "used": 53000,
5380 "size": 200_000
5381 })
5382 );
5383
5384 assert_eq!(
5385 serde_json::to_value(SessionUpdate::UsageUpdate(
5386 UsageUpdate::new(53_000, 200_000).cost(Cost::new(0.045, "USD"))
5387 ))
5388 .unwrap(),
5389 json!({
5390 "sessionUpdate": "usage_update",
5391 "used": 53000,
5392 "size": 200_000,
5393 "cost": {
5394 "amount": 0.045,
5395 "currency": "USD"
5396 }
5397 })
5398 );
5399
5400 let SessionUpdate::UsageUpdate(update) = serde_json::from_value(json!({
5401 "sessionUpdate": "usage_update",
5402 "used": 53000,
5403 "size": 200_000,
5404 "cost": null
5405 }))
5406 .unwrap() else {
5407 panic!("expected usage update");
5408 };
5409
5410 assert_eq!(update.cost, None);
5411 }
5412
5413 #[test]
5414 fn test_client_capabilities_boolean_config_options_serialization() {
5415 use serde_json::json;
5416
5417 let capabilities = ClientCapabilities::new().session(
5418 ClientSessionCapabilities::new().config_options(
5419 SessionConfigOptionsCapabilities::new()
5420 .boolean(BooleanConfigOptionCapabilities::new()),
5421 ),
5422 );
5423 let json = serde_json::to_value(&capabilities).unwrap();
5424
5425 assert_eq!(json["session"]["configOptions"]["boolean"], json!({}));
5426
5427 let omitted: ClientCapabilities = serde_json::from_value(json!({})).unwrap();
5428 assert!(omitted.session.is_none());
5429
5430 let null_session: ClientCapabilities = serde_json::from_value(json!({
5431 "session": null
5432 }))
5433 .unwrap();
5434 assert!(null_session.session.is_none());
5435
5436 let null_config_options: ClientCapabilities = serde_json::from_value(json!({
5437 "session": {
5438 "configOptions": null
5439 }
5440 }))
5441 .unwrap();
5442 assert!(
5443 null_config_options
5444 .session
5445 .and_then(|session| session.config_options)
5446 .is_none()
5447 );
5448
5449 let null_boolean: ClientCapabilities = serde_json::from_value(json!({
5450 "session": {
5451 "configOptions": {
5452 "boolean": null
5453 }
5454 }
5455 }))
5456 .unwrap();
5457 assert!(
5458 null_boolean
5459 .session
5460 .and_then(|session| session.config_options)
5461 .and_then(|config_options| config_options.boolean)
5462 .is_none()
5463 );
5464 }
5465
5466 #[cfg(feature = "unstable_plan_operations")]
5467 #[test]
5468 fn test_plan_operations_serialization() {
5469 use serde_json::json;
5470
5471 use crate::v1::{PlanEntry, PlanEntryPriority, PlanEntryStatus, PlanUpdateContent};
5472
5473 let plan_update = SessionUpdate::PlanUpdate(PlanUpdate::new(PlanUpdateContent::items(
5474 "plan-1",
5475 vec![PlanEntry::new(
5476 "Step 1",
5477 PlanEntryPriority::High,
5478 PlanEntryStatus::Pending,
5479 )],
5480 )));
5481
5482 assert_eq!(
5483 serde_json::to_value(plan_update).unwrap(),
5484 json!({
5485 "sessionUpdate": "plan_update",
5486 "plan": {
5487 "type": "items",
5488 "planId": "plan-1",
5489 "entries": [
5490 {
5491 "content": "Step 1",
5492 "priority": "high",
5493 "status": "pending"
5494 }
5495 ]
5496 }
5497 })
5498 );
5499
5500 assert_eq!(
5501 serde_json::to_value(SessionUpdate::PlanRemoved(PlanRemoved::new("plan-1"))).unwrap(),
5502 json!({
5503 "sessionUpdate": "plan_removed",
5504 "planId": "plan-1"
5505 })
5506 );
5507
5508 let capabilities = ClientCapabilities::new().plan(PlanCapabilities::new());
5509 let json = serde_json::to_value(&capabilities).unwrap();
5510 assert_eq!(json["plan"], json!({}));
5511
5512 assert_eq!(
5513 serde_json::from_value::<ClientCapabilities>(json!({ "plan": null }))
5514 .unwrap()
5515 .plan,
5516 None
5517 );
5518 }
5519
5520 #[cfg(feature = "unstable_mcp_over_acp")]
5521 #[test]
5522 fn test_agent_mcp_request_method_names() {
5523 use serde_json::json;
5524
5525 let params: serde_json::Map<String, serde_json::Value> =
5526 [("cursor".to_string(), json!("abc"))].into_iter().collect();
5527
5528 assert_eq!(CLIENT_METHOD_NAMES.mcp_message, "mcp/message");
5529 assert_eq!(
5530 AgentRequest::MessageMcpRequest(MessageMcpRequest::new(
5531 "server-1",
5532 "req-1",
5533 "tools/list"
5534 ))
5535 .method(),
5536 "mcp/message"
5537 );
5538 assert_eq!(
5539 serde_json::to_value(
5540 MessageMcpRequest::new("server-1", "req-1", "tools/list").params(params)
5541 )
5542 .unwrap(),
5543 json!({
5544 "serverId": "server-1",
5545 "requestId": "req-1",
5546 "method": "tools/list",
5547 "params": { "cursor": "abc" }
5548 })
5549 );
5550
5551 let request_with_null_params: MessageMcpRequest = serde_json::from_value(json!({
5552 "serverId": "server-1",
5553 "requestId": "req-1",
5554 "method": "tools/list",
5555 "params": null,
5556 "_meta": null
5557 }))
5558 .unwrap();
5559 assert_eq!(request_with_null_params.params, None);
5560 assert_eq!(request_with_null_params.meta, None);
5561 for key in ["serverId", "requestId", "method"] {
5562 let mut value =
5563 json!({"serverId":"server-1", "requestId":"req-1", "method":"tools/list"});
5564 value.as_object_mut().unwrap().remove(key);
5565 assert!(serde_json::from_value::<MessageMcpRequest>(value).is_err());
5566 }
5567 for key in ["serverId", "requestId", "method"] {
5568 let mut value =
5569 json!({"serverId":"server-1", "requestId":"req-1", "method":"tools/list"});
5570 value[key] = serde_json::Value::Null;
5571 assert!(serde_json::from_value::<MessageMcpRequest>(value).is_err());
5572 }
5573 }
5574
5575 #[test]
5576 fn request_permission_request_rejects_malformed_options() {
5577 use serde_json::json;
5578
5579 assert!(
5580 serde_json::from_value::<RequestPermissionRequest>(json!({
5581 "sessionId": "sess-1",
5582 "toolCall": {"toolCallId": "tc-1"},
5583 "options": "not-an-array"
5584 }))
5585 .is_err()
5586 );
5587 assert!(
5588 serde_json::from_value::<RequestPermissionRequest>(json!({
5589 "sessionId": "sess-1",
5590 "toolCall": {"toolCallId": "tc-1"},
5591 "options": [{"optionId": "allow"}]
5592 }))
5593 .is_err()
5594 );
5595 }
5596
5597 #[test]
5598 fn create_terminal_request_rejects_malformed_fields() {
5599 use serde_json::json;
5600
5601 let request = |field: &str, value: serde_json::Value| {
5602 let mut request = json!({"sessionId": "sess-1", "command": "npx"});
5603 request[field] = value;
5604 serde_json::from_value::<CreateTerminalRequest>(request)
5605 };
5606
5607 for (field, value) in [
5609 ("args", json!(["serve", "--port", 3000])),
5610 ("args", json!("serve --port 3000")),
5611 ("env", json!([{"name": "PORT", "value": 3000}])),
5612 ("env", json!({"PORT": "3000"})),
5613 ("cwd", json!({"path": "/repo"})),
5614 ("outputByteLimit", json!("1048576")),
5615 ] {
5616 assert!(request(field, value.clone()).is_err(), "{field}: {value}");
5617 }
5618
5619 for field in ["args", "env", "cwd", "outputByteLimit"] {
5620 assert_eq!(
5621 request(field, serde_json::Value::Null).unwrap(),
5622 CreateTerminalRequest::new("sess-1", "npx"),
5623 "{field}"
5624 );
5625 }
5626 }
5627
5628 #[test]
5629 fn read_text_file_request_rejects_malformed_range() {
5630 use serde_json::json;
5631
5632 let request = |field: &str, value: serde_json::Value| {
5633 let mut request = json!({"sessionId": "sess-1", "path": "/repo/src/main.rs"});
5634 request[field] = value;
5635 serde_json::from_value::<ReadTextFileRequest>(request)
5636 };
5637
5638 for field in ["line", "limit"] {
5640 assert!(request(field, json!("120")).is_err(), "{field}");
5641 assert!(request(field, json!(-1)).is_err(), "{field}");
5642 assert_eq!(
5643 request(field, serde_json::Value::Null).unwrap(),
5644 ReadTextFileRequest::new("sess-1", "/repo/src/main.rs"),
5645 "{field}"
5646 );
5647 }
5648 }
5649}