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#[cfg(feature = "unstable_nes")]
40use super::{ClientNesCapabilities, PositionEncodingKind};
41
42#[serde_as]
50#[skip_serializing_none]
51#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
52#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
53#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = SESSION_UPDATE_NOTIFICATION)))]
54#[serde(rename_all = "camelCase")]
55#[non_exhaustive]
56pub struct SessionNotification {
57 pub session_id: SessionId,
59 pub update: SessionUpdate,
61 #[serde_as(deserialize_as = "DefaultOnError")]
67 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
68 #[serde(default)]
69 #[serde(rename = "_meta")]
70 pub meta: Option<Meta>,
71}
72
73impl SessionNotification {
74 #[must_use]
76 pub fn new(session_id: impl Into<SessionId>, update: SessionUpdate) -> Self {
77 Self {
78 session_id: session_id.into(),
79 update,
80 meta: None,
81 }
82 }
83
84 #[must_use]
90 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
91 self.meta = meta.into_option();
92 self
93 }
94}
95
96#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
102#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
103#[serde(tag = "sessionUpdate", rename_all = "snake_case")]
104#[cfg_attr(feature = "schemars", schemars(extend("discriminator" = {"propertyName": "sessionUpdate"})))]
105#[non_exhaustive]
106pub enum SessionUpdate {
107 UserMessageChunk(ContentChunk),
109 AgentMessageChunk(ContentChunk),
111 AgentThoughtChunk(ContentChunk),
113 ToolCall(ToolCall),
115 ToolCallUpdate(ToolCallUpdate),
117 Plan(Plan),
120 #[cfg(feature = "unstable_plan_operations")]
126 PlanUpdate(PlanUpdate),
127 #[cfg(feature = "unstable_plan_operations")]
133 PlanRemoved(PlanRemoved),
134 AvailableCommandsUpdate(AvailableCommandsUpdate),
136 CurrentModeUpdate(CurrentModeUpdate),
140 ConfigOptionUpdate(ConfigOptionUpdate),
142 SessionInfoUpdate(SessionInfoUpdate),
144 UsageUpdate(UsageUpdate),
146 Notice(Notice),
151 CompactionUpdate(CompactionUpdate),
156 CompactionSummaryChunk(CompactionSummaryChunk),
161 #[cfg(feature = "unstable_subagents")]
168 SubagentUpdate(SubagentUpdate),
169 #[cfg(feature = "unstable_subagents")]
176 SessionMessage(SessionMessage),
177 #[cfg(feature = "unstable_subagents")]
183 SessionMessageChunk(SessionMessageChunk),
184}
185
186#[cfg(feature = "unstable_subagents")]
192#[serde_as]
193#[skip_serializing_none]
194#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
195#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
196#[serde(rename_all = "camelCase")]
197#[non_exhaustive]
198pub struct SessionMessageChunk {
199 pub message_id: MessageId,
201 #[serde_as(deserialize_as = "DefaultOnError")]
203 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
204 #[serde(default)]
205 pub sender_session_id: Option<SessionId>,
206 #[serde_as(deserialize_as = "DefaultOnError")]
208 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
209 #[serde(default)]
210 pub recipient_session_id: Option<SessionId>,
211 pub content: ContentBlock,
213 #[serde_as(deserialize_as = "DefaultOnError")]
217 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
218 #[serde(default, rename = "_meta")]
219 pub meta: Option<Meta>,
220}
221
222#[cfg(feature = "unstable_subagents")]
223impl SessionMessageChunk {
224 #[must_use]
226 pub fn new(message_id: impl Into<MessageId>, content: ContentBlock) -> Self {
227 Self {
228 message_id: message_id.into(),
229 sender_session_id: None,
230 recipient_session_id: None,
231 content,
232 meta: None,
233 }
234 }
235
236 #[must_use]
238 pub fn sender_session_id(mut self, sender_session_id: impl IntoOption<SessionId>) -> Self {
239 self.sender_session_id = sender_session_id.into_option();
240 self
241 }
242
243 #[must_use]
245 pub fn recipient_session_id(
246 mut self,
247 recipient_session_id: impl IntoOption<SessionId>,
248 ) -> Self {
249 self.recipient_session_id = recipient_session_id.into_option();
250 self
251 }
252
253 #[must_use]
255 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
256 self.meta = meta.into_option();
257 self
258 }
259}
260
261#[cfg(feature = "unstable_subagents")]
267#[serde_as]
268#[skip_serializing_none]
269#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
270#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
271#[serde(rename_all = "camelCase")]
272#[non_exhaustive]
273pub struct SessionMessage {
274 pub message_id: MessageId,
276 #[serde_as(deserialize_as = "DefaultOnError")]
278 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
279 #[serde(default)]
280 pub sender_session_id: Option<SessionId>,
281 #[serde_as(deserialize_as = "DefaultOnError")]
283 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
284 #[serde(default)]
285 pub recipient_session_id: Option<SessionId>,
286 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<VecSkipError<_>>>")]
289 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
290 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
291 pub content: MaybeUndefined<Vec<ContentBlock>>,
292 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
296 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
297 #[serde(
298 default,
299 rename = "_meta",
300 skip_serializing_if = "MaybeUndefined::is_undefined"
301 )]
302 pub meta: MaybeUndefined<Meta>,
303}
304
305#[cfg(feature = "unstable_subagents")]
306impl SessionMessage {
307 #[must_use]
309 pub fn new(message_id: impl Into<MessageId>) -> Self {
310 Self {
311 message_id: message_id.into(),
312 sender_session_id: None,
313 recipient_session_id: None,
314 content: MaybeUndefined::Undefined,
315 meta: MaybeUndefined::Undefined,
316 }
317 }
318
319 #[must_use]
321 pub fn sender_session_id(mut self, sender_session_id: impl IntoOption<SessionId>) -> Self {
322 self.sender_session_id = sender_session_id.into_option();
323 self
324 }
325
326 #[must_use]
328 pub fn recipient_session_id(
329 mut self,
330 recipient_session_id: impl IntoOption<SessionId>,
331 ) -> Self {
332 self.recipient_session_id = recipient_session_id.into_option();
333 self
334 }
335
336 #[must_use]
338 pub fn content(mut self, content: impl IntoMaybeUndefined<Vec<ContentBlock>>) -> Self {
339 self.content = content.into_maybe_undefined();
340 self
341 }
342
343 #[must_use]
345 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
346 self.meta = meta.into_maybe_undefined();
347 self
348 }
349}
350
351#[cfg(all(test, feature = "unstable_subagents"))]
352mod session_message_tests {
353 use super::*;
354 use serde_json::{Value, json};
355
356 #[test]
357 fn envelopes_preserve_participants_and_multimodal_content() {
358 let content = json!([
359 {"type": "text", "text": "Please inspect this"},
360 {"type": "image", "data": "aGVsbG8=", "mimeType": "image/png"}
361 ]);
362 for (transcript, sender, recipient, id) in [
363 ("parent", "parent", "child", "sent-1"),
364 ("child", "parent", "child", "received-9"),
365 ("child", "child", "parent", "sent-2"),
366 ] {
367 let wire = json!({"sessionId": transcript, "update": {
368 "sessionUpdate": "session_message", "messageId": id,
369 "senderSessionId": sender, "recipientSessionId": recipient, "content": content
370 }});
371 let decoded: SessionNotification = serde_json::from_value(wire.clone()).unwrap();
372 assert_eq!(serde_json::to_value(&decoded).unwrap(), wire);
373 assert!(matches!(decoded.update, SessionUpdate::SessionMessage(_)));
374 let message = SessionMessage::new(id)
375 .sender_session_id(SessionId::new(sender))
376 .recipient_session_id(SessionId::new(recipient))
377 .content(vec![]);
378 assert_eq!(serde_json::to_value(message).unwrap()["content"], json!([]));
379 }
380 }
381
382 #[test]
383 fn message_upserts_validate_ids_and_patch_fields() {
384 let base = json!({"sessionUpdate": "session_message", "messageId": "m1",
385 "senderSessionId": "parent", "recipientSessionId": "child", "content": []});
386 {
387 let key = "messageId";
388 let mut missing = base.clone();
389 missing.as_object_mut().unwrap().remove(key);
390 assert!(
391 serde_json::from_value::<SessionUpdate>(missing).is_err(),
392 "{key}"
393 );
394 let mut null = base.clone();
395 null[key] = Value::Null;
396 assert!(
397 serde_json::from_value::<SessionUpdate>(null).is_err(),
398 "{key}"
399 );
400 let mut non_string = base.clone();
401 non_string[key] = json!(42);
402 assert!(
403 serde_json::from_value::<SessionUpdate>(non_string).is_err(),
404 "{key}"
405 );
406 }
407 for content in [None, Some(json!({}))] {
408 let mut wire = base.clone();
409 wire.as_object_mut().unwrap().remove("content");
410 if let Some(content) = content {
411 wire["content"] = content;
412 }
413 let decoded: SessionUpdate = serde_json::from_value(wire).unwrap();
414 let encoded = serde_json::to_value(decoded).unwrap();
415 let mut unchanged = base.clone();
416 unchanged.as_object_mut().unwrap().remove("content");
417 assert_eq!(encoded, unchanged);
418 }
419 let clear = SessionMessage::new("m1")
420 .sender_session_id(SessionId::new("parent"))
421 .recipient_session_id(SessionId::new("child"))
422 .content(None)
423 .meta(None);
424 let mut clear_wire = base.clone();
425 clear_wire["content"] = Value::Null;
426 clear_wire["_meta"] = Value::Null;
427 assert_eq!(
428 serde_json::to_value(&clear).unwrap()["content"],
429 Value::Null
430 );
431 let clear_update: SessionUpdate = serde_json::from_value(clear_wire.clone()).unwrap();
432 assert_eq!(serde_json::to_value(clear_update).unwrap(), clear_wire);
433 assert!(clear.content.is_null());
434 assert!(clear.meta.is_null());
435 for meta in [None, Some(Value::Null), Some(json!({"tag": "value"}))] {
436 let mut wire = base.clone();
437 if let Some(meta) = meta {
438 wire["_meta"] = meta;
439 }
440 let decoded: SessionUpdate = serde_json::from_value(wire.clone()).unwrap();
441 let encoded = serde_json::to_value(decoded).unwrap();
442 assert_eq!(encoded, wire);
443 }
444 let metadata_only = json!({"sessionUpdate": "session_message",
445 "messageId": "m1", "senderSessionId": "parent",
446 "recipientSessionId": "child", "_meta": {"tag": "value"}});
447 let decoded: SessionUpdate = serde_json::from_value(metadata_only.clone()).unwrap();
448 assert_eq!(serde_json::to_value(decoded).unwrap(), metadata_only);
449 let unset = SessionMessage::new("m1");
450 assert_eq!(
451 serde_json::to_value(&unset).unwrap(),
452 json!({"messageId": "m1"})
453 );
454 assert!(unset.content.is_undefined());
455 assert!(unset.meta.is_undefined());
456 let reset = SessionMessage::new("m1").content(vec![]);
457 assert_eq!(serde_json::to_value(reset).unwrap()["content"], json!([]));
458 let malformed: SessionMessage = serde_json::from_value(json!({
459 "messageId": "m1", "senderSessionId": "parent", "recipientSessionId": "child",
460 "content": false, "_meta": false
461 }))
462 .unwrap();
463 assert!(malformed.content.is_undefined());
464 assert!(malformed.meta.is_undefined());
465 }
466
467 #[test]
468 fn first_chunk_and_upsert_share_transcript_local_identity() {
469 for (transcript, id) in [("parent", "sent-1"), ("child", "received-9")] {
470 let wire = json!({"sessionId": transcript, "update": {
471 "sessionUpdate": "session_message_chunk", "messageId": id,
472 "senderSessionId": "parent", "recipientSessionId": "child",
473 "content": {"type": "text", "text": "first"}
474 }});
475 let decoded: SessionNotification = serde_json::from_value(wire.clone()).unwrap();
476 assert_eq!(serde_json::to_value(decoded).unwrap(), wire);
477 let upsert = SessionMessage::new(id)
478 .sender_session_id(SessionId::new("parent"))
479 .recipient_session_id(SessionId::new("child"));
480 let chunk = SessionMessageChunk::new(
481 id,
482 ContentBlock::Text(crate::v1::TextContent::new("first")),
483 )
484 .sender_session_id(SessionId::new("parent"))
485 .recipient_session_id(SessionId::new("child"));
486 assert_eq!(chunk.message_id, upsert.message_id);
487 assert_eq!(chunk.sender_session_id, upsert.sender_session_id);
488 assert_eq!(chunk.recipient_session_id, upsert.recipient_session_id);
489 assert_eq!(
490 serde_json::to_value(SessionUpdate::SessionMessageChunk(chunk)).unwrap(),
491 wire["update"]
492 );
493 }
494 let wire = json!({"sessionId": "child", "update": {
495 "sessionUpdate": "session_message_chunk", "messageId": "received-9",
496 "senderSessionId": "parent", "recipientSessionId": "child",
497 "content": {"type": "text", "text": "first"}
498 }});
499 let base = wire["update"].clone();
500 for key in ["messageId", "content"] {
501 let mut missing = base.clone();
502 missing.as_object_mut().unwrap().remove(key);
503 assert!(
504 serde_json::from_value::<SessionUpdate>(missing).is_err(),
505 "{key}"
506 );
507 let mut null = base.clone();
508 null[key] = Value::Null;
509 assert!(
510 serde_json::from_value::<SessionUpdate>(null).is_err(),
511 "{key}"
512 );
513 let mut non_string = base.clone();
514 non_string[key] = json!(42);
515 assert!(
516 serde_json::from_value::<SessionUpdate>(non_string).is_err(),
517 "{key}"
518 );
519 }
520 for meta in [None, Some(Value::Null), Some(json!({"chunk": true}))] {
521 let mut value = base.clone();
522 if let Some(meta) = meta {
523 value["_meta"] = meta;
524 }
525 let decoded: SessionUpdate = serde_json::from_value(value.clone()).unwrap();
526 let encoded = serde_json::to_value(decoded).unwrap();
527 if value["_meta"].is_null() {
528 assert_eq!(encoded, base);
529 } else {
530 assert_eq!(encoded, value);
531 }
532 }
533 }
534
535 #[cfg(feature = "schemars")]
536 #[test]
537 fn schema_requires_ids_and_chunk_content() {
538 let schema = serde_json::to_value(schemars::schema_for!(SessionMessage)).unwrap();
539 let required = schema["required"].as_array().unwrap();
540 assert_eq!(required, &vec![json!("messageId")]);
541 let chunk = serde_json::to_value(schemars::schema_for!(SessionMessageChunk)).unwrap();
542 let required = chunk["required"].as_array().unwrap();
543 assert_eq!(required.len(), 2);
544 assert!(required.contains(&json!("messageId")));
545 assert!(required.contains(&json!("content")));
546 }
547
548 #[test]
549 fn endpoints_can_arrive_late_or_be_omitted_from_later_events() {
550 let block = ContentBlock::Text(crate::v1::TextContent::new("hello"));
551 let minimal = SessionMessage::new("m1");
552 let first = SessionMessageChunk::new("m1", block.clone());
553 assert_eq!(
554 serde_json::to_value(&minimal).unwrap(),
555 json!({"messageId": "m1"})
556 );
557 assert_eq!(
558 serde_json::to_value(&first).unwrap(),
559 json!({"messageId": "m1", "content": {"type": "text", "text": "hello"}})
560 );
561 let enriched = SessionMessage::new("m1")
562 .sender_session_id(SessionId::new("parent"))
563 .recipient_session_id(SessionId::new("child"));
564 assert_eq!(enriched.sender_session_id, Some(SessionId::new("parent")));
565 assert_eq!(enriched.recipient_session_id, Some(SessionId::new("child")));
566 let later =
567 SessionMessageChunk::new("m1", block).sender_session_id(SessionId::new("parent"));
568 assert_eq!(later.sender_session_id, Some(SessionId::new("parent")));
569 assert_eq!(later.recipient_session_id, None);
570 for update in [
571 SessionUpdate::SessionMessage(minimal),
572 SessionUpdate::SessionMessageChunk(first),
573 SessionUpdate::SessionMessage(enriched),
574 SessionUpdate::SessionMessageChunk(later),
575 ] {
576 let wire = serde_json::to_value(&update).unwrap();
577 let decoded: SessionUpdate = serde_json::from_value(wire.clone()).unwrap();
578 assert_eq!(serde_json::to_value(decoded).unwrap(), wire);
579 }
580 }
581
582 #[test]
583 fn invalid_or_null_endpoints_are_absent_but_message_id_is_required() {
584 for (kind, content) in [
585 ("session_message", None),
586 (
587 "session_message_chunk",
588 Some(json!({"type": "text", "text": "hello"})),
589 ),
590 ] {
591 let mut base = json!({"sessionUpdate": kind, "messageId": "m1",
592 "senderSessionId": null, "recipientSessionId": 42});
593 if let Some(content) = content {
594 base["content"] = content;
595 }
596 let decoded: SessionUpdate = serde_json::from_value(base.clone()).unwrap();
597 let encoded = serde_json::to_value(decoded).unwrap();
598 base.as_object_mut().unwrap().remove("senderSessionId");
599 base.as_object_mut().unwrap().remove("recipientSessionId");
600 assert_eq!(encoded, base);
601 for bad in [Value::Null, json!(42)] {
602 let mut invalid = base.clone();
603 invalid["messageId"] = bad;
604 assert!(serde_json::from_value::<SessionUpdate>(invalid).is_err());
605 }
606 }
607 }
608}
609
610#[cfg(all(test, not(feature = "unstable_subagents")))]
611mod disabled_session_message_tests {
612 use super::*;
613 use serde_json::json;
614
615 #[test]
616 fn message_updates_require_subagents_gate() {
617 for (kind, content) in [
618 ("session_message", json!([])),
619 (
620 "session_message_chunk",
621 json!({"type": "text", "text": "hello"}),
622 ),
623 ] {
624 let wire = json!({"sessionUpdate": kind, "messageId": "m1", "content": content});
625 assert!(serde_json::from_value::<SessionUpdate>(wire).is_err());
626 }
627 }
628}
629
630#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
632#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
633#[serde(rename_all = "snake_case")]
634#[non_exhaustive]
635pub enum NoticeSeverity {
636 Info,
638 Warning,
640 Error,
642 #[serde(untagged)]
647 Other(String),
648}
649
650#[serde_as]
660#[skip_serializing_none]
661#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
662#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
663#[serde(rename_all = "camelCase")]
664#[non_exhaustive]
665pub struct Notice {
666 pub severity: NoticeSeverity,
668 #[cfg_attr(feature = "schemars", schemars(length(min = 1)))]
670 pub title: String,
671 #[serde_as(deserialize_as = "DefaultOnError")]
675 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
676 #[serde(default)]
677 pub description: Option<String>,
678 #[serde_as(deserialize_as = "DefaultOnError")]
682 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
683 #[serde(default, rename = "_meta")]
684 pub meta: Option<Meta>,
685}
686
687impl Notice {
688 #[must_use]
690 pub fn new(severity: NoticeSeverity, title: impl Into<String>) -> Self {
691 Self {
692 severity,
693 title: title.into(),
694 description: None,
695 meta: None,
696 }
697 }
698
699 #[must_use]
701 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
702 self.description = description.into_option();
703 self
704 }
705
706 #[must_use]
708 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
709 self.meta = meta.into_option();
710 self
711 }
712}
713
714#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
716#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
717#[serde(transparent)]
718#[from(Arc<str>, String, &'static str)]
719#[non_exhaustive]
720pub struct CompactionId(pub Arc<str>);
721
722impl CompactionId {
723 #[must_use]
725 pub fn new(id: impl Into<Arc<str>>) -> Self {
726 Self(id.into())
727 }
728}
729
730#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
732#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
733#[serde(rename_all = "snake_case")]
734#[non_exhaustive]
735pub enum CompactionStatus {
736 #[default]
738 InProgress,
739 Completed,
741 Failed,
743 Cancelled,
745 #[serde(untagged)]
750 Other(String),
751}
752
753#[serde_as]
762#[skip_serializing_none]
763#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
764#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
765#[serde(rename_all = "camelCase")]
766#[non_exhaustive]
767pub struct CompactionUpdate {
768 pub compaction_id: CompactionId,
770 pub status: CompactionStatus,
772 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<VecSkipError<_>>>")]
774 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
775 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
776 pub summary: MaybeUndefined<Vec<ContentBlock>>,
777 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
779 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
780 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
781 pub error: MaybeUndefined<String>,
782 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
784 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
785 #[serde(
786 rename = "_meta",
787 default,
788 skip_serializing_if = "MaybeUndefined::is_undefined"
789 )]
790 pub meta: MaybeUndefined<Meta>,
791}
792
793impl CompactionUpdate {
794 #[must_use]
796 pub fn new(compaction_id: impl Into<CompactionId>, status: CompactionStatus) -> Self {
797 Self {
798 compaction_id: compaction_id.into(),
799 status,
800 summary: MaybeUndefined::Undefined,
801 error: MaybeUndefined::Undefined,
802 meta: MaybeUndefined::Undefined,
803 }
804 }
805
806 #[must_use]
808 pub fn summary(mut self, summary: impl IntoMaybeUndefined<Vec<ContentBlock>>) -> Self {
809 self.summary = summary.into_maybe_undefined();
810 self
811 }
812
813 #[must_use]
815 pub fn error(mut self, error: impl IntoMaybeUndefined<String>) -> Self {
816 self.error = error.into_maybe_undefined();
817 self
818 }
819
820 #[must_use]
822 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
823 self.meta = meta.into_maybe_undefined();
824 self
825 }
826}
827
828#[serde_as]
833#[skip_serializing_none]
834#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
835#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
836#[serde(rename_all = "camelCase")]
837#[non_exhaustive]
838pub struct CompactionSummaryChunk {
839 pub compaction_id: CompactionId,
841 pub content: ContentBlock,
843 #[serde_as(deserialize_as = "DefaultOnError")]
845 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
846 #[serde(default, rename = "_meta")]
847 pub meta: Option<Meta>,
848}
849
850impl CompactionSummaryChunk {
851 #[must_use]
853 pub fn new(compaction_id: impl Into<CompactionId>, content: ContentBlock) -> Self {
854 Self {
855 compaction_id: compaction_id.into(),
856 content,
857 meta: None,
858 }
859 }
860
861 #[must_use]
863 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
864 self.meta = meta.into_option();
865 self
866 }
867}
868
869#[cfg(feature = "unstable_subagents")]
893#[serde_as]
894#[skip_serializing_none]
895#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
896#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
897#[serde(rename_all = "camelCase")]
898#[non_exhaustive]
899pub struct SubagentUpdate {
900 pub session_id: SessionId,
902 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
907 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
908 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
909 pub title: MaybeUndefined<String>,
910 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
916 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
917 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
918 pub description: MaybeUndefined<String>,
919 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
926 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
927 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
928 pub capabilities: MaybeUndefined<SubagentSessionCapabilities>,
929 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
936 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
937 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
938 pub state: MaybeUndefined<StateUpdate>,
939 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
946 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
947 #[serde(
948 default,
949 rename = "_meta",
950 skip_serializing_if = "MaybeUndefined::is_undefined"
951 )]
952 pub meta: MaybeUndefined<Meta>,
953}
954
955#[cfg(feature = "unstable_subagents")]
956impl SubagentUpdate {
957 #[must_use]
959 pub fn new(session_id: impl Into<SessionId>) -> Self {
960 Self {
961 session_id: session_id.into(),
962 title: MaybeUndefined::Undefined,
963 description: MaybeUndefined::Undefined,
964 capabilities: MaybeUndefined::Undefined,
965 state: MaybeUndefined::Undefined,
966 meta: MaybeUndefined::Undefined,
967 }
968 }
969
970 #[must_use]
972 pub fn title(mut self, title: impl IntoMaybeUndefined<String>) -> Self {
973 self.title = title.into_maybe_undefined();
974 self
975 }
976
977 #[must_use]
979 pub fn description(mut self, description: impl IntoMaybeUndefined<String>) -> Self {
980 self.description = description.into_maybe_undefined();
981 self
982 }
983
984 #[must_use]
986 pub fn capabilities(
987 mut self,
988 capabilities: impl IntoMaybeUndefined<SubagentSessionCapabilities>,
989 ) -> Self {
990 self.capabilities = capabilities.into_maybe_undefined();
991 self
992 }
993
994 #[must_use]
996 pub fn state(mut self, state: impl IntoMaybeUndefined<StateUpdate>) -> Self {
997 self.state = state.into_maybe_undefined();
998 self
999 }
1000
1001 #[must_use]
1005 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
1006 self.meta = meta.into_maybe_undefined();
1007 self
1008 }
1009}
1010
1011#[cfg(feature = "unstable_subagents")]
1020#[serde_as]
1021#[skip_serializing_none]
1022#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1023#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1024#[serde(rename_all = "camelCase")]
1025#[non_exhaustive]
1026pub struct SubagentSessionCapabilities {
1027 #[serde_as(deserialize_as = "DefaultOnError")]
1031 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1032 #[serde(default)]
1033 pub cancel: Option<SessionCancelCapabilities>,
1034 #[serde_as(deserialize_as = "DefaultOnError")]
1038 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1039 #[serde(default)]
1040 #[serde(rename = "_meta")]
1041 pub meta: Option<Meta>,
1042}
1043
1044#[cfg(feature = "unstable_subagents")]
1045impl SubagentSessionCapabilities {
1046 #[must_use]
1048 pub fn new() -> Self {
1049 Self::default()
1050 }
1051
1052 #[must_use]
1054 pub fn cancel(mut self, cancel: impl IntoOption<SessionCancelCapabilities>) -> Self {
1055 self.cancel = cancel.into_option();
1056 self
1057 }
1058
1059 #[must_use]
1063 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1064 self.meta = meta.into_option();
1065 self
1066 }
1067}
1068
1069#[cfg(feature = "unstable_subagents")]
1077#[serde_as]
1078#[skip_serializing_none]
1079#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1080#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1081#[non_exhaustive]
1082pub struct SessionCancelCapabilities {
1083 #[serde_as(deserialize_as = "DefaultOnError")]
1089 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1090 #[serde(default)]
1091 #[serde(rename = "_meta")]
1092 pub meta: Option<Meta>,
1093}
1094
1095#[cfg(feature = "unstable_subagents")]
1096impl SessionCancelCapabilities {
1097 #[must_use]
1099 pub fn new() -> Self {
1100 Self::default()
1101 }
1102
1103 #[must_use]
1109 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1110 self.meta = meta.into_option();
1111 self
1112 }
1113}
1114
1115#[cfg(feature = "unstable_subagents")]
1125#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1126#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1127#[serde(tag = "state", rename_all = "snake_case")]
1128#[non_exhaustive]
1129pub enum StateUpdate {
1130 Running(RunningStateUpdate),
1132 Idle(IdleStateUpdate),
1134 RequiresAction(RequiresActionStateUpdate),
1136 Unknown(UnknownStateUpdate),
1140 #[serde(untagged)]
1145 Other(OtherStateUpdate),
1146}
1147
1148#[cfg(feature = "unstable_subagents")]
1150#[serde_as]
1151#[skip_serializing_none]
1152#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1153#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1154#[serde(rename_all = "camelCase")]
1155#[non_exhaustive]
1156pub struct RunningStateUpdate {
1157 #[serde_as(deserialize_as = "DefaultOnError")]
1161 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1162 #[serde(default, rename = "_meta")]
1163 pub meta: Option<Meta>,
1164}
1165
1166#[cfg(feature = "unstable_subagents")]
1167impl RunningStateUpdate {
1168 #[must_use]
1170 pub fn new() -> Self {
1171 Self::default()
1172 }
1173
1174 #[must_use]
1176 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1177 self.meta = meta.into_option();
1178 self
1179 }
1180}
1181
1182#[cfg(feature = "unstable_subagents")]
1186#[serde_as]
1187#[skip_serializing_none]
1188#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1189#[cfg_attr(feature = "schemars", schemars(transform = idle_state_update_schema))]
1190#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
1191#[serde(rename_all = "camelCase")]
1192#[non_exhaustive]
1193pub struct IdleStateUpdate {
1194 #[serde(flatten)]
1196 pub stop_reason: Option<IdleStopReason>,
1197 #[cfg(feature = "unstable_end_turn_token_usage")]
1201 #[serde_as(deserialize_as = "DefaultOnError")]
1202 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1203 #[serde(default)]
1204 pub usage: Option<Box<Usage>>,
1205 #[serde_as(deserialize_as = "DefaultOnError")]
1209 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1210 #[serde(default, rename = "_meta")]
1211 pub meta: Option<Meta>,
1212}
1213
1214#[cfg(feature = "unstable_subagents")]
1215impl IdleStateUpdate {
1216 #[must_use]
1218 pub fn new() -> Self {
1219 Self::default()
1220 }
1221
1222 #[must_use]
1224 pub fn stop_reason(mut self, stop_reason: impl IntoOption<IdleStopReason>) -> Self {
1225 self.stop_reason = stop_reason.into_option();
1226 self
1227 }
1228
1229 #[cfg(feature = "unstable_end_turn_token_usage")]
1231 #[must_use]
1232 pub fn usage(mut self, usage: impl IntoOption<Usage>) -> Self {
1233 self.usage = usage.into_option().map(Box::new);
1234 self
1235 }
1236
1237 #[must_use]
1239 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1240 self.meta = meta.into_option();
1241 self
1242 }
1243}
1244
1245#[cfg(all(feature = "unstable_subagents", feature = "schemars"))]
1254fn idle_state_update_schema(schema: &mut Schema) {
1255 if let Some(properties) = schema
1256 .get_mut("properties")
1257 .and_then(serde_json::Value::as_object_mut)
1258 {
1259 properties.insert(
1260 "stopReason".into(),
1261 serde_json::json!({
1262 "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)",
1263 "type": ["string", "null"],
1264 "x-deserialize-default-on-error": true
1265 }),
1266 );
1267 }
1268 let Some(variants) = schema
1269 .get_mut("anyOf")
1270 .and_then(serde_json::Value::as_array_mut)
1271 else {
1272 return;
1273 };
1274 for variant in variants {
1275 if variant.as_object().is_some_and(serde_json::Map::is_empty) {
1276 *variant = serde_json::json!({
1277 "title": "none",
1278 "description": "No stop reason: `stopReason` is omitted or `null`.",
1279 "type": "object",
1280 "properties": {
1281 "stopReason": { "type": "null" }
1282 }
1283 });
1284 }
1285 }
1286}
1287
1288#[cfg(feature = "unstable_subagents")]
1301#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1302#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1303#[serde(tag = "stopReason", rename_all = "snake_case")]
1304#[non_exhaustive]
1305pub enum IdleStopReason {
1306 EndTurn,
1308 MaxTokens,
1310 MaxTurnRequests,
1313 Refusal,
1315 Cancelled,
1317 Error(ErrorStopReason),
1319 #[serde(untagged)]
1324 Other(OtherStopReason),
1325}
1326
1327#[cfg(feature = "unstable_subagents")]
1328impl From<ErrorStopReason> for IdleStopReason {
1329 fn from(value: ErrorStopReason) -> Self {
1330 Self::Error(value)
1331 }
1332}
1333
1334#[cfg(feature = "unstable_subagents")]
1340#[serde_as]
1341#[skip_serializing_none]
1342#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1343#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1344#[serde(rename_all = "camelCase")]
1345#[non_exhaustive]
1346pub struct ErrorStopReason {
1347 #[serde_as(deserialize_as = "DefaultOnError")]
1352 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1353 #[serde(default)]
1354 pub error: Option<Box<Error>>,
1355}
1356
1357#[cfg(feature = "unstable_subagents")]
1358impl ErrorStopReason {
1359 #[must_use]
1361 pub fn new() -> Self {
1362 Self::default()
1363 }
1364
1365 #[must_use]
1367 pub fn error(mut self, error: impl IntoOption<Error>) -> Self {
1368 self.error = error.into_option().map(Box::new);
1369 self
1370 }
1371}
1372
1373#[cfg(feature = "unstable_subagents")]
1379#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1380#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1381#[cfg_attr(feature = "schemars", schemars(inline))]
1382#[cfg_attr(feature = "schemars", schemars(transform = other_stop_reason_schema))]
1383#[serde(rename_all = "camelCase")]
1384#[non_exhaustive]
1385pub struct OtherStopReason {
1386 pub stop_reason: String,
1388 #[serde(flatten)]
1390 pub fields: BTreeMap<String, serde_json::Value>,
1391}
1392
1393#[cfg(feature = "unstable_subagents")]
1394impl OtherStopReason {
1395 #[must_use]
1397 pub fn new(
1398 stop_reason: impl Into<String>,
1399 mut fields: BTreeMap<String, serde_json::Value>,
1400 ) -> Self {
1401 remove_idle_state_update_fields(&mut fields);
1402 Self {
1403 stop_reason: stop_reason.into(),
1404 fields,
1405 }
1406 }
1407}
1408
1409#[cfg(feature = "unstable_subagents")]
1410impl<'de> Deserialize<'de> for OtherStopReason {
1411 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1412 where
1413 D: serde::Deserializer<'de>,
1414 {
1415 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1416 let stop_reason = fields
1417 .remove("stopReason")
1418 .ok_or_else(|| serde::de::Error::missing_field("stopReason"))?;
1419 let serde_json::Value::String(stop_reason) = stop_reason else {
1420 return Err(serde::de::Error::custom("`stopReason` must be a string"));
1421 };
1422 if KNOWN_STOP_REASONS.contains(&stop_reason.as_str()) {
1423 return Err(serde::de::Error::custom(format!(
1424 "known stop reason `{stop_reason}` did not match its schema"
1425 )));
1426 }
1427 remove_idle_state_update_fields(&mut fields);
1428 Ok(Self {
1429 stop_reason,
1430 fields,
1431 })
1432 }
1433}
1434
1435#[cfg(feature = "unstable_subagents")]
1436const KNOWN_STOP_REASONS: &[&str] = &[
1437 "end_turn",
1438 "max_tokens",
1439 "max_turn_requests",
1440 "refusal",
1441 "cancelled",
1442 "error",
1443];
1444
1445#[cfg(feature = "unstable_subagents")]
1447fn remove_idle_state_update_fields(fields: &mut BTreeMap<String, serde_json::Value>) {
1448 fields.remove("stopReason");
1449 fields.remove("usage");
1450 fields.remove("_meta");
1451}
1452
1453#[cfg(all(feature = "unstable_subagents", feature = "schemars"))]
1454fn other_stop_reason_schema(schema: &mut Schema) {
1455 let known = KNOWN_STOP_REASONS
1456 .iter()
1457 .map(|stop_reason| {
1458 serde_json::json!({
1459 "properties": { "stopReason": { "const": stop_reason, "type": "string" } },
1460 "required": ["stopReason"],
1461 "type": "object"
1462 })
1463 })
1464 .collect::<Vec<_>>();
1465 schema.insert("not".into(), serde_json::json!({ "anyOf": known }));
1466}
1467
1468#[cfg(feature = "unstable_subagents")]
1470#[serde_as]
1471#[skip_serializing_none]
1472#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1473#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1474#[serde(rename_all = "camelCase")]
1475#[non_exhaustive]
1476pub struct RequiresActionStateUpdate {
1477 #[serde_as(deserialize_as = "DefaultOnError")]
1481 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1482 #[serde(default, rename = "_meta")]
1483 pub meta: Option<Meta>,
1484}
1485
1486#[cfg(feature = "unstable_subagents")]
1487impl RequiresActionStateUpdate {
1488 #[must_use]
1490 pub fn new() -> Self {
1491 Self::default()
1492 }
1493
1494 #[must_use]
1496 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1497 self.meta = meta.into_option();
1498 self
1499 }
1500}
1501
1502#[cfg(feature = "unstable_subagents")]
1516#[serde_as]
1517#[skip_serializing_none]
1518#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1519#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1520#[serde(rename_all = "camelCase")]
1521#[non_exhaustive]
1522pub struct UnknownStateUpdate {
1523 #[serde_as(deserialize_as = "DefaultOnError")]
1527 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1528 #[serde(default, rename = "_meta")]
1529 pub meta: Option<Meta>,
1530}
1531
1532#[cfg(feature = "unstable_subagents")]
1533impl UnknownStateUpdate {
1534 #[must_use]
1536 pub fn new() -> Self {
1537 Self::default()
1538 }
1539
1540 #[must_use]
1542 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1543 self.meta = meta.into_option();
1544 self
1545 }
1546}
1547
1548#[cfg(feature = "unstable_subagents")]
1550#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1551#[derive(Debug, Clone, Serialize, PartialEq)]
1552#[cfg_attr(feature = "schemars", schemars(inline))]
1553#[cfg_attr(feature = "schemars", schemars(transform = other_state_update_schema))]
1554#[serde(rename_all = "camelCase")]
1555#[non_exhaustive]
1556pub struct OtherStateUpdate {
1557 pub state: String,
1559 #[serde(flatten)]
1561 pub fields: BTreeMap<String, serde_json::Value>,
1562}
1563
1564#[cfg(feature = "unstable_subagents")]
1565impl OtherStateUpdate {
1566 #[must_use]
1568 pub fn new(state: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
1569 fields.remove("state");
1570 Self {
1571 state: state.into(),
1572 fields,
1573 }
1574 }
1575}
1576
1577#[cfg(feature = "unstable_subagents")]
1578impl<'de> Deserialize<'de> for OtherStateUpdate {
1579 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1580 where
1581 D: serde::Deserializer<'de>,
1582 {
1583 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1584 let state = fields
1585 .remove("state")
1586 .ok_or_else(|| serde::de::Error::missing_field("state"))?;
1587 let serde_json::Value::String(state) = state else {
1588 return Err(serde::de::Error::custom("`state` must be a string"));
1589 };
1590 if is_known_state_update(&state) {
1591 return Err(serde::de::Error::custom(format!(
1592 "known state update `{state}` did not match its schema"
1593 )));
1594 }
1595 Ok(Self { state, fields })
1596 }
1597}
1598
1599#[cfg(feature = "unstable_subagents")]
1600const KNOWN_STATE_UPDATE_STATES: &[&str] = &["running", "idle", "requires_action", "unknown"];
1601
1602#[cfg(feature = "unstable_subagents")]
1603fn is_known_state_update(state: &str) -> bool {
1604 KNOWN_STATE_UPDATE_STATES.contains(&state)
1605}
1606
1607#[cfg(all(feature = "unstable_subagents", feature = "schemars"))]
1608fn other_state_update_schema(schema: &mut Schema) {
1609 let known = KNOWN_STATE_UPDATE_STATES
1610 .iter()
1611 .map(|state| {
1612 serde_json::json!({
1613 "properties": { "state": { "const": state, "type": "string" } },
1614 "required": ["state"],
1615 "type": "object"
1616 })
1617 })
1618 .collect::<Vec<_>>();
1619 schema.insert("not".into(), serde_json::json!({ "anyOf": known }));
1620}
1621
1622#[serde_as]
1626#[skip_serializing_none]
1627#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1628#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1629#[serde(rename_all = "camelCase")]
1630#[non_exhaustive]
1631pub struct CurrentModeUpdate {
1632 pub current_mode_id: SessionModeId,
1634 #[serde_as(deserialize_as = "DefaultOnError")]
1640 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1641 #[serde(default)]
1642 #[serde(rename = "_meta")]
1643 pub meta: Option<Meta>,
1644}
1645
1646impl CurrentModeUpdate {
1647 #[must_use]
1649 pub fn new(current_mode_id: impl Into<SessionModeId>) -> Self {
1650 Self {
1651 current_mode_id: current_mode_id.into(),
1652 meta: None,
1653 }
1654 }
1655
1656 #[must_use]
1662 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1663 self.meta = meta.into_option();
1664 self
1665 }
1666}
1667
1668#[serde_as]
1670#[skip_serializing_none]
1671#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1672#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1673#[serde(rename_all = "camelCase")]
1674#[non_exhaustive]
1675pub struct ConfigOptionUpdate {
1676 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
1678 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
1679 pub config_options: Vec<SessionConfigOption>,
1680 #[serde_as(deserialize_as = "DefaultOnError")]
1686 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1687 #[serde(default)]
1688 #[serde(rename = "_meta")]
1689 pub meta: Option<Meta>,
1690}
1691
1692impl ConfigOptionUpdate {
1693 #[must_use]
1695 pub fn new(config_options: Vec<SessionConfigOption>) -> Self {
1696 Self {
1697 config_options,
1698 meta: None,
1699 }
1700 }
1701
1702 #[must_use]
1708 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1709 self.meta = meta.into_option();
1710 self
1711 }
1712}
1713
1714#[serde_as]
1719#[skip_serializing_none]
1720#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1721#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1722#[serde(rename_all = "camelCase")]
1723#[non_exhaustive]
1724pub struct SessionInfoUpdate {
1725 #[serde_as(deserialize_as = "DefaultOnError")]
1727 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1728 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
1729 pub title: MaybeUndefined<String>,
1730 #[serde_as(deserialize_as = "DefaultOnError")]
1732 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1733 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
1734 pub updated_at: MaybeUndefined<String>,
1735 #[serde_as(deserialize_as = "DefaultOnError")]
1741 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1742 #[serde(default)]
1743 #[serde(rename = "_meta")]
1744 pub meta: Option<Meta>,
1745}
1746
1747impl SessionInfoUpdate {
1748 #[must_use]
1750 pub fn new() -> Self {
1751 Self::default()
1752 }
1753
1754 #[must_use]
1756 pub fn title(mut self, title: impl IntoMaybeUndefined<String>) -> Self {
1757 self.title = title.into_maybe_undefined();
1758 self
1759 }
1760
1761 #[must_use]
1763 pub fn updated_at(mut self, updated_at: impl IntoMaybeUndefined<String>) -> Self {
1764 self.updated_at = updated_at.into_maybe_undefined();
1765 self
1766 }
1767
1768 #[must_use]
1774 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1775 self.meta = meta.into_option();
1776 self
1777 }
1778}
1779
1780#[serde_as]
1782#[skip_serializing_none]
1783#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1784#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1785#[serde(rename_all = "camelCase")]
1786#[non_exhaustive]
1787pub struct UsageUpdate {
1788 pub used: u64,
1790 pub size: u64,
1792 #[serde_as(deserialize_as = "DefaultOnError")]
1794 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1795 #[serde(default)]
1796 pub cost: Option<Cost>,
1797 #[serde_as(deserialize_as = "DefaultOnError")]
1803 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1804 #[serde(default)]
1805 #[serde(rename = "_meta")]
1806 pub meta: Option<Meta>,
1807}
1808
1809impl UsageUpdate {
1810 #[must_use]
1812 pub fn new(used: u64, size: u64) -> Self {
1813 Self {
1814 used,
1815 size,
1816 cost: None,
1817 meta: None,
1818 }
1819 }
1820
1821 #[must_use]
1823 pub fn cost(mut self, cost: impl IntoOption<Cost>) -> Self {
1824 self.cost = cost.into_option();
1825 self
1826 }
1827
1828 #[must_use]
1834 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1835 self.meta = meta.into_option();
1836 self
1837 }
1838}
1839
1840#[serde_as]
1842#[skip_serializing_none]
1843#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1844#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1845#[serde(rename_all = "camelCase")]
1846#[non_exhaustive]
1847pub struct Cost {
1848 pub amount: f64,
1850 pub currency: String,
1852 #[serde_as(deserialize_as = "DefaultOnError")]
1858 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1859 #[serde(default)]
1860 #[serde(rename = "_meta")]
1861 pub meta: Option<Meta>,
1862}
1863
1864impl Cost {
1865 #[must_use]
1867 pub fn new(amount: f64, currency: impl Into<String>) -> Self {
1868 Self {
1869 amount,
1870 currency: currency.into(),
1871 meta: None,
1872 }
1873 }
1874
1875 #[must_use]
1881 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1882 self.meta = meta.into_option();
1883 self
1884 }
1885}
1886
1887#[serde_as]
1889#[skip_serializing_none]
1890#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1891#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1892#[serde(rename_all = "camelCase")]
1893#[non_exhaustive]
1894pub struct ContentChunk {
1895 pub content: ContentBlock,
1897 #[serde_as(deserialize_as = "DefaultOnError")]
1902 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1903 #[serde(default)]
1904 pub message_id: Option<MessageId>,
1905 #[serde_as(deserialize_as = "DefaultOnError")]
1911 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1912 #[serde(default)]
1913 #[serde(rename = "_meta")]
1914 pub meta: Option<Meta>,
1915}
1916
1917impl ContentChunk {
1918 #[must_use]
1920 pub fn new(content: ContentBlock) -> Self {
1921 Self {
1922 content,
1923 message_id: None,
1924 meta: None,
1925 }
1926 }
1927
1928 #[must_use]
1933 pub fn message_id(mut self, message_id: impl IntoOption<MessageId>) -> Self {
1934 self.message_id = message_id.into_option();
1935 self
1936 }
1937
1938 #[must_use]
1944 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1945 self.meta = meta.into_option();
1946 self
1947 }
1948}
1949
1950#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1952#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
1953#[serde(transparent)]
1954#[from(Arc<str>, String, &'static str)]
1955#[non_exhaustive]
1956pub struct MessageId(pub Arc<str>);
1957
1958impl MessageId {
1959 #[must_use]
1961 pub fn new(id: impl Into<Arc<str>>) -> Self {
1962 Self(id.into())
1963 }
1964}
1965
1966impl IntoOption<MessageId> for &str {
1967 fn into_option(self) -> Option<MessageId> {
1968 Some(MessageId::new(self))
1969 }
1970}
1971
1972#[serde_as]
1974#[skip_serializing_none]
1975#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1976#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1977#[serde(rename_all = "camelCase")]
1978#[non_exhaustive]
1979pub struct AvailableCommandsUpdate {
1980 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
1982 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
1983 pub available_commands: Vec<AvailableCommand>,
1984 #[serde_as(deserialize_as = "DefaultOnError")]
1990 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1991 #[serde(default)]
1992 #[serde(rename = "_meta")]
1993 pub meta: Option<Meta>,
1994}
1995
1996impl AvailableCommandsUpdate {
1997 #[must_use]
1999 pub fn new(available_commands: Vec<AvailableCommand>) -> Self {
2000 Self {
2001 available_commands,
2002 meta: None,
2003 }
2004 }
2005
2006 #[must_use]
2012 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2013 self.meta = meta.into_option();
2014 self
2015 }
2016}
2017
2018#[serde_as]
2020#[skip_serializing_none]
2021#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2022#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2023#[serde(rename_all = "camelCase")]
2024#[non_exhaustive]
2025pub struct AvailableCommand {
2026 pub name: String,
2028 pub description: String,
2030 #[serde_as(deserialize_as = "DefaultOnError")]
2032 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2033 #[serde(default)]
2034 pub input: Option<AvailableCommandInput>,
2035 #[serde_as(deserialize_as = "DefaultOnError")]
2041 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2042 #[serde(default)]
2043 #[serde(rename = "_meta")]
2044 pub meta: Option<Meta>,
2045}
2046
2047impl AvailableCommand {
2048 #[must_use]
2050 pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
2051 Self {
2052 name: name.into(),
2053 description: description.into(),
2054 input: None,
2055 meta: None,
2056 }
2057 }
2058
2059 #[must_use]
2061 pub fn input(mut self, input: impl IntoOption<AvailableCommandInput>) -> Self {
2062 self.input = input.into_option();
2063 self
2064 }
2065
2066 #[must_use]
2072 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2073 self.meta = meta.into_option();
2074 self
2075 }
2076}
2077
2078#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2080#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2081#[serde(untagged, rename_all = "camelCase")]
2082#[non_exhaustive]
2083pub enum AvailableCommandInput {
2084 Unstructured(UnstructuredCommandInput),
2086}
2087
2088#[serde_as]
2090#[skip_serializing_none]
2091#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2092#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2093#[serde(rename_all = "camelCase")]
2094#[non_exhaustive]
2095pub struct UnstructuredCommandInput {
2096 pub hint: String,
2098 #[serde_as(deserialize_as = "DefaultOnError")]
2104 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2105 #[serde(default)]
2106 #[serde(rename = "_meta")]
2107 pub meta: Option<Meta>,
2108}
2109
2110impl UnstructuredCommandInput {
2111 #[must_use]
2113 pub fn new(hint: impl Into<String>) -> Self {
2114 Self {
2115 hint: hint.into(),
2116 meta: None,
2117 }
2118 }
2119
2120 #[must_use]
2126 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2127 self.meta = meta.into_option();
2128 self
2129 }
2130}
2131
2132#[serde_as]
2140#[skip_serializing_none]
2141#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2142#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2143#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = SESSION_REQUEST_PERMISSION_METHOD_NAME)))]
2144#[serde(rename_all = "camelCase")]
2145#[non_exhaustive]
2146pub struct RequestPermissionRequest {
2147 pub session_id: SessionId,
2149 pub tool_call: ToolCallUpdate,
2151 pub options: Vec<PermissionOption>,
2153 #[serde_as(deserialize_as = "DefaultOnError")]
2159 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2160 #[serde(default)]
2161 #[serde(rename = "_meta")]
2162 pub meta: Option<Meta>,
2163}
2164
2165impl RequestPermissionRequest {
2166 #[must_use]
2168 pub fn new(
2169 session_id: impl Into<SessionId>,
2170 tool_call: ToolCallUpdate,
2171 options: Vec<PermissionOption>,
2172 ) -> Self {
2173 Self {
2174 session_id: session_id.into(),
2175 tool_call,
2176 options,
2177 meta: None,
2178 }
2179 }
2180
2181 #[must_use]
2187 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2188 self.meta = meta.into_option();
2189 self
2190 }
2191}
2192
2193#[serde_as]
2195#[skip_serializing_none]
2196#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2197#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2198#[serde(rename_all = "camelCase")]
2199#[non_exhaustive]
2200pub struct PermissionOption {
2201 pub option_id: PermissionOptionId,
2203 pub name: String,
2205 pub kind: PermissionOptionKind,
2207 #[serde_as(deserialize_as = "DefaultOnError")]
2213 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2214 #[serde(default)]
2215 #[serde(rename = "_meta")]
2216 pub meta: Option<Meta>,
2217}
2218
2219impl PermissionOption {
2220 #[must_use]
2222 pub fn new(
2223 option_id: impl Into<PermissionOptionId>,
2224 name: impl Into<String>,
2225 kind: PermissionOptionKind,
2226 ) -> Self {
2227 Self {
2228 option_id: option_id.into(),
2229 name: name.into(),
2230 kind,
2231 meta: None,
2232 }
2233 }
2234
2235 #[must_use]
2241 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2242 self.meta = meta.into_option();
2243 self
2244 }
2245}
2246
2247#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2249#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
2250#[serde(transparent)]
2251#[from(Arc<str>, String, &'static str)]
2252#[non_exhaustive]
2253pub struct PermissionOptionId(pub Arc<str>);
2254
2255impl PermissionOptionId {
2256 #[must_use]
2258 pub fn new(id: impl Into<Arc<str>>) -> Self {
2259 Self(id.into())
2260 }
2261}
2262
2263#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2267#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
2268#[serde(rename_all = "snake_case")]
2269#[non_exhaustive]
2270pub enum PermissionOptionKind {
2271 AllowOnce,
2273 AllowAlways,
2275 RejectOnce,
2277 RejectAlways,
2279}
2280
2281#[serde_as]
2283#[skip_serializing_none]
2284#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2285#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2286#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = SESSION_REQUEST_PERMISSION_METHOD_NAME)))]
2287#[serde(rename_all = "camelCase")]
2288#[non_exhaustive]
2289pub struct RequestPermissionResponse {
2290 pub outcome: RequestPermissionOutcome,
2293 #[serde_as(deserialize_as = "DefaultOnError")]
2299 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2300 #[serde(default)]
2301 #[serde(rename = "_meta")]
2302 pub meta: Option<Meta>,
2303}
2304
2305impl RequestPermissionResponse {
2306 #[must_use]
2308 pub fn new(outcome: RequestPermissionOutcome) -> Self {
2309 Self {
2310 outcome,
2311 meta: None,
2312 }
2313 }
2314
2315 #[must_use]
2321 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2322 self.meta = meta.into_option();
2323 self
2324 }
2325}
2326
2327#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2329#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2330#[serde(tag = "outcome", rename_all = "snake_case")]
2331#[cfg_attr(feature = "schemars", schemars(extend("discriminator" = {"propertyName": "outcome"})))]
2332#[non_exhaustive]
2333pub enum RequestPermissionOutcome {
2334 Cancelled,
2342 #[serde(rename_all = "camelCase")]
2344 Selected(SelectedPermissionOutcome),
2345}
2346
2347#[serde_as]
2349#[skip_serializing_none]
2350#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2351#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2352#[serde(rename_all = "camelCase")]
2353#[non_exhaustive]
2354pub struct SelectedPermissionOutcome {
2355 pub option_id: PermissionOptionId,
2357 #[serde_as(deserialize_as = "DefaultOnError")]
2363 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2364 #[serde(default)]
2365 #[serde(rename = "_meta")]
2366 pub meta: Option<Meta>,
2367}
2368
2369impl SelectedPermissionOutcome {
2370 #[must_use]
2372 pub fn new(option_id: impl Into<PermissionOptionId>) -> Self {
2373 Self {
2374 option_id: option_id.into(),
2375 meta: None,
2376 }
2377 }
2378
2379 #[must_use]
2385 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2386 self.meta = meta.into_option();
2387 self
2388 }
2389}
2390
2391#[serde_as]
2397#[skip_serializing_none]
2398#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2399#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2400#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = FS_WRITE_TEXT_FILE_METHOD_NAME)))]
2401#[serde(rename_all = "camelCase")]
2402#[non_exhaustive]
2403pub struct WriteTextFileRequest {
2404 pub session_id: SessionId,
2406 pub path: PathBuf,
2408 pub content: String,
2410 #[serde_as(deserialize_as = "DefaultOnError")]
2416 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2417 #[serde(default)]
2418 #[serde(rename = "_meta")]
2419 pub meta: Option<Meta>,
2420}
2421
2422impl WriteTextFileRequest {
2423 #[must_use]
2425 pub fn new(
2426 session_id: impl Into<SessionId>,
2427 path: impl Into<PathBuf>,
2428 content: impl Into<String>,
2429 ) -> Self {
2430 Self {
2431 session_id: session_id.into(),
2432 path: path.into(),
2433 content: content.into(),
2434 meta: None,
2435 }
2436 }
2437
2438 #[must_use]
2444 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2445 self.meta = meta.into_option();
2446 self
2447 }
2448}
2449
2450crate::serde_util::default_on_null! {
2451 #[serde_as]
2453 #[skip_serializing_none]
2454 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2455 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
2456 #[serde(rename_all = "camelCase")]
2457 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = FS_WRITE_TEXT_FILE_METHOD_NAME)))]
2458 #[non_exhaustive]
2459 pub struct WriteTextFileResponse {
2460 #[serde_as(deserialize_as = "DefaultOnError")]
2466 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2467 #[serde(default)]
2468 #[serde(rename = "_meta")]
2469 pub meta: Option<Meta>,
2470 }
2471}
2472
2473impl WriteTextFileResponse {
2474 #[must_use]
2476 pub fn new() -> Self {
2477 Self::default()
2478 }
2479
2480 #[must_use]
2486 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2487 self.meta = meta.into_option();
2488 self
2489 }
2490}
2491
2492#[serde_as]
2498#[skip_serializing_none]
2499#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2500#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2501#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = FS_READ_TEXT_FILE_METHOD_NAME)))]
2502#[serde(rename_all = "camelCase")]
2503#[non_exhaustive]
2504pub struct ReadTextFileRequest {
2505 pub session_id: SessionId,
2507 pub path: PathBuf,
2509 #[serde(default)]
2511 pub line: Option<u32>,
2512 #[serde(default)]
2514 pub limit: Option<u32>,
2515 #[serde_as(deserialize_as = "DefaultOnError")]
2521 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2522 #[serde(default)]
2523 #[serde(rename = "_meta")]
2524 pub meta: Option<Meta>,
2525}
2526
2527impl ReadTextFileRequest {
2528 #[must_use]
2530 pub fn new(session_id: impl Into<SessionId>, path: impl Into<PathBuf>) -> Self {
2531 Self {
2532 session_id: session_id.into(),
2533 path: path.into(),
2534 line: None,
2535 limit: None,
2536 meta: None,
2537 }
2538 }
2539
2540 #[must_use]
2542 pub fn line(mut self, line: impl IntoOption<u32>) -> Self {
2543 self.line = line.into_option();
2544 self
2545 }
2546
2547 #[must_use]
2549 pub fn limit(mut self, limit: impl IntoOption<u32>) -> Self {
2550 self.limit = limit.into_option();
2551 self
2552 }
2553
2554 #[must_use]
2560 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2561 self.meta = meta.into_option();
2562 self
2563 }
2564}
2565
2566#[serde_as]
2568#[skip_serializing_none]
2569#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2570#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2571#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = FS_READ_TEXT_FILE_METHOD_NAME)))]
2572#[serde(rename_all = "camelCase")]
2573#[non_exhaustive]
2574pub struct ReadTextFileResponse {
2575 pub content: String,
2577 #[serde_as(deserialize_as = "DefaultOnError")]
2583 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2584 #[serde(default)]
2585 #[serde(rename = "_meta")]
2586 pub meta: Option<Meta>,
2587}
2588
2589impl ReadTextFileResponse {
2590 #[must_use]
2592 pub fn new(content: impl Into<String>) -> Self {
2593 Self {
2594 content: content.into(),
2595 meta: None,
2596 }
2597 }
2598
2599 #[must_use]
2605 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2606 self.meta = meta.into_option();
2607 self
2608 }
2609}
2610
2611#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2615#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
2616#[serde(transparent)]
2617#[from(Arc<str>, String, &'static str)]
2618#[non_exhaustive]
2619pub struct TerminalId(pub Arc<str>);
2620
2621impl TerminalId {
2622 #[must_use]
2624 pub fn new(id: impl Into<Arc<str>>) -> Self {
2625 Self(id.into())
2626 }
2627}
2628
2629#[serde_as]
2631#[skip_serializing_none]
2632#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2633#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2634#[serde(rename_all = "camelCase")]
2635#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_CREATE_METHOD_NAME)))]
2636#[non_exhaustive]
2637pub struct CreateTerminalRequest {
2638 pub session_id: SessionId,
2640 pub command: String,
2642 #[serde_as(deserialize_as = "DefaultOnNull")]
2644 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2645 pub args: Vec<String>,
2646 #[serde_as(deserialize_as = "DefaultOnNull")]
2648 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2649 pub env: Vec<EnvVariable>,
2650 #[serde(default)]
2652 pub cwd: Option<PathBuf>,
2653 #[serde(default)]
2662 pub output_byte_limit: Option<u64>,
2663 #[serde_as(deserialize_as = "DefaultOnError")]
2669 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2670 #[serde(default)]
2671 #[serde(rename = "_meta")]
2672 pub meta: Option<Meta>,
2673}
2674
2675impl CreateTerminalRequest {
2676 #[must_use]
2678 pub fn new(session_id: impl Into<SessionId>, command: impl Into<String>) -> Self {
2679 Self {
2680 session_id: session_id.into(),
2681 command: command.into(),
2682 args: Vec::new(),
2683 env: Vec::new(),
2684 cwd: None,
2685 output_byte_limit: None,
2686 meta: None,
2687 }
2688 }
2689
2690 #[must_use]
2692 pub fn args(mut self, args: Vec<String>) -> Self {
2693 self.args = args;
2694 self
2695 }
2696
2697 #[must_use]
2699 pub fn env(mut self, env: Vec<EnvVariable>) -> Self {
2700 self.env = env;
2701 self
2702 }
2703
2704 #[must_use]
2706 pub fn cwd(mut self, cwd: impl IntoOption<PathBuf>) -> Self {
2707 self.cwd = cwd.into_option();
2708 self
2709 }
2710
2711 #[must_use]
2720 pub fn output_byte_limit(mut self, output_byte_limit: impl IntoOption<u64>) -> Self {
2721 self.output_byte_limit = output_byte_limit.into_option();
2722 self
2723 }
2724
2725 #[must_use]
2731 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2732 self.meta = meta.into_option();
2733 self
2734 }
2735}
2736
2737#[serde_as]
2739#[skip_serializing_none]
2740#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2741#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2742#[serde(rename_all = "camelCase")]
2743#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_CREATE_METHOD_NAME)))]
2744#[non_exhaustive]
2745pub struct CreateTerminalResponse {
2746 pub terminal_id: TerminalId,
2748 #[serde_as(deserialize_as = "DefaultOnError")]
2754 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2755 #[serde(default)]
2756 #[serde(rename = "_meta")]
2757 pub meta: Option<Meta>,
2758}
2759
2760impl CreateTerminalResponse {
2761 #[must_use]
2763 pub fn new(terminal_id: impl Into<TerminalId>) -> Self {
2764 Self {
2765 terminal_id: terminal_id.into(),
2766 meta: None,
2767 }
2768 }
2769
2770 #[must_use]
2776 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2777 self.meta = meta.into_option();
2778 self
2779 }
2780}
2781
2782#[serde_as]
2784#[skip_serializing_none]
2785#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2786#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2787#[serde(rename_all = "camelCase")]
2788#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_OUTPUT_METHOD_NAME)))]
2789#[non_exhaustive]
2790pub struct TerminalOutputRequest {
2791 pub session_id: SessionId,
2793 pub terminal_id: TerminalId,
2795 #[serde_as(deserialize_as = "DefaultOnError")]
2801 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2802 #[serde(default)]
2803 #[serde(rename = "_meta")]
2804 pub meta: Option<Meta>,
2805}
2806
2807impl TerminalOutputRequest {
2808 #[must_use]
2810 pub fn new(session_id: impl Into<SessionId>, terminal_id: impl Into<TerminalId>) -> Self {
2811 Self {
2812 session_id: session_id.into(),
2813 terminal_id: terminal_id.into(),
2814 meta: None,
2815 }
2816 }
2817
2818 #[must_use]
2824 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2825 self.meta = meta.into_option();
2826 self
2827 }
2828}
2829
2830#[serde_as]
2832#[skip_serializing_none]
2833#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2834#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2835#[serde(rename_all = "camelCase")]
2836#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_OUTPUT_METHOD_NAME)))]
2837#[non_exhaustive]
2838pub struct TerminalOutputResponse {
2839 pub output: String,
2841 pub truncated: bool,
2843 #[serde_as(deserialize_as = "DefaultOnError")]
2845 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2846 #[serde(default)]
2847 pub exit_status: Option<TerminalExitStatus>,
2848 #[serde_as(deserialize_as = "DefaultOnError")]
2854 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2855 #[serde(default)]
2856 #[serde(rename = "_meta")]
2857 pub meta: Option<Meta>,
2858}
2859
2860impl TerminalOutputResponse {
2861 #[must_use]
2863 pub fn new(output: impl Into<String>, truncated: bool) -> Self {
2864 Self {
2865 output: output.into(),
2866 truncated,
2867 exit_status: None,
2868 meta: None,
2869 }
2870 }
2871
2872 #[must_use]
2874 pub fn exit_status(mut self, exit_status: impl IntoOption<TerminalExitStatus>) -> Self {
2875 self.exit_status = exit_status.into_option();
2876 self
2877 }
2878
2879 #[must_use]
2885 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2886 self.meta = meta.into_option();
2887 self
2888 }
2889}
2890
2891#[serde_as]
2893#[skip_serializing_none]
2894#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2895#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2896#[serde(rename_all = "camelCase")]
2897#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_RELEASE_METHOD_NAME)))]
2898#[non_exhaustive]
2899pub struct ReleaseTerminalRequest {
2900 pub session_id: SessionId,
2902 pub terminal_id: TerminalId,
2904 #[serde_as(deserialize_as = "DefaultOnError")]
2910 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2911 #[serde(default)]
2912 #[serde(rename = "_meta")]
2913 pub meta: Option<Meta>,
2914}
2915
2916impl ReleaseTerminalRequest {
2917 #[must_use]
2919 pub fn new(session_id: impl Into<SessionId>, terminal_id: impl Into<TerminalId>) -> Self {
2920 Self {
2921 session_id: session_id.into(),
2922 terminal_id: terminal_id.into(),
2923 meta: None,
2924 }
2925 }
2926
2927 #[must_use]
2933 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2934 self.meta = meta.into_option();
2935 self
2936 }
2937}
2938
2939crate::serde_util::default_on_null! {
2940 #[serde_as]
2942 #[skip_serializing_none]
2943 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2944 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
2945 #[serde(rename_all = "camelCase")]
2946 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_RELEASE_METHOD_NAME)))]
2947 #[non_exhaustive]
2948 pub struct ReleaseTerminalResponse {
2949 #[serde_as(deserialize_as = "DefaultOnError")]
2955 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2956 #[serde(default)]
2957 #[serde(rename = "_meta")]
2958 pub meta: Option<Meta>,
2959 }
2960}
2961
2962impl ReleaseTerminalResponse {
2963 #[must_use]
2965 pub fn new() -> Self {
2966 Self::default()
2967 }
2968
2969 #[must_use]
2975 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2976 self.meta = meta.into_option();
2977 self
2978 }
2979}
2980
2981#[serde_as]
2983#[skip_serializing_none]
2984#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2985#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2986#[serde(rename_all = "camelCase")]
2987#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_KILL_METHOD_NAME)))]
2988#[non_exhaustive]
2989pub struct KillTerminalRequest {
2990 pub session_id: SessionId,
2992 pub terminal_id: TerminalId,
2994 #[serde_as(deserialize_as = "DefaultOnError")]
3000 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3001 #[serde(default)]
3002 #[serde(rename = "_meta")]
3003 pub meta: Option<Meta>,
3004}
3005
3006impl KillTerminalRequest {
3007 #[must_use]
3009 pub fn new(session_id: impl Into<SessionId>, terminal_id: impl Into<TerminalId>) -> Self {
3010 Self {
3011 session_id: session_id.into(),
3012 terminal_id: terminal_id.into(),
3013 meta: None,
3014 }
3015 }
3016
3017 #[must_use]
3023 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3024 self.meta = meta.into_option();
3025 self
3026 }
3027}
3028
3029crate::serde_util::default_on_null! {
3030 #[serde_as]
3032 #[skip_serializing_none]
3033 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3034 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
3035 #[serde(rename_all = "camelCase")]
3036 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_KILL_METHOD_NAME)))]
3037 #[non_exhaustive]
3038 pub struct KillTerminalResponse {
3039 #[serde_as(deserialize_as = "DefaultOnError")]
3045 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3046 #[serde(default)]
3047 #[serde(rename = "_meta")]
3048 pub meta: Option<Meta>,
3049 }
3050}
3051
3052impl KillTerminalResponse {
3053 #[must_use]
3055 pub fn new() -> Self {
3056 Self::default()
3057 }
3058
3059 #[must_use]
3065 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3066 self.meta = meta.into_option();
3067 self
3068 }
3069}
3070
3071#[serde_as]
3073#[skip_serializing_none]
3074#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3075#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3076#[serde(rename_all = "camelCase")]
3077#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_WAIT_FOR_EXIT_METHOD_NAME)))]
3078#[non_exhaustive]
3079pub struct WaitForTerminalExitRequest {
3080 pub session_id: SessionId,
3082 pub terminal_id: TerminalId,
3084 #[serde_as(deserialize_as = "DefaultOnError")]
3090 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3091 #[serde(default)]
3092 #[serde(rename = "_meta")]
3093 pub meta: Option<Meta>,
3094}
3095
3096impl WaitForTerminalExitRequest {
3097 #[must_use]
3099 pub fn new(session_id: impl Into<SessionId>, terminal_id: impl Into<TerminalId>) -> Self {
3100 Self {
3101 session_id: session_id.into(),
3102 terminal_id: terminal_id.into(),
3103 meta: None,
3104 }
3105 }
3106
3107 #[must_use]
3113 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3114 self.meta = meta.into_option();
3115 self
3116 }
3117}
3118
3119crate::serde_util::default_on_null! {
3120 #[serde_as]
3122 #[skip_serializing_none]
3123 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3124 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
3125 #[serde(rename_all = "camelCase")]
3126 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_WAIT_FOR_EXIT_METHOD_NAME)))]
3127 #[non_exhaustive]
3128 pub struct WaitForTerminalExitResponse {
3129 #[serde(flatten)]
3131 pub exit_status: TerminalExitStatus,
3132 #[serde_as(deserialize_as = "DefaultOnError")]
3138 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3139 #[serde(default)]
3140 #[serde(rename = "_meta")]
3141 pub meta: Option<Meta>,
3142 }
3143}
3144
3145impl WaitForTerminalExitResponse {
3146 #[must_use]
3148 pub fn new(exit_status: TerminalExitStatus) -> Self {
3149 Self {
3150 exit_status,
3151 meta: None,
3152 }
3153 }
3154
3155 #[must_use]
3161 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3162 self.meta = meta.into_option();
3163 self
3164 }
3165}
3166
3167#[serde_as]
3169#[skip_serializing_none]
3170#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3171#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3172#[serde(rename_all = "camelCase")]
3173#[non_exhaustive]
3174pub struct TerminalExitStatus {
3175 #[serde_as(deserialize_as = "DefaultOnError")]
3177 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3178 #[serde(default)]
3179 pub exit_code: Option<u32>,
3180 #[serde_as(deserialize_as = "DefaultOnError")]
3182 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3183 #[serde(default)]
3184 pub signal: Option<String>,
3185 #[serde_as(deserialize_as = "DefaultOnError")]
3191 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3192 #[serde(default)]
3193 #[serde(rename = "_meta")]
3194 pub meta: Option<Meta>,
3195}
3196
3197impl TerminalExitStatus {
3198 #[must_use]
3200 pub fn new() -> Self {
3201 Self::default()
3202 }
3203
3204 #[must_use]
3206 pub fn exit_code(mut self, exit_code: impl IntoOption<u32>) -> Self {
3207 self.exit_code = exit_code.into_option();
3208 self
3209 }
3210
3211 #[must_use]
3213 pub fn signal(mut self, signal: impl IntoOption<String>) -> Self {
3214 self.signal = signal.into_option();
3215 self
3216 }
3217
3218 #[must_use]
3224 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3225 self.meta = meta.into_option();
3226 self
3227 }
3228}
3229
3230#[serde_as]
3239#[skip_serializing_none]
3240#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3241#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3242#[serde(rename_all = "camelCase")]
3243#[non_exhaustive]
3244pub struct ClientCapabilities {
3245 #[serde_as(deserialize_as = "DefaultOnError")]
3248 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3249 #[serde(default)]
3250 pub fs: FileSystemCapabilities,
3251 #[serde_as(deserialize_as = "DefaultOnError")]
3253 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3254 #[serde(default)]
3255 pub terminal: bool,
3256 #[serde_as(deserialize_as = "DefaultOnError")]
3261 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3262 #[serde(default)]
3263 pub session: Option<ClientSessionCapabilities>,
3264 #[cfg(feature = "unstable_subagents")]
3275 #[serde_as(deserialize_as = "DefaultOnError")]
3276 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3277 #[serde(default)]
3278 pub subagents: Option<SubagentCapabilities>,
3279 #[cfg(feature = "unstable_plan_operations")]
3288 #[serde_as(deserialize_as = "DefaultOnError")]
3289 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3290 #[serde(default)]
3291 pub plan: Option<PlanCapabilities>,
3292 #[serde_as(deserialize_as = "DefaultOnError")]
3296 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3297 #[serde(default)]
3298 pub auth: AuthCapabilities,
3299 #[serde_as(deserialize_as = "DefaultOnError")]
3305 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3306 #[serde(default)]
3307 pub elicitation: Option<ElicitationCapabilities>,
3308 #[cfg(feature = "unstable_nes")]
3317 #[serde_as(deserialize_as = "DefaultOnError")]
3318 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3319 #[serde(default)]
3320 pub nes: Option<ClientNesCapabilities>,
3321 #[cfg(feature = "unstable_nes")]
3327 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
3328 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
3329 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3330 pub position_encodings: Vec<PositionEncodingKind>,
3331
3332 #[serde_as(deserialize_as = "DefaultOnError")]
3338 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3339 #[serde(default)]
3340 #[serde(rename = "_meta")]
3341 pub meta: Option<Meta>,
3342}
3343
3344impl ClientCapabilities {
3345 #[must_use]
3347 pub fn new() -> Self {
3348 Self::default()
3349 }
3350
3351 #[must_use]
3354 pub fn fs(mut self, fs: FileSystemCapabilities) -> Self {
3355 self.fs = fs;
3356 self
3357 }
3358
3359 #[must_use]
3361 pub fn terminal(mut self, terminal: bool) -> Self {
3362 self.terminal = terminal;
3363 self
3364 }
3365
3366 #[must_use]
3368 pub fn session(mut self, session: impl IntoOption<ClientSessionCapabilities>) -> Self {
3369 self.session = session.into_option();
3370 self
3371 }
3372
3373 #[cfg(feature = "unstable_subagents")]
3379 #[must_use]
3380 pub fn subagents(mut self, subagents: impl IntoOption<SubagentCapabilities>) -> Self {
3381 self.subagents = subagents.into_option();
3382 self
3383 }
3384
3385 #[cfg(feature = "unstable_plan_operations")]
3394 #[must_use]
3395 pub fn plan(mut self, plan: impl IntoOption<PlanCapabilities>) -> Self {
3396 self.plan = plan.into_option();
3397 self
3398 }
3399
3400 #[must_use]
3404 pub fn auth(mut self, auth: AuthCapabilities) -> Self {
3405 self.auth = auth;
3406 self
3407 }
3408
3409 #[must_use]
3412 pub fn elicitation(mut self, elicitation: impl IntoOption<ElicitationCapabilities>) -> Self {
3413 self.elicitation = elicitation.into_option();
3414 self
3415 }
3416
3417 #[cfg(feature = "unstable_nes")]
3421 #[must_use]
3422 pub fn nes(mut self, nes: impl IntoOption<ClientNesCapabilities>) -> Self {
3423 self.nes = nes.into_option();
3424 self
3425 }
3426
3427 #[cfg(feature = "unstable_nes")]
3431 #[must_use]
3432 pub fn position_encodings(mut self, position_encodings: Vec<PositionEncodingKind>) -> Self {
3433 self.position_encodings = position_encodings;
3434 self
3435 }
3436
3437 #[must_use]
3443 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3444 self.meta = meta.into_option();
3445 self
3446 }
3447}
3448
3449#[cfg(feature = "unstable_subagents")]
3460#[serde_as]
3461#[skip_serializing_none]
3462#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3463#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3464#[non_exhaustive]
3465pub struct SubagentCapabilities {
3466 #[serde_as(deserialize_as = "DefaultOnError")]
3470 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3471 #[serde(default)]
3472 #[serde(rename = "_meta")]
3473 pub meta: Option<Meta>,
3474}
3475
3476#[cfg(feature = "unstable_subagents")]
3477impl SubagentCapabilities {
3478 #[must_use]
3480 pub fn new() -> Self {
3481 Self::default()
3482 }
3483
3484 #[must_use]
3488 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3489 self.meta = meta.into_option();
3490 self
3491 }
3492}
3493
3494#[serde_as]
3496#[skip_serializing_none]
3497#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3498#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3499#[serde(rename_all = "camelCase")]
3500#[non_exhaustive]
3501pub struct ClientSessionCapabilities {
3502 #[serde_as(deserialize_as = "DefaultOnError")]
3505 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3506 #[serde(default)]
3507 pub compaction: Option<CompactionCapabilities>,
3508 #[serde_as(deserialize_as = "DefaultOnError")]
3513 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3514 #[serde(default)]
3515 pub config_options: Option<SessionConfigOptionsCapabilities>,
3516 #[serde_as(deserialize_as = "DefaultOnError")]
3521 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3522 #[serde(default)]
3523 pub notices: Option<NoticeCapabilities>,
3524 #[serde_as(deserialize_as = "DefaultOnError")]
3530 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3531 #[serde(default)]
3532 #[serde(rename = "_meta")]
3533 pub meta: Option<Meta>,
3534}
3535
3536impl ClientSessionCapabilities {
3537 #[must_use]
3539 pub fn new() -> Self {
3540 Self::default()
3541 }
3542
3543 #[must_use]
3545 pub fn compaction(mut self, compaction: impl IntoOption<CompactionCapabilities>) -> Self {
3546 self.compaction = compaction.into_option();
3547 self
3548 }
3549
3550 #[must_use]
3555 pub fn config_options(
3556 mut self,
3557 config_options: impl IntoOption<SessionConfigOptionsCapabilities>,
3558 ) -> Self {
3559 self.config_options = config_options.into_option();
3560 self
3561 }
3562
3563 #[must_use]
3565 pub fn notices(mut self, notices: impl IntoOption<NoticeCapabilities>) -> Self {
3566 self.notices = notices.into_option();
3567 self
3568 }
3569
3570 #[must_use]
3576 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3577 self.meta = meta.into_option();
3578 self
3579 }
3580}
3581
3582#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3584#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3585#[serde(rename_all = "camelCase")]
3586#[non_exhaustive]
3587pub struct CompactionCapabilities {}
3588
3589impl CompactionCapabilities {
3590 #[must_use]
3592 pub fn new() -> Self {
3593 Self {}
3594 }
3595}
3596
3597#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3599#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3600#[serde(rename_all = "camelCase")]
3601#[non_exhaustive]
3602pub struct NoticeCapabilities {}
3603
3604impl NoticeCapabilities {
3605 #[must_use]
3607 pub fn new() -> Self {
3608 Self {}
3609 }
3610}
3611
3612#[serde_as]
3614#[skip_serializing_none]
3615#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3616#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3617#[serde(rename_all = "camelCase")]
3618#[non_exhaustive]
3619pub struct SessionConfigOptionsCapabilities {
3620 #[serde_as(deserialize_as = "DefaultOnError")]
3627 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3628 #[serde(default)]
3629 pub boolean: Option<BooleanConfigOptionCapabilities>,
3630 #[serde_as(deserialize_as = "DefaultOnError")]
3636 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3637 #[serde(default)]
3638 #[serde(rename = "_meta")]
3639 pub meta: Option<Meta>,
3640}
3641
3642impl SessionConfigOptionsCapabilities {
3643 #[must_use]
3645 pub fn new() -> Self {
3646 Self::default()
3647 }
3648
3649 #[must_use]
3656 pub fn boolean(mut self, boolean: impl IntoOption<BooleanConfigOptionCapabilities>) -> Self {
3657 self.boolean = boolean.into_option();
3658 self
3659 }
3660
3661 #[must_use]
3667 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3668 self.meta = meta.into_option();
3669 self
3670 }
3671}
3672
3673#[serde_as]
3677#[skip_serializing_none]
3678#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3679#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3680#[non_exhaustive]
3681pub struct BooleanConfigOptionCapabilities {
3682 #[serde_as(deserialize_as = "DefaultOnError")]
3688 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3689 #[serde(default)]
3690 #[serde(rename = "_meta")]
3691 pub meta: Option<Meta>,
3692}
3693
3694impl BooleanConfigOptionCapabilities {
3695 #[must_use]
3697 pub fn new() -> Self {
3698 Self::default()
3699 }
3700
3701 #[must_use]
3707 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3708 self.meta = meta.into_option();
3709 self
3710 }
3711}
3712
3713#[serde_as]
3719#[skip_serializing_none]
3720#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3721#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3722#[serde(rename_all = "camelCase")]
3723#[non_exhaustive]
3724pub struct AuthCapabilities {
3725 #[serde_as(deserialize_as = "DefaultOnError")]
3731 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3732 #[serde(default)]
3733 pub terminal: bool,
3734 #[serde_as(deserialize_as = "DefaultOnError")]
3740 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3741 #[serde(default)]
3742 #[serde(rename = "_meta")]
3743 pub meta: Option<Meta>,
3744}
3745
3746impl AuthCapabilities {
3747 #[must_use]
3749 pub fn new() -> Self {
3750 Self::default()
3751 }
3752
3753 #[must_use]
3760 pub fn terminal(mut self, terminal: bool) -> Self {
3761 self.terminal = terminal;
3762 self
3763 }
3764
3765 #[must_use]
3771 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3772 self.meta = meta.into_option();
3773 self
3774 }
3775}
3776
3777#[serde_as]
3781#[skip_serializing_none]
3782#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3783#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3784#[serde(rename_all = "camelCase")]
3785#[non_exhaustive]
3786pub struct FileSystemCapabilities {
3787 #[serde_as(deserialize_as = "DefaultOnError")]
3789 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3790 #[serde(default)]
3791 pub read_text_file: bool,
3792 #[serde_as(deserialize_as = "DefaultOnError")]
3794 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3795 #[serde(default)]
3796 pub write_text_file: bool,
3797 #[serde_as(deserialize_as = "DefaultOnError")]
3803 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3804 #[serde(default)]
3805 #[serde(rename = "_meta")]
3806 pub meta: Option<Meta>,
3807}
3808
3809impl FileSystemCapabilities {
3810 #[must_use]
3812 pub fn new() -> Self {
3813 Self::default()
3814 }
3815
3816 #[must_use]
3818 pub fn read_text_file(mut self, read_text_file: bool) -> Self {
3819 self.read_text_file = read_text_file;
3820 self
3821 }
3822
3823 #[must_use]
3825 pub fn write_text_file(mut self, write_text_file: bool) -> Self {
3826 self.write_text_file = write_text_file;
3827 self
3828 }
3829
3830 #[must_use]
3836 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3837 self.meta = meta.into_option();
3838 self
3839 }
3840}
3841
3842#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3848#[non_exhaustive]
3849pub struct ClientMethodNames {
3850 pub session_request_permission: &'static str,
3852 pub session_update: &'static str,
3854 pub fs_write_text_file: &'static str,
3856 pub fs_read_text_file: &'static str,
3858 pub terminal_create: &'static str,
3860 pub terminal_output: &'static str,
3862 pub terminal_release: &'static str,
3864 pub terminal_wait_for_exit: &'static str,
3866 pub terminal_kill: &'static str,
3868 #[cfg(feature = "unstable_mcp_over_acp")]
3870 pub mcp_message: &'static str,
3871 pub elicitation_create: &'static str,
3873 pub elicitation_complete: &'static str,
3875}
3876
3877pub const CLIENT_METHOD_NAMES: ClientMethodNames = ClientMethodNames {
3879 session_update: SESSION_UPDATE_NOTIFICATION,
3880 session_request_permission: SESSION_REQUEST_PERMISSION_METHOD_NAME,
3881 fs_write_text_file: FS_WRITE_TEXT_FILE_METHOD_NAME,
3882 fs_read_text_file: FS_READ_TEXT_FILE_METHOD_NAME,
3883 terminal_create: TERMINAL_CREATE_METHOD_NAME,
3884 terminal_output: TERMINAL_OUTPUT_METHOD_NAME,
3885 terminal_release: TERMINAL_RELEASE_METHOD_NAME,
3886 terminal_wait_for_exit: TERMINAL_WAIT_FOR_EXIT_METHOD_NAME,
3887 terminal_kill: TERMINAL_KILL_METHOD_NAME,
3888 #[cfg(feature = "unstable_mcp_over_acp")]
3889 mcp_message: MCP_MESSAGE_METHOD_NAME,
3890 elicitation_create: ELICITATION_CREATE_METHOD_NAME,
3891 elicitation_complete: ELICITATION_COMPLETE_NOTIFICATION,
3892};
3893
3894pub(crate) const SESSION_UPDATE_NOTIFICATION: &str = "session/update";
3896pub(crate) const SESSION_REQUEST_PERMISSION_METHOD_NAME: &str = "session/request_permission";
3898pub(crate) const FS_WRITE_TEXT_FILE_METHOD_NAME: &str = "fs/write_text_file";
3900pub(crate) const FS_READ_TEXT_FILE_METHOD_NAME: &str = "fs/read_text_file";
3902pub(crate) const TERMINAL_CREATE_METHOD_NAME: &str = "terminal/create";
3904pub(crate) const TERMINAL_OUTPUT_METHOD_NAME: &str = "terminal/output";
3906pub(crate) const TERMINAL_RELEASE_METHOD_NAME: &str = "terminal/release";
3908pub(crate) const TERMINAL_WAIT_FOR_EXIT_METHOD_NAME: &str = "terminal/wait_for_exit";
3910pub(crate) const TERMINAL_KILL_METHOD_NAME: &str = "terminal/kill";
3912pub(crate) const ELICITATION_CREATE_METHOD_NAME: &str = "elicitation/create";
3914pub(crate) const ELICITATION_COMPLETE_NOTIFICATION: &str = "elicitation/complete";
3916
3917#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3924#[derive(Clone, Debug, Serialize, Deserialize)]
3925#[serde(untagged)]
3926#[cfg_attr(feature = "schemars", schemars(inline))]
3927#[non_exhaustive]
3928#[allow(clippy::large_enum_variant)]
3929pub enum AgentRequest {
3930 WriteTextFileRequest(WriteTextFileRequest),
3937 ReadTextFileRequest(ReadTextFileRequest),
3944 RequestPermissionRequest(RequestPermissionRequest),
3955 CreateTerminalRequest(CreateTerminalRequest),
3970 TerminalOutputRequest(TerminalOutputRequest),
3977 ReleaseTerminalRequest(ReleaseTerminalRequest),
3990 WaitForTerminalExitRequest(WaitForTerminalExitRequest),
3994 KillTerminalRequest(KillTerminalRequest),
4007 CreateElicitationRequest(CreateElicitationRequest),
4011 #[cfg(feature = "unstable_mcp_over_acp")]
4017 MessageMcpRequest(MessageMcpRequest),
4018 ExtMethodRequest(ExtRequest),
4026}
4027
4028impl AgentRequest {
4029 #[must_use]
4031 pub fn method(&self) -> &str {
4032 match self {
4033 Self::WriteTextFileRequest(_) => CLIENT_METHOD_NAMES.fs_write_text_file,
4034 Self::ReadTextFileRequest(_) => CLIENT_METHOD_NAMES.fs_read_text_file,
4035 Self::RequestPermissionRequest(_) => CLIENT_METHOD_NAMES.session_request_permission,
4036 Self::CreateTerminalRequest(_) => CLIENT_METHOD_NAMES.terminal_create,
4037 Self::TerminalOutputRequest(_) => CLIENT_METHOD_NAMES.terminal_output,
4038 Self::ReleaseTerminalRequest(_) => CLIENT_METHOD_NAMES.terminal_release,
4039 Self::WaitForTerminalExitRequest(_) => CLIENT_METHOD_NAMES.terminal_wait_for_exit,
4040 Self::KillTerminalRequest(_) => CLIENT_METHOD_NAMES.terminal_kill,
4041 Self::CreateElicitationRequest(_) => CLIENT_METHOD_NAMES.elicitation_create,
4042 #[cfg(feature = "unstable_mcp_over_acp")]
4043 Self::MessageMcpRequest(_) => CLIENT_METHOD_NAMES.mcp_message,
4044 Self::ExtMethodRequest(ext_request) => &ext_request.method,
4045 }
4046 }
4047}
4048
4049#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4056#[derive(Clone, Debug, Serialize, Deserialize)]
4057#[serde(untagged)]
4058#[cfg_attr(feature = "schemars", schemars(inline))]
4059#[non_exhaustive]
4060pub enum ClientResponse {
4061 WriteTextFileResponse(#[serde(default)] WriteTextFileResponse),
4063 ReadTextFileResponse(ReadTextFileResponse),
4065 RequestPermissionResponse(RequestPermissionResponse),
4067 CreateTerminalResponse(CreateTerminalResponse),
4069 TerminalOutputResponse(TerminalOutputResponse),
4071 ReleaseTerminalResponse(#[serde(default)] ReleaseTerminalResponse),
4073 WaitForTerminalExitResponse(WaitForTerminalExitResponse),
4075 KillTerminalResponse(#[serde(default)] KillTerminalResponse),
4077 CreateElicitationResponse(CreateElicitationResponse),
4079 #[cfg(feature = "unstable_mcp_over_acp")]
4081 MessageMcpResponse(MessageMcpResponse),
4082 ExtMethodResponse(ExtResponse),
4084}
4085
4086#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4093#[derive(Clone, Debug, Serialize, Deserialize)]
4094#[serde(untagged)]
4095#[expect(clippy::large_enum_variant)]
4096#[cfg_attr(feature = "schemars", schemars(inline))]
4097#[non_exhaustive]
4098pub enum AgentNotification {
4099 SessionNotification(SessionNotification),
4111 CompleteElicitationNotification(CompleteElicitationNotification),
4115 ExtNotification(ExtNotification),
4123}
4124
4125impl AgentNotification {
4126 #[must_use]
4128 pub fn method(&self) -> &str {
4129 match self {
4130 Self::SessionNotification(_) => CLIENT_METHOD_NAMES.session_update,
4131 Self::CompleteElicitationNotification(_) => CLIENT_METHOD_NAMES.elicitation_complete,
4132 Self::ExtNotification(ext_notification) => &ext_notification.method,
4133 }
4134 }
4135}
4136
4137#[cfg(test)]
4138mod tests {
4139 use super::*;
4140
4141 #[test]
4142 fn notice_preserves_wire_shape_nullable_fields_and_open_severity() {
4143 use serde_json::json;
4144
4145 let mut meta = Meta::new();
4146 meta.insert("source".into(), json!("fallback"));
4147 assert_eq!(
4148 serde_json::to_value(SessionUpdate::Notice(
4149 Notice::new(NoticeSeverity::Warning, "MCP server unavailable")
4150 .description("Continuing without it.")
4151 .meta(meta),
4152 ))
4153 .unwrap(),
4154 json!({
4155 "sessionUpdate": "notice",
4156 "severity": "warning",
4157 "title": "MCP server unavailable",
4158 "description": "Continuing without it.",
4159 "_meta": { "source": "fallback" }
4160 })
4161 );
4162
4163 assert_eq!(
4164 serde_json::to_value(SessionUpdate::Notice(Notice::new(
4165 NoticeSeverity::Info,
4166 "Indexing workspace",
4167 )))
4168 .unwrap(),
4169 json!({
4170 "sessionUpdate": "notice",
4171 "severity": "info",
4172 "title": "Indexing workspace"
4173 })
4174 );
4175
4176 let SessionUpdate::Notice(notice) = serde_json::from_value(json!({
4177 "sessionUpdate": "notice",
4178 "severity": "critical",
4179 "title": "Provider degraded",
4180 "description": null,
4181 "_meta": null
4182 }))
4183 .unwrap() else {
4184 panic!("expected notice");
4185 };
4186
4187 assert_eq!(
4188 notice.severity,
4189 NoticeSeverity::Other("critical".to_string())
4190 );
4191 assert_eq!(notice.description, None);
4192 assert_eq!(notice.meta, None);
4193 assert_eq!(
4194 serde_json::to_value(SessionUpdate::Notice(notice)).unwrap(),
4195 json!({
4196 "sessionUpdate": "notice",
4197 "severity": "critical",
4198 "title": "Provider degraded"
4199 })
4200 );
4201 }
4202
4203 #[test]
4204 fn notice_requires_string_severity_and_title() {
4205 use serde_json::json;
4206
4207 for malformed in [
4208 json!({
4209 "sessionUpdate": "notice",
4210 "severity": "warning",
4211 "title": 42
4212 }),
4213 json!({
4214 "sessionUpdate": "notice",
4215 "severity": 42,
4216 "title": "MCP server unavailable"
4217 }),
4218 json!({
4219 "sessionUpdate": "notice",
4220 "severity": "warning"
4221 }),
4222 json!({
4223 "sessionUpdate": "notice",
4224 "severity": "warning",
4225 "title": null
4226 }),
4227 json!({
4228 "sessionUpdate": "notice",
4229 "title": "MCP server unavailable"
4230 }),
4231 json!({
4232 "sessionUpdate": "notice",
4233 "severity": null,
4234 "title": "MCP server unavailable"
4235 }),
4236 ] {
4237 assert!(serde_json::from_str::<SessionUpdate>(&malformed.to_string()).is_err());
4238 assert!(serde_json::from_value::<SessionUpdate>(malformed).is_err());
4239 }
4240 }
4241
4242 #[test]
4243 fn notice_tolerates_invalid_optional_display_fields_and_custom_severity() {
4244 use serde_json::json;
4245
4246 let wire = json!({
4247 "sessionUpdate": "notice",
4248 "severity": "_custom",
4249 "title": "Provider degraded",
4250 "description": 42,
4251 "_meta": false
4252 });
4253 let SessionUpdate::Notice(notice) = serde_json::from_value(wire.clone()).unwrap() else {
4254 panic!("expected notice");
4255 };
4256 assert_eq!(
4258 serde_json::from_str::<SessionUpdate>(&wire.to_string()).unwrap(),
4259 SessionUpdate::Notice(notice.clone())
4260 );
4261 assert_eq!(notice.severity, NoticeSeverity::Other("_custom".into()));
4262 assert_eq!(notice.description, None);
4263 assert_eq!(notice.meta, None);
4264 assert_eq!(
4265 serde_json::to_value(notice).unwrap(),
4266 json!({"severity": "_custom", "title": "Provider degraded"})
4267 );
4268 }
4269
4270 #[cfg(feature = "schemars")]
4271 #[test]
4272 fn notice_schema_preserves_required_and_nullable_fields() {
4273 use serde_json::json;
4274
4275 let schema = serde_json::to_value(schemars::schema_for!(Notice)).unwrap();
4276 assert_eq!(schema["required"], json!(["severity", "title"]));
4277 assert_eq!(schema["properties"]["title"]["minLength"], 1);
4278 for field in ["description", "_meta"] {
4279 assert!(
4280 schema["properties"][field]["type"]
4281 .as_array()
4282 .unwrap()
4283 .contains(&json!("null"))
4284 );
4285 }
4286 let capabilities =
4287 serde_json::to_value(schemars::schema_for!(ClientSessionCapabilities)).unwrap();
4288 assert!(
4289 capabilities["properties"]["notices"]["anyOf"]
4290 .as_array()
4291 .unwrap()
4292 .contains(&json!({"type": "null"}))
4293 );
4294 assert!(
4295 !capabilities["required"]
4296 .as_array()
4297 .is_some_and(|required| required.contains(&json!("notices")))
4298 );
4299 }
4300
4301 #[test]
4302 fn notice_capability_advertises_support_only_when_present() {
4303 use serde_json::json;
4304
4305 let capabilities = ClientCapabilities::new()
4306 .session(ClientSessionCapabilities::new().notices(NoticeCapabilities::new()));
4307 let value = serde_json::to_value(&capabilities).unwrap();
4308 assert_eq!(value["session"], json!({ "notices": {} }));
4309 assert_eq!(
4310 serde_json::from_value::<ClientCapabilities>(value).unwrap(),
4311 capabilities
4312 );
4313
4314 for unsupported in [
4315 json!({}),
4316 json!({ "session": null }),
4317 json!({ "session": {} }),
4318 json!({ "session": { "notices": null } }),
4319 json!({ "session": { "notices": false } }),
4320 json!({ "session": { "notices": true } }),
4321 json!({ "session": { "notices": "supported" } }),
4322 ] {
4323 let capabilities: ClientCapabilities = serde_json::from_value(unsupported).unwrap();
4324 assert!(
4325 capabilities
4326 .session
4327 .and_then(|session| session.notices)
4328 .is_none()
4329 );
4330 }
4331
4332 assert_eq!(
4333 serde_json::to_value(
4334 ClientSessionCapabilities::new()
4335 .notices(NoticeCapabilities::new())
4336 .notices(None)
4337 )
4338 .unwrap(),
4339 json!({})
4340 );
4341 }
4342
4343 #[test]
4344 fn compaction_updates_preserve_patch_and_open_status_semantics() {
4345 use serde_json::json;
4346
4347 assert_eq!(
4348 serde_json::to_value(SessionUpdate::CompactionUpdate(CompactionUpdate::new(
4349 "cmp_001",
4350 CompactionStatus::InProgress,
4351 )))
4352 .unwrap(),
4353 json!({
4354 "sessionUpdate": "compaction_update",
4355 "compactionId": "cmp_001",
4356 "status": "in_progress"
4357 })
4358 );
4359
4360 let SessionUpdate::CompactionUpdate(update) = serde_json::from_value(json!({
4361 "sessionUpdate": "compaction_update",
4362 "compactionId": "cmp_001",
4363 "status": "paused",
4364 "summary": null,
4365 "error": null
4366 }))
4367 .unwrap() else {
4368 panic!("expected compaction update");
4369 };
4370 assert_eq!(update.status, CompactionStatus::Other("paused".into()));
4371 assert!(update.summary.is_null());
4372 assert!(update.error.is_null());
4373 assert!(update.meta.is_undefined());
4374 }
4375
4376 #[test]
4377 fn compaction_chunk_and_v1_capability_serialize() {
4378 use serde_json::json;
4379
4380 assert_eq!(
4381 serde_json::to_value(SessionUpdate::CompactionSummaryChunk(
4382 CompactionSummaryChunk::new(
4383 "cmp_001",
4384 ContentBlock::Text(crate::v1::TextContent::new("retained")),
4385 ),
4386 ))
4387 .unwrap(),
4388 json!({
4389 "sessionUpdate": "compaction_summary_chunk",
4390 "compactionId": "cmp_001",
4391 "content": { "type": "text", "text": "retained" }
4392 })
4393 );
4394 assert_eq!(
4395 serde_json::to_value(
4396 ClientSessionCapabilities::new().compaction(CompactionCapabilities::new())
4397 )
4398 .unwrap(),
4399 json!({ "compaction": {} })
4400 );
4401 let absent: ClientSessionCapabilities = serde_json::from_value(json!({})).unwrap();
4402 let null: ClientSessionCapabilities =
4403 serde_json::from_value(json!({ "compaction": null })).unwrap();
4404 assert!(absent.compaction.is_none());
4405 assert!(null.compaction.is_none());
4406 }
4407
4408 #[cfg(feature = "unstable_subagents")]
4409 #[test]
4410 fn test_subagent_updates_serialization() {
4411 use serde_json::json;
4412
4413 let announced = SessionUpdate::SubagentUpdate(SubagentUpdate::new("sess_child_1"));
4414 assert_eq!(
4415 serde_json::to_value(&announced).unwrap(),
4416 json!({
4417 "sessionUpdate": "subagent_update",
4418 "sessionId": "sess_child_1"
4419 })
4420 );
4421 assert_eq!(
4422 serde_json::from_value::<SessionUpdate>(serde_json::to_value(&announced).unwrap())
4423 .unwrap(),
4424 announced
4425 );
4426 let capable = SubagentUpdate::new("sess_child_1").capabilities(
4427 SubagentSessionCapabilities::new().cancel(SessionCancelCapabilities::new()),
4428 );
4429 assert_eq!(
4430 serde_json::to_value(capable).unwrap(),
4431 json!({
4432 "sessionId": "sess_child_1",
4433 "capabilities": {"cancel": {}}
4434 })
4435 );
4436 let minimal: SubagentUpdate =
4437 serde_json::from_value(json!({ "sessionId": "sess_child_3" })).unwrap();
4438 assert!(minimal.capabilities.is_undefined());
4439 assert!(minimal.state.is_undefined());
4440
4441 let nulls: SubagentUpdate = serde_json::from_value(json!({
4442 "sessionId": "sess_child_3",
4443 "capabilities": null,
4444 "state": null
4445 }))
4446 .unwrap();
4447 assert!(nulls.capabilities.is_null());
4448 assert!(nulls.state.is_null());
4449 assert_eq!(
4450 serde_json::to_value(nulls).unwrap(),
4451 json!({"sessionId": "sess_child_3", "capabilities": null, "state": null})
4452 );
4453 }
4454
4455 #[cfg(feature = "unstable_subagents")]
4456 #[test]
4457 fn test_subagent_display_metadata() {
4458 use serde_json::json;
4459
4460 let update = SubagentUpdate::new("child")
4461 .title("Test investigator".to_string())
4462 .description("Investigates platform-specific test failures.".to_string());
4463 let wire = json!({
4464 "sessionId": "child",
4465 "title": "Test investigator",
4466 "description": "Investigates platform-specific test failures."
4467 });
4468 assert_eq!(serde_json::to_value(&update).unwrap(), wire);
4469 assert_eq!(
4470 serde_json::from_value::<SubagentUpdate>(wire).unwrap(),
4471 update
4472 );
4473
4474 let minimal = SubagentUpdate::new("child");
4475 assert_eq!(
4476 serde_json::to_value(&minimal).unwrap(),
4477 json!({"sessionId": "child"})
4478 );
4479 let cleared: SubagentUpdate = serde_json::from_value(json!({
4480 "sessionId": "child", "title": null, "description": null
4481 }))
4482 .unwrap();
4483 assert!(cleared.title.is_null());
4484 assert!(cleared.description.is_null());
4485 assert_eq!(
4486 serde_json::to_value(cleared).unwrap(),
4487 json!({"sessionId": "child", "title": null, "description": null})
4488 );
4489 let invalid: SubagentUpdate = serde_json::from_value(json!({
4490 "sessionId": "child", "title": false, "description": false
4491 }))
4492 .unwrap();
4493 assert_eq!(invalid, minimal);
4494
4495 let title_only: SubagentUpdate = serde_json::from_value(json!({
4496 "sessionId": "child", "title": "Updated title"
4497 }))
4498 .unwrap();
4499 assert_eq!(
4500 title_only.title.value().map(String::as_str),
4501 Some("Updated title")
4502 );
4503 assert!(title_only.description.is_undefined());
4504 }
4505
4506 #[cfg(feature = "unstable_subagents")]
4507 #[test]
4508 fn subagent_patch_fields_preserve_omitted_null_and_concrete() {
4509 use serde_json::json;
4510
4511 let omitted = SubagentUpdate::new("child");
4512 for field in ["title", "description", "capabilities", "state", "_meta"] {
4513 let wire = json!({"sessionId": "child", field: null});
4514 let decoded: SubagentUpdate = serde_json::from_value(wire.clone()).unwrap();
4515 assert_eq!(serde_json::to_value(decoded).unwrap(), wire, "{field}");
4516 let malformed = json!({"sessionId": "child", field: false});
4517 let decoded: SubagentUpdate = serde_json::from_value(malformed).unwrap();
4518 assert_eq!(decoded, omitted, "{field}");
4519 }
4520 let concrete = json!({
4521 "sessionId": "child", "title": "Investigator",
4522 "description": "Tests", "capabilities": {"cancel": {}},
4523 "state": {"state": "running"}, "_meta": {"source": "parent"}
4524 });
4525 let decoded: SubagentUpdate = serde_json::from_value(concrete.clone()).unwrap();
4526 assert!(decoded.title.value().is_some());
4527 assert!(decoded.description.value().is_some());
4528 assert!(decoded.capabilities.value().is_some());
4529 assert!(decoded.state.value().is_some());
4530 assert!(decoded.meta.value().is_some());
4531 assert_eq!(serde_json::to_value(decoded).unwrap(), concrete);
4532 let cleared = SubagentUpdate::new("child")
4533 .title(None)
4534 .description(None)
4535 .capabilities(None)
4536 .state(None)
4537 .meta(None);
4538 assert_eq!(
4539 serde_json::to_value(cleared).unwrap(),
4540 json!({
4541 "sessionId": "child", "title": null, "description": null,
4542 "capabilities": null, "state": null, "_meta": null
4543 })
4544 );
4545 }
4546
4547 #[cfg(feature = "unstable_subagents")]
4548 #[test]
4549 fn test_subagent_notification_keeps_parent_and_child_ids_nested() {
4550 use serde_json::json;
4551
4552 let notification = SessionNotification::new(
4553 "parent",
4554 SessionUpdate::SubagentUpdate(SubagentUpdate::new("child")),
4555 );
4556 let wire = json!({
4557 "sessionId": "parent",
4558 "update": {
4559 "sessionUpdate": "subagent_update",
4560 "sessionId": "child"
4561 }
4562 });
4563 assert_eq!(serde_json::to_value(¬ification).unwrap(), wire);
4564 assert_eq!(
4565 serde_json::from_value::<SessionNotification>(wire).unwrap(),
4566 notification
4567 );
4568 for malformed in [
4570 json!({"sessionId": "parent", "update": {"sessionUpdate": "subagent_update"}}),
4571 json!({"update": {"sessionUpdate": "subagent_update", "sessionId": "child"}}),
4572 ] {
4573 assert!(serde_json::from_value::<SessionNotification>(malformed).is_err());
4574 }
4575 }
4576
4577 #[cfg(feature = "unstable_subagents")]
4578 #[test]
4579 fn test_subagent_states_round_trip() {
4580 use serde_json::json;
4581
4582 for (wire_state, expected) in [
4583 (
4584 json!({"state": "running"}),
4585 StateUpdate::Running(RunningStateUpdate::new()),
4586 ),
4587 (
4588 json!({"state": "idle", "stopReason": "end_turn"}),
4589 StateUpdate::Idle(IdleStateUpdate::new().stop_reason(IdleStopReason::EndTurn)),
4590 ),
4591 (
4592 json!({"state": "requires_action"}),
4593 StateUpdate::RequiresAction(RequiresActionStateUpdate::new()),
4594 ),
4595 (
4596 json!({"state": "unknown"}),
4597 StateUpdate::Unknown(UnknownStateUpdate::new()),
4598 ),
4599 (
4600 json!({"state": "_waiting", "detail": {"ticket": 7}}),
4601 StateUpdate::Other(OtherStateUpdate::new(
4602 "_waiting",
4603 [("detail".into(), json!({"ticket": 7}))].into(),
4604 )),
4605 ),
4606 (
4607 json!({"state": "paused", "retryAt": 42}),
4608 StateUpdate::Other(OtherStateUpdate::new(
4609 "paused",
4610 [("retryAt".into(), json!(42))].into(),
4611 )),
4612 ),
4613 ] {
4614 let wire = json!({
4615 "sessionUpdate": "subagent_update",
4616 "sessionId": "sess_child",
4617 "state": wire_state
4618 });
4619 let parsed: SessionUpdate = serde_json::from_value(wire.clone()).unwrap();
4620 let SessionUpdate::SubagentUpdate(update) = &parsed else {
4621 panic!("expected subagent update");
4622 };
4623 assert_eq!(update.state.value(), Some(&expected));
4624 assert_eq!(serde_json::to_value(parsed).unwrap(), wire);
4625 }
4626
4627 for reason in [
4628 "end_turn",
4629 "max_tokens",
4630 "max_turn_requests",
4631 "refusal",
4632 "cancelled",
4633 ] {
4634 let state: StateUpdate = serde_json::from_value(json!({
4635 "state": "idle", "stopReason": reason
4636 }))
4637 .unwrap();
4638 assert_eq!(serde_json::to_value(state).unwrap()["stopReason"], reason);
4639 }
4640
4641 for state in [
4644 StateUpdate::Running(RunningStateUpdate::new()),
4645 StateUpdate::RequiresAction(RequiresActionStateUpdate::new()),
4646 StateUpdate::Unknown(UnknownStateUpdate::new()),
4647 StateUpdate::Running(RunningStateUpdate::new()),
4648 StateUpdate::Idle(IdleStateUpdate::new().stop_reason(IdleStopReason::EndTurn)),
4649 StateUpdate::Unknown(UnknownStateUpdate::new()),
4650 StateUpdate::Running(RunningStateUpdate::new()),
4651 ] {
4652 let update = SubagentUpdate::new("sess_child").state(state.clone());
4653 let wire = serde_json::to_value(&update).unwrap();
4654 assert_eq!(wire["sessionId"], "sess_child");
4655 assert_eq!(
4656 serde_json::from_value::<SubagentUpdate>(wire)
4657 .unwrap()
4658 .state,
4659 MaybeUndefined::Value(state)
4660 );
4661 }
4662 assert_eq!(
4663 serde_json::to_value(
4664 SubagentUpdate::new("sess_child").state(StateUpdate::Idle(IdleStateUpdate::new()))
4665 )
4666 .unwrap()["state"],
4667 json!({"state": "idle"})
4668 );
4669 }
4670
4671 #[cfg(feature = "unstable_subagents")]
4672 #[test]
4673 fn subagent_idle_snapshot_reports_child_failure() {
4674 use serde_json::json;
4675
4676 let update = SubagentUpdate::new("sess_child").state(StateUpdate::Idle(
4677 IdleStateUpdate::new().stop_reason(IdleStopReason::from(
4678 ErrorStopReason::new().error(Error::auth_required()),
4679 )),
4680 ));
4681 let wire = json!({
4682 "sessionId": "sess_child",
4683 "state": {
4684 "state": "idle",
4685 "stopReason": "error",
4686 "error": { "code": -32000, "message": "Authentication required" }
4687 }
4688 });
4689 assert_eq!(serde_json::to_value(&update).unwrap(), wire);
4690 assert_eq!(
4691 serde_json::from_value::<SubagentUpdate>(wire).unwrap(),
4692 update
4693 );
4694
4695 let without_details = StateUpdate::Idle(
4697 IdleStateUpdate::new().stop_reason(IdleStopReason::from(ErrorStopReason::new())),
4698 );
4699 for error in [None, Some(json!(null)), Some(json!("failed"))] {
4700 let mut wire = json!({ "state": "idle", "stopReason": "error" });
4701 if let Some(error) = error {
4702 wire["error"] = error;
4703 }
4704 assert_eq!(
4705 serde_json::from_value::<StateUpdate>(wire).unwrap(),
4706 without_details
4707 );
4708 }
4709
4710 let parsed: StateUpdate = serde_json::from_value(json!({
4713 "state": "idle",
4714 "stopReason": "end_turn",
4715 "error": { "code": -32603, "message": "Internal error" }
4716 }))
4717 .unwrap();
4718 assert_eq!(
4719 parsed,
4720 StateUpdate::Idle(IdleStateUpdate::new().stop_reason(IdleStopReason::EndTurn))
4721 );
4722
4723 let meta: Meta = [("source".into(), json!("worker"))].into_iter().collect();
4726 for stop_reason in [None, Some(json!(null)), Some(json!(true))] {
4727 let mut wire = json!({ "state": "idle", "_meta": { "source": "worker" } });
4728 if let Some(stop_reason) = stop_reason {
4729 wire["stopReason"] = stop_reason;
4730 }
4731 assert_eq!(
4732 serde_json::from_value::<StateUpdate>(wire).unwrap(),
4733 StateUpdate::Idle(IdleStateUpdate::new().meta(meta.clone()))
4734 );
4735 }
4736
4737 for stop_reason in ["_paused", "not_a_reason"] {
4740 let wire = json!({
4741 "state": "idle",
4742 "stopReason": stop_reason,
4743 "resumeAfter": 30,
4744 "_meta": { "source": "worker" }
4745 });
4746 let parsed: StateUpdate = serde_json::from_value(wire.clone()).unwrap();
4747 assert_eq!(
4748 parsed,
4749 StateUpdate::Idle(
4750 IdleStateUpdate::new()
4751 .stop_reason(IdleStopReason::Other(OtherStopReason::new(
4752 stop_reason,
4753 [("resumeAfter".into(), json!(30))].into_iter().collect(),
4754 )))
4755 .meta(meta.clone())
4756 )
4757 );
4758 assert_eq!(serde_json::to_value(&parsed).unwrap(), wire);
4759 }
4760 }
4761
4762 #[cfg(feature = "unstable_subagents")]
4763 #[test]
4764 fn subagent_known_stop_reasons_are_never_read_as_other() {
4765 use serde_json::json;
4766
4767 let known = [
4768 IdleStopReason::EndTurn,
4769 IdleStopReason::MaxTokens,
4770 IdleStopReason::MaxTurnRequests,
4771 IdleStopReason::Refusal,
4772 IdleStopReason::Cancelled,
4773 IdleStopReason::Error(ErrorStopReason::new()),
4774 ];
4775 for stop_reason in &known {
4777 match stop_reason {
4778 IdleStopReason::EndTurn
4779 | IdleStopReason::MaxTokens
4780 | IdleStopReason::MaxTurnRequests
4781 | IdleStopReason::Refusal
4782 | IdleStopReason::Cancelled
4783 | IdleStopReason::Error(_)
4784 | IdleStopReason::Other(_) => {}
4785 }
4786 }
4787
4788 for stop_reason in known {
4789 let state = StateUpdate::Idle(IdleStateUpdate::new().stop_reason(stop_reason));
4790 let wire = serde_json::to_value(&state).unwrap();
4791 assert_eq!(
4792 serde_json::from_value::<StateUpdate>(wire.clone()).unwrap(),
4793 state
4794 );
4795 assert!(
4796 serde_json::from_value::<OtherStopReason>(
4797 json!({ "stopReason": wire["stopReason"] })
4798 )
4799 .is_err(),
4800 "{} must not be accepted as a custom stop reason",
4801 wire["stopReason"]
4802 );
4803 }
4804 }
4805
4806 #[cfg(feature = "unstable_subagents")]
4807 #[test]
4808 fn subagent_unknown_stop_reason_leaves_usage_on_the_snapshot() {
4809 use serde_json::json;
4810
4811 let usage = json!({ "totalTokens": 10, "inputTokens": 6, "outputTokens": 4 });
4812 let wire = json!({ "state": "idle", "stopReason": "_paused", "usage": usage });
4813 let StateUpdate::Idle(idle) = serde_json::from_value(wire).unwrap() else {
4814 panic!("expected idle state snapshot");
4815 };
4816 let Some(IdleStopReason::Other(other)) = &idle.stop_reason else {
4817 panic!("expected a custom stop reason");
4818 };
4819 assert!(other.fields.is_empty());
4820
4821 #[cfg(feature = "unstable_end_turn_token_usage")]
4822 {
4823 assert!(idle.usage.is_some());
4824 assert_eq!(serde_json::to_value(&idle).unwrap()["usage"], usage);
4825 }
4826 #[cfg(not(feature = "unstable_end_turn_token_usage"))]
4829 assert!(serde_json::to_value(&idle).unwrap().get("usage").is_none());
4830 }
4831
4832 #[cfg(feature = "unstable_subagents")]
4833 #[test]
4834 fn test_subagent_unknown_activity_metadata_and_capabilities() {
4835 use serde_json::json;
4836
4837 let update = SubagentUpdate::new("child").state(StateUpdate::Unknown(
4838 UnknownStateUpdate::new().meta(
4839 [("source".into(), json!("worker"))]
4840 .into_iter()
4841 .collect::<Meta>(),
4842 ),
4843 ));
4844 let wire = json!({
4845 "sessionId": "child",
4846 "state": {
4847 "state": "unknown",
4848 "_meta": { "source": "worker" }
4849 }
4850 });
4851 assert_eq!(serde_json::to_value(&update).unwrap(), wire);
4852 assert_eq!(
4853 serde_json::from_value::<SubagentUpdate>(wire).unwrap(),
4854 update
4855 );
4856 assert!(update.capabilities.is_undefined());
4858
4859 for meta in [json!(null), json!(false)] {
4860 let state: StateUpdate = serde_json::from_value(json!({
4861 "state": "unknown",
4862 "_meta": meta
4863 }))
4864 .unwrap();
4865 assert_eq!(state, StateUpdate::Unknown(UnknownStateUpdate::new()));
4866 assert_eq!(
4867 serde_json::to_value(state).unwrap(),
4868 json!({"state": "unknown"})
4869 );
4870 }
4871 assert!(serde_json::from_value::<OtherStateUpdate>(json!({"state": "unknown"})).is_err());
4873 }
4874
4875 #[cfg(feature = "unstable_subagents")]
4876 #[test]
4877 fn test_subagent_malformed_optional_state() {
4878 use serde_json::json;
4879
4880 for malformed in [json!("running"), json!({}), json!({"state": 4})] {
4881 assert!(serde_json::from_value::<StateUpdate>(malformed.clone()).is_err());
4882 let update: SubagentUpdate = serde_json::from_value(json!({
4883 "sessionId": "child", "state": malformed
4884 }))
4885 .unwrap();
4886 assert!(update.state.is_undefined());
4887 }
4888 let bad_reason: StateUpdate =
4889 serde_json::from_value(json!({"state": "idle", "stopReason": false})).unwrap();
4890 assert_eq!(bad_reason, StateUpdate::Idle(IdleStateUpdate::new()));
4891 let null_reason: StateUpdate =
4892 serde_json::from_value(json!({"state": "idle", "stopReason": null})).unwrap();
4893 assert_eq!(null_reason, StateUpdate::Idle(IdleStateUpdate::new()));
4894 }
4895
4896 #[cfg(all(feature = "unstable_subagents", feature = "unstable_protocol_v2"))]
4897 #[test]
4898 fn test_subagent_state_v2_wire_parity() {
4899 use serde_json::json;
4900
4901 for wire in [
4902 json!({"state": "running"}),
4903 json!({"state": "idle", "stopReason": "cancelled"}),
4904 json!({"state": "requires_action"}),
4905 json!({"state": "unknown"}),
4906 json!({"state": "unknown", "_meta": {"source": "worker"}}),
4907 json!({"state": "_waiting", "detail": {"ticket": 7}}),
4908 ] {
4909 let v1: StateUpdate = serde_json::from_value(wire.clone()).unwrap();
4910 let v2: crate::v2::StateUpdate = serde_json::from_value(wire.clone()).unwrap();
4911 assert_eq!(serde_json::to_value(v1).unwrap(), wire);
4912 assert_eq!(serde_json::to_value(v2).unwrap(), wire);
4913 }
4914 }
4915
4916 #[cfg(feature = "unstable_subagents")]
4917 #[test]
4918 fn test_subagent_capability_semantics() {
4919 use serde_json::json;
4920
4921 let capabilities =
4922 serde_json::to_value(ClientCapabilities::new().subagents(SubagentCapabilities::new()))
4923 .unwrap();
4924 assert_eq!(capabilities["subagents"], json!({}));
4925
4926 let omitted: ClientCapabilities = serde_json::from_value(json!({})).unwrap();
4927 assert!(omitted.subagents.is_none());
4928
4929 let null: ClientCapabilities =
4930 serde_json::from_value(json!({ "subagents": null })).unwrap();
4931 assert!(null.subagents.is_none());
4932 assert_eq!(null, omitted);
4933 let serialized = serde_json::to_value(null).unwrap();
4934 assert_eq!(serialized, serde_json::to_value(omitted).unwrap());
4935 assert!(serialized.get("subagents").is_none());
4936
4937 for wire in [
4938 json!({}),
4939 json!({"cancel": null}),
4940 json!({"cancel": true}),
4941 json!({"cancel": false}),
4942 ] {
4943 let child: SubagentSessionCapabilities = serde_json::from_value(wire).unwrap();
4944 assert!(child.cancel.is_none());
4945 assert_eq!(serde_json::to_value(child).unwrap(), json!({}));
4946 }
4947 let enabled = SubagentSessionCapabilities::new().cancel(SessionCancelCapabilities::new());
4948 assert_eq!(
4949 serde_json::to_value(&enabled).unwrap(),
4950 json!({"cancel": {}})
4951 );
4952 assert_eq!(
4953 serde_json::from_value::<SubagentSessionCapabilities>(json!({"cancel": {}})).unwrap(),
4954 enabled
4955 );
4956 let meta: Meta = [("source".into(), json!("worker"))].into_iter().collect();
4957 let with_meta =
4958 SubagentSessionCapabilities::new().cancel(SessionCancelCapabilities::new().meta(meta));
4959 let wire = json!({"cancel": {"_meta": {"source": "worker"}}});
4960 assert_eq!(serde_json::to_value(&with_meta).unwrap(), wire);
4961 assert_eq!(
4962 serde_json::from_value::<SubagentSessionCapabilities>(wire).unwrap(),
4963 with_meta
4964 );
4965 let removed = SubagentSessionCapabilities::new();
4967 assert_eq!(serde_json::to_value(&removed).unwrap(), json!({}));
4968 assert!(removed.cancel.is_none());
4969 assert!(enabled.cancel.is_some());
4970 assert!(enabled.cancel(None).cancel.is_none());
4971 let replacement = SubagentUpdate::new("child").capabilities(removed);
4972 assert_eq!(
4973 serde_json::to_value(replacement).unwrap(),
4974 json!({"sessionId": "child", "capabilities": {}})
4975 );
4976 }
4977
4978 #[cfg(all(feature = "unstable_subagents", feature = "schemars"))]
4979 #[test]
4980 fn test_subagent_capability_schema_is_optional_and_nullable() {
4981 use serde_json::json;
4982
4983 let schema = serde_json::to_value(schemars::schema_for!(ClientCapabilities)).unwrap();
4984 let variants = schema["properties"]["subagents"]["anyOf"]
4985 .as_array()
4986 .expect("subagents must allow the capability object or null");
4987 assert!(variants.contains(&json!({"type": "null"})));
4988 assert!(
4989 !schema["required"]
4990 .as_array()
4991 .is_some_and(|required| required.contains(&json!("subagents")))
4992 );
4993 let child =
4994 serde_json::to_value(schemars::schema_for!(SubagentSessionCapabilities)).unwrap();
4995 let variants = child["properties"]["cancel"]["anyOf"]
4996 .as_array()
4997 .expect("cancel must allow the capability object or null");
4998 assert!(variants.contains(&json!({"type": "null"})));
4999 assert!(
5000 variants
5001 .iter()
5002 .any(|variant| variant["type"] == "object" || variant.get("$ref").is_some())
5003 );
5004 assert!(!variants.iter().any(|variant| variant["type"] == "boolean"));
5005 let cancel =
5006 serde_json::to_value(schemars::schema_for!(SessionCancelCapabilities)).unwrap();
5007 assert_eq!(cancel["type"], "object");
5008 assert!(
5009 !child["required"]
5010 .as_array()
5011 .is_some_and(|required| required.contains(&json!("cancel")))
5012 );
5013 }
5014
5015 #[test]
5016 fn test_elicitation_capability_semantics() {
5017 use serde_json::json;
5018
5019 let unsupported: ClientCapabilities = serde_json::from_value(json!({})).unwrap();
5020 assert!(unsupported.elicitation.is_none());
5021
5022 let null: ClientCapabilities =
5023 serde_json::from_value(json!({ "elicitation": null })).unwrap();
5024 assert!(null.elicitation.is_none());
5025
5026 let malformed: ClientCapabilities =
5027 serde_json::from_value(json!({ "elicitation": false })).unwrap();
5028 assert!(malformed.elicitation.is_none());
5029
5030 let empty: ClientCapabilities =
5031 serde_json::from_value(json!({ "elicitation": {} })).unwrap();
5032 let empty = empty.elicitation.expect("present capability");
5033 assert!(!empty.supports_form());
5034 assert!(!empty.supports_url());
5035
5036 let form_only: ClientCapabilities = serde_json::from_value(json!({
5037 "elicitation": { "form": {} }
5038 }))
5039 .unwrap();
5040 let form_only = form_only.elicitation.expect("advertised capability");
5041 assert!(form_only.supports_form());
5042 assert!(!form_only.supports_url());
5043
5044 let url_only: ClientCapabilities = serde_json::from_value(json!({
5045 "elicitation": { "url": {} }
5046 }))
5047 .unwrap();
5048 let url_only = url_only.elicitation.expect("advertised capability");
5049 assert!(!url_only.supports_form());
5050 assert!(url_only.supports_url());
5051
5052 let both: ClientCapabilities = serde_json::from_value(json!({
5053 "elicitation": { "form": {}, "url": {} }
5054 }))
5055 .unwrap();
5056 let both = both.elicitation.expect("advertised capability");
5057 assert!(both.supports_form());
5058 assert!(both.supports_url());
5059 }
5060
5061 #[test]
5062 fn test_elicitation_method_routing_and_envelopes() {
5063 use serde_json::json;
5064
5065 assert_eq!(CLIENT_METHOD_NAMES.elicitation_create, "elicitation/create");
5066 assert_eq!(
5067 CLIENT_METHOD_NAMES.elicitation_complete,
5068 "elicitation/complete"
5069 );
5070
5071 let request = AgentRequest::CreateElicitationRequest(CreateElicitationRequest::new(
5072 crate::v1::ElicitationFormMode::new(
5073 crate::v1::ElicitationSessionScope::new("sess_1"),
5074 crate::v1::ElicitationSchema::new(),
5075 ),
5076 "Choose a value",
5077 ));
5078 assert_eq!(request.method(), "elicitation/create");
5079 let method = Arc::from(request.method());
5080 let request = crate::v1::JsonRpcMessage::wrap(crate::v1::Request {
5081 id: crate::v1::RequestId::Number(7),
5082 method,
5083 params: Some(request),
5084 });
5085 assert_eq!(
5086 serde_json::to_value(request).unwrap(),
5087 json!({
5088 "jsonrpc": "2.0",
5089 "id": 7,
5090 "method": "elicitation/create",
5091 "params": {
5092 "mode": "form",
5093 "sessionId": "sess_1",
5094 "message": "Choose a value",
5095 "requestedSchema": { "type": "object", "properties": {} }
5096 }
5097 })
5098 );
5099
5100 let notification = AgentNotification::CompleteElicitationNotification(
5101 CompleteElicitationNotification::new("elic_1"),
5102 );
5103 assert_eq!(notification.method(), "elicitation/complete");
5104 let method = Arc::from(notification.method());
5105 let notification = crate::v1::JsonRpcMessage::wrap(crate::v1::Notification {
5106 method,
5107 params: Some(notification),
5108 });
5109 assert_eq!(
5110 serde_json::to_value(notification).unwrap(),
5111 json!({
5112 "jsonrpc": "2.0",
5113 "method": "elicitation/complete",
5114 "params": { "elicitationId": "elic_1" }
5115 })
5116 );
5117 }
5118
5119 #[test]
5120 fn test_client_capabilities_default_on_malformed_values() {
5121 use serde_json::json;
5122
5123 let capabilities: ClientCapabilities = serde_json::from_value(json!({
5124 "fs": {
5125 "readTextFile": "yes",
5126 "writeTextFile": true
5127 },
5128 "terminal": {}
5129 }))
5130 .unwrap();
5131
5132 assert!(!capabilities.fs.read_text_file);
5133 assert!(capabilities.fs.write_text_file);
5134 assert!(!capabilities.terminal);
5135
5136 let capabilities: ClientCapabilities = serde_json::from_value(json!({
5137 "fs": false
5138 }))
5139 .unwrap();
5140 assert_eq!(capabilities.fs, FileSystemCapabilities::default());
5141
5142 {
5143 let capabilities: ClientCapabilities = serde_json::from_value(json!({
5144 "auth": false
5145 }))
5146 .unwrap();
5147 assert_eq!(capabilities.auth, AuthCapabilities::default());
5148
5149 let capabilities: AuthCapabilities = serde_json::from_value(json!({
5150 "terminal": {}
5151 }))
5152 .unwrap();
5153 assert!(!capabilities.terminal);
5154 }
5155 }
5156
5157 #[test]
5158 fn test_serialization_behavior() {
5159 use serde_json::json;
5160
5161 assert_eq!(
5162 serde_json::from_value::<SessionInfoUpdate>(json!({})).unwrap(),
5163 SessionInfoUpdate {
5164 title: MaybeUndefined::Undefined,
5165 updated_at: MaybeUndefined::Undefined,
5166 meta: None
5167 }
5168 );
5169 assert_eq!(
5170 serde_json::from_value::<SessionInfoUpdate>(json!({"title": null, "updatedAt": null}))
5171 .unwrap(),
5172 SessionInfoUpdate {
5173 title: MaybeUndefined::Null,
5174 updated_at: MaybeUndefined::Null,
5175 meta: None
5176 }
5177 );
5178 assert_eq!(
5179 serde_json::from_value::<SessionInfoUpdate>(
5180 json!({"title": "title", "updatedAt": "timestamp"})
5181 )
5182 .unwrap(),
5183 SessionInfoUpdate {
5184 title: MaybeUndefined::Value("title".to_string()),
5185 updated_at: MaybeUndefined::Value("timestamp".to_string()),
5186 meta: None
5187 }
5188 );
5189
5190 assert_eq!(
5191 serde_json::to_value(SessionInfoUpdate::new()).unwrap(),
5192 json!({})
5193 );
5194 assert_eq!(
5195 serde_json::to_value(SessionInfoUpdate::new().title("title")).unwrap(),
5196 json!({"title": "title"})
5197 );
5198 assert_eq!(
5199 serde_json::to_value(SessionInfoUpdate::new().title(None)).unwrap(),
5200 json!({"title": null})
5201 );
5202 assert_eq!(
5203 serde_json::to_value(
5204 SessionInfoUpdate::new()
5205 .title("title")
5206 .title(MaybeUndefined::Undefined)
5207 )
5208 .unwrap(),
5209 json!({})
5210 );
5211 }
5212
5213 #[test]
5214 fn test_content_chunk_message_id_serialization() {
5215 use serde_json::json;
5216
5217 assert_eq!(
5218 serde_json::to_value(SessionUpdate::AgentMessageChunk(ContentChunk::new(
5219 ContentBlock::Text(crate::v1::TextContent::new("Hello"))
5220 )))
5221 .unwrap(),
5222 json!({
5223 "sessionUpdate": "agent_message_chunk",
5224 "content": {
5225 "type": "text",
5226 "text": "Hello"
5227 }
5228 })
5229 );
5230
5231 assert_eq!(
5232 serde_json::to_value(SessionUpdate::AgentMessageChunk(
5233 ContentChunk::new(ContentBlock::Text(crate::v1::TextContent::new("Hello")))
5234 .message_id("msg_agent_c42b9")
5235 ))
5236 .unwrap(),
5237 json!({
5238 "sessionUpdate": "agent_message_chunk",
5239 "messageId": "msg_agent_c42b9",
5240 "content": {
5241 "type": "text",
5242 "text": "Hello"
5243 }
5244 })
5245 );
5246
5247 let SessionUpdate::AgentMessageChunk(chunk) = serde_json::from_value(json!({
5248 "sessionUpdate": "agent_message_chunk",
5249 "messageId": null,
5250 "content": {
5251 "type": "text",
5252 "text": "Hello"
5253 }
5254 }))
5255 .unwrap() else {
5256 panic!("expected agent message chunk");
5257 };
5258
5259 assert_eq!(chunk.message_id, None);
5260 }
5261
5262 #[test]
5263 fn test_usage_update_serialization() {
5264 use serde_json::json;
5265
5266 assert_eq!(
5267 serde_json::to_value(SessionUpdate::UsageUpdate(UsageUpdate::new(
5268 53_000, 200_000
5269 )))
5270 .unwrap(),
5271 json!({
5272 "sessionUpdate": "usage_update",
5273 "used": 53000,
5274 "size": 200_000
5275 })
5276 );
5277
5278 assert_eq!(
5279 serde_json::to_value(SessionUpdate::UsageUpdate(
5280 UsageUpdate::new(53_000, 200_000).cost(Cost::new(0.045, "USD"))
5281 ))
5282 .unwrap(),
5283 json!({
5284 "sessionUpdate": "usage_update",
5285 "used": 53000,
5286 "size": 200_000,
5287 "cost": {
5288 "amount": 0.045,
5289 "currency": "USD"
5290 }
5291 })
5292 );
5293
5294 let SessionUpdate::UsageUpdate(update) = serde_json::from_value(json!({
5295 "sessionUpdate": "usage_update",
5296 "used": 53000,
5297 "size": 200_000,
5298 "cost": null
5299 }))
5300 .unwrap() else {
5301 panic!("expected usage update");
5302 };
5303
5304 assert_eq!(update.cost, None);
5305 }
5306
5307 #[cfg(feature = "unstable_nes")]
5308 #[test]
5309 fn test_client_capabilities_position_encodings_serialization() {
5310 use serde_json::json;
5311
5312 let capabilities = ClientCapabilities::new().position_encodings(vec![
5313 PositionEncodingKind::Utf32,
5314 PositionEncodingKind::Utf16,
5315 ]);
5316 let json = serde_json::to_value(&capabilities).unwrap();
5317
5318 assert_eq!(json["positionEncodings"], json!(["utf-32", "utf-16"]));
5319 }
5320
5321 #[test]
5322 fn test_client_capabilities_boolean_config_options_serialization() {
5323 use serde_json::json;
5324
5325 let capabilities = ClientCapabilities::new().session(
5326 ClientSessionCapabilities::new().config_options(
5327 SessionConfigOptionsCapabilities::new()
5328 .boolean(BooleanConfigOptionCapabilities::new()),
5329 ),
5330 );
5331 let json = serde_json::to_value(&capabilities).unwrap();
5332
5333 assert_eq!(json["session"]["configOptions"]["boolean"], json!({}));
5334
5335 let omitted: ClientCapabilities = serde_json::from_value(json!({})).unwrap();
5336 assert!(omitted.session.is_none());
5337
5338 let null_session: ClientCapabilities = serde_json::from_value(json!({
5339 "session": null
5340 }))
5341 .unwrap();
5342 assert!(null_session.session.is_none());
5343
5344 let null_config_options: ClientCapabilities = serde_json::from_value(json!({
5345 "session": {
5346 "configOptions": null
5347 }
5348 }))
5349 .unwrap();
5350 assert!(
5351 null_config_options
5352 .session
5353 .and_then(|session| session.config_options)
5354 .is_none()
5355 );
5356
5357 let null_boolean: ClientCapabilities = serde_json::from_value(json!({
5358 "session": {
5359 "configOptions": {
5360 "boolean": null
5361 }
5362 }
5363 }))
5364 .unwrap();
5365 assert!(
5366 null_boolean
5367 .session
5368 .and_then(|session| session.config_options)
5369 .and_then(|config_options| config_options.boolean)
5370 .is_none()
5371 );
5372 }
5373
5374 #[cfg(feature = "unstable_plan_operations")]
5375 #[test]
5376 fn test_plan_operations_serialization() {
5377 use serde_json::json;
5378
5379 use crate::v1::{PlanEntry, PlanEntryPriority, PlanEntryStatus, PlanUpdateContent};
5380
5381 let plan_update = SessionUpdate::PlanUpdate(PlanUpdate::new(PlanUpdateContent::items(
5382 "plan-1",
5383 vec![PlanEntry::new(
5384 "Step 1",
5385 PlanEntryPriority::High,
5386 PlanEntryStatus::Pending,
5387 )],
5388 )));
5389
5390 assert_eq!(
5391 serde_json::to_value(plan_update).unwrap(),
5392 json!({
5393 "sessionUpdate": "plan_update",
5394 "plan": {
5395 "type": "items",
5396 "planId": "plan-1",
5397 "entries": [
5398 {
5399 "content": "Step 1",
5400 "priority": "high",
5401 "status": "pending"
5402 }
5403 ]
5404 }
5405 })
5406 );
5407
5408 assert_eq!(
5409 serde_json::to_value(SessionUpdate::PlanRemoved(PlanRemoved::new("plan-1"))).unwrap(),
5410 json!({
5411 "sessionUpdate": "plan_removed",
5412 "planId": "plan-1"
5413 })
5414 );
5415
5416 let capabilities = ClientCapabilities::new().plan(PlanCapabilities::new());
5417 let json = serde_json::to_value(&capabilities).unwrap();
5418 assert_eq!(json["plan"], json!({}));
5419
5420 assert_eq!(
5421 serde_json::from_value::<ClientCapabilities>(json!({ "plan": null }))
5422 .unwrap()
5423 .plan,
5424 None
5425 );
5426 }
5427
5428 #[cfg(feature = "unstable_mcp_over_acp")]
5429 #[test]
5430 fn test_agent_mcp_request_method_names() {
5431 use serde_json::json;
5432
5433 let params: serde_json::Map<String, serde_json::Value> =
5434 [("cursor".to_string(), json!("abc"))].into_iter().collect();
5435
5436 assert_eq!(CLIENT_METHOD_NAMES.mcp_message, "mcp/message");
5437 assert_eq!(
5438 AgentRequest::MessageMcpRequest(MessageMcpRequest::new(
5439 "server-1",
5440 "req-1",
5441 "tools/list"
5442 ))
5443 .method(),
5444 "mcp/message"
5445 );
5446 assert_eq!(
5447 serde_json::to_value(
5448 MessageMcpRequest::new("server-1", "req-1", "tools/list").params(params)
5449 )
5450 .unwrap(),
5451 json!({
5452 "serverId": "server-1",
5453 "requestId": "req-1",
5454 "method": "tools/list",
5455 "params": { "cursor": "abc" }
5456 })
5457 );
5458
5459 let request_with_null_params: MessageMcpRequest = serde_json::from_value(json!({
5460 "serverId": "server-1",
5461 "requestId": "req-1",
5462 "method": "tools/list",
5463 "params": null,
5464 "_meta": null
5465 }))
5466 .unwrap();
5467 assert_eq!(request_with_null_params.params, None);
5468 assert_eq!(request_with_null_params.meta, None);
5469 for key in ["serverId", "requestId", "method"] {
5470 let mut value =
5471 json!({"serverId":"server-1", "requestId":"req-1", "method":"tools/list"});
5472 value.as_object_mut().unwrap().remove(key);
5473 assert!(serde_json::from_value::<MessageMcpRequest>(value).is_err());
5474 }
5475 for key in ["serverId", "requestId", "method"] {
5476 let mut value =
5477 json!({"serverId":"server-1", "requestId":"req-1", "method":"tools/list"});
5478 value[key] = serde_json::Value::Null;
5479 assert!(serde_json::from_value::<MessageMcpRequest>(value).is_err());
5480 }
5481 }
5482
5483 #[test]
5484 fn request_permission_request_rejects_malformed_options() {
5485 use serde_json::json;
5486
5487 assert!(
5488 serde_json::from_value::<RequestPermissionRequest>(json!({
5489 "sessionId": "sess-1",
5490 "toolCall": {"toolCallId": "tc-1"},
5491 "options": "not-an-array"
5492 }))
5493 .is_err()
5494 );
5495 assert!(
5496 serde_json::from_value::<RequestPermissionRequest>(json!({
5497 "sessionId": "sess-1",
5498 "toolCall": {"toolCallId": "tc-1"},
5499 "options": [{"optionId": "allow"}]
5500 }))
5501 .is_err()
5502 );
5503 }
5504
5505 #[test]
5506 fn create_terminal_request_rejects_malformed_fields() {
5507 use serde_json::json;
5508
5509 let request = |field: &str, value: serde_json::Value| {
5510 let mut request = json!({"sessionId": "sess-1", "command": "npx"});
5511 request[field] = value;
5512 serde_json::from_value::<CreateTerminalRequest>(request)
5513 };
5514
5515 for (field, value) in [
5517 ("args", json!(["serve", "--port", 3000])),
5518 ("args", json!("serve --port 3000")),
5519 ("env", json!([{"name": "PORT", "value": 3000}])),
5520 ("env", json!({"PORT": "3000"})),
5521 ("cwd", json!({"path": "/repo"})),
5522 ("outputByteLimit", json!("1048576")),
5523 ] {
5524 assert!(request(field, value.clone()).is_err(), "{field}: {value}");
5525 }
5526
5527 for field in ["args", "env", "cwd", "outputByteLimit"] {
5528 assert_eq!(
5529 request(field, serde_json::Value::Null).unwrap(),
5530 CreateTerminalRequest::new("sess-1", "npx"),
5531 "{field}"
5532 );
5533 }
5534 }
5535
5536 #[test]
5537 fn read_text_file_request_rejects_malformed_range() {
5538 use serde_json::json;
5539
5540 let request = |field: &str, value: serde_json::Value| {
5541 let mut request = json!({"sessionId": "sess-1", "path": "/repo/src/main.rs"});
5542 request[field] = value;
5543 serde_json::from_value::<ReadTextFileRequest>(request)
5544 };
5545
5546 for field in ["line", "limit"] {
5548 assert!(request(field, json!("120")).is_err(), "{field}");
5549 assert!(request(field, json!(-1)).is_err(), "{field}");
5550 assert_eq!(
5551 request(field, serde_json::Value::Null).unwrap(),
5552 ReadTextFileRequest::new("sess-1", "/repo/src/main.rs"),
5553 "{field}"
5554 );
5555 }
5556 }
5557}