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")]
189#[serde_as]
190#[skip_serializing_none]
191#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
192#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
193#[serde(rename_all = "camelCase")]
194#[non_exhaustive]
195pub struct SessionMessageChunk {
196 pub message_id: MessageId,
198 #[serde_as(deserialize_as = "DefaultOnError")]
200 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
201 #[serde(default)]
202 pub sender_session_id: Option<SessionId>,
203 #[serde_as(deserialize_as = "DefaultOnError")]
205 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
206 #[serde(default)]
207 pub recipient_session_id: Option<SessionId>,
208 pub content: ContentBlock,
210 #[serde_as(deserialize_as = "DefaultOnError")]
214 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
215 #[serde(default, rename = "_meta")]
216 pub meta: Option<Meta>,
217}
218
219#[cfg(feature = "unstable_subagents")]
220impl SessionMessageChunk {
221 #[must_use]
223 pub fn new(message_id: impl Into<MessageId>, content: ContentBlock) -> Self {
224 Self {
225 message_id: message_id.into(),
226 sender_session_id: None,
227 recipient_session_id: None,
228 content,
229 meta: None,
230 }
231 }
232
233 #[must_use]
235 pub fn sender_session_id(mut self, sender_session_id: impl IntoOption<SessionId>) -> Self {
236 self.sender_session_id = sender_session_id.into_option();
237 self
238 }
239
240 #[must_use]
242 pub fn recipient_session_id(
243 mut self,
244 recipient_session_id: impl IntoOption<SessionId>,
245 ) -> Self {
246 self.recipient_session_id = recipient_session_id.into_option();
247 self
248 }
249
250 #[must_use]
252 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
253 self.meta = meta.into_option();
254 self
255 }
256}
257
258#[cfg(feature = "unstable_subagents")]
264#[serde_as]
265#[skip_serializing_none]
266#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
267#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
268#[serde(rename_all = "camelCase")]
269#[non_exhaustive]
270pub struct SessionMessage {
271 pub message_id: MessageId,
273 #[serde_as(deserialize_as = "DefaultOnError")]
275 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
276 #[serde(default)]
277 pub sender_session_id: Option<SessionId>,
278 #[serde_as(deserialize_as = "DefaultOnError")]
280 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
281 #[serde(default)]
282 pub recipient_session_id: Option<SessionId>,
283 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<VecSkipError<_>>>")]
286 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
287 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
288 pub content: MaybeUndefined<Vec<ContentBlock>>,
289 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
293 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
294 #[serde(
295 default,
296 rename = "_meta",
297 skip_serializing_if = "MaybeUndefined::is_undefined"
298 )]
299 pub meta: MaybeUndefined<Meta>,
300}
301
302#[cfg(feature = "unstable_subagents")]
303impl SessionMessage {
304 #[must_use]
306 pub fn new(message_id: impl Into<MessageId>) -> Self {
307 Self {
308 message_id: message_id.into(),
309 sender_session_id: None,
310 recipient_session_id: None,
311 content: MaybeUndefined::Undefined,
312 meta: MaybeUndefined::Undefined,
313 }
314 }
315
316 #[must_use]
318 pub fn sender_session_id(mut self, sender_session_id: impl IntoOption<SessionId>) -> Self {
319 self.sender_session_id = sender_session_id.into_option();
320 self
321 }
322
323 #[must_use]
325 pub fn recipient_session_id(
326 mut self,
327 recipient_session_id: impl IntoOption<SessionId>,
328 ) -> Self {
329 self.recipient_session_id = recipient_session_id.into_option();
330 self
331 }
332
333 #[must_use]
335 pub fn content(mut self, content: impl IntoMaybeUndefined<Vec<ContentBlock>>) -> Self {
336 self.content = content.into_maybe_undefined();
337 self
338 }
339
340 #[must_use]
342 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
343 self.meta = meta.into_maybe_undefined();
344 self
345 }
346}
347
348#[cfg(all(test, feature = "unstable_subagents"))]
349mod session_message_tests {
350 use super::*;
351 use serde_json::{Value, json};
352
353 #[test]
354 fn envelopes_preserve_participants_and_multimodal_content() {
355 let content = json!([
356 {"type": "text", "text": "Please inspect this"},
357 {"type": "image", "data": "aGVsbG8=", "mimeType": "image/png"}
358 ]);
359 for (transcript, sender, recipient, id) in [
360 ("parent", "parent", "child", "sent-1"),
361 ("child", "parent", "child", "received-9"),
362 ("child", "child", "parent", "sent-2"),
363 ] {
364 let wire = json!({"sessionId": transcript, "update": {
365 "sessionUpdate": "session_message", "messageId": id,
366 "senderSessionId": sender, "recipientSessionId": recipient, "content": content
367 }});
368 let decoded: SessionNotification = serde_json::from_value(wire.clone()).unwrap();
369 assert_eq!(serde_json::to_value(&decoded).unwrap(), wire);
370 assert!(matches!(decoded.update, SessionUpdate::SessionMessage(_)));
371 let message = SessionMessage::new(id)
372 .sender_session_id(SessionId::new(sender))
373 .recipient_session_id(SessionId::new(recipient))
374 .content(vec![]);
375 assert_eq!(serde_json::to_value(message).unwrap()["content"], json!([]));
376 }
377 }
378
379 #[test]
380 fn message_upserts_validate_ids_and_patch_fields() {
381 let base = json!({"sessionUpdate": "session_message", "messageId": "m1",
382 "senderSessionId": "parent", "recipientSessionId": "child", "content": []});
383 {
384 let key = "messageId";
385 let mut missing = base.clone();
386 missing.as_object_mut().unwrap().remove(key);
387 assert!(
388 serde_json::from_value::<SessionUpdate>(missing).is_err(),
389 "{key}"
390 );
391 let mut null = base.clone();
392 null[key] = Value::Null;
393 assert!(
394 serde_json::from_value::<SessionUpdate>(null).is_err(),
395 "{key}"
396 );
397 let mut non_string = base.clone();
398 non_string[key] = json!(42);
399 assert!(
400 serde_json::from_value::<SessionUpdate>(non_string).is_err(),
401 "{key}"
402 );
403 }
404 for content in [None, Some(json!({}))] {
405 let mut wire = base.clone();
406 wire.as_object_mut().unwrap().remove("content");
407 if let Some(content) = content {
408 wire["content"] = content;
409 }
410 let decoded: SessionUpdate = serde_json::from_value(wire).unwrap();
411 let encoded = serde_json::to_value(decoded).unwrap();
412 let mut unchanged = base.clone();
413 unchanged.as_object_mut().unwrap().remove("content");
414 assert_eq!(encoded, unchanged);
415 }
416 let clear = SessionMessage::new("m1")
417 .sender_session_id(SessionId::new("parent"))
418 .recipient_session_id(SessionId::new("child"))
419 .content(None)
420 .meta(None);
421 let mut clear_wire = base.clone();
422 clear_wire["content"] = Value::Null;
423 clear_wire["_meta"] = Value::Null;
424 assert_eq!(
425 serde_json::to_value(&clear).unwrap()["content"],
426 Value::Null
427 );
428 let clear_update: SessionUpdate = serde_json::from_value(clear_wire.clone()).unwrap();
429 assert_eq!(serde_json::to_value(clear_update).unwrap(), clear_wire);
430 assert!(clear.content.is_null());
431 assert!(clear.meta.is_null());
432 for meta in [None, Some(Value::Null), Some(json!({"tag": "value"}))] {
433 let mut wire = base.clone();
434 if let Some(meta) = meta {
435 wire["_meta"] = meta;
436 }
437 let decoded: SessionUpdate = serde_json::from_value(wire.clone()).unwrap();
438 let encoded = serde_json::to_value(decoded).unwrap();
439 assert_eq!(encoded, wire);
440 }
441 let metadata_only = json!({"sessionUpdate": "session_message",
442 "messageId": "m1", "senderSessionId": "parent",
443 "recipientSessionId": "child", "_meta": {"tag": "value"}});
444 let decoded: SessionUpdate = serde_json::from_value(metadata_only.clone()).unwrap();
445 assert_eq!(serde_json::to_value(decoded).unwrap(), metadata_only);
446 let unset = SessionMessage::new("m1");
447 assert_eq!(
448 serde_json::to_value(&unset).unwrap(),
449 json!({"messageId": "m1"})
450 );
451 assert!(unset.content.is_undefined());
452 assert!(unset.meta.is_undefined());
453 let reset = SessionMessage::new("m1").content(vec![]);
454 assert_eq!(serde_json::to_value(reset).unwrap()["content"], json!([]));
455 let malformed: SessionMessage = serde_json::from_value(json!({
456 "messageId": "m1", "senderSessionId": "parent", "recipientSessionId": "child",
457 "content": false, "_meta": false
458 }))
459 .unwrap();
460 assert!(malformed.content.is_undefined());
461 assert!(malformed.meta.is_undefined());
462 }
463
464 #[test]
465 fn first_chunk_and_upsert_share_transcript_local_identity() {
466 for (transcript, id) in [("parent", "sent-1"), ("child", "received-9")] {
467 let wire = json!({"sessionId": transcript, "update": {
468 "sessionUpdate": "session_message_chunk", "messageId": id,
469 "senderSessionId": "parent", "recipientSessionId": "child",
470 "content": {"type": "text", "text": "first"}
471 }});
472 let decoded: SessionNotification = serde_json::from_value(wire.clone()).unwrap();
473 assert_eq!(serde_json::to_value(decoded).unwrap(), wire);
474 let upsert = SessionMessage::new(id)
475 .sender_session_id(SessionId::new("parent"))
476 .recipient_session_id(SessionId::new("child"));
477 let chunk = SessionMessageChunk::new(
478 id,
479 ContentBlock::Text(crate::v1::TextContent::new("first")),
480 )
481 .sender_session_id(SessionId::new("parent"))
482 .recipient_session_id(SessionId::new("child"));
483 assert_eq!(chunk.message_id, upsert.message_id);
484 assert_eq!(chunk.sender_session_id, upsert.sender_session_id);
485 assert_eq!(chunk.recipient_session_id, upsert.recipient_session_id);
486 assert_eq!(
487 serde_json::to_value(SessionUpdate::SessionMessageChunk(chunk)).unwrap(),
488 wire["update"]
489 );
490 }
491 let wire = json!({"sessionId": "child", "update": {
492 "sessionUpdate": "session_message_chunk", "messageId": "received-9",
493 "senderSessionId": "parent", "recipientSessionId": "child",
494 "content": {"type": "text", "text": "first"}
495 }});
496 let base = wire["update"].clone();
497 for key in ["messageId", "content"] {
498 let mut missing = base.clone();
499 missing.as_object_mut().unwrap().remove(key);
500 assert!(
501 serde_json::from_value::<SessionUpdate>(missing).is_err(),
502 "{key}"
503 );
504 let mut null = base.clone();
505 null[key] = Value::Null;
506 assert!(
507 serde_json::from_value::<SessionUpdate>(null).is_err(),
508 "{key}"
509 );
510 let mut non_string = base.clone();
511 non_string[key] = json!(42);
512 assert!(
513 serde_json::from_value::<SessionUpdate>(non_string).is_err(),
514 "{key}"
515 );
516 }
517 for meta in [None, Some(Value::Null), Some(json!({"chunk": true}))] {
518 let mut value = base.clone();
519 if let Some(meta) = meta {
520 value["_meta"] = meta;
521 }
522 let decoded: SessionUpdate = serde_json::from_value(value.clone()).unwrap();
523 let encoded = serde_json::to_value(decoded).unwrap();
524 if value["_meta"].is_null() {
525 assert_eq!(encoded, base);
526 } else {
527 assert_eq!(encoded, value);
528 }
529 }
530 }
531
532 #[cfg(feature = "schemars")]
533 #[test]
534 fn schema_requires_ids_and_chunk_content() {
535 let schema = serde_json::to_value(schemars::schema_for!(SessionMessage)).unwrap();
536 let required = schema["required"].as_array().unwrap();
537 assert_eq!(required, &vec![json!("messageId")]);
538 let chunk = serde_json::to_value(schemars::schema_for!(SessionMessageChunk)).unwrap();
539 let required = chunk["required"].as_array().unwrap();
540 assert_eq!(required.len(), 2);
541 assert!(required.contains(&json!("messageId")));
542 assert!(required.contains(&json!("content")));
543 }
544
545 #[test]
546 fn endpoints_can_arrive_late_or_be_omitted_from_later_events() {
547 let block = ContentBlock::Text(crate::v1::TextContent::new("hello"));
548 let minimal = SessionMessage::new("m1");
549 let first = SessionMessageChunk::new("m1", block.clone());
550 assert_eq!(
551 serde_json::to_value(&minimal).unwrap(),
552 json!({"messageId": "m1"})
553 );
554 assert_eq!(
555 serde_json::to_value(&first).unwrap(),
556 json!({"messageId": "m1", "content": {"type": "text", "text": "hello"}})
557 );
558 let enriched = SessionMessage::new("m1")
559 .sender_session_id(SessionId::new("parent"))
560 .recipient_session_id(SessionId::new("child"));
561 assert_eq!(enriched.sender_session_id, Some(SessionId::new("parent")));
562 assert_eq!(enriched.recipient_session_id, Some(SessionId::new("child")));
563 let later =
564 SessionMessageChunk::new("m1", block).sender_session_id(SessionId::new("parent"));
565 assert_eq!(later.sender_session_id, Some(SessionId::new("parent")));
566 assert_eq!(later.recipient_session_id, None);
567 for update in [
568 SessionUpdate::SessionMessage(minimal),
569 SessionUpdate::SessionMessageChunk(first),
570 SessionUpdate::SessionMessage(enriched),
571 SessionUpdate::SessionMessageChunk(later),
572 ] {
573 let wire = serde_json::to_value(&update).unwrap();
574 let decoded: SessionUpdate = serde_json::from_value(wire.clone()).unwrap();
575 assert_eq!(serde_json::to_value(decoded).unwrap(), wire);
576 }
577 }
578
579 #[test]
580 fn invalid_or_null_endpoints_are_absent_but_message_id_is_required() {
581 for (kind, content) in [
582 ("session_message", None),
583 (
584 "session_message_chunk",
585 Some(json!({"type": "text", "text": "hello"})),
586 ),
587 ] {
588 let mut base = json!({"sessionUpdate": kind, "messageId": "m1",
589 "senderSessionId": null, "recipientSessionId": 42});
590 if let Some(content) = content {
591 base["content"] = content;
592 }
593 let decoded: SessionUpdate = serde_json::from_value(base.clone()).unwrap();
594 let encoded = serde_json::to_value(decoded).unwrap();
595 base.as_object_mut().unwrap().remove("senderSessionId");
596 base.as_object_mut().unwrap().remove("recipientSessionId");
597 assert_eq!(encoded, base);
598 for bad in [Value::Null, json!(42)] {
599 let mut invalid = base.clone();
600 invalid["messageId"] = bad;
601 assert!(serde_json::from_value::<SessionUpdate>(invalid).is_err());
602 }
603 }
604 }
605}
606
607#[cfg(all(test, not(feature = "unstable_subagents")))]
608mod disabled_session_message_tests {
609 use super::*;
610 use serde_json::json;
611
612 #[test]
613 fn message_updates_require_subagents_gate() {
614 for (kind, content) in [
615 ("session_message", json!([])),
616 (
617 "session_message_chunk",
618 json!({"type": "text", "text": "hello"}),
619 ),
620 ] {
621 let wire = json!({"sessionUpdate": kind, "messageId": "m1", "content": content});
622 assert!(serde_json::from_value::<SessionUpdate>(wire).is_err());
623 }
624 }
625}
626
627#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
629#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
630#[serde(rename_all = "snake_case")]
631#[non_exhaustive]
632pub enum NoticeSeverity {
633 Info,
635 Warning,
637 Error,
639 #[serde(untagged)]
644 Other(String),
645}
646
647#[serde_as]
657#[skip_serializing_none]
658#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
659#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
660#[serde(rename_all = "camelCase")]
661#[non_exhaustive]
662pub struct Notice {
663 pub severity: NoticeSeverity,
665 #[cfg_attr(feature = "schemars", schemars(length(min = 1)))]
667 pub title: String,
668 #[serde_as(deserialize_as = "DefaultOnError")]
672 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
673 #[serde(default)]
674 pub description: Option<String>,
675 #[serde_as(deserialize_as = "DefaultOnError")]
679 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
680 #[serde(default, rename = "_meta")]
681 pub meta: Option<Meta>,
682}
683
684impl Notice {
685 #[must_use]
687 pub fn new(severity: NoticeSeverity, title: impl Into<String>) -> Self {
688 Self {
689 severity,
690 title: title.into(),
691 description: None,
692 meta: None,
693 }
694 }
695
696 #[must_use]
698 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
699 self.description = description.into_option();
700 self
701 }
702
703 #[must_use]
705 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
706 self.meta = meta.into_option();
707 self
708 }
709}
710
711#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
713#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
714#[serde(transparent)]
715#[from(Arc<str>, String, &'static str)]
716#[non_exhaustive]
717pub struct CompactionId(pub Arc<str>);
718
719impl CompactionId {
720 #[must_use]
722 pub fn new(id: impl Into<Arc<str>>) -> Self {
723 Self(id.into())
724 }
725}
726
727#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
729#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
730#[serde(rename_all = "snake_case")]
731#[non_exhaustive]
732pub enum CompactionStatus {
733 #[default]
735 InProgress,
736 Completed,
738 Failed,
740 Cancelled,
742 #[serde(untagged)]
747 Other(String),
748}
749
750#[serde_as]
759#[skip_serializing_none]
760#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
761#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
762#[serde(rename_all = "camelCase")]
763#[non_exhaustive]
764pub struct CompactionUpdate {
765 pub compaction_id: CompactionId,
767 pub status: CompactionStatus,
769 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<VecSkipError<_>>>")]
771 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
772 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
773 pub summary: MaybeUndefined<Vec<ContentBlock>>,
774 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
776 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
777 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
778 pub error: MaybeUndefined<String>,
779 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
781 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
782 #[serde(
783 rename = "_meta",
784 default,
785 skip_serializing_if = "MaybeUndefined::is_undefined"
786 )]
787 pub meta: MaybeUndefined<Meta>,
788}
789
790impl CompactionUpdate {
791 #[must_use]
793 pub fn new(compaction_id: impl Into<CompactionId>, status: CompactionStatus) -> Self {
794 Self {
795 compaction_id: compaction_id.into(),
796 status,
797 summary: MaybeUndefined::Undefined,
798 error: MaybeUndefined::Undefined,
799 meta: MaybeUndefined::Undefined,
800 }
801 }
802
803 #[must_use]
805 pub fn summary(mut self, summary: impl IntoMaybeUndefined<Vec<ContentBlock>>) -> Self {
806 self.summary = summary.into_maybe_undefined();
807 self
808 }
809
810 #[must_use]
812 pub fn error(mut self, error: impl IntoMaybeUndefined<String>) -> Self {
813 self.error = error.into_maybe_undefined();
814 self
815 }
816
817 #[must_use]
819 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
820 self.meta = meta.into_maybe_undefined();
821 self
822 }
823}
824
825#[serde_as]
830#[skip_serializing_none]
831#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
832#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
833#[serde(rename_all = "camelCase")]
834#[non_exhaustive]
835pub struct CompactionSummaryChunk {
836 pub compaction_id: CompactionId,
838 pub content: ContentBlock,
840 #[serde_as(deserialize_as = "DefaultOnError")]
842 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
843 #[serde(default, rename = "_meta")]
844 pub meta: Option<Meta>,
845}
846
847impl CompactionSummaryChunk {
848 #[must_use]
850 pub fn new(compaction_id: impl Into<CompactionId>, content: ContentBlock) -> Self {
851 Self {
852 compaction_id: compaction_id.into(),
853 content,
854 meta: None,
855 }
856 }
857
858 #[must_use]
860 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
861 self.meta = meta.into_option();
862 self
863 }
864}
865
866#[cfg(feature = "unstable_subagents")]
890#[serde_as]
891#[skip_serializing_none]
892#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
893#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
894#[serde(rename_all = "camelCase")]
895#[non_exhaustive]
896pub struct SubagentUpdate {
897 pub session_id: SessionId,
899 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
904 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
905 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
906 pub title: MaybeUndefined<String>,
907 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
913 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
914 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
915 pub description: MaybeUndefined<String>,
916 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
923 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
924 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
925 pub capabilities: MaybeUndefined<SubagentSessionCapabilities>,
926 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
933 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
934 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
935 pub state: MaybeUndefined<StateUpdate>,
936 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
943 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
944 #[serde(
945 default,
946 rename = "_meta",
947 skip_serializing_if = "MaybeUndefined::is_undefined"
948 )]
949 pub meta: MaybeUndefined<Meta>,
950}
951
952#[cfg(feature = "unstable_subagents")]
953impl SubagentUpdate {
954 #[must_use]
956 pub fn new(session_id: impl Into<SessionId>) -> Self {
957 Self {
958 session_id: session_id.into(),
959 title: MaybeUndefined::Undefined,
960 description: MaybeUndefined::Undefined,
961 capabilities: MaybeUndefined::Undefined,
962 state: MaybeUndefined::Undefined,
963 meta: MaybeUndefined::Undefined,
964 }
965 }
966
967 #[must_use]
969 pub fn title(mut self, title: impl IntoMaybeUndefined<String>) -> Self {
970 self.title = title.into_maybe_undefined();
971 self
972 }
973
974 #[must_use]
976 pub fn description(mut self, description: impl IntoMaybeUndefined<String>) -> Self {
977 self.description = description.into_maybe_undefined();
978 self
979 }
980
981 #[must_use]
983 pub fn capabilities(
984 mut self,
985 capabilities: impl IntoMaybeUndefined<SubagentSessionCapabilities>,
986 ) -> Self {
987 self.capabilities = capabilities.into_maybe_undefined();
988 self
989 }
990
991 #[must_use]
993 pub fn state(mut self, state: impl IntoMaybeUndefined<StateUpdate>) -> Self {
994 self.state = state.into_maybe_undefined();
995 self
996 }
997
998 #[must_use]
1002 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
1003 self.meta = meta.into_maybe_undefined();
1004 self
1005 }
1006}
1007
1008#[cfg(feature = "unstable_subagents")]
1017#[serde_as]
1018#[skip_serializing_none]
1019#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1020#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1021#[serde(rename_all = "camelCase")]
1022#[non_exhaustive]
1023pub struct SubagentSessionCapabilities {
1024 #[serde_as(deserialize_as = "DefaultOnError")]
1028 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1029 #[serde(default)]
1030 pub cancel: Option<SessionCancelCapabilities>,
1031 #[serde_as(deserialize_as = "DefaultOnError")]
1035 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1036 #[serde(default)]
1037 #[serde(rename = "_meta")]
1038 pub meta: Option<Meta>,
1039}
1040
1041#[cfg(feature = "unstable_subagents")]
1042impl SubagentSessionCapabilities {
1043 #[must_use]
1045 pub fn new() -> Self {
1046 Self::default()
1047 }
1048
1049 #[must_use]
1051 pub fn cancel(mut self, cancel: impl IntoOption<SessionCancelCapabilities>) -> Self {
1052 self.cancel = cancel.into_option();
1053 self
1054 }
1055
1056 #[must_use]
1060 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1061 self.meta = meta.into_option();
1062 self
1063 }
1064}
1065
1066#[cfg(feature = "unstable_subagents")]
1074#[serde_as]
1075#[skip_serializing_none]
1076#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1077#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1078#[non_exhaustive]
1079pub struct SessionCancelCapabilities {
1080 #[serde_as(deserialize_as = "DefaultOnError")]
1086 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1087 #[serde(default)]
1088 #[serde(rename = "_meta")]
1089 pub meta: Option<Meta>,
1090}
1091
1092#[cfg(feature = "unstable_subagents")]
1093impl SessionCancelCapabilities {
1094 #[must_use]
1096 pub fn new() -> Self {
1097 Self::default()
1098 }
1099
1100 #[must_use]
1106 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1107 self.meta = meta.into_option();
1108 self
1109 }
1110}
1111
1112#[cfg(feature = "unstable_subagents")]
1122#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1123#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1124#[serde(tag = "state", rename_all = "snake_case")]
1125#[non_exhaustive]
1126pub enum StateUpdate {
1127 Running(RunningStateUpdate),
1129 Idle(IdleStateUpdate),
1131 RequiresAction(RequiresActionStateUpdate),
1133 Unknown(UnknownStateUpdate),
1137 #[serde(untagged)]
1142 Other(OtherStateUpdate),
1143}
1144
1145#[cfg(feature = "unstable_subagents")]
1147#[serde_as]
1148#[skip_serializing_none]
1149#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1150#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1151#[serde(rename_all = "camelCase")]
1152#[non_exhaustive]
1153pub struct RunningStateUpdate {
1154 #[serde_as(deserialize_as = "DefaultOnError")]
1158 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1159 #[serde(default, rename = "_meta")]
1160 pub meta: Option<Meta>,
1161}
1162
1163#[cfg(feature = "unstable_subagents")]
1164impl RunningStateUpdate {
1165 #[must_use]
1167 pub fn new() -> Self {
1168 Self::default()
1169 }
1170
1171 #[must_use]
1173 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1174 self.meta = meta.into_option();
1175 self
1176 }
1177}
1178
1179#[cfg(feature = "unstable_subagents")]
1183#[serde_as]
1184#[skip_serializing_none]
1185#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1186#[cfg_attr(feature = "schemars", schemars(transform = idle_state_update_schema))]
1187#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1188#[serde(rename_all = "camelCase")]
1189#[non_exhaustive]
1190pub struct IdleStateUpdate {
1191 #[serde(flatten)]
1193 pub stop_reason: Option<IdleStopReason>,
1194 #[cfg(feature = "unstable_end_turn_token_usage")]
1198 #[serde_as(deserialize_as = "DefaultOnError")]
1199 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1200 #[serde(default)]
1201 pub usage: Option<Box<Usage>>,
1202 #[serde_as(deserialize_as = "DefaultOnError")]
1206 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1207 #[serde(default, rename = "_meta")]
1208 pub meta: Option<Meta>,
1209}
1210
1211#[cfg(feature = "unstable_subagents")]
1212impl IdleStateUpdate {
1213 #[must_use]
1215 pub fn new() -> Self {
1216 Self::default()
1217 }
1218
1219 #[must_use]
1221 pub fn stop_reason(mut self, stop_reason: impl IntoOption<IdleStopReason>) -> Self {
1222 self.stop_reason = stop_reason.into_option();
1223 self
1224 }
1225
1226 #[cfg(feature = "unstable_end_turn_token_usage")]
1228 #[must_use]
1229 pub fn usage(mut self, usage: impl IntoOption<Usage>) -> Self {
1230 self.usage = usage.into_option().map(Box::new);
1231 self
1232 }
1233
1234 #[must_use]
1236 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1237 self.meta = meta.into_option();
1238 self
1239 }
1240}
1241
1242#[cfg(all(feature = "unstable_subagents", feature = "schemars"))]
1251fn idle_state_update_schema(schema: &mut Schema) {
1252 if let Some(properties) = schema
1253 .get_mut("properties")
1254 .and_then(serde_json::Value::as_object_mut)
1255 {
1256 properties.insert(
1257 "stopReason".into(),
1258 serde_json::json!({
1259 "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)",
1260 "type": ["string", "null"],
1261 "x-deserialize-default-on-error": true
1262 }),
1263 );
1264 }
1265 let Some(variants) = schema
1266 .get_mut("anyOf")
1267 .and_then(serde_json::Value::as_array_mut)
1268 else {
1269 return;
1270 };
1271 for variant in variants {
1272 if variant.as_object().is_some_and(serde_json::Map::is_empty) {
1273 *variant = serde_json::json!({
1274 "title": "none",
1275 "description": "No stop reason: `stopReason` is omitted or `null`.",
1276 "type": "object",
1277 "properties": {
1278 "stopReason": { "type": "null" }
1279 }
1280 });
1281 }
1282 }
1283}
1284
1285#[cfg(feature = "unstable_subagents")]
1298#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1299#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1300#[serde(tag = "stopReason", rename_all = "snake_case")]
1301#[non_exhaustive]
1302pub enum IdleStopReason {
1303 EndTurn,
1305 MaxTokens,
1307 MaxTurnRequests,
1310 Refusal,
1312 Cancelled,
1314 Error(ErrorStopReason),
1316 #[serde(untagged)]
1321 Other(OtherStopReason),
1322}
1323
1324#[cfg(feature = "unstable_subagents")]
1325impl From<ErrorStopReason> for IdleStopReason {
1326 fn from(value: ErrorStopReason) -> Self {
1327 Self::Error(value)
1328 }
1329}
1330
1331#[cfg(feature = "unstable_subagents")]
1337#[serde_as]
1338#[skip_serializing_none]
1339#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1340#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1341#[serde(rename_all = "camelCase")]
1342#[non_exhaustive]
1343pub struct ErrorStopReason {
1344 #[serde_as(deserialize_as = "DefaultOnError")]
1349 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1350 #[serde(default)]
1351 pub error: Option<Box<Error>>,
1352}
1353
1354#[cfg(feature = "unstable_subagents")]
1355impl ErrorStopReason {
1356 #[must_use]
1358 pub fn new() -> Self {
1359 Self::default()
1360 }
1361
1362 #[must_use]
1364 pub fn error(mut self, error: impl IntoOption<Error>) -> Self {
1365 self.error = error.into_option().map(Box::new);
1366 self
1367 }
1368}
1369
1370#[cfg(feature = "unstable_subagents")]
1376#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1377#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1378#[cfg_attr(feature = "schemars", schemars(inline))]
1379#[cfg_attr(feature = "schemars", schemars(transform = other_stop_reason_schema))]
1380#[serde(rename_all = "camelCase")]
1381#[non_exhaustive]
1382pub struct OtherStopReason {
1383 pub stop_reason: String,
1385 #[serde(flatten)]
1387 pub fields: BTreeMap<String, serde_json::Value>,
1388}
1389
1390#[cfg(feature = "unstable_subagents")]
1391impl OtherStopReason {
1392 #[must_use]
1394 pub fn new(
1395 stop_reason: impl Into<String>,
1396 mut fields: BTreeMap<String, serde_json::Value>,
1397 ) -> Self {
1398 remove_idle_state_update_fields(&mut fields);
1399 Self {
1400 stop_reason: stop_reason.into(),
1401 fields,
1402 }
1403 }
1404}
1405
1406#[cfg(feature = "unstable_subagents")]
1407impl<'de> Deserialize<'de> for OtherStopReason {
1408 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1409 where
1410 D: serde::Deserializer<'de>,
1411 {
1412 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1413 let stop_reason = fields
1414 .remove("stopReason")
1415 .ok_or_else(|| serde::de::Error::missing_field("stopReason"))?;
1416 let serde_json::Value::String(stop_reason) = stop_reason else {
1417 return Err(serde::de::Error::custom("`stopReason` must be a string"));
1418 };
1419 if KNOWN_STOP_REASONS.contains(&stop_reason.as_str()) {
1420 return Err(serde::de::Error::custom(format!(
1421 "known stop reason `{stop_reason}` did not match its schema"
1422 )));
1423 }
1424 remove_idle_state_update_fields(&mut fields);
1425 Ok(Self {
1426 stop_reason,
1427 fields,
1428 })
1429 }
1430}
1431
1432#[cfg(feature = "unstable_subagents")]
1433const KNOWN_STOP_REASONS: &[&str] = &[
1434 "end_turn",
1435 "max_tokens",
1436 "max_turn_requests",
1437 "refusal",
1438 "cancelled",
1439 "error",
1440];
1441
1442#[cfg(feature = "unstable_subagents")]
1444fn remove_idle_state_update_fields(fields: &mut BTreeMap<String, serde_json::Value>) {
1445 fields.remove("stopReason");
1446 fields.remove("usage");
1447 fields.remove("_meta");
1448}
1449
1450#[cfg(all(feature = "unstable_subagents", feature = "schemars"))]
1451fn other_stop_reason_schema(schema: &mut Schema) {
1452 let known = KNOWN_STOP_REASONS
1453 .iter()
1454 .map(|stop_reason| {
1455 serde_json::json!({
1456 "properties": { "stopReason": { "const": stop_reason, "type": "string" } },
1457 "required": ["stopReason"],
1458 "type": "object"
1459 })
1460 })
1461 .collect::<Vec<_>>();
1462 schema.insert("not".into(), serde_json::json!({ "anyOf": known }));
1463}
1464
1465#[cfg(feature = "unstable_subagents")]
1467#[serde_as]
1468#[skip_serializing_none]
1469#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1470#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1471#[serde(rename_all = "camelCase")]
1472#[non_exhaustive]
1473pub struct RequiresActionStateUpdate {
1474 #[serde_as(deserialize_as = "DefaultOnError")]
1478 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1479 #[serde(default, rename = "_meta")]
1480 pub meta: Option<Meta>,
1481}
1482
1483#[cfg(feature = "unstable_subagents")]
1484impl RequiresActionStateUpdate {
1485 #[must_use]
1487 pub fn new() -> Self {
1488 Self::default()
1489 }
1490
1491 #[must_use]
1493 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1494 self.meta = meta.into_option();
1495 self
1496 }
1497}
1498
1499#[cfg(feature = "unstable_subagents")]
1513#[serde_as]
1514#[skip_serializing_none]
1515#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1516#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1517#[serde(rename_all = "camelCase")]
1518#[non_exhaustive]
1519pub struct UnknownStateUpdate {
1520 #[serde_as(deserialize_as = "DefaultOnError")]
1524 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1525 #[serde(default, rename = "_meta")]
1526 pub meta: Option<Meta>,
1527}
1528
1529#[cfg(feature = "unstable_subagents")]
1530impl UnknownStateUpdate {
1531 #[must_use]
1533 pub fn new() -> Self {
1534 Self::default()
1535 }
1536
1537 #[must_use]
1539 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1540 self.meta = meta.into_option();
1541 self
1542 }
1543}
1544
1545#[cfg(feature = "unstable_subagents")]
1547#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1548#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
1549#[cfg_attr(feature = "schemars", schemars(inline))]
1550#[cfg_attr(feature = "schemars", schemars(transform = other_state_update_schema))]
1551#[serde(rename_all = "camelCase")]
1552#[non_exhaustive]
1553pub struct OtherStateUpdate {
1554 pub state: String,
1556 #[serde(flatten)]
1558 pub fields: BTreeMap<String, serde_json::Value>,
1559}
1560
1561#[cfg(feature = "unstable_subagents")]
1562impl OtherStateUpdate {
1563 #[must_use]
1565 pub fn new(state: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
1566 fields.remove("state");
1567 Self {
1568 state: state.into(),
1569 fields,
1570 }
1571 }
1572}
1573
1574#[cfg(feature = "unstable_subagents")]
1575impl<'de> Deserialize<'de> for OtherStateUpdate {
1576 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1577 where
1578 D: serde::Deserializer<'de>,
1579 {
1580 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1581 let state = fields
1582 .remove("state")
1583 .ok_or_else(|| serde::de::Error::missing_field("state"))?;
1584 let serde_json::Value::String(state) = state else {
1585 return Err(serde::de::Error::custom("`state` must be a string"));
1586 };
1587 if is_known_state_update(&state) {
1588 return Err(serde::de::Error::custom(format!(
1589 "known state update `{state}` did not match its schema"
1590 )));
1591 }
1592 Ok(Self { state, fields })
1593 }
1594}
1595
1596#[cfg(feature = "unstable_subagents")]
1597const KNOWN_STATE_UPDATE_STATES: &[&str] = &["running", "idle", "requires_action", "unknown"];
1598
1599#[cfg(feature = "unstable_subagents")]
1600fn is_known_state_update(state: &str) -> bool {
1601 KNOWN_STATE_UPDATE_STATES.contains(&state)
1602}
1603
1604#[cfg(all(feature = "unstable_subagents", feature = "schemars"))]
1605fn other_state_update_schema(schema: &mut Schema) {
1606 let known = KNOWN_STATE_UPDATE_STATES
1607 .iter()
1608 .map(|state| {
1609 serde_json::json!({
1610 "properties": { "state": { "const": state, "type": "string" } },
1611 "required": ["state"],
1612 "type": "object"
1613 })
1614 })
1615 .collect::<Vec<_>>();
1616 schema.insert("not".into(), serde_json::json!({ "anyOf": known }));
1617}
1618
1619#[serde_as]
1623#[skip_serializing_none]
1624#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1625#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1626#[serde(rename_all = "camelCase")]
1627#[non_exhaustive]
1628pub struct CurrentModeUpdate {
1629 pub current_mode_id: SessionModeId,
1631 #[serde_as(deserialize_as = "DefaultOnError")]
1637 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1638 #[serde(default)]
1639 #[serde(rename = "_meta")]
1640 pub meta: Option<Meta>,
1641}
1642
1643impl CurrentModeUpdate {
1644 #[must_use]
1646 pub fn new(current_mode_id: impl Into<SessionModeId>) -> Self {
1647 Self {
1648 current_mode_id: current_mode_id.into(),
1649 meta: None,
1650 }
1651 }
1652
1653 #[must_use]
1659 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1660 self.meta = meta.into_option();
1661 self
1662 }
1663}
1664
1665#[serde_as]
1667#[skip_serializing_none]
1668#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1669#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1670#[serde(rename_all = "camelCase")]
1671#[non_exhaustive]
1672pub struct ConfigOptionUpdate {
1673 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
1675 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
1676 pub config_options: Vec<SessionConfigOption>,
1677 #[serde_as(deserialize_as = "DefaultOnError")]
1683 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1684 #[serde(default)]
1685 #[serde(rename = "_meta")]
1686 pub meta: Option<Meta>,
1687}
1688
1689impl ConfigOptionUpdate {
1690 #[must_use]
1692 pub fn new(config_options: Vec<SessionConfigOption>) -> Self {
1693 Self {
1694 config_options,
1695 meta: None,
1696 }
1697 }
1698
1699 #[must_use]
1705 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1706 self.meta = meta.into_option();
1707 self
1708 }
1709}
1710
1711#[serde_as]
1716#[skip_serializing_none]
1717#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1718#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1719#[serde(rename_all = "camelCase")]
1720#[non_exhaustive]
1721pub struct SessionInfoUpdate {
1722 #[serde_as(deserialize_as = "DefaultOnError")]
1724 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1725 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
1726 pub title: MaybeUndefined<String>,
1727 #[serde_as(deserialize_as = "DefaultOnError")]
1729 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1730 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
1731 pub updated_at: MaybeUndefined<String>,
1732 #[serde_as(deserialize_as = "DefaultOnError")]
1738 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1739 #[serde(default)]
1740 #[serde(rename = "_meta")]
1741 pub meta: Option<Meta>,
1742}
1743
1744impl SessionInfoUpdate {
1745 #[must_use]
1747 pub fn new() -> Self {
1748 Self::default()
1749 }
1750
1751 #[must_use]
1753 pub fn title(mut self, title: impl IntoMaybeUndefined<String>) -> Self {
1754 self.title = title.into_maybe_undefined();
1755 self
1756 }
1757
1758 #[must_use]
1760 pub fn updated_at(mut self, updated_at: impl IntoMaybeUndefined<String>) -> Self {
1761 self.updated_at = updated_at.into_maybe_undefined();
1762 self
1763 }
1764
1765 #[must_use]
1771 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1772 self.meta = meta.into_option();
1773 self
1774 }
1775}
1776
1777#[serde_as]
1779#[skip_serializing_none]
1780#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1781#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1782#[serde(rename_all = "camelCase")]
1783#[non_exhaustive]
1784pub struct UsageUpdate {
1785 pub used: u64,
1787 pub size: u64,
1789 #[serde_as(deserialize_as = "DefaultOnError")]
1791 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1792 #[serde(default)]
1793 pub cost: Option<Cost>,
1794 #[serde_as(deserialize_as = "DefaultOnError")]
1800 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1801 #[serde(default)]
1802 #[serde(rename = "_meta")]
1803 pub meta: Option<Meta>,
1804}
1805
1806impl UsageUpdate {
1807 #[must_use]
1809 pub fn new(used: u64, size: u64) -> Self {
1810 Self {
1811 used,
1812 size,
1813 cost: None,
1814 meta: None,
1815 }
1816 }
1817
1818 #[must_use]
1820 pub fn cost(mut self, cost: impl IntoOption<Cost>) -> Self {
1821 self.cost = cost.into_option();
1822 self
1823 }
1824
1825 #[must_use]
1831 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1832 self.meta = meta.into_option();
1833 self
1834 }
1835}
1836
1837#[serde_as]
1839#[skip_serializing_none]
1840#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1841#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1842#[serde(rename_all = "camelCase")]
1843#[non_exhaustive]
1844pub struct Cost {
1845 pub amount: f64,
1847 pub currency: String,
1849 #[serde_as(deserialize_as = "DefaultOnError")]
1855 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1856 #[serde(default)]
1857 #[serde(rename = "_meta")]
1858 pub meta: Option<Meta>,
1859}
1860
1861impl Cost {
1862 #[must_use]
1864 pub fn new(amount: f64, currency: impl Into<String>) -> Self {
1865 Self {
1866 amount,
1867 currency: currency.into(),
1868 meta: None,
1869 }
1870 }
1871
1872 #[must_use]
1878 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1879 self.meta = meta.into_option();
1880 self
1881 }
1882}
1883
1884#[serde_as]
1886#[skip_serializing_none]
1887#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1888#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1889#[serde(rename_all = "camelCase")]
1890#[non_exhaustive]
1891pub struct ContentChunk {
1892 pub content: ContentBlock,
1894 #[serde_as(deserialize_as = "DefaultOnError")]
1899 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1900 #[serde(default)]
1901 pub message_id: Option<MessageId>,
1902 #[serde_as(deserialize_as = "DefaultOnError")]
1908 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1909 #[serde(default)]
1910 #[serde(rename = "_meta")]
1911 pub meta: Option<Meta>,
1912}
1913
1914impl ContentChunk {
1915 #[must_use]
1917 pub fn new(content: ContentBlock) -> Self {
1918 Self {
1919 content,
1920 message_id: None,
1921 meta: None,
1922 }
1923 }
1924
1925 #[must_use]
1930 pub fn message_id(mut self, message_id: impl IntoOption<MessageId>) -> Self {
1931 self.message_id = message_id.into_option();
1932 self
1933 }
1934
1935 #[must_use]
1941 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1942 self.meta = meta.into_option();
1943 self
1944 }
1945}
1946
1947#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1949#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
1950#[serde(transparent)]
1951#[from(Arc<str>, String, &'static str)]
1952#[non_exhaustive]
1953pub struct MessageId(pub Arc<str>);
1954
1955impl MessageId {
1956 #[must_use]
1958 pub fn new(id: impl Into<Arc<str>>) -> Self {
1959 Self(id.into())
1960 }
1961}
1962
1963impl IntoOption<MessageId> for &str {
1964 fn into_option(self) -> Option<MessageId> {
1965 Some(MessageId::new(self))
1966 }
1967}
1968
1969#[serde_as]
1971#[skip_serializing_none]
1972#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1973#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1974#[serde(rename_all = "camelCase")]
1975#[non_exhaustive]
1976pub struct AvailableCommandsUpdate {
1977 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
1979 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
1980 pub available_commands: Vec<AvailableCommand>,
1981 #[serde_as(deserialize_as = "DefaultOnError")]
1987 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1988 #[serde(default)]
1989 #[serde(rename = "_meta")]
1990 pub meta: Option<Meta>,
1991}
1992
1993impl AvailableCommandsUpdate {
1994 #[must_use]
1996 pub fn new(available_commands: Vec<AvailableCommand>) -> Self {
1997 Self {
1998 available_commands,
1999 meta: None,
2000 }
2001 }
2002
2003 #[must_use]
2009 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2010 self.meta = meta.into_option();
2011 self
2012 }
2013}
2014
2015#[serde_as]
2017#[skip_serializing_none]
2018#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2019#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2020#[serde(rename_all = "camelCase")]
2021#[non_exhaustive]
2022pub struct AvailableCommand {
2023 pub name: String,
2025 pub description: String,
2027 #[serde_as(deserialize_as = "DefaultOnError")]
2029 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2030 #[serde(default)]
2031 pub input: Option<AvailableCommandInput>,
2032 #[serde_as(deserialize_as = "DefaultOnError")]
2038 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2039 #[serde(default)]
2040 #[serde(rename = "_meta")]
2041 pub meta: Option<Meta>,
2042}
2043
2044impl AvailableCommand {
2045 #[must_use]
2047 pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
2048 Self {
2049 name: name.into(),
2050 description: description.into(),
2051 input: None,
2052 meta: None,
2053 }
2054 }
2055
2056 #[must_use]
2058 pub fn input(mut self, input: impl IntoOption<AvailableCommandInput>) -> Self {
2059 self.input = input.into_option();
2060 self
2061 }
2062
2063 #[must_use]
2069 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2070 self.meta = meta.into_option();
2071 self
2072 }
2073}
2074
2075#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2077#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2078#[serde(untagged, rename_all = "camelCase")]
2079#[non_exhaustive]
2080pub enum AvailableCommandInput {
2081 Unstructured(UnstructuredCommandInput),
2083}
2084
2085#[serde_as]
2087#[skip_serializing_none]
2088#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2089#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2090#[serde(rename_all = "camelCase")]
2091#[non_exhaustive]
2092pub struct UnstructuredCommandInput {
2093 pub hint: String,
2095 #[serde_as(deserialize_as = "DefaultOnError")]
2101 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2102 #[serde(default)]
2103 #[serde(rename = "_meta")]
2104 pub meta: Option<Meta>,
2105}
2106
2107impl UnstructuredCommandInput {
2108 #[must_use]
2110 pub fn new(hint: impl Into<String>) -> Self {
2111 Self {
2112 hint: hint.into(),
2113 meta: None,
2114 }
2115 }
2116
2117 #[must_use]
2123 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2124 self.meta = meta.into_option();
2125 self
2126 }
2127}
2128
2129#[serde_as]
2137#[skip_serializing_none]
2138#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2139#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2140#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = SESSION_REQUEST_PERMISSION_METHOD_NAME)))]
2141#[serde(rename_all = "camelCase")]
2142#[non_exhaustive]
2143pub struct RequestPermissionRequest {
2144 pub session_id: SessionId,
2146 pub tool_call: ToolCallUpdate,
2148 pub options: Vec<PermissionOption>,
2150 #[serde_as(deserialize_as = "DefaultOnError")]
2156 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2157 #[serde(default)]
2158 #[serde(rename = "_meta")]
2159 pub meta: Option<Meta>,
2160}
2161
2162impl RequestPermissionRequest {
2163 #[must_use]
2165 pub fn new(
2166 session_id: impl Into<SessionId>,
2167 tool_call: ToolCallUpdate,
2168 options: Vec<PermissionOption>,
2169 ) -> Self {
2170 Self {
2171 session_id: session_id.into(),
2172 tool_call,
2173 options,
2174 meta: None,
2175 }
2176 }
2177
2178 #[must_use]
2184 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2185 self.meta = meta.into_option();
2186 self
2187 }
2188}
2189
2190#[serde_as]
2192#[skip_serializing_none]
2193#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2194#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2195#[serde(rename_all = "camelCase")]
2196#[non_exhaustive]
2197pub struct PermissionOption {
2198 pub option_id: PermissionOptionId,
2200 pub name: String,
2202 pub kind: PermissionOptionKind,
2204 #[serde_as(deserialize_as = "DefaultOnError")]
2210 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2211 #[serde(default)]
2212 #[serde(rename = "_meta")]
2213 pub meta: Option<Meta>,
2214}
2215
2216impl PermissionOption {
2217 #[must_use]
2219 pub fn new(
2220 option_id: impl Into<PermissionOptionId>,
2221 name: impl Into<String>,
2222 kind: PermissionOptionKind,
2223 ) -> Self {
2224 Self {
2225 option_id: option_id.into(),
2226 name: name.into(),
2227 kind,
2228 meta: None,
2229 }
2230 }
2231
2232 #[must_use]
2238 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2239 self.meta = meta.into_option();
2240 self
2241 }
2242}
2243
2244#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2246#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
2247#[serde(transparent)]
2248#[from(Arc<str>, String, &'static str)]
2249#[non_exhaustive]
2250pub struct PermissionOptionId(pub Arc<str>);
2251
2252impl PermissionOptionId {
2253 #[must_use]
2255 pub fn new(id: impl Into<Arc<str>>) -> Self {
2256 Self(id.into())
2257 }
2258}
2259
2260#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2264#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
2265#[serde(rename_all = "snake_case")]
2266#[non_exhaustive]
2267pub enum PermissionOptionKind {
2268 AllowOnce,
2270 AllowAlways,
2272 RejectOnce,
2274 RejectAlways,
2276}
2277
2278#[serde_as]
2280#[skip_serializing_none]
2281#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2282#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2283#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = SESSION_REQUEST_PERMISSION_METHOD_NAME)))]
2284#[serde(rename_all = "camelCase")]
2285#[non_exhaustive]
2286pub struct RequestPermissionResponse {
2287 pub outcome: RequestPermissionOutcome,
2290 #[serde_as(deserialize_as = "DefaultOnError")]
2296 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2297 #[serde(default)]
2298 #[serde(rename = "_meta")]
2299 pub meta: Option<Meta>,
2300}
2301
2302impl RequestPermissionResponse {
2303 #[must_use]
2305 pub fn new(outcome: RequestPermissionOutcome) -> Self {
2306 Self {
2307 outcome,
2308 meta: None,
2309 }
2310 }
2311
2312 #[must_use]
2318 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2319 self.meta = meta.into_option();
2320 self
2321 }
2322}
2323
2324#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2326#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2327#[serde(tag = "outcome", rename_all = "snake_case")]
2328#[cfg_attr(feature = "schemars", schemars(extend("discriminator" = {"propertyName": "outcome"})))]
2329#[non_exhaustive]
2330pub enum RequestPermissionOutcome {
2331 Cancelled,
2339 #[serde(rename_all = "camelCase")]
2341 Selected(SelectedPermissionOutcome),
2342}
2343
2344#[serde_as]
2346#[skip_serializing_none]
2347#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2348#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2349#[serde(rename_all = "camelCase")]
2350#[non_exhaustive]
2351pub struct SelectedPermissionOutcome {
2352 pub option_id: PermissionOptionId,
2354 #[serde_as(deserialize_as = "DefaultOnError")]
2360 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2361 #[serde(default)]
2362 #[serde(rename = "_meta")]
2363 pub meta: Option<Meta>,
2364}
2365
2366impl SelectedPermissionOutcome {
2367 #[must_use]
2369 pub fn new(option_id: impl Into<PermissionOptionId>) -> Self {
2370 Self {
2371 option_id: option_id.into(),
2372 meta: None,
2373 }
2374 }
2375
2376 #[must_use]
2382 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2383 self.meta = meta.into_option();
2384 self
2385 }
2386}
2387
2388#[serde_as]
2394#[skip_serializing_none]
2395#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2396#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2397#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = FS_WRITE_TEXT_FILE_METHOD_NAME)))]
2398#[serde(rename_all = "camelCase")]
2399#[non_exhaustive]
2400pub struct WriteTextFileRequest {
2401 pub session_id: SessionId,
2403 pub path: PathBuf,
2405 pub content: String,
2407 #[serde_as(deserialize_as = "DefaultOnError")]
2413 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2414 #[serde(default)]
2415 #[serde(rename = "_meta")]
2416 pub meta: Option<Meta>,
2417}
2418
2419impl WriteTextFileRequest {
2420 #[must_use]
2422 pub fn new(
2423 session_id: impl Into<SessionId>,
2424 path: impl Into<PathBuf>,
2425 content: impl Into<String>,
2426 ) -> Self {
2427 Self {
2428 session_id: session_id.into(),
2429 path: path.into(),
2430 content: content.into(),
2431 meta: None,
2432 }
2433 }
2434
2435 #[must_use]
2441 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2442 self.meta = meta.into_option();
2443 self
2444 }
2445}
2446
2447crate::serde_util::default_on_null! {
2448 #[serde_as]
2450 #[skip_serializing_none]
2451 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2452 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
2453 #[serde(rename_all = "camelCase")]
2454 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = FS_WRITE_TEXT_FILE_METHOD_NAME)))]
2455 #[non_exhaustive]
2456 pub struct WriteTextFileResponse {
2457 #[serde_as(deserialize_as = "DefaultOnError")]
2463 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2464 #[serde(default)]
2465 #[serde(rename = "_meta")]
2466 pub meta: Option<Meta>,
2467 }
2468}
2469
2470impl WriteTextFileResponse {
2471 #[must_use]
2473 pub fn new() -> Self {
2474 Self::default()
2475 }
2476
2477 #[must_use]
2483 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2484 self.meta = meta.into_option();
2485 self
2486 }
2487}
2488
2489#[serde_as]
2495#[skip_serializing_none]
2496#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2497#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2498#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = FS_READ_TEXT_FILE_METHOD_NAME)))]
2499#[serde(rename_all = "camelCase")]
2500#[non_exhaustive]
2501pub struct ReadTextFileRequest {
2502 pub session_id: SessionId,
2504 pub path: PathBuf,
2506 #[serde(default)]
2508 pub line: Option<u32>,
2509 #[serde(default)]
2511 pub limit: Option<u32>,
2512 #[serde_as(deserialize_as = "DefaultOnError")]
2518 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2519 #[serde(default)]
2520 #[serde(rename = "_meta")]
2521 pub meta: Option<Meta>,
2522}
2523
2524impl ReadTextFileRequest {
2525 #[must_use]
2527 pub fn new(session_id: impl Into<SessionId>, path: impl Into<PathBuf>) -> Self {
2528 Self {
2529 session_id: session_id.into(),
2530 path: path.into(),
2531 line: None,
2532 limit: None,
2533 meta: None,
2534 }
2535 }
2536
2537 #[must_use]
2539 pub fn line(mut self, line: impl IntoOption<u32>) -> Self {
2540 self.line = line.into_option();
2541 self
2542 }
2543
2544 #[must_use]
2546 pub fn limit(mut self, limit: impl IntoOption<u32>) -> Self {
2547 self.limit = limit.into_option();
2548 self
2549 }
2550
2551 #[must_use]
2557 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2558 self.meta = meta.into_option();
2559 self
2560 }
2561}
2562
2563#[serde_as]
2565#[skip_serializing_none]
2566#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2567#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2568#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = FS_READ_TEXT_FILE_METHOD_NAME)))]
2569#[serde(rename_all = "camelCase")]
2570#[non_exhaustive]
2571pub struct ReadTextFileResponse {
2572 pub content: String,
2574 #[serde_as(deserialize_as = "DefaultOnError")]
2580 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2581 #[serde(default)]
2582 #[serde(rename = "_meta")]
2583 pub meta: Option<Meta>,
2584}
2585
2586impl ReadTextFileResponse {
2587 #[must_use]
2589 pub fn new(content: impl Into<String>) -> Self {
2590 Self {
2591 content: content.into(),
2592 meta: None,
2593 }
2594 }
2595
2596 #[must_use]
2602 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2603 self.meta = meta.into_option();
2604 self
2605 }
2606}
2607
2608#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2612#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
2613#[serde(transparent)]
2614#[from(Arc<str>, String, &'static str)]
2615#[non_exhaustive]
2616pub struct TerminalId(pub Arc<str>);
2617
2618impl TerminalId {
2619 #[must_use]
2621 pub fn new(id: impl Into<Arc<str>>) -> Self {
2622 Self(id.into())
2623 }
2624}
2625
2626#[serde_as]
2628#[skip_serializing_none]
2629#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2630#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2631#[serde(rename_all = "camelCase")]
2632#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_CREATE_METHOD_NAME)))]
2633#[non_exhaustive]
2634pub struct CreateTerminalRequest {
2635 pub session_id: SessionId,
2637 pub command: String,
2639 #[serde_as(deserialize_as = "DefaultOnNull")]
2641 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2642 pub args: Vec<String>,
2643 #[serde_as(deserialize_as = "DefaultOnNull")]
2645 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2646 pub env: Vec<EnvVariable>,
2647 #[serde(default)]
2649 pub cwd: Option<PathBuf>,
2650 #[serde(default)]
2659 pub output_byte_limit: Option<u64>,
2660 #[serde_as(deserialize_as = "DefaultOnError")]
2666 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2667 #[serde(default)]
2668 #[serde(rename = "_meta")]
2669 pub meta: Option<Meta>,
2670}
2671
2672impl CreateTerminalRequest {
2673 #[must_use]
2675 pub fn new(session_id: impl Into<SessionId>, command: impl Into<String>) -> Self {
2676 Self {
2677 session_id: session_id.into(),
2678 command: command.into(),
2679 args: Vec::new(),
2680 env: Vec::new(),
2681 cwd: None,
2682 output_byte_limit: None,
2683 meta: None,
2684 }
2685 }
2686
2687 #[must_use]
2689 pub fn args(mut self, args: Vec<String>) -> Self {
2690 self.args = args;
2691 self
2692 }
2693
2694 #[must_use]
2696 pub fn env(mut self, env: Vec<EnvVariable>) -> Self {
2697 self.env = env;
2698 self
2699 }
2700
2701 #[must_use]
2703 pub fn cwd(mut self, cwd: impl IntoOption<PathBuf>) -> Self {
2704 self.cwd = cwd.into_option();
2705 self
2706 }
2707
2708 #[must_use]
2717 pub fn output_byte_limit(mut self, output_byte_limit: impl IntoOption<u64>) -> Self {
2718 self.output_byte_limit = output_byte_limit.into_option();
2719 self
2720 }
2721
2722 #[must_use]
2728 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2729 self.meta = meta.into_option();
2730 self
2731 }
2732}
2733
2734#[serde_as]
2736#[skip_serializing_none]
2737#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2738#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2739#[serde(rename_all = "camelCase")]
2740#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_CREATE_METHOD_NAME)))]
2741#[non_exhaustive]
2742pub struct CreateTerminalResponse {
2743 pub terminal_id: TerminalId,
2745 #[serde_as(deserialize_as = "DefaultOnError")]
2751 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2752 #[serde(default)]
2753 #[serde(rename = "_meta")]
2754 pub meta: Option<Meta>,
2755}
2756
2757impl CreateTerminalResponse {
2758 #[must_use]
2760 pub fn new(terminal_id: impl Into<TerminalId>) -> Self {
2761 Self {
2762 terminal_id: terminal_id.into(),
2763 meta: None,
2764 }
2765 }
2766
2767 #[must_use]
2773 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2774 self.meta = meta.into_option();
2775 self
2776 }
2777}
2778
2779#[serde_as]
2781#[skip_serializing_none]
2782#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2783#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2784#[serde(rename_all = "camelCase")]
2785#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_OUTPUT_METHOD_NAME)))]
2786#[non_exhaustive]
2787pub struct TerminalOutputRequest {
2788 pub session_id: SessionId,
2790 pub terminal_id: TerminalId,
2792 #[serde_as(deserialize_as = "DefaultOnError")]
2798 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2799 #[serde(default)]
2800 #[serde(rename = "_meta")]
2801 pub meta: Option<Meta>,
2802}
2803
2804impl TerminalOutputRequest {
2805 #[must_use]
2807 pub fn new(session_id: impl Into<SessionId>, terminal_id: impl Into<TerminalId>) -> Self {
2808 Self {
2809 session_id: session_id.into(),
2810 terminal_id: terminal_id.into(),
2811 meta: None,
2812 }
2813 }
2814
2815 #[must_use]
2821 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2822 self.meta = meta.into_option();
2823 self
2824 }
2825}
2826
2827#[serde_as]
2829#[skip_serializing_none]
2830#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2831#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2832#[serde(rename_all = "camelCase")]
2833#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_OUTPUT_METHOD_NAME)))]
2834#[non_exhaustive]
2835pub struct TerminalOutputResponse {
2836 pub output: String,
2838 pub truncated: bool,
2840 #[serde_as(deserialize_as = "DefaultOnError")]
2842 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2843 #[serde(default)]
2844 pub exit_status: Option<TerminalExitStatus>,
2845 #[serde_as(deserialize_as = "DefaultOnError")]
2851 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2852 #[serde(default)]
2853 #[serde(rename = "_meta")]
2854 pub meta: Option<Meta>,
2855}
2856
2857impl TerminalOutputResponse {
2858 #[must_use]
2860 pub fn new(output: impl Into<String>, truncated: bool) -> Self {
2861 Self {
2862 output: output.into(),
2863 truncated,
2864 exit_status: None,
2865 meta: None,
2866 }
2867 }
2868
2869 #[must_use]
2871 pub fn exit_status(mut self, exit_status: impl IntoOption<TerminalExitStatus>) -> Self {
2872 self.exit_status = exit_status.into_option();
2873 self
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_RELEASE_METHOD_NAME)))]
2895#[non_exhaustive]
2896pub struct ReleaseTerminalRequest {
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 ReleaseTerminalRequest {
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
2936crate::serde_util::default_on_null! {
2937 #[serde_as]
2939 #[skip_serializing_none]
2940 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2941 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
2942 #[serde(rename_all = "camelCase")]
2943 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_RELEASE_METHOD_NAME)))]
2944 #[non_exhaustive]
2945 pub struct ReleaseTerminalResponse {
2946 #[serde_as(deserialize_as = "DefaultOnError")]
2952 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2953 #[serde(default)]
2954 #[serde(rename = "_meta")]
2955 pub meta: Option<Meta>,
2956 }
2957}
2958
2959impl ReleaseTerminalResponse {
2960 #[must_use]
2962 pub fn new() -> Self {
2963 Self::default()
2964 }
2965
2966 #[must_use]
2972 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2973 self.meta = meta.into_option();
2974 self
2975 }
2976}
2977
2978#[serde_as]
2980#[skip_serializing_none]
2981#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2982#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2983#[serde(rename_all = "camelCase")]
2984#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_KILL_METHOD_NAME)))]
2985#[non_exhaustive]
2986pub struct KillTerminalRequest {
2987 pub session_id: SessionId,
2989 pub terminal_id: TerminalId,
2991 #[serde_as(deserialize_as = "DefaultOnError")]
2997 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2998 #[serde(default)]
2999 #[serde(rename = "_meta")]
3000 pub meta: Option<Meta>,
3001}
3002
3003impl KillTerminalRequest {
3004 #[must_use]
3006 pub fn new(session_id: impl Into<SessionId>, terminal_id: impl Into<TerminalId>) -> Self {
3007 Self {
3008 session_id: session_id.into(),
3009 terminal_id: terminal_id.into(),
3010 meta: None,
3011 }
3012 }
3013
3014 #[must_use]
3020 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3021 self.meta = meta.into_option();
3022 self
3023 }
3024}
3025
3026crate::serde_util::default_on_null! {
3027 #[serde_as]
3029 #[skip_serializing_none]
3030 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3031 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
3032 #[serde(rename_all = "camelCase")]
3033 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_KILL_METHOD_NAME)))]
3034 #[non_exhaustive]
3035 pub struct KillTerminalResponse {
3036 #[serde_as(deserialize_as = "DefaultOnError")]
3042 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3043 #[serde(default)]
3044 #[serde(rename = "_meta")]
3045 pub meta: Option<Meta>,
3046 }
3047}
3048
3049impl KillTerminalResponse {
3050 #[must_use]
3052 pub fn new() -> Self {
3053 Self::default()
3054 }
3055
3056 #[must_use]
3062 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3063 self.meta = meta.into_option();
3064 self
3065 }
3066}
3067
3068#[serde_as]
3070#[skip_serializing_none]
3071#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3072#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3073#[serde(rename_all = "camelCase")]
3074#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_WAIT_FOR_EXIT_METHOD_NAME)))]
3075#[non_exhaustive]
3076pub struct WaitForTerminalExitRequest {
3077 pub session_id: SessionId,
3079 pub terminal_id: TerminalId,
3081 #[serde_as(deserialize_as = "DefaultOnError")]
3087 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3088 #[serde(default)]
3089 #[serde(rename = "_meta")]
3090 pub meta: Option<Meta>,
3091}
3092
3093impl WaitForTerminalExitRequest {
3094 #[must_use]
3096 pub fn new(session_id: impl Into<SessionId>, terminal_id: impl Into<TerminalId>) -> Self {
3097 Self {
3098 session_id: session_id.into(),
3099 terminal_id: terminal_id.into(),
3100 meta: None,
3101 }
3102 }
3103
3104 #[must_use]
3110 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3111 self.meta = meta.into_option();
3112 self
3113 }
3114}
3115
3116crate::serde_util::default_on_null! {
3117 #[serde_as]
3119 #[skip_serializing_none]
3120 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3121 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
3122 #[serde(rename_all = "camelCase")]
3123 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_WAIT_FOR_EXIT_METHOD_NAME)))]
3124 #[non_exhaustive]
3125 pub struct WaitForTerminalExitResponse {
3126 #[serde(flatten)]
3128 pub exit_status: TerminalExitStatus,
3129 #[serde_as(deserialize_as = "DefaultOnError")]
3135 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3136 #[serde(default)]
3137 #[serde(rename = "_meta")]
3138 pub meta: Option<Meta>,
3139 }
3140}
3141
3142impl WaitForTerminalExitResponse {
3143 #[must_use]
3145 pub fn new(exit_status: TerminalExitStatus) -> Self {
3146 Self {
3147 exit_status,
3148 meta: None,
3149 }
3150 }
3151
3152 #[must_use]
3158 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3159 self.meta = meta.into_option();
3160 self
3161 }
3162}
3163
3164#[serde_as]
3166#[skip_serializing_none]
3167#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3168#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3169#[serde(rename_all = "camelCase")]
3170#[non_exhaustive]
3171pub struct TerminalExitStatus {
3172 #[serde_as(deserialize_as = "DefaultOnError")]
3174 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3175 #[serde(default)]
3176 pub exit_code: Option<u32>,
3177 #[serde_as(deserialize_as = "DefaultOnError")]
3179 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3180 #[serde(default)]
3181 pub signal: Option<String>,
3182 #[serde_as(deserialize_as = "DefaultOnError")]
3188 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3189 #[serde(default)]
3190 #[serde(rename = "_meta")]
3191 pub meta: Option<Meta>,
3192}
3193
3194impl TerminalExitStatus {
3195 #[must_use]
3197 pub fn new() -> Self {
3198 Self::default()
3199 }
3200
3201 #[must_use]
3203 pub fn exit_code(mut self, exit_code: impl IntoOption<u32>) -> Self {
3204 self.exit_code = exit_code.into_option();
3205 self
3206 }
3207
3208 #[must_use]
3210 pub fn signal(mut self, signal: impl IntoOption<String>) -> Self {
3211 self.signal = signal.into_option();
3212 self
3213 }
3214
3215 #[must_use]
3221 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3222 self.meta = meta.into_option();
3223 self
3224 }
3225}
3226
3227#[serde_as]
3236#[skip_serializing_none]
3237#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3238#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3239#[serde(rename_all = "camelCase")]
3240#[non_exhaustive]
3241pub struct ClientCapabilities {
3242 #[serde_as(deserialize_as = "DefaultOnError")]
3245 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3246 #[serde(default)]
3247 pub fs: FileSystemCapabilities,
3248 #[serde_as(deserialize_as = "DefaultOnError")]
3250 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3251 #[serde(default)]
3252 pub terminal: bool,
3253 #[serde_as(deserialize_as = "DefaultOnError")]
3258 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3259 #[serde(default)]
3260 pub session: Option<ClientSessionCapabilities>,
3261 #[cfg(feature = "unstable_subagents")]
3272 #[serde_as(deserialize_as = "DefaultOnError")]
3273 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3274 #[serde(default)]
3275 pub subagents: Option<SubagentCapabilities>,
3276 #[cfg(feature = "unstable_plan_operations")]
3285 #[serde_as(deserialize_as = "DefaultOnError")]
3286 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3287 #[serde(default)]
3288 pub plan: Option<PlanCapabilities>,
3289 #[serde_as(deserialize_as = "DefaultOnError")]
3293 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3294 #[serde(default)]
3295 pub auth: AuthCapabilities,
3296 #[serde_as(deserialize_as = "DefaultOnError")]
3302 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3303 #[serde(default)]
3304 pub elicitation: Option<ElicitationCapabilities>,
3305 #[serde_as(deserialize_as = "DefaultOnError")]
3311 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3312 #[serde(default)]
3313 #[serde(rename = "_meta")]
3314 pub meta: Option<Meta>,
3315}
3316
3317impl ClientCapabilities {
3318 #[must_use]
3320 pub fn new() -> Self {
3321 Self::default()
3322 }
3323
3324 #[must_use]
3327 pub fn fs(mut self, fs: FileSystemCapabilities) -> Self {
3328 self.fs = fs;
3329 self
3330 }
3331
3332 #[must_use]
3334 pub fn terminal(mut self, terminal: bool) -> Self {
3335 self.terminal = terminal;
3336 self
3337 }
3338
3339 #[must_use]
3341 pub fn session(mut self, session: impl IntoOption<ClientSessionCapabilities>) -> Self {
3342 self.session = session.into_option();
3343 self
3344 }
3345
3346 #[cfg(feature = "unstable_subagents")]
3352 #[must_use]
3353 pub fn subagents(mut self, subagents: impl IntoOption<SubagentCapabilities>) -> Self {
3354 self.subagents = subagents.into_option();
3355 self
3356 }
3357
3358 #[cfg(feature = "unstable_plan_operations")]
3367 #[must_use]
3368 pub fn plan(mut self, plan: impl IntoOption<PlanCapabilities>) -> Self {
3369 self.plan = plan.into_option();
3370 self
3371 }
3372
3373 #[must_use]
3377 pub fn auth(mut self, auth: AuthCapabilities) -> Self {
3378 self.auth = auth;
3379 self
3380 }
3381
3382 #[must_use]
3385 pub fn elicitation(mut self, elicitation: impl IntoOption<ElicitationCapabilities>) -> Self {
3386 self.elicitation = elicitation.into_option();
3387 self
3388 }
3389
3390 #[must_use]
3396 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3397 self.meta = meta.into_option();
3398 self
3399 }
3400}
3401
3402#[cfg(feature = "unstable_subagents")]
3413#[serde_as]
3414#[skip_serializing_none]
3415#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3416#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3417#[non_exhaustive]
3418pub struct SubagentCapabilities {
3419 #[serde_as(deserialize_as = "DefaultOnError")]
3423 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3424 #[serde(default)]
3425 #[serde(rename = "_meta")]
3426 pub meta: Option<Meta>,
3427}
3428
3429#[cfg(feature = "unstable_subagents")]
3430impl SubagentCapabilities {
3431 #[must_use]
3433 pub fn new() -> Self {
3434 Self::default()
3435 }
3436
3437 #[must_use]
3441 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3442 self.meta = meta.into_option();
3443 self
3444 }
3445}
3446
3447#[serde_as]
3449#[skip_serializing_none]
3450#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3451#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3452#[serde(rename_all = "camelCase")]
3453#[non_exhaustive]
3454pub struct ClientSessionCapabilities {
3455 #[serde_as(deserialize_as = "DefaultOnError")]
3458 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3459 #[serde(default)]
3460 pub compaction: Option<CompactionCapabilities>,
3461 #[serde_as(deserialize_as = "DefaultOnError")]
3466 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3467 #[serde(default)]
3468 pub config_options: Option<SessionConfigOptionsCapabilities>,
3469 #[serde_as(deserialize_as = "DefaultOnError")]
3474 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3475 #[serde(default)]
3476 pub notices: Option<NoticeCapabilities>,
3477 #[serde_as(deserialize_as = "DefaultOnError")]
3483 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3484 #[serde(default)]
3485 #[serde(rename = "_meta")]
3486 pub meta: Option<Meta>,
3487}
3488
3489impl ClientSessionCapabilities {
3490 #[must_use]
3492 pub fn new() -> Self {
3493 Self::default()
3494 }
3495
3496 #[must_use]
3498 pub fn compaction(mut self, compaction: impl IntoOption<CompactionCapabilities>) -> Self {
3499 self.compaction = compaction.into_option();
3500 self
3501 }
3502
3503 #[must_use]
3508 pub fn config_options(
3509 mut self,
3510 config_options: impl IntoOption<SessionConfigOptionsCapabilities>,
3511 ) -> Self {
3512 self.config_options = config_options.into_option();
3513 self
3514 }
3515
3516 #[must_use]
3518 pub fn notices(mut self, notices: impl IntoOption<NoticeCapabilities>) -> Self {
3519 self.notices = notices.into_option();
3520 self
3521 }
3522
3523 #[must_use]
3529 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3530 self.meta = meta.into_option();
3531 self
3532 }
3533}
3534
3535#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3537#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3538#[serde(rename_all = "camelCase")]
3539#[non_exhaustive]
3540pub struct CompactionCapabilities {}
3541
3542impl CompactionCapabilities {
3543 #[must_use]
3545 pub fn new() -> Self {
3546 Self {}
3547 }
3548}
3549
3550#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3552#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3553#[serde(rename_all = "camelCase")]
3554#[non_exhaustive]
3555pub struct NoticeCapabilities {}
3556
3557impl NoticeCapabilities {
3558 #[must_use]
3560 pub fn new() -> Self {
3561 Self {}
3562 }
3563}
3564
3565#[serde_as]
3567#[skip_serializing_none]
3568#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3569#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3570#[serde(rename_all = "camelCase")]
3571#[non_exhaustive]
3572pub struct SessionConfigOptionsCapabilities {
3573 #[serde_as(deserialize_as = "DefaultOnError")]
3580 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3581 #[serde(default)]
3582 pub boolean: Option<BooleanConfigOptionCapabilities>,
3583 #[serde_as(deserialize_as = "DefaultOnError")]
3589 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3590 #[serde(default)]
3591 #[serde(rename = "_meta")]
3592 pub meta: Option<Meta>,
3593}
3594
3595impl SessionConfigOptionsCapabilities {
3596 #[must_use]
3598 pub fn new() -> Self {
3599 Self::default()
3600 }
3601
3602 #[must_use]
3609 pub fn boolean(mut self, boolean: impl IntoOption<BooleanConfigOptionCapabilities>) -> Self {
3610 self.boolean = boolean.into_option();
3611 self
3612 }
3613
3614 #[must_use]
3620 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3621 self.meta = meta.into_option();
3622 self
3623 }
3624}
3625
3626#[serde_as]
3630#[skip_serializing_none]
3631#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3632#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3633#[non_exhaustive]
3634pub struct BooleanConfigOptionCapabilities {
3635 #[serde_as(deserialize_as = "DefaultOnError")]
3641 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3642 #[serde(default)]
3643 #[serde(rename = "_meta")]
3644 pub meta: Option<Meta>,
3645}
3646
3647impl BooleanConfigOptionCapabilities {
3648 #[must_use]
3650 pub fn new() -> Self {
3651 Self::default()
3652 }
3653
3654 #[must_use]
3660 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3661 self.meta = meta.into_option();
3662 self
3663 }
3664}
3665
3666#[serde_as]
3672#[skip_serializing_none]
3673#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3674#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3675#[serde(rename_all = "camelCase")]
3676#[non_exhaustive]
3677pub struct AuthCapabilities {
3678 #[serde_as(deserialize_as = "DefaultOnError")]
3684 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3685 #[serde(default)]
3686 pub terminal: bool,
3687 #[serde_as(deserialize_as = "DefaultOnError")]
3693 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3694 #[serde(default)]
3695 #[serde(rename = "_meta")]
3696 pub meta: Option<Meta>,
3697}
3698
3699impl AuthCapabilities {
3700 #[must_use]
3702 pub fn new() -> Self {
3703 Self::default()
3704 }
3705
3706 #[must_use]
3713 pub fn terminal(mut self, terminal: bool) -> Self {
3714 self.terminal = terminal;
3715 self
3716 }
3717
3718 #[must_use]
3724 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3725 self.meta = meta.into_option();
3726 self
3727 }
3728}
3729
3730#[serde_as]
3734#[skip_serializing_none]
3735#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3736#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3737#[serde(rename_all = "camelCase")]
3738#[non_exhaustive]
3739pub struct FileSystemCapabilities {
3740 #[serde_as(deserialize_as = "DefaultOnError")]
3742 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3743 #[serde(default)]
3744 pub read_text_file: bool,
3745 #[serde_as(deserialize_as = "DefaultOnError")]
3747 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3748 #[serde(default)]
3749 pub write_text_file: bool,
3750 #[serde_as(deserialize_as = "DefaultOnError")]
3756 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3757 #[serde(default)]
3758 #[serde(rename = "_meta")]
3759 pub meta: Option<Meta>,
3760}
3761
3762impl FileSystemCapabilities {
3763 #[must_use]
3765 pub fn new() -> Self {
3766 Self::default()
3767 }
3768
3769 #[must_use]
3771 pub fn read_text_file(mut self, read_text_file: bool) -> Self {
3772 self.read_text_file = read_text_file;
3773 self
3774 }
3775
3776 #[must_use]
3778 pub fn write_text_file(mut self, write_text_file: bool) -> Self {
3779 self.write_text_file = write_text_file;
3780 self
3781 }
3782
3783 #[must_use]
3789 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3790 self.meta = meta.into_option();
3791 self
3792 }
3793}
3794
3795#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3801#[non_exhaustive]
3802pub struct ClientMethodNames {
3803 pub session_request_permission: &'static str,
3805 pub session_update: &'static str,
3807 pub fs_write_text_file: &'static str,
3809 pub fs_read_text_file: &'static str,
3811 pub terminal_create: &'static str,
3813 pub terminal_output: &'static str,
3815 pub terminal_release: &'static str,
3817 pub terminal_wait_for_exit: &'static str,
3819 pub terminal_kill: &'static str,
3821 #[cfg(feature = "unstable_mcp_over_acp")]
3823 pub mcp_message: &'static str,
3824 pub elicitation_create: &'static str,
3826 pub elicitation_complete: &'static str,
3828}
3829
3830pub const CLIENT_METHOD_NAMES: ClientMethodNames = ClientMethodNames {
3832 session_update: SESSION_UPDATE_NOTIFICATION,
3833 session_request_permission: SESSION_REQUEST_PERMISSION_METHOD_NAME,
3834 fs_write_text_file: FS_WRITE_TEXT_FILE_METHOD_NAME,
3835 fs_read_text_file: FS_READ_TEXT_FILE_METHOD_NAME,
3836 terminal_create: TERMINAL_CREATE_METHOD_NAME,
3837 terminal_output: TERMINAL_OUTPUT_METHOD_NAME,
3838 terminal_release: TERMINAL_RELEASE_METHOD_NAME,
3839 terminal_wait_for_exit: TERMINAL_WAIT_FOR_EXIT_METHOD_NAME,
3840 terminal_kill: TERMINAL_KILL_METHOD_NAME,
3841 #[cfg(feature = "unstable_mcp_over_acp")]
3842 mcp_message: MCP_MESSAGE_METHOD_NAME,
3843 elicitation_create: ELICITATION_CREATE_METHOD_NAME,
3844 elicitation_complete: ELICITATION_COMPLETE_NOTIFICATION,
3845};
3846
3847pub(crate) const SESSION_UPDATE_NOTIFICATION: &str = "session/update";
3849pub(crate) const SESSION_REQUEST_PERMISSION_METHOD_NAME: &str = "session/request_permission";
3851pub(crate) const FS_WRITE_TEXT_FILE_METHOD_NAME: &str = "fs/write_text_file";
3853pub(crate) const FS_READ_TEXT_FILE_METHOD_NAME: &str = "fs/read_text_file";
3855pub(crate) const TERMINAL_CREATE_METHOD_NAME: &str = "terminal/create";
3857pub(crate) const TERMINAL_OUTPUT_METHOD_NAME: &str = "terminal/output";
3859pub(crate) const TERMINAL_RELEASE_METHOD_NAME: &str = "terminal/release";
3861pub(crate) const TERMINAL_WAIT_FOR_EXIT_METHOD_NAME: &str = "terminal/wait_for_exit";
3863pub(crate) const TERMINAL_KILL_METHOD_NAME: &str = "terminal/kill";
3865pub(crate) const ELICITATION_CREATE_METHOD_NAME: &str = "elicitation/create";
3867pub(crate) const ELICITATION_COMPLETE_NOTIFICATION: &str = "elicitation/complete";
3869
3870#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3877#[derive(Clone, Debug, Serialize, Deserialize)]
3878#[serde(untagged)]
3879#[cfg_attr(feature = "schemars", schemars(inline))]
3880#[non_exhaustive]
3881#[allow(clippy::large_enum_variant)]
3882pub enum AgentRequest {
3883 WriteTextFileRequest(WriteTextFileRequest),
3890 ReadTextFileRequest(ReadTextFileRequest),
3897 RequestPermissionRequest(RequestPermissionRequest),
3908 CreateTerminalRequest(CreateTerminalRequest),
3923 TerminalOutputRequest(TerminalOutputRequest),
3930 ReleaseTerminalRequest(ReleaseTerminalRequest),
3943 WaitForTerminalExitRequest(WaitForTerminalExitRequest),
3947 KillTerminalRequest(KillTerminalRequest),
3960 CreateElicitationRequest(CreateElicitationRequest),
3964 #[cfg(feature = "unstable_mcp_over_acp")]
3970 MessageMcpRequest(MessageMcpRequest),
3971 ExtMethodRequest(ExtRequest),
3979}
3980
3981impl AgentRequest {
3982 #[must_use]
3984 pub fn method(&self) -> &str {
3985 match self {
3986 Self::WriteTextFileRequest(_) => CLIENT_METHOD_NAMES.fs_write_text_file,
3987 Self::ReadTextFileRequest(_) => CLIENT_METHOD_NAMES.fs_read_text_file,
3988 Self::RequestPermissionRequest(_) => CLIENT_METHOD_NAMES.session_request_permission,
3989 Self::CreateTerminalRequest(_) => CLIENT_METHOD_NAMES.terminal_create,
3990 Self::TerminalOutputRequest(_) => CLIENT_METHOD_NAMES.terminal_output,
3991 Self::ReleaseTerminalRequest(_) => CLIENT_METHOD_NAMES.terminal_release,
3992 Self::WaitForTerminalExitRequest(_) => CLIENT_METHOD_NAMES.terminal_wait_for_exit,
3993 Self::KillTerminalRequest(_) => CLIENT_METHOD_NAMES.terminal_kill,
3994 Self::CreateElicitationRequest(_) => CLIENT_METHOD_NAMES.elicitation_create,
3995 #[cfg(feature = "unstable_mcp_over_acp")]
3996 Self::MessageMcpRequest(_) => CLIENT_METHOD_NAMES.mcp_message,
3997 Self::ExtMethodRequest(ext_request) => &ext_request.method,
3998 }
3999 }
4000}
4001
4002#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4009#[derive(Clone, Debug, Serialize, Deserialize)]
4010#[serde(untagged)]
4011#[cfg_attr(feature = "schemars", schemars(inline))]
4012#[non_exhaustive]
4013pub enum ClientResponse {
4014 WriteTextFileResponse(#[serde(default)] WriteTextFileResponse),
4016 ReadTextFileResponse(ReadTextFileResponse),
4018 RequestPermissionResponse(RequestPermissionResponse),
4020 CreateTerminalResponse(CreateTerminalResponse),
4022 TerminalOutputResponse(TerminalOutputResponse),
4024 ReleaseTerminalResponse(#[serde(default)] ReleaseTerminalResponse),
4026 WaitForTerminalExitResponse(WaitForTerminalExitResponse),
4028 KillTerminalResponse(#[serde(default)] KillTerminalResponse),
4030 CreateElicitationResponse(CreateElicitationResponse),
4032 #[cfg(feature = "unstable_mcp_over_acp")]
4034 MessageMcpResponse(MessageMcpResponse),
4035 ExtMethodResponse(ExtResponse),
4037}
4038
4039#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4046#[derive(Clone, Debug, Serialize, Deserialize)]
4047#[serde(untagged)]
4048#[expect(clippy::large_enum_variant)]
4049#[cfg_attr(feature = "schemars", schemars(inline))]
4050#[non_exhaustive]
4051pub enum AgentNotification {
4052 SessionNotification(SessionNotification),
4064 CompleteElicitationNotification(CompleteElicitationNotification),
4068 ExtNotification(ExtNotification),
4076}
4077
4078impl AgentNotification {
4079 #[must_use]
4081 pub fn method(&self) -> &str {
4082 match self {
4083 Self::SessionNotification(_) => CLIENT_METHOD_NAMES.session_update,
4084 Self::CompleteElicitationNotification(_) => CLIENT_METHOD_NAMES.elicitation_complete,
4085 Self::ExtNotification(ext_notification) => &ext_notification.method,
4086 }
4087 }
4088}
4089
4090#[cfg(test)]
4091mod tests {
4092 use super::*;
4093
4094 #[test]
4095 fn notice_preserves_wire_shape_nullable_fields_and_open_severity() {
4096 use serde_json::json;
4097
4098 let mut meta = Meta::new();
4099 meta.insert("source".into(), json!("fallback"));
4100 assert_eq!(
4101 serde_json::to_value(SessionUpdate::Notice(
4102 Notice::new(NoticeSeverity::Warning, "MCP server unavailable")
4103 .description("Continuing without it.")
4104 .meta(meta),
4105 ))
4106 .unwrap(),
4107 json!({
4108 "sessionUpdate": "notice",
4109 "severity": "warning",
4110 "title": "MCP server unavailable",
4111 "description": "Continuing without it.",
4112 "_meta": { "source": "fallback" }
4113 })
4114 );
4115
4116 assert_eq!(
4117 serde_json::to_value(SessionUpdate::Notice(Notice::new(
4118 NoticeSeverity::Info,
4119 "Indexing workspace",
4120 )))
4121 .unwrap(),
4122 json!({
4123 "sessionUpdate": "notice",
4124 "severity": "info",
4125 "title": "Indexing workspace"
4126 })
4127 );
4128
4129 let SessionUpdate::Notice(notice) = serde_json::from_value(json!({
4130 "sessionUpdate": "notice",
4131 "severity": "critical",
4132 "title": "Provider degraded",
4133 "description": null,
4134 "_meta": null
4135 }))
4136 .unwrap() else {
4137 panic!("expected notice");
4138 };
4139
4140 assert_eq!(
4141 notice.severity,
4142 NoticeSeverity::Other("critical".to_string())
4143 );
4144 assert_eq!(notice.description, None);
4145 assert_eq!(notice.meta, None);
4146 assert_eq!(
4147 serde_json::to_value(SessionUpdate::Notice(notice)).unwrap(),
4148 json!({
4149 "sessionUpdate": "notice",
4150 "severity": "critical",
4151 "title": "Provider degraded"
4152 })
4153 );
4154 }
4155
4156 #[test]
4157 fn notice_requires_string_severity_and_title() {
4158 use serde_json::json;
4159
4160 for malformed in [
4161 json!({
4162 "sessionUpdate": "notice",
4163 "severity": "warning",
4164 "title": 42
4165 }),
4166 json!({
4167 "sessionUpdate": "notice",
4168 "severity": 42,
4169 "title": "MCP server unavailable"
4170 }),
4171 json!({
4172 "sessionUpdate": "notice",
4173 "severity": "warning"
4174 }),
4175 json!({
4176 "sessionUpdate": "notice",
4177 "severity": "warning",
4178 "title": null
4179 }),
4180 json!({
4181 "sessionUpdate": "notice",
4182 "title": "MCP server unavailable"
4183 }),
4184 json!({
4185 "sessionUpdate": "notice",
4186 "severity": null,
4187 "title": "MCP server unavailable"
4188 }),
4189 ] {
4190 assert!(serde_json::from_str::<SessionUpdate>(&malformed.to_string()).is_err());
4191 assert!(serde_json::from_value::<SessionUpdate>(malformed).is_err());
4192 }
4193 }
4194
4195 #[test]
4196 fn notice_tolerates_invalid_optional_display_fields_and_custom_severity() {
4197 use serde_json::json;
4198
4199 let wire = json!({
4200 "sessionUpdate": "notice",
4201 "severity": "_custom",
4202 "title": "Provider degraded",
4203 "description": 42,
4204 "_meta": false
4205 });
4206 let SessionUpdate::Notice(notice) = serde_json::from_value(wire.clone()).unwrap() else {
4207 panic!("expected notice");
4208 };
4209 assert_eq!(
4211 serde_json::from_str::<SessionUpdate>(&wire.to_string()).unwrap(),
4212 SessionUpdate::Notice(notice.clone())
4213 );
4214 assert_eq!(notice.severity, NoticeSeverity::Other("_custom".into()));
4215 assert_eq!(notice.description, None);
4216 assert_eq!(notice.meta, None);
4217 assert_eq!(
4218 serde_json::to_value(notice).unwrap(),
4219 json!({"severity": "_custom", "title": "Provider degraded"})
4220 );
4221 }
4222
4223 #[cfg(feature = "schemars")]
4224 #[test]
4225 fn notice_schema_preserves_required_and_nullable_fields() {
4226 use serde_json::json;
4227
4228 let schema = serde_json::to_value(schemars::schema_for!(Notice)).unwrap();
4229 assert_eq!(schema["required"], json!(["severity", "title"]));
4230 assert_eq!(schema["properties"]["title"]["minLength"], 1);
4231 for field in ["description", "_meta"] {
4232 assert!(
4233 schema["properties"][field]["type"]
4234 .as_array()
4235 .unwrap()
4236 .contains(&json!("null"))
4237 );
4238 }
4239 let capabilities =
4240 serde_json::to_value(schemars::schema_for!(ClientSessionCapabilities)).unwrap();
4241 assert!(
4242 capabilities["properties"]["notices"]["anyOf"]
4243 .as_array()
4244 .unwrap()
4245 .contains(&json!({"type": "null"}))
4246 );
4247 assert!(
4248 !capabilities["required"]
4249 .as_array()
4250 .is_some_and(|required| required.contains(&json!("notices")))
4251 );
4252 }
4253
4254 #[test]
4255 fn notice_capability_advertises_support_only_when_present() {
4256 use serde_json::json;
4257
4258 let capabilities = ClientCapabilities::new()
4259 .session(ClientSessionCapabilities::new().notices(NoticeCapabilities::new()));
4260 let value = serde_json::to_value(&capabilities).unwrap();
4261 assert_eq!(value["session"], json!({ "notices": {} }));
4262 assert_eq!(
4263 serde_json::from_value::<ClientCapabilities>(value).unwrap(),
4264 capabilities
4265 );
4266
4267 for unsupported in [
4268 json!({}),
4269 json!({ "session": null }),
4270 json!({ "session": {} }),
4271 json!({ "session": { "notices": null } }),
4272 json!({ "session": { "notices": false } }),
4273 json!({ "session": { "notices": true } }),
4274 json!({ "session": { "notices": "supported" } }),
4275 ] {
4276 let capabilities: ClientCapabilities = serde_json::from_value(unsupported).unwrap();
4277 assert!(
4278 capabilities
4279 .session
4280 .and_then(|session| session.notices)
4281 .is_none()
4282 );
4283 }
4284
4285 assert_eq!(
4286 serde_json::to_value(
4287 ClientSessionCapabilities::new()
4288 .notices(NoticeCapabilities::new())
4289 .notices(None)
4290 )
4291 .unwrap(),
4292 json!({})
4293 );
4294 }
4295
4296 #[test]
4297 fn compaction_updates_preserve_patch_and_open_status_semantics() {
4298 use serde_json::json;
4299
4300 assert_eq!(
4301 serde_json::to_value(SessionUpdate::CompactionUpdate(CompactionUpdate::new(
4302 "cmp_001",
4303 CompactionStatus::InProgress,
4304 )))
4305 .unwrap(),
4306 json!({
4307 "sessionUpdate": "compaction_update",
4308 "compactionId": "cmp_001",
4309 "status": "in_progress"
4310 })
4311 );
4312
4313 let SessionUpdate::CompactionUpdate(update) = serde_json::from_value(json!({
4314 "sessionUpdate": "compaction_update",
4315 "compactionId": "cmp_001",
4316 "status": "paused",
4317 "summary": null,
4318 "error": null
4319 }))
4320 .unwrap() else {
4321 panic!("expected compaction update");
4322 };
4323 assert_eq!(update.status, CompactionStatus::Other("paused".into()));
4324 assert!(update.summary.is_null());
4325 assert!(update.error.is_null());
4326 assert!(update.meta.is_undefined());
4327 }
4328
4329 #[test]
4330 fn compaction_chunk_and_v1_capability_serialize() {
4331 use serde_json::json;
4332
4333 assert_eq!(
4334 serde_json::to_value(SessionUpdate::CompactionSummaryChunk(
4335 CompactionSummaryChunk::new(
4336 "cmp_001",
4337 ContentBlock::Text(crate::v1::TextContent::new("retained")),
4338 ),
4339 ))
4340 .unwrap(),
4341 json!({
4342 "sessionUpdate": "compaction_summary_chunk",
4343 "compactionId": "cmp_001",
4344 "content": { "type": "text", "text": "retained" }
4345 })
4346 );
4347 assert_eq!(
4348 serde_json::to_value(
4349 ClientSessionCapabilities::new().compaction(CompactionCapabilities::new())
4350 )
4351 .unwrap(),
4352 json!({ "compaction": {} })
4353 );
4354 let absent: ClientSessionCapabilities = serde_json::from_value(json!({})).unwrap();
4355 let null: ClientSessionCapabilities =
4356 serde_json::from_value(json!({ "compaction": null })).unwrap();
4357 assert!(absent.compaction.is_none());
4358 assert!(null.compaction.is_none());
4359 }
4360
4361 #[cfg(feature = "unstable_subagents")]
4362 #[test]
4363 fn test_subagent_updates_serialization() {
4364 use serde_json::json;
4365
4366 let announced = SessionUpdate::SubagentUpdate(SubagentUpdate::new("sess_child_1"));
4367 assert_eq!(
4368 serde_json::to_value(&announced).unwrap(),
4369 json!({
4370 "sessionUpdate": "subagent_update",
4371 "sessionId": "sess_child_1"
4372 })
4373 );
4374 assert_eq!(
4375 serde_json::from_value::<SessionUpdate>(serde_json::to_value(&announced).unwrap())
4376 .unwrap(),
4377 announced
4378 );
4379 let capable = SubagentUpdate::new("sess_child_1").capabilities(
4380 SubagentSessionCapabilities::new().cancel(SessionCancelCapabilities::new()),
4381 );
4382 assert_eq!(
4383 serde_json::to_value(capable).unwrap(),
4384 json!({
4385 "sessionId": "sess_child_1",
4386 "capabilities": {"cancel": {}}
4387 })
4388 );
4389 let minimal: SubagentUpdate =
4390 serde_json::from_value(json!({ "sessionId": "sess_child_3" })).unwrap();
4391 assert!(minimal.capabilities.is_undefined());
4392 assert!(minimal.state.is_undefined());
4393
4394 let nulls: SubagentUpdate = serde_json::from_value(json!({
4395 "sessionId": "sess_child_3",
4396 "capabilities": null,
4397 "state": null
4398 }))
4399 .unwrap();
4400 assert!(nulls.capabilities.is_null());
4401 assert!(nulls.state.is_null());
4402 assert_eq!(
4403 serde_json::to_value(nulls).unwrap(),
4404 json!({"sessionId": "sess_child_3", "capabilities": null, "state": null})
4405 );
4406 }
4407
4408 #[cfg(feature = "unstable_subagents")]
4409 #[test]
4410 fn test_subagent_display_metadata() {
4411 use serde_json::json;
4412
4413 let update = SubagentUpdate::new("child")
4414 .title("Test investigator".to_string())
4415 .description("Investigates platform-specific test failures.".to_string());
4416 let wire = json!({
4417 "sessionId": "child",
4418 "title": "Test investigator",
4419 "description": "Investigates platform-specific test failures."
4420 });
4421 assert_eq!(serde_json::to_value(&update).unwrap(), wire);
4422 assert_eq!(
4423 serde_json::from_value::<SubagentUpdate>(wire).unwrap(),
4424 update
4425 );
4426
4427 let minimal = SubagentUpdate::new("child");
4428 assert_eq!(
4429 serde_json::to_value(&minimal).unwrap(),
4430 json!({"sessionId": "child"})
4431 );
4432 let cleared: SubagentUpdate = serde_json::from_value(json!({
4433 "sessionId": "child", "title": null, "description": null
4434 }))
4435 .unwrap();
4436 assert!(cleared.title.is_null());
4437 assert!(cleared.description.is_null());
4438 assert_eq!(
4439 serde_json::to_value(cleared).unwrap(),
4440 json!({"sessionId": "child", "title": null, "description": null})
4441 );
4442 let invalid: SubagentUpdate = serde_json::from_value(json!({
4443 "sessionId": "child", "title": false, "description": false
4444 }))
4445 .unwrap();
4446 assert_eq!(invalid, minimal);
4447
4448 let title_only: SubagentUpdate = serde_json::from_value(json!({
4449 "sessionId": "child", "title": "Updated title"
4450 }))
4451 .unwrap();
4452 assert_eq!(
4453 title_only.title.value().map(String::as_str),
4454 Some("Updated title")
4455 );
4456 assert!(title_only.description.is_undefined());
4457 }
4458
4459 #[cfg(feature = "unstable_subagents")]
4460 #[test]
4461 fn subagent_patch_fields_preserve_omitted_null_and_concrete() {
4462 use serde_json::json;
4463
4464 let omitted = SubagentUpdate::new("child");
4465 for field in ["title", "description", "capabilities", "state", "_meta"] {
4466 let wire = json!({"sessionId": "child", field: null});
4467 let decoded: SubagentUpdate = serde_json::from_value(wire.clone()).unwrap();
4468 assert_eq!(serde_json::to_value(decoded).unwrap(), wire, "{field}");
4469 let malformed = json!({"sessionId": "child", field: false});
4470 let decoded: SubagentUpdate = serde_json::from_value(malformed).unwrap();
4471 assert_eq!(decoded, omitted, "{field}");
4472 }
4473 let concrete = json!({
4474 "sessionId": "child", "title": "Investigator",
4475 "description": "Tests", "capabilities": {"cancel": {}},
4476 "state": {"state": "running"}, "_meta": {"source": "parent"}
4477 });
4478 let decoded: SubagentUpdate = serde_json::from_value(concrete.clone()).unwrap();
4479 assert!(decoded.title.value().is_some());
4480 assert!(decoded.description.value().is_some());
4481 assert!(decoded.capabilities.value().is_some());
4482 assert!(decoded.state.value().is_some());
4483 assert!(decoded.meta.value().is_some());
4484 assert_eq!(serde_json::to_value(decoded).unwrap(), concrete);
4485 let cleared = SubagentUpdate::new("child")
4486 .title(None)
4487 .description(None)
4488 .capabilities(None)
4489 .state(None)
4490 .meta(None);
4491 assert_eq!(
4492 serde_json::to_value(cleared).unwrap(),
4493 json!({
4494 "sessionId": "child", "title": null, "description": null,
4495 "capabilities": null, "state": null, "_meta": null
4496 })
4497 );
4498 }
4499
4500 #[cfg(feature = "unstable_subagents")]
4501 #[test]
4502 fn test_subagent_notification_keeps_parent_and_child_ids_nested() {
4503 use serde_json::json;
4504
4505 let notification = SessionNotification::new(
4506 "parent",
4507 SessionUpdate::SubagentUpdate(SubagentUpdate::new("child")),
4508 );
4509 let wire = json!({
4510 "sessionId": "parent",
4511 "update": {
4512 "sessionUpdate": "subagent_update",
4513 "sessionId": "child"
4514 }
4515 });
4516 assert_eq!(serde_json::to_value(¬ification).unwrap(), wire);
4517 assert_eq!(
4518 serde_json::from_value::<SessionNotification>(wire).unwrap(),
4519 notification
4520 );
4521 for malformed in [
4523 json!({"sessionId": "parent", "update": {"sessionUpdate": "subagent_update"}}),
4524 json!({"update": {"sessionUpdate": "subagent_update", "sessionId": "child"}}),
4525 ] {
4526 assert!(serde_json::from_value::<SessionNotification>(malformed).is_err());
4527 }
4528 }
4529
4530 #[cfg(feature = "unstable_subagents")]
4531 #[test]
4532 fn test_subagent_states_round_trip() {
4533 use serde_json::json;
4534
4535 for (wire_state, expected) in [
4536 (
4537 json!({"state": "running"}),
4538 StateUpdate::Running(RunningStateUpdate::new()),
4539 ),
4540 (
4541 json!({"state": "idle", "stopReason": "end_turn"}),
4542 StateUpdate::Idle(IdleStateUpdate::new().stop_reason(IdleStopReason::EndTurn)),
4543 ),
4544 (
4545 json!({"state": "requires_action"}),
4546 StateUpdate::RequiresAction(RequiresActionStateUpdate::new()),
4547 ),
4548 (
4549 json!({"state": "unknown"}),
4550 StateUpdate::Unknown(UnknownStateUpdate::new()),
4551 ),
4552 (
4553 json!({"state": "_waiting", "detail": {"ticket": 7}}),
4554 StateUpdate::Other(OtherStateUpdate::new(
4555 "_waiting",
4556 [("detail".into(), json!({"ticket": 7}))].into(),
4557 )),
4558 ),
4559 (
4560 json!({"state": "paused", "retryAt": 42}),
4561 StateUpdate::Other(OtherStateUpdate::new(
4562 "paused",
4563 [("retryAt".into(), json!(42))].into(),
4564 )),
4565 ),
4566 ] {
4567 let wire = json!({
4568 "sessionUpdate": "subagent_update",
4569 "sessionId": "sess_child",
4570 "state": wire_state
4571 });
4572 let parsed: SessionUpdate = serde_json::from_value(wire.clone()).unwrap();
4573 let SessionUpdate::SubagentUpdate(update) = &parsed else {
4574 panic!("expected subagent update");
4575 };
4576 assert_eq!(update.state.value(), Some(&expected));
4577 assert_eq!(serde_json::to_value(parsed).unwrap(), wire);
4578 }
4579
4580 for reason in [
4581 "end_turn",
4582 "max_tokens",
4583 "max_turn_requests",
4584 "refusal",
4585 "cancelled",
4586 ] {
4587 let state: StateUpdate = serde_json::from_value(json!({
4588 "state": "idle", "stopReason": reason
4589 }))
4590 .unwrap();
4591 assert_eq!(serde_json::to_value(state).unwrap()["stopReason"], reason);
4592 }
4593
4594 for state in [
4597 StateUpdate::Running(RunningStateUpdate::new()),
4598 StateUpdate::RequiresAction(RequiresActionStateUpdate::new()),
4599 StateUpdate::Unknown(UnknownStateUpdate::new()),
4600 StateUpdate::Running(RunningStateUpdate::new()),
4601 StateUpdate::Idle(IdleStateUpdate::new().stop_reason(IdleStopReason::EndTurn)),
4602 StateUpdate::Unknown(UnknownStateUpdate::new()),
4603 StateUpdate::Running(RunningStateUpdate::new()),
4604 ] {
4605 let update = SubagentUpdate::new("sess_child").state(state.clone());
4606 let wire = serde_json::to_value(&update).unwrap();
4607 assert_eq!(wire["sessionId"], "sess_child");
4608 assert_eq!(
4609 serde_json::from_value::<SubagentUpdate>(wire)
4610 .unwrap()
4611 .state,
4612 MaybeUndefined::Value(state)
4613 );
4614 }
4615 assert_eq!(
4616 serde_json::to_value(
4617 SubagentUpdate::new("sess_child").state(StateUpdate::Idle(IdleStateUpdate::new()))
4618 )
4619 .unwrap()["state"],
4620 json!({"state": "idle"})
4621 );
4622 }
4623
4624 #[cfg(feature = "unstable_subagents")]
4625 #[test]
4626 fn subagent_idle_snapshot_reports_child_failure() {
4627 use serde_json::json;
4628
4629 let update = SubagentUpdate::new("sess_child").state(StateUpdate::Idle(
4630 IdleStateUpdate::new().stop_reason(IdleStopReason::from(
4631 ErrorStopReason::new().error(Error::auth_required()),
4632 )),
4633 ));
4634 let wire = json!({
4635 "sessionId": "sess_child",
4636 "state": {
4637 "state": "idle",
4638 "stopReason": "error",
4639 "error": { "code": -32000, "message": "Authentication required" }
4640 }
4641 });
4642 assert_eq!(serde_json::to_value(&update).unwrap(), wire);
4643 assert_eq!(
4644 serde_json::from_value::<SubagentUpdate>(wire).unwrap(),
4645 update
4646 );
4647
4648 let without_details = StateUpdate::Idle(
4650 IdleStateUpdate::new().stop_reason(IdleStopReason::from(ErrorStopReason::new())),
4651 );
4652 for error in [None, Some(json!(null)), Some(json!("failed"))] {
4653 let mut wire = json!({ "state": "idle", "stopReason": "error" });
4654 if let Some(error) = error {
4655 wire["error"] = error;
4656 }
4657 assert_eq!(
4658 serde_json::from_value::<StateUpdate>(wire).unwrap(),
4659 without_details
4660 );
4661 }
4662
4663 let parsed: StateUpdate = serde_json::from_value(json!({
4666 "state": "idle",
4667 "stopReason": "end_turn",
4668 "error": { "code": -32603, "message": "Internal error" }
4669 }))
4670 .unwrap();
4671 assert_eq!(
4672 parsed,
4673 StateUpdate::Idle(IdleStateUpdate::new().stop_reason(IdleStopReason::EndTurn))
4674 );
4675
4676 let meta: Meta = [("source".into(), json!("worker"))].into_iter().collect();
4679 for stop_reason in [None, Some(json!(null)), Some(json!(true))] {
4680 let mut wire = json!({ "state": "idle", "_meta": { "source": "worker" } });
4681 if let Some(stop_reason) = stop_reason {
4682 wire["stopReason"] = stop_reason;
4683 }
4684 assert_eq!(
4685 serde_json::from_value::<StateUpdate>(wire).unwrap(),
4686 StateUpdate::Idle(IdleStateUpdate::new().meta(meta.clone()))
4687 );
4688 }
4689
4690 for stop_reason in ["_paused", "not_a_reason"] {
4693 let wire = json!({
4694 "state": "idle",
4695 "stopReason": stop_reason,
4696 "resumeAfter": 30,
4697 "_meta": { "source": "worker" }
4698 });
4699 let parsed: StateUpdate = serde_json::from_value(wire.clone()).unwrap();
4700 assert_eq!(
4701 parsed,
4702 StateUpdate::Idle(
4703 IdleStateUpdate::new()
4704 .stop_reason(IdleStopReason::Other(OtherStopReason::new(
4705 stop_reason,
4706 [("resumeAfter".into(), json!(30))].into_iter().collect(),
4707 )))
4708 .meta(meta.clone())
4709 )
4710 );
4711 assert_eq!(serde_json::to_value(&parsed).unwrap(), wire);
4712 }
4713 }
4714
4715 #[cfg(feature = "unstable_subagents")]
4716 #[test]
4717 fn subagent_known_stop_reasons_are_never_read_as_other() {
4718 use serde_json::json;
4719
4720 let known = [
4721 IdleStopReason::EndTurn,
4722 IdleStopReason::MaxTokens,
4723 IdleStopReason::MaxTurnRequests,
4724 IdleStopReason::Refusal,
4725 IdleStopReason::Cancelled,
4726 IdleStopReason::Error(ErrorStopReason::new()),
4727 ];
4728 for stop_reason in &known {
4730 match stop_reason {
4731 IdleStopReason::EndTurn
4732 | IdleStopReason::MaxTokens
4733 | IdleStopReason::MaxTurnRequests
4734 | IdleStopReason::Refusal
4735 | IdleStopReason::Cancelled
4736 | IdleStopReason::Error(_)
4737 | IdleStopReason::Other(_) => {}
4738 }
4739 }
4740
4741 for stop_reason in known {
4742 let state = StateUpdate::Idle(IdleStateUpdate::new().stop_reason(stop_reason));
4743 let wire = serde_json::to_value(&state).unwrap();
4744 assert_eq!(
4745 serde_json::from_value::<StateUpdate>(wire.clone()).unwrap(),
4746 state
4747 );
4748 assert!(
4749 serde_json::from_value::<OtherStopReason>(
4750 json!({ "stopReason": wire["stopReason"] })
4751 )
4752 .is_err(),
4753 "{} must not be accepted as a custom stop reason",
4754 wire["stopReason"]
4755 );
4756 }
4757 }
4758
4759 #[cfg(feature = "unstable_subagents")]
4760 #[test]
4761 fn subagent_unknown_stop_reason_leaves_usage_on_the_snapshot() {
4762 use serde_json::json;
4763
4764 let usage = json!({ "totalTokens": 10, "inputTokens": 6, "outputTokens": 4 });
4765 let wire = json!({ "state": "idle", "stopReason": "_paused", "usage": usage });
4766 let StateUpdate::Idle(idle) = serde_json::from_value(wire).unwrap() else {
4767 panic!("expected idle state snapshot");
4768 };
4769 let Some(IdleStopReason::Other(other)) = &idle.stop_reason else {
4770 panic!("expected a custom stop reason");
4771 };
4772 assert!(other.fields.is_empty());
4773
4774 #[cfg(feature = "unstable_end_turn_token_usage")]
4775 {
4776 assert!(idle.usage.is_some());
4777 assert_eq!(serde_json::to_value(&idle).unwrap()["usage"], usage);
4778 }
4779 #[cfg(not(feature = "unstable_end_turn_token_usage"))]
4782 assert!(serde_json::to_value(&idle).unwrap().get("usage").is_none());
4783 }
4784
4785 #[cfg(feature = "unstable_subagents")]
4786 #[test]
4787 fn test_subagent_unknown_activity_metadata_and_capabilities() {
4788 use serde_json::json;
4789
4790 let update = SubagentUpdate::new("child").state(StateUpdate::Unknown(
4791 UnknownStateUpdate::new().meta(
4792 [("source".into(), json!("worker"))]
4793 .into_iter()
4794 .collect::<Meta>(),
4795 ),
4796 ));
4797 let wire = json!({
4798 "sessionId": "child",
4799 "state": {
4800 "state": "unknown",
4801 "_meta": { "source": "worker" }
4802 }
4803 });
4804 assert_eq!(serde_json::to_value(&update).unwrap(), wire);
4805 assert_eq!(
4806 serde_json::from_value::<SubagentUpdate>(wire).unwrap(),
4807 update
4808 );
4809 assert!(update.capabilities.is_undefined());
4811
4812 for meta in [json!(null), json!(false)] {
4813 let state: StateUpdate = serde_json::from_value(json!({
4814 "state": "unknown",
4815 "_meta": meta
4816 }))
4817 .unwrap();
4818 assert_eq!(state, StateUpdate::Unknown(UnknownStateUpdate::new()));
4819 assert_eq!(
4820 serde_json::to_value(state).unwrap(),
4821 json!({"state": "unknown"})
4822 );
4823 }
4824 assert!(serde_json::from_value::<OtherStateUpdate>(json!({"state": "unknown"})).is_err());
4826 }
4827
4828 #[cfg(feature = "unstable_subagents")]
4829 #[test]
4830 fn test_subagent_malformed_optional_state() {
4831 use serde_json::json;
4832
4833 for malformed in [json!("running"), json!({}), json!({"state": 4})] {
4834 assert!(serde_json::from_value::<StateUpdate>(malformed.clone()).is_err());
4835 let update: SubagentUpdate = serde_json::from_value(json!({
4836 "sessionId": "child", "state": malformed
4837 }))
4838 .unwrap();
4839 assert!(update.state.is_undefined());
4840 }
4841 let bad_reason: StateUpdate =
4842 serde_json::from_value(json!({"state": "idle", "stopReason": false})).unwrap();
4843 assert_eq!(bad_reason, StateUpdate::Idle(IdleStateUpdate::new()));
4844 let null_reason: StateUpdate =
4845 serde_json::from_value(json!({"state": "idle", "stopReason": null})).unwrap();
4846 assert_eq!(null_reason, StateUpdate::Idle(IdleStateUpdate::new()));
4847 }
4848
4849 #[cfg(all(feature = "unstable_subagents", feature = "unstable_protocol_v2"))]
4850 #[test]
4851 fn test_subagent_state_v2_wire_parity() {
4852 use serde_json::json;
4853
4854 for wire in [
4855 json!({"state": "running"}),
4856 json!({"state": "idle", "stopReason": "cancelled"}),
4857 json!({"state": "requires_action"}),
4858 json!({"state": "unknown"}),
4859 json!({"state": "unknown", "_meta": {"source": "worker"}}),
4860 json!({"state": "_waiting", "detail": {"ticket": 7}}),
4861 ] {
4862 let v1: StateUpdate = serde_json::from_value(wire.clone()).unwrap();
4863 let v2: crate::v2::StateUpdate = serde_json::from_value(wire.clone()).unwrap();
4864 assert_eq!(serde_json::to_value(v1).unwrap(), wire);
4865 assert_eq!(serde_json::to_value(v2).unwrap(), wire);
4866 }
4867 }
4868
4869 #[cfg(feature = "unstable_subagents")]
4870 #[test]
4871 fn test_subagent_capability_semantics() {
4872 use serde_json::json;
4873
4874 let capabilities =
4875 serde_json::to_value(ClientCapabilities::new().subagents(SubagentCapabilities::new()))
4876 .unwrap();
4877 assert_eq!(capabilities["subagents"], json!({}));
4878
4879 let omitted: ClientCapabilities = serde_json::from_value(json!({})).unwrap();
4880 assert!(omitted.subagents.is_none());
4881
4882 let null: ClientCapabilities =
4883 serde_json::from_value(json!({ "subagents": null })).unwrap();
4884 assert!(null.subagents.is_none());
4885 assert_eq!(null, omitted);
4886 let serialized = serde_json::to_value(null).unwrap();
4887 assert_eq!(serialized, serde_json::to_value(omitted).unwrap());
4888 assert!(serialized.get("subagents").is_none());
4889
4890 for wire in [
4891 json!({}),
4892 json!({"cancel": null}),
4893 json!({"cancel": true}),
4894 json!({"cancel": false}),
4895 ] {
4896 let child: SubagentSessionCapabilities = serde_json::from_value(wire).unwrap();
4897 assert!(child.cancel.is_none());
4898 assert_eq!(serde_json::to_value(child).unwrap(), json!({}));
4899 }
4900 let enabled = SubagentSessionCapabilities::new().cancel(SessionCancelCapabilities::new());
4901 assert_eq!(
4902 serde_json::to_value(&enabled).unwrap(),
4903 json!({"cancel": {}})
4904 );
4905 assert_eq!(
4906 serde_json::from_value::<SubagentSessionCapabilities>(json!({"cancel": {}})).unwrap(),
4907 enabled
4908 );
4909 let meta: Meta = [("source".into(), json!("worker"))].into_iter().collect();
4910 let with_meta =
4911 SubagentSessionCapabilities::new().cancel(SessionCancelCapabilities::new().meta(meta));
4912 let wire = json!({"cancel": {"_meta": {"source": "worker"}}});
4913 assert_eq!(serde_json::to_value(&with_meta).unwrap(), wire);
4914 assert_eq!(
4915 serde_json::from_value::<SubagentSessionCapabilities>(wire).unwrap(),
4916 with_meta
4917 );
4918 let removed = SubagentSessionCapabilities::new();
4920 assert_eq!(serde_json::to_value(&removed).unwrap(), json!({}));
4921 assert!(removed.cancel.is_none());
4922 assert!(enabled.cancel.is_some());
4923 assert!(enabled.cancel(None).cancel.is_none());
4924 let replacement = SubagentUpdate::new("child").capabilities(removed);
4925 assert_eq!(
4926 serde_json::to_value(replacement).unwrap(),
4927 json!({"sessionId": "child", "capabilities": {}})
4928 );
4929 }
4930
4931 #[cfg(all(feature = "unstable_subagents", feature = "schemars"))]
4932 #[test]
4933 fn test_subagent_capability_schema_is_optional_and_nullable() {
4934 use serde_json::json;
4935
4936 let schema = serde_json::to_value(schemars::schema_for!(ClientCapabilities)).unwrap();
4937 let variants = schema["properties"]["subagents"]["anyOf"]
4938 .as_array()
4939 .expect("subagents must allow the capability object or null");
4940 assert!(variants.contains(&json!({"type": "null"})));
4941 assert!(
4942 !schema["required"]
4943 .as_array()
4944 .is_some_and(|required| required.contains(&json!("subagents")))
4945 );
4946 let child =
4947 serde_json::to_value(schemars::schema_for!(SubagentSessionCapabilities)).unwrap();
4948 let variants = child["properties"]["cancel"]["anyOf"]
4949 .as_array()
4950 .expect("cancel must allow the capability object or null");
4951 assert!(variants.contains(&json!({"type": "null"})));
4952 assert!(
4953 variants
4954 .iter()
4955 .any(|variant| variant["type"] == "object" || variant.get("$ref").is_some())
4956 );
4957 assert!(!variants.iter().any(|variant| variant["type"] == "boolean"));
4958 let cancel =
4959 serde_json::to_value(schemars::schema_for!(SessionCancelCapabilities)).unwrap();
4960 assert_eq!(cancel["type"], "object");
4961 assert!(
4962 !child["required"]
4963 .as_array()
4964 .is_some_and(|required| required.contains(&json!("cancel")))
4965 );
4966 }
4967
4968 #[test]
4969 fn test_elicitation_capability_semantics() {
4970 use serde_json::json;
4971
4972 let unsupported: ClientCapabilities = serde_json::from_value(json!({})).unwrap();
4973 assert!(unsupported.elicitation.is_none());
4974
4975 let null: ClientCapabilities =
4976 serde_json::from_value(json!({ "elicitation": null })).unwrap();
4977 assert!(null.elicitation.is_none());
4978
4979 let malformed: ClientCapabilities =
4980 serde_json::from_value(json!({ "elicitation": false })).unwrap();
4981 assert!(malformed.elicitation.is_none());
4982
4983 let empty: ClientCapabilities =
4984 serde_json::from_value(json!({ "elicitation": {} })).unwrap();
4985 let empty = empty.elicitation.expect("present capability");
4986 assert!(!empty.supports_form());
4987 assert!(!empty.supports_url());
4988
4989 let form_only: ClientCapabilities = serde_json::from_value(json!({
4990 "elicitation": { "form": {} }
4991 }))
4992 .unwrap();
4993 let form_only = form_only.elicitation.expect("advertised capability");
4994 assert!(form_only.supports_form());
4995 assert!(!form_only.supports_url());
4996
4997 let url_only: ClientCapabilities = serde_json::from_value(json!({
4998 "elicitation": { "url": {} }
4999 }))
5000 .unwrap();
5001 let url_only = url_only.elicitation.expect("advertised capability");
5002 assert!(!url_only.supports_form());
5003 assert!(url_only.supports_url());
5004
5005 let both: ClientCapabilities = serde_json::from_value(json!({
5006 "elicitation": { "form": {}, "url": {} }
5007 }))
5008 .unwrap();
5009 let both = both.elicitation.expect("advertised capability");
5010 assert!(both.supports_form());
5011 assert!(both.supports_url());
5012 }
5013
5014 #[test]
5015 fn test_elicitation_method_routing_and_envelopes() {
5016 use serde_json::json;
5017
5018 assert_eq!(CLIENT_METHOD_NAMES.elicitation_create, "elicitation/create");
5019 assert_eq!(
5020 CLIENT_METHOD_NAMES.elicitation_complete,
5021 "elicitation/complete"
5022 );
5023
5024 let request = AgentRequest::CreateElicitationRequest(CreateElicitationRequest::new(
5025 crate::v1::ElicitationFormMode::new(
5026 crate::v1::ElicitationSessionScope::new("sess_1"),
5027 crate::v1::ElicitationSchema::new(),
5028 ),
5029 "Choose a value",
5030 ));
5031 assert_eq!(request.method(), "elicitation/create");
5032 let method = Arc::from(request.method());
5033 let request = crate::v1::JsonRpcMessage::wrap(crate::v1::Request {
5034 id: crate::v1::RequestId::Number(7),
5035 method,
5036 params: Some(request),
5037 });
5038 assert_eq!(
5039 serde_json::to_value(request).unwrap(),
5040 json!({
5041 "jsonrpc": "2.0",
5042 "id": 7,
5043 "method": "elicitation/create",
5044 "params": {
5045 "mode": "form",
5046 "sessionId": "sess_1",
5047 "message": "Choose a value",
5048 "requestedSchema": { "type": "object", "properties": {} }
5049 }
5050 })
5051 );
5052
5053 let notification = AgentNotification::CompleteElicitationNotification(
5054 CompleteElicitationNotification::new("elic_1"),
5055 );
5056 assert_eq!(notification.method(), "elicitation/complete");
5057 let method = Arc::from(notification.method());
5058 let notification = crate::v1::JsonRpcMessage::wrap(crate::v1::Notification {
5059 method,
5060 params: Some(notification),
5061 });
5062 assert_eq!(
5063 serde_json::to_value(notification).unwrap(),
5064 json!({
5065 "jsonrpc": "2.0",
5066 "method": "elicitation/complete",
5067 "params": { "elicitationId": "elic_1" }
5068 })
5069 );
5070 }
5071
5072 #[test]
5073 fn test_client_capabilities_default_on_malformed_values() {
5074 use serde_json::json;
5075
5076 let capabilities: ClientCapabilities = serde_json::from_value(json!({
5077 "fs": {
5078 "readTextFile": "yes",
5079 "writeTextFile": true
5080 },
5081 "terminal": {}
5082 }))
5083 .unwrap();
5084
5085 assert!(!capabilities.fs.read_text_file);
5086 assert!(capabilities.fs.write_text_file);
5087 assert!(!capabilities.terminal);
5088
5089 let capabilities: ClientCapabilities = serde_json::from_value(json!({
5090 "fs": false
5091 }))
5092 .unwrap();
5093 assert_eq!(capabilities.fs, FileSystemCapabilities::default());
5094
5095 {
5096 let capabilities: ClientCapabilities = serde_json::from_value(json!({
5097 "auth": false
5098 }))
5099 .unwrap();
5100 assert_eq!(capabilities.auth, AuthCapabilities::default());
5101
5102 let capabilities: AuthCapabilities = serde_json::from_value(json!({
5103 "terminal": {}
5104 }))
5105 .unwrap();
5106 assert!(!capabilities.terminal);
5107 }
5108 }
5109
5110 #[test]
5111 fn test_serialization_behavior() {
5112 use serde_json::json;
5113
5114 assert_eq!(
5115 serde_json::from_value::<SessionInfoUpdate>(json!({})).unwrap(),
5116 SessionInfoUpdate {
5117 title: MaybeUndefined::Undefined,
5118 updated_at: MaybeUndefined::Undefined,
5119 meta: None
5120 }
5121 );
5122 assert_eq!(
5123 serde_json::from_value::<SessionInfoUpdate>(json!({"title": null, "updatedAt": null}))
5124 .unwrap(),
5125 SessionInfoUpdate {
5126 title: MaybeUndefined::Null,
5127 updated_at: MaybeUndefined::Null,
5128 meta: None
5129 }
5130 );
5131 assert_eq!(
5132 serde_json::from_value::<SessionInfoUpdate>(
5133 json!({"title": "title", "updatedAt": "timestamp"})
5134 )
5135 .unwrap(),
5136 SessionInfoUpdate {
5137 title: MaybeUndefined::Value("title".to_string()),
5138 updated_at: MaybeUndefined::Value("timestamp".to_string()),
5139 meta: None
5140 }
5141 );
5142
5143 assert_eq!(
5144 serde_json::to_value(SessionInfoUpdate::new()).unwrap(),
5145 json!({})
5146 );
5147 assert_eq!(
5148 serde_json::to_value(SessionInfoUpdate::new().title("title")).unwrap(),
5149 json!({"title": "title"})
5150 );
5151 assert_eq!(
5152 serde_json::to_value(SessionInfoUpdate::new().title(None)).unwrap(),
5153 json!({"title": null})
5154 );
5155 assert_eq!(
5156 serde_json::to_value(
5157 SessionInfoUpdate::new()
5158 .title("title")
5159 .title(MaybeUndefined::Undefined)
5160 )
5161 .unwrap(),
5162 json!({})
5163 );
5164 }
5165
5166 #[test]
5167 fn test_content_chunk_message_id_serialization() {
5168 use serde_json::json;
5169
5170 assert_eq!(
5171 serde_json::to_value(SessionUpdate::AgentMessageChunk(ContentChunk::new(
5172 ContentBlock::Text(crate::v1::TextContent::new("Hello"))
5173 )))
5174 .unwrap(),
5175 json!({
5176 "sessionUpdate": "agent_message_chunk",
5177 "content": {
5178 "type": "text",
5179 "text": "Hello"
5180 }
5181 })
5182 );
5183
5184 assert_eq!(
5185 serde_json::to_value(SessionUpdate::AgentMessageChunk(
5186 ContentChunk::new(ContentBlock::Text(crate::v1::TextContent::new("Hello")))
5187 .message_id("msg_agent_c42b9")
5188 ))
5189 .unwrap(),
5190 json!({
5191 "sessionUpdate": "agent_message_chunk",
5192 "messageId": "msg_agent_c42b9",
5193 "content": {
5194 "type": "text",
5195 "text": "Hello"
5196 }
5197 })
5198 );
5199
5200 let SessionUpdate::AgentMessageChunk(chunk) = serde_json::from_value(json!({
5201 "sessionUpdate": "agent_message_chunk",
5202 "messageId": null,
5203 "content": {
5204 "type": "text",
5205 "text": "Hello"
5206 }
5207 }))
5208 .unwrap() else {
5209 panic!("expected agent message chunk");
5210 };
5211
5212 assert_eq!(chunk.message_id, None);
5213 }
5214
5215 #[test]
5216 fn test_usage_update_serialization() {
5217 use serde_json::json;
5218
5219 assert_eq!(
5220 serde_json::to_value(SessionUpdate::UsageUpdate(UsageUpdate::new(
5221 53_000, 200_000
5222 )))
5223 .unwrap(),
5224 json!({
5225 "sessionUpdate": "usage_update",
5226 "used": 53000,
5227 "size": 200_000
5228 })
5229 );
5230
5231 assert_eq!(
5232 serde_json::to_value(SessionUpdate::UsageUpdate(
5233 UsageUpdate::new(53_000, 200_000).cost(Cost::new(0.045, "USD"))
5234 ))
5235 .unwrap(),
5236 json!({
5237 "sessionUpdate": "usage_update",
5238 "used": 53000,
5239 "size": 200_000,
5240 "cost": {
5241 "amount": 0.045,
5242 "currency": "USD"
5243 }
5244 })
5245 );
5246
5247 let SessionUpdate::UsageUpdate(update) = serde_json::from_value(json!({
5248 "sessionUpdate": "usage_update",
5249 "used": 53000,
5250 "size": 200_000,
5251 "cost": null
5252 }))
5253 .unwrap() else {
5254 panic!("expected usage update");
5255 };
5256
5257 assert_eq!(update.cost, None);
5258 }
5259
5260 #[test]
5261 fn test_client_capabilities_boolean_config_options_serialization() {
5262 use serde_json::json;
5263
5264 let capabilities = ClientCapabilities::new().session(
5265 ClientSessionCapabilities::new().config_options(
5266 SessionConfigOptionsCapabilities::new()
5267 .boolean(BooleanConfigOptionCapabilities::new()),
5268 ),
5269 );
5270 let json = serde_json::to_value(&capabilities).unwrap();
5271
5272 assert_eq!(json["session"]["configOptions"]["boolean"], json!({}));
5273
5274 let omitted: ClientCapabilities = serde_json::from_value(json!({})).unwrap();
5275 assert!(omitted.session.is_none());
5276
5277 let null_session: ClientCapabilities = serde_json::from_value(json!({
5278 "session": null
5279 }))
5280 .unwrap();
5281 assert!(null_session.session.is_none());
5282
5283 let null_config_options: ClientCapabilities = serde_json::from_value(json!({
5284 "session": {
5285 "configOptions": null
5286 }
5287 }))
5288 .unwrap();
5289 assert!(
5290 null_config_options
5291 .session
5292 .and_then(|session| session.config_options)
5293 .is_none()
5294 );
5295
5296 let null_boolean: ClientCapabilities = serde_json::from_value(json!({
5297 "session": {
5298 "configOptions": {
5299 "boolean": null
5300 }
5301 }
5302 }))
5303 .unwrap();
5304 assert!(
5305 null_boolean
5306 .session
5307 .and_then(|session| session.config_options)
5308 .and_then(|config_options| config_options.boolean)
5309 .is_none()
5310 );
5311 }
5312
5313 #[cfg(feature = "unstable_plan_operations")]
5314 #[test]
5315 fn test_plan_operations_serialization() {
5316 use serde_json::json;
5317
5318 use crate::v1::{PlanEntry, PlanEntryPriority, PlanEntryStatus, PlanUpdateContent};
5319
5320 let plan_update = SessionUpdate::PlanUpdate(PlanUpdate::new(PlanUpdateContent::items(
5321 "plan-1",
5322 vec![PlanEntry::new(
5323 "Step 1",
5324 PlanEntryPriority::High,
5325 PlanEntryStatus::Pending,
5326 )],
5327 )));
5328
5329 assert_eq!(
5330 serde_json::to_value(plan_update).unwrap(),
5331 json!({
5332 "sessionUpdate": "plan_update",
5333 "plan": {
5334 "type": "items",
5335 "planId": "plan-1",
5336 "entries": [
5337 {
5338 "content": "Step 1",
5339 "priority": "high",
5340 "status": "pending"
5341 }
5342 ]
5343 }
5344 })
5345 );
5346
5347 assert_eq!(
5348 serde_json::to_value(SessionUpdate::PlanRemoved(PlanRemoved::new("plan-1"))).unwrap(),
5349 json!({
5350 "sessionUpdate": "plan_removed",
5351 "planId": "plan-1"
5352 })
5353 );
5354
5355 let capabilities = ClientCapabilities::new().plan(PlanCapabilities::new());
5356 let json = serde_json::to_value(&capabilities).unwrap();
5357 assert_eq!(json["plan"], json!({}));
5358
5359 assert_eq!(
5360 serde_json::from_value::<ClientCapabilities>(json!({ "plan": null }))
5361 .unwrap()
5362 .plan,
5363 None
5364 );
5365 }
5366
5367 #[cfg(feature = "unstable_mcp_over_acp")]
5368 #[test]
5369 fn test_agent_mcp_request_method_names() {
5370 use serde_json::json;
5371
5372 let params: serde_json::Map<String, serde_json::Value> =
5373 [("cursor".to_string(), json!("abc"))].into_iter().collect();
5374
5375 assert_eq!(CLIENT_METHOD_NAMES.mcp_message, "mcp/message");
5376 assert_eq!(
5377 AgentRequest::MessageMcpRequest(MessageMcpRequest::new(
5378 "server-1",
5379 "req-1",
5380 "tools/list"
5381 ))
5382 .method(),
5383 "mcp/message"
5384 );
5385 assert_eq!(
5386 serde_json::to_value(
5387 MessageMcpRequest::new("server-1", "req-1", "tools/list").params(params)
5388 )
5389 .unwrap(),
5390 json!({
5391 "serverId": "server-1",
5392 "requestId": "req-1",
5393 "method": "tools/list",
5394 "params": { "cursor": "abc" }
5395 })
5396 );
5397
5398 let request_with_null_params: MessageMcpRequest = serde_json::from_value(json!({
5399 "serverId": "server-1",
5400 "requestId": "req-1",
5401 "method": "tools/list",
5402 "params": null,
5403 "_meta": null
5404 }))
5405 .unwrap();
5406 assert_eq!(request_with_null_params.params, None);
5407 assert_eq!(request_with_null_params.meta, None);
5408 for key in ["serverId", "requestId", "method"] {
5409 let mut value =
5410 json!({"serverId":"server-1", "requestId":"req-1", "method":"tools/list"});
5411 value.as_object_mut().unwrap().remove(key);
5412 assert!(serde_json::from_value::<MessageMcpRequest>(value).is_err());
5413 }
5414 for key in ["serverId", "requestId", "method"] {
5415 let mut value =
5416 json!({"serverId":"server-1", "requestId":"req-1", "method":"tools/list"});
5417 value[key] = serde_json::Value::Null;
5418 assert!(serde_json::from_value::<MessageMcpRequest>(value).is_err());
5419 }
5420 }
5421
5422 #[test]
5423 fn request_permission_request_rejects_malformed_options() {
5424 use serde_json::json;
5425
5426 assert!(
5427 serde_json::from_value::<RequestPermissionRequest>(json!({
5428 "sessionId": "sess-1",
5429 "toolCall": {"toolCallId": "tc-1"},
5430 "options": "not-an-array"
5431 }))
5432 .is_err()
5433 );
5434 assert!(
5435 serde_json::from_value::<RequestPermissionRequest>(json!({
5436 "sessionId": "sess-1",
5437 "toolCall": {"toolCallId": "tc-1"},
5438 "options": [{"optionId": "allow"}]
5439 }))
5440 .is_err()
5441 );
5442 }
5443
5444 #[test]
5445 fn create_terminal_request_rejects_malformed_fields() {
5446 use serde_json::json;
5447
5448 let request = |field: &str, value: serde_json::Value| {
5449 let mut request = json!({"sessionId": "sess-1", "command": "npx"});
5450 request[field] = value;
5451 serde_json::from_value::<CreateTerminalRequest>(request)
5452 };
5453
5454 for (field, value) in [
5456 ("args", json!(["serve", "--port", 3000])),
5457 ("args", json!("serve --port 3000")),
5458 ("env", json!([{"name": "PORT", "value": 3000}])),
5459 ("env", json!({"PORT": "3000"})),
5460 ("cwd", json!({"path": "/repo"})),
5461 ("outputByteLimit", json!("1048576")),
5462 ] {
5463 assert!(request(field, value.clone()).is_err(), "{field}: {value}");
5464 }
5465
5466 for field in ["args", "env", "cwd", "outputByteLimit"] {
5467 assert_eq!(
5468 request(field, serde_json::Value::Null).unwrap(),
5469 CreateTerminalRequest::new("sess-1", "npx"),
5470 "{field}"
5471 );
5472 }
5473 }
5474
5475 #[test]
5476 fn read_text_file_request_rejects_malformed_range() {
5477 use serde_json::json;
5478
5479 let request = |field: &str, value: serde_json::Value| {
5480 let mut request = json!({"sessionId": "sess-1", "path": "/repo/src/main.rs"});
5481 request[field] = value;
5482 serde_json::from_value::<ReadTextFileRequest>(request)
5483 };
5484
5485 for field in ["line", "limit"] {
5487 assert!(request(field, json!("120")).is_err(), "{field}");
5488 assert!(request(field, json!(-1)).is_err(), "{field}");
5489 assert_eq!(
5490 request(field, serde_json::Value::Null).unwrap(),
5491 ReadTextFileRequest::new("sess-1", "/repo/src/main.rs"),
5492 "{field}"
5493 );
5494 }
5495 }
5496}