1use std::{path::PathBuf, sync::Arc};
7
8use derive_more::{Display, From};
9#[cfg(all(feature = "schemars", feature = "unstable_subagents"))]
10use schemars::Schema;
11use serde::{Deserialize, Serialize};
12use serde_with::{DefaultOnError, DefaultOnNull, VecSkipError, serde_as, skip_serializing_none};
13#[cfg(feature = "unstable_subagents")]
14use std::collections::BTreeMap;
15
16#[cfg(feature = "unstable_auth_status")]
17use super::AuthStatusUpdateNotification;
18#[cfg(feature = "unstable_subagents")]
19use super::Error;
20#[cfg(all(
21 feature = "unstable_subagents",
22 feature = "unstable_end_turn_token_usage"
23))]
24use super::Usage;
25use super::{
26 CompleteElicitationNotification, CreateElicitationRequest, CreateElicitationResponse,
27 ElicitationCapabilities,
28};
29use crate::{IntoMaybeUndefined, IntoOption, MaybeUndefined};
30
31use super::{
32 ContentBlock, EnvVariable, ExtNotification, ExtRequest, ExtResponse, Meta, Plan,
33 SessionConfigOption, SessionId, SessionModeId, ToolCall, ToolCallUpdate,
34};
35#[cfg(feature = "unstable_plan_operations")]
36use super::{PlanCapabilities, PlanRemoved, PlanUpdate};
37
38#[cfg(feature = "unstable_mcp_over_acp")]
39use super::mcp::{MCP_MESSAGE_METHOD_NAME, MessageMcpRequest, MessageMcpResponse};
40
41#[serde_as]
49#[skip_serializing_none]
50#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
51#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
52#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = SESSION_UPDATE_NOTIFICATION)))]
53#[serde(rename_all = "camelCase")]
54#[non_exhaustive]
55pub struct SessionNotification {
56 pub session_id: SessionId,
58 pub update: SessionUpdate,
60 #[serde_as(deserialize_as = "DefaultOnError")]
66 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
67 #[serde(default)]
68 #[serde(rename = "_meta")]
69 pub meta: Option<Meta>,
70}
71
72impl SessionNotification {
73 #[must_use]
75 pub fn new(session_id: impl Into<SessionId>, update: SessionUpdate) -> Self {
76 Self {
77 session_id: session_id.into(),
78 update,
79 meta: None,
80 }
81 }
82
83 #[must_use]
89 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
90 self.meta = meta.into_option();
91 self
92 }
93}
94
95#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
101#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
102#[serde(tag = "sessionUpdate", rename_all = "snake_case")]
103#[cfg_attr(feature = "schemars", schemars(extend("discriminator" = {"propertyName": "sessionUpdate"})))]
104#[non_exhaustive]
105pub enum SessionUpdate {
106 UserMessageChunk(ContentChunk),
108 AgentMessageChunk(ContentChunk),
110 AgentThoughtChunk(ContentChunk),
112 ToolCall(ToolCall),
114 ToolCallUpdate(ToolCallUpdate),
116 Plan(Plan),
119 #[cfg(feature = "unstable_plan_operations")]
125 PlanUpdate(PlanUpdate),
126 #[cfg(feature = "unstable_plan_operations")]
132 PlanRemoved(PlanRemoved),
133 AvailableCommandsUpdate(AvailableCommandsUpdate),
135 CurrentModeUpdate(CurrentModeUpdate),
139 ConfigOptionUpdate(ConfigOptionUpdate),
141 SessionInfoUpdate(SessionInfoUpdate),
143 UsageUpdate(UsageUpdate),
145 Notice(Notice),
150 CompactionUpdate(CompactionUpdate),
155 CompactionSummaryChunk(CompactionSummaryChunk),
160 #[cfg(feature = "unstable_subagents")]
167 SubagentUpdate(SubagentUpdate),
168 #[cfg(feature = "unstable_subagents")]
175 SessionMessage(SessionMessage),
176 #[cfg(feature = "unstable_subagents")]
182 SessionMessageChunk(SessionMessageChunk),
183}
184
185#[cfg(feature = "unstable_subagents")]
194#[serde_as]
195#[skip_serializing_none]
196#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
197#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
198#[serde(rename_all = "camelCase")]
199#[non_exhaustive]
200pub struct SessionMessageChunk {
201 pub message_id: MessageId,
203 pub content: ContentBlock,
205 #[serde_as(deserialize_as = "DefaultOnError")]
209 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
210 #[serde(default, rename = "_meta")]
211 pub meta: Option<Meta>,
212}
213
214#[cfg(feature = "unstable_subagents")]
215impl SessionMessageChunk {
216 #[must_use]
218 pub fn new(message_id: impl Into<MessageId>, content: ContentBlock) -> Self {
219 Self {
220 message_id: message_id.into(),
221 content,
222 meta: None,
223 }
224 }
225
226 #[must_use]
228 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
229 self.meta = meta.into_option();
230 self
231 }
232}
233
234#[cfg(feature = "unstable_subagents")]
240#[serde_as]
241#[skip_serializing_none]
242#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
243#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
244#[serde(rename_all = "camelCase")]
245#[non_exhaustive]
246pub struct SessionMessage {
247 pub message_id: MessageId,
249 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
253 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
254 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
255 pub sender_session_id: MaybeUndefined<SessionId>,
256 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
260 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
261 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
262 pub recipient_session_id: MaybeUndefined<SessionId>,
263 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
269 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
270 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
271 pub sender_message_id: MaybeUndefined<MessageId>,
272 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
278 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
279 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
280 pub recipient_message_id: MaybeUndefined<MessageId>,
281 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<VecSkipError<_>>>")]
284 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
285 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
286 pub content: MaybeUndefined<Vec<ContentBlock>>,
287 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
291 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
292 #[serde(
293 default,
294 rename = "_meta",
295 skip_serializing_if = "MaybeUndefined::is_undefined"
296 )]
297 pub meta: MaybeUndefined<Meta>,
298}
299
300#[cfg(feature = "unstable_subagents")]
301impl SessionMessage {
302 #[must_use]
304 pub fn new(message_id: impl Into<MessageId>) -> Self {
305 Self {
306 message_id: message_id.into(),
307 sender_session_id: MaybeUndefined::Undefined,
308 recipient_session_id: MaybeUndefined::Undefined,
309 sender_message_id: MaybeUndefined::Undefined,
310 recipient_message_id: MaybeUndefined::Undefined,
311 content: MaybeUndefined::Undefined,
312 meta: MaybeUndefined::Undefined,
313 }
314 }
315
316 #[must_use]
318 pub fn sender_session_id(
319 mut self,
320 sender_session_id: impl IntoMaybeUndefined<SessionId>,
321 ) -> Self {
322 self.sender_session_id = sender_session_id.into_maybe_undefined();
323 self
324 }
325
326 #[must_use]
328 pub fn recipient_session_id(
329 mut self,
330 recipient_session_id: impl IntoMaybeUndefined<SessionId>,
331 ) -> Self {
332 self.recipient_session_id = recipient_session_id.into_maybe_undefined();
333 self
334 }
335
336 #[must_use]
338 pub fn sender_message_id(
339 mut self,
340 sender_message_id: impl IntoMaybeUndefined<MessageId>,
341 ) -> Self {
342 self.sender_message_id = sender_message_id.into_maybe_undefined();
343 self
344 }
345
346 #[must_use]
348 pub fn recipient_message_id(
349 mut self,
350 recipient_message_id: impl IntoMaybeUndefined<MessageId>,
351 ) -> Self {
352 self.recipient_message_id = recipient_message_id.into_maybe_undefined();
353 self
354 }
355
356 #[must_use]
358 pub fn content(mut self, content: impl IntoMaybeUndefined<Vec<ContentBlock>>) -> Self {
359 self.content = content.into_maybe_undefined();
360 self
361 }
362
363 #[must_use]
365 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
366 self.meta = meta.into_maybe_undefined();
367 self
368 }
369}
370
371#[cfg(all(test, feature = "unstable_subagents"))]
372mod session_message_tests {
373 use super::*;
374 use serde_json::{Value, json};
375
376 #[test]
377 fn envelopes_preserve_participants_and_multimodal_content() {
378 let content = json!([
379 {"type": "text", "text": "Please inspect this"},
380 {"type": "image", "data": "aGVsbG8=", "mimeType": "image/png"}
381 ]);
382 for (transcript, sender, recipient, id) in [
383 ("parent", "parent", "child", "sent-1"),
384 ("child", "parent", "child", "received-9"),
385 ("child", "child", "parent", "sent-2"),
386 ] {
387 let wire = json!({"sessionId": transcript, "update": {
388 "sessionUpdate": "session_message", "messageId": id,
389 "senderSessionId": sender, "recipientSessionId": recipient, "content": content
390 }});
391 let decoded: SessionNotification = serde_json::from_value(wire.clone()).unwrap();
392 assert_eq!(serde_json::to_value(&decoded).unwrap(), wire);
393 assert!(matches!(decoded.update, SessionUpdate::SessionMessage(_)));
394 let message = SessionMessage::new(id)
395 .sender_session_id(SessionId::new(sender))
396 .recipient_session_id(SessionId::new(recipient))
397 .content(vec![]);
398 assert_eq!(serde_json::to_value(message).unwrap()["content"], json!([]));
399 }
400 }
401
402 #[test]
403 fn message_upserts_validate_ids_and_patch_fields() {
404 let base = json!({"sessionUpdate": "session_message", "messageId": "m1",
405 "senderSessionId": "parent", "recipientSessionId": "child", "content": []});
406 {
407 let key = "messageId";
408 let mut missing = base.clone();
409 missing.as_object_mut().unwrap().remove(key);
410 assert!(
411 serde_json::from_value::<SessionUpdate>(missing).is_err(),
412 "{key}"
413 );
414 let mut null = base.clone();
415 null[key] = Value::Null;
416 assert!(
417 serde_json::from_value::<SessionUpdate>(null).is_err(),
418 "{key}"
419 );
420 let mut non_string = base.clone();
421 non_string[key] = json!(42);
422 assert!(
423 serde_json::from_value::<SessionUpdate>(non_string).is_err(),
424 "{key}"
425 );
426 }
427 for content in [None, Some(json!({}))] {
428 let mut wire = base.clone();
429 wire.as_object_mut().unwrap().remove("content");
430 if let Some(content) = content {
431 wire["content"] = content;
432 }
433 let decoded: SessionUpdate = serde_json::from_value(wire).unwrap();
434 let encoded = serde_json::to_value(decoded).unwrap();
435 let mut unchanged = base.clone();
436 unchanged.as_object_mut().unwrap().remove("content");
437 assert_eq!(encoded, unchanged);
438 }
439 let clear = SessionMessage::new("m1")
440 .sender_session_id(SessionId::new("parent"))
441 .recipient_session_id(SessionId::new("child"))
442 .content(None)
443 .meta(None);
444 let mut clear_wire = base.clone();
445 clear_wire["content"] = Value::Null;
446 clear_wire["_meta"] = Value::Null;
447 assert_eq!(
448 serde_json::to_value(&clear).unwrap()["content"],
449 Value::Null
450 );
451 let clear_update: SessionUpdate = serde_json::from_value(clear_wire.clone()).unwrap();
452 assert_eq!(serde_json::to_value(clear_update).unwrap(), clear_wire);
453 assert!(clear.content.is_null());
454 assert!(clear.meta.is_null());
455 for meta in [None, Some(Value::Null), Some(json!({"tag": "value"}))] {
456 let mut wire = base.clone();
457 if let Some(meta) = meta {
458 wire["_meta"] = meta;
459 }
460 let decoded: SessionUpdate = serde_json::from_value(wire.clone()).unwrap();
461 let encoded = serde_json::to_value(decoded).unwrap();
462 assert_eq!(encoded, wire);
463 }
464 let metadata_only = json!({"sessionUpdate": "session_message",
465 "messageId": "m1", "senderSessionId": "parent",
466 "recipientSessionId": "child", "_meta": {"tag": "value"}});
467 let decoded: SessionUpdate = serde_json::from_value(metadata_only.clone()).unwrap();
468 assert_eq!(serde_json::to_value(decoded).unwrap(), metadata_only);
469 let unset = SessionMessage::new("m1");
470 assert_eq!(
471 serde_json::to_value(&unset).unwrap(),
472 json!({"messageId": "m1"})
473 );
474 assert!(unset.content.is_undefined());
475 assert!(unset.meta.is_undefined());
476 let reset = SessionMessage::new("m1").content(vec![]);
477 assert_eq!(serde_json::to_value(reset).unwrap()["content"], json!([]));
478 let malformed: SessionMessage = serde_json::from_value(json!({
479 "messageId": "m1", "senderSessionId": "parent", "recipientSessionId": "child",
480 "content": false, "_meta": false
481 }))
482 .unwrap();
483 assert!(malformed.content.is_undefined());
484 assert!(malformed.meta.is_undefined());
485 }
486
487 #[test]
488 fn first_chunk_and_upsert_share_transcript_local_identity() {
489 for (transcript, id) in [("parent", "sent-1"), ("child", "received-9")] {
490 let wire = json!({"sessionId": transcript, "update": {
491 "sessionUpdate": "session_message_chunk", "messageId": id,
492 "content": {"type": "text", "text": "first"}
493 }});
494 let decoded: SessionNotification = serde_json::from_value(wire.clone()).unwrap();
495 assert_eq!(serde_json::to_value(decoded).unwrap(), wire);
496 let upsert = SessionMessage::new(id)
497 .sender_session_id(SessionId::new("parent"))
498 .recipient_session_id(SessionId::new("child"));
499 let chunk = SessionMessageChunk::new(
500 id,
501 ContentBlock::Text(crate::v1::TextContent::new("first")),
502 );
503 assert_eq!(chunk.message_id, upsert.message_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 "content": {"type": "text", "text": "first"}
512 }});
513 let base = wire["update"].clone();
514 for key in ["messageId", "content"] {
515 let mut missing = base.clone();
516 missing.as_object_mut().unwrap().remove(key);
517 assert!(
518 serde_json::from_value::<SessionUpdate>(missing).is_err(),
519 "{key}"
520 );
521 let mut null = base.clone();
522 null[key] = Value::Null;
523 assert!(
524 serde_json::from_value::<SessionUpdate>(null).is_err(),
525 "{key}"
526 );
527 let mut non_string = base.clone();
528 non_string[key] = json!(42);
529 assert!(
530 serde_json::from_value::<SessionUpdate>(non_string).is_err(),
531 "{key}"
532 );
533 }
534 for meta in [None, Some(Value::Null), Some(json!({"chunk": true}))] {
535 let mut value = base.clone();
536 if let Some(meta) = meta {
537 value["_meta"] = meta;
538 }
539 let decoded: SessionUpdate = serde_json::from_value(value.clone()).unwrap();
540 let encoded = serde_json::to_value(decoded).unwrap();
541 if value["_meta"].is_null() {
542 assert_eq!(encoded, base);
543 } else {
544 assert_eq!(encoded, value);
545 }
546 }
547 let with_identity = json!({"sessionUpdate": "session_message_chunk", "messageId": "m1",
549 "senderSessionId": "parent", "recipientSessionId": "child",
550 "senderMessageId": "sent-1", "recipientMessageId": "received-1",
551 "content": {"type": "text", "text": "hello"}});
552 let decoded: SessionUpdate = serde_json::from_value(with_identity).unwrap();
553 assert_eq!(
554 serde_json::to_value(decoded).unwrap(),
555 json!({"sessionUpdate": "session_message_chunk", "messageId": "m1",
556 "content": {"type": "text", "text": "hello"}})
557 );
558 }
559
560 #[cfg(feature = "schemars")]
561 #[test]
562 fn schema_requires_ids_and_chunk_content() {
563 let schema = serde_json::to_value(schemars::schema_for!(SessionMessage)).unwrap();
564 let required = schema["required"].as_array().unwrap();
565 assert_eq!(required, &vec![json!("messageId")]);
566 let chunk = serde_json::to_value(schemars::schema_for!(SessionMessageChunk)).unwrap();
567 let required = chunk["required"].as_array().unwrap();
568 assert_eq!(required.len(), 2);
569 assert!(required.contains(&json!("messageId")));
570 assert!(required.contains(&json!("content")));
571 }
572
573 #[test]
574 fn endpoints_can_arrive_late_or_be_omitted_from_later_events() {
575 let block = ContentBlock::Text(crate::v1::TextContent::new("hello"));
576 let minimal = SessionMessage::new("m1");
577 let first = SessionMessageChunk::new("m1", block.clone());
578 assert_eq!(
579 serde_json::to_value(&minimal).unwrap(),
580 json!({"messageId": "m1"})
581 );
582 assert_eq!(
583 serde_json::to_value(&first).unwrap(),
584 json!({"messageId": "m1", "content": {"type": "text", "text": "hello"}})
585 );
586 let enriched = SessionMessage::new("m1")
587 .sender_session_id(SessionId::new("parent"))
588 .recipient_session_id(SessionId::new("child"));
589 assert_eq!(
590 enriched.sender_session_id,
591 MaybeUndefined::Value(SessionId::new("parent"))
592 );
593 assert_eq!(
594 enriched.recipient_session_id,
595 MaybeUndefined::Value(SessionId::new("child"))
596 );
597 let later = SessionMessage::new("m1").sender_session_id(SessionId::new("parent"));
598 assert_eq!(
599 later.sender_session_id,
600 MaybeUndefined::Value(SessionId::new("parent"))
601 );
602 assert_eq!(later.recipient_session_id, MaybeUndefined::Undefined);
603 let more = SessionMessageChunk::new("m1", block);
604 for update in [
605 SessionUpdate::SessionMessage(minimal),
606 SessionUpdate::SessionMessageChunk(first),
607 SessionUpdate::SessionMessage(enriched),
608 SessionUpdate::SessionMessage(later),
609 SessionUpdate::SessionMessageChunk(more),
610 ] {
611 let wire = serde_json::to_value(&update).unwrap();
612 let decoded: SessionUpdate = serde_json::from_value(wire.clone()).unwrap();
613 assert_eq!(serde_json::to_value(decoded).unwrap(), wire);
614 }
615 }
616
617 #[test]
618 fn null_endpoints_clear_invalid_are_absent_but_message_id_is_required() {
619 let mut base = json!({"sessionUpdate": "session_message", "messageId": "m1",
620 "senderSessionId": null, "recipientSessionId": 42});
621 let decoded: SessionUpdate = serde_json::from_value(base.clone()).unwrap();
622 let encoded = serde_json::to_value(decoded).unwrap();
623 base.as_object_mut().unwrap().remove("recipientSessionId");
624 assert_eq!(encoded, base);
625 for bad in [Value::Null, json!(42)] {
626 let mut invalid = base.clone();
627 invalid["messageId"] = bad;
628 assert!(serde_json::from_value::<SessionUpdate>(invalid).is_err());
629 }
630 }
631
632 #[test]
633 fn message_references_link_views_and_can_arrive_late() {
634 let block = ContentBlock::Text(crate::v1::TextContent::new("hello"));
635 let incoming = SessionMessage::new("received-1")
636 .sender_session_id(SessionId::new("parent"))
637 .recipient_session_id(SessionId::new("child"))
638 .sender_message_id(MessageId::new("sent-1"))
639 .content(vec![block]);
640 let outgoing =
642 SessionMessage::new("sent-1").recipient_message_id(MessageId::new("received-1"));
643 for (update, wire) in [
644 (
645 SessionUpdate::SessionMessage(incoming),
646 json!({"sessionUpdate": "session_message", "messageId": "received-1",
647 "senderSessionId": "parent", "recipientSessionId": "child",
648 "senderMessageId": "sent-1", "content": [{"type": "text", "text": "hello"}]}),
649 ),
650 (
651 SessionUpdate::SessionMessage(outgoing),
652 json!({"sessionUpdate": "session_message", "messageId": "sent-1",
653 "recipientMessageId": "received-1"}),
654 ),
655 ] {
656 assert_eq!(serde_json::to_value(&update).unwrap(), wire);
657 let decoded: SessionUpdate = serde_json::from_value(wire.clone()).unwrap();
658 assert_eq!(decoded, update);
659 }
660
661 let mut wire = json!({"sessionUpdate": "session_message", "messageId": "m1",
662 "senderMessageId": null, "recipientMessageId": 42});
663 let decoded: SessionUpdate = serde_json::from_value(wire.clone()).unwrap();
664 wire.as_object_mut().unwrap().remove("recipientMessageId");
665 assert_eq!(
666 serde_json::to_value(decoded).unwrap(),
667 wire,
668 "null clears a reference and a malformed one is not supplied"
669 );
670 }
671}
672
673#[cfg(all(test, not(feature = "unstable_subagents")))]
674mod disabled_session_message_tests {
675 use super::*;
676 use serde_json::json;
677
678 #[test]
679 fn message_updates_require_subagents_gate() {
680 for (kind, content) in [
681 ("session_message", json!([])),
682 (
683 "session_message_chunk",
684 json!({"type": "text", "text": "hello"}),
685 ),
686 ] {
687 let wire = json!({"sessionUpdate": kind, "messageId": "m1", "content": content});
688 assert!(serde_json::from_value::<SessionUpdate>(wire).is_err());
689 }
690 }
691}
692
693#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
695#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
696#[serde(rename_all = "snake_case")]
697#[non_exhaustive]
698pub enum NoticeSeverity {
699 Info,
701 Warning,
703 Error,
705 #[serde(untagged)]
710 Other(String),
711}
712
713#[serde_as]
723#[skip_serializing_none]
724#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
725#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
726#[serde(rename_all = "camelCase")]
727#[non_exhaustive]
728pub struct Notice {
729 pub severity: NoticeSeverity,
731 #[cfg_attr(feature = "schemars", schemars(length(min = 1)))]
733 pub title: String,
734 #[serde_as(deserialize_as = "DefaultOnError")]
738 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
739 #[serde(default)]
740 pub description: Option<String>,
741 #[serde_as(deserialize_as = "DefaultOnError")]
745 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
746 #[serde(default, rename = "_meta")]
747 pub meta: Option<Meta>,
748}
749
750impl Notice {
751 #[must_use]
753 pub fn new(severity: NoticeSeverity, title: impl Into<String>) -> Self {
754 Self {
755 severity,
756 title: title.into(),
757 description: None,
758 meta: None,
759 }
760 }
761
762 #[must_use]
764 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
765 self.description = description.into_option();
766 self
767 }
768
769 #[must_use]
771 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
772 self.meta = meta.into_option();
773 self
774 }
775}
776
777#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
779#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
780#[serde(transparent)]
781#[from(Arc<str>, String, &'static str)]
782#[non_exhaustive]
783pub struct CompactionId(pub Arc<str>);
784
785impl CompactionId {
786 #[must_use]
788 pub fn new(id: impl Into<Arc<str>>) -> Self {
789 Self(id.into())
790 }
791}
792
793#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
795#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
796#[serde(rename_all = "snake_case")]
797#[non_exhaustive]
798pub enum CompactionStatus {
799 #[default]
801 InProgress,
802 Completed,
804 Failed,
806 Cancelled,
808 #[serde(untagged)]
813 Other(String),
814}
815
816#[serde_as]
825#[skip_serializing_none]
826#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
827#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
828#[serde(rename_all = "camelCase")]
829#[non_exhaustive]
830pub struct CompactionUpdate {
831 pub compaction_id: CompactionId,
833 pub status: CompactionStatus,
835 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<VecSkipError<_>>>")]
837 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
838 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
839 pub summary: MaybeUndefined<Vec<ContentBlock>>,
840 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
842 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
843 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
844 pub error: MaybeUndefined<String>,
845 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
847 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
848 #[serde(
849 rename = "_meta",
850 default,
851 skip_serializing_if = "MaybeUndefined::is_undefined"
852 )]
853 pub meta: MaybeUndefined<Meta>,
854}
855
856impl CompactionUpdate {
857 #[must_use]
859 pub fn new(compaction_id: impl Into<CompactionId>, status: CompactionStatus) -> Self {
860 Self {
861 compaction_id: compaction_id.into(),
862 status,
863 summary: MaybeUndefined::Undefined,
864 error: MaybeUndefined::Undefined,
865 meta: MaybeUndefined::Undefined,
866 }
867 }
868
869 #[must_use]
871 pub fn summary(mut self, summary: impl IntoMaybeUndefined<Vec<ContentBlock>>) -> Self {
872 self.summary = summary.into_maybe_undefined();
873 self
874 }
875
876 #[must_use]
878 pub fn error(mut self, error: impl IntoMaybeUndefined<String>) -> Self {
879 self.error = error.into_maybe_undefined();
880 self
881 }
882
883 #[must_use]
885 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
886 self.meta = meta.into_maybe_undefined();
887 self
888 }
889}
890
891#[serde_as]
896#[skip_serializing_none]
897#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
898#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
899#[serde(rename_all = "camelCase")]
900#[non_exhaustive]
901pub struct CompactionSummaryChunk {
902 pub compaction_id: CompactionId,
904 pub content: ContentBlock,
906 #[serde_as(deserialize_as = "DefaultOnError")]
908 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
909 #[serde(default, rename = "_meta")]
910 pub meta: Option<Meta>,
911}
912
913impl CompactionSummaryChunk {
914 #[must_use]
916 pub fn new(compaction_id: impl Into<CompactionId>, content: ContentBlock) -> Self {
917 Self {
918 compaction_id: compaction_id.into(),
919 content,
920 meta: None,
921 }
922 }
923
924 #[must_use]
926 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
927 self.meta = meta.into_option();
928 self
929 }
930}
931
932#[cfg(feature = "unstable_subagents")]
958#[serde_as]
959#[skip_serializing_none]
960#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
961#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
962#[serde(rename_all = "camelCase")]
963#[non_exhaustive]
964pub struct SubagentUpdate {
965 pub session_id: SessionId,
967 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
972 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
973 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
974 pub title: MaybeUndefined<String>,
975 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
981 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
982 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
983 pub description: MaybeUndefined<String>,
984 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
991 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
992 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
993 pub capabilities: MaybeUndefined<SubagentSessionCapabilities>,
994 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
1001 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1002 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
1003 pub state: MaybeUndefined<StateUpdate>,
1004 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
1011 pub cwd: MaybeUndefined<PathBuf>,
1012 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
1020 pub additional_directories: MaybeUndefined<Vec<PathBuf>>,
1021 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
1028 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1029 #[serde(
1030 default,
1031 rename = "_meta",
1032 skip_serializing_if = "MaybeUndefined::is_undefined"
1033 )]
1034 pub meta: MaybeUndefined<Meta>,
1035}
1036
1037#[cfg(feature = "unstable_subagents")]
1038impl SubagentUpdate {
1039 #[must_use]
1041 pub fn new(session_id: impl Into<SessionId>) -> Self {
1042 Self {
1043 session_id: session_id.into(),
1044 title: MaybeUndefined::Undefined,
1045 description: MaybeUndefined::Undefined,
1046 capabilities: MaybeUndefined::Undefined,
1047 state: MaybeUndefined::Undefined,
1048 cwd: MaybeUndefined::Undefined,
1049 additional_directories: MaybeUndefined::Undefined,
1050 meta: MaybeUndefined::Undefined,
1051 }
1052 }
1053
1054 #[must_use]
1056 pub fn title(mut self, title: impl IntoMaybeUndefined<String>) -> Self {
1057 self.title = title.into_maybe_undefined();
1058 self
1059 }
1060
1061 #[must_use]
1063 pub fn description(mut self, description: impl IntoMaybeUndefined<String>) -> Self {
1064 self.description = description.into_maybe_undefined();
1065 self
1066 }
1067
1068 #[must_use]
1070 pub fn capabilities(
1071 mut self,
1072 capabilities: impl IntoMaybeUndefined<SubagentSessionCapabilities>,
1073 ) -> Self {
1074 self.capabilities = capabilities.into_maybe_undefined();
1075 self
1076 }
1077
1078 #[must_use]
1080 pub fn state(mut self, state: impl IntoMaybeUndefined<StateUpdate>) -> Self {
1081 self.state = state.into_maybe_undefined();
1082 self
1083 }
1084
1085 #[must_use]
1089 pub fn cwd(mut self, cwd: impl IntoMaybeUndefined<PathBuf>) -> Self {
1090 self.cwd = cwd.into_maybe_undefined();
1091 self
1092 }
1093
1094 #[must_use]
1098 pub fn additional_directories(
1099 mut self,
1100 additional_directories: impl IntoMaybeUndefined<Vec<PathBuf>>,
1101 ) -> Self {
1102 self.additional_directories = additional_directories.into_maybe_undefined();
1103 self
1104 }
1105
1106 #[must_use]
1110 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
1111 self.meta = meta.into_maybe_undefined();
1112 self
1113 }
1114}
1115
1116#[cfg(feature = "unstable_subagents")]
1125#[serde_as]
1126#[skip_serializing_none]
1127#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1128#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1129#[serde(rename_all = "camelCase")]
1130#[non_exhaustive]
1131pub struct SubagentSessionCapabilities {
1132 #[serde_as(deserialize_as = "DefaultOnError")]
1136 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1137 #[serde(default)]
1138 pub cancel: Option<SessionCancelCapabilities>,
1139 #[serde_as(deserialize_as = "DefaultOnError")]
1143 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1144 #[serde(default)]
1145 #[serde(rename = "_meta")]
1146 pub meta: Option<Meta>,
1147}
1148
1149#[cfg(feature = "unstable_subagents")]
1150impl SubagentSessionCapabilities {
1151 #[must_use]
1153 pub fn new() -> Self {
1154 Self::default()
1155 }
1156
1157 #[must_use]
1159 pub fn cancel(mut self, cancel: impl IntoOption<SessionCancelCapabilities>) -> Self {
1160 self.cancel = cancel.into_option();
1161 self
1162 }
1163
1164 #[must_use]
1168 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1169 self.meta = meta.into_option();
1170 self
1171 }
1172}
1173
1174#[cfg(feature = "unstable_subagents")]
1182#[serde_as]
1183#[skip_serializing_none]
1184#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1185#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1186#[non_exhaustive]
1187pub struct SessionCancelCapabilities {
1188 #[serde_as(deserialize_as = "DefaultOnError")]
1194 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1195 #[serde(default)]
1196 #[serde(rename = "_meta")]
1197 pub meta: Option<Meta>,
1198}
1199
1200#[cfg(feature = "unstable_subagents")]
1201impl SessionCancelCapabilities {
1202 #[must_use]
1204 pub fn new() -> Self {
1205 Self::default()
1206 }
1207
1208 #[must_use]
1214 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1215 self.meta = meta.into_option();
1216 self
1217 }
1218}
1219
1220#[cfg(feature = "unstable_subagents")]
1230#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1231#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1232#[serde(tag = "state", rename_all = "snake_case")]
1233#[non_exhaustive]
1234pub enum StateUpdate {
1235 Running(RunningStateUpdate),
1237 Idle(IdleStateUpdate),
1239 RequiresAction(RequiresActionStateUpdate),
1241 Unknown(UnknownStateUpdate),
1245 #[serde(untagged)]
1250 Other(OtherStateUpdate),
1251}
1252
1253#[cfg(feature = "unstable_subagents")]
1255#[serde_as]
1256#[skip_serializing_none]
1257#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1258#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1259#[serde(rename_all = "camelCase")]
1260#[non_exhaustive]
1261pub struct RunningStateUpdate {
1262 #[serde_as(deserialize_as = "DefaultOnError")]
1266 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1267 #[serde(default, rename = "_meta")]
1268 pub meta: Option<Meta>,
1269}
1270
1271#[cfg(feature = "unstable_subagents")]
1272impl RunningStateUpdate {
1273 #[must_use]
1275 pub fn new() -> Self {
1276 Self::default()
1277 }
1278
1279 #[must_use]
1281 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1282 self.meta = meta.into_option();
1283 self
1284 }
1285}
1286
1287#[cfg(feature = "unstable_subagents")]
1291#[serde_as]
1292#[skip_serializing_none]
1293#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1294#[cfg_attr(feature = "schemars", schemars(transform = idle_state_update_schema))]
1295#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1296#[serde(rename_all = "camelCase")]
1297#[non_exhaustive]
1298pub struct IdleStateUpdate {
1299 #[serde(flatten)]
1301 pub stop_reason: Option<IdleStopReason>,
1302 #[cfg(feature = "unstable_end_turn_token_usage")]
1306 #[serde_as(deserialize_as = "DefaultOnError")]
1307 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1308 #[serde(default)]
1309 pub usage: Option<Box<Usage>>,
1310 #[serde_as(deserialize_as = "DefaultOnError")]
1314 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1315 #[serde(default, rename = "_meta")]
1316 pub meta: Option<Meta>,
1317}
1318
1319#[cfg(feature = "unstable_subagents")]
1320impl IdleStateUpdate {
1321 #[must_use]
1323 pub fn new() -> Self {
1324 Self::default()
1325 }
1326
1327 #[must_use]
1329 pub fn stop_reason(mut self, stop_reason: impl IntoOption<IdleStopReason>) -> Self {
1330 self.stop_reason = stop_reason.into_option();
1331 self
1332 }
1333
1334 #[cfg(feature = "unstable_end_turn_token_usage")]
1336 #[must_use]
1337 pub fn usage(mut self, usage: impl IntoOption<Usage>) -> Self {
1338 self.usage = usage.into_option().map(Box::new);
1339 self
1340 }
1341
1342 #[must_use]
1344 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1345 self.meta = meta.into_option();
1346 self
1347 }
1348}
1349
1350#[cfg(all(feature = "unstable_subagents", feature = "schemars"))]
1359fn idle_state_update_schema(schema: &mut Schema) {
1360 if let Some(properties) = schema
1361 .get_mut("properties")
1362 .and_then(serde_json::Value::as_object_mut)
1363 {
1364 properties.insert(
1365 "stopReason".into(),
1366 serde_json::json!({
1367 "description": "Why foreground work stopped. The value selects one of this type's variants, which may add fields of their own.\n\nOptional. Omitted or `null` both mean the agent is not reporting a stop reason; a malformed value is treated the same way.\n\nSee protocol docs: [Current work state](https://agentclientprotocol.com/rfds/subagents#current-work-state)",
1368 "type": ["string", "null"],
1369 "x-deserialize-default-on-error": true
1370 }),
1371 );
1372 }
1373 let Some(variants) = schema
1374 .get_mut("anyOf")
1375 .and_then(serde_json::Value::as_array_mut)
1376 else {
1377 return;
1378 };
1379 for variant in variants {
1380 if variant.as_object().is_some_and(serde_json::Map::is_empty) {
1381 *variant = serde_json::json!({
1382 "title": "none",
1383 "description": "No stop reason: `stopReason` is omitted or `null`.",
1384 "type": "object",
1385 "properties": {
1386 "stopReason": { "type": "null" }
1387 }
1388 });
1389 }
1390 }
1391}
1392
1393#[cfg(feature = "unstable_subagents")]
1406#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1407#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1408#[serde(tag = "stopReason", rename_all = "snake_case")]
1409#[non_exhaustive]
1410pub enum IdleStopReason {
1411 EndTurn,
1413 MaxTokens,
1415 MaxTurnRequests,
1418 Refusal,
1420 Cancelled,
1422 Error(ErrorStopReason),
1424 #[serde(untagged)]
1429 Other(OtherStopReason),
1430}
1431
1432#[cfg(feature = "unstable_subagents")]
1433impl From<ErrorStopReason> for IdleStopReason {
1434 fn from(value: ErrorStopReason) -> Self {
1435 Self::Error(value)
1436 }
1437}
1438
1439#[cfg(feature = "unstable_subagents")]
1445#[serde_as]
1446#[skip_serializing_none]
1447#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1448#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1449#[serde(rename_all = "camelCase")]
1450#[non_exhaustive]
1451pub struct ErrorStopReason {
1452 #[serde_as(deserialize_as = "DefaultOnError")]
1457 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1458 #[serde(default)]
1459 pub error: Option<Box<Error>>,
1460}
1461
1462#[cfg(feature = "unstable_subagents")]
1463impl ErrorStopReason {
1464 #[must_use]
1466 pub fn new() -> Self {
1467 Self::default()
1468 }
1469
1470 #[must_use]
1472 pub fn error(mut self, error: impl IntoOption<Error>) -> Self {
1473 self.error = error.into_option().map(Box::new);
1474 self
1475 }
1476}
1477
1478#[cfg(feature = "unstable_subagents")]
1484#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1485#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1486#[cfg_attr(feature = "schemars", schemars(inline))]
1487#[cfg_attr(feature = "schemars", schemars(transform = other_stop_reason_schema))]
1488#[serde(rename_all = "camelCase")]
1489#[non_exhaustive]
1490pub struct OtherStopReason {
1491 pub stop_reason: String,
1493 #[serde(flatten)]
1495 pub fields: BTreeMap<String, serde_json::Value>,
1496}
1497
1498#[cfg(feature = "unstable_subagents")]
1499impl OtherStopReason {
1500 #[must_use]
1502 pub fn new(
1503 stop_reason: impl Into<String>,
1504 mut fields: BTreeMap<String, serde_json::Value>,
1505 ) -> Self {
1506 remove_idle_state_update_fields(&mut fields);
1507 Self {
1508 stop_reason: stop_reason.into(),
1509 fields,
1510 }
1511 }
1512}
1513
1514#[cfg(feature = "unstable_subagents")]
1515impl<'de> Deserialize<'de> for OtherStopReason {
1516 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1517 where
1518 D: serde::Deserializer<'de>,
1519 {
1520 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1521 let stop_reason = fields
1522 .remove("stopReason")
1523 .ok_or_else(|| serde::de::Error::missing_field("stopReason"))?;
1524 let serde_json::Value::String(stop_reason) = stop_reason else {
1525 return Err(serde::de::Error::custom("`stopReason` must be a string"));
1526 };
1527 if KNOWN_STOP_REASONS.contains(&stop_reason.as_str()) {
1528 return Err(serde::de::Error::custom(format!(
1529 "known stop reason `{stop_reason}` did not match its schema"
1530 )));
1531 }
1532 remove_idle_state_update_fields(&mut fields);
1533 Ok(Self {
1534 stop_reason,
1535 fields,
1536 })
1537 }
1538}
1539
1540#[cfg(feature = "unstable_subagents")]
1541const KNOWN_STOP_REASONS: &[&str] = &[
1542 "end_turn",
1543 "max_tokens",
1544 "max_turn_requests",
1545 "refusal",
1546 "cancelled",
1547 "error",
1548];
1549
1550#[cfg(feature = "unstable_subagents")]
1552fn remove_idle_state_update_fields(fields: &mut BTreeMap<String, serde_json::Value>) {
1553 fields.remove("stopReason");
1554 fields.remove("usage");
1555 fields.remove("_meta");
1556}
1557
1558#[cfg(all(feature = "unstable_subagents", feature = "schemars"))]
1559fn other_stop_reason_schema(schema: &mut Schema) {
1560 let known = KNOWN_STOP_REASONS
1561 .iter()
1562 .map(|stop_reason| {
1563 serde_json::json!({
1564 "properties": { "stopReason": { "const": stop_reason, "type": "string" } },
1565 "required": ["stopReason"],
1566 "type": "object"
1567 })
1568 })
1569 .collect::<Vec<_>>();
1570 schema.insert("not".into(), serde_json::json!({ "anyOf": known }));
1571}
1572
1573#[cfg(feature = "unstable_subagents")]
1575#[serde_as]
1576#[skip_serializing_none]
1577#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1578#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1579#[serde(rename_all = "camelCase")]
1580#[non_exhaustive]
1581pub struct RequiresActionStateUpdate {
1582 #[serde_as(deserialize_as = "DefaultOnError")]
1586 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1587 #[serde(default, rename = "_meta")]
1588 pub meta: Option<Meta>,
1589}
1590
1591#[cfg(feature = "unstable_subagents")]
1592impl RequiresActionStateUpdate {
1593 #[must_use]
1595 pub fn new() -> Self {
1596 Self::default()
1597 }
1598
1599 #[must_use]
1601 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1602 self.meta = meta.into_option();
1603 self
1604 }
1605}
1606
1607#[cfg(feature = "unstable_subagents")]
1621#[serde_as]
1622#[skip_serializing_none]
1623#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1624#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1625#[serde(rename_all = "camelCase")]
1626#[non_exhaustive]
1627pub struct UnknownStateUpdate {
1628 #[serde_as(deserialize_as = "DefaultOnError")]
1632 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1633 #[serde(default, rename = "_meta")]
1634 pub meta: Option<Meta>,
1635}
1636
1637#[cfg(feature = "unstable_subagents")]
1638impl UnknownStateUpdate {
1639 #[must_use]
1641 pub fn new() -> Self {
1642 Self::default()
1643 }
1644
1645 #[must_use]
1647 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1648 self.meta = meta.into_option();
1649 self
1650 }
1651}
1652
1653#[cfg(feature = "unstable_subagents")]
1655#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1656#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
1657#[cfg_attr(feature = "schemars", schemars(inline))]
1658#[cfg_attr(feature = "schemars", schemars(transform = other_state_update_schema))]
1659#[serde(rename_all = "camelCase")]
1660#[non_exhaustive]
1661pub struct OtherStateUpdate {
1662 pub state: String,
1664 #[serde(flatten)]
1666 pub fields: BTreeMap<String, serde_json::Value>,
1667}
1668
1669#[cfg(feature = "unstable_subagents")]
1670impl OtherStateUpdate {
1671 #[must_use]
1673 pub fn new(state: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
1674 fields.remove("state");
1675 Self {
1676 state: state.into(),
1677 fields,
1678 }
1679 }
1680}
1681
1682#[cfg(feature = "unstable_subagents")]
1683impl<'de> Deserialize<'de> for OtherStateUpdate {
1684 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1685 where
1686 D: serde::Deserializer<'de>,
1687 {
1688 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1689 let state = fields
1690 .remove("state")
1691 .ok_or_else(|| serde::de::Error::missing_field("state"))?;
1692 let serde_json::Value::String(state) = state else {
1693 return Err(serde::de::Error::custom("`state` must be a string"));
1694 };
1695 if is_known_state_update(&state) {
1696 return Err(serde::de::Error::custom(format!(
1697 "known state update `{state}` did not match its schema"
1698 )));
1699 }
1700 Ok(Self { state, fields })
1701 }
1702}
1703
1704#[cfg(feature = "unstable_subagents")]
1705const KNOWN_STATE_UPDATE_STATES: &[&str] = &["running", "idle", "requires_action", "unknown"];
1706
1707#[cfg(feature = "unstable_subagents")]
1708fn is_known_state_update(state: &str) -> bool {
1709 KNOWN_STATE_UPDATE_STATES.contains(&state)
1710}
1711
1712#[cfg(all(feature = "unstable_subagents", feature = "schemars"))]
1713fn other_state_update_schema(schema: &mut Schema) {
1714 let known = KNOWN_STATE_UPDATE_STATES
1715 .iter()
1716 .map(|state| {
1717 serde_json::json!({
1718 "properties": { "state": { "const": state, "type": "string" } },
1719 "required": ["state"],
1720 "type": "object"
1721 })
1722 })
1723 .collect::<Vec<_>>();
1724 schema.insert("not".into(), serde_json::json!({ "anyOf": known }));
1725}
1726
1727#[serde_as]
1731#[skip_serializing_none]
1732#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1733#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1734#[serde(rename_all = "camelCase")]
1735#[non_exhaustive]
1736pub struct CurrentModeUpdate {
1737 pub current_mode_id: SessionModeId,
1739 #[serde_as(deserialize_as = "DefaultOnError")]
1745 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1746 #[serde(default)]
1747 #[serde(rename = "_meta")]
1748 pub meta: Option<Meta>,
1749}
1750
1751impl CurrentModeUpdate {
1752 #[must_use]
1754 pub fn new(current_mode_id: impl Into<SessionModeId>) -> Self {
1755 Self {
1756 current_mode_id: current_mode_id.into(),
1757 meta: None,
1758 }
1759 }
1760
1761 #[must_use]
1767 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1768 self.meta = meta.into_option();
1769 self
1770 }
1771}
1772
1773#[serde_as]
1775#[skip_serializing_none]
1776#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1777#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1778#[serde(rename_all = "camelCase")]
1779#[non_exhaustive]
1780pub struct ConfigOptionUpdate {
1781 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
1783 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
1784 pub config_options: Vec<SessionConfigOption>,
1785 #[serde_as(deserialize_as = "DefaultOnError")]
1791 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1792 #[serde(default)]
1793 #[serde(rename = "_meta")]
1794 pub meta: Option<Meta>,
1795}
1796
1797impl ConfigOptionUpdate {
1798 #[must_use]
1800 pub fn new(config_options: Vec<SessionConfigOption>) -> Self {
1801 Self {
1802 config_options,
1803 meta: None,
1804 }
1805 }
1806
1807 #[must_use]
1813 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1814 self.meta = meta.into_option();
1815 self
1816 }
1817}
1818
1819#[serde_as]
1824#[skip_serializing_none]
1825#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1826#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1827#[serde(rename_all = "camelCase")]
1828#[non_exhaustive]
1829pub struct SessionInfoUpdate {
1830 #[serde_as(deserialize_as = "DefaultOnError")]
1832 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1833 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
1834 pub title: MaybeUndefined<String>,
1835 #[serde_as(deserialize_as = "DefaultOnError")]
1837 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1838 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
1839 pub updated_at: MaybeUndefined<String>,
1840 #[serde_as(deserialize_as = "DefaultOnError")]
1846 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1847 #[serde(default)]
1848 #[serde(rename = "_meta")]
1849 pub meta: Option<Meta>,
1850}
1851
1852impl SessionInfoUpdate {
1853 #[must_use]
1855 pub fn new() -> Self {
1856 Self::default()
1857 }
1858
1859 #[must_use]
1861 pub fn title(mut self, title: impl IntoMaybeUndefined<String>) -> Self {
1862 self.title = title.into_maybe_undefined();
1863 self
1864 }
1865
1866 #[must_use]
1868 pub fn updated_at(mut self, updated_at: impl IntoMaybeUndefined<String>) -> Self {
1869 self.updated_at = updated_at.into_maybe_undefined();
1870 self
1871 }
1872
1873 #[must_use]
1879 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1880 self.meta = meta.into_option();
1881 self
1882 }
1883}
1884
1885#[serde_as]
1887#[skip_serializing_none]
1888#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1889#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1890#[serde(rename_all = "camelCase")]
1891#[non_exhaustive]
1892pub struct UsageUpdate {
1893 pub used: u64,
1895 pub size: u64,
1897 #[serde_as(deserialize_as = "DefaultOnError")]
1899 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1900 #[serde(default)]
1901 pub cost: Option<Cost>,
1902 #[serde_as(deserialize_as = "DefaultOnError")]
1908 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1909 #[serde(default)]
1910 #[serde(rename = "_meta")]
1911 pub meta: Option<Meta>,
1912}
1913
1914impl UsageUpdate {
1915 #[must_use]
1917 pub fn new(used: u64, size: u64) -> Self {
1918 Self {
1919 used,
1920 size,
1921 cost: None,
1922 meta: None,
1923 }
1924 }
1925
1926 #[must_use]
1928 pub fn cost(mut self, cost: impl IntoOption<Cost>) -> Self {
1929 self.cost = cost.into_option();
1930 self
1931 }
1932
1933 #[must_use]
1939 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1940 self.meta = meta.into_option();
1941 self
1942 }
1943}
1944
1945#[serde_as]
1947#[skip_serializing_none]
1948#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1949#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1950#[serde(rename_all = "camelCase")]
1951#[non_exhaustive]
1952pub struct Cost {
1953 pub amount: f64,
1955 pub currency: String,
1958 #[serde_as(deserialize_as = "DefaultOnError")]
1964 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1965 #[serde(default)]
1966 #[serde(rename = "_meta")]
1967 pub meta: Option<Meta>,
1968}
1969
1970impl Cost {
1971 #[must_use]
1973 pub fn new(amount: f64, currency: impl Into<String>) -> Self {
1974 Self {
1975 amount,
1976 currency: currency.into(),
1977 meta: None,
1978 }
1979 }
1980
1981 #[must_use]
1987 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1988 self.meta = meta.into_option();
1989 self
1990 }
1991}
1992
1993#[serde_as]
1995#[skip_serializing_none]
1996#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1997#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1998#[serde(rename_all = "camelCase")]
1999#[non_exhaustive]
2000pub struct ContentChunk {
2001 pub content: ContentBlock,
2003 #[serde_as(deserialize_as = "DefaultOnError")]
2008 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2009 #[serde(default)]
2010 pub message_id: Option<MessageId>,
2011 #[serde_as(deserialize_as = "DefaultOnError")]
2017 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2018 #[serde(default)]
2019 #[serde(rename = "_meta")]
2020 pub meta: Option<Meta>,
2021}
2022
2023impl ContentChunk {
2024 #[must_use]
2026 pub fn new(content: ContentBlock) -> Self {
2027 Self {
2028 content,
2029 message_id: None,
2030 meta: None,
2031 }
2032 }
2033
2034 #[must_use]
2039 pub fn message_id(mut self, message_id: impl IntoOption<MessageId>) -> Self {
2040 self.message_id = message_id.into_option();
2041 self
2042 }
2043
2044 #[must_use]
2050 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2051 self.meta = meta.into_option();
2052 self
2053 }
2054}
2055
2056#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2058#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
2059#[serde(transparent)]
2060#[from(Arc<str>, String, &'static str)]
2061#[non_exhaustive]
2062pub struct MessageId(pub Arc<str>);
2063
2064impl MessageId {
2065 #[must_use]
2067 pub fn new(id: impl Into<Arc<str>>) -> Self {
2068 Self(id.into())
2069 }
2070}
2071
2072impl IntoOption<MessageId> for &str {
2073 fn into_option(self) -> Option<MessageId> {
2074 Some(MessageId::new(self))
2075 }
2076}
2077
2078#[serde_as]
2080#[skip_serializing_none]
2081#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2082#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2083#[serde(rename_all = "camelCase")]
2084#[non_exhaustive]
2085pub struct AvailableCommandsUpdate {
2086 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
2088 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
2089 pub available_commands: Vec<AvailableCommand>,
2090 #[serde_as(deserialize_as = "DefaultOnError")]
2096 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2097 #[serde(default)]
2098 #[serde(rename = "_meta")]
2099 pub meta: Option<Meta>,
2100}
2101
2102impl AvailableCommandsUpdate {
2103 #[must_use]
2105 pub fn new(available_commands: Vec<AvailableCommand>) -> Self {
2106 Self {
2107 available_commands,
2108 meta: None,
2109 }
2110 }
2111
2112 #[must_use]
2118 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2119 self.meta = meta.into_option();
2120 self
2121 }
2122}
2123
2124#[serde_as]
2126#[skip_serializing_none]
2127#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2128#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2129#[serde(rename_all = "camelCase")]
2130#[non_exhaustive]
2131pub struct AvailableCommand {
2132 pub name: String,
2134 pub description: String,
2136 #[serde_as(deserialize_as = "DefaultOnError")]
2138 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2139 #[serde(default)]
2140 pub input: Option<AvailableCommandInput>,
2141 #[serde_as(deserialize_as = "DefaultOnError")]
2147 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2148 #[serde(default)]
2149 #[serde(rename = "_meta")]
2150 pub meta: Option<Meta>,
2151}
2152
2153impl AvailableCommand {
2154 #[must_use]
2156 pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
2157 Self {
2158 name: name.into(),
2159 description: description.into(),
2160 input: None,
2161 meta: None,
2162 }
2163 }
2164
2165 #[must_use]
2167 pub fn input(mut self, input: impl IntoOption<AvailableCommandInput>) -> Self {
2168 self.input = input.into_option();
2169 self
2170 }
2171
2172 #[must_use]
2178 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2179 self.meta = meta.into_option();
2180 self
2181 }
2182}
2183
2184#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2186#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2187#[serde(untagged, rename_all = "camelCase")]
2188#[non_exhaustive]
2189pub enum AvailableCommandInput {
2190 Unstructured(UnstructuredCommandInput),
2192}
2193
2194#[serde_as]
2196#[skip_serializing_none]
2197#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2198#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2199#[serde(rename_all = "camelCase")]
2200#[non_exhaustive]
2201pub struct UnstructuredCommandInput {
2202 pub hint: String,
2204 #[serde_as(deserialize_as = "DefaultOnError")]
2210 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2211 #[serde(default)]
2212 #[serde(rename = "_meta")]
2213 pub meta: Option<Meta>,
2214}
2215
2216impl UnstructuredCommandInput {
2217 #[must_use]
2219 pub fn new(hint: impl Into<String>) -> Self {
2220 Self {
2221 hint: hint.into(),
2222 meta: None,
2223 }
2224 }
2225
2226 #[must_use]
2232 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2233 self.meta = meta.into_option();
2234 self
2235 }
2236}
2237
2238#[serde_as]
2246#[skip_serializing_none]
2247#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2248#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2249#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = SESSION_REQUEST_PERMISSION_METHOD_NAME)))]
2250#[serde(rename_all = "camelCase")]
2251#[non_exhaustive]
2252pub struct RequestPermissionRequest {
2253 pub session_id: SessionId,
2255 pub tool_call: ToolCallUpdate,
2257 pub options: Vec<PermissionOption>,
2259 #[serde_as(deserialize_as = "DefaultOnError")]
2265 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2266 #[serde(default)]
2267 #[serde(rename = "_meta")]
2268 pub meta: Option<Meta>,
2269}
2270
2271impl RequestPermissionRequest {
2272 #[must_use]
2274 pub fn new(
2275 session_id: impl Into<SessionId>,
2276 tool_call: ToolCallUpdate,
2277 options: Vec<PermissionOption>,
2278 ) -> Self {
2279 Self {
2280 session_id: session_id.into(),
2281 tool_call,
2282 options,
2283 meta: None,
2284 }
2285 }
2286
2287 #[must_use]
2293 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2294 self.meta = meta.into_option();
2295 self
2296 }
2297}
2298
2299#[serde_as]
2301#[skip_serializing_none]
2302#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2303#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2304#[serde(rename_all = "camelCase")]
2305#[non_exhaustive]
2306pub struct PermissionOption {
2307 pub option_id: PermissionOptionId,
2309 pub name: String,
2311 pub kind: PermissionOptionKind,
2313 #[serde_as(deserialize_as = "DefaultOnError")]
2319 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2320 #[serde(default)]
2321 #[serde(rename = "_meta")]
2322 pub meta: Option<Meta>,
2323}
2324
2325impl PermissionOption {
2326 #[must_use]
2328 pub fn new(
2329 option_id: impl Into<PermissionOptionId>,
2330 name: impl Into<String>,
2331 kind: PermissionOptionKind,
2332 ) -> Self {
2333 Self {
2334 option_id: option_id.into(),
2335 name: name.into(),
2336 kind,
2337 meta: None,
2338 }
2339 }
2340
2341 #[must_use]
2347 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2348 self.meta = meta.into_option();
2349 self
2350 }
2351}
2352
2353#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2355#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
2356#[serde(transparent)]
2357#[from(Arc<str>, String, &'static str)]
2358#[non_exhaustive]
2359pub struct PermissionOptionId(pub Arc<str>);
2360
2361impl PermissionOptionId {
2362 #[must_use]
2364 pub fn new(id: impl Into<Arc<str>>) -> Self {
2365 Self(id.into())
2366 }
2367}
2368
2369#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2373#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
2374#[serde(rename_all = "snake_case")]
2375#[non_exhaustive]
2376pub enum PermissionOptionKind {
2377 AllowOnce,
2379 AllowAlways,
2381 RejectOnce,
2383 RejectAlways,
2385}
2386
2387#[serde_as]
2389#[skip_serializing_none]
2390#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2391#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2392#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = SESSION_REQUEST_PERMISSION_METHOD_NAME)))]
2393#[serde(rename_all = "camelCase")]
2394#[non_exhaustive]
2395pub struct RequestPermissionResponse {
2396 pub outcome: RequestPermissionOutcome,
2399 #[serde_as(deserialize_as = "DefaultOnError")]
2405 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2406 #[serde(default)]
2407 #[serde(rename = "_meta")]
2408 pub meta: Option<Meta>,
2409}
2410
2411impl RequestPermissionResponse {
2412 #[must_use]
2414 pub fn new(outcome: RequestPermissionOutcome) -> Self {
2415 Self {
2416 outcome,
2417 meta: None,
2418 }
2419 }
2420
2421 #[must_use]
2427 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2428 self.meta = meta.into_option();
2429 self
2430 }
2431}
2432
2433#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2435#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2436#[serde(tag = "outcome", rename_all = "snake_case")]
2437#[cfg_attr(feature = "schemars", schemars(extend("discriminator" = {"propertyName": "outcome"})))]
2438#[non_exhaustive]
2439pub enum RequestPermissionOutcome {
2440 Cancelled,
2448 #[serde(rename_all = "camelCase")]
2450 Selected(SelectedPermissionOutcome),
2451}
2452
2453#[serde_as]
2455#[skip_serializing_none]
2456#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2457#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2458#[serde(rename_all = "camelCase")]
2459#[non_exhaustive]
2460pub struct SelectedPermissionOutcome {
2461 pub option_id: PermissionOptionId,
2463 #[serde_as(deserialize_as = "DefaultOnError")]
2469 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2470 #[serde(default)]
2471 #[serde(rename = "_meta")]
2472 pub meta: Option<Meta>,
2473}
2474
2475impl SelectedPermissionOutcome {
2476 #[must_use]
2478 pub fn new(option_id: impl Into<PermissionOptionId>) -> Self {
2479 Self {
2480 option_id: option_id.into(),
2481 meta: None,
2482 }
2483 }
2484
2485 #[must_use]
2491 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2492 self.meta = meta.into_option();
2493 self
2494 }
2495}
2496
2497#[serde_as]
2503#[skip_serializing_none]
2504#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2505#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2506#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = FS_WRITE_TEXT_FILE_METHOD_NAME)))]
2507#[serde(rename_all = "camelCase")]
2508#[non_exhaustive]
2509pub struct WriteTextFileRequest {
2510 pub session_id: SessionId,
2512 pub path: PathBuf,
2514 pub content: String,
2516 #[serde_as(deserialize_as = "DefaultOnError")]
2522 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2523 #[serde(default)]
2524 #[serde(rename = "_meta")]
2525 pub meta: Option<Meta>,
2526}
2527
2528impl WriteTextFileRequest {
2529 #[must_use]
2531 pub fn new(
2532 session_id: impl Into<SessionId>,
2533 path: impl Into<PathBuf>,
2534 content: impl Into<String>,
2535 ) -> Self {
2536 Self {
2537 session_id: session_id.into(),
2538 path: path.into(),
2539 content: content.into(),
2540 meta: None,
2541 }
2542 }
2543
2544 #[must_use]
2550 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2551 self.meta = meta.into_option();
2552 self
2553 }
2554}
2555
2556crate::serde_util::default_on_null! {
2557 #[serde_as]
2559 #[skip_serializing_none]
2560 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2561 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
2562 #[serde(rename_all = "camelCase")]
2563 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = FS_WRITE_TEXT_FILE_METHOD_NAME)))]
2564 #[non_exhaustive]
2565 pub struct WriteTextFileResponse {
2566 #[serde_as(deserialize_as = "DefaultOnError")]
2572 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2573 #[serde(default)]
2574 #[serde(rename = "_meta")]
2575 pub meta: Option<Meta>,
2576 }
2577}
2578
2579impl WriteTextFileResponse {
2580 #[must_use]
2582 pub fn new() -> Self {
2583 Self::default()
2584 }
2585
2586 #[must_use]
2592 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2593 self.meta = meta.into_option();
2594 self
2595 }
2596}
2597
2598#[serde_as]
2604#[skip_serializing_none]
2605#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2606#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2607#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = FS_READ_TEXT_FILE_METHOD_NAME)))]
2608#[serde(rename_all = "camelCase")]
2609#[non_exhaustive]
2610pub struct ReadTextFileRequest {
2611 pub session_id: SessionId,
2613 pub path: PathBuf,
2615 #[serde(default)]
2617 pub line: Option<u32>,
2618 #[serde(default)]
2620 pub limit: Option<u32>,
2621 #[serde_as(deserialize_as = "DefaultOnError")]
2627 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2628 #[serde(default)]
2629 #[serde(rename = "_meta")]
2630 pub meta: Option<Meta>,
2631}
2632
2633impl ReadTextFileRequest {
2634 #[must_use]
2636 pub fn new(session_id: impl Into<SessionId>, path: impl Into<PathBuf>) -> Self {
2637 Self {
2638 session_id: session_id.into(),
2639 path: path.into(),
2640 line: None,
2641 limit: None,
2642 meta: None,
2643 }
2644 }
2645
2646 #[must_use]
2648 pub fn line(mut self, line: impl IntoOption<u32>) -> Self {
2649 self.line = line.into_option();
2650 self
2651 }
2652
2653 #[must_use]
2655 pub fn limit(mut self, limit: impl IntoOption<u32>) -> Self {
2656 self.limit = limit.into_option();
2657 self
2658 }
2659
2660 #[must_use]
2666 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2667 self.meta = meta.into_option();
2668 self
2669 }
2670}
2671
2672#[serde_as]
2674#[skip_serializing_none]
2675#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2676#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2677#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = FS_READ_TEXT_FILE_METHOD_NAME)))]
2678#[serde(rename_all = "camelCase")]
2679#[non_exhaustive]
2680pub struct ReadTextFileResponse {
2681 pub content: String,
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 ReadTextFileResponse {
2696 #[must_use]
2698 pub fn new(content: impl Into<String>) -> Self {
2699 Self {
2700 content: content.into(),
2701 meta: None,
2702 }
2703 }
2704
2705 #[must_use]
2711 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2712 self.meta = meta.into_option();
2713 self
2714 }
2715}
2716
2717#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2721#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
2722#[serde(transparent)]
2723#[from(Arc<str>, String, &'static str)]
2724#[non_exhaustive]
2725pub struct TerminalId(pub Arc<str>);
2726
2727impl TerminalId {
2728 #[must_use]
2730 pub fn new(id: impl Into<Arc<str>>) -> Self {
2731 Self(id.into())
2732 }
2733}
2734
2735#[serde_as]
2737#[skip_serializing_none]
2738#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2739#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2740#[serde(rename_all = "camelCase")]
2741#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_CREATE_METHOD_NAME)))]
2742#[non_exhaustive]
2743pub struct CreateTerminalRequest {
2744 pub session_id: SessionId,
2746 pub command: String,
2748 #[serde_as(deserialize_as = "DefaultOnNull")]
2750 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-null" = true)))]
2751 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2752 pub args: Vec<String>,
2753 #[serde_as(deserialize_as = "DefaultOnNull")]
2755 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-null" = true)))]
2756 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2757 pub env: Vec<EnvVariable>,
2758 #[serde(default)]
2760 pub cwd: Option<PathBuf>,
2761 #[serde(default)]
2770 pub output_byte_limit: Option<u64>,
2771 #[serde_as(deserialize_as = "DefaultOnError")]
2777 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2778 #[serde(default)]
2779 #[serde(rename = "_meta")]
2780 pub meta: Option<Meta>,
2781}
2782
2783impl CreateTerminalRequest {
2784 #[must_use]
2786 pub fn new(session_id: impl Into<SessionId>, command: impl Into<String>) -> Self {
2787 Self {
2788 session_id: session_id.into(),
2789 command: command.into(),
2790 args: Vec::new(),
2791 env: Vec::new(),
2792 cwd: None,
2793 output_byte_limit: None,
2794 meta: None,
2795 }
2796 }
2797
2798 #[must_use]
2800 pub fn args(mut self, args: Vec<String>) -> Self {
2801 self.args = args;
2802 self
2803 }
2804
2805 #[must_use]
2807 pub fn env(mut self, env: Vec<EnvVariable>) -> Self {
2808 self.env = env;
2809 self
2810 }
2811
2812 #[must_use]
2814 pub fn cwd(mut self, cwd: impl IntoOption<PathBuf>) -> Self {
2815 self.cwd = cwd.into_option();
2816 self
2817 }
2818
2819 #[must_use]
2828 pub fn output_byte_limit(mut self, output_byte_limit: impl IntoOption<u64>) -> Self {
2829 self.output_byte_limit = output_byte_limit.into_option();
2830 self
2831 }
2832
2833 #[must_use]
2839 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2840 self.meta = meta.into_option();
2841 self
2842 }
2843}
2844
2845#[serde_as]
2847#[skip_serializing_none]
2848#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2849#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2850#[serde(rename_all = "camelCase")]
2851#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_CREATE_METHOD_NAME)))]
2852#[non_exhaustive]
2853pub struct CreateTerminalResponse {
2854 pub terminal_id: TerminalId,
2856 #[serde_as(deserialize_as = "DefaultOnError")]
2862 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2863 #[serde(default)]
2864 #[serde(rename = "_meta")]
2865 pub meta: Option<Meta>,
2866}
2867
2868impl CreateTerminalResponse {
2869 #[must_use]
2871 pub fn new(terminal_id: impl Into<TerminalId>) -> Self {
2872 Self {
2873 terminal_id: terminal_id.into(),
2874 meta: None,
2875 }
2876 }
2877
2878 #[must_use]
2884 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2885 self.meta = meta.into_option();
2886 self
2887 }
2888}
2889
2890#[serde_as]
2892#[skip_serializing_none]
2893#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2894#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2895#[serde(rename_all = "camelCase")]
2896#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_OUTPUT_METHOD_NAME)))]
2897#[non_exhaustive]
2898pub struct TerminalOutputRequest {
2899 pub session_id: SessionId,
2901 pub terminal_id: TerminalId,
2903 #[serde_as(deserialize_as = "DefaultOnError")]
2909 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2910 #[serde(default)]
2911 #[serde(rename = "_meta")]
2912 pub meta: Option<Meta>,
2913}
2914
2915impl TerminalOutputRequest {
2916 #[must_use]
2918 pub fn new(session_id: impl Into<SessionId>, terminal_id: impl Into<TerminalId>) -> Self {
2919 Self {
2920 session_id: session_id.into(),
2921 terminal_id: terminal_id.into(),
2922 meta: None,
2923 }
2924 }
2925
2926 #[must_use]
2932 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2933 self.meta = meta.into_option();
2934 self
2935 }
2936}
2937
2938#[serde_as]
2940#[skip_serializing_none]
2941#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2942#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2943#[serde(rename_all = "camelCase")]
2944#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_OUTPUT_METHOD_NAME)))]
2945#[non_exhaustive]
2946pub struct TerminalOutputResponse {
2947 pub output: String,
2949 pub truncated: bool,
2951 #[serde_as(deserialize_as = "DefaultOnError")]
2953 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2954 #[serde(default)]
2955 pub exit_status: Option<TerminalExitStatus>,
2956 #[serde_as(deserialize_as = "DefaultOnError")]
2962 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2963 #[serde(default)]
2964 #[serde(rename = "_meta")]
2965 pub meta: Option<Meta>,
2966}
2967
2968impl TerminalOutputResponse {
2969 #[must_use]
2971 pub fn new(output: impl Into<String>, truncated: bool) -> Self {
2972 Self {
2973 output: output.into(),
2974 truncated,
2975 exit_status: None,
2976 meta: None,
2977 }
2978 }
2979
2980 #[must_use]
2982 pub fn exit_status(mut self, exit_status: impl IntoOption<TerminalExitStatus>) -> Self {
2983 self.exit_status = exit_status.into_option();
2984 self
2985 }
2986
2987 #[must_use]
2993 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2994 self.meta = meta.into_option();
2995 self
2996 }
2997}
2998
2999#[serde_as]
3001#[skip_serializing_none]
3002#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3003#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3004#[serde(rename_all = "camelCase")]
3005#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_RELEASE_METHOD_NAME)))]
3006#[non_exhaustive]
3007pub struct ReleaseTerminalRequest {
3008 pub session_id: SessionId,
3010 pub terminal_id: TerminalId,
3012 #[serde_as(deserialize_as = "DefaultOnError")]
3018 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3019 #[serde(default)]
3020 #[serde(rename = "_meta")]
3021 pub meta: Option<Meta>,
3022}
3023
3024impl ReleaseTerminalRequest {
3025 #[must_use]
3027 pub fn new(session_id: impl Into<SessionId>, terminal_id: impl Into<TerminalId>) -> Self {
3028 Self {
3029 session_id: session_id.into(),
3030 terminal_id: terminal_id.into(),
3031 meta: None,
3032 }
3033 }
3034
3035 #[must_use]
3041 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3042 self.meta = meta.into_option();
3043 self
3044 }
3045}
3046
3047crate::serde_util::default_on_null! {
3048 #[serde_as]
3050 #[skip_serializing_none]
3051 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3052 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
3053 #[serde(rename_all = "camelCase")]
3054 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_RELEASE_METHOD_NAME)))]
3055 #[non_exhaustive]
3056 pub struct ReleaseTerminalResponse {
3057 #[serde_as(deserialize_as = "DefaultOnError")]
3063 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3064 #[serde(default)]
3065 #[serde(rename = "_meta")]
3066 pub meta: Option<Meta>,
3067 }
3068}
3069
3070impl ReleaseTerminalResponse {
3071 #[must_use]
3073 pub fn new() -> Self {
3074 Self::default()
3075 }
3076
3077 #[must_use]
3083 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3084 self.meta = meta.into_option();
3085 self
3086 }
3087}
3088
3089#[serde_as]
3091#[skip_serializing_none]
3092#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3093#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3094#[serde(rename_all = "camelCase")]
3095#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_KILL_METHOD_NAME)))]
3096#[non_exhaustive]
3097pub struct KillTerminalRequest {
3098 pub session_id: SessionId,
3100 pub terminal_id: TerminalId,
3102 #[serde_as(deserialize_as = "DefaultOnError")]
3108 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3109 #[serde(default)]
3110 #[serde(rename = "_meta")]
3111 pub meta: Option<Meta>,
3112}
3113
3114impl KillTerminalRequest {
3115 #[must_use]
3117 pub fn new(session_id: impl Into<SessionId>, terminal_id: impl Into<TerminalId>) -> Self {
3118 Self {
3119 session_id: session_id.into(),
3120 terminal_id: terminal_id.into(),
3121 meta: None,
3122 }
3123 }
3124
3125 #[must_use]
3131 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3132 self.meta = meta.into_option();
3133 self
3134 }
3135}
3136
3137crate::serde_util::default_on_null! {
3138 #[serde_as]
3140 #[skip_serializing_none]
3141 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3142 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
3143 #[serde(rename_all = "camelCase")]
3144 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_KILL_METHOD_NAME)))]
3145 #[non_exhaustive]
3146 pub struct KillTerminalResponse {
3147 #[serde_as(deserialize_as = "DefaultOnError")]
3153 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3154 #[serde(default)]
3155 #[serde(rename = "_meta")]
3156 pub meta: Option<Meta>,
3157 }
3158}
3159
3160impl KillTerminalResponse {
3161 #[must_use]
3163 pub fn new() -> Self {
3164 Self::default()
3165 }
3166
3167 #[must_use]
3173 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3174 self.meta = meta.into_option();
3175 self
3176 }
3177}
3178
3179#[serde_as]
3181#[skip_serializing_none]
3182#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3183#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3184#[serde(rename_all = "camelCase")]
3185#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_WAIT_FOR_EXIT_METHOD_NAME)))]
3186#[non_exhaustive]
3187pub struct WaitForTerminalExitRequest {
3188 pub session_id: SessionId,
3190 pub terminal_id: TerminalId,
3192 #[serde_as(deserialize_as = "DefaultOnError")]
3198 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3199 #[serde(default)]
3200 #[serde(rename = "_meta")]
3201 pub meta: Option<Meta>,
3202}
3203
3204impl WaitForTerminalExitRequest {
3205 #[must_use]
3207 pub fn new(session_id: impl Into<SessionId>, terminal_id: impl Into<TerminalId>) -> Self {
3208 Self {
3209 session_id: session_id.into(),
3210 terminal_id: terminal_id.into(),
3211 meta: None,
3212 }
3213 }
3214
3215 #[must_use]
3221 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3222 self.meta = meta.into_option();
3223 self
3224 }
3225}
3226
3227crate::serde_util::default_on_null! {
3228 #[serde_as]
3230 #[skip_serializing_none]
3231 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3232 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
3233 #[serde(rename_all = "camelCase")]
3234 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = TERMINAL_WAIT_FOR_EXIT_METHOD_NAME)))]
3235 #[non_exhaustive]
3236 pub struct WaitForTerminalExitResponse {
3237 #[serde(flatten)]
3239 pub exit_status: TerminalExitStatus,
3240 #[serde_as(deserialize_as = "DefaultOnError")]
3246 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3247 #[serde(default)]
3248 #[serde(rename = "_meta")]
3249 pub meta: Option<Meta>,
3250 }
3251}
3252
3253impl WaitForTerminalExitResponse {
3254 #[must_use]
3256 pub fn new(exit_status: TerminalExitStatus) -> Self {
3257 Self {
3258 exit_status,
3259 meta: None,
3260 }
3261 }
3262
3263 #[must_use]
3269 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3270 self.meta = meta.into_option();
3271 self
3272 }
3273}
3274
3275#[serde_as]
3277#[skip_serializing_none]
3278#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3279#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3280#[serde(rename_all = "camelCase")]
3281#[non_exhaustive]
3282pub struct TerminalExitStatus {
3283 #[serde_as(deserialize_as = "DefaultOnError")]
3285 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3286 #[serde(default)]
3287 pub exit_code: Option<u32>,
3288 #[serde_as(deserialize_as = "DefaultOnError")]
3290 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3291 #[serde(default)]
3292 pub signal: Option<String>,
3293 #[serde_as(deserialize_as = "DefaultOnError")]
3299 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3300 #[serde(default)]
3301 #[serde(rename = "_meta")]
3302 pub meta: Option<Meta>,
3303}
3304
3305impl TerminalExitStatus {
3306 #[must_use]
3308 pub fn new() -> Self {
3309 Self::default()
3310 }
3311
3312 #[must_use]
3314 pub fn exit_code(mut self, exit_code: impl IntoOption<u32>) -> Self {
3315 self.exit_code = exit_code.into_option();
3316 self
3317 }
3318
3319 #[must_use]
3321 pub fn signal(mut self, signal: impl IntoOption<String>) -> Self {
3322 self.signal = signal.into_option();
3323 self
3324 }
3325
3326 #[must_use]
3332 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3333 self.meta = meta.into_option();
3334 self
3335 }
3336}
3337
3338#[serde_as]
3347#[skip_serializing_none]
3348#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3349#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3350#[serde(rename_all = "camelCase")]
3351#[non_exhaustive]
3352pub struct ClientCapabilities {
3353 #[serde_as(deserialize_as = "DefaultOnError")]
3356 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3357 #[serde(default)]
3358 pub fs: FileSystemCapabilities,
3359 #[serde_as(deserialize_as = "DefaultOnError")]
3361 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3362 #[serde(default)]
3363 pub terminal: bool,
3364 #[serde_as(deserialize_as = "DefaultOnError")]
3369 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3370 #[serde(default)]
3371 pub session: Option<ClientSessionCapabilities>,
3372 #[cfg(feature = "unstable_subagents")]
3383 #[serde_as(deserialize_as = "DefaultOnError")]
3384 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3385 #[serde(default)]
3386 pub subagents: Option<SubagentCapabilities>,
3387 #[cfg(feature = "unstable_plan_operations")]
3396 #[serde_as(deserialize_as = "DefaultOnError")]
3397 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3398 #[serde(default)]
3399 pub plan: Option<PlanCapabilities>,
3400 #[serde_as(deserialize_as = "DefaultOnError")]
3404 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3405 #[serde(default)]
3406 pub auth: AuthCapabilities,
3407 #[serde_as(deserialize_as = "DefaultOnError")]
3413 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3414 #[serde(default)]
3415 pub elicitation: Option<ElicitationCapabilities>,
3416 #[serde_as(deserialize_as = "DefaultOnError")]
3422 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3423 #[serde(default)]
3424 #[serde(rename = "_meta")]
3425 pub meta: Option<Meta>,
3426}
3427
3428impl ClientCapabilities {
3429 #[must_use]
3431 pub fn new() -> Self {
3432 Self::default()
3433 }
3434
3435 #[must_use]
3438 pub fn fs(mut self, fs: FileSystemCapabilities) -> Self {
3439 self.fs = fs;
3440 self
3441 }
3442
3443 #[must_use]
3445 pub fn terminal(mut self, terminal: bool) -> Self {
3446 self.terminal = terminal;
3447 self
3448 }
3449
3450 #[must_use]
3452 pub fn session(mut self, session: impl IntoOption<ClientSessionCapabilities>) -> Self {
3453 self.session = session.into_option();
3454 self
3455 }
3456
3457 #[cfg(feature = "unstable_subagents")]
3463 #[must_use]
3464 pub fn subagents(mut self, subagents: impl IntoOption<SubagentCapabilities>) -> Self {
3465 self.subagents = subagents.into_option();
3466 self
3467 }
3468
3469 #[cfg(feature = "unstable_plan_operations")]
3478 #[must_use]
3479 pub fn plan(mut self, plan: impl IntoOption<PlanCapabilities>) -> Self {
3480 self.plan = plan.into_option();
3481 self
3482 }
3483
3484 #[must_use]
3488 pub fn auth(mut self, auth: AuthCapabilities) -> Self {
3489 self.auth = auth;
3490 self
3491 }
3492
3493 #[must_use]
3496 pub fn elicitation(mut self, elicitation: impl IntoOption<ElicitationCapabilities>) -> Self {
3497 self.elicitation = elicitation.into_option();
3498 self
3499 }
3500
3501 #[must_use]
3507 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3508 self.meta = meta.into_option();
3509 self
3510 }
3511}
3512
3513#[cfg(feature = "unstable_subagents")]
3524#[serde_as]
3525#[skip_serializing_none]
3526#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3527#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3528#[non_exhaustive]
3529pub struct SubagentCapabilities {
3530 #[serde_as(deserialize_as = "DefaultOnError")]
3534 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3535 #[serde(default)]
3536 #[serde(rename = "_meta")]
3537 pub meta: Option<Meta>,
3538}
3539
3540#[cfg(feature = "unstable_subagents")]
3541impl SubagentCapabilities {
3542 #[must_use]
3544 pub fn new() -> Self {
3545 Self::default()
3546 }
3547
3548 #[must_use]
3552 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3553 self.meta = meta.into_option();
3554 self
3555 }
3556}
3557
3558#[serde_as]
3560#[skip_serializing_none]
3561#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3562#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3563#[serde(rename_all = "camelCase")]
3564#[non_exhaustive]
3565pub struct ClientSessionCapabilities {
3566 #[serde_as(deserialize_as = "DefaultOnError")]
3569 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3570 #[serde(default)]
3571 pub compaction: Option<CompactionCapabilities>,
3572 #[serde_as(deserialize_as = "DefaultOnError")]
3577 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3578 #[serde(default)]
3579 pub config_options: Option<SessionConfigOptionsCapabilities>,
3580 #[serde_as(deserialize_as = "DefaultOnError")]
3585 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3586 #[serde(default)]
3587 pub notices: Option<NoticeCapabilities>,
3588 #[serde_as(deserialize_as = "DefaultOnError")]
3594 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3595 #[serde(default)]
3596 #[serde(rename = "_meta")]
3597 pub meta: Option<Meta>,
3598}
3599
3600impl ClientSessionCapabilities {
3601 #[must_use]
3603 pub fn new() -> Self {
3604 Self::default()
3605 }
3606
3607 #[must_use]
3609 pub fn compaction(mut self, compaction: impl IntoOption<CompactionCapabilities>) -> Self {
3610 self.compaction = compaction.into_option();
3611 self
3612 }
3613
3614 #[must_use]
3619 pub fn config_options(
3620 mut self,
3621 config_options: impl IntoOption<SessionConfigOptionsCapabilities>,
3622 ) -> Self {
3623 self.config_options = config_options.into_option();
3624 self
3625 }
3626
3627 #[must_use]
3629 pub fn notices(mut self, notices: impl IntoOption<NoticeCapabilities>) -> Self {
3630 self.notices = notices.into_option();
3631 self
3632 }
3633
3634 #[must_use]
3640 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3641 self.meta = meta.into_option();
3642 self
3643 }
3644}
3645
3646#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3648#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3649#[serde(rename_all = "camelCase")]
3650#[non_exhaustive]
3651pub struct CompactionCapabilities {}
3652
3653impl CompactionCapabilities {
3654 #[must_use]
3656 pub fn new() -> Self {
3657 Self {}
3658 }
3659}
3660
3661#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3663#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3664#[serde(rename_all = "camelCase")]
3665#[non_exhaustive]
3666pub struct NoticeCapabilities {}
3667
3668impl NoticeCapabilities {
3669 #[must_use]
3671 pub fn new() -> Self {
3672 Self {}
3673 }
3674}
3675
3676#[serde_as]
3678#[skip_serializing_none]
3679#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3680#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3681#[serde(rename_all = "camelCase")]
3682#[non_exhaustive]
3683pub struct SessionConfigOptionsCapabilities {
3684 #[serde_as(deserialize_as = "DefaultOnError")]
3691 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3692 #[serde(default)]
3693 pub boolean: Option<BooleanConfigOptionCapabilities>,
3694 #[serde_as(deserialize_as = "DefaultOnError")]
3700 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3701 #[serde(default)]
3702 #[serde(rename = "_meta")]
3703 pub meta: Option<Meta>,
3704}
3705
3706impl SessionConfigOptionsCapabilities {
3707 #[must_use]
3709 pub fn new() -> Self {
3710 Self::default()
3711 }
3712
3713 #[must_use]
3720 pub fn boolean(mut self, boolean: impl IntoOption<BooleanConfigOptionCapabilities>) -> Self {
3721 self.boolean = boolean.into_option();
3722 self
3723 }
3724
3725 #[must_use]
3731 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3732 self.meta = meta.into_option();
3733 self
3734 }
3735}
3736
3737#[serde_as]
3741#[skip_serializing_none]
3742#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3743#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3744#[non_exhaustive]
3745pub struct BooleanConfigOptionCapabilities {
3746 #[serde_as(deserialize_as = "DefaultOnError")]
3752 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3753 #[serde(default)]
3754 #[serde(rename = "_meta")]
3755 pub meta: Option<Meta>,
3756}
3757
3758impl BooleanConfigOptionCapabilities {
3759 #[must_use]
3761 pub fn new() -> Self {
3762 Self::default()
3763 }
3764
3765 #[must_use]
3771 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3772 self.meta = meta.into_option();
3773 self
3774 }
3775}
3776
3777#[serde_as]
3783#[skip_serializing_none]
3784#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3785#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3786#[serde(rename_all = "camelCase")]
3787#[non_exhaustive]
3788pub struct AuthCapabilities {
3789 #[serde_as(deserialize_as = "DefaultOnError")]
3795 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3796 #[serde(default)]
3797 pub terminal: bool,
3798 #[serde_as(deserialize_as = "DefaultOnError")]
3804 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3805 #[serde(default)]
3806 #[serde(rename = "_meta")]
3807 pub meta: Option<Meta>,
3808}
3809
3810impl AuthCapabilities {
3811 #[must_use]
3813 pub fn new() -> Self {
3814 Self::default()
3815 }
3816
3817 #[must_use]
3824 pub fn terminal(mut self, terminal: bool) -> Self {
3825 self.terminal = terminal;
3826 self
3827 }
3828
3829 #[must_use]
3835 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3836 self.meta = meta.into_option();
3837 self
3838 }
3839}
3840
3841#[serde_as]
3845#[skip_serializing_none]
3846#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3847#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3848#[serde(rename_all = "camelCase")]
3849#[non_exhaustive]
3850pub struct FileSystemCapabilities {
3851 #[serde_as(deserialize_as = "DefaultOnError")]
3853 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3854 #[serde(default)]
3855 pub read_text_file: bool,
3856 #[serde_as(deserialize_as = "DefaultOnError")]
3858 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3859 #[serde(default)]
3860 pub write_text_file: bool,
3861 #[serde_as(deserialize_as = "DefaultOnError")]
3867 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3868 #[serde(default)]
3869 #[serde(rename = "_meta")]
3870 pub meta: Option<Meta>,
3871}
3872
3873impl FileSystemCapabilities {
3874 #[must_use]
3876 pub fn new() -> Self {
3877 Self::default()
3878 }
3879
3880 #[must_use]
3882 pub fn read_text_file(mut self, read_text_file: bool) -> Self {
3883 self.read_text_file = read_text_file;
3884 self
3885 }
3886
3887 #[must_use]
3889 pub fn write_text_file(mut self, write_text_file: bool) -> Self {
3890 self.write_text_file = write_text_file;
3891 self
3892 }
3893
3894 #[must_use]
3900 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3901 self.meta = meta.into_option();
3902 self
3903 }
3904}
3905
3906#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3912#[non_exhaustive]
3913pub struct ClientMethodNames {
3914 pub session_request_permission: &'static str,
3916 pub session_update: &'static str,
3918 pub fs_write_text_file: &'static str,
3920 pub fs_read_text_file: &'static str,
3922 pub terminal_create: &'static str,
3924 pub terminal_output: &'static str,
3926 pub terminal_release: &'static str,
3928 pub terminal_wait_for_exit: &'static str,
3930 pub terminal_kill: &'static str,
3932 #[cfg(feature = "unstable_mcp_over_acp")]
3934 pub mcp_message: &'static str,
3935 pub elicitation_create: &'static str,
3937 pub elicitation_complete: &'static str,
3939 #[cfg(feature = "unstable_auth_status")]
3941 pub auth_status_update: &'static str,
3942}
3943
3944pub const CLIENT_METHOD_NAMES: ClientMethodNames = ClientMethodNames {
3946 session_update: SESSION_UPDATE_NOTIFICATION,
3947 session_request_permission: SESSION_REQUEST_PERMISSION_METHOD_NAME,
3948 fs_write_text_file: FS_WRITE_TEXT_FILE_METHOD_NAME,
3949 fs_read_text_file: FS_READ_TEXT_FILE_METHOD_NAME,
3950 terminal_create: TERMINAL_CREATE_METHOD_NAME,
3951 terminal_output: TERMINAL_OUTPUT_METHOD_NAME,
3952 terminal_release: TERMINAL_RELEASE_METHOD_NAME,
3953 terminal_wait_for_exit: TERMINAL_WAIT_FOR_EXIT_METHOD_NAME,
3954 terminal_kill: TERMINAL_KILL_METHOD_NAME,
3955 #[cfg(feature = "unstable_mcp_over_acp")]
3956 mcp_message: MCP_MESSAGE_METHOD_NAME,
3957 elicitation_create: ELICITATION_CREATE_METHOD_NAME,
3958 elicitation_complete: ELICITATION_COMPLETE_NOTIFICATION,
3959 #[cfg(feature = "unstable_auth_status")]
3960 auth_status_update: AUTH_STATUS_UPDATE_NOTIFICATION,
3961};
3962
3963pub(crate) const SESSION_UPDATE_NOTIFICATION: &str = "session/update";
3965pub(crate) const SESSION_REQUEST_PERMISSION_METHOD_NAME: &str = "session/request_permission";
3967pub(crate) const FS_WRITE_TEXT_FILE_METHOD_NAME: &str = "fs/write_text_file";
3969pub(crate) const FS_READ_TEXT_FILE_METHOD_NAME: &str = "fs/read_text_file";
3971pub(crate) const TERMINAL_CREATE_METHOD_NAME: &str = "terminal/create";
3973pub(crate) const TERMINAL_OUTPUT_METHOD_NAME: &str = "terminal/output";
3975pub(crate) const TERMINAL_RELEASE_METHOD_NAME: &str = "terminal/release";
3977pub(crate) const TERMINAL_WAIT_FOR_EXIT_METHOD_NAME: &str = "terminal/wait_for_exit";
3979pub(crate) const TERMINAL_KILL_METHOD_NAME: &str = "terminal/kill";
3981pub(crate) const ELICITATION_CREATE_METHOD_NAME: &str = "elicitation/create";
3983pub(crate) const ELICITATION_COMPLETE_NOTIFICATION: &str = "elicitation/complete";
3985#[cfg(feature = "unstable_auth_status")]
3987pub(crate) const AUTH_STATUS_UPDATE_NOTIFICATION: &str = "auth/status_update";
3988
3989#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3996#[derive(Clone, Debug, Serialize, Deserialize)]
3997#[serde(untagged)]
3998#[cfg_attr(feature = "schemars", schemars(inline))]
3999#[non_exhaustive]
4000#[allow(clippy::large_enum_variant)]
4001pub enum AgentRequest {
4002 WriteTextFileRequest(WriteTextFileRequest),
4009 ReadTextFileRequest(ReadTextFileRequest),
4016 RequestPermissionRequest(RequestPermissionRequest),
4027 CreateTerminalRequest(CreateTerminalRequest),
4042 TerminalOutputRequest(TerminalOutputRequest),
4049 ReleaseTerminalRequest(ReleaseTerminalRequest),
4062 WaitForTerminalExitRequest(WaitForTerminalExitRequest),
4066 KillTerminalRequest(KillTerminalRequest),
4079 CreateElicitationRequest(CreateElicitationRequest),
4083 #[cfg(feature = "unstable_mcp_over_acp")]
4089 MessageMcpRequest(MessageMcpRequest),
4090 ExtMethodRequest(ExtRequest),
4098}
4099
4100impl AgentRequest {
4101 #[must_use]
4103 pub fn method(&self) -> &str {
4104 match self {
4105 Self::WriteTextFileRequest(_) => CLIENT_METHOD_NAMES.fs_write_text_file,
4106 Self::ReadTextFileRequest(_) => CLIENT_METHOD_NAMES.fs_read_text_file,
4107 Self::RequestPermissionRequest(_) => CLIENT_METHOD_NAMES.session_request_permission,
4108 Self::CreateTerminalRequest(_) => CLIENT_METHOD_NAMES.terminal_create,
4109 Self::TerminalOutputRequest(_) => CLIENT_METHOD_NAMES.terminal_output,
4110 Self::ReleaseTerminalRequest(_) => CLIENT_METHOD_NAMES.terminal_release,
4111 Self::WaitForTerminalExitRequest(_) => CLIENT_METHOD_NAMES.terminal_wait_for_exit,
4112 Self::KillTerminalRequest(_) => CLIENT_METHOD_NAMES.terminal_kill,
4113 Self::CreateElicitationRequest(_) => CLIENT_METHOD_NAMES.elicitation_create,
4114 #[cfg(feature = "unstable_mcp_over_acp")]
4115 Self::MessageMcpRequest(_) => CLIENT_METHOD_NAMES.mcp_message,
4116 Self::ExtMethodRequest(ext_request) => &ext_request.method,
4117 }
4118 }
4119}
4120
4121#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4128#[derive(Clone, Debug, Serialize, Deserialize)]
4129#[serde(untagged)]
4130#[cfg_attr(feature = "schemars", schemars(inline))]
4131#[non_exhaustive]
4132pub enum ClientResponse {
4133 WriteTextFileResponse(#[serde(default)] WriteTextFileResponse),
4135 ReadTextFileResponse(ReadTextFileResponse),
4137 RequestPermissionResponse(RequestPermissionResponse),
4139 CreateTerminalResponse(CreateTerminalResponse),
4141 TerminalOutputResponse(TerminalOutputResponse),
4143 ReleaseTerminalResponse(#[serde(default)] ReleaseTerminalResponse),
4145 WaitForTerminalExitResponse(WaitForTerminalExitResponse),
4147 KillTerminalResponse(#[serde(default)] KillTerminalResponse),
4149 CreateElicitationResponse(CreateElicitationResponse),
4151 #[cfg(feature = "unstable_mcp_over_acp")]
4153 MessageMcpResponse(MessageMcpResponse),
4154 ExtMethodResponse(ExtResponse),
4156}
4157
4158#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4165#[derive(Clone, Debug, Serialize, Deserialize)]
4166#[serde(untagged)]
4167#[expect(clippy::large_enum_variant)]
4168#[cfg_attr(feature = "schemars", schemars(inline))]
4169#[non_exhaustive]
4170pub enum AgentNotification {
4171 SessionNotification(SessionNotification),
4183 CompleteElicitationNotification(CompleteElicitationNotification),
4187 #[cfg(feature = "unstable_auth_status")]
4195 AuthStatusUpdateNotification(AuthStatusUpdateNotification),
4196 ExtNotification(ExtNotification),
4204}
4205
4206impl AgentNotification {
4207 #[must_use]
4209 pub fn method(&self) -> &str {
4210 match self {
4211 Self::SessionNotification(_) => CLIENT_METHOD_NAMES.session_update,
4212 Self::CompleteElicitationNotification(_) => CLIENT_METHOD_NAMES.elicitation_complete,
4213 #[cfg(feature = "unstable_auth_status")]
4214 Self::AuthStatusUpdateNotification(_) => CLIENT_METHOD_NAMES.auth_status_update,
4215 Self::ExtNotification(ext_notification) => &ext_notification.method,
4216 }
4217 }
4218}
4219
4220#[cfg(test)]
4221mod tests {
4222 use super::*;
4223
4224 #[test]
4225 fn notice_preserves_wire_shape_nullable_fields_and_open_severity() {
4226 use serde_json::json;
4227
4228 let mut meta = Meta::new();
4229 meta.insert("source".into(), json!("fallback"));
4230 assert_eq!(
4231 serde_json::to_value(SessionUpdate::Notice(
4232 Notice::new(NoticeSeverity::Warning, "MCP server unavailable")
4233 .description("Continuing without it.")
4234 .meta(meta),
4235 ))
4236 .unwrap(),
4237 json!({
4238 "sessionUpdate": "notice",
4239 "severity": "warning",
4240 "title": "MCP server unavailable",
4241 "description": "Continuing without it.",
4242 "_meta": { "source": "fallback" }
4243 })
4244 );
4245
4246 assert_eq!(
4247 serde_json::to_value(SessionUpdate::Notice(Notice::new(
4248 NoticeSeverity::Info,
4249 "Indexing workspace",
4250 )))
4251 .unwrap(),
4252 json!({
4253 "sessionUpdate": "notice",
4254 "severity": "info",
4255 "title": "Indexing workspace"
4256 })
4257 );
4258
4259 let SessionUpdate::Notice(notice) = serde_json::from_value(json!({
4260 "sessionUpdate": "notice",
4261 "severity": "critical",
4262 "title": "Provider degraded",
4263 "description": null,
4264 "_meta": null
4265 }))
4266 .unwrap() else {
4267 panic!("expected notice");
4268 };
4269
4270 assert_eq!(
4271 notice.severity,
4272 NoticeSeverity::Other("critical".to_string())
4273 );
4274 assert_eq!(notice.description, None);
4275 assert_eq!(notice.meta, None);
4276 assert_eq!(
4277 serde_json::to_value(SessionUpdate::Notice(notice)).unwrap(),
4278 json!({
4279 "sessionUpdate": "notice",
4280 "severity": "critical",
4281 "title": "Provider degraded"
4282 })
4283 );
4284 }
4285
4286 #[test]
4287 fn notice_requires_string_severity_and_title() {
4288 use serde_json::json;
4289
4290 for malformed in [
4291 json!({
4292 "sessionUpdate": "notice",
4293 "severity": "warning",
4294 "title": 42
4295 }),
4296 json!({
4297 "sessionUpdate": "notice",
4298 "severity": 42,
4299 "title": "MCP server unavailable"
4300 }),
4301 json!({
4302 "sessionUpdate": "notice",
4303 "severity": "warning"
4304 }),
4305 json!({
4306 "sessionUpdate": "notice",
4307 "severity": "warning",
4308 "title": null
4309 }),
4310 json!({
4311 "sessionUpdate": "notice",
4312 "title": "MCP server unavailable"
4313 }),
4314 json!({
4315 "sessionUpdate": "notice",
4316 "severity": null,
4317 "title": "MCP server unavailable"
4318 }),
4319 ] {
4320 assert!(serde_json::from_str::<SessionUpdate>(&malformed.to_string()).is_err());
4321 assert!(serde_json::from_value::<SessionUpdate>(malformed).is_err());
4322 }
4323 }
4324
4325 #[test]
4326 fn notice_tolerates_invalid_optional_display_fields_and_custom_severity() {
4327 use serde_json::json;
4328
4329 let wire = json!({
4330 "sessionUpdate": "notice",
4331 "severity": "_custom",
4332 "title": "Provider degraded",
4333 "description": 42,
4334 "_meta": false
4335 });
4336 let SessionUpdate::Notice(notice) = serde_json::from_value(wire.clone()).unwrap() else {
4337 panic!("expected notice");
4338 };
4339 assert_eq!(
4341 serde_json::from_str::<SessionUpdate>(&wire.to_string()).unwrap(),
4342 SessionUpdate::Notice(notice.clone())
4343 );
4344 assert_eq!(notice.severity, NoticeSeverity::Other("_custom".into()));
4345 assert_eq!(notice.description, None);
4346 assert_eq!(notice.meta, None);
4347 assert_eq!(
4348 serde_json::to_value(notice).unwrap(),
4349 json!({"severity": "_custom", "title": "Provider degraded"})
4350 );
4351 }
4352
4353 #[cfg(feature = "schemars")]
4354 #[test]
4355 fn notice_schema_preserves_required_and_nullable_fields() {
4356 use serde_json::json;
4357
4358 let schema = serde_json::to_value(schemars::schema_for!(Notice)).unwrap();
4359 assert_eq!(schema["required"], json!(["severity", "title"]));
4360 assert_eq!(schema["properties"]["title"]["minLength"], 1);
4361 for field in ["description", "_meta"] {
4362 assert!(
4363 schema["properties"][field]["type"]
4364 .as_array()
4365 .unwrap()
4366 .contains(&json!("null"))
4367 );
4368 }
4369 let capabilities =
4370 serde_json::to_value(schemars::schema_for!(ClientSessionCapabilities)).unwrap();
4371 assert!(
4372 capabilities["properties"]["notices"]["anyOf"]
4373 .as_array()
4374 .unwrap()
4375 .contains(&json!({"type": "null"}))
4376 );
4377 assert!(
4378 !capabilities["required"]
4379 .as_array()
4380 .is_some_and(|required| required.contains(&json!("notices")))
4381 );
4382 }
4383
4384 #[test]
4385 fn notice_capability_advertises_support_only_when_present() {
4386 use serde_json::json;
4387
4388 let capabilities = ClientCapabilities::new()
4389 .session(ClientSessionCapabilities::new().notices(NoticeCapabilities::new()));
4390 let value = serde_json::to_value(&capabilities).unwrap();
4391 assert_eq!(value["session"], json!({ "notices": {} }));
4392 assert_eq!(
4393 serde_json::from_value::<ClientCapabilities>(value).unwrap(),
4394 capabilities
4395 );
4396
4397 for unsupported in [
4398 json!({}),
4399 json!({ "session": null }),
4400 json!({ "session": {} }),
4401 json!({ "session": { "notices": null } }),
4402 json!({ "session": { "notices": false } }),
4403 json!({ "session": { "notices": true } }),
4404 json!({ "session": { "notices": "supported" } }),
4405 ] {
4406 let capabilities: ClientCapabilities = serde_json::from_value(unsupported).unwrap();
4407 assert!(
4408 capabilities
4409 .session
4410 .and_then(|session| session.notices)
4411 .is_none()
4412 );
4413 }
4414
4415 assert_eq!(
4416 serde_json::to_value(
4417 ClientSessionCapabilities::new()
4418 .notices(NoticeCapabilities::new())
4419 .notices(None)
4420 )
4421 .unwrap(),
4422 json!({})
4423 );
4424 }
4425
4426 #[test]
4427 fn compaction_updates_preserve_patch_and_open_status_semantics() {
4428 use serde_json::json;
4429
4430 assert_eq!(
4431 serde_json::to_value(SessionUpdate::CompactionUpdate(CompactionUpdate::new(
4432 "cmp_001",
4433 CompactionStatus::InProgress,
4434 )))
4435 .unwrap(),
4436 json!({
4437 "sessionUpdate": "compaction_update",
4438 "compactionId": "cmp_001",
4439 "status": "in_progress"
4440 })
4441 );
4442
4443 let SessionUpdate::CompactionUpdate(update) = serde_json::from_value(json!({
4444 "sessionUpdate": "compaction_update",
4445 "compactionId": "cmp_001",
4446 "status": "paused",
4447 "summary": null,
4448 "error": null
4449 }))
4450 .unwrap() else {
4451 panic!("expected compaction update");
4452 };
4453 assert_eq!(update.status, CompactionStatus::Other("paused".into()));
4454 assert!(update.summary.is_null());
4455 assert!(update.error.is_null());
4456 assert!(update.meta.is_undefined());
4457 }
4458
4459 #[test]
4460 fn compaction_chunk_and_v1_capability_serialize() {
4461 use serde_json::json;
4462
4463 assert_eq!(
4464 serde_json::to_value(SessionUpdate::CompactionSummaryChunk(
4465 CompactionSummaryChunk::new(
4466 "cmp_001",
4467 ContentBlock::Text(crate::v1::TextContent::new("retained")),
4468 ),
4469 ))
4470 .unwrap(),
4471 json!({
4472 "sessionUpdate": "compaction_summary_chunk",
4473 "compactionId": "cmp_001",
4474 "content": { "type": "text", "text": "retained" }
4475 })
4476 );
4477 assert_eq!(
4478 serde_json::to_value(
4479 ClientSessionCapabilities::new().compaction(CompactionCapabilities::new())
4480 )
4481 .unwrap(),
4482 json!({ "compaction": {} })
4483 );
4484 let absent: ClientSessionCapabilities = serde_json::from_value(json!({})).unwrap();
4485 let null: ClientSessionCapabilities =
4486 serde_json::from_value(json!({ "compaction": null })).unwrap();
4487 assert!(absent.compaction.is_none());
4488 assert!(null.compaction.is_none());
4489 }
4490
4491 #[cfg(feature = "unstable_subagents")]
4492 #[test]
4493 fn test_subagent_updates_serialization() {
4494 use serde_json::json;
4495
4496 let announced = SessionUpdate::SubagentUpdate(SubagentUpdate::new("sess_child_1"));
4497 assert_eq!(
4498 serde_json::to_value(&announced).unwrap(),
4499 json!({
4500 "sessionUpdate": "subagent_update",
4501 "sessionId": "sess_child_1"
4502 })
4503 );
4504 assert_eq!(
4505 serde_json::from_value::<SessionUpdate>(serde_json::to_value(&announced).unwrap())
4506 .unwrap(),
4507 announced
4508 );
4509 let capable = SubagentUpdate::new("sess_child_1").capabilities(
4510 SubagentSessionCapabilities::new().cancel(SessionCancelCapabilities::new()),
4511 );
4512 assert_eq!(
4513 serde_json::to_value(capable).unwrap(),
4514 json!({
4515 "sessionId": "sess_child_1",
4516 "capabilities": {"cancel": {}}
4517 })
4518 );
4519 let minimal: SubagentUpdate =
4520 serde_json::from_value(json!({ "sessionId": "sess_child_3" })).unwrap();
4521 assert!(minimal.capabilities.is_undefined());
4522 assert!(minimal.state.is_undefined());
4523
4524 let nulls: SubagentUpdate = serde_json::from_value(json!({
4525 "sessionId": "sess_child_3",
4526 "capabilities": null,
4527 "state": null
4528 }))
4529 .unwrap();
4530 assert!(nulls.capabilities.is_null());
4531 assert!(nulls.state.is_null());
4532 assert_eq!(
4533 serde_json::to_value(nulls).unwrap(),
4534 json!({"sessionId": "sess_child_3", "capabilities": null, "state": null})
4535 );
4536 }
4537
4538 #[cfg(feature = "unstable_subagents")]
4539 #[test]
4540 fn test_subagent_display_metadata() {
4541 use serde_json::json;
4542
4543 let update = SubagentUpdate::new("child")
4544 .title("Test investigator".to_string())
4545 .description("Investigates platform-specific test failures.".to_string());
4546 let wire = json!({
4547 "sessionId": "child",
4548 "title": "Test investigator",
4549 "description": "Investigates platform-specific test failures."
4550 });
4551 assert_eq!(serde_json::to_value(&update).unwrap(), wire);
4552 assert_eq!(
4553 serde_json::from_value::<SubagentUpdate>(wire).unwrap(),
4554 update
4555 );
4556
4557 let minimal = SubagentUpdate::new("child");
4558 assert_eq!(
4559 serde_json::to_value(&minimal).unwrap(),
4560 json!({"sessionId": "child"})
4561 );
4562 let cleared: SubagentUpdate = serde_json::from_value(json!({
4563 "sessionId": "child", "title": null, "description": null
4564 }))
4565 .unwrap();
4566 assert!(cleared.title.is_null());
4567 assert!(cleared.description.is_null());
4568 assert_eq!(
4569 serde_json::to_value(cleared).unwrap(),
4570 json!({"sessionId": "child", "title": null, "description": null})
4571 );
4572 let invalid: SubagentUpdate = serde_json::from_value(json!({
4573 "sessionId": "child", "title": false, "description": false
4574 }))
4575 .unwrap();
4576 assert_eq!(invalid, minimal);
4577
4578 let title_only: SubagentUpdate = serde_json::from_value(json!({
4579 "sessionId": "child", "title": "Updated title"
4580 }))
4581 .unwrap();
4582 assert_eq!(
4583 title_only.title.value().map(String::as_str),
4584 Some("Updated title")
4585 );
4586 assert!(title_only.description.is_undefined());
4587 }
4588
4589 #[cfg(feature = "unstable_subagents")]
4590 #[test]
4591 fn subagent_directories_use_strict_patch_semantics() {
4592 use serde_json::json;
4593
4594 let update = SubagentUpdate::new("child")
4595 .cwd("/home/user/project/.worktrees/investigator")
4596 .additional_directories(vec![]);
4597 let wire = json!({
4598 "sessionId": "child",
4599 "cwd": "/home/user/project/.worktrees/investigator",
4600 "additionalDirectories": []
4601 });
4602 assert_eq!(serde_json::to_value(&update).unwrap(), wire);
4603 assert_eq!(
4604 serde_json::from_value::<SubagentUpdate>(wire).unwrap(),
4605 update
4606 );
4607
4608 let inherited: SubagentUpdate =
4609 serde_json::from_value(json!({"sessionId": "child"})).unwrap();
4610 assert!(inherited.cwd.is_undefined());
4611 assert!(inherited.additional_directories.is_undefined());
4612
4613 let cleared_wire = json!({
4614 "sessionId": "child", "cwd": null, "additionalDirectories": null
4615 });
4616 let cleared: SubagentUpdate = serde_json::from_value(cleared_wire.clone()).unwrap();
4617 assert!(cleared.cwd.is_null());
4618 assert!(cleared.additional_directories.is_null());
4619 assert_eq!(serde_json::to_value(cleared).unwrap(), cleared_wire);
4620
4621 for malformed in [
4622 json!({"sessionId": "child", "cwd": false}),
4623 json!({"sessionId": "child", "additionalDirectories": "/tmp/wt"}),
4624 json!({"sessionId": "child", "additionalDirectories": ["/tmp/wt", false]}),
4625 ] {
4626 assert!(
4627 serde_json::from_value::<SubagentUpdate>(malformed.clone()).is_err(),
4628 "{malformed}"
4629 );
4630 }
4631 }
4632
4633 #[cfg(feature = "unstable_subagents")]
4634 #[test]
4635 fn subagent_patch_fields_preserve_omitted_null_and_concrete() {
4636 use serde_json::json;
4637
4638 let omitted = SubagentUpdate::new("child");
4639 for field in ["title", "description", "capabilities", "state", "_meta"] {
4640 let wire = json!({"sessionId": "child", field: null});
4641 let decoded: SubagentUpdate = serde_json::from_value(wire.clone()).unwrap();
4642 assert_eq!(serde_json::to_value(decoded).unwrap(), wire, "{field}");
4643 let malformed = json!({"sessionId": "child", field: false});
4644 let decoded: SubagentUpdate = serde_json::from_value(malformed).unwrap();
4645 assert_eq!(decoded, omitted, "{field}");
4646 }
4647 let concrete = json!({
4648 "sessionId": "child", "title": "Investigator",
4649 "description": "Tests", "capabilities": {"cancel": {}},
4650 "state": {"state": "running"}, "_meta": {"source": "parent"}
4651 });
4652 let decoded: SubagentUpdate = serde_json::from_value(concrete.clone()).unwrap();
4653 assert!(decoded.title.value().is_some());
4654 assert!(decoded.description.value().is_some());
4655 assert!(decoded.capabilities.value().is_some());
4656 assert!(decoded.state.value().is_some());
4657 assert!(decoded.meta.value().is_some());
4658 assert_eq!(serde_json::to_value(decoded).unwrap(), concrete);
4659 let cleared = SubagentUpdate::new("child")
4660 .title(None)
4661 .description(None)
4662 .capabilities(None)
4663 .state(None)
4664 .meta(None);
4665 assert_eq!(
4666 serde_json::to_value(cleared).unwrap(),
4667 json!({
4668 "sessionId": "child", "title": null, "description": null,
4669 "capabilities": null, "state": null, "_meta": null
4670 })
4671 );
4672 }
4673
4674 #[cfg(feature = "unstable_subagents")]
4675 #[test]
4676 fn test_subagent_notification_keeps_parent_and_child_ids_nested() {
4677 use serde_json::json;
4678
4679 let notification = SessionNotification::new(
4680 "parent",
4681 SessionUpdate::SubagentUpdate(SubagentUpdate::new("child")),
4682 );
4683 let wire = json!({
4684 "sessionId": "parent",
4685 "update": {
4686 "sessionUpdate": "subagent_update",
4687 "sessionId": "child"
4688 }
4689 });
4690 assert_eq!(serde_json::to_value(¬ification).unwrap(), wire);
4691 assert_eq!(
4692 serde_json::from_value::<SessionNotification>(wire).unwrap(),
4693 notification
4694 );
4695 for malformed in [
4697 json!({"sessionId": "parent", "update": {"sessionUpdate": "subagent_update"}}),
4698 json!({"update": {"sessionUpdate": "subagent_update", "sessionId": "child"}}),
4699 ] {
4700 assert!(serde_json::from_value::<SessionNotification>(malformed).is_err());
4701 }
4702 }
4703
4704 #[cfg(feature = "unstable_subagents")]
4705 #[test]
4706 fn test_subagent_states_round_trip() {
4707 use serde_json::json;
4708
4709 for (wire_state, expected) in [
4710 (
4711 json!({"state": "running"}),
4712 StateUpdate::Running(RunningStateUpdate::new()),
4713 ),
4714 (
4715 json!({"state": "idle", "stopReason": "end_turn"}),
4716 StateUpdate::Idle(IdleStateUpdate::new().stop_reason(IdleStopReason::EndTurn)),
4717 ),
4718 (
4719 json!({"state": "requires_action"}),
4720 StateUpdate::RequiresAction(RequiresActionStateUpdate::new()),
4721 ),
4722 (
4723 json!({"state": "unknown"}),
4724 StateUpdate::Unknown(UnknownStateUpdate::new()),
4725 ),
4726 (
4727 json!({"state": "_waiting", "detail": {"ticket": 7}}),
4728 StateUpdate::Other(OtherStateUpdate::new(
4729 "_waiting",
4730 [("detail".into(), json!({"ticket": 7}))].into(),
4731 )),
4732 ),
4733 (
4734 json!({"state": "paused", "retryAt": 42}),
4735 StateUpdate::Other(OtherStateUpdate::new(
4736 "paused",
4737 [("retryAt".into(), json!(42))].into(),
4738 )),
4739 ),
4740 ] {
4741 let wire = json!({
4742 "sessionUpdate": "subagent_update",
4743 "sessionId": "sess_child",
4744 "state": wire_state
4745 });
4746 let parsed: SessionUpdate = serde_json::from_value(wire.clone()).unwrap();
4747 let SessionUpdate::SubagentUpdate(update) = &parsed else {
4748 panic!("expected subagent update");
4749 };
4750 assert_eq!(update.state.value(), Some(&expected));
4751 assert_eq!(serde_json::to_value(parsed).unwrap(), wire);
4752 }
4753
4754 for reason in [
4755 "end_turn",
4756 "max_tokens",
4757 "max_turn_requests",
4758 "refusal",
4759 "cancelled",
4760 ] {
4761 let state: StateUpdate = serde_json::from_value(json!({
4762 "state": "idle", "stopReason": reason
4763 }))
4764 .unwrap();
4765 assert_eq!(serde_json::to_value(state).unwrap()["stopReason"], reason);
4766 }
4767
4768 for state in [
4771 StateUpdate::Running(RunningStateUpdate::new()),
4772 StateUpdate::RequiresAction(RequiresActionStateUpdate::new()),
4773 StateUpdate::Unknown(UnknownStateUpdate::new()),
4774 StateUpdate::Running(RunningStateUpdate::new()),
4775 StateUpdate::Idle(IdleStateUpdate::new().stop_reason(IdleStopReason::EndTurn)),
4776 StateUpdate::Unknown(UnknownStateUpdate::new()),
4777 StateUpdate::Running(RunningStateUpdate::new()),
4778 ] {
4779 let update = SubagentUpdate::new("sess_child").state(state.clone());
4780 let wire = serde_json::to_value(&update).unwrap();
4781 assert_eq!(wire["sessionId"], "sess_child");
4782 assert_eq!(
4783 serde_json::from_value::<SubagentUpdate>(wire)
4784 .unwrap()
4785 .state,
4786 MaybeUndefined::Value(state)
4787 );
4788 }
4789 assert_eq!(
4790 serde_json::to_value(
4791 SubagentUpdate::new("sess_child").state(StateUpdate::Idle(IdleStateUpdate::new()))
4792 )
4793 .unwrap()["state"],
4794 json!({"state": "idle"})
4795 );
4796 }
4797
4798 #[cfg(feature = "unstable_subagents")]
4799 #[test]
4800 fn subagent_idle_snapshot_reports_child_failure() {
4801 use serde_json::json;
4802
4803 let update = SubagentUpdate::new("sess_child").state(StateUpdate::Idle(
4804 IdleStateUpdate::new().stop_reason(IdleStopReason::from(
4805 ErrorStopReason::new().error(Error::auth_required()),
4806 )),
4807 ));
4808 let wire = json!({
4809 "sessionId": "sess_child",
4810 "state": {
4811 "state": "idle",
4812 "stopReason": "error",
4813 "error": { "code": -32000, "message": "Authentication required" }
4814 }
4815 });
4816 assert_eq!(serde_json::to_value(&update).unwrap(), wire);
4817 assert_eq!(
4818 serde_json::from_value::<SubagentUpdate>(wire).unwrap(),
4819 update
4820 );
4821
4822 let without_details = StateUpdate::Idle(
4824 IdleStateUpdate::new().stop_reason(IdleStopReason::from(ErrorStopReason::new())),
4825 );
4826 for error in [None, Some(json!(null)), Some(json!("failed"))] {
4827 let mut wire = json!({ "state": "idle", "stopReason": "error" });
4828 if let Some(error) = error {
4829 wire["error"] = error;
4830 }
4831 assert_eq!(
4832 serde_json::from_value::<StateUpdate>(wire).unwrap(),
4833 without_details
4834 );
4835 }
4836
4837 let parsed: StateUpdate = serde_json::from_value(json!({
4840 "state": "idle",
4841 "stopReason": "end_turn",
4842 "error": { "code": -32603, "message": "Internal error" }
4843 }))
4844 .unwrap();
4845 assert_eq!(
4846 parsed,
4847 StateUpdate::Idle(IdleStateUpdate::new().stop_reason(IdleStopReason::EndTurn))
4848 );
4849
4850 let meta: Meta = [("source".into(), json!("worker"))].into_iter().collect();
4853 for stop_reason in [None, Some(json!(null)), Some(json!(true))] {
4854 let mut wire = json!({ "state": "idle", "_meta": { "source": "worker" } });
4855 if let Some(stop_reason) = stop_reason {
4856 wire["stopReason"] = stop_reason;
4857 }
4858 assert_eq!(
4859 serde_json::from_value::<StateUpdate>(wire).unwrap(),
4860 StateUpdate::Idle(IdleStateUpdate::new().meta(meta.clone()))
4861 );
4862 }
4863
4864 for stop_reason in ["_paused", "not_a_reason"] {
4867 let wire = json!({
4868 "state": "idle",
4869 "stopReason": stop_reason,
4870 "resumeAfter": 30,
4871 "_meta": { "source": "worker" }
4872 });
4873 let parsed: StateUpdate = serde_json::from_value(wire.clone()).unwrap();
4874 assert_eq!(
4875 parsed,
4876 StateUpdate::Idle(
4877 IdleStateUpdate::new()
4878 .stop_reason(IdleStopReason::Other(OtherStopReason::new(
4879 stop_reason,
4880 [("resumeAfter".into(), json!(30))].into_iter().collect(),
4881 )))
4882 .meta(meta.clone())
4883 )
4884 );
4885 assert_eq!(serde_json::to_value(&parsed).unwrap(), wire);
4886 }
4887 }
4888
4889 #[cfg(feature = "unstable_subagents")]
4890 #[test]
4891 fn subagent_known_stop_reasons_are_never_read_as_other() {
4892 use serde_json::json;
4893
4894 let known = [
4895 IdleStopReason::EndTurn,
4896 IdleStopReason::MaxTokens,
4897 IdleStopReason::MaxTurnRequests,
4898 IdleStopReason::Refusal,
4899 IdleStopReason::Cancelled,
4900 IdleStopReason::Error(ErrorStopReason::new()),
4901 ];
4902 for stop_reason in &known {
4904 match stop_reason {
4905 IdleStopReason::EndTurn
4906 | IdleStopReason::MaxTokens
4907 | IdleStopReason::MaxTurnRequests
4908 | IdleStopReason::Refusal
4909 | IdleStopReason::Cancelled
4910 | IdleStopReason::Error(_)
4911 | IdleStopReason::Other(_) => {}
4912 }
4913 }
4914
4915 for stop_reason in known {
4916 let state = StateUpdate::Idle(IdleStateUpdate::new().stop_reason(stop_reason));
4917 let wire = serde_json::to_value(&state).unwrap();
4918 assert_eq!(
4919 serde_json::from_value::<StateUpdate>(wire.clone()).unwrap(),
4920 state
4921 );
4922 assert!(
4923 serde_json::from_value::<OtherStopReason>(
4924 json!({ "stopReason": wire["stopReason"] })
4925 )
4926 .is_err(),
4927 "{} must not be accepted as a custom stop reason",
4928 wire["stopReason"]
4929 );
4930 }
4931 }
4932
4933 #[cfg(feature = "unstable_subagents")]
4934 #[test]
4935 fn subagent_unknown_stop_reason_leaves_usage_on_the_snapshot() {
4936 use serde_json::json;
4937
4938 let usage = json!({ "totalTokens": 10, "inputTokens": 6, "outputTokens": 4 });
4939 let wire = json!({ "state": "idle", "stopReason": "_paused", "usage": usage });
4940 let StateUpdate::Idle(idle) = serde_json::from_value(wire).unwrap() else {
4941 panic!("expected idle state snapshot");
4942 };
4943 let Some(IdleStopReason::Other(other)) = &idle.stop_reason else {
4944 panic!("expected a custom stop reason");
4945 };
4946 assert!(other.fields.is_empty());
4947
4948 #[cfg(feature = "unstable_end_turn_token_usage")]
4949 {
4950 assert!(idle.usage.is_some());
4951 assert_eq!(serde_json::to_value(&idle).unwrap()["usage"], usage);
4952 }
4953 #[cfg(not(feature = "unstable_end_turn_token_usage"))]
4956 assert!(serde_json::to_value(&idle).unwrap().get("usage").is_none());
4957 }
4958
4959 #[cfg(feature = "unstable_subagents")]
4960 #[test]
4961 fn test_subagent_unknown_activity_metadata_and_capabilities() {
4962 use serde_json::json;
4963
4964 let update = SubagentUpdate::new("child").state(StateUpdate::Unknown(
4965 UnknownStateUpdate::new().meta(
4966 [("source".into(), json!("worker"))]
4967 .into_iter()
4968 .collect::<Meta>(),
4969 ),
4970 ));
4971 let wire = json!({
4972 "sessionId": "child",
4973 "state": {
4974 "state": "unknown",
4975 "_meta": { "source": "worker" }
4976 }
4977 });
4978 assert_eq!(serde_json::to_value(&update).unwrap(), wire);
4979 assert_eq!(
4980 serde_json::from_value::<SubagentUpdate>(wire).unwrap(),
4981 update
4982 );
4983 assert!(update.capabilities.is_undefined());
4985
4986 for meta in [json!(null), json!(false)] {
4987 let state: StateUpdate = serde_json::from_value(json!({
4988 "state": "unknown",
4989 "_meta": meta
4990 }))
4991 .unwrap();
4992 assert_eq!(state, StateUpdate::Unknown(UnknownStateUpdate::new()));
4993 assert_eq!(
4994 serde_json::to_value(state).unwrap(),
4995 json!({"state": "unknown"})
4996 );
4997 }
4998 assert!(serde_json::from_value::<OtherStateUpdate>(json!({"state": "unknown"})).is_err());
5000 }
5001
5002 #[cfg(feature = "unstable_subagents")]
5003 #[test]
5004 fn test_subagent_malformed_optional_state() {
5005 use serde_json::json;
5006
5007 for malformed in [json!("running"), json!({}), json!({"state": 4})] {
5008 assert!(serde_json::from_value::<StateUpdate>(malformed.clone()).is_err());
5009 let update: SubagentUpdate = serde_json::from_value(json!({
5010 "sessionId": "child", "state": malformed
5011 }))
5012 .unwrap();
5013 assert!(update.state.is_undefined());
5014 }
5015 let bad_reason: StateUpdate =
5016 serde_json::from_value(json!({"state": "idle", "stopReason": false})).unwrap();
5017 assert_eq!(bad_reason, StateUpdate::Idle(IdleStateUpdate::new()));
5018 let null_reason: StateUpdate =
5019 serde_json::from_value(json!({"state": "idle", "stopReason": null})).unwrap();
5020 assert_eq!(null_reason, StateUpdate::Idle(IdleStateUpdate::new()));
5021 }
5022
5023 #[cfg(all(feature = "unstable_subagents", feature = "unstable_protocol_v2"))]
5024 #[test]
5025 fn test_subagent_state_v2_wire_parity() {
5026 use serde_json::json;
5027
5028 for wire in [
5029 json!({"state": "running"}),
5030 json!({"state": "idle", "stopReason": "cancelled"}),
5031 json!({"state": "requires_action"}),
5032 json!({"state": "unknown"}),
5033 json!({"state": "unknown", "_meta": {"source": "worker"}}),
5034 json!({"state": "_waiting", "detail": {"ticket": 7}}),
5035 ] {
5036 let v1: StateUpdate = serde_json::from_value(wire.clone()).unwrap();
5037 let v2: crate::v2::StateUpdate = serde_json::from_value(wire.clone()).unwrap();
5038 assert_eq!(serde_json::to_value(v1).unwrap(), wire);
5039 assert_eq!(serde_json::to_value(v2).unwrap(), wire);
5040 }
5041 }
5042
5043 #[cfg(feature = "unstable_subagents")]
5044 #[test]
5045 fn test_subagent_capability_semantics() {
5046 use serde_json::json;
5047
5048 let capabilities =
5049 serde_json::to_value(ClientCapabilities::new().subagents(SubagentCapabilities::new()))
5050 .unwrap();
5051 assert_eq!(capabilities["subagents"], json!({}));
5052
5053 let omitted: ClientCapabilities = serde_json::from_value(json!({})).unwrap();
5054 assert!(omitted.subagents.is_none());
5055
5056 let null: ClientCapabilities =
5057 serde_json::from_value(json!({ "subagents": null })).unwrap();
5058 assert!(null.subagents.is_none());
5059 assert_eq!(null, omitted);
5060 let serialized = serde_json::to_value(null).unwrap();
5061 assert_eq!(serialized, serde_json::to_value(omitted).unwrap());
5062 assert!(serialized.get("subagents").is_none());
5063
5064 for wire in [
5065 json!({}),
5066 json!({"cancel": null}),
5067 json!({"cancel": true}),
5068 json!({"cancel": false}),
5069 ] {
5070 let child: SubagentSessionCapabilities = serde_json::from_value(wire).unwrap();
5071 assert!(child.cancel.is_none());
5072 assert_eq!(serde_json::to_value(child).unwrap(), json!({}));
5073 }
5074 let enabled = SubagentSessionCapabilities::new().cancel(SessionCancelCapabilities::new());
5075 assert_eq!(
5076 serde_json::to_value(&enabled).unwrap(),
5077 json!({"cancel": {}})
5078 );
5079 assert_eq!(
5080 serde_json::from_value::<SubagentSessionCapabilities>(json!({"cancel": {}})).unwrap(),
5081 enabled
5082 );
5083 let meta: Meta = [("source".into(), json!("worker"))].into_iter().collect();
5084 let with_meta =
5085 SubagentSessionCapabilities::new().cancel(SessionCancelCapabilities::new().meta(meta));
5086 let wire = json!({"cancel": {"_meta": {"source": "worker"}}});
5087 assert_eq!(serde_json::to_value(&with_meta).unwrap(), wire);
5088 assert_eq!(
5089 serde_json::from_value::<SubagentSessionCapabilities>(wire).unwrap(),
5090 with_meta
5091 );
5092 let removed = SubagentSessionCapabilities::new();
5094 assert_eq!(serde_json::to_value(&removed).unwrap(), json!({}));
5095 assert!(removed.cancel.is_none());
5096 assert!(enabled.cancel.is_some());
5097 assert!(enabled.cancel(None).cancel.is_none());
5098 let replacement = SubagentUpdate::new("child").capabilities(removed);
5099 assert_eq!(
5100 serde_json::to_value(replacement).unwrap(),
5101 json!({"sessionId": "child", "capabilities": {}})
5102 );
5103 }
5104
5105 #[cfg(all(feature = "unstable_subagents", feature = "schemars"))]
5106 #[test]
5107 fn test_subagent_capability_schema_is_optional_and_nullable() {
5108 use serde_json::json;
5109
5110 let schema = serde_json::to_value(schemars::schema_for!(ClientCapabilities)).unwrap();
5111 let variants = schema["properties"]["subagents"]["anyOf"]
5112 .as_array()
5113 .expect("subagents must allow the capability object or null");
5114 assert!(variants.contains(&json!({"type": "null"})));
5115 assert!(
5116 !schema["required"]
5117 .as_array()
5118 .is_some_and(|required| required.contains(&json!("subagents")))
5119 );
5120 let child =
5121 serde_json::to_value(schemars::schema_for!(SubagentSessionCapabilities)).unwrap();
5122 let variants = child["properties"]["cancel"]["anyOf"]
5123 .as_array()
5124 .expect("cancel must allow the capability object or null");
5125 assert!(variants.contains(&json!({"type": "null"})));
5126 assert!(
5127 variants
5128 .iter()
5129 .any(|variant| variant["type"] == "object" || variant.get("$ref").is_some())
5130 );
5131 assert!(!variants.iter().any(|variant| variant["type"] == "boolean"));
5132 let cancel =
5133 serde_json::to_value(schemars::schema_for!(SessionCancelCapabilities)).unwrap();
5134 assert_eq!(cancel["type"], "object");
5135 assert!(
5136 !child["required"]
5137 .as_array()
5138 .is_some_and(|required| required.contains(&json!("cancel")))
5139 );
5140 }
5141
5142 #[test]
5143 fn test_elicitation_capability_semantics() {
5144 use serde_json::json;
5145
5146 let unsupported: ClientCapabilities = serde_json::from_value(json!({})).unwrap();
5147 assert!(unsupported.elicitation.is_none());
5148
5149 let null: ClientCapabilities =
5150 serde_json::from_value(json!({ "elicitation": null })).unwrap();
5151 assert!(null.elicitation.is_none());
5152
5153 let malformed: ClientCapabilities =
5154 serde_json::from_value(json!({ "elicitation": false })).unwrap();
5155 assert!(malformed.elicitation.is_none());
5156
5157 let empty: ClientCapabilities =
5158 serde_json::from_value(json!({ "elicitation": {} })).unwrap();
5159 let empty = empty.elicitation.expect("present capability");
5160 assert!(!empty.supports_form());
5161 assert!(!empty.supports_url());
5162
5163 let form_only: ClientCapabilities = serde_json::from_value(json!({
5164 "elicitation": { "form": {} }
5165 }))
5166 .unwrap();
5167 let form_only = form_only.elicitation.expect("advertised capability");
5168 assert!(form_only.supports_form());
5169 assert!(!form_only.supports_url());
5170
5171 let url_only: ClientCapabilities = serde_json::from_value(json!({
5172 "elicitation": { "url": {} }
5173 }))
5174 .unwrap();
5175 let url_only = url_only.elicitation.expect("advertised capability");
5176 assert!(!url_only.supports_form());
5177 assert!(url_only.supports_url());
5178
5179 let both: ClientCapabilities = serde_json::from_value(json!({
5180 "elicitation": { "form": {}, "url": {} }
5181 }))
5182 .unwrap();
5183 let both = both.elicitation.expect("advertised capability");
5184 assert!(both.supports_form());
5185 assert!(both.supports_url());
5186 }
5187
5188 #[test]
5189 fn test_elicitation_method_routing_and_envelopes() {
5190 use serde_json::json;
5191
5192 assert_eq!(CLIENT_METHOD_NAMES.elicitation_create, "elicitation/create");
5193 assert_eq!(
5194 CLIENT_METHOD_NAMES.elicitation_complete,
5195 "elicitation/complete"
5196 );
5197
5198 let request = AgentRequest::CreateElicitationRequest(CreateElicitationRequest::new(
5199 crate::v1::ElicitationFormMode::new(
5200 crate::v1::ElicitationSessionScope::new("sess_1"),
5201 crate::v1::ElicitationSchema::new(),
5202 ),
5203 "Choose a value",
5204 ));
5205 assert_eq!(request.method(), "elicitation/create");
5206 let method = Arc::from(request.method());
5207 let request = crate::v1::JsonRpcMessage::wrap(crate::v1::Request {
5208 id: crate::v1::RequestId::Number(7),
5209 method,
5210 params: Some(request),
5211 });
5212 assert_eq!(
5213 serde_json::to_value(request).unwrap(),
5214 json!({
5215 "jsonrpc": "2.0",
5216 "id": 7,
5217 "method": "elicitation/create",
5218 "params": {
5219 "mode": "form",
5220 "sessionId": "sess_1",
5221 "message": "Choose a value",
5222 "requestedSchema": { "type": "object", "properties": {} }
5223 }
5224 })
5225 );
5226
5227 let notification = AgentNotification::CompleteElicitationNotification(
5228 CompleteElicitationNotification::new("elic_1"),
5229 );
5230 assert_eq!(notification.method(), "elicitation/complete");
5231 let method = Arc::from(notification.method());
5232 let notification = crate::v1::JsonRpcMessage::wrap(crate::v1::Notification {
5233 method,
5234 params: Some(notification),
5235 });
5236 assert_eq!(
5237 serde_json::to_value(notification).unwrap(),
5238 json!({
5239 "jsonrpc": "2.0",
5240 "method": "elicitation/complete",
5241 "params": { "elicitationId": "elic_1" }
5242 })
5243 );
5244 }
5245
5246 #[test]
5247 fn test_client_capabilities_default_on_malformed_values() {
5248 use serde_json::json;
5249
5250 let capabilities: ClientCapabilities = serde_json::from_value(json!({
5251 "fs": {
5252 "readTextFile": "yes",
5253 "writeTextFile": true
5254 },
5255 "terminal": {}
5256 }))
5257 .unwrap();
5258
5259 assert!(!capabilities.fs.read_text_file);
5260 assert!(capabilities.fs.write_text_file);
5261 assert!(!capabilities.terminal);
5262
5263 let capabilities: ClientCapabilities = serde_json::from_value(json!({
5264 "fs": false
5265 }))
5266 .unwrap();
5267 assert_eq!(capabilities.fs, FileSystemCapabilities::default());
5268
5269 {
5270 let capabilities: ClientCapabilities = serde_json::from_value(json!({
5271 "auth": false
5272 }))
5273 .unwrap();
5274 assert_eq!(capabilities.auth, AuthCapabilities::default());
5275
5276 let capabilities: AuthCapabilities = serde_json::from_value(json!({
5277 "terminal": {}
5278 }))
5279 .unwrap();
5280 assert!(!capabilities.terminal);
5281 }
5282 }
5283
5284 #[test]
5285 fn test_serialization_behavior() {
5286 use serde_json::json;
5287
5288 assert_eq!(
5289 serde_json::from_value::<SessionInfoUpdate>(json!({})).unwrap(),
5290 SessionInfoUpdate {
5291 title: MaybeUndefined::Undefined,
5292 updated_at: MaybeUndefined::Undefined,
5293 meta: None
5294 }
5295 );
5296 assert_eq!(
5297 serde_json::from_value::<SessionInfoUpdate>(json!({"title": null, "updatedAt": null}))
5298 .unwrap(),
5299 SessionInfoUpdate {
5300 title: MaybeUndefined::Null,
5301 updated_at: MaybeUndefined::Null,
5302 meta: None
5303 }
5304 );
5305 assert_eq!(
5306 serde_json::from_value::<SessionInfoUpdate>(
5307 json!({"title": "title", "updatedAt": "timestamp"})
5308 )
5309 .unwrap(),
5310 SessionInfoUpdate {
5311 title: MaybeUndefined::Value("title".to_string()),
5312 updated_at: MaybeUndefined::Value("timestamp".to_string()),
5313 meta: None
5314 }
5315 );
5316
5317 assert_eq!(
5318 serde_json::to_value(SessionInfoUpdate::new()).unwrap(),
5319 json!({})
5320 );
5321 assert_eq!(
5322 serde_json::to_value(SessionInfoUpdate::new().title("title")).unwrap(),
5323 json!({"title": "title"})
5324 );
5325 assert_eq!(
5326 serde_json::to_value(SessionInfoUpdate::new().title(None)).unwrap(),
5327 json!({"title": null})
5328 );
5329 assert_eq!(
5330 serde_json::to_value(
5331 SessionInfoUpdate::new()
5332 .title("title")
5333 .title(MaybeUndefined::Undefined)
5334 )
5335 .unwrap(),
5336 json!({})
5337 );
5338 }
5339
5340 #[test]
5341 fn test_content_chunk_message_id_serialization() {
5342 use serde_json::json;
5343
5344 assert_eq!(
5345 serde_json::to_value(SessionUpdate::AgentMessageChunk(ContentChunk::new(
5346 ContentBlock::Text(crate::v1::TextContent::new("Hello"))
5347 )))
5348 .unwrap(),
5349 json!({
5350 "sessionUpdate": "agent_message_chunk",
5351 "content": {
5352 "type": "text",
5353 "text": "Hello"
5354 }
5355 })
5356 );
5357
5358 assert_eq!(
5359 serde_json::to_value(SessionUpdate::AgentMessageChunk(
5360 ContentChunk::new(ContentBlock::Text(crate::v1::TextContent::new("Hello")))
5361 .message_id("msg_agent_c42b9")
5362 ))
5363 .unwrap(),
5364 json!({
5365 "sessionUpdate": "agent_message_chunk",
5366 "messageId": "msg_agent_c42b9",
5367 "content": {
5368 "type": "text",
5369 "text": "Hello"
5370 }
5371 })
5372 );
5373
5374 let SessionUpdate::AgentMessageChunk(chunk) = serde_json::from_value(json!({
5375 "sessionUpdate": "agent_message_chunk",
5376 "messageId": null,
5377 "content": {
5378 "type": "text",
5379 "text": "Hello"
5380 }
5381 }))
5382 .unwrap() else {
5383 panic!("expected agent message chunk");
5384 };
5385
5386 assert_eq!(chunk.message_id, None);
5387 }
5388
5389 #[test]
5390 fn test_usage_update_serialization() {
5391 use serde_json::json;
5392
5393 assert_eq!(
5394 serde_json::to_value(SessionUpdate::UsageUpdate(UsageUpdate::new(
5395 53_000, 200_000
5396 )))
5397 .unwrap(),
5398 json!({
5399 "sessionUpdate": "usage_update",
5400 "used": 53000,
5401 "size": 200_000
5402 })
5403 );
5404
5405 assert_eq!(
5406 serde_json::to_value(SessionUpdate::UsageUpdate(
5407 UsageUpdate::new(53_000, 200_000).cost(Cost::new(0.045, "USD"))
5408 ))
5409 .unwrap(),
5410 json!({
5411 "sessionUpdate": "usage_update",
5412 "used": 53000,
5413 "size": 200_000,
5414 "cost": {
5415 "amount": 0.045,
5416 "currency": "USD"
5417 }
5418 })
5419 );
5420
5421 let SessionUpdate::UsageUpdate(update) = serde_json::from_value(json!({
5422 "sessionUpdate": "usage_update",
5423 "used": 53000,
5424 "size": 200_000,
5425 "cost": null
5426 }))
5427 .unwrap() else {
5428 panic!("expected usage update");
5429 };
5430
5431 assert_eq!(update.cost, None);
5432 }
5433
5434 #[test]
5435 fn test_client_capabilities_boolean_config_options_serialization() {
5436 use serde_json::json;
5437
5438 let capabilities = ClientCapabilities::new().session(
5439 ClientSessionCapabilities::new().config_options(
5440 SessionConfigOptionsCapabilities::new()
5441 .boolean(BooleanConfigOptionCapabilities::new()),
5442 ),
5443 );
5444 let json = serde_json::to_value(&capabilities).unwrap();
5445
5446 assert_eq!(json["session"]["configOptions"]["boolean"], json!({}));
5447
5448 let omitted: ClientCapabilities = serde_json::from_value(json!({})).unwrap();
5449 assert!(omitted.session.is_none());
5450
5451 let null_session: ClientCapabilities = serde_json::from_value(json!({
5452 "session": null
5453 }))
5454 .unwrap();
5455 assert!(null_session.session.is_none());
5456
5457 let null_config_options: ClientCapabilities = serde_json::from_value(json!({
5458 "session": {
5459 "configOptions": null
5460 }
5461 }))
5462 .unwrap();
5463 assert!(
5464 null_config_options
5465 .session
5466 .and_then(|session| session.config_options)
5467 .is_none()
5468 );
5469
5470 let null_boolean: ClientCapabilities = serde_json::from_value(json!({
5471 "session": {
5472 "configOptions": {
5473 "boolean": null
5474 }
5475 }
5476 }))
5477 .unwrap();
5478 assert!(
5479 null_boolean
5480 .session
5481 .and_then(|session| session.config_options)
5482 .and_then(|config_options| config_options.boolean)
5483 .is_none()
5484 );
5485 }
5486
5487 #[cfg(feature = "unstable_plan_operations")]
5488 #[test]
5489 fn test_plan_operations_serialization() {
5490 use serde_json::json;
5491
5492 use crate::v1::{PlanEntry, PlanEntryPriority, PlanEntryStatus, PlanUpdateContent};
5493
5494 let plan_update = SessionUpdate::PlanUpdate(PlanUpdate::new(PlanUpdateContent::items(
5495 "plan-1",
5496 vec![PlanEntry::new(
5497 "Step 1",
5498 PlanEntryPriority::High,
5499 PlanEntryStatus::Pending,
5500 )],
5501 )));
5502
5503 assert_eq!(
5504 serde_json::to_value(plan_update).unwrap(),
5505 json!({
5506 "sessionUpdate": "plan_update",
5507 "plan": {
5508 "type": "items",
5509 "planId": "plan-1",
5510 "entries": [
5511 {
5512 "content": "Step 1",
5513 "priority": "high",
5514 "status": "pending"
5515 }
5516 ]
5517 }
5518 })
5519 );
5520
5521 assert_eq!(
5522 serde_json::to_value(SessionUpdate::PlanRemoved(PlanRemoved::new("plan-1"))).unwrap(),
5523 json!({
5524 "sessionUpdate": "plan_removed",
5525 "planId": "plan-1"
5526 })
5527 );
5528
5529 let capabilities = ClientCapabilities::new().plan(PlanCapabilities::new());
5530 let json = serde_json::to_value(&capabilities).unwrap();
5531 assert_eq!(json["plan"], json!({}));
5532
5533 assert_eq!(
5534 serde_json::from_value::<ClientCapabilities>(json!({ "plan": null }))
5535 .unwrap()
5536 .plan,
5537 None
5538 );
5539 }
5540
5541 #[cfg(feature = "unstable_mcp_over_acp")]
5542 #[test]
5543 fn test_agent_mcp_request_method_names() {
5544 use serde_json::json;
5545
5546 let params: serde_json::Map<String, serde_json::Value> =
5547 [("cursor".to_string(), json!("abc"))].into_iter().collect();
5548
5549 assert_eq!(CLIENT_METHOD_NAMES.mcp_message, "mcp/message");
5550 assert_eq!(
5551 AgentRequest::MessageMcpRequest(MessageMcpRequest::new(
5552 "server-1",
5553 "req-1",
5554 "tools/list"
5555 ))
5556 .method(),
5557 "mcp/message"
5558 );
5559 assert_eq!(
5560 serde_json::to_value(
5561 MessageMcpRequest::new("server-1", "req-1", "tools/list").params(params)
5562 )
5563 .unwrap(),
5564 json!({
5565 "serverId": "server-1",
5566 "requestId": "req-1",
5567 "method": "tools/list",
5568 "params": { "cursor": "abc" }
5569 })
5570 );
5571
5572 let request_with_null_params: MessageMcpRequest = serde_json::from_value(json!({
5573 "serverId": "server-1",
5574 "requestId": "req-1",
5575 "method": "tools/list",
5576 "params": null,
5577 "_meta": null
5578 }))
5579 .unwrap();
5580 assert_eq!(request_with_null_params.params, None);
5581 assert_eq!(request_with_null_params.meta, None);
5582 for key in ["serverId", "requestId", "method"] {
5583 let mut value =
5584 json!({"serverId":"server-1", "requestId":"req-1", "method":"tools/list"});
5585 value.as_object_mut().unwrap().remove(key);
5586 assert!(serde_json::from_value::<MessageMcpRequest>(value).is_err());
5587 }
5588 for key in ["serverId", "requestId", "method"] {
5589 let mut value =
5590 json!({"serverId":"server-1", "requestId":"req-1", "method":"tools/list"});
5591 value[key] = serde_json::Value::Null;
5592 assert!(serde_json::from_value::<MessageMcpRequest>(value).is_err());
5593 }
5594 }
5595
5596 #[test]
5597 fn request_permission_request_rejects_malformed_options() {
5598 use serde_json::json;
5599
5600 assert!(
5601 serde_json::from_value::<RequestPermissionRequest>(json!({
5602 "sessionId": "sess-1",
5603 "toolCall": {"toolCallId": "tc-1"},
5604 "options": "not-an-array"
5605 }))
5606 .is_err()
5607 );
5608 assert!(
5609 serde_json::from_value::<RequestPermissionRequest>(json!({
5610 "sessionId": "sess-1",
5611 "toolCall": {"toolCallId": "tc-1"},
5612 "options": [{"optionId": "allow"}]
5613 }))
5614 .is_err()
5615 );
5616 }
5617
5618 #[test]
5619 fn create_terminal_request_rejects_malformed_fields() {
5620 use serde_json::json;
5621
5622 let request = |field: &str, value: serde_json::Value| {
5623 let mut request = json!({"sessionId": "sess-1", "command": "npx"});
5624 request[field] = value;
5625 serde_json::from_value::<CreateTerminalRequest>(request)
5626 };
5627
5628 for (field, value) in [
5630 ("args", json!(["serve", "--port", 3000])),
5631 ("args", json!("serve --port 3000")),
5632 ("env", json!([{"name": "PORT", "value": 3000}])),
5633 ("env", json!({"PORT": "3000"})),
5634 ("cwd", json!({"path": "/repo"})),
5635 ("outputByteLimit", json!("1048576")),
5636 ] {
5637 assert!(request(field, value.clone()).is_err(), "{field}: {value}");
5638 }
5639
5640 for field in ["args", "env", "cwd", "outputByteLimit"] {
5641 assert_eq!(
5642 request(field, serde_json::Value::Null).unwrap(),
5643 CreateTerminalRequest::new("sess-1", "npx"),
5644 "{field}"
5645 );
5646 }
5647 }
5648
5649 #[test]
5650 fn read_text_file_request_rejects_malformed_range() {
5651 use serde_json::json;
5652
5653 let request = |field: &str, value: serde_json::Value| {
5654 let mut request = json!({"sessionId": "sess-1", "path": "/repo/src/main.rs"});
5655 request[field] = value;
5656 serde_json::from_value::<ReadTextFileRequest>(request)
5657 };
5658
5659 for field in ["line", "limit"] {
5661 assert!(request(field, json!("120")).is_err(), "{field}");
5662 assert!(request(field, json!(-1)).is_err(), "{field}");
5663 assert_eq!(
5664 request(field, serde_json::Value::Null).unwrap(),
5665 ReadTextFileRequest::new("sess-1", "/repo/src/main.rs"),
5666 "{field}"
5667 );
5668 }
5669 }
5670}