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, VecSkipError, serde_as, skip_serializing_none};
13#[cfg(feature = "unstable_subagents")]
14use std::collections::BTreeMap;
15
16#[cfg(feature = "unstable_subagents")]
17use super::StopReason;
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 #[cfg(feature = "unstable_session_notices")]
155 Notice(Notice),
156 #[cfg(feature = "unstable_session_compaction")]
165 CompactionUpdate(CompactionUpdate),
166 #[cfg(feature = "unstable_session_compaction")]
175 CompactionSummaryChunk(CompactionSummaryChunk),
176 #[cfg(feature = "unstable_subagents")]
183 SubagentUpdate(SubagentUpdate),
184 #[cfg(feature = "unstable_subagents")]
191 SessionMessage(SessionMessage),
192 #[cfg(feature = "unstable_subagents")]
198 SessionMessageChunk(SessionMessageChunk),
199}
200
201#[cfg(feature = "unstable_subagents")]
207#[serde_as]
208#[skip_serializing_none]
209#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
210#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
211#[serde(rename_all = "camelCase")]
212#[non_exhaustive]
213pub struct SessionMessageChunk {
214 pub message_id: MessageId,
216 #[serde_as(deserialize_as = "DefaultOnError")]
218 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
219 #[serde(default)]
220 pub sender_session_id: Option<SessionId>,
221 #[serde_as(deserialize_as = "DefaultOnError")]
223 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
224 #[serde(default)]
225 pub recipient_session_id: Option<SessionId>,
226 pub content: ContentBlock,
228 #[serde_as(deserialize_as = "DefaultOnError")]
232 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
233 #[serde(default, rename = "_meta")]
234 pub meta: Option<Meta>,
235}
236
237#[cfg(feature = "unstable_subagents")]
238impl SessionMessageChunk {
239 #[must_use]
241 pub fn new(message_id: impl Into<MessageId>, content: ContentBlock) -> Self {
242 Self {
243 message_id: message_id.into(),
244 sender_session_id: None,
245 recipient_session_id: None,
246 content,
247 meta: None,
248 }
249 }
250
251 #[must_use]
253 pub fn sender_session_id(mut self, sender_session_id: impl IntoOption<SessionId>) -> Self {
254 self.sender_session_id = sender_session_id.into_option();
255 self
256 }
257
258 #[must_use]
260 pub fn recipient_session_id(
261 mut self,
262 recipient_session_id: impl IntoOption<SessionId>,
263 ) -> Self {
264 self.recipient_session_id = recipient_session_id.into_option();
265 self
266 }
267
268 #[must_use]
270 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
271 self.meta = meta.into_option();
272 self
273 }
274}
275
276#[cfg(feature = "unstable_subagents")]
282#[serde_as]
283#[skip_serializing_none]
284#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
285#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
286#[serde(rename_all = "camelCase")]
287#[non_exhaustive]
288pub struct SessionMessage {
289 pub message_id: MessageId,
291 #[serde_as(deserialize_as = "DefaultOnError")]
293 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
294 #[serde(default)]
295 pub sender_session_id: Option<SessionId>,
296 #[serde_as(deserialize_as = "DefaultOnError")]
298 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
299 #[serde(default)]
300 pub recipient_session_id: Option<SessionId>,
301 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<VecSkipError<_>>>")]
304 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
305 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
306 pub content: MaybeUndefined<Vec<ContentBlock>>,
307 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
311 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
312 #[serde(
313 default,
314 rename = "_meta",
315 skip_serializing_if = "MaybeUndefined::is_undefined"
316 )]
317 pub meta: MaybeUndefined<Meta>,
318}
319
320#[cfg(feature = "unstable_subagents")]
321impl SessionMessage {
322 #[must_use]
324 pub fn new(message_id: impl Into<MessageId>) -> Self {
325 Self {
326 message_id: message_id.into(),
327 sender_session_id: None,
328 recipient_session_id: None,
329 content: MaybeUndefined::Undefined,
330 meta: MaybeUndefined::Undefined,
331 }
332 }
333
334 #[must_use]
336 pub fn sender_session_id(mut self, sender_session_id: impl IntoOption<SessionId>) -> Self {
337 self.sender_session_id = sender_session_id.into_option();
338 self
339 }
340
341 #[must_use]
343 pub fn recipient_session_id(
344 mut self,
345 recipient_session_id: impl IntoOption<SessionId>,
346 ) -> Self {
347 self.recipient_session_id = recipient_session_id.into_option();
348 self
349 }
350
351 #[must_use]
353 pub fn content(mut self, content: impl IntoMaybeUndefined<Vec<ContentBlock>>) -> Self {
354 self.content = content.into_maybe_undefined();
355 self
356 }
357
358 #[must_use]
360 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
361 self.meta = meta.into_maybe_undefined();
362 self
363 }
364}
365
366#[cfg(all(test, feature = "unstable_subagents"))]
367mod session_message_tests {
368 use super::*;
369 use serde_json::{Value, json};
370
371 #[test]
372 fn envelopes_preserve_participants_and_multimodal_content() {
373 let content = json!([
374 {"type": "text", "text": "Please inspect this"},
375 {"type": "image", "data": "aGVsbG8=", "mimeType": "image/png"}
376 ]);
377 for (transcript, sender, recipient, id) in [
378 ("parent", "parent", "child", "sent-1"),
379 ("child", "parent", "child", "received-9"),
380 ("child", "child", "parent", "sent-2"),
381 ] {
382 let wire = json!({"sessionId": transcript, "update": {
383 "sessionUpdate": "session_message", "messageId": id,
384 "senderSessionId": sender, "recipientSessionId": recipient, "content": content
385 }});
386 let decoded: SessionNotification = serde_json::from_value(wire.clone()).unwrap();
387 assert_eq!(serde_json::to_value(&decoded).unwrap(), wire);
388 assert!(matches!(decoded.update, SessionUpdate::SessionMessage(_)));
389 let message = SessionMessage::new(id)
390 .sender_session_id(SessionId::new(sender))
391 .recipient_session_id(SessionId::new(recipient))
392 .content(vec![]);
393 assert_eq!(serde_json::to_value(message).unwrap()["content"], json!([]));
394 }
395 }
396
397 #[test]
398 fn message_upserts_validate_ids_and_patch_fields() {
399 let base = json!({"sessionUpdate": "session_message", "messageId": "m1",
400 "senderSessionId": "parent", "recipientSessionId": "child", "content": []});
401 {
402 let key = "messageId";
403 let mut missing = base.clone();
404 missing.as_object_mut().unwrap().remove(key);
405 assert!(
406 serde_json::from_value::<SessionUpdate>(missing).is_err(),
407 "{key}"
408 );
409 let mut null = base.clone();
410 null[key] = Value::Null;
411 assert!(
412 serde_json::from_value::<SessionUpdate>(null).is_err(),
413 "{key}"
414 );
415 let mut non_string = base.clone();
416 non_string[key] = json!(42);
417 assert!(
418 serde_json::from_value::<SessionUpdate>(non_string).is_err(),
419 "{key}"
420 );
421 }
422 for content in [None, Some(json!({}))] {
423 let mut wire = base.clone();
424 wire.as_object_mut().unwrap().remove("content");
425 if let Some(content) = content {
426 wire["content"] = content;
427 }
428 let decoded: SessionUpdate = serde_json::from_value(wire).unwrap();
429 let encoded = serde_json::to_value(decoded).unwrap();
430 let mut unchanged = base.clone();
431 unchanged.as_object_mut().unwrap().remove("content");
432 assert_eq!(encoded, unchanged);
433 }
434 let clear = SessionMessage::new("m1")
435 .sender_session_id(SessionId::new("parent"))
436 .recipient_session_id(SessionId::new("child"))
437 .content(None)
438 .meta(None);
439 let mut clear_wire = base.clone();
440 clear_wire["content"] = Value::Null;
441 clear_wire["_meta"] = Value::Null;
442 assert_eq!(
443 serde_json::to_value(&clear).unwrap()["content"],
444 Value::Null
445 );
446 let clear_update: SessionUpdate = serde_json::from_value(clear_wire.clone()).unwrap();
447 assert_eq!(serde_json::to_value(clear_update).unwrap(), clear_wire);
448 assert!(clear.content.is_null());
449 assert!(clear.meta.is_null());
450 for meta in [None, Some(Value::Null), Some(json!({"tag": "value"}))] {
451 let mut wire = base.clone();
452 if let Some(meta) = meta {
453 wire["_meta"] = meta;
454 }
455 let decoded: SessionUpdate = serde_json::from_value(wire.clone()).unwrap();
456 let encoded = serde_json::to_value(decoded).unwrap();
457 assert_eq!(encoded, wire);
458 }
459 let metadata_only = json!({"sessionUpdate": "session_message",
460 "messageId": "m1", "senderSessionId": "parent",
461 "recipientSessionId": "child", "_meta": {"tag": "value"}});
462 let decoded: SessionUpdate = serde_json::from_value(metadata_only.clone()).unwrap();
463 assert_eq!(serde_json::to_value(decoded).unwrap(), metadata_only);
464 let unset = SessionMessage::new("m1");
465 assert_eq!(
466 serde_json::to_value(&unset).unwrap(),
467 json!({"messageId": "m1"})
468 );
469 assert!(unset.content.is_undefined());
470 assert!(unset.meta.is_undefined());
471 let reset = SessionMessage::new("m1").content(vec![]);
472 assert_eq!(serde_json::to_value(reset).unwrap()["content"], json!([]));
473 let malformed: SessionMessage = serde_json::from_value(json!({
474 "messageId": "m1", "senderSessionId": "parent", "recipientSessionId": "child",
475 "content": false, "_meta": false
476 }))
477 .unwrap();
478 assert!(malformed.content.is_undefined());
479 assert!(malformed.meta.is_undefined());
480 }
481
482 #[test]
483 fn first_chunk_and_upsert_share_transcript_local_identity() {
484 for (transcript, id) in [("parent", "sent-1"), ("child", "received-9")] {
485 let wire = json!({"sessionId": transcript, "update": {
486 "sessionUpdate": "session_message_chunk", "messageId": id,
487 "senderSessionId": "parent", "recipientSessionId": "child",
488 "content": {"type": "text", "text": "first"}
489 }});
490 let decoded: SessionNotification = serde_json::from_value(wire.clone()).unwrap();
491 assert_eq!(serde_json::to_value(decoded).unwrap(), wire);
492 let upsert = SessionMessage::new(id)
493 .sender_session_id(SessionId::new("parent"))
494 .recipient_session_id(SessionId::new("child"));
495 let chunk = SessionMessageChunk::new(
496 id,
497 ContentBlock::Text(crate::v1::TextContent::new("first")),
498 )
499 .sender_session_id(SessionId::new("parent"))
500 .recipient_session_id(SessionId::new("child"));
501 assert_eq!(chunk.message_id, upsert.message_id);
502 assert_eq!(chunk.sender_session_id, upsert.sender_session_id);
503 assert_eq!(chunk.recipient_session_id, upsert.recipient_session_id);
504 assert_eq!(
505 serde_json::to_value(SessionUpdate::SessionMessageChunk(chunk)).unwrap(),
506 wire["update"]
507 );
508 }
509 let wire = json!({"sessionId": "child", "update": {
510 "sessionUpdate": "session_message_chunk", "messageId": "received-9",
511 "senderSessionId": "parent", "recipientSessionId": "child",
512 "content": {"type": "text", "text": "first"}
513 }});
514 let base = wire["update"].clone();
515 for key in ["messageId", "content"] {
516 let mut missing = base.clone();
517 missing.as_object_mut().unwrap().remove(key);
518 assert!(
519 serde_json::from_value::<SessionUpdate>(missing).is_err(),
520 "{key}"
521 );
522 let mut null = base.clone();
523 null[key] = Value::Null;
524 assert!(
525 serde_json::from_value::<SessionUpdate>(null).is_err(),
526 "{key}"
527 );
528 let mut non_string = base.clone();
529 non_string[key] = json!(42);
530 assert!(
531 serde_json::from_value::<SessionUpdate>(non_string).is_err(),
532 "{key}"
533 );
534 }
535 for meta in [None, Some(Value::Null), Some(json!({"chunk": true}))] {
536 let mut value = base.clone();
537 if let Some(meta) = meta {
538 value["_meta"] = meta;
539 }
540 let decoded: SessionUpdate = serde_json::from_value(value.clone()).unwrap();
541 let encoded = serde_json::to_value(decoded).unwrap();
542 if value["_meta"].is_null() {
543 assert_eq!(encoded, base);
544 } else {
545 assert_eq!(encoded, value);
546 }
547 }
548 }
549
550 #[cfg(feature = "schemars")]
551 #[test]
552 fn schema_requires_ids_and_chunk_content() {
553 let schema = serde_json::to_value(schemars::schema_for!(SessionMessage)).unwrap();
554 let required = schema["required"].as_array().unwrap();
555 assert_eq!(required, &vec![json!("messageId")]);
556 let chunk = serde_json::to_value(schemars::schema_for!(SessionMessageChunk)).unwrap();
557 let required = chunk["required"].as_array().unwrap();
558 assert_eq!(required.len(), 2);
559 assert!(required.contains(&json!("messageId")));
560 assert!(required.contains(&json!("content")));
561 }
562
563 #[test]
564 fn endpoints_can_arrive_late_or_be_omitted_from_later_events() {
565 let block = ContentBlock::Text(crate::v1::TextContent::new("hello"));
566 let minimal = SessionMessage::new("m1");
567 let first = SessionMessageChunk::new("m1", block.clone());
568 assert_eq!(
569 serde_json::to_value(&minimal).unwrap(),
570 json!({"messageId": "m1"})
571 );
572 assert_eq!(
573 serde_json::to_value(&first).unwrap(),
574 json!({"messageId": "m1", "content": {"type": "text", "text": "hello"}})
575 );
576 let enriched = SessionMessage::new("m1")
577 .sender_session_id(SessionId::new("parent"))
578 .recipient_session_id(SessionId::new("child"));
579 assert_eq!(enriched.sender_session_id, Some(SessionId::new("parent")));
580 assert_eq!(enriched.recipient_session_id, Some(SessionId::new("child")));
581 let later =
582 SessionMessageChunk::new("m1", block).sender_session_id(SessionId::new("parent"));
583 assert_eq!(later.sender_session_id, Some(SessionId::new("parent")));
584 assert_eq!(later.recipient_session_id, None);
585 for update in [
586 SessionUpdate::SessionMessage(minimal),
587 SessionUpdate::SessionMessageChunk(first),
588 SessionUpdate::SessionMessage(enriched),
589 SessionUpdate::SessionMessageChunk(later),
590 ] {
591 let wire = serde_json::to_value(&update).unwrap();
592 let decoded: SessionUpdate = serde_json::from_value(wire.clone()).unwrap();
593 assert_eq!(serde_json::to_value(decoded).unwrap(), wire);
594 }
595 }
596
597 #[test]
598 fn invalid_or_null_endpoints_are_absent_but_message_id_is_required() {
599 for (kind, content) in [
600 ("session_message", None),
601 (
602 "session_message_chunk",
603 Some(json!({"type": "text", "text": "hello"})),
604 ),
605 ] {
606 let mut base = json!({"sessionUpdate": kind, "messageId": "m1",
607 "senderSessionId": null, "recipientSessionId": 42});
608 if let Some(content) = content {
609 base["content"] = content;
610 }
611 let decoded: SessionUpdate = serde_json::from_value(base.clone()).unwrap();
612 let encoded = serde_json::to_value(decoded).unwrap();
613 base.as_object_mut().unwrap().remove("senderSessionId");
614 base.as_object_mut().unwrap().remove("recipientSessionId");
615 assert_eq!(encoded, base);
616 for bad in [Value::Null, json!(42)] {
617 let mut invalid = base.clone();
618 invalid["messageId"] = bad;
619 assert!(serde_json::from_value::<SessionUpdate>(invalid).is_err());
620 }
621 }
622 }
623}
624
625#[cfg(all(test, not(feature = "unstable_subagents")))]
626mod disabled_session_message_tests {
627 use super::*;
628 use serde_json::json;
629
630 #[test]
631 fn message_updates_require_subagents_gate() {
632 for (kind, content) in [
633 ("session_message", json!([])),
634 (
635 "session_message_chunk",
636 json!({"type": "text", "text": "hello"}),
637 ),
638 ] {
639 let wire = json!({"sessionUpdate": kind, "messageId": "m1", "content": content});
640 assert!(serde_json::from_value::<SessionUpdate>(wire).is_err());
641 }
642 }
643}
644
645#[cfg(feature = "unstable_session_notices")]
651#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
652#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
653#[serde(rename_all = "snake_case")]
654#[non_exhaustive]
655pub enum NoticeSeverity {
656 Info,
658 Warning,
660 Error,
662 #[serde(untagged)]
667 Other(String),
668}
669
670#[cfg(feature = "unstable_session_notices")]
684#[serde_as]
685#[skip_serializing_none]
686#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
687#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
688#[serde(rename_all = "camelCase")]
689#[non_exhaustive]
690pub struct Notice {
691 pub severity: NoticeSeverity,
693 #[cfg_attr(feature = "schemars", schemars(length(min = 1)))]
695 pub title: String,
696 #[serde_as(deserialize_as = "DefaultOnError")]
700 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
701 #[serde(default)]
702 pub description: Option<String>,
703 #[serde_as(deserialize_as = "DefaultOnError")]
707 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
708 #[serde(default, rename = "_meta")]
709 pub meta: Option<Meta>,
710}
711
712#[cfg(feature = "unstable_session_notices")]
713impl Notice {
714 #[must_use]
716 pub fn new(severity: NoticeSeverity, title: impl Into<String>) -> Self {
717 Self {
718 severity,
719 title: title.into(),
720 description: None,
721 meta: None,
722 }
723 }
724
725 #[must_use]
727 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
728 self.description = description.into_option();
729 self
730 }
731
732 #[must_use]
734 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
735 self.meta = meta.into_option();
736 self
737 }
738}
739
740#[cfg(feature = "unstable_session_compaction")]
746#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
747#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
748#[serde(transparent)]
749#[from(Arc<str>, String, &'static str)]
750#[non_exhaustive]
751pub struct CompactionId(pub Arc<str>);
752
753#[cfg(feature = "unstable_session_compaction")]
754impl CompactionId {
755 #[must_use]
757 pub fn new(id: impl Into<Arc<str>>) -> Self {
758 Self(id.into())
759 }
760}
761
762#[cfg(feature = "unstable_session_compaction")]
768#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
769#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
770#[serde(rename_all = "snake_case")]
771#[non_exhaustive]
772pub enum CompactionStatus {
773 InProgress,
775 Completed,
777 Failed,
779 Cancelled,
781 #[serde(untagged)]
786 Other(String),
787}
788
789#[cfg(feature = "unstable_session_compaction")]
803#[serde_as]
804#[skip_serializing_none]
805#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
806#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
807#[serde(rename_all = "camelCase")]
808#[non_exhaustive]
809pub struct CompactionUpdate {
810 pub compaction_id: CompactionId,
812 pub status: CompactionStatus,
814 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<VecSkipError<_>>>")]
816 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
817 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
818 pub summary: MaybeUndefined<Vec<ContentBlock>>,
819 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
821 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
822 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
823 pub error: MaybeUndefined<String>,
824 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
826 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
827 #[serde(
828 rename = "_meta",
829 default,
830 skip_serializing_if = "MaybeUndefined::is_undefined"
831 )]
832 pub meta: MaybeUndefined<Meta>,
833}
834
835#[cfg(feature = "unstable_session_compaction")]
836impl CompactionUpdate {
837 #[must_use]
839 pub fn new(compaction_id: impl Into<CompactionId>, status: CompactionStatus) -> Self {
840 Self {
841 compaction_id: compaction_id.into(),
842 status,
843 summary: MaybeUndefined::Undefined,
844 error: MaybeUndefined::Undefined,
845 meta: MaybeUndefined::Undefined,
846 }
847 }
848
849 #[must_use]
851 pub fn summary(mut self, summary: impl IntoMaybeUndefined<Vec<ContentBlock>>) -> Self {
852 self.summary = summary.into_maybe_undefined();
853 self
854 }
855
856 #[must_use]
858 pub fn error(mut self, error: impl IntoMaybeUndefined<String>) -> Self {
859 self.error = error.into_maybe_undefined();
860 self
861 }
862
863 #[must_use]
865 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
866 self.meta = meta.into_maybe_undefined();
867 self
868 }
869}
870
871#[cfg(feature = "unstable_session_compaction")]
880#[serde_as]
881#[skip_serializing_none]
882#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
883#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
884#[serde(rename_all = "camelCase")]
885#[non_exhaustive]
886pub struct CompactionSummaryChunk {
887 pub compaction_id: CompactionId,
889 pub content: ContentBlock,
891 #[serde_as(deserialize_as = "DefaultOnError")]
893 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
894 #[serde(default, rename = "_meta")]
895 pub meta: Option<Meta>,
896}
897
898#[cfg(feature = "unstable_session_compaction")]
899impl CompactionSummaryChunk {
900 #[must_use]
902 pub fn new(compaction_id: impl Into<CompactionId>, content: ContentBlock) -> Self {
903 Self {
904 compaction_id: compaction_id.into(),
905 content,
906 meta: None,
907 }
908 }
909
910 #[must_use]
912 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
913 self.meta = meta.into_option();
914 self
915 }
916}
917
918#[cfg(feature = "unstable_subagents")]
942#[serde_as]
943#[skip_serializing_none]
944#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
945#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
946#[serde(rename_all = "camelCase")]
947#[non_exhaustive]
948pub struct SubagentUpdate {
949 pub session_id: SessionId,
951 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
956 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
957 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
958 pub title: MaybeUndefined<String>,
959 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
965 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
966 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
967 pub description: MaybeUndefined<String>,
968 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
975 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
976 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
977 pub capabilities: MaybeUndefined<SubagentSessionCapabilities>,
978 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
985 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
986 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
987 pub state: MaybeUndefined<StateUpdate>,
988 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
995 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
996 #[serde(
997 default,
998 rename = "_meta",
999 skip_serializing_if = "MaybeUndefined::is_undefined"
1000 )]
1001 pub meta: MaybeUndefined<Meta>,
1002}
1003
1004#[cfg(feature = "unstable_subagents")]
1005impl SubagentUpdate {
1006 #[must_use]
1008 pub fn new(session_id: impl Into<SessionId>) -> Self {
1009 Self {
1010 session_id: session_id.into(),
1011 title: MaybeUndefined::Undefined,
1012 description: MaybeUndefined::Undefined,
1013 capabilities: MaybeUndefined::Undefined,
1014 state: MaybeUndefined::Undefined,
1015 meta: MaybeUndefined::Undefined,
1016 }
1017 }
1018
1019 #[must_use]
1021 pub fn title(mut self, title: impl IntoMaybeUndefined<String>) -> Self {
1022 self.title = title.into_maybe_undefined();
1023 self
1024 }
1025
1026 #[must_use]
1028 pub fn description(mut self, description: impl IntoMaybeUndefined<String>) -> Self {
1029 self.description = description.into_maybe_undefined();
1030 self
1031 }
1032
1033 #[must_use]
1035 pub fn capabilities(
1036 mut self,
1037 capabilities: impl IntoMaybeUndefined<SubagentSessionCapabilities>,
1038 ) -> Self {
1039 self.capabilities = capabilities.into_maybe_undefined();
1040 self
1041 }
1042
1043 #[must_use]
1045 pub fn state(mut self, state: impl IntoMaybeUndefined<StateUpdate>) -> Self {
1046 self.state = state.into_maybe_undefined();
1047 self
1048 }
1049
1050 #[must_use]
1054 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
1055 self.meta = meta.into_maybe_undefined();
1056 self
1057 }
1058}
1059
1060#[cfg(feature = "unstable_subagents")]
1069#[serde_as]
1070#[skip_serializing_none]
1071#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1072#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1073#[serde(rename_all = "camelCase")]
1074#[non_exhaustive]
1075pub struct SubagentSessionCapabilities {
1076 #[serde_as(deserialize_as = "DefaultOnError")]
1080 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1081 #[serde(default)]
1082 pub cancel: Option<SessionCancelCapabilities>,
1083 #[serde_as(deserialize_as = "DefaultOnError")]
1087 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1088 #[serde(default)]
1089 #[serde(rename = "_meta")]
1090 pub meta: Option<Meta>,
1091}
1092
1093#[cfg(feature = "unstable_subagents")]
1094impl SubagentSessionCapabilities {
1095 #[must_use]
1097 pub fn new() -> Self {
1098 Self::default()
1099 }
1100
1101 #[must_use]
1103 pub fn cancel(mut self, cancel: impl IntoOption<SessionCancelCapabilities>) -> Self {
1104 self.cancel = cancel.into_option();
1105 self
1106 }
1107
1108 #[must_use]
1112 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1113 self.meta = meta.into_option();
1114 self
1115 }
1116}
1117
1118#[cfg(feature = "unstable_subagents")]
1126#[serde_as]
1127#[skip_serializing_none]
1128#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1129#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1130#[non_exhaustive]
1131pub struct SessionCancelCapabilities {
1132 #[serde_as(deserialize_as = "DefaultOnError")]
1138 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1139 #[serde(default)]
1140 #[serde(rename = "_meta")]
1141 pub meta: Option<Meta>,
1142}
1143
1144#[cfg(feature = "unstable_subagents")]
1145impl SessionCancelCapabilities {
1146 #[must_use]
1148 pub fn new() -> Self {
1149 Self::default()
1150 }
1151
1152 #[must_use]
1158 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1159 self.meta = meta.into_option();
1160 self
1161 }
1162}
1163
1164#[cfg(feature = "unstable_subagents")]
1174#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1175#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1176#[serde(tag = "state", rename_all = "snake_case")]
1177#[non_exhaustive]
1178pub enum StateUpdate {
1179 Running(RunningStateUpdate),
1181 Idle(IdleStateUpdate),
1183 RequiresAction(RequiresActionStateUpdate),
1185 Unknown(UnknownStateUpdate),
1189 #[serde(untagged)]
1194 Other(OtherStateUpdate),
1195}
1196
1197#[cfg(feature = "unstable_subagents")]
1199#[serde_as]
1200#[skip_serializing_none]
1201#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1202#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1203#[serde(rename_all = "camelCase")]
1204#[non_exhaustive]
1205pub struct RunningStateUpdate {
1206 #[serde_as(deserialize_as = "DefaultOnError")]
1210 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1211 #[serde(default, rename = "_meta")]
1212 pub meta: Option<Meta>,
1213}
1214
1215#[cfg(feature = "unstable_subagents")]
1216impl RunningStateUpdate {
1217 #[must_use]
1219 pub fn new() -> Self {
1220 Self::default()
1221 }
1222
1223 #[must_use]
1225 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1226 self.meta = meta.into_option();
1227 self
1228 }
1229}
1230
1231#[cfg(feature = "unstable_subagents")]
1233#[serde_as]
1234#[skip_serializing_none]
1235#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1236#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
1237#[serde(rename_all = "camelCase")]
1238#[non_exhaustive]
1239pub struct IdleStateUpdate {
1240 #[serde_as(deserialize_as = "DefaultOnError")]
1242 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1243 #[serde(default)]
1244 pub stop_reason: Option<StopReason>,
1245 #[cfg(feature = "unstable_end_turn_token_usage")]
1249 #[serde_as(deserialize_as = "DefaultOnError")]
1250 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1251 #[serde(default)]
1252 pub usage: Option<Usage>,
1253 #[serde_as(deserialize_as = "DefaultOnError")]
1257 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1258 #[serde(default, rename = "_meta")]
1259 pub meta: Option<Meta>,
1260}
1261
1262#[cfg(feature = "unstable_subagents")]
1263impl IdleStateUpdate {
1264 #[must_use]
1266 pub fn new() -> Self {
1267 Self::default()
1268 }
1269
1270 #[must_use]
1272 pub fn stop_reason(mut self, stop_reason: impl IntoOption<StopReason>) -> Self {
1273 self.stop_reason = stop_reason.into_option();
1274 self
1275 }
1276
1277 #[cfg(feature = "unstable_end_turn_token_usage")]
1279 #[must_use]
1280 pub fn usage(mut self, usage: impl IntoOption<Usage>) -> Self {
1281 self.usage = usage.into_option();
1282 self
1283 }
1284
1285 #[must_use]
1287 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1288 self.meta = meta.into_option();
1289 self
1290 }
1291}
1292
1293#[cfg(feature = "unstable_subagents")]
1295#[serde_as]
1296#[skip_serializing_none]
1297#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1298#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1299#[serde(rename_all = "camelCase")]
1300#[non_exhaustive]
1301pub struct RequiresActionStateUpdate {
1302 #[serde_as(deserialize_as = "DefaultOnError")]
1306 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1307 #[serde(default, rename = "_meta")]
1308 pub meta: Option<Meta>,
1309}
1310
1311#[cfg(feature = "unstable_subagents")]
1312impl RequiresActionStateUpdate {
1313 #[must_use]
1315 pub fn new() -> Self {
1316 Self::default()
1317 }
1318
1319 #[must_use]
1321 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1322 self.meta = meta.into_option();
1323 self
1324 }
1325}
1326
1327#[cfg(feature = "unstable_subagents")]
1341#[serde_as]
1342#[skip_serializing_none]
1343#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1344#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1345#[serde(rename_all = "camelCase")]
1346#[non_exhaustive]
1347pub struct UnknownStateUpdate {
1348 #[serde_as(deserialize_as = "DefaultOnError")]
1352 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1353 #[serde(default, rename = "_meta")]
1354 pub meta: Option<Meta>,
1355}
1356
1357#[cfg(feature = "unstable_subagents")]
1358impl UnknownStateUpdate {
1359 #[must_use]
1361 pub fn new() -> Self {
1362 Self::default()
1363 }
1364
1365 #[must_use]
1367 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1368 self.meta = meta.into_option();
1369 self
1370 }
1371}
1372
1373#[cfg(feature = "unstable_subagents")]
1375#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1376#[derive(Debug, Clone, Serialize, PartialEq)]
1377#[cfg_attr(feature = "schemars", schemars(inline))]
1378#[cfg_attr(feature = "schemars", schemars(transform = other_state_update_schema))]
1379#[serde(rename_all = "camelCase")]
1380#[non_exhaustive]
1381pub struct OtherStateUpdate {
1382 pub state: String,
1384 #[serde(flatten)]
1386 pub fields: BTreeMap<String, serde_json::Value>,
1387}
1388
1389#[cfg(feature = "unstable_subagents")]
1390impl OtherStateUpdate {
1391 #[must_use]
1393 pub fn new(state: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
1394 fields.remove("state");
1395 Self {
1396 state: state.into(),
1397 fields,
1398 }
1399 }
1400}
1401
1402#[cfg(feature = "unstable_subagents")]
1403impl<'de> Deserialize<'de> for OtherStateUpdate {
1404 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1405 where
1406 D: serde::Deserializer<'de>,
1407 {
1408 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1409 let state = fields
1410 .remove("state")
1411 .ok_or_else(|| serde::de::Error::missing_field("state"))?;
1412 let serde_json::Value::String(state) = state else {
1413 return Err(serde::de::Error::custom("`state` must be a string"));
1414 };
1415 if is_known_state_update(&state) {
1416 return Err(serde::de::Error::custom(format!(
1417 "known state update `{state}` did not match its schema"
1418 )));
1419 }
1420 Ok(Self { state, fields })
1421 }
1422}
1423
1424#[cfg(feature = "unstable_subagents")]
1425const KNOWN_STATE_UPDATE_STATES: &[&str] = &["running", "idle", "requires_action", "unknown"];
1426
1427#[cfg(feature = "unstable_subagents")]
1428fn is_known_state_update(state: &str) -> bool {
1429 KNOWN_STATE_UPDATE_STATES.contains(&state)
1430}
1431
1432#[cfg(all(feature = "unstable_subagents", feature = "schemars"))]
1433fn other_state_update_schema(schema: &mut Schema) {
1434 let known = KNOWN_STATE_UPDATE_STATES
1435 .iter()
1436 .map(|state| {
1437 serde_json::json!({
1438 "properties": { "state": { "const": state, "type": "string" } },
1439 "required": ["state"],
1440 "type": "object"
1441 })
1442 })
1443 .collect::<Vec<_>>();
1444 schema.insert("not".into(), serde_json::json!({ "anyOf": known }));
1445}
1446
1447#[serde_as]
1451#[skip_serializing_none]
1452#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1453#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1454#[serde(rename_all = "camelCase")]
1455#[non_exhaustive]
1456pub struct CurrentModeUpdate {
1457 pub current_mode_id: SessionModeId,
1459 #[serde_as(deserialize_as = "DefaultOnError")]
1465 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1466 #[serde(default)]
1467 #[serde(rename = "_meta")]
1468 pub meta: Option<Meta>,
1469}
1470
1471impl CurrentModeUpdate {
1472 #[must_use]
1474 pub fn new(current_mode_id: impl Into<SessionModeId>) -> Self {
1475 Self {
1476 current_mode_id: current_mode_id.into(),
1477 meta: None,
1478 }
1479 }
1480
1481 #[must_use]
1487 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1488 self.meta = meta.into_option();
1489 self
1490 }
1491}
1492
1493#[serde_as]
1495#[skip_serializing_none]
1496#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1497#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1498#[serde(rename_all = "camelCase")]
1499#[non_exhaustive]
1500pub struct ConfigOptionUpdate {
1501 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
1503 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
1504 pub config_options: Vec<SessionConfigOption>,
1505 #[serde_as(deserialize_as = "DefaultOnError")]
1511 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1512 #[serde(default)]
1513 #[serde(rename = "_meta")]
1514 pub meta: Option<Meta>,
1515}
1516
1517impl ConfigOptionUpdate {
1518 #[must_use]
1520 pub fn new(config_options: Vec<SessionConfigOption>) -> Self {
1521 Self {
1522 config_options,
1523 meta: None,
1524 }
1525 }
1526
1527 #[must_use]
1533 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1534 self.meta = meta.into_option();
1535 self
1536 }
1537}
1538
1539#[serde_as]
1544#[skip_serializing_none]
1545#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1546#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1547#[serde(rename_all = "camelCase")]
1548#[non_exhaustive]
1549pub struct SessionInfoUpdate {
1550 #[serde_as(deserialize_as = "DefaultOnError")]
1552 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1553 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
1554 pub title: MaybeUndefined<String>,
1555 #[serde_as(deserialize_as = "DefaultOnError")]
1557 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1558 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
1559 pub updated_at: MaybeUndefined<String>,
1560 #[serde_as(deserialize_as = "DefaultOnError")]
1566 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1567 #[serde(default)]
1568 #[serde(rename = "_meta")]
1569 pub meta: Option<Meta>,
1570}
1571
1572impl SessionInfoUpdate {
1573 #[must_use]
1575 pub fn new() -> Self {
1576 Self::default()
1577 }
1578
1579 #[must_use]
1581 pub fn title(mut self, title: impl IntoMaybeUndefined<String>) -> Self {
1582 self.title = title.into_maybe_undefined();
1583 self
1584 }
1585
1586 #[must_use]
1588 pub fn updated_at(mut self, updated_at: impl IntoMaybeUndefined<String>) -> Self {
1589 self.updated_at = updated_at.into_maybe_undefined();
1590 self
1591 }
1592
1593 #[must_use]
1599 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1600 self.meta = meta.into_option();
1601 self
1602 }
1603}
1604
1605#[serde_as]
1607#[skip_serializing_none]
1608#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1609#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1610#[serde(rename_all = "camelCase")]
1611#[non_exhaustive]
1612pub struct UsageUpdate {
1613 pub used: u64,
1615 pub size: u64,
1617 #[serde_as(deserialize_as = "DefaultOnError")]
1619 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1620 #[serde(default)]
1621 pub cost: Option<Cost>,
1622 #[serde_as(deserialize_as = "DefaultOnError")]
1628 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1629 #[serde(default)]
1630 #[serde(rename = "_meta")]
1631 pub meta: Option<Meta>,
1632}
1633
1634impl UsageUpdate {
1635 #[must_use]
1637 pub fn new(used: u64, size: u64) -> Self {
1638 Self {
1639 used,
1640 size,
1641 cost: None,
1642 meta: None,
1643 }
1644 }
1645
1646 #[must_use]
1648 pub fn cost(mut self, cost: impl IntoOption<Cost>) -> Self {
1649 self.cost = cost.into_option();
1650 self
1651 }
1652
1653 #[must_use]
1659 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1660 self.meta = meta.into_option();
1661 self
1662 }
1663}
1664
1665#[serde_as]
1667#[skip_serializing_none]
1668#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1669#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1670#[serde(rename_all = "camelCase")]
1671#[non_exhaustive]
1672pub struct Cost {
1673 pub amount: f64,
1675 pub currency: String,
1677 #[serde_as(deserialize_as = "DefaultOnError")]
1683 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1684 #[serde(default)]
1685 #[serde(rename = "_meta")]
1686 pub meta: Option<Meta>,
1687}
1688
1689impl Cost {
1690 #[must_use]
1692 pub fn new(amount: f64, currency: impl Into<String>) -> Self {
1693 Self {
1694 amount,
1695 currency: currency.into(),
1696 meta: None,
1697 }
1698 }
1699
1700 #[must_use]
1706 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1707 self.meta = meta.into_option();
1708 self
1709 }
1710}
1711
1712#[serde_as]
1714#[skip_serializing_none]
1715#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1716#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1717#[serde(rename_all = "camelCase")]
1718#[non_exhaustive]
1719pub struct ContentChunk {
1720 pub content: ContentBlock,
1722 #[serde_as(deserialize_as = "DefaultOnError")]
1727 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1728 #[serde(default)]
1729 pub message_id: Option<MessageId>,
1730 #[serde_as(deserialize_as = "DefaultOnError")]
1736 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1737 #[serde(default)]
1738 #[serde(rename = "_meta")]
1739 pub meta: Option<Meta>,
1740}
1741
1742impl ContentChunk {
1743 #[must_use]
1745 pub fn new(content: ContentBlock) -> Self {
1746 Self {
1747 content,
1748 message_id: None,
1749 meta: None,
1750 }
1751 }
1752
1753 #[must_use]
1758 pub fn message_id(mut self, message_id: impl IntoOption<MessageId>) -> Self {
1759 self.message_id = message_id.into_option();
1760 self
1761 }
1762
1763 #[must_use]
1769 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1770 self.meta = meta.into_option();
1771 self
1772 }
1773}
1774
1775#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1777#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
1778#[serde(transparent)]
1779#[from(Arc<str>, String, &'static str)]
1780#[non_exhaustive]
1781pub struct MessageId(pub Arc<str>);
1782
1783impl MessageId {
1784 #[must_use]
1786 pub fn new(id: impl Into<Arc<str>>) -> Self {
1787 Self(id.into())
1788 }
1789}
1790
1791impl IntoOption<MessageId> for &str {
1792 fn into_option(self) -> Option<MessageId> {
1793 Some(MessageId::new(self))
1794 }
1795}
1796
1797#[serde_as]
1799#[skip_serializing_none]
1800#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1801#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1802#[serde(rename_all = "camelCase")]
1803#[non_exhaustive]
1804pub struct AvailableCommandsUpdate {
1805 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
1807 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
1808 pub available_commands: Vec<AvailableCommand>,
1809 #[serde_as(deserialize_as = "DefaultOnError")]
1815 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1816 #[serde(default)]
1817 #[serde(rename = "_meta")]
1818 pub meta: Option<Meta>,
1819}
1820
1821impl AvailableCommandsUpdate {
1822 #[must_use]
1824 pub fn new(available_commands: Vec<AvailableCommand>) -> Self {
1825 Self {
1826 available_commands,
1827 meta: None,
1828 }
1829 }
1830
1831 #[must_use]
1837 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1838 self.meta = meta.into_option();
1839 self
1840 }
1841}
1842
1843#[serde_as]
1845#[skip_serializing_none]
1846#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1847#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1848#[serde(rename_all = "camelCase")]
1849#[non_exhaustive]
1850pub struct AvailableCommand {
1851 pub name: String,
1853 pub description: String,
1855 #[serde_as(deserialize_as = "DefaultOnError")]
1857 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1858 #[serde(default)]
1859 pub input: Option<AvailableCommandInput>,
1860 #[serde_as(deserialize_as = "DefaultOnError")]
1866 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1867 #[serde(default)]
1868 #[serde(rename = "_meta")]
1869 pub meta: Option<Meta>,
1870}
1871
1872impl AvailableCommand {
1873 #[must_use]
1875 pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
1876 Self {
1877 name: name.into(),
1878 description: description.into(),
1879 input: None,
1880 meta: None,
1881 }
1882 }
1883
1884 #[must_use]
1886 pub fn input(mut self, input: impl IntoOption<AvailableCommandInput>) -> Self {
1887 self.input = input.into_option();
1888 self
1889 }
1890
1891 #[must_use]
1897 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1898 self.meta = meta.into_option();
1899 self
1900 }
1901}
1902
1903#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1905#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1906#[serde(untagged, rename_all = "camelCase")]
1907#[non_exhaustive]
1908pub enum AvailableCommandInput {
1909 Unstructured(UnstructuredCommandInput),
1911}
1912
1913#[serde_as]
1915#[skip_serializing_none]
1916#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1917#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1918#[serde(rename_all = "camelCase")]
1919#[non_exhaustive]
1920pub struct UnstructuredCommandInput {
1921 pub hint: String,
1923 #[serde_as(deserialize_as = "DefaultOnError")]
1929 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1930 #[serde(default)]
1931 #[serde(rename = "_meta")]
1932 pub meta: Option<Meta>,
1933}
1934
1935impl UnstructuredCommandInput {
1936 #[must_use]
1938 pub fn new(hint: impl Into<String>) -> Self {
1939 Self {
1940 hint: hint.into(),
1941 meta: None,
1942 }
1943 }
1944
1945 #[must_use]
1951 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1952 self.meta = meta.into_option();
1953 self
1954 }
1955}
1956
1957#[serde_as]
1965#[skip_serializing_none]
1966#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1967#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1968#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = SESSION_REQUEST_PERMISSION_METHOD_NAME)))]
1969#[serde(rename_all = "camelCase")]
1970#[non_exhaustive]
1971pub struct RequestPermissionRequest {
1972 pub session_id: SessionId,
1974 pub tool_call: ToolCallUpdate,
1976 pub options: Vec<PermissionOption>,
1978 #[serde_as(deserialize_as = "DefaultOnError")]
1984 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1985 #[serde(default)]
1986 #[serde(rename = "_meta")]
1987 pub meta: Option<Meta>,
1988}
1989
1990impl RequestPermissionRequest {
1991 #[must_use]
1993 pub fn new(
1994 session_id: impl Into<SessionId>,
1995 tool_call: ToolCallUpdate,
1996 options: Vec<PermissionOption>,
1997 ) -> Self {
1998 Self {
1999 session_id: session_id.into(),
2000 tool_call,
2001 options,
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 PermissionOption {
2026 pub option_id: PermissionOptionId,
2028 pub name: String,
2030 pub kind: PermissionOptionKind,
2032 #[serde_as(deserialize_as = "DefaultOnError")]
2038 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2039 #[serde(default)]
2040 #[serde(rename = "_meta")]
2041 pub meta: Option<Meta>,
2042}
2043
2044impl PermissionOption {
2045 #[must_use]
2047 pub fn new(
2048 option_id: impl Into<PermissionOptionId>,
2049 name: impl Into<String>,
2050 kind: PermissionOptionKind,
2051 ) -> Self {
2052 Self {
2053 option_id: option_id.into(),
2054 name: name.into(),
2055 kind,
2056 meta: None,
2057 }
2058 }
2059
2060 #[must_use]
2066 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2067 self.meta = meta.into_option();
2068 self
2069 }
2070}
2071
2072#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2074#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
2075#[serde(transparent)]
2076#[from(Arc<str>, String, &'static str)]
2077#[non_exhaustive]
2078pub struct PermissionOptionId(pub Arc<str>);
2079
2080impl PermissionOptionId {
2081 #[must_use]
2083 pub fn new(id: impl Into<Arc<str>>) -> Self {
2084 Self(id.into())
2085 }
2086}
2087
2088#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2092#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
2093#[serde(rename_all = "snake_case")]
2094#[non_exhaustive]
2095pub enum PermissionOptionKind {
2096 AllowOnce,
2098 AllowAlways,
2100 RejectOnce,
2102 RejectAlways,
2104}
2105
2106#[serde_as]
2108#[skip_serializing_none]
2109#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2110#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2111#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = SESSION_REQUEST_PERMISSION_METHOD_NAME)))]
2112#[serde(rename_all = "camelCase")]
2113#[non_exhaustive]
2114pub struct RequestPermissionResponse {
2115 pub outcome: RequestPermissionOutcome,
2118 #[serde_as(deserialize_as = "DefaultOnError")]
2124 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2125 #[serde(default)]
2126 #[serde(rename = "_meta")]
2127 pub meta: Option<Meta>,
2128}
2129
2130impl RequestPermissionResponse {
2131 #[must_use]
2133 pub fn new(outcome: RequestPermissionOutcome) -> Self {
2134 Self {
2135 outcome,
2136 meta: None,
2137 }
2138 }
2139
2140 #[must_use]
2146 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2147 self.meta = meta.into_option();
2148 self
2149 }
2150}
2151
2152#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2154#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2155#[serde(tag = "outcome", rename_all = "snake_case")]
2156#[cfg_attr(feature = "schemars", schemars(extend("discriminator" = {"propertyName": "outcome"})))]
2157#[non_exhaustive]
2158pub enum RequestPermissionOutcome {
2159 Cancelled,
2167 #[serde(rename_all = "camelCase")]
2169 Selected(SelectedPermissionOutcome),
2170}
2171
2172#[serde_as]
2174#[skip_serializing_none]
2175#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2176#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2177#[serde(rename_all = "camelCase")]
2178#[non_exhaustive]
2179pub struct SelectedPermissionOutcome {
2180 pub option_id: PermissionOptionId,
2182 #[serde_as(deserialize_as = "DefaultOnError")]
2188 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2189 #[serde(default)]
2190 #[serde(rename = "_meta")]
2191 pub meta: Option<Meta>,
2192}
2193
2194impl SelectedPermissionOutcome {
2195 #[must_use]
2197 pub fn new(option_id: impl Into<PermissionOptionId>) -> Self {
2198 Self {
2199 option_id: option_id.into(),
2200 meta: None,
2201 }
2202 }
2203
2204 #[must_use]
2210 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2211 self.meta = meta.into_option();
2212 self
2213 }
2214}
2215
2216#[serde_as]
2222#[skip_serializing_none]
2223#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2224#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2225#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = FS_WRITE_TEXT_FILE_METHOD_NAME)))]
2226#[serde(rename_all = "camelCase")]
2227#[non_exhaustive]
2228pub struct WriteTextFileRequest {
2229 pub session_id: SessionId,
2231 pub path: PathBuf,
2233 pub content: String,
2235 #[serde_as(deserialize_as = "DefaultOnError")]
2241 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2242 #[serde(default)]
2243 #[serde(rename = "_meta")]
2244 pub meta: Option<Meta>,
2245}
2246
2247impl WriteTextFileRequest {
2248 #[must_use]
2250 pub fn new(
2251 session_id: impl Into<SessionId>,
2252 path: impl Into<PathBuf>,
2253 content: impl Into<String>,
2254 ) -> Self {
2255 Self {
2256 session_id: session_id.into(),
2257 path: path.into(),
2258 content: content.into(),
2259 meta: None,
2260 }
2261 }
2262
2263 #[must_use]
2269 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2270 self.meta = meta.into_option();
2271 self
2272 }
2273}
2274
2275crate::serde_util::default_on_null! {
2276 #[serde_as]
2278 #[skip_serializing_none]
2279 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2280 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
2281 #[serde(rename_all = "camelCase")]
2282 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = FS_WRITE_TEXT_FILE_METHOD_NAME)))]
2283 #[non_exhaustive]
2284 pub struct WriteTextFileResponse {
2285 #[serde_as(deserialize_as = "DefaultOnError")]
2291 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2292 #[serde(default)]
2293 #[serde(rename = "_meta")]
2294 pub meta: Option<Meta>,
2295 }
2296}
2297
2298impl WriteTextFileResponse {
2299 #[must_use]
2301 pub fn new() -> Self {
2302 Self::default()
2303 }
2304
2305 #[must_use]
2311 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2312 self.meta = meta.into_option();
2313 self
2314 }
2315}
2316
2317#[serde_as]
2323#[skip_serializing_none]
2324#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2325#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2326#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = FS_READ_TEXT_FILE_METHOD_NAME)))]
2327#[serde(rename_all = "camelCase")]
2328#[non_exhaustive]
2329pub struct ReadTextFileRequest {
2330 pub session_id: SessionId,
2332 pub path: PathBuf,
2334 #[serde_as(deserialize_as = "DefaultOnError")]
2336 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2337 #[serde(default)]
2338 pub line: Option<u32>,
2339 #[serde_as(deserialize_as = "DefaultOnError")]
2341 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2342 #[serde(default)]
2343 pub limit: Option<u32>,
2344 #[serde_as(deserialize_as = "DefaultOnError")]
2350 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2351 #[serde(default)]
2352 #[serde(rename = "_meta")]
2353 pub meta: Option<Meta>,
2354}
2355
2356impl ReadTextFileRequest {
2357 #[must_use]
2359 pub fn new(session_id: impl Into<SessionId>, path: impl Into<PathBuf>) -> Self {
2360 Self {
2361 session_id: session_id.into(),
2362 path: path.into(),
2363 line: None,
2364 limit: None,
2365 meta: None,
2366 }
2367 }
2368
2369 #[must_use]
2371 pub fn line(mut self, line: impl IntoOption<u32>) -> Self {
2372 self.line = line.into_option();
2373 self
2374 }
2375
2376 #[must_use]
2378 pub fn limit(mut self, limit: impl IntoOption<u32>) -> Self {
2379 self.limit = limit.into_option();
2380 self
2381 }
2382
2383 #[must_use]
2389 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2390 self.meta = meta.into_option();
2391 self
2392 }
2393}
2394
2395#[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_READ_TEXT_FILE_METHOD_NAME)))]
2401#[serde(rename_all = "camelCase")]
2402#[non_exhaustive]
2403pub struct ReadTextFileResponse {
2404 pub content: String,
2406 #[serde_as(deserialize_as = "DefaultOnError")]
2412 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2413 #[serde(default)]
2414 #[serde(rename = "_meta")]
2415 pub meta: Option<Meta>,
2416}
2417
2418impl ReadTextFileResponse {
2419 #[must_use]
2421 pub fn new(content: impl Into<String>) -> Self {
2422 Self {
2423 content: content.into(),
2424 meta: None,
2425 }
2426 }
2427
2428 #[must_use]
2434 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2435 self.meta = meta.into_option();
2436 self
2437 }
2438}
2439
2440#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2444#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
2445#[serde(transparent)]
2446#[from(Arc<str>, String, &'static str)]
2447#[non_exhaustive]
2448pub struct TerminalId(pub Arc<str>);
2449
2450impl TerminalId {
2451 #[must_use]
2453 pub fn new(id: impl Into<Arc<str>>) -> Self {
2454 Self(id.into())
2455 }
2456}
2457
2458#[serde_as]
2460#[skip_serializing_none]
2461#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2462#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2463#[serde(rename_all = "camelCase")]
2464#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_CREATE_METHOD_NAME)))]
2465#[non_exhaustive]
2466pub struct CreateTerminalRequest {
2467 pub session_id: SessionId,
2469 pub command: String,
2471 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
2473 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
2474 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2475 pub args: Vec<String>,
2476 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
2478 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
2479 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2480 pub env: Vec<EnvVariable>,
2481 #[serde_as(deserialize_as = "DefaultOnError")]
2483 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2484 #[serde(default)]
2485 pub cwd: Option<PathBuf>,
2486 #[serde_as(deserialize_as = "DefaultOnError")]
2495 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2496 #[serde(default)]
2497 pub output_byte_limit: Option<u64>,
2498 #[serde_as(deserialize_as = "DefaultOnError")]
2504 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2505 #[serde(default)]
2506 #[serde(rename = "_meta")]
2507 pub meta: Option<Meta>,
2508}
2509
2510impl CreateTerminalRequest {
2511 #[must_use]
2513 pub fn new(session_id: impl Into<SessionId>, command: impl Into<String>) -> Self {
2514 Self {
2515 session_id: session_id.into(),
2516 command: command.into(),
2517 args: Vec::new(),
2518 env: Vec::new(),
2519 cwd: None,
2520 output_byte_limit: None,
2521 meta: None,
2522 }
2523 }
2524
2525 #[must_use]
2527 pub fn args(mut self, args: Vec<String>) -> Self {
2528 self.args = args;
2529 self
2530 }
2531
2532 #[must_use]
2534 pub fn env(mut self, env: Vec<EnvVariable>) -> Self {
2535 self.env = env;
2536 self
2537 }
2538
2539 #[must_use]
2541 pub fn cwd(mut self, cwd: impl IntoOption<PathBuf>) -> Self {
2542 self.cwd = cwd.into_option();
2543 self
2544 }
2545
2546 #[must_use]
2555 pub fn output_byte_limit(mut self, output_byte_limit: impl IntoOption<u64>) -> Self {
2556 self.output_byte_limit = output_byte_limit.into_option();
2557 self
2558 }
2559
2560 #[must_use]
2566 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2567 self.meta = meta.into_option();
2568 self
2569 }
2570}
2571
2572#[serde_as]
2574#[skip_serializing_none]
2575#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2576#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2577#[serde(rename_all = "camelCase")]
2578#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_CREATE_METHOD_NAME)))]
2579#[non_exhaustive]
2580pub struct CreateTerminalResponse {
2581 pub terminal_id: TerminalId,
2583 #[serde_as(deserialize_as = "DefaultOnError")]
2589 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2590 #[serde(default)]
2591 #[serde(rename = "_meta")]
2592 pub meta: Option<Meta>,
2593}
2594
2595impl CreateTerminalResponse {
2596 #[must_use]
2598 pub fn new(terminal_id: impl Into<TerminalId>) -> Self {
2599 Self {
2600 terminal_id: terminal_id.into(),
2601 meta: None,
2602 }
2603 }
2604
2605 #[must_use]
2611 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2612 self.meta = meta.into_option();
2613 self
2614 }
2615}
2616
2617#[serde_as]
2619#[skip_serializing_none]
2620#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2621#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2622#[serde(rename_all = "camelCase")]
2623#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_OUTPUT_METHOD_NAME)))]
2624#[non_exhaustive]
2625pub struct TerminalOutputRequest {
2626 pub session_id: SessionId,
2628 pub terminal_id: TerminalId,
2630 #[serde_as(deserialize_as = "DefaultOnError")]
2636 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2637 #[serde(default)]
2638 #[serde(rename = "_meta")]
2639 pub meta: Option<Meta>,
2640}
2641
2642impl TerminalOutputRequest {
2643 #[must_use]
2645 pub fn new(session_id: impl Into<SessionId>, terminal_id: impl Into<TerminalId>) -> Self {
2646 Self {
2647 session_id: session_id.into(),
2648 terminal_id: terminal_id.into(),
2649 meta: None,
2650 }
2651 }
2652
2653 #[must_use]
2659 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2660 self.meta = meta.into_option();
2661 self
2662 }
2663}
2664
2665#[serde_as]
2667#[skip_serializing_none]
2668#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2669#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2670#[serde(rename_all = "camelCase")]
2671#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_OUTPUT_METHOD_NAME)))]
2672#[non_exhaustive]
2673pub struct TerminalOutputResponse {
2674 pub output: String,
2676 pub truncated: bool,
2678 #[serde_as(deserialize_as = "DefaultOnError")]
2680 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2681 #[serde(default)]
2682 pub exit_status: Option<TerminalExitStatus>,
2683 #[serde_as(deserialize_as = "DefaultOnError")]
2689 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2690 #[serde(default)]
2691 #[serde(rename = "_meta")]
2692 pub meta: Option<Meta>,
2693}
2694
2695impl TerminalOutputResponse {
2696 #[must_use]
2698 pub fn new(output: impl Into<String>, truncated: bool) -> Self {
2699 Self {
2700 output: output.into(),
2701 truncated,
2702 exit_status: None,
2703 meta: None,
2704 }
2705 }
2706
2707 #[must_use]
2709 pub fn exit_status(mut self, exit_status: impl IntoOption<TerminalExitStatus>) -> Self {
2710 self.exit_status = exit_status.into_option();
2711 self
2712 }
2713
2714 #[must_use]
2720 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2721 self.meta = meta.into_option();
2722 self
2723 }
2724}
2725
2726#[serde_as]
2728#[skip_serializing_none]
2729#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2730#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2731#[serde(rename_all = "camelCase")]
2732#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_RELEASE_METHOD_NAME)))]
2733#[non_exhaustive]
2734pub struct ReleaseTerminalRequest {
2735 pub session_id: SessionId,
2737 pub terminal_id: TerminalId,
2739 #[serde_as(deserialize_as = "DefaultOnError")]
2745 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2746 #[serde(default)]
2747 #[serde(rename = "_meta")]
2748 pub meta: Option<Meta>,
2749}
2750
2751impl ReleaseTerminalRequest {
2752 #[must_use]
2754 pub fn new(session_id: impl Into<SessionId>, terminal_id: impl Into<TerminalId>) -> Self {
2755 Self {
2756 session_id: session_id.into(),
2757 terminal_id: terminal_id.into(),
2758 meta: None,
2759 }
2760 }
2761
2762 #[must_use]
2768 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2769 self.meta = meta.into_option();
2770 self
2771 }
2772}
2773
2774crate::serde_util::default_on_null! {
2775 #[serde_as]
2777 #[skip_serializing_none]
2778 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2779 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
2780 #[serde(rename_all = "camelCase")]
2781 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_RELEASE_METHOD_NAME)))]
2782 #[non_exhaustive]
2783 pub struct ReleaseTerminalResponse {
2784 #[serde_as(deserialize_as = "DefaultOnError")]
2790 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2791 #[serde(default)]
2792 #[serde(rename = "_meta")]
2793 pub meta: Option<Meta>,
2794 }
2795}
2796
2797impl ReleaseTerminalResponse {
2798 #[must_use]
2800 pub fn new() -> Self {
2801 Self::default()
2802 }
2803
2804 #[must_use]
2810 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2811 self.meta = meta.into_option();
2812 self
2813 }
2814}
2815
2816#[serde_as]
2818#[skip_serializing_none]
2819#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2820#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2821#[serde(rename_all = "camelCase")]
2822#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_KILL_METHOD_NAME)))]
2823#[non_exhaustive]
2824pub struct KillTerminalRequest {
2825 pub session_id: SessionId,
2827 pub terminal_id: TerminalId,
2829 #[serde_as(deserialize_as = "DefaultOnError")]
2835 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2836 #[serde(default)]
2837 #[serde(rename = "_meta")]
2838 pub meta: Option<Meta>,
2839}
2840
2841impl KillTerminalRequest {
2842 #[must_use]
2844 pub fn new(session_id: impl Into<SessionId>, terminal_id: impl Into<TerminalId>) -> Self {
2845 Self {
2846 session_id: session_id.into(),
2847 terminal_id: terminal_id.into(),
2848 meta: None,
2849 }
2850 }
2851
2852 #[must_use]
2858 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2859 self.meta = meta.into_option();
2860 self
2861 }
2862}
2863
2864crate::serde_util::default_on_null! {
2865 #[serde_as]
2867 #[skip_serializing_none]
2868 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2869 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
2870 #[serde(rename_all = "camelCase")]
2871 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_KILL_METHOD_NAME)))]
2872 #[non_exhaustive]
2873 pub struct KillTerminalResponse {
2874 #[serde_as(deserialize_as = "DefaultOnError")]
2880 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2881 #[serde(default)]
2882 #[serde(rename = "_meta")]
2883 pub meta: Option<Meta>,
2884 }
2885}
2886
2887impl KillTerminalResponse {
2888 #[must_use]
2890 pub fn new() -> Self {
2891 Self::default()
2892 }
2893
2894 #[must_use]
2900 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2901 self.meta = meta.into_option();
2902 self
2903 }
2904}
2905
2906#[serde_as]
2908#[skip_serializing_none]
2909#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2910#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2911#[serde(rename_all = "camelCase")]
2912#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_WAIT_FOR_EXIT_METHOD_NAME)))]
2913#[non_exhaustive]
2914pub struct WaitForTerminalExitRequest {
2915 pub session_id: SessionId,
2917 pub terminal_id: TerminalId,
2919 #[serde_as(deserialize_as = "DefaultOnError")]
2925 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2926 #[serde(default)]
2927 #[serde(rename = "_meta")]
2928 pub meta: Option<Meta>,
2929}
2930
2931impl WaitForTerminalExitRequest {
2932 #[must_use]
2934 pub fn new(session_id: impl Into<SessionId>, terminal_id: impl Into<TerminalId>) -> Self {
2935 Self {
2936 session_id: session_id.into(),
2937 terminal_id: terminal_id.into(),
2938 meta: None,
2939 }
2940 }
2941
2942 #[must_use]
2948 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2949 self.meta = meta.into_option();
2950 self
2951 }
2952}
2953
2954crate::serde_util::default_on_null! {
2955 #[serde_as]
2957 #[skip_serializing_none]
2958 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2959 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
2960 #[serde(rename_all = "camelCase")]
2961 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_WAIT_FOR_EXIT_METHOD_NAME)))]
2962 #[non_exhaustive]
2963 pub struct WaitForTerminalExitResponse {
2964 #[serde(flatten)]
2966 pub exit_status: TerminalExitStatus,
2967 #[serde_as(deserialize_as = "DefaultOnError")]
2973 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2974 #[serde(default)]
2975 #[serde(rename = "_meta")]
2976 pub meta: Option<Meta>,
2977 }
2978}
2979
2980impl WaitForTerminalExitResponse {
2981 #[must_use]
2983 pub fn new(exit_status: TerminalExitStatus) -> Self {
2984 Self {
2985 exit_status,
2986 meta: None,
2987 }
2988 }
2989
2990 #[must_use]
2996 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2997 self.meta = meta.into_option();
2998 self
2999 }
3000}
3001
3002#[serde_as]
3004#[skip_serializing_none]
3005#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3006#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3007#[serde(rename_all = "camelCase")]
3008#[non_exhaustive]
3009pub struct TerminalExitStatus {
3010 #[serde_as(deserialize_as = "DefaultOnError")]
3012 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3013 #[serde(default)]
3014 pub exit_code: Option<u32>,
3015 #[serde_as(deserialize_as = "DefaultOnError")]
3017 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3018 #[serde(default)]
3019 pub signal: Option<String>,
3020 #[serde_as(deserialize_as = "DefaultOnError")]
3026 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3027 #[serde(default)]
3028 #[serde(rename = "_meta")]
3029 pub meta: Option<Meta>,
3030}
3031
3032impl TerminalExitStatus {
3033 #[must_use]
3035 pub fn new() -> Self {
3036 Self::default()
3037 }
3038
3039 #[must_use]
3041 pub fn exit_code(mut self, exit_code: impl IntoOption<u32>) -> Self {
3042 self.exit_code = exit_code.into_option();
3043 self
3044 }
3045
3046 #[must_use]
3048 pub fn signal(mut self, signal: impl IntoOption<String>) -> Self {
3049 self.signal = signal.into_option();
3050 self
3051 }
3052
3053 #[must_use]
3059 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3060 self.meta = meta.into_option();
3061 self
3062 }
3063}
3064
3065#[serde_as]
3074#[skip_serializing_none]
3075#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3076#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3077#[serde(rename_all = "camelCase")]
3078#[non_exhaustive]
3079pub struct ClientCapabilities {
3080 #[serde_as(deserialize_as = "DefaultOnError")]
3083 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3084 #[serde(default)]
3085 pub fs: FileSystemCapabilities,
3086 #[serde_as(deserialize_as = "DefaultOnError")]
3088 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3089 #[serde(default)]
3090 pub terminal: bool,
3091 #[serde_as(deserialize_as = "DefaultOnError")]
3096 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3097 #[serde(default)]
3098 pub session: Option<ClientSessionCapabilities>,
3099 #[cfg(feature = "unstable_subagents")]
3110 #[serde_as(deserialize_as = "DefaultOnError")]
3111 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3112 #[serde(default)]
3113 pub subagents: Option<SubagentCapabilities>,
3114 #[cfg(feature = "unstable_plan_operations")]
3123 #[serde_as(deserialize_as = "DefaultOnError")]
3124 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3125 #[serde(default)]
3126 pub plan: Option<PlanCapabilities>,
3127 #[serde_as(deserialize_as = "DefaultOnError")]
3131 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3132 #[serde(default)]
3133 pub auth: AuthCapabilities,
3134 #[serde_as(deserialize_as = "DefaultOnError")]
3140 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3141 #[serde(default)]
3142 pub elicitation: Option<ElicitationCapabilities>,
3143 #[cfg(feature = "unstable_nes")]
3152 #[serde_as(deserialize_as = "DefaultOnError")]
3153 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3154 #[serde(default)]
3155 pub nes: Option<ClientNesCapabilities>,
3156 #[cfg(feature = "unstable_nes")]
3162 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
3163 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
3164 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3165 pub position_encodings: Vec<PositionEncodingKind>,
3166
3167 #[serde_as(deserialize_as = "DefaultOnError")]
3173 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3174 #[serde(default)]
3175 #[serde(rename = "_meta")]
3176 pub meta: Option<Meta>,
3177}
3178
3179impl ClientCapabilities {
3180 #[must_use]
3182 pub fn new() -> Self {
3183 Self::default()
3184 }
3185
3186 #[must_use]
3189 pub fn fs(mut self, fs: FileSystemCapabilities) -> Self {
3190 self.fs = fs;
3191 self
3192 }
3193
3194 #[must_use]
3196 pub fn terminal(mut self, terminal: bool) -> Self {
3197 self.terminal = terminal;
3198 self
3199 }
3200
3201 #[must_use]
3203 pub fn session(mut self, session: impl IntoOption<ClientSessionCapabilities>) -> Self {
3204 self.session = session.into_option();
3205 self
3206 }
3207
3208 #[cfg(feature = "unstable_subagents")]
3214 #[must_use]
3215 pub fn subagents(mut self, subagents: impl IntoOption<SubagentCapabilities>) -> Self {
3216 self.subagents = subagents.into_option();
3217 self
3218 }
3219
3220 #[cfg(feature = "unstable_plan_operations")]
3229 #[must_use]
3230 pub fn plan(mut self, plan: impl IntoOption<PlanCapabilities>) -> Self {
3231 self.plan = plan.into_option();
3232 self
3233 }
3234
3235 #[must_use]
3239 pub fn auth(mut self, auth: AuthCapabilities) -> Self {
3240 self.auth = auth;
3241 self
3242 }
3243
3244 #[must_use]
3247 pub fn elicitation(mut self, elicitation: impl IntoOption<ElicitationCapabilities>) -> Self {
3248 self.elicitation = elicitation.into_option();
3249 self
3250 }
3251
3252 #[cfg(feature = "unstable_nes")]
3256 #[must_use]
3257 pub fn nes(mut self, nes: impl IntoOption<ClientNesCapabilities>) -> Self {
3258 self.nes = nes.into_option();
3259 self
3260 }
3261
3262 #[cfg(feature = "unstable_nes")]
3266 #[must_use]
3267 pub fn position_encodings(mut self, position_encodings: Vec<PositionEncodingKind>) -> Self {
3268 self.position_encodings = position_encodings;
3269 self
3270 }
3271
3272 #[must_use]
3278 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3279 self.meta = meta.into_option();
3280 self
3281 }
3282}
3283
3284#[cfg(feature = "unstable_subagents")]
3295#[serde_as]
3296#[skip_serializing_none]
3297#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3298#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3299#[non_exhaustive]
3300pub struct SubagentCapabilities {
3301 #[serde_as(deserialize_as = "DefaultOnError")]
3305 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3306 #[serde(default)]
3307 #[serde(rename = "_meta")]
3308 pub meta: Option<Meta>,
3309}
3310
3311#[cfg(feature = "unstable_subagents")]
3312impl SubagentCapabilities {
3313 #[must_use]
3315 pub fn new() -> Self {
3316 Self::default()
3317 }
3318
3319 #[must_use]
3323 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3324 self.meta = meta.into_option();
3325 self
3326 }
3327}
3328
3329#[serde_as]
3331#[skip_serializing_none]
3332#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3333#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3334#[serde(rename_all = "camelCase")]
3335#[non_exhaustive]
3336pub struct ClientSessionCapabilities {
3337 #[cfg(feature = "unstable_session_compaction")]
3344 #[serde_as(deserialize_as = "DefaultOnError")]
3345 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3346 #[serde(default)]
3347 pub compaction: Option<CompactionCapabilities>,
3348 #[serde_as(deserialize_as = "DefaultOnError")]
3353 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3354 #[serde(default)]
3355 pub config_options: Option<SessionConfigOptionsCapabilities>,
3356 #[cfg(feature = "unstable_session_notices")]
3365 #[serde_as(deserialize_as = "DefaultOnError")]
3366 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3367 #[serde(default)]
3368 pub notices: Option<NoticeCapabilities>,
3369 #[serde_as(deserialize_as = "DefaultOnError")]
3375 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3376 #[serde(default)]
3377 #[serde(rename = "_meta")]
3378 pub meta: Option<Meta>,
3379}
3380
3381impl ClientSessionCapabilities {
3382 #[must_use]
3384 pub fn new() -> Self {
3385 Self::default()
3386 }
3387
3388 #[cfg(feature = "unstable_session_compaction")]
3390 #[must_use]
3391 pub fn compaction(mut self, compaction: impl IntoOption<CompactionCapabilities>) -> Self {
3392 self.compaction = compaction.into_option();
3393 self
3394 }
3395
3396 #[must_use]
3401 pub fn config_options(
3402 mut self,
3403 config_options: impl IntoOption<SessionConfigOptionsCapabilities>,
3404 ) -> Self {
3405 self.config_options = config_options.into_option();
3406 self
3407 }
3408
3409 #[cfg(feature = "unstable_session_notices")]
3411 #[must_use]
3412 pub fn notices(mut self, notices: impl IntoOption<NoticeCapabilities>) -> Self {
3413 self.notices = notices.into_option();
3414 self
3415 }
3416
3417 #[must_use]
3423 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3424 self.meta = meta.into_option();
3425 self
3426 }
3427}
3428
3429#[cfg(feature = "unstable_session_compaction")]
3435#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3436#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3437#[serde(rename_all = "camelCase")]
3438#[non_exhaustive]
3439pub struct CompactionCapabilities {}
3440
3441#[cfg(feature = "unstable_session_compaction")]
3442impl CompactionCapabilities {
3443 #[must_use]
3445 pub fn new() -> Self {
3446 Self {}
3447 }
3448}
3449
3450#[cfg(feature = "unstable_session_notices")]
3456#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3457#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3458#[serde(rename_all = "camelCase")]
3459#[non_exhaustive]
3460pub struct NoticeCapabilities {}
3461
3462#[cfg(feature = "unstable_session_notices")]
3463impl NoticeCapabilities {
3464 #[must_use]
3466 pub fn new() -> Self {
3467 Self {}
3468 }
3469}
3470
3471#[serde_as]
3473#[skip_serializing_none]
3474#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3475#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3476#[serde(rename_all = "camelCase")]
3477#[non_exhaustive]
3478pub struct SessionConfigOptionsCapabilities {
3479 #[serde_as(deserialize_as = "DefaultOnError")]
3486 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3487 #[serde(default)]
3488 pub boolean: Option<BooleanConfigOptionCapabilities>,
3489 #[serde_as(deserialize_as = "DefaultOnError")]
3495 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3496 #[serde(default)]
3497 #[serde(rename = "_meta")]
3498 pub meta: Option<Meta>,
3499}
3500
3501impl SessionConfigOptionsCapabilities {
3502 #[must_use]
3504 pub fn new() -> Self {
3505 Self::default()
3506 }
3507
3508 #[must_use]
3515 pub fn boolean(mut self, boolean: impl IntoOption<BooleanConfigOptionCapabilities>) -> Self {
3516 self.boolean = boolean.into_option();
3517 self
3518 }
3519
3520 #[must_use]
3526 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3527 self.meta = meta.into_option();
3528 self
3529 }
3530}
3531
3532#[serde_as]
3536#[skip_serializing_none]
3537#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3538#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3539#[non_exhaustive]
3540pub struct BooleanConfigOptionCapabilities {
3541 #[serde_as(deserialize_as = "DefaultOnError")]
3547 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3548 #[serde(default)]
3549 #[serde(rename = "_meta")]
3550 pub meta: Option<Meta>,
3551}
3552
3553impl BooleanConfigOptionCapabilities {
3554 #[must_use]
3556 pub fn new() -> Self {
3557 Self::default()
3558 }
3559
3560 #[must_use]
3566 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3567 self.meta = meta.into_option();
3568 self
3569 }
3570}
3571
3572#[serde_as]
3578#[skip_serializing_none]
3579#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3580#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3581#[serde(rename_all = "camelCase")]
3582#[non_exhaustive]
3583pub struct AuthCapabilities {
3584 #[serde_as(deserialize_as = "DefaultOnError")]
3590 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3591 #[serde(default)]
3592 pub terminal: bool,
3593 #[serde_as(deserialize_as = "DefaultOnError")]
3599 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3600 #[serde(default)]
3601 #[serde(rename = "_meta")]
3602 pub meta: Option<Meta>,
3603}
3604
3605impl AuthCapabilities {
3606 #[must_use]
3608 pub fn new() -> Self {
3609 Self::default()
3610 }
3611
3612 #[must_use]
3619 pub fn terminal(mut self, terminal: bool) -> Self {
3620 self.terminal = terminal;
3621 self
3622 }
3623
3624 #[must_use]
3630 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3631 self.meta = meta.into_option();
3632 self
3633 }
3634}
3635
3636#[serde_as]
3640#[skip_serializing_none]
3641#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3642#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3643#[serde(rename_all = "camelCase")]
3644#[non_exhaustive]
3645pub struct FileSystemCapabilities {
3646 #[serde_as(deserialize_as = "DefaultOnError")]
3648 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3649 #[serde(default)]
3650 pub read_text_file: bool,
3651 #[serde_as(deserialize_as = "DefaultOnError")]
3653 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3654 #[serde(default)]
3655 pub write_text_file: bool,
3656 #[serde_as(deserialize_as = "DefaultOnError")]
3662 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3663 #[serde(default)]
3664 #[serde(rename = "_meta")]
3665 pub meta: Option<Meta>,
3666}
3667
3668impl FileSystemCapabilities {
3669 #[must_use]
3671 pub fn new() -> Self {
3672 Self::default()
3673 }
3674
3675 #[must_use]
3677 pub fn read_text_file(mut self, read_text_file: bool) -> Self {
3678 self.read_text_file = read_text_file;
3679 self
3680 }
3681
3682 #[must_use]
3684 pub fn write_text_file(mut self, write_text_file: bool) -> Self {
3685 self.write_text_file = write_text_file;
3686 self
3687 }
3688
3689 #[must_use]
3695 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3696 self.meta = meta.into_option();
3697 self
3698 }
3699}
3700
3701#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3707#[non_exhaustive]
3708pub struct ClientMethodNames {
3709 pub session_request_permission: &'static str,
3711 pub session_update: &'static str,
3713 pub fs_write_text_file: &'static str,
3715 pub fs_read_text_file: &'static str,
3717 pub terminal_create: &'static str,
3719 pub terminal_output: &'static str,
3721 pub terminal_release: &'static str,
3723 pub terminal_wait_for_exit: &'static str,
3725 pub terminal_kill: &'static str,
3727 #[cfg(feature = "unstable_mcp_over_acp")]
3729 pub mcp_message: &'static str,
3730 pub elicitation_create: &'static str,
3732 pub elicitation_complete: &'static str,
3734}
3735
3736pub const CLIENT_METHOD_NAMES: ClientMethodNames = ClientMethodNames {
3738 session_update: SESSION_UPDATE_NOTIFICATION,
3739 session_request_permission: SESSION_REQUEST_PERMISSION_METHOD_NAME,
3740 fs_write_text_file: FS_WRITE_TEXT_FILE_METHOD_NAME,
3741 fs_read_text_file: FS_READ_TEXT_FILE_METHOD_NAME,
3742 terminal_create: TERMINAL_CREATE_METHOD_NAME,
3743 terminal_output: TERMINAL_OUTPUT_METHOD_NAME,
3744 terminal_release: TERMINAL_RELEASE_METHOD_NAME,
3745 terminal_wait_for_exit: TERMINAL_WAIT_FOR_EXIT_METHOD_NAME,
3746 terminal_kill: TERMINAL_KILL_METHOD_NAME,
3747 #[cfg(feature = "unstable_mcp_over_acp")]
3748 mcp_message: MCP_MESSAGE_METHOD_NAME,
3749 elicitation_create: ELICITATION_CREATE_METHOD_NAME,
3750 elicitation_complete: ELICITATION_COMPLETE_NOTIFICATION,
3751};
3752
3753pub(crate) const SESSION_UPDATE_NOTIFICATION: &str = "session/update";
3755pub(crate) const SESSION_REQUEST_PERMISSION_METHOD_NAME: &str = "session/request_permission";
3757pub(crate) const FS_WRITE_TEXT_FILE_METHOD_NAME: &str = "fs/write_text_file";
3759pub(crate) const FS_READ_TEXT_FILE_METHOD_NAME: &str = "fs/read_text_file";
3761pub(crate) const TERMINAL_CREATE_METHOD_NAME: &str = "terminal/create";
3763pub(crate) const TERMINAL_OUTPUT_METHOD_NAME: &str = "terminal/output";
3765pub(crate) const TERMINAL_RELEASE_METHOD_NAME: &str = "terminal/release";
3767pub(crate) const TERMINAL_WAIT_FOR_EXIT_METHOD_NAME: &str = "terminal/wait_for_exit";
3769pub(crate) const TERMINAL_KILL_METHOD_NAME: &str = "terminal/kill";
3771pub(crate) const ELICITATION_CREATE_METHOD_NAME: &str = "elicitation/create";
3773pub(crate) const ELICITATION_COMPLETE_NOTIFICATION: &str = "elicitation/complete";
3775
3776#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3783#[derive(Clone, Debug, Serialize, Deserialize)]
3784#[serde(untagged)]
3785#[cfg_attr(feature = "schemars", schemars(inline))]
3786#[non_exhaustive]
3787#[allow(clippy::large_enum_variant)]
3788pub enum AgentRequest {
3789 WriteTextFileRequest(WriteTextFileRequest),
3796 ReadTextFileRequest(ReadTextFileRequest),
3803 RequestPermissionRequest(RequestPermissionRequest),
3814 CreateTerminalRequest(CreateTerminalRequest),
3829 TerminalOutputRequest(TerminalOutputRequest),
3836 ReleaseTerminalRequest(ReleaseTerminalRequest),
3849 WaitForTerminalExitRequest(WaitForTerminalExitRequest),
3853 KillTerminalRequest(KillTerminalRequest),
3866 CreateElicitationRequest(CreateElicitationRequest),
3870 #[cfg(feature = "unstable_mcp_over_acp")]
3876 MessageMcpRequest(MessageMcpRequest),
3877 ExtMethodRequest(ExtRequest),
3885}
3886
3887impl AgentRequest {
3888 #[must_use]
3890 pub fn method(&self) -> &str {
3891 match self {
3892 Self::WriteTextFileRequest(_) => CLIENT_METHOD_NAMES.fs_write_text_file,
3893 Self::ReadTextFileRequest(_) => CLIENT_METHOD_NAMES.fs_read_text_file,
3894 Self::RequestPermissionRequest(_) => CLIENT_METHOD_NAMES.session_request_permission,
3895 Self::CreateTerminalRequest(_) => CLIENT_METHOD_NAMES.terminal_create,
3896 Self::TerminalOutputRequest(_) => CLIENT_METHOD_NAMES.terminal_output,
3897 Self::ReleaseTerminalRequest(_) => CLIENT_METHOD_NAMES.terminal_release,
3898 Self::WaitForTerminalExitRequest(_) => CLIENT_METHOD_NAMES.terminal_wait_for_exit,
3899 Self::KillTerminalRequest(_) => CLIENT_METHOD_NAMES.terminal_kill,
3900 Self::CreateElicitationRequest(_) => CLIENT_METHOD_NAMES.elicitation_create,
3901 #[cfg(feature = "unstable_mcp_over_acp")]
3902 Self::MessageMcpRequest(_) => CLIENT_METHOD_NAMES.mcp_message,
3903 Self::ExtMethodRequest(ext_request) => &ext_request.method,
3904 }
3905 }
3906}
3907
3908#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3915#[derive(Clone, Debug, Serialize, Deserialize)]
3916#[serde(untagged)]
3917#[cfg_attr(feature = "schemars", schemars(inline))]
3918#[non_exhaustive]
3919pub enum ClientResponse {
3920 WriteTextFileResponse(#[serde(default)] WriteTextFileResponse),
3922 ReadTextFileResponse(ReadTextFileResponse),
3924 RequestPermissionResponse(RequestPermissionResponse),
3926 CreateTerminalResponse(CreateTerminalResponse),
3928 TerminalOutputResponse(TerminalOutputResponse),
3930 ReleaseTerminalResponse(#[serde(default)] ReleaseTerminalResponse),
3932 WaitForTerminalExitResponse(WaitForTerminalExitResponse),
3934 KillTerminalResponse(#[serde(default)] KillTerminalResponse),
3936 CreateElicitationResponse(CreateElicitationResponse),
3938 #[cfg(feature = "unstable_mcp_over_acp")]
3940 MessageMcpResponse(MessageMcpResponse),
3941 ExtMethodResponse(ExtResponse),
3943}
3944
3945#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3952#[derive(Clone, Debug, Serialize, Deserialize)]
3953#[serde(untagged)]
3954#[expect(clippy::large_enum_variant)]
3955#[cfg_attr(feature = "schemars", schemars(inline))]
3956#[non_exhaustive]
3957pub enum AgentNotification {
3958 SessionNotification(SessionNotification),
3970 CompleteElicitationNotification(CompleteElicitationNotification),
3974 ExtNotification(ExtNotification),
3982}
3983
3984impl AgentNotification {
3985 #[must_use]
3987 pub fn method(&self) -> &str {
3988 match self {
3989 Self::SessionNotification(_) => CLIENT_METHOD_NAMES.session_update,
3990 Self::CompleteElicitationNotification(_) => CLIENT_METHOD_NAMES.elicitation_complete,
3991 Self::ExtNotification(ext_notification) => &ext_notification.method,
3992 }
3993 }
3994}
3995
3996#[cfg(test)]
3997mod tests {
3998 use super::*;
3999
4000 #[cfg(feature = "unstable_session_notices")]
4001 #[test]
4002 fn notice_preserves_wire_shape_nullable_fields_and_open_severity() {
4003 use serde_json::json;
4004
4005 let mut meta = Meta::new();
4006 meta.insert("source".into(), json!("fallback"));
4007 assert_eq!(
4008 serde_json::to_value(SessionUpdate::Notice(
4009 Notice::new(NoticeSeverity::Warning, "MCP server unavailable")
4010 .description("Continuing without it.")
4011 .meta(meta),
4012 ))
4013 .unwrap(),
4014 json!({
4015 "sessionUpdate": "notice",
4016 "severity": "warning",
4017 "title": "MCP server unavailable",
4018 "description": "Continuing without it.",
4019 "_meta": { "source": "fallback" }
4020 })
4021 );
4022
4023 assert_eq!(
4024 serde_json::to_value(SessionUpdate::Notice(Notice::new(
4025 NoticeSeverity::Info,
4026 "Indexing workspace",
4027 )))
4028 .unwrap(),
4029 json!({
4030 "sessionUpdate": "notice",
4031 "severity": "info",
4032 "title": "Indexing workspace"
4033 })
4034 );
4035
4036 let SessionUpdate::Notice(notice) = serde_json::from_value(json!({
4037 "sessionUpdate": "notice",
4038 "severity": "critical",
4039 "title": "Provider degraded",
4040 "description": null,
4041 "_meta": null
4042 }))
4043 .unwrap() else {
4044 panic!("expected notice");
4045 };
4046
4047 assert_eq!(
4048 notice.severity,
4049 NoticeSeverity::Other("critical".to_string())
4050 );
4051 assert_eq!(notice.description, None);
4052 assert_eq!(notice.meta, None);
4053 assert_eq!(
4054 serde_json::to_value(SessionUpdate::Notice(notice)).unwrap(),
4055 json!({
4056 "sessionUpdate": "notice",
4057 "severity": "critical",
4058 "title": "Provider degraded"
4059 })
4060 );
4061 }
4062
4063 #[cfg(feature = "unstable_session_notices")]
4064 #[test]
4065 fn notice_requires_non_null_severity_and_title() {
4066 use serde_json::json;
4067
4068 for malformed in [
4069 json!({
4070 "sessionUpdate": "notice",
4071 "severity": "warning"
4072 }),
4073 json!({
4074 "sessionUpdate": "notice",
4075 "severity": "warning",
4076 "title": null
4077 }),
4078 json!({
4079 "sessionUpdate": "notice",
4080 "title": "MCP server unavailable"
4081 }),
4082 json!({
4083 "sessionUpdate": "notice",
4084 "severity": null,
4085 "title": "MCP server unavailable"
4086 }),
4087 ] {
4088 assert!(serde_json::from_value::<SessionUpdate>(malformed).is_err());
4089 }
4090 }
4091
4092 #[cfg(feature = "unstable_session_notices")]
4093 #[test]
4094 fn notice_capability_advertises_support_only_when_present() {
4095 use serde_json::json;
4096
4097 let capabilities = ClientCapabilities::new()
4098 .session(ClientSessionCapabilities::new().notices(NoticeCapabilities::new()));
4099 let value = serde_json::to_value(&capabilities).unwrap();
4100 assert_eq!(value["session"], json!({ "notices": {} }));
4101 assert_eq!(
4102 serde_json::from_value::<ClientCapabilities>(value).unwrap(),
4103 capabilities
4104 );
4105
4106 for unsupported in [
4107 json!({}),
4108 json!({ "session": null }),
4109 json!({ "session": {} }),
4110 json!({ "session": { "notices": null } }),
4111 json!({ "session": { "notices": false } }),
4112 json!({ "session": { "notices": true } }),
4113 json!({ "session": { "notices": "supported" } }),
4114 ] {
4115 let capabilities: ClientCapabilities = serde_json::from_value(unsupported).unwrap();
4116 assert!(
4117 capabilities
4118 .session
4119 .and_then(|session| session.notices)
4120 .is_none()
4121 );
4122 }
4123
4124 assert_eq!(
4125 serde_json::to_value(
4126 ClientSessionCapabilities::new()
4127 .notices(NoticeCapabilities::new())
4128 .notices(None)
4129 )
4130 .unwrap(),
4131 json!({})
4132 );
4133 }
4134
4135 #[cfg(not(feature = "unstable_session_notices"))]
4136 #[test]
4137 fn unsupported_notice_capability_is_ignored() {
4138 use serde_json::json;
4139
4140 let capabilities: ClientSessionCapabilities =
4141 serde_json::from_value(json!({ "notices": {} })).unwrap();
4142 assert_eq!(serde_json::to_value(capabilities).unwrap(), json!({}));
4143 }
4144
4145 #[cfg(not(feature = "unstable_session_notices"))]
4146 #[test]
4147 fn unsupported_notice_is_rejected_by_closed_update_union() {
4148 use serde_json::json;
4149
4150 assert!(
4151 serde_json::from_value::<SessionUpdate>(json!({
4152 "sessionUpdate": "notice",
4153 "severity": "warning",
4154 "title": "MCP server unavailable"
4155 }))
4156 .is_err()
4157 );
4158 }
4159
4160 #[cfg(feature = "unstable_session_compaction")]
4161 #[test]
4162 fn compaction_updates_preserve_patch_and_open_status_semantics() {
4163 use serde_json::json;
4164
4165 assert_eq!(
4166 serde_json::to_value(SessionUpdate::CompactionUpdate(CompactionUpdate::new(
4167 "cmp_001",
4168 CompactionStatus::InProgress,
4169 )))
4170 .unwrap(),
4171 json!({
4172 "sessionUpdate": "compaction_update",
4173 "compactionId": "cmp_001",
4174 "status": "in_progress"
4175 })
4176 );
4177
4178 let SessionUpdate::CompactionUpdate(update) = serde_json::from_value(json!({
4179 "sessionUpdate": "compaction_update",
4180 "compactionId": "cmp_001",
4181 "status": "paused",
4182 "summary": null,
4183 "error": "waiting"
4184 }))
4185 .unwrap() else {
4186 panic!("expected compaction update");
4187 };
4188 assert_eq!(update.status, CompactionStatus::Other("paused".into()));
4189 assert!(update.summary.is_null());
4190 assert_eq!(update.error.value().map(String::as_str), Some("waiting"));
4191 assert!(update.meta.is_undefined());
4192 }
4193
4194 #[cfg(feature = "unstable_session_compaction")]
4195 #[test]
4196 fn compaction_chunk_and_v1_capability_serialize() {
4197 use serde_json::json;
4198
4199 assert_eq!(
4200 serde_json::to_value(SessionUpdate::CompactionSummaryChunk(
4201 CompactionSummaryChunk::new(
4202 "cmp_001",
4203 ContentBlock::Text(crate::v1::TextContent::new("retained")),
4204 ),
4205 ))
4206 .unwrap(),
4207 json!({
4208 "sessionUpdate": "compaction_summary_chunk",
4209 "compactionId": "cmp_001",
4210 "content": { "type": "text", "text": "retained" }
4211 })
4212 );
4213 assert_eq!(
4214 serde_json::to_value(
4215 ClientSessionCapabilities::new().compaction(CompactionCapabilities::new())
4216 )
4217 .unwrap(),
4218 json!({ "compaction": {} })
4219 );
4220 let absent: ClientSessionCapabilities = serde_json::from_value(json!({})).unwrap();
4221 let null: ClientSessionCapabilities =
4222 serde_json::from_value(json!({ "compaction": null })).unwrap();
4223 assert!(absent.compaction.is_none());
4224 assert!(null.compaction.is_none());
4225 }
4226
4227 #[cfg(feature = "unstable_subagents")]
4228 #[test]
4229 fn test_subagent_updates_serialization() {
4230 use serde_json::json;
4231
4232 let announced = SessionUpdate::SubagentUpdate(SubagentUpdate::new("sess_child_1"));
4233 assert_eq!(
4234 serde_json::to_value(&announced).unwrap(),
4235 json!({
4236 "sessionUpdate": "subagent_update",
4237 "sessionId": "sess_child_1"
4238 })
4239 );
4240 assert_eq!(
4241 serde_json::from_value::<SessionUpdate>(serde_json::to_value(&announced).unwrap())
4242 .unwrap(),
4243 announced
4244 );
4245 let capable = SubagentUpdate::new("sess_child_1").capabilities(
4246 SubagentSessionCapabilities::new().cancel(SessionCancelCapabilities::new()),
4247 );
4248 assert_eq!(
4249 serde_json::to_value(capable).unwrap(),
4250 json!({
4251 "sessionId": "sess_child_1",
4252 "capabilities": {"cancel": {}}
4253 })
4254 );
4255 let minimal: SubagentUpdate =
4256 serde_json::from_value(json!({ "sessionId": "sess_child_3" })).unwrap();
4257 assert!(minimal.capabilities.is_undefined());
4258 assert!(minimal.state.is_undefined());
4259
4260 let nulls: SubagentUpdate = serde_json::from_value(json!({
4261 "sessionId": "sess_child_3",
4262 "capabilities": null,
4263 "state": null
4264 }))
4265 .unwrap();
4266 assert!(nulls.capabilities.is_null());
4267 assert!(nulls.state.is_null());
4268 assert_eq!(
4269 serde_json::to_value(nulls).unwrap(),
4270 json!({"sessionId": "sess_child_3", "capabilities": null, "state": null})
4271 );
4272 }
4273
4274 #[cfg(feature = "unstable_subagents")]
4275 #[test]
4276 fn test_subagent_display_metadata() {
4277 use serde_json::json;
4278
4279 let update = SubagentUpdate::new("child")
4280 .title("Test investigator".to_string())
4281 .description("Investigates platform-specific test failures.".to_string());
4282 let wire = json!({
4283 "sessionId": "child",
4284 "title": "Test investigator",
4285 "description": "Investigates platform-specific test failures."
4286 });
4287 assert_eq!(serde_json::to_value(&update).unwrap(), wire);
4288 assert_eq!(
4289 serde_json::from_value::<SubagentUpdate>(wire).unwrap(),
4290 update
4291 );
4292
4293 let minimal = SubagentUpdate::new("child");
4294 assert_eq!(
4295 serde_json::to_value(&minimal).unwrap(),
4296 json!({"sessionId": "child"})
4297 );
4298 let cleared: SubagentUpdate = serde_json::from_value(json!({
4299 "sessionId": "child", "title": null, "description": null
4300 }))
4301 .unwrap();
4302 assert!(cleared.title.is_null());
4303 assert!(cleared.description.is_null());
4304 assert_eq!(
4305 serde_json::to_value(cleared).unwrap(),
4306 json!({"sessionId": "child", "title": null, "description": null})
4307 );
4308 let invalid: SubagentUpdate = serde_json::from_value(json!({
4309 "sessionId": "child", "title": false, "description": false
4310 }))
4311 .unwrap();
4312 assert_eq!(invalid, minimal);
4313
4314 let title_only: SubagentUpdate = serde_json::from_value(json!({
4315 "sessionId": "child", "title": "Updated title"
4316 }))
4317 .unwrap();
4318 assert_eq!(
4319 title_only.title.value().map(String::as_str),
4320 Some("Updated title")
4321 );
4322 assert!(title_only.description.is_undefined());
4323 }
4324
4325 #[cfg(feature = "unstable_subagents")]
4326 #[test]
4327 fn subagent_patch_fields_preserve_omitted_null_and_concrete() {
4328 use serde_json::json;
4329
4330 let omitted = SubagentUpdate::new("child");
4331 for field in ["title", "description", "capabilities", "state", "_meta"] {
4332 let wire = json!({"sessionId": "child", field: null});
4333 let decoded: SubagentUpdate = serde_json::from_value(wire.clone()).unwrap();
4334 assert_eq!(serde_json::to_value(decoded).unwrap(), wire, "{field}");
4335 let malformed = json!({"sessionId": "child", field: false});
4336 let decoded: SubagentUpdate = serde_json::from_value(malformed).unwrap();
4337 assert_eq!(decoded, omitted, "{field}");
4338 }
4339 let concrete = json!({
4340 "sessionId": "child", "title": "Investigator",
4341 "description": "Tests", "capabilities": {"cancel": {}},
4342 "state": {"state": "running"}, "_meta": {"source": "parent"}
4343 });
4344 let decoded: SubagentUpdate = serde_json::from_value(concrete.clone()).unwrap();
4345 assert!(decoded.title.value().is_some());
4346 assert!(decoded.description.value().is_some());
4347 assert!(decoded.capabilities.value().is_some());
4348 assert!(decoded.state.value().is_some());
4349 assert!(decoded.meta.value().is_some());
4350 assert_eq!(serde_json::to_value(decoded).unwrap(), concrete);
4351 let cleared = SubagentUpdate::new("child")
4352 .title(None)
4353 .description(None)
4354 .capabilities(None)
4355 .state(None)
4356 .meta(None);
4357 assert_eq!(
4358 serde_json::to_value(cleared).unwrap(),
4359 json!({
4360 "sessionId": "child", "title": null, "description": null,
4361 "capabilities": null, "state": null, "_meta": null
4362 })
4363 );
4364 }
4365
4366 #[cfg(feature = "unstable_subagents")]
4367 #[test]
4368 fn test_subagent_notification_keeps_parent_and_child_ids_nested() {
4369 use serde_json::json;
4370
4371 let notification = SessionNotification::new(
4372 "parent",
4373 SessionUpdate::SubagentUpdate(SubagentUpdate::new("child")),
4374 );
4375 let wire = json!({
4376 "sessionId": "parent",
4377 "update": {
4378 "sessionUpdate": "subagent_update",
4379 "sessionId": "child"
4380 }
4381 });
4382 assert_eq!(serde_json::to_value(¬ification).unwrap(), wire);
4383 assert_eq!(
4384 serde_json::from_value::<SessionNotification>(wire).unwrap(),
4385 notification
4386 );
4387 for malformed in [
4389 json!({"sessionId": "parent", "update": {"sessionUpdate": "subagent_update"}}),
4390 json!({"update": {"sessionUpdate": "subagent_update", "sessionId": "child"}}),
4391 ] {
4392 assert!(serde_json::from_value::<SessionNotification>(malformed).is_err());
4393 }
4394 }
4395
4396 #[cfg(feature = "unstable_subagents")]
4397 #[test]
4398 fn test_subagent_states_round_trip() {
4399 use serde_json::json;
4400
4401 for (wire_state, expected) in [
4402 (
4403 json!({"state": "running"}),
4404 StateUpdate::Running(RunningStateUpdate::new()),
4405 ),
4406 (
4407 json!({"state": "idle", "stopReason": "end_turn"}),
4408 StateUpdate::Idle(IdleStateUpdate::new().stop_reason(StopReason::EndTurn)),
4409 ),
4410 (
4411 json!({"state": "requires_action"}),
4412 StateUpdate::RequiresAction(RequiresActionStateUpdate::new()),
4413 ),
4414 (
4415 json!({"state": "unknown"}),
4416 StateUpdate::Unknown(UnknownStateUpdate::new()),
4417 ),
4418 (
4419 json!({"state": "_waiting", "detail": {"ticket": 7}}),
4420 StateUpdate::Other(OtherStateUpdate::new(
4421 "_waiting",
4422 [("detail".into(), json!({"ticket": 7}))].into(),
4423 )),
4424 ),
4425 (
4426 json!({"state": "paused", "retryAt": 42}),
4427 StateUpdate::Other(OtherStateUpdate::new(
4428 "paused",
4429 [("retryAt".into(), json!(42))].into(),
4430 )),
4431 ),
4432 ] {
4433 let wire = json!({
4434 "sessionUpdate": "subagent_update",
4435 "sessionId": "sess_child",
4436 "state": wire_state
4437 });
4438 let parsed: SessionUpdate = serde_json::from_value(wire.clone()).unwrap();
4439 let SessionUpdate::SubagentUpdate(update) = &parsed else {
4440 panic!("expected subagent update");
4441 };
4442 assert_eq!(update.state.value(), Some(&expected));
4443 assert_eq!(serde_json::to_value(parsed).unwrap(), wire);
4444 }
4445
4446 for reason in [
4447 "end_turn",
4448 "max_tokens",
4449 "max_turn_requests",
4450 "refusal",
4451 "cancelled",
4452 ] {
4453 let state: StateUpdate = serde_json::from_value(json!({
4454 "state": "idle", "stopReason": reason
4455 }))
4456 .unwrap();
4457 assert_eq!(serde_json::to_value(state).unwrap()["stopReason"], reason);
4458 }
4459
4460 for state in [
4463 StateUpdate::Running(RunningStateUpdate::new()),
4464 StateUpdate::RequiresAction(RequiresActionStateUpdate::new()),
4465 StateUpdate::Unknown(UnknownStateUpdate::new()),
4466 StateUpdate::Running(RunningStateUpdate::new()),
4467 StateUpdate::Idle(IdleStateUpdate::new().stop_reason(StopReason::EndTurn)),
4468 StateUpdate::Unknown(UnknownStateUpdate::new()),
4469 StateUpdate::Running(RunningStateUpdate::new()),
4470 ] {
4471 let update = SubagentUpdate::new("sess_child").state(state.clone());
4472 let wire = serde_json::to_value(&update).unwrap();
4473 assert_eq!(wire["sessionId"], "sess_child");
4474 assert_eq!(
4475 serde_json::from_value::<SubagentUpdate>(wire)
4476 .unwrap()
4477 .state,
4478 MaybeUndefined::Value(state)
4479 );
4480 }
4481 assert_eq!(
4482 serde_json::to_value(
4483 SubagentUpdate::new("sess_child").state(StateUpdate::Idle(IdleStateUpdate::new()))
4484 )
4485 .unwrap()["state"],
4486 json!({"state": "idle"})
4487 );
4488 }
4489
4490 #[cfg(feature = "unstable_subagents")]
4491 #[test]
4492 fn test_subagent_unknown_activity_metadata_and_capabilities() {
4493 use serde_json::json;
4494
4495 let update = SubagentUpdate::new("child").state(StateUpdate::Unknown(
4496 UnknownStateUpdate::new().meta(
4497 [("source".into(), json!("worker"))]
4498 .into_iter()
4499 .collect::<Meta>(),
4500 ),
4501 ));
4502 let wire = json!({
4503 "sessionId": "child",
4504 "state": {
4505 "state": "unknown",
4506 "_meta": { "source": "worker" }
4507 }
4508 });
4509 assert_eq!(serde_json::to_value(&update).unwrap(), wire);
4510 assert_eq!(
4511 serde_json::from_value::<SubagentUpdate>(wire).unwrap(),
4512 update
4513 );
4514 assert!(update.capabilities.is_undefined());
4516
4517 for meta in [json!(null), json!(false)] {
4518 let state: StateUpdate = serde_json::from_value(json!({
4519 "state": "unknown",
4520 "_meta": meta
4521 }))
4522 .unwrap();
4523 assert_eq!(state, StateUpdate::Unknown(UnknownStateUpdate::new()));
4524 assert_eq!(
4525 serde_json::to_value(state).unwrap(),
4526 json!({"state": "unknown"})
4527 );
4528 }
4529 assert!(serde_json::from_value::<OtherStateUpdate>(json!({"state": "unknown"})).is_err());
4531 }
4532
4533 #[cfg(feature = "unstable_subagents")]
4534 #[test]
4535 fn test_subagent_malformed_optional_state() {
4536 use serde_json::json;
4537
4538 for malformed in [json!("running"), json!({}), json!({"state": 4})] {
4539 assert!(serde_json::from_value::<StateUpdate>(malformed.clone()).is_err());
4540 let update: SubagentUpdate = serde_json::from_value(json!({
4541 "sessionId": "child", "state": malformed
4542 }))
4543 .unwrap();
4544 assert!(update.state.is_undefined());
4545 }
4546 let bad_reason: StateUpdate =
4547 serde_json::from_value(json!({"state": "idle", "stopReason": "not_a_reason"})).unwrap();
4548 assert_eq!(bad_reason, StateUpdate::Idle(IdleStateUpdate::new()));
4549 let null_reason: StateUpdate =
4550 serde_json::from_value(json!({"state": "idle", "stopReason": null})).unwrap();
4551 assert_eq!(null_reason, StateUpdate::Idle(IdleStateUpdate::new()));
4552 }
4553
4554 #[cfg(all(feature = "unstable_subagents", feature = "unstable_protocol_v2"))]
4555 #[test]
4556 fn test_subagent_state_v2_wire_parity() {
4557 use serde_json::json;
4558
4559 for wire in [
4560 json!({"state": "running"}),
4561 json!({"state": "idle", "stopReason": "cancelled"}),
4562 json!({"state": "requires_action"}),
4563 json!({"state": "unknown"}),
4564 json!({"state": "unknown", "_meta": {"source": "worker"}}),
4565 json!({"state": "_waiting", "detail": {"ticket": 7}}),
4566 ] {
4567 let v1: StateUpdate = serde_json::from_value(wire.clone()).unwrap();
4568 let v2: crate::v2::StateUpdate = serde_json::from_value(wire.clone()).unwrap();
4569 assert_eq!(serde_json::to_value(v1).unwrap(), wire);
4570 assert_eq!(serde_json::to_value(v2).unwrap(), wire);
4571 }
4572 }
4573
4574 #[cfg(feature = "unstable_subagents")]
4575 #[test]
4576 fn test_subagent_capability_semantics() {
4577 use serde_json::json;
4578
4579 let capabilities =
4580 serde_json::to_value(ClientCapabilities::new().subagents(SubagentCapabilities::new()))
4581 .unwrap();
4582 assert_eq!(capabilities["subagents"], json!({}));
4583
4584 let omitted: ClientCapabilities = serde_json::from_value(json!({})).unwrap();
4585 assert!(omitted.subagents.is_none());
4586
4587 let null: ClientCapabilities =
4588 serde_json::from_value(json!({ "subagents": null })).unwrap();
4589 assert!(null.subagents.is_none());
4590 assert_eq!(null, omitted);
4591 let serialized = serde_json::to_value(null).unwrap();
4592 assert_eq!(serialized, serde_json::to_value(omitted).unwrap());
4593 assert!(serialized.get("subagents").is_none());
4594
4595 for wire in [
4596 json!({}),
4597 json!({"cancel": null}),
4598 json!({"cancel": true}),
4599 json!({"cancel": false}),
4600 ] {
4601 let child: SubagentSessionCapabilities = serde_json::from_value(wire).unwrap();
4602 assert!(child.cancel.is_none());
4603 assert_eq!(serde_json::to_value(child).unwrap(), json!({}));
4604 }
4605 let enabled = SubagentSessionCapabilities::new().cancel(SessionCancelCapabilities::new());
4606 assert_eq!(
4607 serde_json::to_value(&enabled).unwrap(),
4608 json!({"cancel": {}})
4609 );
4610 assert_eq!(
4611 serde_json::from_value::<SubagentSessionCapabilities>(json!({"cancel": {}})).unwrap(),
4612 enabled
4613 );
4614 let meta: Meta = [("source".into(), json!("worker"))].into_iter().collect();
4615 let with_meta =
4616 SubagentSessionCapabilities::new().cancel(SessionCancelCapabilities::new().meta(meta));
4617 let wire = json!({"cancel": {"_meta": {"source": "worker"}}});
4618 assert_eq!(serde_json::to_value(&with_meta).unwrap(), wire);
4619 assert_eq!(
4620 serde_json::from_value::<SubagentSessionCapabilities>(wire).unwrap(),
4621 with_meta
4622 );
4623 let removed = SubagentSessionCapabilities::new();
4625 assert_eq!(serde_json::to_value(&removed).unwrap(), json!({}));
4626 assert!(removed.cancel.is_none());
4627 assert!(enabled.cancel.is_some());
4628 assert!(enabled.cancel(None).cancel.is_none());
4629 let replacement = SubagentUpdate::new("child").capabilities(removed);
4630 assert_eq!(
4631 serde_json::to_value(replacement).unwrap(),
4632 json!({"sessionId": "child", "capabilities": {}})
4633 );
4634 }
4635
4636 #[cfg(all(feature = "unstable_subagents", feature = "schemars"))]
4637 #[test]
4638 fn test_subagent_capability_schema_is_optional_and_nullable() {
4639 use serde_json::json;
4640
4641 let schema = serde_json::to_value(schemars::schema_for!(ClientCapabilities)).unwrap();
4642 let variants = schema["properties"]["subagents"]["anyOf"]
4643 .as_array()
4644 .expect("subagents must allow the capability object or null");
4645 assert!(variants.contains(&json!({"type": "null"})));
4646 assert!(
4647 !schema["required"]
4648 .as_array()
4649 .is_some_and(|required| required.contains(&json!("subagents")))
4650 );
4651 let child =
4652 serde_json::to_value(schemars::schema_for!(SubagentSessionCapabilities)).unwrap();
4653 let variants = child["properties"]["cancel"]["anyOf"]
4654 .as_array()
4655 .expect("cancel must allow the capability object or null");
4656 assert!(variants.contains(&json!({"type": "null"})));
4657 assert!(
4658 variants
4659 .iter()
4660 .any(|variant| variant["type"] == "object" || variant.get("$ref").is_some())
4661 );
4662 assert!(!variants.iter().any(|variant| variant["type"] == "boolean"));
4663 let cancel =
4664 serde_json::to_value(schemars::schema_for!(SessionCancelCapabilities)).unwrap();
4665 assert_eq!(cancel["type"], "object");
4666 assert!(
4667 !child["required"]
4668 .as_array()
4669 .is_some_and(|required| required.contains(&json!("cancel")))
4670 );
4671 }
4672
4673 #[test]
4674 fn test_elicitation_capability_semantics() {
4675 use serde_json::json;
4676
4677 let unsupported: ClientCapabilities = serde_json::from_value(json!({})).unwrap();
4678 assert!(unsupported.elicitation.is_none());
4679
4680 let null: ClientCapabilities =
4681 serde_json::from_value(json!({ "elicitation": null })).unwrap();
4682 assert!(null.elicitation.is_none());
4683
4684 let malformed: ClientCapabilities =
4685 serde_json::from_value(json!({ "elicitation": false })).unwrap();
4686 assert!(malformed.elicitation.is_none());
4687
4688 let empty: ClientCapabilities =
4689 serde_json::from_value(json!({ "elicitation": {} })).unwrap();
4690 let empty = empty.elicitation.expect("present capability");
4691 assert!(!empty.supports_form());
4692 assert!(!empty.supports_url());
4693
4694 let form_only: ClientCapabilities = serde_json::from_value(json!({
4695 "elicitation": { "form": {} }
4696 }))
4697 .unwrap();
4698 let form_only = form_only.elicitation.expect("advertised capability");
4699 assert!(form_only.supports_form());
4700 assert!(!form_only.supports_url());
4701
4702 let url_only: ClientCapabilities = serde_json::from_value(json!({
4703 "elicitation": { "url": {} }
4704 }))
4705 .unwrap();
4706 let url_only = url_only.elicitation.expect("advertised capability");
4707 assert!(!url_only.supports_form());
4708 assert!(url_only.supports_url());
4709
4710 let both: ClientCapabilities = serde_json::from_value(json!({
4711 "elicitation": { "form": {}, "url": {} }
4712 }))
4713 .unwrap();
4714 let both = both.elicitation.expect("advertised capability");
4715 assert!(both.supports_form());
4716 assert!(both.supports_url());
4717 }
4718
4719 #[test]
4720 fn test_elicitation_method_routing_and_envelopes() {
4721 use serde_json::json;
4722
4723 assert_eq!(CLIENT_METHOD_NAMES.elicitation_create, "elicitation/create");
4724 assert_eq!(
4725 CLIENT_METHOD_NAMES.elicitation_complete,
4726 "elicitation/complete"
4727 );
4728
4729 let request = AgentRequest::CreateElicitationRequest(CreateElicitationRequest::new(
4730 crate::v1::ElicitationFormMode::new(
4731 crate::v1::ElicitationSessionScope::new("sess_1"),
4732 crate::v1::ElicitationSchema::new(),
4733 ),
4734 "Choose a value",
4735 ));
4736 assert_eq!(request.method(), "elicitation/create");
4737 let method = Arc::from(request.method());
4738 let request = crate::v1::JsonRpcMessage::wrap(crate::v1::Request {
4739 id: crate::v1::RequestId::Number(7),
4740 method,
4741 params: Some(request),
4742 });
4743 assert_eq!(
4744 serde_json::to_value(request).unwrap(),
4745 json!({
4746 "jsonrpc": "2.0",
4747 "id": 7,
4748 "method": "elicitation/create",
4749 "params": {
4750 "mode": "form",
4751 "sessionId": "sess_1",
4752 "message": "Choose a value",
4753 "requestedSchema": { "type": "object", "properties": {} }
4754 }
4755 })
4756 );
4757
4758 let notification = AgentNotification::CompleteElicitationNotification(
4759 CompleteElicitationNotification::new("elic_1"),
4760 );
4761 assert_eq!(notification.method(), "elicitation/complete");
4762 let method = Arc::from(notification.method());
4763 let notification = crate::v1::JsonRpcMessage::wrap(crate::v1::Notification {
4764 method,
4765 params: Some(notification),
4766 });
4767 assert_eq!(
4768 serde_json::to_value(notification).unwrap(),
4769 json!({
4770 "jsonrpc": "2.0",
4771 "method": "elicitation/complete",
4772 "params": { "elicitationId": "elic_1" }
4773 })
4774 );
4775 }
4776
4777 #[test]
4778 fn test_client_capabilities_default_on_malformed_values() {
4779 use serde_json::json;
4780
4781 let capabilities: ClientCapabilities = serde_json::from_value(json!({
4782 "fs": {
4783 "readTextFile": "yes",
4784 "writeTextFile": true
4785 },
4786 "terminal": {}
4787 }))
4788 .unwrap();
4789
4790 assert!(!capabilities.fs.read_text_file);
4791 assert!(capabilities.fs.write_text_file);
4792 assert!(!capabilities.terminal);
4793
4794 let capabilities: ClientCapabilities = serde_json::from_value(json!({
4795 "fs": false
4796 }))
4797 .unwrap();
4798 assert_eq!(capabilities.fs, FileSystemCapabilities::default());
4799
4800 {
4801 let capabilities: ClientCapabilities = serde_json::from_value(json!({
4802 "auth": false
4803 }))
4804 .unwrap();
4805 assert_eq!(capabilities.auth, AuthCapabilities::default());
4806
4807 let capabilities: AuthCapabilities = serde_json::from_value(json!({
4808 "terminal": {}
4809 }))
4810 .unwrap();
4811 assert!(!capabilities.terminal);
4812 }
4813 }
4814
4815 #[test]
4816 fn test_serialization_behavior() {
4817 use serde_json::json;
4818
4819 assert_eq!(
4820 serde_json::from_value::<SessionInfoUpdate>(json!({})).unwrap(),
4821 SessionInfoUpdate {
4822 title: MaybeUndefined::Undefined,
4823 updated_at: MaybeUndefined::Undefined,
4824 meta: None
4825 }
4826 );
4827 assert_eq!(
4828 serde_json::from_value::<SessionInfoUpdate>(json!({"title": null, "updatedAt": null}))
4829 .unwrap(),
4830 SessionInfoUpdate {
4831 title: MaybeUndefined::Null,
4832 updated_at: MaybeUndefined::Null,
4833 meta: None
4834 }
4835 );
4836 assert_eq!(
4837 serde_json::from_value::<SessionInfoUpdate>(
4838 json!({"title": "title", "updatedAt": "timestamp"})
4839 )
4840 .unwrap(),
4841 SessionInfoUpdate {
4842 title: MaybeUndefined::Value("title".to_string()),
4843 updated_at: MaybeUndefined::Value("timestamp".to_string()),
4844 meta: None
4845 }
4846 );
4847
4848 assert_eq!(
4849 serde_json::to_value(SessionInfoUpdate::new()).unwrap(),
4850 json!({})
4851 );
4852 assert_eq!(
4853 serde_json::to_value(SessionInfoUpdate::new().title("title")).unwrap(),
4854 json!({"title": "title"})
4855 );
4856 assert_eq!(
4857 serde_json::to_value(SessionInfoUpdate::new().title(None)).unwrap(),
4858 json!({"title": null})
4859 );
4860 assert_eq!(
4861 serde_json::to_value(
4862 SessionInfoUpdate::new()
4863 .title("title")
4864 .title(MaybeUndefined::Undefined)
4865 )
4866 .unwrap(),
4867 json!({})
4868 );
4869 }
4870
4871 #[test]
4872 fn test_content_chunk_message_id_serialization() {
4873 use serde_json::json;
4874
4875 assert_eq!(
4876 serde_json::to_value(SessionUpdate::AgentMessageChunk(ContentChunk::new(
4877 ContentBlock::Text(crate::v1::TextContent::new("Hello"))
4878 )))
4879 .unwrap(),
4880 json!({
4881 "sessionUpdate": "agent_message_chunk",
4882 "content": {
4883 "type": "text",
4884 "text": "Hello"
4885 }
4886 })
4887 );
4888
4889 assert_eq!(
4890 serde_json::to_value(SessionUpdate::AgentMessageChunk(
4891 ContentChunk::new(ContentBlock::Text(crate::v1::TextContent::new("Hello")))
4892 .message_id("msg_agent_c42b9")
4893 ))
4894 .unwrap(),
4895 json!({
4896 "sessionUpdate": "agent_message_chunk",
4897 "messageId": "msg_agent_c42b9",
4898 "content": {
4899 "type": "text",
4900 "text": "Hello"
4901 }
4902 })
4903 );
4904
4905 let SessionUpdate::AgentMessageChunk(chunk) = serde_json::from_value(json!({
4906 "sessionUpdate": "agent_message_chunk",
4907 "messageId": null,
4908 "content": {
4909 "type": "text",
4910 "text": "Hello"
4911 }
4912 }))
4913 .unwrap() else {
4914 panic!("expected agent message chunk");
4915 };
4916
4917 assert_eq!(chunk.message_id, None);
4918 }
4919
4920 #[test]
4921 fn test_usage_update_serialization() {
4922 use serde_json::json;
4923
4924 assert_eq!(
4925 serde_json::to_value(SessionUpdate::UsageUpdate(UsageUpdate::new(
4926 53_000, 200_000
4927 )))
4928 .unwrap(),
4929 json!({
4930 "sessionUpdate": "usage_update",
4931 "used": 53000,
4932 "size": 200_000
4933 })
4934 );
4935
4936 assert_eq!(
4937 serde_json::to_value(SessionUpdate::UsageUpdate(
4938 UsageUpdate::new(53_000, 200_000).cost(Cost::new(0.045, "USD"))
4939 ))
4940 .unwrap(),
4941 json!({
4942 "sessionUpdate": "usage_update",
4943 "used": 53000,
4944 "size": 200_000,
4945 "cost": {
4946 "amount": 0.045,
4947 "currency": "USD"
4948 }
4949 })
4950 );
4951
4952 let SessionUpdate::UsageUpdate(update) = serde_json::from_value(json!({
4953 "sessionUpdate": "usage_update",
4954 "used": 53000,
4955 "size": 200_000,
4956 "cost": null
4957 }))
4958 .unwrap() else {
4959 panic!("expected usage update");
4960 };
4961
4962 assert_eq!(update.cost, None);
4963 }
4964
4965 #[cfg(feature = "unstable_nes")]
4966 #[test]
4967 fn test_client_capabilities_position_encodings_serialization() {
4968 use serde_json::json;
4969
4970 let capabilities = ClientCapabilities::new().position_encodings(vec![
4971 PositionEncodingKind::Utf32,
4972 PositionEncodingKind::Utf16,
4973 ]);
4974 let json = serde_json::to_value(&capabilities).unwrap();
4975
4976 assert_eq!(json["positionEncodings"], json!(["utf-32", "utf-16"]));
4977 }
4978
4979 #[test]
4980 fn test_client_capabilities_boolean_config_options_serialization() {
4981 use serde_json::json;
4982
4983 let capabilities = ClientCapabilities::new().session(
4984 ClientSessionCapabilities::new().config_options(
4985 SessionConfigOptionsCapabilities::new()
4986 .boolean(BooleanConfigOptionCapabilities::new()),
4987 ),
4988 );
4989 let json = serde_json::to_value(&capabilities).unwrap();
4990
4991 assert_eq!(json["session"]["configOptions"]["boolean"], json!({}));
4992
4993 let omitted: ClientCapabilities = serde_json::from_value(json!({})).unwrap();
4994 assert!(omitted.session.is_none());
4995
4996 let null_session: ClientCapabilities = serde_json::from_value(json!({
4997 "session": null
4998 }))
4999 .unwrap();
5000 assert!(null_session.session.is_none());
5001
5002 let null_config_options: ClientCapabilities = serde_json::from_value(json!({
5003 "session": {
5004 "configOptions": null
5005 }
5006 }))
5007 .unwrap();
5008 assert!(
5009 null_config_options
5010 .session
5011 .and_then(|session| session.config_options)
5012 .is_none()
5013 );
5014
5015 let null_boolean: ClientCapabilities = serde_json::from_value(json!({
5016 "session": {
5017 "configOptions": {
5018 "boolean": null
5019 }
5020 }
5021 }))
5022 .unwrap();
5023 assert!(
5024 null_boolean
5025 .session
5026 .and_then(|session| session.config_options)
5027 .and_then(|config_options| config_options.boolean)
5028 .is_none()
5029 );
5030 }
5031
5032 #[cfg(feature = "unstable_plan_operations")]
5033 #[test]
5034 fn test_plan_operations_serialization() {
5035 use serde_json::json;
5036
5037 use crate::v1::{PlanEntry, PlanEntryPriority, PlanEntryStatus, PlanUpdateContent};
5038
5039 let plan_update = SessionUpdate::PlanUpdate(PlanUpdate::new(PlanUpdateContent::items(
5040 "plan-1",
5041 vec![PlanEntry::new(
5042 "Step 1",
5043 PlanEntryPriority::High,
5044 PlanEntryStatus::Pending,
5045 )],
5046 )));
5047
5048 assert_eq!(
5049 serde_json::to_value(plan_update).unwrap(),
5050 json!({
5051 "sessionUpdate": "plan_update",
5052 "plan": {
5053 "type": "items",
5054 "planId": "plan-1",
5055 "entries": [
5056 {
5057 "content": "Step 1",
5058 "priority": "high",
5059 "status": "pending"
5060 }
5061 ]
5062 }
5063 })
5064 );
5065
5066 assert_eq!(
5067 serde_json::to_value(SessionUpdate::PlanRemoved(PlanRemoved::new("plan-1"))).unwrap(),
5068 json!({
5069 "sessionUpdate": "plan_removed",
5070 "planId": "plan-1"
5071 })
5072 );
5073
5074 let capabilities = ClientCapabilities::new().plan(PlanCapabilities::new());
5075 let json = serde_json::to_value(&capabilities).unwrap();
5076 assert_eq!(json["plan"], json!({}));
5077
5078 assert_eq!(
5079 serde_json::from_value::<ClientCapabilities>(json!({ "plan": null }))
5080 .unwrap()
5081 .plan,
5082 None
5083 );
5084 }
5085
5086 #[cfg(feature = "unstable_mcp_over_acp")]
5087 #[test]
5088 fn test_agent_mcp_request_method_names() {
5089 use serde_json::json;
5090
5091 let params: serde_json::Map<String, serde_json::Value> =
5092 [("cursor".to_string(), json!("abc"))].into_iter().collect();
5093
5094 assert_eq!(CLIENT_METHOD_NAMES.mcp_message, "mcp/message");
5095 assert_eq!(
5096 AgentRequest::MessageMcpRequest(MessageMcpRequest::new(
5097 "server-1",
5098 "req-1",
5099 "tools/list"
5100 ))
5101 .method(),
5102 "mcp/message"
5103 );
5104 assert_eq!(
5105 serde_json::to_value(
5106 MessageMcpRequest::new("server-1", "req-1", "tools/list").params(params)
5107 )
5108 .unwrap(),
5109 json!({
5110 "serverId": "server-1",
5111 "requestId": "req-1",
5112 "method": "tools/list",
5113 "params": { "cursor": "abc" }
5114 })
5115 );
5116
5117 let request_with_null_params: MessageMcpRequest = serde_json::from_value(json!({
5118 "serverId": "server-1",
5119 "requestId": "req-1",
5120 "method": "tools/list",
5121 "params": null,
5122 "_meta": null
5123 }))
5124 .unwrap();
5125 assert_eq!(request_with_null_params.params, None);
5126 assert_eq!(request_with_null_params.meta, None);
5127 for key in ["serverId", "requestId", "method"] {
5128 let mut value =
5129 json!({"serverId":"server-1", "requestId":"req-1", "method":"tools/list"});
5130 value.as_object_mut().unwrap().remove(key);
5131 assert!(serde_json::from_value::<MessageMcpRequest>(value).is_err());
5132 }
5133 for key in ["serverId", "requestId", "method"] {
5134 let mut value =
5135 json!({"serverId":"server-1", "requestId":"req-1", "method":"tools/list"});
5136 value[key] = serde_json::Value::Null;
5137 assert!(serde_json::from_value::<MessageMcpRequest>(value).is_err());
5138 }
5139 }
5140
5141 #[test]
5142 fn request_permission_request_rejects_malformed_options() {
5143 use serde_json::json;
5144
5145 assert!(
5146 serde_json::from_value::<RequestPermissionRequest>(json!({
5147 "sessionId": "sess-1",
5148 "toolCall": {"toolCallId": "tc-1"},
5149 "options": "not-an-array"
5150 }))
5151 .is_err()
5152 );
5153 assert!(
5154 serde_json::from_value::<RequestPermissionRequest>(json!({
5155 "sessionId": "sess-1",
5156 "toolCall": {"toolCallId": "tc-1"},
5157 "options": [{"optionId": "allow"}]
5158 }))
5159 .is_err()
5160 );
5161 }
5162}