1use std::{collections::BTreeMap, sync::Arc};
7
8use derive_more::{Display, From};
9use schemars::{JsonSchema, Schema};
10use serde::{Deserialize, Serialize};
11use serde_with::{DefaultOnError, VecSkipError, serde_as, skip_serializing_none};
12
13#[cfg(feature = "unstable_plan_operations")]
14use super::PlanRemoved;
15#[cfg(feature = "unstable_end_turn_token_usage")]
16use super::Usage;
17#[cfg(feature = "unstable_elicitation")]
18use super::{
19 CompleteElicitationNotification, CreateElicitationRequest, CreateElicitationResponse,
20 ElicitationCapabilities,
21};
22use super::{
23 ContentBlock, ExtNotification, ExtRequest, ExtResponse, Meta, PlanUpdate, SessionConfigOption,
24 SessionId, StopReason, ToolCallContentChunk, ToolCallUpdate,
25};
26use crate::{IntoMaybeUndefined, IntoOption, MaybeUndefined, SkipListener};
27
28#[cfg(feature = "unstable_mcp_over_acp")]
29use super::mcp::{
30 ConnectMcpRequest, ConnectMcpResponse, DisconnectMcpRequest, DisconnectMcpResponse,
31 MCP_CONNECT_METHOD_NAME, MCP_DISCONNECT_METHOD_NAME, MCP_MESSAGE_METHOD_NAME,
32 MessageMcpNotification, MessageMcpRequest, MessageMcpResponse,
33};
34
35#[cfg(feature = "unstable_nes")]
36use super::{ClientNesCapabilities, PositionEncodingKind};
37
38#[skip_serializing_none]
46#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
47#[schemars(extend("x-side" = "client", "x-method" = SESSION_UPDATE_NOTIFICATION))]
48#[serde(rename_all = "camelCase")]
49#[non_exhaustive]
50pub struct SessionNotification {
51 pub session_id: SessionId,
53 pub update: SessionUpdate,
55 #[serde(rename = "_meta")]
61 pub meta: Option<Meta>,
62}
63
64impl SessionNotification {
65 #[must_use]
67 pub fn new(session_id: impl Into<SessionId>, update: SessionUpdate) -> Self {
68 Self {
69 session_id: session_id.into(),
70 update,
71 meta: None,
72 }
73 }
74
75 #[must_use]
81 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
82 self.meta = meta.into_option();
83 self
84 }
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
93#[serde(tag = "sessionUpdate", rename_all = "snake_case")]
94#[schemars(extend("discriminator" = {"propertyName": "sessionUpdate"}))]
95#[non_exhaustive]
96pub enum SessionUpdate {
97 UserMessageChunk(ContentChunk),
99 UserMessage(UserMessage),
105 AgentMessageChunk(ContentChunk),
107 AgentMessage(AgentMessage),
113 AgentThoughtChunk(ContentChunk),
115 AgentThought(AgentThought),
121 StateUpdate(StateUpdate),
127 ToolCallContentChunk(ToolCallContentChunk),
129 ToolCallUpdate(ToolCallUpdate),
131 PlanUpdate(PlanUpdate),
134 #[cfg(feature = "unstable_plan_operations")]
140 PlanRemoved(PlanRemoved),
141 AvailableCommandsUpdate(AvailableCommandsUpdate),
143 ConfigOptionUpdate(ConfigOptionUpdate),
145 SessionInfoUpdate(SessionInfoUpdate),
147 UsageUpdate(UsageUpdate),
149 #[serde(untagged)]
159 Other(OtherSessionUpdate),
160}
161
162#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq)]
168#[schemars(inline)]
169#[schemars(transform = other_session_update_schema)]
170#[serde(rename_all = "camelCase")]
171#[non_exhaustive]
172pub struct OtherSessionUpdate {
173 #[serde(rename = "sessionUpdate")]
179 pub session_update: String,
180 #[serde(flatten)]
182 pub fields: BTreeMap<String, serde_json::Value>,
183}
184
185impl OtherSessionUpdate {
186 #[must_use]
188 pub fn new(
189 session_update: impl Into<String>,
190 mut fields: BTreeMap<String, serde_json::Value>,
191 ) -> Self {
192 fields.remove("sessionUpdate");
193 Self {
194 session_update: session_update.into(),
195 fields,
196 }
197 }
198}
199
200impl<'de> Deserialize<'de> for OtherSessionUpdate {
201 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
202 where
203 D: serde::Deserializer<'de>,
204 {
205 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
206 let session_update = fields
207 .remove("sessionUpdate")
208 .ok_or_else(|| serde::de::Error::missing_field("sessionUpdate"))?;
209 let serde_json::Value::String(session_update) = session_update else {
210 return Err(serde::de::Error::custom("`sessionUpdate` must be a string"));
211 };
212
213 if is_known_session_update(&session_update) {
214 return Err(serde::de::Error::custom(format!(
215 "known session update `{session_update}` did not match its schema"
216 )));
217 }
218
219 Ok(Self {
220 session_update,
221 fields,
222 })
223 }
224}
225
226fn is_known_session_update(session_update: &str) -> bool {
227 matches!(
228 session_update,
229 "user_message_chunk"
230 | "user_message"
231 | "agent_message_chunk"
232 | "agent_message"
233 | "agent_thought_chunk"
234 | "agent_thought"
235 | "state_update"
236 | "tool_call_content_chunk"
237 | "tool_call_update"
238 | "plan_update"
239 | "available_commands_update"
240 | "config_option_update"
241 | "session_info_update"
242 | "usage_update"
243 )
244}
245
246fn other_session_update_schema(schema: &mut Schema) {
247 super::schema_util::reject_known_string_discriminators(
248 schema,
249 "sessionUpdate",
250 &[
251 "user_message_chunk",
252 "user_message",
253 "agent_message_chunk",
254 "agent_message",
255 "agent_thought_chunk",
256 "agent_thought",
257 "state_update",
258 "tool_call_content_chunk",
259 "tool_call_update",
260 "plan_update",
261 "available_commands_update",
262 "config_option_update",
263 "session_info_update",
264 #[cfg(feature = "unstable_plan_operations")]
265 "plan_removed",
266 "usage_update",
267 ],
268 );
269}
270
271#[serde_as]
273#[skip_serializing_none]
274#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
275#[serde(rename_all = "camelCase")]
276#[non_exhaustive]
277pub struct ConfigOptionUpdate {
278 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, SkipListener>>")]
280 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
281 pub config_options: Vec<SessionConfigOption>,
282 #[serde(rename = "_meta")]
288 pub meta: Option<Meta>,
289}
290
291impl ConfigOptionUpdate {
292 #[must_use]
294 pub fn new(config_options: Vec<SessionConfigOption>) -> Self {
295 Self {
296 config_options,
297 meta: None,
298 }
299 }
300
301 #[must_use]
307 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
308 self.meta = meta.into_option();
309 self
310 }
311}
312
313#[skip_serializing_none]
318#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
319#[serde(rename_all = "camelCase")]
320#[non_exhaustive]
321pub struct SessionInfoUpdate {
322 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
324 pub title: MaybeUndefined<String>,
325 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
327 pub updated_at: MaybeUndefined<String>,
328 #[serde(rename = "_meta")]
334 pub meta: Option<Meta>,
335}
336
337impl SessionInfoUpdate {
338 #[must_use]
340 pub fn new() -> Self {
341 Self::default()
342 }
343
344 #[must_use]
346 pub fn title(mut self, title: impl IntoMaybeUndefined<String>) -> Self {
347 self.title = title.into_maybe_undefined();
348 self
349 }
350
351 #[must_use]
353 pub fn updated_at(mut self, updated_at: impl IntoMaybeUndefined<String>) -> Self {
354 self.updated_at = updated_at.into_maybe_undefined();
355 self
356 }
357
358 #[must_use]
364 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
365 self.meta = meta.into_option();
366 self
367 }
368}
369
370#[serde_as]
372#[skip_serializing_none]
373#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
374#[serde(rename_all = "camelCase")]
375#[non_exhaustive]
376pub struct UsageUpdate {
377 pub used: u64,
379 pub size: u64,
381 #[serde_as(deserialize_as = "DefaultOnError")]
383 #[schemars(extend("x-deserialize-default-on-error" = true))]
384 #[serde(default)]
385 pub cost: Option<Cost>,
386 #[serde(rename = "_meta")]
392 pub meta: Option<Meta>,
393}
394
395impl UsageUpdate {
396 #[must_use]
398 pub fn new(used: u64, size: u64) -> Self {
399 Self {
400 used,
401 size,
402 cost: None,
403 meta: None,
404 }
405 }
406
407 #[must_use]
409 pub fn cost(mut self, cost: impl IntoOption<Cost>) -> Self {
410 self.cost = cost.into_option();
411 self
412 }
413
414 #[must_use]
420 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
421 self.meta = meta.into_option();
422 self
423 }
424}
425
426#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
433#[serde(tag = "state", rename_all = "snake_case")]
434#[schemars(extend("discriminator" = {"propertyName": "state"}))]
435#[non_exhaustive]
436pub enum StateUpdate {
437 Running(RunningStateUpdate),
439 Idle(IdleStateUpdate),
441 RequiresAction(RequiresActionStateUpdate),
443 #[serde(untagged)]
449 Other(OtherStateUpdate),
450}
451
452#[skip_serializing_none]
454#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
455#[serde(rename_all = "camelCase")]
456#[non_exhaustive]
457pub struct RunningStateUpdate {
458 #[serde(rename = "_meta")]
464 pub meta: Option<Meta>,
465}
466
467impl RunningStateUpdate {
468 #[must_use]
470 pub fn new() -> Self {
471 Self::default()
472 }
473
474 #[must_use]
480 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
481 self.meta = meta.into_option();
482 self
483 }
484}
485
486#[serde_as]
488#[skip_serializing_none]
489#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
490#[serde(rename_all = "camelCase")]
491#[non_exhaustive]
492pub struct IdleStateUpdate {
493 #[serde_as(deserialize_as = "DefaultOnError")]
498 #[schemars(extend("x-deserialize-default-on-error" = true))]
499 #[serde(default)]
500 pub stop_reason: Option<StopReason>,
501 #[cfg(feature = "unstable_end_turn_token_usage")]
510 #[serde_as(deserialize_as = "DefaultOnError")]
511 #[schemars(extend("x-deserialize-default-on-error" = true))]
512 #[serde(default)]
513 pub usage: Option<Usage>,
514 #[serde(rename = "_meta")]
520 pub meta: Option<Meta>,
521}
522
523impl IdleStateUpdate {
524 #[must_use]
526 pub fn new() -> Self {
527 Self::default()
528 }
529
530 #[must_use]
532 pub fn stop_reason(mut self, stop_reason: impl IntoOption<StopReason>) -> Self {
533 self.stop_reason = stop_reason.into_option();
534 self
535 }
536
537 #[cfg(feature = "unstable_end_turn_token_usage")]
543 #[must_use]
544 pub fn usage(mut self, usage: impl IntoOption<Usage>) -> Self {
545 self.usage = usage.into_option();
546 self
547 }
548
549 #[must_use]
555 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
556 self.meta = meta.into_option();
557 self
558 }
559}
560
561#[skip_serializing_none]
563#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
564#[serde(rename_all = "camelCase")]
565#[non_exhaustive]
566pub struct RequiresActionStateUpdate {
567 #[serde(rename = "_meta")]
573 pub meta: Option<Meta>,
574}
575
576impl RequiresActionStateUpdate {
577 #[must_use]
579 pub fn new() -> Self {
580 Self::default()
581 }
582
583 #[must_use]
589 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
590 self.meta = meta.into_option();
591 self
592 }
593}
594
595#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq)]
600#[schemars(inline)]
601#[schemars(transform = other_state_update_schema)]
602#[serde(rename_all = "camelCase")]
603#[non_exhaustive]
604pub struct OtherStateUpdate {
605 #[serde(rename = "state")]
611 pub state: String,
612 #[serde(flatten)]
614 pub fields: BTreeMap<String, serde_json::Value>,
615}
616
617impl OtherStateUpdate {
618 #[must_use]
620 pub fn new(state: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
621 fields.remove("state");
622 Self {
623 state: state.into(),
624 fields,
625 }
626 }
627}
628
629impl<'de> Deserialize<'de> for OtherStateUpdate {
630 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
631 where
632 D: serde::Deserializer<'de>,
633 {
634 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
635 let state = fields
636 .remove("state")
637 .ok_or_else(|| serde::de::Error::missing_field("state"))?;
638 let serde_json::Value::String(state) = state else {
639 return Err(serde::de::Error::custom("`state` must be a string"));
640 };
641
642 if is_known_state_update(&state) {
643 return Err(serde::de::Error::custom(format!(
644 "known state update `{state}` did not match its schema"
645 )));
646 }
647
648 Ok(Self { state, fields })
649 }
650}
651
652fn is_known_state_update(state: &str) -> bool {
653 matches!(state, "running" | "idle" | "requires_action")
654}
655
656fn other_state_update_schema(schema: &mut Schema) {
657 super::schema_util::reject_known_string_discriminators(
658 schema,
659 "state",
660 &["running", "idle", "requires_action"],
661 );
662}
663
664#[skip_serializing_none]
666#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
667#[serde(rename_all = "camelCase")]
668#[non_exhaustive]
669pub struct Cost {
670 pub amount: f64,
672 pub currency: String,
674 #[serde(rename = "_meta")]
680 pub meta: Option<Meta>,
681}
682
683impl Cost {
684 #[must_use]
686 pub fn new(amount: f64, currency: impl Into<String>) -> Self {
687 Self {
688 amount,
689 currency: currency.into(),
690 meta: None,
691 }
692 }
693
694 #[must_use]
700 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
701 self.meta = meta.into_option();
702 self
703 }
704}
705
706#[skip_serializing_none]
708#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
709#[serde(rename_all = "camelCase")]
710#[non_exhaustive]
711pub struct ContentChunk {
712 pub content: ContentBlock,
714 pub message_id: MessageId,
719 #[serde(rename = "_meta")]
725 pub meta: Option<Meta>,
726}
727
728impl ContentChunk {
729 #[must_use]
731 pub fn new(content: ContentBlock, message_id: impl Into<MessageId>) -> Self {
732 Self {
733 content,
734 message_id: message_id.into(),
735 meta: None,
736 }
737 }
738
739 #[must_use]
745 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
746 self.meta = meta.into_option();
747 self
748 }
749}
750
751#[serde_as]
765#[skip_serializing_none]
766#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
767#[serde(rename_all = "camelCase")]
768#[non_exhaustive]
769pub struct UserMessage {
770 pub message_id: MessageId,
772 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<VecSkipError<_, SkipListener>>>")]
774 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
775 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
776 pub content: MaybeUndefined<Vec<ContentBlock>>,
777 #[serde(
783 rename = "_meta",
784 default,
785 skip_serializing_if = "MaybeUndefined::is_undefined"
786 )]
787 pub meta: MaybeUndefined<Meta>,
788}
789
790impl UserMessage {
791 #[must_use]
793 pub fn new(message_id: impl Into<MessageId>) -> Self {
794 Self {
795 message_id: message_id.into(),
796 content: MaybeUndefined::Undefined,
797 meta: MaybeUndefined::Undefined,
798 }
799 }
800
801 #[must_use]
803 pub fn content(mut self, content: impl IntoMaybeUndefined<Vec<ContentBlock>>) -> Self {
804 self.content = content.into_maybe_undefined();
805 self
806 }
807
808 #[must_use]
814 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
815 self.meta = meta.into_maybe_undefined();
816 self
817 }
818}
819
820#[serde_as]
834#[skip_serializing_none]
835#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
836#[serde(rename_all = "camelCase")]
837#[non_exhaustive]
838pub struct AgentMessage {
839 pub message_id: MessageId,
841 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<VecSkipError<_, SkipListener>>>")]
843 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
844 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
845 pub content: MaybeUndefined<Vec<ContentBlock>>,
846 #[serde(
852 rename = "_meta",
853 default,
854 skip_serializing_if = "MaybeUndefined::is_undefined"
855 )]
856 pub meta: MaybeUndefined<Meta>,
857}
858
859impl AgentMessage {
860 #[must_use]
862 pub fn new(message_id: impl Into<MessageId>) -> Self {
863 Self {
864 message_id: message_id.into(),
865 content: MaybeUndefined::Undefined,
866 meta: MaybeUndefined::Undefined,
867 }
868 }
869
870 #[must_use]
872 pub fn content(mut self, content: impl IntoMaybeUndefined<Vec<ContentBlock>>) -> Self {
873 self.content = content.into_maybe_undefined();
874 self
875 }
876
877 #[must_use]
883 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
884 self.meta = meta.into_maybe_undefined();
885 self
886 }
887}
888
889#[serde_as]
903#[skip_serializing_none]
904#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
905#[serde(rename_all = "camelCase")]
906#[non_exhaustive]
907pub struct AgentThought {
908 pub message_id: MessageId,
910 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<VecSkipError<_, SkipListener>>>")]
912 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
913 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
914 pub content: MaybeUndefined<Vec<ContentBlock>>,
915 #[serde(
921 rename = "_meta",
922 default,
923 skip_serializing_if = "MaybeUndefined::is_undefined"
924 )]
925 pub meta: MaybeUndefined<Meta>,
926}
927
928impl AgentThought {
929 #[must_use]
931 pub fn new(message_id: impl Into<MessageId>) -> Self {
932 Self {
933 message_id: message_id.into(),
934 content: MaybeUndefined::Undefined,
935 meta: MaybeUndefined::Undefined,
936 }
937 }
938
939 #[must_use]
941 pub fn content(mut self, content: impl IntoMaybeUndefined<Vec<ContentBlock>>) -> Self {
942 self.content = content.into_maybe_undefined();
943 self
944 }
945
946 #[must_use]
952 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
953 self.meta = meta.into_maybe_undefined();
954 self
955 }
956}
957
958#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash, Display, From)]
960#[serde(transparent)]
961#[from(Arc<str>, String, &'static str)]
962#[non_exhaustive]
963pub struct MessageId(pub Arc<str>);
964
965impl MessageId {
966 #[must_use]
968 pub fn new(id: impl Into<Arc<str>>) -> Self {
969 Self(id.into())
970 }
971}
972
973#[serde_as]
975#[skip_serializing_none]
976#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
977#[serde(rename_all = "camelCase")]
978#[non_exhaustive]
979pub struct AvailableCommandsUpdate {
980 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, SkipListener>>")]
982 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
983 pub available_commands: Vec<AvailableCommand>,
984 #[serde(rename = "_meta")]
990 pub meta: Option<Meta>,
991}
992
993impl AvailableCommandsUpdate {
994 #[must_use]
996 pub fn new(available_commands: Vec<AvailableCommand>) -> Self {
997 Self {
998 available_commands,
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]
1017#[skip_serializing_none]
1018#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1019#[serde(rename_all = "camelCase")]
1020#[non_exhaustive]
1021pub struct AvailableCommand {
1022 pub name: String,
1024 pub description: String,
1026 #[serde_as(deserialize_as = "DefaultOnError")]
1028 #[schemars(extend("x-deserialize-default-on-error" = true))]
1029 #[serde(default)]
1030 pub input: Option<AvailableCommandInput>,
1031 #[serde(rename = "_meta")]
1037 pub meta: Option<Meta>,
1038}
1039
1040impl AvailableCommand {
1041 #[must_use]
1043 pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
1044 Self {
1045 name: name.into(),
1046 description: description.into(),
1047 input: None,
1048 meta: None,
1049 }
1050 }
1051
1052 #[must_use]
1054 pub fn input(mut self, input: impl IntoOption<AvailableCommandInput>) -> Self {
1055 self.input = input.into_option();
1056 self
1057 }
1058
1059 #[must_use]
1065 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1066 self.meta = meta.into_option();
1067 self
1068 }
1069}
1070
1071#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1073#[serde(untagged, rename_all = "camelCase")]
1074#[non_exhaustive]
1075pub enum AvailableCommandInput {
1076 Unstructured(UnstructuredCommandInput),
1078 Other(OtherAvailableCommandInput),
1089}
1090
1091#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq, Eq)]
1093#[schemars(inline)]
1094#[serde(rename_all = "camelCase")]
1095#[non_exhaustive]
1096pub struct OtherAvailableCommandInput {
1097 #[serde(rename = "type")]
1103 pub type_: String,
1104 #[serde(flatten)]
1106 pub fields: BTreeMap<String, serde_json::Value>,
1107}
1108
1109impl OtherAvailableCommandInput {
1110 #[must_use]
1112 pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
1113 fields.remove("type");
1114 Self {
1115 type_: type_.into(),
1116 fields,
1117 }
1118 }
1119}
1120
1121impl<'de> Deserialize<'de> for OtherAvailableCommandInput {
1122 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1123 where
1124 D: serde::Deserializer<'de>,
1125 {
1126 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1127 let type_ = fields
1128 .remove("type")
1129 .ok_or_else(|| serde::de::Error::missing_field("type"))?;
1130 let serde_json::Value::String(type_) = type_ else {
1131 return Err(serde::de::Error::custom("`type` must be a string"));
1132 };
1133
1134 Ok(Self { type_, fields })
1135 }
1136}
1137
1138#[skip_serializing_none]
1140#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq, Eq)]
1141#[schemars(transform = unstructured_command_input_schema)]
1142#[serde(rename_all = "camelCase")]
1143#[non_exhaustive]
1144pub struct UnstructuredCommandInput {
1145 pub hint: String,
1147 #[serde(rename = "_meta")]
1153 pub meta: Option<Meta>,
1154}
1155
1156impl UnstructuredCommandInput {
1157 #[must_use]
1159 pub fn new(hint: impl Into<String>) -> Self {
1160 Self {
1161 hint: hint.into(),
1162 meta: None,
1163 }
1164 }
1165
1166 #[must_use]
1172 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1173 self.meta = meta.into_option();
1174 self
1175 }
1176}
1177
1178impl<'de> Deserialize<'de> for UnstructuredCommandInput {
1179 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1180 where
1181 D: serde::Deserializer<'de>,
1182 {
1183 #[derive(Deserialize)]
1184 #[serde(rename_all = "camelCase")]
1185 struct RawUnstructuredCommandInput {
1186 hint: String,
1187 #[serde(rename = "_meta")]
1188 meta: Option<Meta>,
1189 #[serde(flatten)]
1190 fields: BTreeMap<String, serde_json::Value>,
1191 }
1192
1193 let raw = RawUnstructuredCommandInput::deserialize(deserializer)?;
1194 if raw.fields.contains_key("type") {
1195 return Err(serde::de::Error::custom(
1196 "unstructured command input cannot include a `type` field",
1197 ));
1198 }
1199
1200 Ok(Self {
1201 hint: raw.hint,
1202 meta: raw.meta,
1203 })
1204 }
1205}
1206
1207fn unstructured_command_input_schema(schema: &mut Schema) {
1208 super::schema_util::reject_property(schema, "type");
1209}
1210
1211#[skip_serializing_none]
1219#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1220#[schemars(extend("x-side" = "client", "x-method" = SESSION_REQUEST_PERMISSION_METHOD_NAME))]
1221#[serde(rename_all = "camelCase")]
1222#[non_exhaustive]
1223pub struct RequestPermissionRequest {
1224 pub session_id: SessionId,
1226 pub tool_call: ToolCallUpdate,
1228 pub options: Vec<PermissionOption>,
1230 #[serde(rename = "_meta")]
1236 pub meta: Option<Meta>,
1237}
1238
1239impl RequestPermissionRequest {
1240 #[must_use]
1242 pub fn new(
1243 session_id: impl Into<SessionId>,
1244 tool_call: ToolCallUpdate,
1245 options: Vec<PermissionOption>,
1246 ) -> Self {
1247 Self {
1248 session_id: session_id.into(),
1249 tool_call,
1250 options,
1251 meta: None,
1252 }
1253 }
1254
1255 #[must_use]
1261 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1262 self.meta = meta.into_option();
1263 self
1264 }
1265}
1266
1267#[skip_serializing_none]
1269#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1270#[serde(rename_all = "camelCase")]
1271#[non_exhaustive]
1272pub struct PermissionOption {
1273 pub option_id: PermissionOptionId,
1275 pub name: String,
1277 pub kind: PermissionOptionKind,
1279 #[serde(rename = "_meta")]
1285 pub meta: Option<Meta>,
1286}
1287
1288impl PermissionOption {
1289 #[must_use]
1291 pub fn new(
1292 option_id: impl Into<PermissionOptionId>,
1293 name: impl Into<String>,
1294 kind: PermissionOptionKind,
1295 ) -> Self {
1296 Self {
1297 option_id: option_id.into(),
1298 name: name.into(),
1299 kind,
1300 meta: None,
1301 }
1302 }
1303
1304 #[must_use]
1310 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1311 self.meta = meta.into_option();
1312 self
1313 }
1314}
1315
1316#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash, Display, From)]
1318#[serde(transparent)]
1319#[from(Arc<str>, String, &'static str)]
1320#[non_exhaustive]
1321pub struct PermissionOptionId(pub Arc<str>);
1322
1323impl PermissionOptionId {
1324 #[must_use]
1326 pub fn new(id: impl Into<Arc<str>>) -> Self {
1327 Self(id.into())
1328 }
1329}
1330
1331#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1335#[serde(rename_all = "snake_case")]
1336#[non_exhaustive]
1337pub enum PermissionOptionKind {
1338 AllowOnce,
1340 AllowAlways,
1342 RejectOnce,
1344 RejectAlways,
1346 #[serde(untagged)]
1352 Other(String),
1353}
1354
1355#[skip_serializing_none]
1357#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1358#[schemars(extend("x-side" = "client", "x-method" = SESSION_REQUEST_PERMISSION_METHOD_NAME))]
1359#[serde(rename_all = "camelCase")]
1360#[non_exhaustive]
1361pub struct RequestPermissionResponse {
1362 pub outcome: RequestPermissionOutcome,
1365 #[serde(rename = "_meta")]
1371 pub meta: Option<Meta>,
1372}
1373
1374impl RequestPermissionResponse {
1375 #[must_use]
1377 pub fn new(outcome: RequestPermissionOutcome) -> Self {
1378 Self {
1379 outcome,
1380 meta: None,
1381 }
1382 }
1383
1384 #[must_use]
1390 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1391 self.meta = meta.into_option();
1392 self
1393 }
1394}
1395
1396#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1398#[serde(tag = "outcome", rename_all = "snake_case")]
1399#[schemars(extend("discriminator" = {"propertyName": "outcome"}))]
1400#[non_exhaustive]
1401pub enum RequestPermissionOutcome {
1402 Cancelled,
1410 #[serde(rename_all = "camelCase")]
1412 Selected(SelectedPermissionOutcome),
1413}
1414
1415#[skip_serializing_none]
1417#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1418#[serde(rename_all = "camelCase")]
1419#[non_exhaustive]
1420pub struct SelectedPermissionOutcome {
1421 pub option_id: PermissionOptionId,
1423 #[serde(rename = "_meta")]
1429 pub meta: Option<Meta>,
1430}
1431
1432impl SelectedPermissionOutcome {
1433 #[must_use]
1435 pub fn new(option_id: impl Into<PermissionOptionId>) -> Self {
1436 Self {
1437 option_id: option_id.into(),
1438 meta: None,
1439 }
1440 }
1441
1442 #[must_use]
1448 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1449 self.meta = meta.into_option();
1450 self
1451 }
1452}
1453
1454#[serde_as]
1463#[skip_serializing_none]
1464#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1465#[serde(rename_all = "camelCase")]
1466#[non_exhaustive]
1467pub struct ClientCapabilities {
1468 #[cfg(feature = "unstable_auth_methods")]
1476 #[serde(default)]
1477 pub auth: AuthCapabilities,
1478 #[cfg(feature = "unstable_elicitation")]
1485 #[serde_as(deserialize_as = "DefaultOnError")]
1486 #[schemars(extend("x-deserialize-default-on-error" = true))]
1487 #[serde(default)]
1488 pub elicitation: Option<ElicitationCapabilities>,
1489 #[cfg(feature = "unstable_nes")]
1495 #[serde_as(deserialize_as = "DefaultOnError")]
1496 #[schemars(extend("x-deserialize-default-on-error" = true))]
1497 #[serde(default)]
1498 pub nes: Option<ClientNesCapabilities>,
1499 #[cfg(feature = "unstable_nes")]
1505 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, SkipListener>>")]
1506 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
1507 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1508 pub position_encodings: Vec<PositionEncodingKind>,
1509
1510 #[serde(rename = "_meta")]
1516 pub meta: Option<Meta>,
1517}
1518
1519impl ClientCapabilities {
1520 #[must_use]
1522 pub fn new() -> Self {
1523 Self::default()
1524 }
1525
1526 #[cfg(feature = "unstable_auth_methods")]
1534 #[must_use]
1535 pub fn auth(mut self, auth: AuthCapabilities) -> Self {
1536 self.auth = auth;
1537 self
1538 }
1539
1540 #[cfg(feature = "unstable_elicitation")]
1547 #[must_use]
1548 pub fn elicitation(mut self, elicitation: impl IntoOption<ElicitationCapabilities>) -> Self {
1549 self.elicitation = elicitation.into_option();
1550 self
1551 }
1552
1553 #[cfg(feature = "unstable_nes")]
1557 #[must_use]
1558 pub fn nes(mut self, nes: impl IntoOption<ClientNesCapabilities>) -> Self {
1559 self.nes = nes.into_option();
1560 self
1561 }
1562
1563 #[cfg(feature = "unstable_nes")]
1567 #[must_use]
1568 pub fn position_encodings(mut self, position_encodings: Vec<PositionEncodingKind>) -> Self {
1569 self.position_encodings = position_encodings;
1570 self
1571 }
1572
1573 #[must_use]
1579 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1580 self.meta = meta.into_option();
1581 self
1582 }
1583}
1584
1585#[cfg(feature = "unstable_auth_methods")]
1595#[serde_as]
1596#[skip_serializing_none]
1597#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1598#[serde(rename_all = "camelCase")]
1599#[non_exhaustive]
1600pub struct AuthCapabilities {
1601 #[serde_as(deserialize_as = "DefaultOnError")]
1606 #[schemars(extend("x-deserialize-default-on-error" = true))]
1607 #[serde(default)]
1608 pub terminal: Option<TerminalAuthCapabilities>,
1609 #[serde(rename = "_meta")]
1615 pub meta: Option<Meta>,
1616}
1617
1618#[cfg(feature = "unstable_auth_methods")]
1619impl AuthCapabilities {
1620 #[must_use]
1622 pub fn new() -> Self {
1623 Self::default()
1624 }
1625
1626 #[must_use]
1631 pub fn terminal(mut self, terminal: impl IntoOption<TerminalAuthCapabilities>) -> Self {
1632 self.terminal = terminal.into_option();
1633 self
1634 }
1635
1636 #[must_use]
1642 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1643 self.meta = meta.into_option();
1644 self
1645 }
1646}
1647
1648#[cfg(feature = "unstable_auth_methods")]
1656#[skip_serializing_none]
1657#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1658#[non_exhaustive]
1659pub struct TerminalAuthCapabilities {
1660 #[serde(rename = "_meta")]
1666 pub meta: Option<Meta>,
1667}
1668
1669#[cfg(feature = "unstable_auth_methods")]
1670impl TerminalAuthCapabilities {
1671 #[must_use]
1673 pub fn new() -> Self {
1674 Self::default()
1675 }
1676
1677 #[must_use]
1683 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1684 self.meta = meta.into_option();
1685 self
1686 }
1687}
1688
1689#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1695#[non_exhaustive]
1696pub struct ClientMethodNames {
1697 pub session_request_permission: &'static str,
1699 pub session_update: &'static str,
1701 #[cfg(feature = "unstable_mcp_over_acp")]
1703 pub mcp_connect: &'static str,
1704 #[cfg(feature = "unstable_mcp_over_acp")]
1706 pub mcp_message: &'static str,
1707 #[cfg(feature = "unstable_mcp_over_acp")]
1709 pub mcp_disconnect: &'static str,
1710 #[cfg(feature = "unstable_elicitation")]
1712 pub elicitation_create: &'static str,
1713 #[cfg(feature = "unstable_elicitation")]
1715 pub elicitation_complete: &'static str,
1716}
1717
1718pub const CLIENT_METHOD_NAMES: ClientMethodNames = ClientMethodNames {
1720 session_update: SESSION_UPDATE_NOTIFICATION,
1721 session_request_permission: SESSION_REQUEST_PERMISSION_METHOD_NAME,
1722 #[cfg(feature = "unstable_mcp_over_acp")]
1723 mcp_connect: MCP_CONNECT_METHOD_NAME,
1724 #[cfg(feature = "unstable_mcp_over_acp")]
1725 mcp_message: MCP_MESSAGE_METHOD_NAME,
1726 #[cfg(feature = "unstable_mcp_over_acp")]
1727 mcp_disconnect: MCP_DISCONNECT_METHOD_NAME,
1728 #[cfg(feature = "unstable_elicitation")]
1729 elicitation_create: ELICITATION_CREATE_METHOD_NAME,
1730 #[cfg(feature = "unstable_elicitation")]
1731 elicitation_complete: ELICITATION_COMPLETE_NOTIFICATION,
1732};
1733
1734pub(crate) const SESSION_UPDATE_NOTIFICATION: &str = "session/update";
1736pub(crate) const SESSION_REQUEST_PERMISSION_METHOD_NAME: &str = "session/request_permission";
1738#[cfg(feature = "unstable_elicitation")]
1740pub(crate) const ELICITATION_CREATE_METHOD_NAME: &str = "elicitation/create";
1741#[cfg(feature = "unstable_elicitation")]
1743pub(crate) const ELICITATION_COMPLETE_NOTIFICATION: &str = "elicitation/complete";
1744
1745#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
1752#[serde(untagged)]
1753#[schemars(inline)]
1754#[non_exhaustive]
1755pub enum AgentRequest {
1756 RequestPermissionRequest(Box<RequestPermissionRequest>),
1767 #[cfg(feature = "unstable_elicitation")]
1773 CreateElicitationRequest(CreateElicitationRequest),
1774 #[cfg(feature = "unstable_mcp_over_acp")]
1780 ConnectMcpRequest(ConnectMcpRequest),
1781 #[cfg(feature = "unstable_mcp_over_acp")]
1787 MessageMcpRequest(MessageMcpRequest),
1788 #[cfg(feature = "unstable_mcp_over_acp")]
1794 DisconnectMcpRequest(DisconnectMcpRequest),
1795 ExtMethodRequest(ExtRequest),
1803}
1804
1805impl AgentRequest {
1806 #[must_use]
1808 pub fn method(&self) -> &str {
1809 match self {
1810 Self::RequestPermissionRequest(_) => CLIENT_METHOD_NAMES.session_request_permission,
1811 #[cfg(feature = "unstable_elicitation")]
1812 Self::CreateElicitationRequest(_) => CLIENT_METHOD_NAMES.elicitation_create,
1813 #[cfg(feature = "unstable_mcp_over_acp")]
1814 Self::ConnectMcpRequest(_) => CLIENT_METHOD_NAMES.mcp_connect,
1815 #[cfg(feature = "unstable_mcp_over_acp")]
1816 Self::MessageMcpRequest(_) => CLIENT_METHOD_NAMES.mcp_message,
1817 #[cfg(feature = "unstable_mcp_over_acp")]
1818 Self::DisconnectMcpRequest(_) => CLIENT_METHOD_NAMES.mcp_disconnect,
1819 Self::ExtMethodRequest(ext_request) => &ext_request.method,
1820 }
1821 }
1822}
1823
1824#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
1831#[serde(untagged)]
1832#[schemars(inline)]
1833#[non_exhaustive]
1834pub enum ClientResponse {
1835 RequestPermissionResponse(RequestPermissionResponse),
1837 #[cfg(feature = "unstable_elicitation")]
1839 CreateElicitationResponse(CreateElicitationResponse),
1840 #[cfg(feature = "unstable_mcp_over_acp")]
1842 ConnectMcpResponse(ConnectMcpResponse),
1843 #[cfg(feature = "unstable_mcp_over_acp")]
1845 DisconnectMcpResponse(#[serde(default)] DisconnectMcpResponse),
1846 ExtMethodResponse(ExtResponse),
1848 #[cfg(feature = "unstable_mcp_over_acp")]
1850 MessageMcpResponse(MessageMcpResponse),
1851}
1852
1853#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
1860#[serde(untagged)]
1861#[schemars(inline)]
1862#[non_exhaustive]
1863pub enum AgentNotification {
1864 SessionNotification(Box<SessionNotification>),
1877 #[cfg(feature = "unstable_elicitation")]
1883 CompleteElicitationNotification(CompleteElicitationNotification),
1884 #[cfg(feature = "unstable_mcp_over_acp")]
1890 MessageMcpNotification(MessageMcpNotification),
1891 ExtNotification(ExtNotification),
1899}
1900
1901impl AgentNotification {
1902 #[must_use]
1904 pub fn method(&self) -> &str {
1905 match self {
1906 Self::SessionNotification(_) => CLIENT_METHOD_NAMES.session_update,
1907 #[cfg(feature = "unstable_elicitation")]
1908 Self::CompleteElicitationNotification(_) => CLIENT_METHOD_NAMES.elicitation_complete,
1909 #[cfg(feature = "unstable_mcp_over_acp")]
1910 Self::MessageMcpNotification(_) => CLIENT_METHOD_NAMES.mcp_message,
1911 Self::ExtNotification(ext_notification) => &ext_notification.method,
1912 }
1913 }
1914}
1915
1916#[cfg(test)]
1917mod tests {
1918 use super::*;
1919
1920 #[test]
1921 fn test_serialization_behavior() {
1922 use serde_json::json;
1923
1924 assert_eq!(
1925 serde_json::from_value::<SessionInfoUpdate>(json!({})).unwrap(),
1926 SessionInfoUpdate {
1927 title: MaybeUndefined::Undefined,
1928 updated_at: MaybeUndefined::Undefined,
1929 meta: None
1930 }
1931 );
1932 assert_eq!(
1933 serde_json::from_value::<SessionInfoUpdate>(json!({"title": null, "updatedAt": null}))
1934 .unwrap(),
1935 SessionInfoUpdate {
1936 title: MaybeUndefined::Null,
1937 updated_at: MaybeUndefined::Null,
1938 meta: None
1939 }
1940 );
1941 assert_eq!(
1942 serde_json::from_value::<SessionInfoUpdate>(
1943 json!({"title": "title", "updatedAt": "timestamp"})
1944 )
1945 .unwrap(),
1946 SessionInfoUpdate {
1947 title: MaybeUndefined::Value("title".to_string()),
1948 updated_at: MaybeUndefined::Value("timestamp".to_string()),
1949 meta: None
1950 }
1951 );
1952
1953 assert_eq!(
1954 serde_json::to_value(SessionInfoUpdate::new()).unwrap(),
1955 json!({})
1956 );
1957 assert_eq!(
1958 serde_json::to_value(SessionInfoUpdate::new().title("title")).unwrap(),
1959 json!({"title": "title"})
1960 );
1961 assert_eq!(
1962 serde_json::to_value(SessionInfoUpdate::new().title(None)).unwrap(),
1963 json!({"title": null})
1964 );
1965 assert_eq!(
1966 serde_json::to_value(
1967 SessionInfoUpdate::new()
1968 .title("title")
1969 .title(MaybeUndefined::Undefined)
1970 )
1971 .unwrap(),
1972 json!({})
1973 );
1974 }
1975
1976 #[test]
1977 fn test_content_chunk_message_id_serialization() {
1978 use serde_json::json;
1979
1980 assert_eq!(
1981 serde_json::to_value(SessionUpdate::AgentMessageChunk(ContentChunk::new(
1982 ContentBlock::Text(crate::v2::TextContent::new("Hello")),
1983 "msg_agent_c42b9",
1984 )))
1985 .unwrap(),
1986 json!({
1987 "sessionUpdate": "agent_message_chunk",
1988 "messageId": "msg_agent_c42b9",
1989 "content": {
1990 "type": "text",
1991 "text": "Hello"
1992 }
1993 })
1994 );
1995
1996 let err = serde_json::from_value::<ContentChunk>(json!({
1997 "content": {
1998 "type": "text",
1999 "text": "Hello"
2000 }
2001 }))
2002 .unwrap_err();
2003
2004 assert!(err.to_string().contains("messageId"), "{err}");
2005 }
2006
2007 #[test]
2008 fn test_tool_call_content_chunk_serialization() {
2009 use serde_json::json;
2010
2011 assert_eq!(
2012 serde_json::to_value(SessionUpdate::ToolCallContentChunk(
2013 ToolCallContentChunk::new(
2014 "call_001",
2015 crate::v2::ContentBlock::Text(crate::v2::TextContent::new("partial output")),
2016 )
2017 ))
2018 .unwrap(),
2019 json!({
2020 "sessionUpdate": "tool_call_content_chunk",
2021 "toolCallId": "call_001",
2022 "content": {
2023 "type": "content",
2024 "content": {
2025 "type": "text",
2026 "text": "partial output"
2027 }
2028 }
2029 })
2030 );
2031
2032 let err = serde_json::from_value::<ToolCallContentChunk>(json!({
2033 "content": {
2034 "type": "content",
2035 "content": {
2036 "type": "text",
2037 "text": "partial output"
2038 }
2039 }
2040 }))
2041 .unwrap_err();
2042
2043 assert!(err.to_string().contains("toolCallId"), "{err}");
2044 }
2045
2046 #[test]
2047 fn test_full_message_serialization() {
2048 use serde_json::json;
2049
2050 assert_eq!(
2051 serde_json::to_value(SessionUpdate::UserMessage(
2052 UserMessage::new("msg_user_8f7a1").content(vec![ContentBlock::Text(
2053 crate::v2::TextContent::new("Hello")
2054 )])
2055 ))
2056 .unwrap(),
2057 json!({
2058 "sessionUpdate": "user_message",
2059 "messageId": "msg_user_8f7a1",
2060 "content": [
2061 {
2062 "type": "text",
2063 "text": "Hello"
2064 }
2065 ]
2066 })
2067 );
2068
2069 assert_eq!(
2070 serde_json::to_value(SessionUpdate::AgentMessage(
2071 AgentMessage::new("msg_agent_c42b9").content(vec![ContentBlock::Text(
2072 crate::v2::TextContent::new("Hello")
2073 )])
2074 ))
2075 .unwrap(),
2076 json!({
2077 "sessionUpdate": "agent_message",
2078 "messageId": "msg_agent_c42b9",
2079 "content": [
2080 {
2081 "type": "text",
2082 "text": "Hello"
2083 }
2084 ]
2085 })
2086 );
2087
2088 assert_eq!(
2089 serde_json::to_value(SessionUpdate::AgentThought(
2090 AgentThought::new("msg_thought_a12").content(vec![ContentBlock::Text(
2091 crate::v2::TextContent::new("Need to inspect the call sites first.")
2092 )])
2093 ))
2094 .unwrap(),
2095 json!({
2096 "sessionUpdate": "agent_thought",
2097 "messageId": "msg_thought_a12",
2098 "content": [
2099 {
2100 "type": "text",
2101 "text": "Need to inspect the call sites first."
2102 }
2103 ]
2104 })
2105 );
2106 }
2107
2108 #[test]
2109 fn test_message_upsert_serialization() {
2110 use serde_json::json;
2111
2112 assert_eq!(
2113 serde_json::to_value(SessionUpdate::UserMessage(
2114 UserMessage::new("msg_empty").content(Vec::<ContentBlock>::new())
2115 ))
2116 .unwrap(),
2117 json!({
2118 "sessionUpdate": "user_message",
2119 "messageId": "msg_empty",
2120 "content": []
2121 })
2122 );
2123
2124 let empty = serde_json::from_value::<UserMessage>(json!({
2125 "messageId": "msg_empty",
2126 "content": []
2127 }))
2128 .unwrap();
2129 assert!(matches!(
2130 empty.content,
2131 MaybeUndefined::Value(ref content) if content.is_empty()
2132 ));
2133
2134 let patch = serde_json::from_value::<AgentMessage>(json!({
2135 "messageId": "msg_agent_c42b9"
2136 }))
2137 .unwrap();
2138 assert_eq!(patch.content, MaybeUndefined::Undefined);
2139 assert_eq!(patch.meta, MaybeUndefined::Undefined);
2140
2141 let patch = serde_json::from_value::<AgentThought>(json!({
2142 "messageId": "msg_thought_a12"
2143 }))
2144 .unwrap();
2145 assert_eq!(patch.content, MaybeUndefined::Undefined);
2146
2147 let clear = serde_json::from_value::<UserMessage>(json!({
2148 "messageId": "msg_user_8f7a1",
2149 "content": null
2150 }))
2151 .unwrap();
2152 assert_eq!(clear.content, MaybeUndefined::Null);
2153
2154 let mut meta = Meta::new();
2155 meta.insert("source".to_string(), json!("replay"));
2156
2157 assert_eq!(
2158 serde_json::to_value(SessionUpdate::UserMessage(
2159 UserMessage::new("msg_user_8f7a1").meta(meta)
2160 ))
2161 .unwrap(),
2162 json!({
2163 "sessionUpdate": "user_message",
2164 "messageId": "msg_user_8f7a1",
2165 "_meta": {
2166 "source": "replay"
2167 }
2168 })
2169 );
2170
2171 assert_eq!(
2172 serde_json::to_value(SessionUpdate::UserMessage(
2173 UserMessage::new("msg_user_8f7a1").meta(None::<Meta>)
2174 ))
2175 .unwrap(),
2176 json!({
2177 "sessionUpdate": "user_message",
2178 "messageId": "msg_user_8f7a1",
2179 "_meta": null
2180 })
2181 );
2182 }
2183
2184 #[test]
2185 fn test_usage_update_serialization() {
2186 use serde_json::json;
2187
2188 assert_eq!(
2189 serde_json::to_value(SessionUpdate::UsageUpdate(UsageUpdate::new(
2190 53_000, 200_000
2191 )))
2192 .unwrap(),
2193 json!({
2194 "sessionUpdate": "usage_update",
2195 "used": 53000,
2196 "size": 200_000
2197 })
2198 );
2199
2200 assert_eq!(
2201 serde_json::to_value(SessionUpdate::UsageUpdate(
2202 UsageUpdate::new(53_000, 200_000).cost(Cost::new(0.045, "USD"))
2203 ))
2204 .unwrap(),
2205 json!({
2206 "sessionUpdate": "usage_update",
2207 "used": 53000,
2208 "size": 200_000,
2209 "cost": {
2210 "amount": 0.045,
2211 "currency": "USD"
2212 }
2213 })
2214 );
2215
2216 let SessionUpdate::UsageUpdate(update) = serde_json::from_value(json!({
2217 "sessionUpdate": "usage_update",
2218 "used": 53000,
2219 "size": 200_000,
2220 "cost": null
2221 }))
2222 .unwrap() else {
2223 panic!("expected usage update");
2224 };
2225
2226 assert_eq!(update.cost, None);
2227 }
2228
2229 #[test]
2230 fn test_state_update_serialization() {
2231 use serde_json::json;
2232
2233 assert_eq!(
2234 serde_json::to_value(SessionUpdate::StateUpdate(StateUpdate::Running(
2235 RunningStateUpdate::new()
2236 )))
2237 .unwrap(),
2238 json!({
2239 "sessionUpdate": "state_update",
2240 "state": "running"
2241 })
2242 );
2243
2244 assert_eq!(
2245 serde_json::to_value(SessionUpdate::StateUpdate(StateUpdate::Idle(
2246 IdleStateUpdate::new().stop_reason(StopReason::EndTurn)
2247 )))
2248 .unwrap(),
2249 json!({
2250 "sessionUpdate": "state_update",
2251 "state": "idle",
2252 "stopReason": "end_turn"
2253 })
2254 );
2255
2256 let SessionUpdate::StateUpdate(update) = serde_json::from_value(json!({
2257 "sessionUpdate": "state_update",
2258 "state": "requires_action"
2259 }))
2260 .unwrap() else {
2261 panic!("expected state update");
2262 };
2263
2264 assert!(matches!(update, StateUpdate::RequiresAction(_)));
2265
2266 let SessionUpdate::StateUpdate(StateUpdate::Idle(update)) = serde_json::from_value(json!({
2267 "sessionUpdate": "state_update",
2268 "state": "idle",
2269 "stopReason": null
2270 }))
2271 .unwrap() else {
2272 panic!("expected idle state update");
2273 };
2274
2275 assert_eq!(update.stop_reason, None);
2276
2277 let SessionUpdate::StateUpdate(StateUpdate::Other(update)) =
2278 serde_json::from_value(json!({
2279 "sessionUpdate": "state_update",
2280 "state": "_paused",
2281 "label": "Paused"
2282 }))
2283 .unwrap()
2284 else {
2285 panic!("expected unknown state update");
2286 };
2287
2288 assert_eq!(update.state, "_paused");
2289 assert_eq!(update.fields["label"], json!("Paused"));
2290 }
2291
2292 #[test]
2293 fn session_update_preserves_unknown_variant() {
2294 use serde_json::json;
2295
2296 let update: SessionUpdate = serde_json::from_value(json!({
2297 "sessionUpdate": "_status_badge",
2298 "label": "Indexing",
2299 "progress": 0.5
2300 }))
2301 .unwrap();
2302
2303 let SessionUpdate::Other(unknown) = update else {
2304 panic!("expected unknown session update");
2305 };
2306
2307 assert_eq!(unknown.session_update, "_status_badge");
2308 assert_eq!(unknown.fields.get("label"), Some(&json!("Indexing")));
2309 assert_eq!(unknown.fields.get("progress"), Some(&json!(0.5)));
2310
2311 assert_eq!(
2312 serde_json::to_value(SessionUpdate::Other(unknown)).unwrap(),
2313 json!({
2314 "sessionUpdate": "_status_badge",
2315 "label": "Indexing",
2316 "progress": 0.5
2317 })
2318 );
2319 }
2320
2321 #[test]
2322 fn test_plan_update_serialization() {
2323 use serde_json::json;
2324
2325 let plan_update =
2326 SessionUpdate::PlanUpdate(PlanUpdate::new(crate::v2::PlanUpdateContent::items(
2327 "plan-1",
2328 vec![crate::v2::PlanEntry::new(
2329 "Step 1",
2330 crate::v2::PlanEntryPriority::High,
2331 crate::v2::PlanEntryStatus::Pending,
2332 )],
2333 )));
2334
2335 assert_eq!(
2336 serde_json::to_value(plan_update).unwrap(),
2337 json!({
2338 "sessionUpdate": "plan_update",
2339 "plan": {
2340 "type": "items",
2341 "id": "plan-1",
2342 "entries": [
2343 {
2344 "content": "Step 1",
2345 "priority": "high",
2346 "status": "pending"
2347 }
2348 ]
2349 }
2350 })
2351 );
2352 }
2353
2354 #[cfg(feature = "unstable_plan_operations")]
2355 #[test]
2356 fn test_plan_removed_serialization() {
2357 use serde_json::json;
2358
2359 assert_eq!(
2360 serde_json::to_value(SessionUpdate::PlanRemoved(PlanRemoved::new("plan-1"))).unwrap(),
2361 json!({
2362 "sessionUpdate": "plan_removed",
2363 "id": "plan-1"
2364 })
2365 );
2366 }
2367
2368 #[test]
2369 fn available_command_input_preserves_unknown_typed_variant() {
2370 use serde_json::json;
2371
2372 let input: AvailableCommandInput = serde_json::from_value(json!({
2373 "type": "_choices",
2374 "hint": "Pick one",
2375 "options": ["fast", "careful"]
2376 }))
2377 .unwrap();
2378
2379 let AvailableCommandInput::Other(unknown) = input else {
2380 panic!("expected unknown command input");
2381 };
2382
2383 assert_eq!(unknown.type_, "_choices");
2384 assert_eq!(unknown.fields.get("hint"), Some(&json!("Pick one")));
2385 assert_eq!(
2386 unknown.fields.get("options"),
2387 Some(&json!(["fast", "careful"]))
2388 );
2389 assert_eq!(
2390 serde_json::to_value(AvailableCommandInput::Other(unknown)).unwrap(),
2391 json!({
2392 "type": "_choices",
2393 "hint": "Pick one",
2394 "options": ["fast", "careful"]
2395 })
2396 );
2397 }
2398
2399 #[test]
2400 fn available_command_input_unknown_does_not_hide_malformed_unstructured_variant() {
2401 use serde_json::json;
2402
2403 assert!(serde_json::from_value::<AvailableCommandInput>(json!({})).is_err());
2404 assert!(
2405 serde_json::from_value::<AvailableCommandInput>(json!({
2406 "type": 1,
2407 "hint": "Pick one"
2408 }))
2409 .is_err()
2410 );
2411 }
2412
2413 #[cfg(feature = "unstable_nes")]
2414 #[test]
2415 fn test_client_capabilities_position_encodings_serialization() {
2416 use serde_json::json;
2417
2418 let capabilities = ClientCapabilities::new().position_encodings(vec![
2419 PositionEncodingKind::Utf32,
2420 PositionEncodingKind::Utf16,
2421 ]);
2422 let json = serde_json::to_value(&capabilities).unwrap();
2423
2424 assert_eq!(json["positionEncodings"], json!(["utf-32", "utf-16"]));
2425 }
2426
2427 #[cfg(feature = "unstable_mcp_over_acp")]
2428 #[test]
2429 fn test_agent_mcp_request_method_names() {
2430 use serde_json::json;
2431
2432 let params: serde_json::Map<String, serde_json::Value> =
2433 [("cursor".to_string(), json!("abc"))].into_iter().collect();
2434
2435 assert_eq!(CLIENT_METHOD_NAMES.mcp_connect, "mcp/connect");
2436 assert_eq!(CLIENT_METHOD_NAMES.mcp_message, "mcp/message");
2437 assert_eq!(CLIENT_METHOD_NAMES.mcp_disconnect, "mcp/disconnect");
2438
2439 assert_eq!(
2440 AgentRequest::ConnectMcpRequest(ConnectMcpRequest::new("server-1")).method(),
2441 "mcp/connect"
2442 );
2443 assert_eq!(
2444 AgentRequest::MessageMcpRequest(MessageMcpRequest::new("conn-1", "tools/list"))
2445 .method(),
2446 "mcp/message"
2447 );
2448 assert_eq!(
2449 AgentRequest::DisconnectMcpRequest(DisconnectMcpRequest::new("conn-1")).method(),
2450 "mcp/disconnect"
2451 );
2452 assert_eq!(
2453 AgentNotification::MessageMcpNotification(MessageMcpNotification::new(
2454 "conn-1",
2455 "notifications/progress"
2456 ))
2457 .method(),
2458 "mcp/message"
2459 );
2460
2461 assert_eq!(
2462 serde_json::to_value(ConnectMcpRequest::new("server-1")).unwrap(),
2463 json!({ "acpId": "server-1" })
2464 );
2465 assert_eq!(
2466 serde_json::to_value(ConnectMcpResponse::new("conn-1")).unwrap(),
2467 json!({ "connectionId": "conn-1" })
2468 );
2469 assert_eq!(
2470 serde_json::to_value(MessageMcpRequest::new("conn-1", "tools/list").params(params))
2471 .unwrap(),
2472 json!({
2473 "connectionId": "conn-1",
2474 "method": "tools/list",
2475 "params": { "cursor": "abc" }
2476 })
2477 );
2478 assert_eq!(
2479 serde_json::to_value(DisconnectMcpRequest::new("conn-1")).unwrap(),
2480 json!({ "connectionId": "conn-1" })
2481 );
2482 assert_eq!(
2483 serde_json::to_value(MessageMcpNotification::new(
2484 "conn-1",
2485 "notifications/progress"
2486 ))
2487 .unwrap(),
2488 json!({
2489 "connectionId": "conn-1",
2490 "method": "notifications/progress"
2491 })
2492 );
2493
2494 let request_with_null_params: MessageMcpRequest = serde_json::from_value(json!({
2495 "connectionId": "conn-1",
2496 "method": "tools/list",
2497 "params": null
2498 }))
2499 .unwrap();
2500 assert_eq!(request_with_null_params.params, None);
2501 }
2502
2503 #[cfg(feature = "unstable_auth_methods")]
2504 #[test]
2505 fn test_auth_capabilities_serialize_terminal_support_as_object() {
2506 use serde_json::json;
2507
2508 let capabilities = AuthCapabilities::new().terminal(TerminalAuthCapabilities::new());
2509
2510 assert_eq!(
2511 serde_json::to_value(&capabilities).unwrap(),
2512 json!({
2513 "terminal": {}
2514 })
2515 );
2516
2517 let deserialized: AuthCapabilities = serde_json::from_value(json!({
2518 "terminal": false
2519 }))
2520 .unwrap();
2521 assert!(deserialized.terminal.is_none());
2522 }
2523}