1use std::{collections::BTreeMap, sync::Arc};
7
8use derive_more::{Display, From};
9#[cfg(feature = "schemars")]
10use schemars::Schema;
11use serde::{Deserialize, Serialize};
12use serde_with::{DefaultOnError, VecSkipError, serde_as, skip_serializing_none};
13
14#[cfg(feature = "unstable_plan_operations")]
15use super::PlanRemoved;
16#[cfg(feature = "unstable_end_turn_token_usage")]
17use super::Usage;
18use super::{
19 AbsolutePath, ContentBlock, ExtNotification, ExtRequest, ExtResponse, Meta, PlanUpdate,
20 SessionConfigOption, SessionId, StopReason, TerminalId, TerminalOutputChunk, TerminalUpdate,
21 ToolCallContentChunk, ToolCallId, ToolCallUpdate,
22};
23use super::{
24 CompleteElicitationNotification, CreateElicitationRequest, CreateElicitationResponse,
25 ElicitationCapabilities,
26};
27use crate::{IntoMaybeUndefined, IntoOption, MaybeUndefined, SkipListener};
28
29#[cfg(feature = "unstable_mcp_over_acp")]
30use super::mcp::{
31 ConnectMcpRequest, ConnectMcpResponse, DisconnectMcpRequest, DisconnectMcpResponse,
32 MCP_CONNECT_METHOD_NAME, MCP_DISCONNECT_METHOD_NAME, MCP_MESSAGE_METHOD_NAME,
33 MessageMcpNotification, MessageMcpRequest, MessageMcpResponse,
34};
35
36#[cfg(feature = "unstable_nes")]
37use super::{ClientNesCapabilities, PositionEncodingKind};
38
39#[serde_as]
47#[skip_serializing_none]
48#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
49#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
50#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = SESSION_UPDATE_NOTIFICATION)))]
51#[serde(rename_all = "camelCase")]
52#[non_exhaustive]
53pub struct UpdateSessionNotification {
54 pub session_id: SessionId,
56 pub update: SessionUpdate,
58 #[serde_as(deserialize_as = "DefaultOnError")]
64 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
65 #[serde(default)]
66 #[serde(rename = "_meta")]
67 pub meta: Option<Meta>,
68}
69
70impl UpdateSessionNotification {
71 #[must_use]
73 pub fn new(session_id: impl Into<SessionId>, update: SessionUpdate) -> Self {
74 Self {
75 session_id: session_id.into(),
76 update,
77 meta: None,
78 }
79 }
80
81 #[must_use]
87 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
88 self.meta = meta.into_option();
89 self
90 }
91}
92
93#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
99#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
100#[serde(tag = "sessionUpdate", rename_all = "snake_case")]
101#[non_exhaustive]
102pub enum SessionUpdate {
103 UserMessageChunk(ContentChunk),
105 UserMessage(UserMessage),
111 AgentMessageChunk(ContentChunk),
113 AgentMessage(AgentMessage),
119 AgentThoughtChunk(ContentChunk),
121 AgentThought(AgentThought),
127 StateUpdate(StateUpdate),
129 ToolCallContentChunk(ToolCallContentChunk),
131 ToolCallUpdate(ToolCallUpdate),
133 TerminalUpdate(TerminalUpdate),
135 TerminalOutputChunk(TerminalOutputChunk),
137 PlanUpdate(PlanUpdate),
140 #[cfg(feature = "unstable_plan_operations")]
146 PlanRemoved(PlanRemoved),
147 AvailableCommandsUpdate(AvailableCommandsUpdate),
149 ConfigOptionUpdate(ConfigOptionUpdate),
151 SessionInfoUpdate(SessionInfoUpdate),
153 UsageUpdate(UsageUpdate),
155 #[cfg(feature = "unstable_session_compaction")]
161 CompactionUpdate(CompactionUpdate),
162 #[cfg(feature = "unstable_session_compaction")]
168 CompactionSummaryChunk(CompactionSummaryChunk),
169 #[serde(untagged)]
179 Other(OtherSessionUpdate),
180}
181
182#[cfg(feature = "unstable_session_compaction")]
188#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
189#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
190#[serde(transparent)]
191#[from(forward)]
192#[non_exhaustive]
193pub struct CompactionId(pub Arc<str>);
194
195#[cfg(feature = "unstable_session_compaction")]
196impl CompactionId {
197 #[must_use]
199 pub fn new(id: impl Into<Self>) -> Self {
200 id.into()
201 }
202}
203
204#[cfg(feature = "unstable_session_compaction")]
210#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
211#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
212#[serde(rename_all = "snake_case")]
213#[non_exhaustive]
214pub enum CompactionStatus {
215 InProgress,
217 Completed,
219 Failed,
221 Cancelled,
223 #[serde(untagged)]
228 Other(String),
229}
230
231#[cfg(feature = "unstable_session_compaction")]
243#[serde_as]
244#[skip_serializing_none]
245#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
246#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
247#[serde(rename_all = "camelCase")]
248#[non_exhaustive]
249pub struct CompactionUpdate {
250 pub compaction_id: CompactionId,
252 pub status: CompactionStatus,
254 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<VecSkipError<_, SkipListener>>>")]
256 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
257 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
258 pub summary: MaybeUndefined<Vec<ContentBlock>>,
259 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
261 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
262 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
263 pub error: MaybeUndefined<String>,
264 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
266 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
267 #[serde(
268 rename = "_meta",
269 default,
270 skip_serializing_if = "MaybeUndefined::is_undefined"
271 )]
272 pub meta: MaybeUndefined<Meta>,
273}
274
275#[cfg(feature = "unstable_session_compaction")]
276impl CompactionUpdate {
277 #[must_use]
279 pub fn new(compaction_id: impl Into<CompactionId>, status: CompactionStatus) -> Self {
280 Self {
281 compaction_id: compaction_id.into(),
282 status,
283 summary: MaybeUndefined::Undefined,
284 error: MaybeUndefined::Undefined,
285 meta: MaybeUndefined::Undefined,
286 }
287 }
288
289 #[must_use]
291 pub fn summary(mut self, summary: impl IntoMaybeUndefined<Vec<ContentBlock>>) -> Self {
292 self.summary = summary.into_maybe_undefined();
293 self
294 }
295
296 #[must_use]
298 pub fn error(mut self, error: impl IntoMaybeUndefined<String>) -> Self {
299 self.error = error.into_maybe_undefined();
300 self
301 }
302
303 #[must_use]
305 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
306 self.meta = meta.into_maybe_undefined();
307 self
308 }
309}
310
311#[cfg(feature = "unstable_session_compaction")]
319#[serde_as]
320#[skip_serializing_none]
321#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
322#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
323#[serde(rename_all = "camelCase")]
324#[non_exhaustive]
325pub struct CompactionSummaryChunk {
326 pub compaction_id: CompactionId,
328 pub content: ContentBlock,
330 #[serde_as(deserialize_as = "DefaultOnError")]
332 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
333 #[serde(default, rename = "_meta")]
334 pub meta: Option<Meta>,
335}
336
337#[cfg(feature = "unstable_session_compaction")]
338impl CompactionSummaryChunk {
339 #[must_use]
341 pub fn new(compaction_id: impl Into<CompactionId>, content: ContentBlock) -> Self {
342 Self {
343 compaction_id: compaction_id.into(),
344 content,
345 meta: None,
346 }
347 }
348
349 #[must_use]
351 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
352 self.meta = meta.into_option();
353 self
354 }
355}
356
357#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
363#[derive(Debug, Clone, Serialize, PartialEq)]
364#[cfg_attr(feature = "schemars", schemars(inline))]
365#[cfg_attr(feature = "schemars", schemars(transform = other_session_update_schema))]
366#[serde(rename_all = "camelCase")]
367#[non_exhaustive]
368pub struct OtherSessionUpdate {
369 #[serde(rename = "sessionUpdate")]
375 pub session_update: String,
376 #[serde(flatten)]
378 pub fields: BTreeMap<String, serde_json::Value>,
379}
380
381impl OtherSessionUpdate {
382 #[must_use]
384 pub fn new(
385 session_update: impl Into<String>,
386 mut fields: BTreeMap<String, serde_json::Value>,
387 ) -> Self {
388 fields.remove("sessionUpdate");
389 Self {
390 session_update: session_update.into(),
391 fields,
392 }
393 }
394}
395
396impl<'de> Deserialize<'de> for OtherSessionUpdate {
397 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
398 where
399 D: serde::Deserializer<'de>,
400 {
401 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
402 let session_update = fields
403 .remove("sessionUpdate")
404 .ok_or_else(|| serde::de::Error::missing_field("sessionUpdate"))?;
405 let serde_json::Value::String(session_update) = session_update else {
406 return Err(serde::de::Error::custom("`sessionUpdate` must be a string"));
407 };
408
409 if is_known_session_update(&session_update) {
410 return Err(serde::de::Error::custom(format!(
411 "known session update `{session_update}` did not match its schema"
412 )));
413 }
414
415 Ok(Self {
416 session_update,
417 fields,
418 })
419 }
420}
421
422fn is_known_session_update(session_update: &str) -> bool {
423 #[cfg(feature = "unstable_session_compaction")]
424 if matches!(
425 session_update,
426 "compaction_update" | "compaction_summary_chunk"
427 ) {
428 return true;
429 }
430 #[cfg(feature = "unstable_plan_operations")]
431 if session_update == "plan_removed" {
432 return true;
433 }
434 matches!(
435 session_update,
436 "user_message_chunk"
437 | "user_message"
438 | "agent_message_chunk"
439 | "agent_message"
440 | "agent_thought_chunk"
441 | "agent_thought"
442 | "state_update"
443 | "tool_call_content_chunk"
444 | "tool_call_update"
445 | "terminal_update"
446 | "terminal_output_chunk"
447 | "plan_update"
448 | "available_commands_update"
449 | "config_option_update"
450 | "session_info_update"
451 | "usage_update"
452 )
453}
454
455#[cfg(feature = "schemars")]
456fn other_session_update_schema(schema: &mut Schema) {
457 super::schema_util::reject_known_string_discriminators(
458 schema,
459 "sessionUpdate",
460 &[
461 "user_message_chunk",
462 "user_message",
463 "agent_message_chunk",
464 "agent_message",
465 "agent_thought_chunk",
466 "agent_thought",
467 "state_update",
468 "tool_call_content_chunk",
469 "tool_call_update",
470 "terminal_update",
471 "terminal_output_chunk",
472 "plan_update",
473 "available_commands_update",
474 "config_option_update",
475 "session_info_update",
476 #[cfg(feature = "unstable_plan_operations")]
477 "plan_removed",
478 "usage_update",
479 #[cfg(feature = "unstable_session_compaction")]
480 "compaction_update",
481 #[cfg(feature = "unstable_session_compaction")]
482 "compaction_summary_chunk",
483 ],
484 );
485}
486
487#[serde_as]
489#[skip_serializing_none]
490#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
491#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
492#[serde(rename_all = "camelCase")]
493#[non_exhaustive]
494pub struct ConfigOptionUpdate {
495 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, SkipListener>>")]
497 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
498 pub config_options: Vec<SessionConfigOption>,
499 #[serde_as(deserialize_as = "DefaultOnError")]
505 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
506 #[serde(default)]
507 #[serde(rename = "_meta")]
508 pub meta: Option<Meta>,
509}
510
511impl ConfigOptionUpdate {
512 #[must_use]
514 pub fn new(config_options: Vec<SessionConfigOption>) -> Self {
515 Self {
516 config_options,
517 meta: None,
518 }
519 }
520
521 #[must_use]
527 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
528 self.meta = meta.into_option();
529 self
530 }
531}
532
533#[serde_as]
541#[skip_serializing_none]
542#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
543#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
544#[serde(rename_all = "camelCase")]
545#[non_exhaustive]
546pub struct SessionInfoUpdate {
547 #[serde_as(deserialize_as = "DefaultOnError")]
549 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
550 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
551 pub title: MaybeUndefined<String>,
552 #[serde_as(deserialize_as = "DefaultOnError")]
554 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "format" = "date-time")))]
555 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
556 pub updated_at: MaybeUndefined<String>,
557 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
563 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
564 #[serde(
565 rename = "_meta",
566 default,
567 skip_serializing_if = "MaybeUndefined::is_undefined"
568 )]
569 pub meta: MaybeUndefined<Meta>,
570}
571
572impl SessionInfoUpdate {
573 #[must_use]
575 pub fn new() -> Self {
576 Self::default()
577 }
578
579 #[must_use]
581 pub fn title(mut self, title: impl IntoMaybeUndefined<String>) -> Self {
582 self.title = title.into_maybe_undefined();
583 self
584 }
585
586 #[must_use]
588 pub fn updated_at(mut self, updated_at: impl IntoMaybeUndefined<String>) -> Self {
589 self.updated_at = updated_at.into_maybe_undefined();
590 self
591 }
592
593 #[must_use]
599 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
600 self.meta = meta.into_maybe_undefined();
601 self
602 }
603}
604
605#[serde_as]
607#[skip_serializing_none]
608#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
609#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
610#[serde(rename_all = "camelCase")]
611#[non_exhaustive]
612pub struct UsageUpdate {
613 pub used: u64,
615 pub size: u64,
617 #[serde_as(deserialize_as = "DefaultOnError")]
619 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
620 #[serde(default)]
621 pub cost: Option<Cost>,
622 #[serde_as(deserialize_as = "DefaultOnError")]
628 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
629 #[serde(default)]
630 #[serde(rename = "_meta")]
631 pub meta: Option<Meta>,
632}
633
634impl UsageUpdate {
635 #[must_use]
637 pub fn new(used: u64, size: u64) -> Self {
638 Self {
639 used,
640 size,
641 cost: None,
642 meta: None,
643 }
644 }
645
646 #[must_use]
648 pub fn cost(mut self, cost: impl IntoOption<Cost>) -> Self {
649 self.cost = cost.into_option();
650 self
651 }
652
653 #[must_use]
659 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
660 self.meta = meta.into_option();
661 self
662 }
663}
664
665#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
670#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
671#[serde(tag = "state", rename_all = "snake_case")]
672#[non_exhaustive]
673pub enum StateUpdate {
674 Running(RunningStateUpdate),
676 Idle(IdleStateUpdate),
678 RequiresAction(RequiresActionStateUpdate),
680 #[serde(untagged)]
686 Other(OtherStateUpdate),
687}
688
689#[serde_as]
691#[skip_serializing_none]
692#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
693#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
694#[serde(rename_all = "camelCase")]
695#[non_exhaustive]
696pub struct RunningStateUpdate {
697 #[serde_as(deserialize_as = "DefaultOnError")]
703 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
704 #[serde(default)]
705 #[serde(rename = "_meta")]
706 pub meta: Option<Meta>,
707}
708
709impl RunningStateUpdate {
710 #[must_use]
712 pub fn new() -> Self {
713 Self::default()
714 }
715
716 #[must_use]
722 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
723 self.meta = meta.into_option();
724 self
725 }
726}
727
728#[serde_as]
730#[skip_serializing_none]
731#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
732#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
733#[serde(rename_all = "camelCase")]
734#[non_exhaustive]
735pub struct IdleStateUpdate {
736 #[serde_as(deserialize_as = "DefaultOnError")]
741 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
742 #[serde(default)]
743 pub stop_reason: Option<StopReason>,
744 #[cfg(feature = "unstable_end_turn_token_usage")]
753 #[serde_as(deserialize_as = "DefaultOnError")]
754 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
755 #[serde(default)]
756 pub usage: Option<Usage>,
757 #[serde_as(deserialize_as = "DefaultOnError")]
763 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
764 #[serde(default)]
765 #[serde(rename = "_meta")]
766 pub meta: Option<Meta>,
767}
768
769impl IdleStateUpdate {
770 #[must_use]
772 pub fn new() -> Self {
773 Self::default()
774 }
775
776 #[must_use]
778 pub fn stop_reason(mut self, stop_reason: impl IntoOption<StopReason>) -> Self {
779 self.stop_reason = stop_reason.into_option();
780 self
781 }
782
783 #[cfg(feature = "unstable_end_turn_token_usage")]
789 #[must_use]
790 pub fn usage(mut self, usage: impl IntoOption<Usage>) -> Self {
791 self.usage = usage.into_option();
792 self
793 }
794
795 #[must_use]
801 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
802 self.meta = meta.into_option();
803 self
804 }
805}
806
807#[serde_as]
809#[skip_serializing_none]
810#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
811#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
812#[serde(rename_all = "camelCase")]
813#[non_exhaustive]
814pub struct RequiresActionStateUpdate {
815 #[serde_as(deserialize_as = "DefaultOnError")]
821 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
822 #[serde(default)]
823 #[serde(rename = "_meta")]
824 pub meta: Option<Meta>,
825}
826
827impl RequiresActionStateUpdate {
828 #[must_use]
830 pub fn new() -> Self {
831 Self::default()
832 }
833
834 #[must_use]
840 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
841 self.meta = meta.into_option();
842 self
843 }
844}
845
846#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
851#[derive(Debug, Clone, Serialize, PartialEq)]
852#[cfg_attr(feature = "schemars", schemars(inline))]
853#[cfg_attr(feature = "schemars", schemars(transform = other_state_update_schema))]
854#[serde(rename_all = "camelCase")]
855#[non_exhaustive]
856pub struct OtherStateUpdate {
857 #[serde(rename = "state")]
863 pub state: String,
864 #[serde(flatten)]
866 pub fields: BTreeMap<String, serde_json::Value>,
867}
868
869impl OtherStateUpdate {
870 #[must_use]
872 pub fn new(state: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
873 fields.remove("state");
874 Self {
875 state: state.into(),
876 fields,
877 }
878 }
879}
880
881impl<'de> Deserialize<'de> for OtherStateUpdate {
882 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
883 where
884 D: serde::Deserializer<'de>,
885 {
886 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
887 let state = fields
888 .remove("state")
889 .ok_or_else(|| serde::de::Error::missing_field("state"))?;
890 let serde_json::Value::String(state) = state else {
891 return Err(serde::de::Error::custom("`state` must be a string"));
892 };
893
894 if is_known_state_update(&state) {
895 return Err(serde::de::Error::custom(format!(
896 "known state update `{state}` did not match its schema"
897 )));
898 }
899
900 Ok(Self { state, fields })
901 }
902}
903
904fn is_known_state_update(state: &str) -> bool {
905 matches!(state, "running" | "idle" | "requires_action")
906}
907
908#[cfg(feature = "schemars")]
909fn other_state_update_schema(schema: &mut Schema) {
910 super::schema_util::reject_known_string_discriminators(
911 schema,
912 "state",
913 &["running", "idle", "requires_action"],
914 );
915}
916
917#[serde_as]
919#[skip_serializing_none]
920#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
921#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
922#[serde(rename_all = "camelCase")]
923#[non_exhaustive]
924pub struct Cost {
925 pub amount: f64,
927 #[cfg_attr(feature = "schemars", schemars(pattern(r"^[A-Z]{3}$")))]
929 pub currency: String,
930 #[serde_as(deserialize_as = "DefaultOnError")]
936 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
937 #[serde(default)]
938 #[serde(rename = "_meta")]
939 pub meta: Option<Meta>,
940}
941
942impl Cost {
943 #[must_use]
945 pub fn new(amount: f64, currency: impl Into<String>) -> Self {
946 Self {
947 amount,
948 currency: currency.into(),
949 meta: None,
950 }
951 }
952
953 #[must_use]
959 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
960 self.meta = meta.into_option();
961 self
962 }
963}
964
965#[serde_as]
967#[skip_serializing_none]
968#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
969#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
970#[serde(rename_all = "camelCase")]
971#[non_exhaustive]
972pub struct ContentChunk {
973 pub message_id: MessageId,
978 pub content: ContentBlock,
980 #[serde_as(deserialize_as = "DefaultOnError")]
986 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
987 #[serde(default)]
988 #[serde(rename = "_meta")]
989 pub meta: Option<Meta>,
990}
991
992impl ContentChunk {
993 #[must_use]
995 pub fn new(content: ContentBlock, message_id: impl Into<MessageId>) -> Self {
996 Self {
997 content,
998 message_id: message_id.into(),
999 meta: None,
1000 }
1001 }
1002
1003 #[must_use]
1009 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1010 self.meta = meta.into_option();
1011 self
1012 }
1013}
1014
1015#[serde_as]
1029#[skip_serializing_none]
1030#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1031#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1032#[serde(rename_all = "camelCase")]
1033#[non_exhaustive]
1034pub struct UserMessage {
1035 pub message_id: MessageId,
1037 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<VecSkipError<_, SkipListener>>>")]
1039 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
1040 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
1041 pub content: MaybeUndefined<Vec<ContentBlock>>,
1042 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
1048 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1049 #[serde(
1050 rename = "_meta",
1051 default,
1052 skip_serializing_if = "MaybeUndefined::is_undefined"
1053 )]
1054 pub meta: MaybeUndefined<Meta>,
1055}
1056
1057impl UserMessage {
1058 #[must_use]
1060 pub fn new(message_id: impl Into<MessageId>) -> Self {
1061 Self {
1062 message_id: message_id.into(),
1063 content: MaybeUndefined::Undefined,
1064 meta: MaybeUndefined::Undefined,
1065 }
1066 }
1067
1068 #[must_use]
1070 pub fn content(mut self, content: impl IntoMaybeUndefined<Vec<ContentBlock>>) -> Self {
1071 self.content = content.into_maybe_undefined();
1072 self
1073 }
1074
1075 #[must_use]
1081 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
1082 self.meta = meta.into_maybe_undefined();
1083 self
1084 }
1085}
1086
1087#[serde_as]
1101#[skip_serializing_none]
1102#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1103#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1104#[serde(rename_all = "camelCase")]
1105#[non_exhaustive]
1106pub struct AgentMessage {
1107 pub message_id: MessageId,
1109 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<VecSkipError<_, SkipListener>>>")]
1111 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
1112 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
1113 pub content: MaybeUndefined<Vec<ContentBlock>>,
1114 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
1120 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1121 #[serde(
1122 rename = "_meta",
1123 default,
1124 skip_serializing_if = "MaybeUndefined::is_undefined"
1125 )]
1126 pub meta: MaybeUndefined<Meta>,
1127}
1128
1129impl AgentMessage {
1130 #[must_use]
1132 pub fn new(message_id: impl Into<MessageId>) -> Self {
1133 Self {
1134 message_id: message_id.into(),
1135 content: MaybeUndefined::Undefined,
1136 meta: MaybeUndefined::Undefined,
1137 }
1138 }
1139
1140 #[must_use]
1142 pub fn content(mut self, content: impl IntoMaybeUndefined<Vec<ContentBlock>>) -> Self {
1143 self.content = content.into_maybe_undefined();
1144 self
1145 }
1146
1147 #[must_use]
1153 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
1154 self.meta = meta.into_maybe_undefined();
1155 self
1156 }
1157}
1158
1159#[serde_as]
1173#[skip_serializing_none]
1174#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1175#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1176#[serde(rename_all = "camelCase")]
1177#[non_exhaustive]
1178pub struct AgentThought {
1179 pub message_id: MessageId,
1181 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<VecSkipError<_, SkipListener>>>")]
1183 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
1184 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
1185 pub content: MaybeUndefined<Vec<ContentBlock>>,
1186 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
1192 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1193 #[serde(
1194 rename = "_meta",
1195 default,
1196 skip_serializing_if = "MaybeUndefined::is_undefined"
1197 )]
1198 pub meta: MaybeUndefined<Meta>,
1199}
1200
1201impl AgentThought {
1202 #[must_use]
1204 pub fn new(message_id: impl Into<MessageId>) -> Self {
1205 Self {
1206 message_id: message_id.into(),
1207 content: MaybeUndefined::Undefined,
1208 meta: MaybeUndefined::Undefined,
1209 }
1210 }
1211
1212 #[must_use]
1214 pub fn content(mut self, content: impl IntoMaybeUndefined<Vec<ContentBlock>>) -> Self {
1215 self.content = content.into_maybe_undefined();
1216 self
1217 }
1218
1219 #[must_use]
1225 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
1226 self.meta = meta.into_maybe_undefined();
1227 self
1228 }
1229}
1230
1231#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1233#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
1234#[serde(transparent)]
1235#[from(forward)]
1236#[non_exhaustive]
1237pub struct MessageId(pub Arc<str>);
1238
1239impl MessageId {
1240 #[must_use]
1242 pub fn new(id: impl Into<Self>) -> Self {
1243 id.into()
1244 }
1245}
1246
1247#[serde_as]
1249#[skip_serializing_none]
1250#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1251#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1252#[serde(rename_all = "camelCase")]
1253#[non_exhaustive]
1254pub struct AvailableCommandsUpdate {
1255 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, SkipListener>>")]
1257 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
1258 pub available_commands: Vec<AvailableCommand>,
1259 #[serde_as(deserialize_as = "DefaultOnError")]
1265 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1266 #[serde(default)]
1267 #[serde(rename = "_meta")]
1268 pub meta: Option<Meta>,
1269}
1270
1271impl AvailableCommandsUpdate {
1272 #[must_use]
1274 pub fn new(available_commands: Vec<AvailableCommand>) -> Self {
1275 Self {
1276 available_commands,
1277 meta: None,
1278 }
1279 }
1280
1281 #[must_use]
1287 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1288 self.meta = meta.into_option();
1289 self
1290 }
1291}
1292
1293#[serde_as]
1295#[skip_serializing_none]
1296#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1297#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1298#[serde(rename_all = "camelCase")]
1299#[non_exhaustive]
1300pub struct AvailableCommand {
1301 pub name: String,
1303 pub description: String,
1305 #[serde_as(deserialize_as = "DefaultOnError")]
1307 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1308 #[serde(default)]
1309 pub input: Option<AvailableCommandInput>,
1310 #[serde_as(deserialize_as = "DefaultOnError")]
1316 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1317 #[serde(default)]
1318 #[serde(rename = "_meta")]
1319 pub meta: Option<Meta>,
1320}
1321
1322impl AvailableCommand {
1323 #[must_use]
1325 pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
1326 Self {
1327 name: name.into(),
1328 description: description.into(),
1329 input: None,
1330 meta: None,
1331 }
1332 }
1333
1334 #[must_use]
1336 pub fn input(mut self, input: impl IntoOption<AvailableCommandInput>) -> Self {
1337 self.input = input.into_option();
1338 self
1339 }
1340
1341 #[must_use]
1347 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1348 self.meta = meta.into_option();
1349 self
1350 }
1351}
1352
1353#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1355#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1356#[serde(tag = "type", rename_all = "snake_case")]
1357#[non_exhaustive]
1358pub enum AvailableCommandInput {
1359 #[serde(rename = "text")]
1361 Text(TextCommandInput),
1362 #[serde(untagged)]
1373 Other(OtherAvailableCommandInput),
1374}
1375
1376#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1378#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
1379#[cfg_attr(feature = "schemars", schemars(inline))]
1380#[cfg_attr(feature = "schemars", schemars(transform = other_available_command_input_schema))]
1381#[serde(rename_all = "camelCase")]
1382#[non_exhaustive]
1383pub struct OtherAvailableCommandInput {
1384 #[serde(rename = "type")]
1390 pub type_: String,
1391 #[serde(flatten)]
1393 pub fields: BTreeMap<String, serde_json::Value>,
1394}
1395
1396impl OtherAvailableCommandInput {
1397 #[must_use]
1399 pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
1400 fields.remove("type");
1401 Self {
1402 type_: type_.into(),
1403 fields,
1404 }
1405 }
1406}
1407
1408impl<'de> Deserialize<'de> for OtherAvailableCommandInput {
1409 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1410 where
1411 D: serde::Deserializer<'de>,
1412 {
1413 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1414 let type_ = fields
1415 .remove("type")
1416 .ok_or_else(|| serde::de::Error::missing_field("type"))?;
1417 let serde_json::Value::String(type_) = type_ else {
1418 return Err(serde::de::Error::custom("`type` must be a string"));
1419 };
1420
1421 if is_known_available_command_input_type(&type_) {
1422 return Err(serde::de::Error::custom(format!(
1423 "known available command input type `{type_}` did not match its schema"
1424 )));
1425 }
1426
1427 Ok(Self { type_, fields })
1428 }
1429}
1430
1431const KNOWN_AVAILABLE_COMMAND_INPUT_TYPES: &[&str] = &["text"];
1432
1433fn is_known_available_command_input_type(type_: &str) -> bool {
1434 KNOWN_AVAILABLE_COMMAND_INPUT_TYPES.contains(&type_)
1435}
1436
1437#[cfg(feature = "schemars")]
1438fn other_available_command_input_schema(schema: &mut Schema) {
1439 super::schema_util::reject_known_string_discriminators(
1440 schema,
1441 "type",
1442 KNOWN_AVAILABLE_COMMAND_INPUT_TYPES,
1443 );
1444}
1445
1446#[serde_as]
1448#[skip_serializing_none]
1449#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1450#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1451#[serde(rename_all = "camelCase")]
1452#[non_exhaustive]
1453pub struct TextCommandInput {
1454 pub hint: String,
1456 #[serde_as(deserialize_as = "DefaultOnError")]
1462 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1463 #[serde(default)]
1464 #[serde(rename = "_meta")]
1465 pub meta: Option<Meta>,
1466}
1467
1468impl TextCommandInput {
1469 #[must_use]
1471 pub fn new(hint: impl Into<String>) -> Self {
1472 Self {
1473 hint: hint.into(),
1474 meta: None,
1475 }
1476 }
1477
1478 #[must_use]
1484 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1485 self.meta = meta.into_option();
1486 self
1487 }
1488}
1489
1490#[serde_as]
1498#[skip_serializing_none]
1499#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1500#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1501#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = SESSION_REQUEST_PERMISSION_METHOD_NAME)))]
1502#[serde(rename_all = "camelCase")]
1503#[non_exhaustive]
1504pub struct RequestPermissionRequest {
1505 pub session_id: SessionId,
1507 pub title: String,
1512 #[serde_as(deserialize_as = "DefaultOnError")]
1518 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1519 #[serde(default)]
1520 pub description: Option<String>,
1521 #[serde(default)]
1525 pub subject: Option<RequestPermissionSubject>,
1526 #[cfg_attr(feature = "schemars", schemars(length(min = 1)))]
1529 pub options: Vec<PermissionOption>,
1530 #[serde_as(deserialize_as = "DefaultOnError")]
1536 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1537 #[serde(default)]
1538 #[serde(rename = "_meta")]
1539 pub meta: Option<Meta>,
1540}
1541
1542impl RequestPermissionRequest {
1543 #[must_use]
1545 pub fn new(
1546 session_id: impl Into<SessionId>,
1547 title: impl Into<String>,
1548 options: Vec<PermissionOption>,
1549 ) -> Self {
1550 Self {
1551 session_id: session_id.into(),
1552 title: title.into(),
1553 description: None,
1554 subject: None,
1555 options,
1556 meta: None,
1557 }
1558 }
1559
1560 #[must_use]
1562 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
1563 self.description = description.into_option();
1564 self
1565 }
1566
1567 #[must_use]
1569 pub fn subject(mut self, subject: impl IntoOption<RequestPermissionSubject>) -> Self {
1570 self.subject = subject.into_option();
1571 self
1572 }
1573
1574 #[must_use]
1580 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1581 self.meta = meta.into_option();
1582 self
1583 }
1584}
1585
1586#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1588#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1589#[serde(tag = "type", rename_all = "snake_case")]
1590#[non_exhaustive]
1591pub enum RequestPermissionSubject {
1592 ToolCall(Box<ToolCallPermissionSubject>),
1594 Command(CommandPermissionSubject),
1596 #[serde(untagged)]
1607 Other(OtherRequestPermissionSubject),
1608}
1609
1610impl From<ToolCallPermissionSubject> for RequestPermissionSubject {
1611 fn from(subject: ToolCallPermissionSubject) -> Self {
1612 Self::ToolCall(Box::new(subject))
1613 }
1614}
1615
1616impl From<ToolCallUpdate> for RequestPermissionSubject {
1617 fn from(tool_call: ToolCallUpdate) -> Self {
1618 ToolCallPermissionSubject::new(tool_call).into()
1619 }
1620}
1621
1622impl From<CommandPermissionSubject> for RequestPermissionSubject {
1623 fn from(subject: CommandPermissionSubject) -> Self {
1624 Self::Command(subject)
1625 }
1626}
1627
1628#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1630#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1631#[serde(rename_all = "camelCase")]
1632#[non_exhaustive]
1633pub struct ToolCallPermissionSubject {
1634 pub tool_call: ToolCallUpdate,
1636}
1637
1638impl ToolCallPermissionSubject {
1639 #[must_use]
1641 pub fn new(tool_call: ToolCallUpdate) -> Self {
1642 Self { tool_call }
1643 }
1644}
1645
1646#[serde_as]
1648#[skip_serializing_none]
1649#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1650#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1651#[serde(rename_all = "camelCase")]
1652#[non_exhaustive]
1653pub struct CommandPermissionSubject {
1654 pub command: String,
1656 pub cwd: AbsolutePath,
1658 #[serde_as(deserialize_as = "DefaultOnError")]
1660 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1661 #[serde(default)]
1662 pub tool_call_id: Option<ToolCallId>,
1663 #[serde_as(deserialize_as = "DefaultOnError")]
1665 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1666 #[serde(default)]
1667 pub terminal_id: Option<TerminalId>,
1668 #[serde_as(deserialize_as = "DefaultOnError")]
1674 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1675 #[serde(default)]
1676 #[serde(rename = "_meta")]
1677 pub meta: Option<Meta>,
1678}
1679
1680impl CommandPermissionSubject {
1681 #[must_use]
1683 pub fn new(command: impl Into<String>, cwd: impl Into<AbsolutePath>) -> Self {
1684 Self {
1685 command: command.into(),
1686 cwd: cwd.into(),
1687 tool_call_id: None,
1688 terminal_id: None,
1689 meta: None,
1690 }
1691 }
1692
1693 #[must_use]
1695 pub fn tool_call_id(mut self, tool_call_id: impl IntoOption<ToolCallId>) -> Self {
1696 self.tool_call_id = tool_call_id.into_option();
1697 self
1698 }
1699
1700 #[must_use]
1702 pub fn terminal_id(mut self, terminal_id: impl IntoOption<TerminalId>) -> Self {
1703 self.terminal_id = terminal_id.into_option();
1704 self
1705 }
1706
1707 #[must_use]
1709 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1710 self.meta = meta.into_option();
1711 self
1712 }
1713}
1714
1715#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1717#[derive(Debug, Clone, Serialize, PartialEq)]
1718#[cfg_attr(feature = "schemars", schemars(inline))]
1719#[cfg_attr(feature = "schemars", schemars(transform = other_request_permission_subject_schema))]
1720#[serde(rename_all = "camelCase")]
1721#[non_exhaustive]
1722pub struct OtherRequestPermissionSubject {
1723 #[serde(rename = "type")]
1729 pub type_: String,
1730 #[serde(flatten)]
1732 pub fields: BTreeMap<String, serde_json::Value>,
1733}
1734
1735impl OtherRequestPermissionSubject {
1736 #[must_use]
1738 pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
1739 fields.remove("type");
1740 Self {
1741 type_: type_.into(),
1742 fields,
1743 }
1744 }
1745}
1746
1747impl<'de> Deserialize<'de> for OtherRequestPermissionSubject {
1748 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1749 where
1750 D: serde::Deserializer<'de>,
1751 {
1752 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1753 let type_ = fields
1754 .remove("type")
1755 .ok_or_else(|| serde::de::Error::missing_field("type"))?;
1756 let serde_json::Value::String(type_) = type_ else {
1757 return Err(serde::de::Error::custom("`type` must be a string"));
1758 };
1759
1760 if is_known_request_permission_subject_type(&type_) {
1761 return Err(serde::de::Error::custom(format!(
1762 "known request permission subject `{type_}` did not match its schema"
1763 )));
1764 }
1765
1766 Ok(Self { type_, fields })
1767 }
1768}
1769
1770fn is_known_request_permission_subject_type(type_: &str) -> bool {
1771 matches!(type_, "tool_call" | "command")
1772}
1773
1774#[cfg(feature = "schemars")]
1775fn other_request_permission_subject_schema(schema: &mut Schema) {
1776 super::schema_util::reject_known_string_discriminators(
1777 schema,
1778 "type",
1779 &["tool_call", "command"],
1780 );
1781}
1782
1783#[serde_as]
1785#[skip_serializing_none]
1786#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1787#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1788#[serde(rename_all = "camelCase")]
1789#[non_exhaustive]
1790pub struct PermissionOption {
1791 pub option_id: PermissionOptionId,
1793 pub name: String,
1795 pub kind: PermissionOptionKind,
1797 #[serde_as(deserialize_as = "DefaultOnError")]
1803 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1804 #[serde(default)]
1805 #[serde(rename = "_meta")]
1806 pub meta: Option<Meta>,
1807}
1808
1809impl PermissionOption {
1810 #[must_use]
1812 pub fn new(
1813 option_id: impl Into<PermissionOptionId>,
1814 name: impl Into<String>,
1815 kind: PermissionOptionKind,
1816 ) -> Self {
1817 Self {
1818 option_id: option_id.into(),
1819 name: name.into(),
1820 kind,
1821 meta: None,
1822 }
1823 }
1824
1825 #[must_use]
1831 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1832 self.meta = meta.into_option();
1833 self
1834 }
1835}
1836
1837#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1839#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
1840#[serde(transparent)]
1841#[from(forward)]
1842#[non_exhaustive]
1843pub struct PermissionOptionId(pub Arc<str>);
1844
1845impl PermissionOptionId {
1846 #[must_use]
1848 pub fn new(id: impl Into<Self>) -> Self {
1849 id.into()
1850 }
1851}
1852
1853#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1857#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1858#[serde(rename_all = "snake_case")]
1859#[non_exhaustive]
1860pub enum PermissionOptionKind {
1861 AllowOnce,
1863 AllowAlways,
1865 RejectOnce,
1867 RejectAlways,
1869 #[serde(untagged)]
1875 Other(String),
1876}
1877
1878#[serde_as]
1880#[skip_serializing_none]
1881#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1882#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1883#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = SESSION_REQUEST_PERMISSION_METHOD_NAME)))]
1884#[serde(rename_all = "camelCase")]
1885#[non_exhaustive]
1886pub struct RequestPermissionResponse {
1887 pub outcome: RequestPermissionOutcome,
1889 #[serde_as(deserialize_as = "DefaultOnError")]
1895 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1896 #[serde(default)]
1897 #[serde(rename = "_meta")]
1898 pub meta: Option<Meta>,
1899}
1900
1901impl RequestPermissionResponse {
1902 #[must_use]
1904 pub fn new(outcome: RequestPermissionOutcome) -> Self {
1905 Self {
1906 outcome,
1907 meta: None,
1908 }
1909 }
1910
1911 #[must_use]
1917 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1918 self.meta = meta.into_option();
1919 self
1920 }
1921}
1922
1923#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1925#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1926#[serde(tag = "outcome", rename_all = "snake_case")]
1927#[non_exhaustive]
1928pub enum RequestPermissionOutcome {
1929 Cancelled,
1937 #[serde(rename_all = "camelCase")]
1939 Selected(SelectedPermissionOutcome),
1940 #[serde(untagged)]
1951 Other(OtherRequestPermissionOutcome),
1952}
1953
1954#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1960#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
1961#[cfg_attr(feature = "schemars", schemars(inline))]
1962#[cfg_attr(feature = "schemars", schemars(transform = other_request_permission_outcome_schema))]
1963#[serde(rename_all = "camelCase")]
1964#[non_exhaustive]
1965pub struct OtherRequestPermissionOutcome {
1966 pub outcome: String,
1972 #[serde(flatten)]
1974 pub fields: BTreeMap<String, serde_json::Value>,
1975}
1976
1977impl OtherRequestPermissionOutcome {
1978 #[must_use]
1980 pub fn new(
1981 outcome: impl Into<String>,
1982 mut fields: BTreeMap<String, serde_json::Value>,
1983 ) -> Self {
1984 fields.remove("outcome");
1985 Self {
1986 outcome: outcome.into(),
1987 fields,
1988 }
1989 }
1990}
1991
1992impl<'de> Deserialize<'de> for OtherRequestPermissionOutcome {
1993 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1994 where
1995 D: serde::Deserializer<'de>,
1996 {
1997 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1998 let outcome = fields
1999 .remove("outcome")
2000 .ok_or_else(|| serde::de::Error::missing_field("outcome"))?;
2001 let serde_json::Value::String(outcome) = outcome else {
2002 return Err(serde::de::Error::custom("`outcome` must be a string"));
2003 };
2004
2005 if is_known_request_permission_outcome(&outcome) {
2006 return Err(serde::de::Error::custom(format!(
2007 "known request permission outcome `{outcome}` did not match its schema"
2008 )));
2009 }
2010
2011 Ok(Self { outcome, fields })
2012 }
2013}
2014
2015fn is_known_request_permission_outcome(outcome: &str) -> bool {
2016 matches!(outcome, "cancelled" | "selected")
2017}
2018
2019#[cfg(feature = "schemars")]
2020fn other_request_permission_outcome_schema(schema: &mut Schema) {
2021 super::schema_util::reject_known_string_discriminators(
2022 schema,
2023 "outcome",
2024 &["cancelled", "selected"],
2025 );
2026}
2027
2028#[serde_as]
2030#[skip_serializing_none]
2031#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2032#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2033#[serde(rename_all = "camelCase")]
2034#[non_exhaustive]
2035pub struct SelectedPermissionOutcome {
2036 pub option_id: PermissionOptionId,
2038 #[serde_as(deserialize_as = "DefaultOnError")]
2044 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2045 #[serde(default)]
2046 #[serde(rename = "_meta")]
2047 pub meta: Option<Meta>,
2048}
2049
2050impl SelectedPermissionOutcome {
2051 #[must_use]
2053 pub fn new(option_id: impl Into<PermissionOptionId>) -> Self {
2054 Self {
2055 option_id: option_id.into(),
2056 meta: None,
2057 }
2058 }
2059
2060 #[must_use]
2066 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2067 self.meta = meta.into_option();
2068 self
2069 }
2070}
2071
2072#[serde_as]
2081#[skip_serializing_none]
2082#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2083#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2084#[serde(rename_all = "camelCase")]
2085#[non_exhaustive]
2086pub struct ClientCapabilities {
2087 #[serde_as(deserialize_as = "DefaultOnError")]
2094 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2095 #[serde(default)]
2096 pub auth: Option<AuthCapabilities>,
2097 #[serde_as(deserialize_as = "DefaultOnError")]
2103 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2104 #[serde(default)]
2105 pub elicitation: Option<ElicitationCapabilities>,
2106 #[cfg(feature = "unstable_nes")]
2115 #[serde_as(deserialize_as = "DefaultOnError")]
2116 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2117 #[serde(default)]
2118 pub nes: Option<ClientNesCapabilities>,
2119 #[cfg(feature = "unstable_nes")]
2125 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, SkipListener>>")]
2126 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
2127 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2128 pub position_encodings: Vec<PositionEncodingKind>,
2129
2130 #[serde_as(deserialize_as = "DefaultOnError")]
2136 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2137 #[serde(default)]
2138 #[serde(rename = "_meta")]
2139 pub meta: Option<Meta>,
2140}
2141
2142impl ClientCapabilities {
2143 #[must_use]
2145 pub fn new() -> Self {
2146 Self::default()
2147 }
2148
2149 #[must_use]
2153 pub fn auth(mut self, auth: impl IntoOption<AuthCapabilities>) -> Self {
2154 self.auth = auth.into_option();
2155 self
2156 }
2157
2158 #[must_use]
2161 pub fn elicitation(mut self, elicitation: impl IntoOption<ElicitationCapabilities>) -> Self {
2162 self.elicitation = elicitation.into_option();
2163 self
2164 }
2165
2166 #[cfg(feature = "unstable_nes")]
2170 #[must_use]
2171 pub fn nes(mut self, nes: impl IntoOption<ClientNesCapabilities>) -> Self {
2172 self.nes = nes.into_option();
2173 self
2174 }
2175
2176 #[cfg(feature = "unstable_nes")]
2180 #[must_use]
2181 pub fn position_encodings(mut self, position_encodings: Vec<PositionEncodingKind>) -> Self {
2182 self.position_encodings = position_encodings;
2183 self
2184 }
2185
2186 #[must_use]
2192 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2193 self.meta = meta.into_option();
2194 self
2195 }
2196}
2197
2198#[serde_as]
2204#[skip_serializing_none]
2205#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2206#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2207#[serde(rename_all = "camelCase")]
2208#[non_exhaustive]
2209pub struct AuthCapabilities {
2210 #[serde_as(deserialize_as = "DefaultOnError")]
2217 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2218 #[serde(default)]
2219 pub terminal: Option<TerminalAuthCapabilities>,
2220 #[serde_as(deserialize_as = "DefaultOnError")]
2226 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2227 #[serde(default)]
2228 #[serde(rename = "_meta")]
2229 pub meta: Option<Meta>,
2230}
2231
2232impl AuthCapabilities {
2233 #[must_use]
2235 pub fn new() -> Self {
2236 Self::default()
2237 }
2238
2239 #[must_use]
2247 pub fn terminal(mut self, terminal: impl IntoOption<TerminalAuthCapabilities>) -> Self {
2248 self.terminal = terminal.into_option();
2249 self
2250 }
2251
2252 #[must_use]
2258 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2259 self.meta = meta.into_option();
2260 self
2261 }
2262}
2263
2264#[serde_as]
2270#[skip_serializing_none]
2271#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2272#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2273#[non_exhaustive]
2274pub struct TerminalAuthCapabilities {
2275 #[serde_as(deserialize_as = "DefaultOnError")]
2281 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2282 #[serde(default)]
2283 #[serde(rename = "_meta")]
2284 pub meta: Option<Meta>,
2285}
2286
2287impl TerminalAuthCapabilities {
2288 #[must_use]
2290 pub fn new() -> Self {
2291 Self::default()
2292 }
2293
2294 #[must_use]
2300 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2301 self.meta = meta.into_option();
2302 self
2303 }
2304}
2305
2306#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2312#[non_exhaustive]
2313pub struct ClientMethodNames {
2314 pub session_request_permission: &'static str,
2316 pub session_update: &'static str,
2318 #[cfg(feature = "unstable_mcp_over_acp")]
2320 pub mcp_connect: &'static str,
2321 #[cfg(feature = "unstable_mcp_over_acp")]
2323 pub mcp_message: &'static str,
2324 #[cfg(feature = "unstable_mcp_over_acp")]
2326 pub mcp_disconnect: &'static str,
2327 pub elicitation_create: &'static str,
2329 pub elicitation_complete: &'static str,
2331}
2332
2333pub const CLIENT_METHOD_NAMES: ClientMethodNames = ClientMethodNames {
2335 session_update: SESSION_UPDATE_NOTIFICATION,
2336 session_request_permission: SESSION_REQUEST_PERMISSION_METHOD_NAME,
2337 #[cfg(feature = "unstable_mcp_over_acp")]
2338 mcp_connect: MCP_CONNECT_METHOD_NAME,
2339 #[cfg(feature = "unstable_mcp_over_acp")]
2340 mcp_message: MCP_MESSAGE_METHOD_NAME,
2341 #[cfg(feature = "unstable_mcp_over_acp")]
2342 mcp_disconnect: MCP_DISCONNECT_METHOD_NAME,
2343 elicitation_create: ELICITATION_CREATE_METHOD_NAME,
2344 elicitation_complete: ELICITATION_COMPLETE_NOTIFICATION,
2345};
2346
2347pub(crate) const SESSION_UPDATE_NOTIFICATION: &str = "session/update";
2349pub(crate) const SESSION_REQUEST_PERMISSION_METHOD_NAME: &str = "session/request_permission";
2351pub(crate) const ELICITATION_CREATE_METHOD_NAME: &str = "elicitation/create";
2353pub(crate) const ELICITATION_COMPLETE_NOTIFICATION: &str = "elicitation/complete";
2355
2356#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2363#[derive(Clone, Debug, Serialize, Deserialize)]
2364#[serde(untagged)]
2365#[cfg_attr(feature = "schemars", schemars(inline))]
2366#[non_exhaustive]
2367pub enum AgentRequest {
2368 RequestPermissionRequest(Box<RequestPermissionRequest>),
2379 CreateElicitationRequest(Box<CreateElicitationRequest>),
2383 #[cfg(feature = "unstable_mcp_over_acp")]
2389 ConnectMcpRequest(Box<ConnectMcpRequest>),
2390 #[cfg(feature = "unstable_mcp_over_acp")]
2396 MessageMcpRequest(Box<MessageMcpRequest>),
2397 #[cfg(feature = "unstable_mcp_over_acp")]
2403 DisconnectMcpRequest(Box<DisconnectMcpRequest>),
2404 ExtMethodRequest(Box<ExtRequest>),
2412}
2413
2414impl AgentRequest {
2415 #[must_use]
2417 pub fn method(&self) -> &str {
2418 match self {
2419 Self::RequestPermissionRequest(_) => CLIENT_METHOD_NAMES.session_request_permission,
2420 Self::CreateElicitationRequest(_) => CLIENT_METHOD_NAMES.elicitation_create,
2421 #[cfg(feature = "unstable_mcp_over_acp")]
2422 Self::ConnectMcpRequest(_) => CLIENT_METHOD_NAMES.mcp_connect,
2423 #[cfg(feature = "unstable_mcp_over_acp")]
2424 Self::MessageMcpRequest(_) => CLIENT_METHOD_NAMES.mcp_message,
2425 #[cfg(feature = "unstable_mcp_over_acp")]
2426 Self::DisconnectMcpRequest(_) => CLIENT_METHOD_NAMES.mcp_disconnect,
2427 Self::ExtMethodRequest(ext_request) => &ext_request.method,
2428 }
2429 }
2430}
2431
2432#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2439#[derive(Clone, Debug, Serialize, Deserialize)]
2440#[serde(untagged)]
2441#[cfg_attr(feature = "schemars", schemars(inline))]
2442#[non_exhaustive]
2443pub enum ClientResponse {
2444 RequestPermissionResponse(Box<RequestPermissionResponse>),
2446 CreateElicitationResponse(Box<CreateElicitationResponse>),
2448 #[cfg(feature = "unstable_mcp_over_acp")]
2450 ConnectMcpResponse(Box<ConnectMcpResponse>),
2451 #[cfg(feature = "unstable_mcp_over_acp")]
2453 DisconnectMcpResponse(#[serde(default)] Box<DisconnectMcpResponse>),
2454 #[cfg(feature = "unstable_mcp_over_acp")]
2456 MessageMcpResponse(Box<MessageMcpResponse>),
2457 ExtMethodResponse(Box<ExtResponse>),
2459}
2460
2461#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2468#[derive(Clone, Debug, Serialize, Deserialize)]
2469#[serde(untagged)]
2470#[cfg_attr(feature = "schemars", schemars(inline))]
2471#[non_exhaustive]
2472pub enum AgentNotification {
2473 UpdateSessionNotification(Box<UpdateSessionNotification>),
2486 CompleteElicitationNotification(Box<CompleteElicitationNotification>),
2490 #[cfg(feature = "unstable_mcp_over_acp")]
2496 MessageMcpNotification(Box<MessageMcpNotification>),
2497 ExtNotification(Box<ExtNotification>),
2505}
2506
2507impl AgentNotification {
2508 #[must_use]
2510 pub fn method(&self) -> &str {
2511 match self {
2512 Self::UpdateSessionNotification(_) => CLIENT_METHOD_NAMES.session_update,
2513 Self::CompleteElicitationNotification(_) => CLIENT_METHOD_NAMES.elicitation_complete,
2514 #[cfg(feature = "unstable_mcp_over_acp")]
2515 Self::MessageMcpNotification(_) => CLIENT_METHOD_NAMES.mcp_message,
2516 Self::ExtNotification(ext_notification) => &ext_notification.method,
2517 }
2518 }
2519}
2520
2521#[cfg(test)]
2522mod tests {
2523 use super::*;
2524
2525 #[cfg(feature = "unstable_session_compaction")]
2526 #[test]
2527 fn compaction_updates_preserve_patch_and_open_status_semantics() {
2528 use serde_json::json;
2529
2530 assert_eq!(
2531 serde_json::to_value(SessionUpdate::CompactionUpdate(
2532 CompactionUpdate::new("cmp_001", CompactionStatus::Completed).summary(vec![
2533 ContentBlock::Text(crate::v2::TextContent::new("retained")),
2534 ]),
2535 ))
2536 .unwrap(),
2537 json!({
2538 "sessionUpdate": "compaction_update",
2539 "compactionId": "cmp_001",
2540 "status": "completed",
2541 "summary": [{ "type": "text", "text": "retained" }]
2542 })
2543 );
2544
2545 let SessionUpdate::CompactionUpdate(update) = serde_json::from_value(json!({
2546 "sessionUpdate": "compaction_update",
2547 "compactionId": "cmp_001",
2548 "status": "paused",
2549 "summary": null
2550 }))
2551 .unwrap() else {
2552 panic!("expected compaction update");
2553 };
2554 assert_eq!(update.status, CompactionStatus::Other("paused".into()));
2555 assert!(update.summary.is_null());
2556 assert!(update.error.is_undefined());
2557 }
2558
2559 #[cfg(feature = "unstable_session_compaction")]
2560 #[test]
2561 fn malformed_known_compaction_update_is_not_hidden_as_unknown() {
2562 use serde_json::json;
2563
2564 assert!(
2565 serde_json::from_value::<SessionUpdate>(json!({
2566 "sessionUpdate": "compaction_update",
2567 "status": "completed"
2568 }))
2569 .is_err()
2570 );
2571 assert!(
2572 serde_json::from_value::<SessionUpdate>(json!({
2573 "sessionUpdate": "compaction_summary_chunk",
2574 "compactionId": "cmp_001"
2575 }))
2576 .is_err()
2577 );
2578 }
2579
2580 #[test]
2581 fn test_elicitation_capability_semantics() {
2582 use serde_json::json;
2583
2584 let unsupported: ClientCapabilities = serde_json::from_value(json!({})).unwrap();
2585 assert!(unsupported.elicitation.is_none());
2586
2587 let null: ClientCapabilities =
2588 serde_json::from_value(json!({ "elicitation": null })).unwrap();
2589 assert!(null.elicitation.is_none());
2590
2591 let malformed: ClientCapabilities =
2592 serde_json::from_value(json!({ "elicitation": false })).unwrap();
2593 assert!(malformed.elicitation.is_none());
2594
2595 let empty: ClientCapabilities =
2596 serde_json::from_value(json!({ "elicitation": {} })).unwrap();
2597 let empty = empty.elicitation.expect("present capability");
2598 assert!(!empty.supports_form());
2599 assert!(!empty.supports_url());
2600
2601 let form_only: ClientCapabilities = serde_json::from_value(json!({
2602 "elicitation": { "form": {} }
2603 }))
2604 .unwrap();
2605 let form_only = form_only.elicitation.expect("advertised capability");
2606 assert!(form_only.supports_form());
2607 assert!(!form_only.supports_url());
2608
2609 let url_only: ClientCapabilities = serde_json::from_value(json!({
2610 "elicitation": { "url": {} }
2611 }))
2612 .unwrap();
2613 let url_only = url_only.elicitation.expect("advertised capability");
2614 assert!(!url_only.supports_form());
2615 assert!(url_only.supports_url());
2616
2617 let both: ClientCapabilities = serde_json::from_value(json!({
2618 "elicitation": { "form": {}, "url": {} }
2619 }))
2620 .unwrap();
2621 let both = both.elicitation.expect("advertised capability");
2622 assert!(both.supports_form());
2623 assert!(both.supports_url());
2624 }
2625
2626 #[test]
2627 fn test_elicitation_method_routing_and_envelopes() {
2628 use serde_json::json;
2629
2630 assert_eq!(CLIENT_METHOD_NAMES.elicitation_create, "elicitation/create");
2631 assert_eq!(
2632 CLIENT_METHOD_NAMES.elicitation_complete,
2633 "elicitation/complete"
2634 );
2635
2636 let request =
2637 AgentRequest::CreateElicitationRequest(Box::new(CreateElicitationRequest::new(
2638 crate::v2::ElicitationFormMode::new(
2639 crate::v2::ElicitationSessionScope::new("sess_1"),
2640 crate::v2::ElicitationSchema::new(),
2641 ),
2642 "Choose a value",
2643 )));
2644 assert_eq!(request.method(), "elicitation/create");
2645 let method = Arc::from(request.method());
2646 let request = crate::v2::JsonRpcMessage::wrap(crate::v2::Request {
2647 id: crate::v2::RequestId::Number(7),
2648 method,
2649 params: Some(request),
2650 });
2651 assert_eq!(
2652 serde_json::to_value(request).unwrap(),
2653 json!({
2654 "jsonrpc": "2.0",
2655 "id": 7,
2656 "method": "elicitation/create",
2657 "params": {
2658 "mode": "form",
2659 "sessionId": "sess_1",
2660 "message": "Choose a value",
2661 "requestedSchema": { "type": "object", "properties": {} }
2662 }
2663 })
2664 );
2665
2666 let notification = AgentNotification::CompleteElicitationNotification(Box::new(
2667 CompleteElicitationNotification::new("elic_1"),
2668 ));
2669 assert_eq!(notification.method(), "elicitation/complete");
2670 let method = Arc::from(notification.method());
2671 let notification = crate::v2::JsonRpcMessage::wrap(crate::v2::Notification {
2672 method,
2673 params: Some(notification),
2674 });
2675 assert_eq!(
2676 serde_json::to_value(notification).unwrap(),
2677 json!({
2678 "jsonrpc": "2.0",
2679 "method": "elicitation/complete",
2680 "params": { "elicitationId": "elic_1" }
2681 })
2682 );
2683 }
2684
2685 #[test]
2686 fn test_client_capabilities_auth_defaults_on_malformed_value() {
2687 use serde_json::json;
2688
2689 let capabilities: ClientCapabilities = serde_json::from_value(json!({
2690 "auth": false
2691 }))
2692 .unwrap();
2693
2694 assert_eq!(capabilities.auth, None);
2695 }
2696
2697 #[test]
2698 fn test_serialization_behavior() {
2699 use serde_json::json;
2700
2701 assert_eq!(
2702 serde_json::from_value::<SessionInfoUpdate>(json!({})).unwrap(),
2703 SessionInfoUpdate {
2704 title: MaybeUndefined::Undefined,
2705 updated_at: MaybeUndefined::Undefined,
2706 meta: MaybeUndefined::Undefined
2707 }
2708 );
2709 assert_eq!(
2710 serde_json::from_value::<SessionInfoUpdate>(json!({"title": null, "updatedAt": null}))
2711 .unwrap(),
2712 SessionInfoUpdate {
2713 title: MaybeUndefined::Null,
2714 updated_at: MaybeUndefined::Null,
2715 meta: MaybeUndefined::Undefined
2716 }
2717 );
2718 assert_eq!(
2719 serde_json::from_value::<SessionInfoUpdate>(
2720 json!({"title": "title", "updatedAt": "timestamp"})
2721 )
2722 .unwrap(),
2723 SessionInfoUpdate {
2724 title: MaybeUndefined::Value("title".to_string()),
2725 updated_at: MaybeUndefined::Value("timestamp".to_string()),
2726 meta: MaybeUndefined::Undefined
2727 }
2728 );
2729
2730 let clear_meta =
2731 serde_json::from_value::<SessionInfoUpdate>(json!({"_meta": null})).unwrap();
2732 assert_eq!(clear_meta.meta, MaybeUndefined::Null);
2733
2734 let mut meta = Meta::new();
2735 meta.insert("source".to_string(), json!("session-info"));
2736
2737 assert_eq!(
2738 serde_json::from_value::<SessionInfoUpdate>(json!({"_meta": {
2739 "source": "session-info"
2740 }}))
2741 .unwrap()
2742 .meta,
2743 MaybeUndefined::Value(meta.clone())
2744 );
2745
2746 assert_eq!(
2747 serde_json::to_value(SessionInfoUpdate::new()).unwrap(),
2748 json!({})
2749 );
2750
2751 assert_eq!(
2752 serde_json::to_value(SessionInfoUpdate::new().meta(None::<Meta>)).unwrap(),
2753 json!({"_meta": null})
2754 );
2755
2756 assert_eq!(
2757 serde_json::to_value(SessionInfoUpdate::new().meta(meta)).unwrap(),
2758 json!({"_meta": {
2759 "source": "session-info"
2760 }})
2761 );
2762 assert_eq!(
2763 serde_json::to_value(SessionInfoUpdate::new().title("title")).unwrap(),
2764 json!({"title": "title"})
2765 );
2766 assert_eq!(
2767 serde_json::to_value(SessionInfoUpdate::new().title(None)).unwrap(),
2768 json!({"title": null})
2769 );
2770 assert_eq!(
2771 serde_json::to_value(
2772 SessionInfoUpdate::new()
2773 .title("title")
2774 .title(MaybeUndefined::Undefined)
2775 )
2776 .unwrap(),
2777 json!({})
2778 );
2779 }
2780
2781 #[test]
2782 fn test_content_chunk_message_id_serialization() {
2783 use serde_json::json;
2784
2785 assert_eq!(
2786 serde_json::to_value(SessionUpdate::AgentMessageChunk(ContentChunk::new(
2787 ContentBlock::Text(crate::v2::TextContent::new("Hello")),
2788 "msg_agent_c42b9",
2789 )))
2790 .unwrap(),
2791 json!({
2792 "sessionUpdate": "agent_message_chunk",
2793 "messageId": "msg_agent_c42b9",
2794 "content": {
2795 "type": "text",
2796 "text": "Hello"
2797 }
2798 })
2799 );
2800
2801 let err = serde_json::from_value::<ContentChunk>(json!({
2802 "content": {
2803 "type": "text",
2804 "text": "Hello"
2805 }
2806 }))
2807 .unwrap_err();
2808
2809 assert!(err.to_string().contains("messageId"), "{err}");
2810 }
2811
2812 #[test]
2813 fn test_tool_call_content_chunk_serialization() {
2814 use serde_json::json;
2815
2816 assert_eq!(
2817 serde_json::to_value(SessionUpdate::ToolCallContentChunk(
2818 ToolCallContentChunk::new(
2819 "call_001",
2820 crate::v2::ContentBlock::Text(crate::v2::TextContent::new("partial output")),
2821 )
2822 ))
2823 .unwrap(),
2824 json!({
2825 "sessionUpdate": "tool_call_content_chunk",
2826 "toolCallId": "call_001",
2827 "content": {
2828 "type": "content",
2829 "content": {
2830 "type": "text",
2831 "text": "partial output"
2832 }
2833 }
2834 })
2835 );
2836
2837 let err = serde_json::from_value::<ToolCallContentChunk>(json!({
2838 "content": {
2839 "type": "content",
2840 "content": {
2841 "type": "text",
2842 "text": "partial output"
2843 }
2844 }
2845 }))
2846 .unwrap_err();
2847
2848 assert!(err.to_string().contains("toolCallId"), "{err}");
2849 }
2850
2851 #[test]
2852 fn test_full_message_serialization() {
2853 use serde_json::json;
2854
2855 assert_eq!(
2856 serde_json::to_value(SessionUpdate::UserMessage(
2857 UserMessage::new("msg_user_8f7a1").content(vec![ContentBlock::Text(
2858 crate::v2::TextContent::new("Hello")
2859 )])
2860 ))
2861 .unwrap(),
2862 json!({
2863 "sessionUpdate": "user_message",
2864 "messageId": "msg_user_8f7a1",
2865 "content": [
2866 {
2867 "type": "text",
2868 "text": "Hello"
2869 }
2870 ]
2871 })
2872 );
2873
2874 assert_eq!(
2875 serde_json::to_value(SessionUpdate::AgentMessage(
2876 AgentMessage::new("msg_agent_c42b9").content(vec![ContentBlock::Text(
2877 crate::v2::TextContent::new("Hello")
2878 )])
2879 ))
2880 .unwrap(),
2881 json!({
2882 "sessionUpdate": "agent_message",
2883 "messageId": "msg_agent_c42b9",
2884 "content": [
2885 {
2886 "type": "text",
2887 "text": "Hello"
2888 }
2889 ]
2890 })
2891 );
2892
2893 assert_eq!(
2894 serde_json::to_value(SessionUpdate::AgentThought(
2895 AgentThought::new("msg_thought_a12").content(vec![ContentBlock::Text(
2896 crate::v2::TextContent::new("Need to inspect the call sites first.")
2897 )])
2898 ))
2899 .unwrap(),
2900 json!({
2901 "sessionUpdate": "agent_thought",
2902 "messageId": "msg_thought_a12",
2903 "content": [
2904 {
2905 "type": "text",
2906 "text": "Need to inspect the call sites first."
2907 }
2908 ]
2909 })
2910 );
2911 }
2912
2913 #[test]
2914 fn test_message_upsert_serialization() {
2915 use serde_json::json;
2916
2917 assert_eq!(
2918 serde_json::to_value(SessionUpdate::UserMessage(
2919 UserMessage::new("msg_empty").content(Vec::<ContentBlock>::new())
2920 ))
2921 .unwrap(),
2922 json!({
2923 "sessionUpdate": "user_message",
2924 "messageId": "msg_empty",
2925 "content": []
2926 })
2927 );
2928
2929 let empty = serde_json::from_value::<UserMessage>(json!({
2930 "messageId": "msg_empty",
2931 "content": []
2932 }))
2933 .unwrap();
2934 assert!(matches!(
2935 empty.content,
2936 MaybeUndefined::Value(ref content) if content.is_empty()
2937 ));
2938
2939 let patch = serde_json::from_value::<AgentMessage>(json!({
2940 "messageId": "msg_agent_c42b9"
2941 }))
2942 .unwrap();
2943 assert_eq!(patch.content, MaybeUndefined::Undefined);
2944 assert_eq!(patch.meta, MaybeUndefined::Undefined);
2945
2946 let malformed_meta = serde_json::from_value::<AgentMessage>(json!({
2947 "messageId": "msg_agent_c42b9",
2948 "_meta": false
2949 }))
2950 .unwrap();
2951 assert_eq!(malformed_meta.meta, MaybeUndefined::Undefined);
2952
2953 let patch = serde_json::from_value::<AgentThought>(json!({
2954 "messageId": "msg_thought_a12"
2955 }))
2956 .unwrap();
2957 assert_eq!(patch.content, MaybeUndefined::Undefined);
2958
2959 let clear = serde_json::from_value::<UserMessage>(json!({
2960 "messageId": "msg_user_8f7a1",
2961 "content": null
2962 }))
2963 .unwrap();
2964 assert_eq!(clear.content, MaybeUndefined::Null);
2965
2966 let clear_meta = serde_json::from_value::<UserMessage>(json!({
2967 "messageId": "msg_user_8f7a1",
2968 "_meta": null
2969 }))
2970 .unwrap();
2971 assert_eq!(clear_meta.meta, MaybeUndefined::Null);
2972
2973 let mut meta = Meta::new();
2974 meta.insert("source".to_string(), json!("replay"));
2975
2976 assert_eq!(
2977 serde_json::to_value(SessionUpdate::UserMessage(
2978 UserMessage::new("msg_user_8f7a1").meta(meta)
2979 ))
2980 .unwrap(),
2981 json!({
2982 "sessionUpdate": "user_message",
2983 "messageId": "msg_user_8f7a1",
2984 "_meta": {
2985 "source": "replay"
2986 }
2987 })
2988 );
2989
2990 assert_eq!(
2991 serde_json::to_value(SessionUpdate::UserMessage(
2992 UserMessage::new("msg_user_8f7a1").meta(None::<Meta>)
2993 ))
2994 .unwrap(),
2995 json!({
2996 "sessionUpdate": "user_message",
2997 "messageId": "msg_user_8f7a1",
2998 "_meta": null
2999 })
3000 );
3001 }
3002
3003 #[test]
3004 fn test_usage_update_serialization() {
3005 use serde_json::json;
3006
3007 assert_eq!(
3008 serde_json::to_value(SessionUpdate::UsageUpdate(UsageUpdate::new(
3009 53_000, 200_000
3010 )))
3011 .unwrap(),
3012 json!({
3013 "sessionUpdate": "usage_update",
3014 "used": 53000,
3015 "size": 200_000
3016 })
3017 );
3018
3019 assert_eq!(
3020 serde_json::to_value(SessionUpdate::UsageUpdate(
3021 UsageUpdate::new(53_000, 200_000).cost(Cost::new(0.045, "USD"))
3022 ))
3023 .unwrap(),
3024 json!({
3025 "sessionUpdate": "usage_update",
3026 "used": 53000,
3027 "size": 200_000,
3028 "cost": {
3029 "amount": 0.045,
3030 "currency": "USD"
3031 }
3032 })
3033 );
3034
3035 let SessionUpdate::UsageUpdate(update) = serde_json::from_value(json!({
3036 "sessionUpdate": "usage_update",
3037 "used": 53000,
3038 "size": 200_000,
3039 "cost": null
3040 }))
3041 .unwrap() else {
3042 panic!("expected usage update");
3043 };
3044
3045 assert_eq!(update.cost, None);
3046 }
3047
3048 #[test]
3049 fn test_state_update_serialization() {
3050 use serde_json::json;
3051
3052 assert_eq!(
3053 serde_json::to_value(SessionUpdate::StateUpdate(StateUpdate::Running(
3054 RunningStateUpdate::new()
3055 )))
3056 .unwrap(),
3057 json!({
3058 "sessionUpdate": "state_update",
3059 "state": "running"
3060 })
3061 );
3062
3063 assert_eq!(
3064 serde_json::to_value(SessionUpdate::StateUpdate(StateUpdate::Idle(
3065 IdleStateUpdate::new().stop_reason(StopReason::EndTurn)
3066 )))
3067 .unwrap(),
3068 json!({
3069 "sessionUpdate": "state_update",
3070 "state": "idle",
3071 "stopReason": "end_turn"
3072 })
3073 );
3074
3075 let SessionUpdate::StateUpdate(update) = serde_json::from_value(json!({
3076 "sessionUpdate": "state_update",
3077 "state": "requires_action"
3078 }))
3079 .unwrap() else {
3080 panic!("expected state update");
3081 };
3082
3083 assert!(matches!(update, StateUpdate::RequiresAction(_)));
3084
3085 let SessionUpdate::StateUpdate(StateUpdate::Idle(update)) = serde_json::from_value(json!({
3086 "sessionUpdate": "state_update",
3087 "state": "idle",
3088 "stopReason": null
3089 }))
3090 .unwrap() else {
3091 panic!("expected idle state update");
3092 };
3093
3094 assert_eq!(update.stop_reason, None);
3095
3096 let SessionUpdate::StateUpdate(StateUpdate::Other(update)) =
3097 serde_json::from_value(json!({
3098 "sessionUpdate": "state_update",
3099 "state": "_paused",
3100 "label": "Paused"
3101 }))
3102 .unwrap()
3103 else {
3104 panic!("expected unknown state update");
3105 };
3106
3107 assert_eq!(update.state, "_paused");
3108 assert_eq!(update.fields["label"], json!("Paused"));
3109 }
3110
3111 #[test]
3112 fn session_update_preserves_unknown_variant() {
3113 use serde_json::json;
3114
3115 let update: SessionUpdate = serde_json::from_value(json!({
3116 "sessionUpdate": "_status_badge",
3117 "label": "Indexing",
3118 "progress": 0.5
3119 }))
3120 .unwrap();
3121
3122 let SessionUpdate::Other(unknown) = update else {
3123 panic!("expected unknown session update");
3124 };
3125
3126 assert_eq!(unknown.session_update, "_status_badge");
3127 assert_eq!(unknown.fields.get("label"), Some(&json!("Indexing")));
3128 assert_eq!(unknown.fields.get("progress"), Some(&json!(0.5)));
3129
3130 assert_eq!(
3131 serde_json::to_value(SessionUpdate::Other(unknown)).unwrap(),
3132 json!({
3133 "sessionUpdate": "_status_badge",
3134 "label": "Indexing",
3135 "progress": 0.5
3136 })
3137 );
3138 }
3139
3140 #[test]
3141 fn terminal_session_updates_use_known_discriminators() {
3142 use serde_json::json;
3143
3144 assert_eq!(
3145 serde_json::to_value(SessionUpdate::TerminalUpdate(
3146 TerminalUpdate::new("term_1").command("cargo test")
3147 ))
3148 .unwrap(),
3149 json!({
3150 "sessionUpdate": "terminal_update",
3151 "terminalId": "term_1",
3152 "command": "cargo test"
3153 })
3154 );
3155 assert_eq!(
3156 serde_json::to_value(SessionUpdate::TerminalOutputChunk(
3157 TerminalOutputChunk::new("term_1", "dGVzdAo=")
3158 ))
3159 .unwrap(),
3160 json!({
3161 "sessionUpdate": "terminal_output_chunk",
3162 "terminalId": "term_1",
3163 "data": "dGVzdAo="
3164 })
3165 );
3166 }
3167
3168 #[test]
3169 fn session_update_does_not_hide_malformed_known_terminal_variants() {
3170 use serde_json::json;
3171
3172 assert!(
3173 serde_json::from_value::<SessionUpdate>(json!({
3174 "sessionUpdate": "terminal_update"
3175 }))
3176 .is_err()
3177 );
3178 assert!(
3179 serde_json::from_value::<SessionUpdate>(json!({
3180 "sessionUpdate": "terminal_output_chunk",
3181 "terminalId": "term_1"
3182 }))
3183 .is_err()
3184 );
3185 }
3186
3187 #[test]
3188 fn test_plan_update_serialization() {
3189 use serde_json::json;
3190
3191 let plan_update =
3192 SessionUpdate::PlanUpdate(PlanUpdate::new(crate::v2::PlanUpdateContent::items(
3193 "plan-1",
3194 vec![crate::v2::PlanEntry::new(
3195 "Step 1",
3196 crate::v2::PlanEntryPriority::High,
3197 crate::v2::PlanEntryStatus::Pending,
3198 )],
3199 )));
3200
3201 assert_eq!(
3202 serde_json::to_value(plan_update).unwrap(),
3203 json!({
3204 "sessionUpdate": "plan_update",
3205 "plan": {
3206 "type": "items",
3207 "planId": "plan-1",
3208 "entries": [
3209 {
3210 "content": "Step 1",
3211 "priority": "high",
3212 "status": "pending"
3213 }
3214 ]
3215 }
3216 })
3217 );
3218 }
3219
3220 #[cfg(feature = "unstable_plan_operations")]
3221 #[test]
3222 fn test_plan_removed_serialization() {
3223 use serde_json::json;
3224
3225 assert_eq!(
3226 serde_json::to_value(SessionUpdate::PlanRemoved(PlanRemoved::new("plan-1"))).unwrap(),
3227 json!({
3228 "sessionUpdate": "plan_removed",
3229 "planId": "plan-1"
3230 })
3231 );
3232 }
3233
3234 #[test]
3235 fn available_command_input_preserves_unknown_typed_variant() {
3236 use serde_json::json;
3237
3238 let input: AvailableCommandInput = serde_json::from_value(json!({
3239 "type": "_choices",
3240 "hint": "Pick one",
3241 "options": ["fast", "careful"]
3242 }))
3243 .unwrap();
3244
3245 let AvailableCommandInput::Other(unknown) = input else {
3246 panic!("expected unknown command input");
3247 };
3248
3249 assert_eq!(unknown.type_, "_choices");
3250 assert_eq!(unknown.fields.get("hint"), Some(&json!("Pick one")));
3251 assert_eq!(
3252 unknown.fields.get("options"),
3253 Some(&json!(["fast", "careful"]))
3254 );
3255 assert_eq!(
3256 serde_json::to_value(AvailableCommandInput::Other(unknown)).unwrap(),
3257 json!({
3258 "type": "_choices",
3259 "hint": "Pick one",
3260 "options": ["fast", "careful"]
3261 })
3262 );
3263 }
3264
3265 #[test]
3266 fn available_command_input_text_uses_type_discriminator() {
3267 use serde_json::json;
3268
3269 let input = AvailableCommandInput::Text(TextCommandInput::new("Describe changes"));
3270
3271 let json = serde_json::to_value(&input).unwrap();
3272 assert_eq!(
3273 json,
3274 json!({
3275 "type": "text",
3276 "hint": "Describe changes"
3277 })
3278 );
3279
3280 let roundtripped: AvailableCommandInput = serde_json::from_value(json).unwrap();
3281 assert!(matches!(roundtripped, AvailableCommandInput::Text(_)));
3282 }
3283
3284 #[test]
3285 fn request_permission_subject_tool_call_uses_type_discriminator() {
3286 use serde_json::json;
3287
3288 let subject = RequestPermissionSubject::from(ToolCallUpdate::new("call_001"));
3289
3290 let json = serde_json::to_value(&subject).unwrap();
3291 assert_eq!(
3292 json,
3293 json!({
3294 "type": "tool_call",
3295 "toolCall": {
3296 "toolCallId": "call_001"
3297 }
3298 })
3299 );
3300
3301 let roundtripped: RequestPermissionSubject = serde_json::from_value(json).unwrap();
3302 assert!(matches!(
3303 roundtripped,
3304 RequestPermissionSubject::ToolCall(_)
3305 ));
3306 }
3307
3308 #[test]
3309 fn request_permission_subject_command_uses_type_discriminator() {
3310 use serde_json::json;
3311
3312 let mut meta = Meta::new();
3313 meta.insert("source".to_string(), json!("shell"));
3314 let subject = RequestPermissionSubject::from(
3315 CommandPermissionSubject::new("cargo test", "/workspace/project")
3316 .tool_call_id("call_001")
3317 .terminal_id("term_1")
3318 .meta(meta),
3319 );
3320
3321 let json = serde_json::to_value(&subject).unwrap();
3322 assert_eq!(
3323 json,
3324 json!({
3325 "type": "command",
3326 "command": "cargo test",
3327 "cwd": "/workspace/project",
3328 "toolCallId": "call_001",
3329 "terminalId": "term_1",
3330 "_meta": {
3331 "source": "shell"
3332 }
3333 })
3334 );
3335
3336 let roundtripped: RequestPermissionSubject = serde_json::from_value(json).unwrap();
3337 assert!(matches!(roundtripped, RequestPermissionSubject::Command(_)));
3338 }
3339
3340 #[test]
3341 fn command_permission_subject_treats_optional_association_nulls_as_omitted() {
3342 use serde_json::json;
3343
3344 let subject: RequestPermissionSubject = serde_json::from_value(json!({
3345 "type": "command",
3346 "command": "cargo test",
3347 "cwd": "/workspace/project",
3348 "toolCallId": null,
3349 "terminalId": null,
3350 "_meta": null
3351 }))
3352 .unwrap();
3353
3354 let RequestPermissionSubject::Command(subject) = subject else {
3355 panic!("expected command permission subject");
3356 };
3357 assert_eq!(subject.cwd, AbsolutePath::new("/workspace/project"));
3358 assert_eq!(subject.tool_call_id, None);
3359 assert_eq!(subject.terminal_id, None);
3360 assert_eq!(subject.meta, None);
3361 assert_eq!(
3362 serde_json::to_value(RequestPermissionSubject::Command(subject)).unwrap(),
3363 json!({
3364 "type": "command",
3365 "command": "cargo test",
3366 "cwd": "/workspace/project"
3367 })
3368 );
3369 }
3370
3371 #[test]
3372 fn request_permission_subject_preserves_unknown_variant() {
3373 use serde_json::json;
3374
3375 let subject: RequestPermissionSubject = serde_json::from_value(json!({
3376 "type": "_review",
3377 "reason": "needs-review",
3378 "retryAfterSeconds": 30
3379 }))
3380 .unwrap();
3381
3382 let RequestPermissionSubject::Other(unknown) = subject else {
3383 panic!("expected unknown permission subject");
3384 };
3385
3386 assert_eq!(unknown.type_, "_review");
3387 assert_eq!(unknown.fields.get("reason"), Some(&json!("needs-review")));
3388 assert_eq!(unknown.fields.get("retryAfterSeconds"), Some(&json!(30)));
3389 assert_eq!(
3390 serde_json::to_value(RequestPermissionSubject::Other(unknown)).unwrap(),
3391 json!({
3392 "type": "_review",
3393 "reason": "needs-review",
3394 "retryAfterSeconds": 30
3395 })
3396 );
3397 }
3398
3399 #[test]
3400 fn request_permission_subject_unknown_does_not_hide_malformed_known_variant() {
3401 use serde_json::json;
3402
3403 assert!(
3404 serde_json::from_value::<RequestPermissionSubject>(json!({
3405 "type": "tool_call"
3406 }))
3407 .is_err()
3408 );
3409 assert!(
3410 serde_json::from_value::<RequestPermissionSubject>(json!({
3411 "type": 1
3412 }))
3413 .is_err()
3414 );
3415 assert!(
3416 serde_json::from_value::<RequestPermissionSubject>(json!({
3417 "type": "command",
3418 "cwd": "/workspace/project"
3419 }))
3420 .is_err()
3421 );
3422 assert!(
3423 serde_json::from_value::<RequestPermissionSubject>(json!({
3424 "type": "command",
3425 "command": "cargo test"
3426 }))
3427 .is_err()
3428 );
3429 assert!(
3430 serde_json::from_value::<RequestPermissionSubject>(json!({
3431 "type": "command",
3432 "command": "cargo test",
3433 "cwd": null
3434 }))
3435 .is_err()
3436 );
3437 }
3438
3439 #[test]
3440 fn request_permission_title_and_description_are_separate_from_tool_call_content() {
3441 use serde_json::json;
3442
3443 let request =
3444 RequestPermissionRequest::new("sess_abc123def456", "Approve file edit?", Vec::new())
3445 .description("Allow this tool to edit src/main.rs?")
3446 .subject(RequestPermissionSubject::from(ToolCallUpdate::new(
3447 "call_001",
3448 )));
3449
3450 assert_eq!(
3451 serde_json::to_value(request).unwrap(),
3452 json!({
3453 "sessionId": "sess_abc123def456",
3454 "title": "Approve file edit?",
3455 "description": "Allow this tool to edit src/main.rs?",
3456 "subject": {
3457 "type": "tool_call",
3458 "toolCall": {
3459 "toolCallId": "call_001"
3460 }
3461 },
3462 "options": []
3463 })
3464 );
3465 }
3466
3467 #[test]
3468 fn request_permission_requires_title_and_allows_missing_subject() {
3469 use serde_json::json;
3470
3471 let request = RequestPermissionRequest::new(
3472 "sess_abc123def456",
3473 "Approve elevated permissions?",
3474 Vec::new(),
3475 );
3476
3477 assert_eq!(
3478 serde_json::to_value(request).unwrap(),
3479 json!({
3480 "sessionId": "sess_abc123def456",
3481 "title": "Approve elevated permissions?",
3482 "options": []
3483 })
3484 );
3485
3486 let missing_subject: RequestPermissionRequest = serde_json::from_value(json!({
3487 "sessionId": "sess_abc123def456",
3488 "title": "Approve elevated permissions?",
3489 "options": []
3490 }))
3491 .unwrap();
3492 assert!(missing_subject.subject.is_none());
3493
3494 let null_subject: RequestPermissionRequest = serde_json::from_value(json!({
3495 "sessionId": "sess_abc123def456",
3496 "title": "Approve elevated permissions?",
3497 "subject": null,
3498 "options": []
3499 }))
3500 .unwrap();
3501 assert!(null_subject.subject.is_none());
3502
3503 assert!(
3504 serde_json::from_value::<RequestPermissionRequest>(json!({
3505 "sessionId": "sess_abc123def456",
3506 "options": []
3507 }))
3508 .is_err()
3509 );
3510 }
3511
3512 #[test]
3513 fn request_permission_outcome_preserves_unknown_variant() {
3514 use serde_json::json;
3515
3516 let outcome: RequestPermissionOutcome = serde_json::from_value(json!({
3517 "outcome": "_defer",
3518 "reason": "needs-review",
3519 "retryAfterSeconds": 30
3520 }))
3521 .unwrap();
3522
3523 let RequestPermissionOutcome::Other(unknown) = outcome else {
3524 panic!("expected unknown permission outcome");
3525 };
3526
3527 assert_eq!(unknown.outcome, "_defer");
3528 assert_eq!(unknown.fields.get("reason"), Some(&json!("needs-review")));
3529 assert_eq!(unknown.fields.get("retryAfterSeconds"), Some(&json!(30)));
3530 assert_eq!(
3531 serde_json::to_value(RequestPermissionOutcome::Other(unknown)).unwrap(),
3532 json!({
3533 "outcome": "_defer",
3534 "reason": "needs-review",
3535 "retryAfterSeconds": 30
3536 })
3537 );
3538 }
3539
3540 #[test]
3541 fn request_permission_outcome_unknown_does_not_hide_malformed_known_variant() {
3542 use serde_json::json;
3543
3544 assert!(
3545 serde_json::from_value::<RequestPermissionOutcome>(json!({
3546 "outcome": "selected"
3547 }))
3548 .is_err()
3549 );
3550 assert!(
3551 serde_json::from_value::<RequestPermissionOutcome>(json!({
3552 "outcome": 1
3553 }))
3554 .is_err()
3555 );
3556 }
3557
3558 #[test]
3559 fn available_command_input_unknown_does_not_hide_malformed_text_variant() {
3560 use serde_json::json;
3561
3562 assert!(serde_json::from_value::<AvailableCommandInput>(json!({})).is_err());
3563 assert!(
3564 serde_json::from_value::<AvailableCommandInput>(json!({
3565 "hint": "Pick one"
3566 }))
3567 .is_err()
3568 );
3569 assert!(
3570 serde_json::from_value::<AvailableCommandInput>(json!({
3571 "type": 1,
3572 "hint": "Pick one"
3573 }))
3574 .is_err()
3575 );
3576 assert!(
3577 serde_json::from_value::<OtherAvailableCommandInput>(json!({
3578 "type": "text",
3579 "hint": "Pick one"
3580 }))
3581 .is_err()
3582 );
3583 }
3584
3585 #[cfg(feature = "unstable_nes")]
3586 #[test]
3587 fn test_client_capabilities_position_encodings_serialization() {
3588 use serde_json::json;
3589
3590 let capabilities = ClientCapabilities::new().position_encodings(vec![
3591 PositionEncodingKind::Utf32,
3592 PositionEncodingKind::Utf16,
3593 ]);
3594 let json = serde_json::to_value(&capabilities).unwrap();
3595
3596 assert_eq!(json["positionEncodings"], json!(["utf-32", "utf-16"]));
3597 }
3598
3599 #[cfg(feature = "unstable_mcp_over_acp")]
3600 #[test]
3601 fn test_agent_mcp_request_method_names() {
3602 use serde_json::json;
3603
3604 let params: serde_json::Map<String, serde_json::Value> =
3605 [("cursor".to_string(), json!("abc"))].into_iter().collect();
3606
3607 assert_eq!(CLIENT_METHOD_NAMES.mcp_connect, "mcp/connect");
3608 assert_eq!(CLIENT_METHOD_NAMES.mcp_message, "mcp/message");
3609 assert_eq!(CLIENT_METHOD_NAMES.mcp_disconnect, "mcp/disconnect");
3610
3611 assert_eq!(
3612 AgentRequest::ConnectMcpRequest(Box::new(ConnectMcpRequest::new("server-1"))).method(),
3613 "mcp/connect"
3614 );
3615 assert_eq!(
3616 AgentRequest::MessageMcpRequest(Box::new(MessageMcpRequest::new(
3617 "conn-1",
3618 "tools/list"
3619 )))
3620 .method(),
3621 "mcp/message"
3622 );
3623 assert_eq!(
3624 AgentRequest::DisconnectMcpRequest(Box::new(DisconnectMcpRequest::new("conn-1")))
3625 .method(),
3626 "mcp/disconnect"
3627 );
3628 assert_eq!(
3629 AgentNotification::MessageMcpNotification(Box::new(MessageMcpNotification::new(
3630 "conn-1",
3631 "notifications/progress"
3632 )))
3633 .method(),
3634 "mcp/message"
3635 );
3636
3637 assert_eq!(
3638 serde_json::to_value(ConnectMcpRequest::new("server-1")).unwrap(),
3639 json!({ "serverId": "server-1" })
3640 );
3641 assert_eq!(
3642 serde_json::to_value(ConnectMcpResponse::new("conn-1")).unwrap(),
3643 json!({ "connectionId": "conn-1" })
3644 );
3645 assert_eq!(
3646 serde_json::to_value(MessageMcpRequest::new("conn-1", "tools/list").params(params))
3647 .unwrap(),
3648 json!({
3649 "connectionId": "conn-1",
3650 "method": "tools/list",
3651 "params": { "cursor": "abc" }
3652 })
3653 );
3654 assert_eq!(
3655 serde_json::to_value(DisconnectMcpRequest::new("conn-1")).unwrap(),
3656 json!({ "connectionId": "conn-1" })
3657 );
3658 assert_eq!(
3659 serde_json::to_value(MessageMcpNotification::new(
3660 "conn-1",
3661 "notifications/progress"
3662 ))
3663 .unwrap(),
3664 json!({
3665 "connectionId": "conn-1",
3666 "method": "notifications/progress"
3667 })
3668 );
3669
3670 let request_with_null_params: MessageMcpRequest = serde_json::from_value(json!({
3671 "connectionId": "conn-1",
3672 "method": "tools/list",
3673 "params": null
3674 }))
3675 .unwrap();
3676 assert_eq!(request_with_null_params.params, None);
3677 }
3678
3679 #[test]
3680 fn test_auth_capabilities_serialize_terminal_support_as_object() {
3681 use serde_json::json;
3682
3683 let capabilities = AuthCapabilities::new().terminal(TerminalAuthCapabilities::new());
3684
3685 assert_eq!(
3686 serde_json::to_value(&capabilities).unwrap(),
3687 json!({
3688 "terminal": {}
3689 })
3690 );
3691
3692 let deserialized: AuthCapabilities = serde_json::from_value(json!({
3693 "terminal": false
3694 }))
3695 .unwrap();
3696 assert!(deserialized.terminal.is_none());
3697 }
3698
3699 #[test]
3700 fn request_permission_request_rejects_malformed_options() {
3701 use serde_json::json;
3702
3703 assert!(
3704 serde_json::from_value::<RequestPermissionRequest>(json!({
3705 "sessionId": "sess-1",
3706 "title": "Run tool?",
3707 "options": "not-an-array"
3708 }))
3709 .is_err()
3710 );
3711 assert!(
3712 serde_json::from_value::<RequestPermissionRequest>(json!({
3713 "sessionId": "sess-1",
3714 "title": "Run tool?",
3715 "options": [{"optionId": "allow"}]
3716 }))
3717 .is_err()
3718 );
3719 }
3720
3721 #[cfg(feature = "unstable_plan_operations")]
3722 #[test]
3723 fn malformed_plan_removed_is_not_hidden_as_unknown_update() {
3724 use serde_json::json;
3725
3726 assert!(
3727 serde_json::from_value::<SessionUpdate>(json!({
3728 "sessionUpdate": "plan_removed"
3729 }))
3730 .is_err()
3731 );
3732 }
3733}