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_elicitation")]
16use super::{
17 CompleteElicitationNotification, CreateElicitationRequest, CreateElicitationResponse,
18 ElicitationCapabilities,
19};
20use super::{
21 ContentBlock, ExtNotification, ExtRequest, ExtResponse, Meta, PlanUpdate, SessionConfigOption,
22 SessionId, ToolCallContentChunk, ToolCallUpdate,
23};
24use crate::{IntoMaybeUndefined, IntoOption, MaybeUndefined, SkipListener};
25
26#[cfg(feature = "unstable_mcp_over_acp")]
27use super::mcp::{
28 ConnectMcpRequest, ConnectMcpResponse, DisconnectMcpRequest, DisconnectMcpResponse,
29 MCP_CONNECT_METHOD_NAME, MCP_DISCONNECT_METHOD_NAME, MCP_MESSAGE_METHOD_NAME,
30 MessageMcpNotification, MessageMcpRequest, MessageMcpResponse,
31};
32
33#[cfg(feature = "unstable_nes")]
34use super::{ClientNesCapabilities, PositionEncodingKind};
35
36#[skip_serializing_none]
44#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
45#[schemars(extend("x-side" = "client", "x-method" = SESSION_UPDATE_NOTIFICATION))]
46#[serde(rename_all = "camelCase")]
47#[non_exhaustive]
48pub struct SessionNotification {
49 pub session_id: SessionId,
51 pub update: SessionUpdate,
53 #[serde(rename = "_meta")]
59 pub meta: Option<Meta>,
60}
61
62impl SessionNotification {
63 #[must_use]
64 pub fn new(session_id: impl Into<SessionId>, update: SessionUpdate) -> Self {
65 Self {
66 session_id: session_id.into(),
67 update,
68 meta: None,
69 }
70 }
71
72 #[must_use]
78 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
79 self.meta = meta.into_option();
80 self
81 }
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
90#[serde(tag = "sessionUpdate", rename_all = "snake_case")]
91#[schemars(extend("discriminator" = {"propertyName": "sessionUpdate"}))]
92#[non_exhaustive]
93pub enum SessionUpdate {
94 UserMessageChunk(ContentChunk),
96 UserMessage(UserMessage),
102 AgentMessageChunk(ContentChunk),
104 AgentMessage(AgentMessage),
110 AgentThoughtChunk(ContentChunk),
112 AgentThought(AgentThought),
118 ToolCallContentChunk(ToolCallContentChunk),
120 ToolCallUpdate(ToolCallUpdate),
122 PlanUpdate(PlanUpdate),
125 #[cfg(feature = "unstable_plan_operations")]
131 PlanRemoved(PlanRemoved),
132 AvailableCommandsUpdate(AvailableCommandsUpdate),
134 ConfigOptionUpdate(ConfigOptionUpdate),
136 SessionInfoUpdate(SessionInfoUpdate),
138 UsageUpdate(UsageUpdate),
140 #[serde(untagged)]
150 Other(OtherSessionUpdate),
151}
152
153#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq)]
159#[schemars(inline)]
160#[schemars(transform = other_session_update_schema)]
161#[serde(rename_all = "camelCase")]
162#[non_exhaustive]
163pub struct OtherSessionUpdate {
164 #[serde(rename = "sessionUpdate")]
170 pub session_update: String,
171 #[serde(flatten)]
173 pub fields: BTreeMap<String, serde_json::Value>,
174}
175
176impl OtherSessionUpdate {
177 #[must_use]
178 pub fn new(
179 session_update: impl Into<String>,
180 mut fields: BTreeMap<String, serde_json::Value>,
181 ) -> Self {
182 fields.remove("sessionUpdate");
183 Self {
184 session_update: session_update.into(),
185 fields,
186 }
187 }
188}
189
190impl<'de> Deserialize<'de> for OtherSessionUpdate {
191 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
192 where
193 D: serde::Deserializer<'de>,
194 {
195 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
196 let session_update = fields
197 .remove("sessionUpdate")
198 .ok_or_else(|| serde::de::Error::missing_field("sessionUpdate"))?;
199 let serde_json::Value::String(session_update) = session_update else {
200 return Err(serde::de::Error::custom("`sessionUpdate` must be a string"));
201 };
202
203 if is_known_session_update(&session_update) {
204 return Err(serde::de::Error::custom(format!(
205 "known session update `{session_update}` did not match its schema"
206 )));
207 }
208
209 Ok(Self {
210 session_update,
211 fields,
212 })
213 }
214}
215
216fn is_known_session_update(session_update: &str) -> bool {
217 matches!(
218 session_update,
219 "user_message_chunk"
220 | "user_message"
221 | "agent_message_chunk"
222 | "agent_message"
223 | "agent_thought_chunk"
224 | "agent_thought"
225 | "tool_call_content_chunk"
226 | "tool_call_update"
227 | "plan_update"
228 | "available_commands_update"
229 | "config_option_update"
230 | "session_info_update"
231 | "usage_update"
232 )
233}
234
235fn other_session_update_schema(schema: &mut Schema) {
236 super::schema_util::reject_known_string_discriminators(
237 schema,
238 "sessionUpdate",
239 &[
240 "user_message_chunk",
241 "user_message",
242 "agent_message_chunk",
243 "agent_message",
244 "agent_thought_chunk",
245 "agent_thought",
246 "tool_call_content_chunk",
247 "tool_call_update",
248 "plan_update",
249 "available_commands_update",
250 "config_option_update",
251 "session_info_update",
252 #[cfg(feature = "unstable_plan_operations")]
253 "plan_removed",
254 "usage_update",
255 ],
256 );
257}
258
259#[serde_as]
261#[skip_serializing_none]
262#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
263#[serde(rename_all = "camelCase")]
264#[non_exhaustive]
265pub struct ConfigOptionUpdate {
266 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, SkipListener>>")]
268 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
269 pub config_options: Vec<SessionConfigOption>,
270 #[serde(rename = "_meta")]
276 pub meta: Option<Meta>,
277}
278
279impl ConfigOptionUpdate {
280 #[must_use]
281 pub fn new(config_options: Vec<SessionConfigOption>) -> Self {
282 Self {
283 config_options,
284 meta: None,
285 }
286 }
287
288 #[must_use]
294 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
295 self.meta = meta.into_option();
296 self
297 }
298}
299
300#[skip_serializing_none]
305#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
306#[serde(rename_all = "camelCase")]
307#[non_exhaustive]
308pub struct SessionInfoUpdate {
309 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
311 pub title: MaybeUndefined<String>,
312 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
314 pub updated_at: MaybeUndefined<String>,
315 #[serde(rename = "_meta")]
321 pub meta: Option<Meta>,
322}
323
324impl SessionInfoUpdate {
325 #[must_use]
326 pub fn new() -> Self {
327 Self::default()
328 }
329
330 #[must_use]
332 pub fn title(mut self, title: impl IntoMaybeUndefined<String>) -> Self {
333 self.title = title.into_maybe_undefined();
334 self
335 }
336
337 #[must_use]
339 pub fn updated_at(mut self, updated_at: impl IntoMaybeUndefined<String>) -> Self {
340 self.updated_at = updated_at.into_maybe_undefined();
341 self
342 }
343
344 #[must_use]
350 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
351 self.meta = meta.into_option();
352 self
353 }
354}
355
356#[serde_as]
358#[skip_serializing_none]
359#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
360#[serde(rename_all = "camelCase")]
361#[non_exhaustive]
362pub struct UsageUpdate {
363 pub used: u64,
365 pub size: u64,
367 #[serde_as(deserialize_as = "DefaultOnError")]
369 #[schemars(extend("x-deserialize-default-on-error" = true))]
370 #[serde(default)]
371 pub cost: Option<Cost>,
372 #[serde(rename = "_meta")]
378 pub meta: Option<Meta>,
379}
380
381impl UsageUpdate {
382 #[must_use]
383 pub fn new(used: u64, size: u64) -> Self {
384 Self {
385 used,
386 size,
387 cost: None,
388 meta: None,
389 }
390 }
391
392 #[must_use]
394 pub fn cost(mut self, cost: impl IntoOption<Cost>) -> Self {
395 self.cost = cost.into_option();
396 self
397 }
398
399 #[must_use]
405 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
406 self.meta = meta.into_option();
407 self
408 }
409}
410
411#[skip_serializing_none]
413#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
414#[serde(rename_all = "camelCase")]
415#[non_exhaustive]
416pub struct Cost {
417 pub amount: f64,
419 pub currency: String,
421 #[serde(rename = "_meta")]
427 pub meta: Option<Meta>,
428}
429
430impl Cost {
431 #[must_use]
432 pub fn new(amount: f64, currency: impl Into<String>) -> Self {
433 Self {
434 amount,
435 currency: currency.into(),
436 meta: None,
437 }
438 }
439
440 #[must_use]
446 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
447 self.meta = meta.into_option();
448 self
449 }
450}
451
452#[skip_serializing_none]
454#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
455#[serde(rename_all = "camelCase")]
456#[non_exhaustive]
457pub struct ContentChunk {
458 pub content: ContentBlock,
460 pub message_id: MessageId,
465 #[serde(rename = "_meta")]
471 pub meta: Option<Meta>,
472}
473
474impl ContentChunk {
475 #[must_use]
476 pub fn new(content: ContentBlock, message_id: impl Into<MessageId>) -> Self {
477 Self {
478 content,
479 message_id: message_id.into(),
480 meta: None,
481 }
482 }
483
484 #[must_use]
490 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
491 self.meta = meta.into_option();
492 self
493 }
494}
495
496#[serde_as]
510#[skip_serializing_none]
511#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
512#[serde(rename_all = "camelCase")]
513#[non_exhaustive]
514pub struct UserMessage {
515 pub message_id: MessageId,
517 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<VecSkipError<_, SkipListener>>>")]
519 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
520 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
521 pub content: MaybeUndefined<Vec<ContentBlock>>,
522 #[serde(
528 rename = "_meta",
529 default,
530 skip_serializing_if = "MaybeUndefined::is_undefined"
531 )]
532 pub meta: MaybeUndefined<Meta>,
533}
534
535impl UserMessage {
536 #[must_use]
537 pub fn new(message_id: impl Into<MessageId>) -> Self {
538 Self {
539 message_id: message_id.into(),
540 content: MaybeUndefined::Undefined,
541 meta: MaybeUndefined::Undefined,
542 }
543 }
544
545 #[must_use]
547 pub fn content(mut self, content: impl IntoMaybeUndefined<Vec<ContentBlock>>) -> Self {
548 self.content = content.into_maybe_undefined();
549 self
550 }
551
552 #[must_use]
558 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
559 self.meta = meta.into_maybe_undefined();
560 self
561 }
562}
563
564#[serde_as]
578#[skip_serializing_none]
579#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
580#[serde(rename_all = "camelCase")]
581#[non_exhaustive]
582pub struct AgentMessage {
583 pub message_id: MessageId,
585 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<VecSkipError<_, SkipListener>>>")]
587 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
588 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
589 pub content: MaybeUndefined<Vec<ContentBlock>>,
590 #[serde(
596 rename = "_meta",
597 default,
598 skip_serializing_if = "MaybeUndefined::is_undefined"
599 )]
600 pub meta: MaybeUndefined<Meta>,
601}
602
603impl AgentMessage {
604 #[must_use]
605 pub fn new(message_id: impl Into<MessageId>) -> Self {
606 Self {
607 message_id: message_id.into(),
608 content: MaybeUndefined::Undefined,
609 meta: MaybeUndefined::Undefined,
610 }
611 }
612
613 #[must_use]
615 pub fn content(mut self, content: impl IntoMaybeUndefined<Vec<ContentBlock>>) -> Self {
616 self.content = content.into_maybe_undefined();
617 self
618 }
619
620 #[must_use]
626 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
627 self.meta = meta.into_maybe_undefined();
628 self
629 }
630}
631
632#[serde_as]
646#[skip_serializing_none]
647#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
648#[serde(rename_all = "camelCase")]
649#[non_exhaustive]
650pub struct AgentThought {
651 pub message_id: MessageId,
653 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<VecSkipError<_, SkipListener>>>")]
655 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
656 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
657 pub content: MaybeUndefined<Vec<ContentBlock>>,
658 #[serde(
664 rename = "_meta",
665 default,
666 skip_serializing_if = "MaybeUndefined::is_undefined"
667 )]
668 pub meta: MaybeUndefined<Meta>,
669}
670
671impl AgentThought {
672 #[must_use]
673 pub fn new(message_id: impl Into<MessageId>) -> Self {
674 Self {
675 message_id: message_id.into(),
676 content: MaybeUndefined::Undefined,
677 meta: MaybeUndefined::Undefined,
678 }
679 }
680
681 #[must_use]
683 pub fn content(mut self, content: impl IntoMaybeUndefined<Vec<ContentBlock>>) -> Self {
684 self.content = content.into_maybe_undefined();
685 self
686 }
687
688 #[must_use]
694 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
695 self.meta = meta.into_maybe_undefined();
696 self
697 }
698}
699
700#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash, Display, From)]
702#[serde(transparent)]
703#[from(Arc<str>, String, &'static str)]
704#[non_exhaustive]
705pub struct MessageId(pub Arc<str>);
706
707impl MessageId {
708 #[must_use]
709 pub fn new(id: impl Into<Arc<str>>) -> Self {
710 Self(id.into())
711 }
712}
713
714#[serde_as]
716#[skip_serializing_none]
717#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
718#[serde(rename_all = "camelCase")]
719#[non_exhaustive]
720pub struct AvailableCommandsUpdate {
721 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, SkipListener>>")]
723 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
724 pub available_commands: Vec<AvailableCommand>,
725 #[serde(rename = "_meta")]
731 pub meta: Option<Meta>,
732}
733
734impl AvailableCommandsUpdate {
735 #[must_use]
736 pub fn new(available_commands: Vec<AvailableCommand>) -> Self {
737 Self {
738 available_commands,
739 meta: None,
740 }
741 }
742
743 #[must_use]
749 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
750 self.meta = meta.into_option();
751 self
752 }
753}
754
755#[serde_as]
757#[skip_serializing_none]
758#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
759#[serde(rename_all = "camelCase")]
760#[non_exhaustive]
761pub struct AvailableCommand {
762 pub name: String,
764 pub description: String,
766 #[serde_as(deserialize_as = "DefaultOnError")]
768 #[schemars(extend("x-deserialize-default-on-error" = true))]
769 #[serde(default)]
770 pub input: Option<AvailableCommandInput>,
771 #[serde(rename = "_meta")]
777 pub meta: Option<Meta>,
778}
779
780impl AvailableCommand {
781 #[must_use]
782 pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
783 Self {
784 name: name.into(),
785 description: description.into(),
786 input: None,
787 meta: None,
788 }
789 }
790
791 #[must_use]
793 pub fn input(mut self, input: impl IntoOption<AvailableCommandInput>) -> Self {
794 self.input = input.into_option();
795 self
796 }
797
798 #[must_use]
804 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
805 self.meta = meta.into_option();
806 self
807 }
808}
809
810#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
812#[serde(untagged, rename_all = "camelCase")]
813#[non_exhaustive]
814pub enum AvailableCommandInput {
815 Unstructured(UnstructuredCommandInput),
817 Other(OtherAvailableCommandInput),
828}
829
830#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq, Eq)]
832#[schemars(inline)]
833#[serde(rename_all = "camelCase")]
834#[non_exhaustive]
835pub struct OtherAvailableCommandInput {
836 #[serde(rename = "type")]
842 pub type_: String,
843 #[serde(flatten)]
845 pub fields: BTreeMap<String, serde_json::Value>,
846}
847
848impl OtherAvailableCommandInput {
849 #[must_use]
850 pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
851 fields.remove("type");
852 Self {
853 type_: type_.into(),
854 fields,
855 }
856 }
857}
858
859impl<'de> Deserialize<'de> for OtherAvailableCommandInput {
860 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
861 where
862 D: serde::Deserializer<'de>,
863 {
864 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
865 let type_ = fields
866 .remove("type")
867 .ok_or_else(|| serde::de::Error::missing_field("type"))?;
868 let serde_json::Value::String(type_) = type_ else {
869 return Err(serde::de::Error::custom("`type` must be a string"));
870 };
871
872 Ok(Self { type_, fields })
873 }
874}
875
876#[skip_serializing_none]
878#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq, Eq)]
879#[schemars(transform = unstructured_command_input_schema)]
880#[serde(rename_all = "camelCase")]
881#[non_exhaustive]
882pub struct UnstructuredCommandInput {
883 pub hint: String,
885 #[serde(rename = "_meta")]
891 pub meta: Option<Meta>,
892}
893
894impl UnstructuredCommandInput {
895 #[must_use]
896 pub fn new(hint: impl Into<String>) -> Self {
897 Self {
898 hint: hint.into(),
899 meta: None,
900 }
901 }
902
903 #[must_use]
909 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
910 self.meta = meta.into_option();
911 self
912 }
913}
914
915impl<'de> Deserialize<'de> for UnstructuredCommandInput {
916 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
917 where
918 D: serde::Deserializer<'de>,
919 {
920 #[derive(Deserialize)]
921 #[serde(rename_all = "camelCase")]
922 struct RawUnstructuredCommandInput {
923 hint: String,
924 #[serde(rename = "_meta")]
925 meta: Option<Meta>,
926 #[serde(flatten)]
927 fields: BTreeMap<String, serde_json::Value>,
928 }
929
930 let raw = RawUnstructuredCommandInput::deserialize(deserializer)?;
931 if raw.fields.contains_key("type") {
932 return Err(serde::de::Error::custom(
933 "unstructured command input cannot include a `type` field",
934 ));
935 }
936
937 Ok(Self {
938 hint: raw.hint,
939 meta: raw.meta,
940 })
941 }
942}
943
944fn unstructured_command_input_schema(schema: &mut Schema) {
945 super::schema_util::reject_property(schema, "type");
946}
947
948#[skip_serializing_none]
956#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
957#[schemars(extend("x-side" = "client", "x-method" = SESSION_REQUEST_PERMISSION_METHOD_NAME))]
958#[serde(rename_all = "camelCase")]
959#[non_exhaustive]
960pub struct RequestPermissionRequest {
961 pub session_id: SessionId,
963 pub tool_call: ToolCallUpdate,
965 pub options: Vec<PermissionOption>,
967 #[serde(rename = "_meta")]
973 pub meta: Option<Meta>,
974}
975
976impl RequestPermissionRequest {
977 #[must_use]
978 pub fn new(
979 session_id: impl Into<SessionId>,
980 tool_call: ToolCallUpdate,
981 options: Vec<PermissionOption>,
982 ) -> Self {
983 Self {
984 session_id: session_id.into(),
985 tool_call,
986 options,
987 meta: None,
988 }
989 }
990
991 #[must_use]
997 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
998 self.meta = meta.into_option();
999 self
1000 }
1001}
1002
1003#[skip_serializing_none]
1005#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1006#[serde(rename_all = "camelCase")]
1007#[non_exhaustive]
1008pub struct PermissionOption {
1009 pub option_id: PermissionOptionId,
1011 pub name: String,
1013 pub kind: PermissionOptionKind,
1015 #[serde(rename = "_meta")]
1021 pub meta: Option<Meta>,
1022}
1023
1024impl PermissionOption {
1025 #[must_use]
1026 pub fn new(
1027 option_id: impl Into<PermissionOptionId>,
1028 name: impl Into<String>,
1029 kind: PermissionOptionKind,
1030 ) -> Self {
1031 Self {
1032 option_id: option_id.into(),
1033 name: name.into(),
1034 kind,
1035 meta: None,
1036 }
1037 }
1038
1039 #[must_use]
1045 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1046 self.meta = meta.into_option();
1047 self
1048 }
1049}
1050
1051#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash, Display, From)]
1053#[serde(transparent)]
1054#[from(Arc<str>, String, &'static str)]
1055#[non_exhaustive]
1056pub struct PermissionOptionId(pub Arc<str>);
1057
1058impl PermissionOptionId {
1059 #[must_use]
1060 pub fn new(id: impl Into<Arc<str>>) -> Self {
1061 Self(id.into())
1062 }
1063}
1064
1065#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1069#[serde(rename_all = "snake_case")]
1070#[non_exhaustive]
1071pub enum PermissionOptionKind {
1072 AllowOnce,
1074 AllowAlways,
1076 RejectOnce,
1078 RejectAlways,
1080 #[serde(untagged)]
1086 Other(String),
1087}
1088
1089#[skip_serializing_none]
1091#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1092#[schemars(extend("x-side" = "client", "x-method" = SESSION_REQUEST_PERMISSION_METHOD_NAME))]
1093#[serde(rename_all = "camelCase")]
1094#[non_exhaustive]
1095pub struct RequestPermissionResponse {
1096 pub outcome: RequestPermissionOutcome,
1099 #[serde(rename = "_meta")]
1105 pub meta: Option<Meta>,
1106}
1107
1108impl RequestPermissionResponse {
1109 #[must_use]
1110 pub fn new(outcome: RequestPermissionOutcome) -> Self {
1111 Self {
1112 outcome,
1113 meta: None,
1114 }
1115 }
1116
1117 #[must_use]
1123 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1124 self.meta = meta.into_option();
1125 self
1126 }
1127}
1128
1129#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1131#[serde(tag = "outcome", rename_all = "snake_case")]
1132#[schemars(extend("discriminator" = {"propertyName": "outcome"}))]
1133#[non_exhaustive]
1134pub enum RequestPermissionOutcome {
1135 Cancelled,
1143 #[serde(rename_all = "camelCase")]
1145 Selected(SelectedPermissionOutcome),
1146}
1147
1148#[skip_serializing_none]
1150#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1151#[serde(rename_all = "camelCase")]
1152#[non_exhaustive]
1153pub struct SelectedPermissionOutcome {
1154 pub option_id: PermissionOptionId,
1156 #[serde(rename = "_meta")]
1162 pub meta: Option<Meta>,
1163}
1164
1165impl SelectedPermissionOutcome {
1166 #[must_use]
1167 pub fn new(option_id: impl Into<PermissionOptionId>) -> Self {
1168 Self {
1169 option_id: option_id.into(),
1170 meta: None,
1171 }
1172 }
1173
1174 #[must_use]
1180 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1181 self.meta = meta.into_option();
1182 self
1183 }
1184}
1185
1186#[serde_as]
1195#[skip_serializing_none]
1196#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1197#[serde(rename_all = "camelCase")]
1198#[non_exhaustive]
1199pub struct ClientCapabilities {
1200 #[cfg(feature = "unstable_auth_methods")]
1208 #[serde(default)]
1209 pub auth: AuthCapabilities,
1210 #[cfg(feature = "unstable_elicitation")]
1217 #[serde_as(deserialize_as = "DefaultOnError")]
1218 #[schemars(extend("x-deserialize-default-on-error" = true))]
1219 #[serde(default)]
1220 pub elicitation: Option<ElicitationCapabilities>,
1221 #[cfg(feature = "unstable_nes")]
1227 #[serde_as(deserialize_as = "DefaultOnError")]
1228 #[schemars(extend("x-deserialize-default-on-error" = true))]
1229 #[serde(default)]
1230 pub nes: Option<ClientNesCapabilities>,
1231 #[cfg(feature = "unstable_nes")]
1237 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, SkipListener>>")]
1238 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
1239 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1240 pub position_encodings: Vec<PositionEncodingKind>,
1241
1242 #[serde(rename = "_meta")]
1248 pub meta: Option<Meta>,
1249}
1250
1251impl ClientCapabilities {
1252 #[must_use]
1253 pub fn new() -> Self {
1254 Self::default()
1255 }
1256
1257 #[cfg(feature = "unstable_auth_methods")]
1265 #[must_use]
1266 pub fn auth(mut self, auth: AuthCapabilities) -> Self {
1267 self.auth = auth;
1268 self
1269 }
1270
1271 #[cfg(feature = "unstable_elicitation")]
1278 #[must_use]
1279 pub fn elicitation(mut self, elicitation: impl IntoOption<ElicitationCapabilities>) -> Self {
1280 self.elicitation = elicitation.into_option();
1281 self
1282 }
1283
1284 #[cfg(feature = "unstable_nes")]
1288 #[must_use]
1289 pub fn nes(mut self, nes: impl IntoOption<ClientNesCapabilities>) -> Self {
1290 self.nes = nes.into_option();
1291 self
1292 }
1293
1294 #[cfg(feature = "unstable_nes")]
1298 #[must_use]
1299 pub fn position_encodings(mut self, position_encodings: Vec<PositionEncodingKind>) -> Self {
1300 self.position_encodings = position_encodings;
1301 self
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#[cfg(feature = "unstable_auth_methods")]
1326#[serde_as]
1327#[skip_serializing_none]
1328#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1329#[serde(rename_all = "camelCase")]
1330#[non_exhaustive]
1331pub struct AuthCapabilities {
1332 #[serde_as(deserialize_as = "DefaultOnError")]
1337 #[schemars(extend("x-deserialize-default-on-error" = true))]
1338 #[serde(default)]
1339 pub terminal: Option<TerminalAuthCapabilities>,
1340 #[serde(rename = "_meta")]
1346 pub meta: Option<Meta>,
1347}
1348
1349#[cfg(feature = "unstable_auth_methods")]
1350impl AuthCapabilities {
1351 #[must_use]
1352 pub fn new() -> Self {
1353 Self::default()
1354 }
1355
1356 #[must_use]
1361 pub fn terminal(mut self, terminal: impl IntoOption<TerminalAuthCapabilities>) -> Self {
1362 self.terminal = terminal.into_option();
1363 self
1364 }
1365
1366 #[must_use]
1372 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1373 self.meta = meta.into_option();
1374 self
1375 }
1376}
1377
1378#[cfg(feature = "unstable_auth_methods")]
1386#[skip_serializing_none]
1387#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1388#[non_exhaustive]
1389pub struct TerminalAuthCapabilities {
1390 #[serde(rename = "_meta")]
1396 pub meta: Option<Meta>,
1397}
1398
1399#[cfg(feature = "unstable_auth_methods")]
1400impl TerminalAuthCapabilities {
1401 #[must_use]
1402 pub fn new() -> Self {
1403 Self::default()
1404 }
1405
1406 #[must_use]
1412 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1413 self.meta = meta.into_option();
1414 self
1415 }
1416}
1417
1418#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1424#[non_exhaustive]
1425pub struct ClientMethodNames {
1426 pub session_request_permission: &'static str,
1428 pub session_update: &'static str,
1430 #[cfg(feature = "unstable_mcp_over_acp")]
1432 pub mcp_connect: &'static str,
1433 #[cfg(feature = "unstable_mcp_over_acp")]
1435 pub mcp_message: &'static str,
1436 #[cfg(feature = "unstable_mcp_over_acp")]
1438 pub mcp_disconnect: &'static str,
1439 #[cfg(feature = "unstable_elicitation")]
1441 pub elicitation_create: &'static str,
1442 #[cfg(feature = "unstable_elicitation")]
1444 pub elicitation_complete: &'static str,
1445}
1446
1447pub const CLIENT_METHOD_NAMES: ClientMethodNames = ClientMethodNames {
1449 session_update: SESSION_UPDATE_NOTIFICATION,
1450 session_request_permission: SESSION_REQUEST_PERMISSION_METHOD_NAME,
1451 #[cfg(feature = "unstable_mcp_over_acp")]
1452 mcp_connect: MCP_CONNECT_METHOD_NAME,
1453 #[cfg(feature = "unstable_mcp_over_acp")]
1454 mcp_message: MCP_MESSAGE_METHOD_NAME,
1455 #[cfg(feature = "unstable_mcp_over_acp")]
1456 mcp_disconnect: MCP_DISCONNECT_METHOD_NAME,
1457 #[cfg(feature = "unstable_elicitation")]
1458 elicitation_create: ELICITATION_CREATE_METHOD_NAME,
1459 #[cfg(feature = "unstable_elicitation")]
1460 elicitation_complete: ELICITATION_COMPLETE_NOTIFICATION,
1461};
1462
1463pub(crate) const SESSION_UPDATE_NOTIFICATION: &str = "session/update";
1465pub(crate) const SESSION_REQUEST_PERMISSION_METHOD_NAME: &str = "session/request_permission";
1467#[cfg(feature = "unstable_elicitation")]
1469pub(crate) const ELICITATION_CREATE_METHOD_NAME: &str = "elicitation/create";
1470#[cfg(feature = "unstable_elicitation")]
1472pub(crate) const ELICITATION_COMPLETE_NOTIFICATION: &str = "elicitation/complete";
1473
1474#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
1481#[serde(untagged)]
1482#[schemars(inline)]
1483#[non_exhaustive]
1484pub enum AgentRequest {
1485 RequestPermissionRequest(Box<RequestPermissionRequest>),
1496 #[cfg(feature = "unstable_elicitation")]
1502 CreateElicitationRequest(CreateElicitationRequest),
1503 #[cfg(feature = "unstable_mcp_over_acp")]
1509 ConnectMcpRequest(ConnectMcpRequest),
1510 #[cfg(feature = "unstable_mcp_over_acp")]
1516 MessageMcpRequest(MessageMcpRequest),
1517 #[cfg(feature = "unstable_mcp_over_acp")]
1523 DisconnectMcpRequest(DisconnectMcpRequest),
1524 ExtMethodRequest(ExtRequest),
1532}
1533
1534impl AgentRequest {
1535 #[must_use]
1537 pub fn method(&self) -> &str {
1538 match self {
1539 Self::RequestPermissionRequest(_) => CLIENT_METHOD_NAMES.session_request_permission,
1540 #[cfg(feature = "unstable_elicitation")]
1541 Self::CreateElicitationRequest(_) => CLIENT_METHOD_NAMES.elicitation_create,
1542 #[cfg(feature = "unstable_mcp_over_acp")]
1543 Self::ConnectMcpRequest(_) => CLIENT_METHOD_NAMES.mcp_connect,
1544 #[cfg(feature = "unstable_mcp_over_acp")]
1545 Self::MessageMcpRequest(_) => CLIENT_METHOD_NAMES.mcp_message,
1546 #[cfg(feature = "unstable_mcp_over_acp")]
1547 Self::DisconnectMcpRequest(_) => CLIENT_METHOD_NAMES.mcp_disconnect,
1548 Self::ExtMethodRequest(ext_request) => &ext_request.method,
1549 }
1550 }
1551}
1552
1553#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
1560#[serde(untagged)]
1561#[schemars(inline)]
1562#[non_exhaustive]
1563pub enum ClientResponse {
1564 RequestPermissionResponse(RequestPermissionResponse),
1565 #[cfg(feature = "unstable_elicitation")]
1566 CreateElicitationResponse(CreateElicitationResponse),
1567 #[cfg(feature = "unstable_mcp_over_acp")]
1568 ConnectMcpResponse(ConnectMcpResponse),
1569 #[cfg(feature = "unstable_mcp_over_acp")]
1570 DisconnectMcpResponse(#[serde(default)] DisconnectMcpResponse),
1571 ExtMethodResponse(ExtResponse),
1572 #[cfg(feature = "unstable_mcp_over_acp")]
1573 MessageMcpResponse(MessageMcpResponse),
1574}
1575
1576#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
1583#[serde(untagged)]
1584#[schemars(inline)]
1585#[non_exhaustive]
1586pub enum AgentNotification {
1587 SessionNotification(Box<SessionNotification>),
1599 #[cfg(feature = "unstable_elicitation")]
1605 CompleteElicitationNotification(CompleteElicitationNotification),
1606 #[cfg(feature = "unstable_mcp_over_acp")]
1612 MessageMcpNotification(MessageMcpNotification),
1613 ExtNotification(ExtNotification),
1621}
1622
1623impl AgentNotification {
1624 #[must_use]
1626 pub fn method(&self) -> &str {
1627 match self {
1628 Self::SessionNotification(_) => CLIENT_METHOD_NAMES.session_update,
1629 #[cfg(feature = "unstable_elicitation")]
1630 Self::CompleteElicitationNotification(_) => CLIENT_METHOD_NAMES.elicitation_complete,
1631 #[cfg(feature = "unstable_mcp_over_acp")]
1632 Self::MessageMcpNotification(_) => CLIENT_METHOD_NAMES.mcp_message,
1633 Self::ExtNotification(ext_notification) => &ext_notification.method,
1634 }
1635 }
1636}
1637
1638#[cfg(test)]
1639mod tests {
1640 use super::*;
1641
1642 #[test]
1643 fn test_serialization_behavior() {
1644 use serde_json::json;
1645
1646 assert_eq!(
1647 serde_json::from_value::<SessionInfoUpdate>(json!({})).unwrap(),
1648 SessionInfoUpdate {
1649 title: MaybeUndefined::Undefined,
1650 updated_at: MaybeUndefined::Undefined,
1651 meta: None
1652 }
1653 );
1654 assert_eq!(
1655 serde_json::from_value::<SessionInfoUpdate>(json!({"title": null, "updatedAt": null}))
1656 .unwrap(),
1657 SessionInfoUpdate {
1658 title: MaybeUndefined::Null,
1659 updated_at: MaybeUndefined::Null,
1660 meta: None
1661 }
1662 );
1663 assert_eq!(
1664 serde_json::from_value::<SessionInfoUpdate>(
1665 json!({"title": "title", "updatedAt": "timestamp"})
1666 )
1667 .unwrap(),
1668 SessionInfoUpdate {
1669 title: MaybeUndefined::Value("title".to_string()),
1670 updated_at: MaybeUndefined::Value("timestamp".to_string()),
1671 meta: None
1672 }
1673 );
1674
1675 assert_eq!(
1676 serde_json::to_value(SessionInfoUpdate::new()).unwrap(),
1677 json!({})
1678 );
1679 assert_eq!(
1680 serde_json::to_value(SessionInfoUpdate::new().title("title")).unwrap(),
1681 json!({"title": "title"})
1682 );
1683 assert_eq!(
1684 serde_json::to_value(SessionInfoUpdate::new().title(None)).unwrap(),
1685 json!({"title": null})
1686 );
1687 assert_eq!(
1688 serde_json::to_value(
1689 SessionInfoUpdate::new()
1690 .title("title")
1691 .title(MaybeUndefined::Undefined)
1692 )
1693 .unwrap(),
1694 json!({})
1695 );
1696 }
1697
1698 #[test]
1699 fn test_content_chunk_message_id_serialization() {
1700 use serde_json::json;
1701
1702 assert_eq!(
1703 serde_json::to_value(SessionUpdate::AgentMessageChunk(ContentChunk::new(
1704 ContentBlock::Text(crate::v2::TextContent::new("Hello")),
1705 "msg_agent_c42b9",
1706 )))
1707 .unwrap(),
1708 json!({
1709 "sessionUpdate": "agent_message_chunk",
1710 "messageId": "msg_agent_c42b9",
1711 "content": {
1712 "type": "text",
1713 "text": "Hello"
1714 }
1715 })
1716 );
1717
1718 let err = serde_json::from_value::<ContentChunk>(json!({
1719 "content": {
1720 "type": "text",
1721 "text": "Hello"
1722 }
1723 }))
1724 .unwrap_err();
1725
1726 assert!(err.to_string().contains("messageId"), "{err}");
1727 }
1728
1729 #[test]
1730 fn test_tool_call_content_chunk_serialization() {
1731 use serde_json::json;
1732
1733 assert_eq!(
1734 serde_json::to_value(SessionUpdate::ToolCallContentChunk(
1735 ToolCallContentChunk::new(
1736 "call_001",
1737 crate::v2::ContentBlock::Text(crate::v2::TextContent::new("partial output")),
1738 )
1739 ))
1740 .unwrap(),
1741 json!({
1742 "sessionUpdate": "tool_call_content_chunk",
1743 "toolCallId": "call_001",
1744 "content": {
1745 "type": "content",
1746 "content": {
1747 "type": "text",
1748 "text": "partial output"
1749 }
1750 }
1751 })
1752 );
1753
1754 let err = serde_json::from_value::<ToolCallContentChunk>(json!({
1755 "content": {
1756 "type": "content",
1757 "content": {
1758 "type": "text",
1759 "text": "partial output"
1760 }
1761 }
1762 }))
1763 .unwrap_err();
1764
1765 assert!(err.to_string().contains("toolCallId"), "{err}");
1766 }
1767
1768 #[test]
1769 fn test_full_message_serialization() {
1770 use serde_json::json;
1771
1772 assert_eq!(
1773 serde_json::to_value(SessionUpdate::UserMessage(
1774 UserMessage::new("msg_user_8f7a1").content(vec![ContentBlock::Text(
1775 crate::v2::TextContent::new("Hello")
1776 )])
1777 ))
1778 .unwrap(),
1779 json!({
1780 "sessionUpdate": "user_message",
1781 "messageId": "msg_user_8f7a1",
1782 "content": [
1783 {
1784 "type": "text",
1785 "text": "Hello"
1786 }
1787 ]
1788 })
1789 );
1790
1791 assert_eq!(
1792 serde_json::to_value(SessionUpdate::AgentMessage(
1793 AgentMessage::new("msg_agent_c42b9").content(vec![ContentBlock::Text(
1794 crate::v2::TextContent::new("Hello")
1795 )])
1796 ))
1797 .unwrap(),
1798 json!({
1799 "sessionUpdate": "agent_message",
1800 "messageId": "msg_agent_c42b9",
1801 "content": [
1802 {
1803 "type": "text",
1804 "text": "Hello"
1805 }
1806 ]
1807 })
1808 );
1809
1810 assert_eq!(
1811 serde_json::to_value(SessionUpdate::AgentThought(
1812 AgentThought::new("msg_thought_a12").content(vec![ContentBlock::Text(
1813 crate::v2::TextContent::new("Need to inspect the call sites first.")
1814 )])
1815 ))
1816 .unwrap(),
1817 json!({
1818 "sessionUpdate": "agent_thought",
1819 "messageId": "msg_thought_a12",
1820 "content": [
1821 {
1822 "type": "text",
1823 "text": "Need to inspect the call sites first."
1824 }
1825 ]
1826 })
1827 );
1828 }
1829
1830 #[test]
1831 fn test_message_upsert_serialization() {
1832 use serde_json::json;
1833
1834 assert_eq!(
1835 serde_json::to_value(SessionUpdate::UserMessage(
1836 UserMessage::new("msg_empty").content(Vec::<ContentBlock>::new())
1837 ))
1838 .unwrap(),
1839 json!({
1840 "sessionUpdate": "user_message",
1841 "messageId": "msg_empty",
1842 "content": []
1843 })
1844 );
1845
1846 let empty = serde_json::from_value::<UserMessage>(json!({
1847 "messageId": "msg_empty",
1848 "content": []
1849 }))
1850 .unwrap();
1851 assert!(matches!(
1852 empty.content,
1853 MaybeUndefined::Value(ref content) if content.is_empty()
1854 ));
1855
1856 let patch = serde_json::from_value::<AgentMessage>(json!({
1857 "messageId": "msg_agent_c42b9"
1858 }))
1859 .unwrap();
1860 assert_eq!(patch.content, MaybeUndefined::Undefined);
1861 assert_eq!(patch.meta, MaybeUndefined::Undefined);
1862
1863 let patch = serde_json::from_value::<AgentThought>(json!({
1864 "messageId": "msg_thought_a12"
1865 }))
1866 .unwrap();
1867 assert_eq!(patch.content, MaybeUndefined::Undefined);
1868
1869 let clear = serde_json::from_value::<UserMessage>(json!({
1870 "messageId": "msg_user_8f7a1",
1871 "content": null
1872 }))
1873 .unwrap();
1874 assert_eq!(clear.content, MaybeUndefined::Null);
1875
1876 let mut meta = Meta::new();
1877 meta.insert("source".to_string(), json!("replay"));
1878
1879 assert_eq!(
1880 serde_json::to_value(SessionUpdate::UserMessage(
1881 UserMessage::new("msg_user_8f7a1").meta(meta)
1882 ))
1883 .unwrap(),
1884 json!({
1885 "sessionUpdate": "user_message",
1886 "messageId": "msg_user_8f7a1",
1887 "_meta": {
1888 "source": "replay"
1889 }
1890 })
1891 );
1892
1893 assert_eq!(
1894 serde_json::to_value(SessionUpdate::UserMessage(
1895 UserMessage::new("msg_user_8f7a1").meta(None::<Meta>)
1896 ))
1897 .unwrap(),
1898 json!({
1899 "sessionUpdate": "user_message",
1900 "messageId": "msg_user_8f7a1",
1901 "_meta": null
1902 })
1903 );
1904 }
1905
1906 #[test]
1907 fn test_usage_update_serialization() {
1908 use serde_json::json;
1909
1910 assert_eq!(
1911 serde_json::to_value(SessionUpdate::UsageUpdate(UsageUpdate::new(
1912 53_000, 200_000
1913 )))
1914 .unwrap(),
1915 json!({
1916 "sessionUpdate": "usage_update",
1917 "used": 53000,
1918 "size": 200_000
1919 })
1920 );
1921
1922 assert_eq!(
1923 serde_json::to_value(SessionUpdate::UsageUpdate(
1924 UsageUpdate::new(53_000, 200_000).cost(Cost::new(0.045, "USD"))
1925 ))
1926 .unwrap(),
1927 json!({
1928 "sessionUpdate": "usage_update",
1929 "used": 53000,
1930 "size": 200_000,
1931 "cost": {
1932 "amount": 0.045,
1933 "currency": "USD"
1934 }
1935 })
1936 );
1937
1938 let SessionUpdate::UsageUpdate(update) = serde_json::from_value(json!({
1939 "sessionUpdate": "usage_update",
1940 "used": 53000,
1941 "size": 200_000,
1942 "cost": null
1943 }))
1944 .unwrap() else {
1945 panic!("expected usage update");
1946 };
1947
1948 assert_eq!(update.cost, None);
1949 }
1950
1951 #[test]
1952 fn session_update_preserves_unknown_variant() {
1953 use serde_json::json;
1954
1955 let update: SessionUpdate = serde_json::from_value(json!({
1956 "sessionUpdate": "_status_badge",
1957 "label": "Indexing",
1958 "progress": 0.5
1959 }))
1960 .unwrap();
1961
1962 let SessionUpdate::Other(unknown) = update else {
1963 panic!("expected unknown session update");
1964 };
1965
1966 assert_eq!(unknown.session_update, "_status_badge");
1967 assert_eq!(unknown.fields.get("label"), Some(&json!("Indexing")));
1968 assert_eq!(unknown.fields.get("progress"), Some(&json!(0.5)));
1969
1970 assert_eq!(
1971 serde_json::to_value(SessionUpdate::Other(unknown)).unwrap(),
1972 json!({
1973 "sessionUpdate": "_status_badge",
1974 "label": "Indexing",
1975 "progress": 0.5
1976 })
1977 );
1978 }
1979
1980 #[test]
1981 fn test_plan_update_serialization() {
1982 use serde_json::json;
1983
1984 let plan_update =
1985 SessionUpdate::PlanUpdate(PlanUpdate::new(crate::v2::PlanUpdateContent::items(
1986 "plan-1",
1987 vec![crate::v2::PlanEntry::new(
1988 "Step 1",
1989 crate::v2::PlanEntryPriority::High,
1990 crate::v2::PlanEntryStatus::Pending,
1991 )],
1992 )));
1993
1994 assert_eq!(
1995 serde_json::to_value(plan_update).unwrap(),
1996 json!({
1997 "sessionUpdate": "plan_update",
1998 "plan": {
1999 "type": "items",
2000 "id": "plan-1",
2001 "entries": [
2002 {
2003 "content": "Step 1",
2004 "priority": "high",
2005 "status": "pending"
2006 }
2007 ]
2008 }
2009 })
2010 );
2011 }
2012
2013 #[cfg(feature = "unstable_plan_operations")]
2014 #[test]
2015 fn test_plan_removed_serialization() {
2016 use serde_json::json;
2017
2018 assert_eq!(
2019 serde_json::to_value(SessionUpdate::PlanRemoved(PlanRemoved::new("plan-1"))).unwrap(),
2020 json!({
2021 "sessionUpdate": "plan_removed",
2022 "id": "plan-1"
2023 })
2024 );
2025 }
2026
2027 #[test]
2028 fn available_command_input_preserves_unknown_typed_variant() {
2029 use serde_json::json;
2030
2031 let input: AvailableCommandInput = serde_json::from_value(json!({
2032 "type": "_choices",
2033 "hint": "Pick one",
2034 "options": ["fast", "careful"]
2035 }))
2036 .unwrap();
2037
2038 let AvailableCommandInput::Other(unknown) = input else {
2039 panic!("expected unknown command input");
2040 };
2041
2042 assert_eq!(unknown.type_, "_choices");
2043 assert_eq!(unknown.fields.get("hint"), Some(&json!("Pick one")));
2044 assert_eq!(
2045 unknown.fields.get("options"),
2046 Some(&json!(["fast", "careful"]))
2047 );
2048 assert_eq!(
2049 serde_json::to_value(AvailableCommandInput::Other(unknown)).unwrap(),
2050 json!({
2051 "type": "_choices",
2052 "hint": "Pick one",
2053 "options": ["fast", "careful"]
2054 })
2055 );
2056 }
2057
2058 #[test]
2059 fn available_command_input_unknown_does_not_hide_malformed_unstructured_variant() {
2060 use serde_json::json;
2061
2062 assert!(serde_json::from_value::<AvailableCommandInput>(json!({})).is_err());
2063 assert!(
2064 serde_json::from_value::<AvailableCommandInput>(json!({
2065 "type": 1,
2066 "hint": "Pick one"
2067 }))
2068 .is_err()
2069 );
2070 }
2071
2072 #[cfg(feature = "unstable_nes")]
2073 #[test]
2074 fn test_client_capabilities_position_encodings_serialization() {
2075 use serde_json::json;
2076
2077 let capabilities = ClientCapabilities::new().position_encodings(vec![
2078 PositionEncodingKind::Utf32,
2079 PositionEncodingKind::Utf16,
2080 ]);
2081 let json = serde_json::to_value(&capabilities).unwrap();
2082
2083 assert_eq!(json["positionEncodings"], json!(["utf-32", "utf-16"]));
2084 }
2085
2086 #[cfg(feature = "unstable_mcp_over_acp")]
2087 #[test]
2088 fn test_agent_mcp_request_method_names() {
2089 use serde_json::json;
2090
2091 let params: serde_json::Map<String, serde_json::Value> =
2092 [("cursor".to_string(), json!("abc"))].into_iter().collect();
2093
2094 assert_eq!(CLIENT_METHOD_NAMES.mcp_connect, "mcp/connect");
2095 assert_eq!(CLIENT_METHOD_NAMES.mcp_message, "mcp/message");
2096 assert_eq!(CLIENT_METHOD_NAMES.mcp_disconnect, "mcp/disconnect");
2097
2098 assert_eq!(
2099 AgentRequest::ConnectMcpRequest(ConnectMcpRequest::new("server-1")).method(),
2100 "mcp/connect"
2101 );
2102 assert_eq!(
2103 AgentRequest::MessageMcpRequest(MessageMcpRequest::new("conn-1", "tools/list"))
2104 .method(),
2105 "mcp/message"
2106 );
2107 assert_eq!(
2108 AgentRequest::DisconnectMcpRequest(DisconnectMcpRequest::new("conn-1")).method(),
2109 "mcp/disconnect"
2110 );
2111 assert_eq!(
2112 AgentNotification::MessageMcpNotification(MessageMcpNotification::new(
2113 "conn-1",
2114 "notifications/progress"
2115 ))
2116 .method(),
2117 "mcp/message"
2118 );
2119
2120 assert_eq!(
2121 serde_json::to_value(ConnectMcpRequest::new("server-1")).unwrap(),
2122 json!({ "acpId": "server-1" })
2123 );
2124 assert_eq!(
2125 serde_json::to_value(ConnectMcpResponse::new("conn-1")).unwrap(),
2126 json!({ "connectionId": "conn-1" })
2127 );
2128 assert_eq!(
2129 serde_json::to_value(MessageMcpRequest::new("conn-1", "tools/list").params(params))
2130 .unwrap(),
2131 json!({
2132 "connectionId": "conn-1",
2133 "method": "tools/list",
2134 "params": { "cursor": "abc" }
2135 })
2136 );
2137 assert_eq!(
2138 serde_json::to_value(DisconnectMcpRequest::new("conn-1")).unwrap(),
2139 json!({ "connectionId": "conn-1" })
2140 );
2141 assert_eq!(
2142 serde_json::to_value(MessageMcpNotification::new(
2143 "conn-1",
2144 "notifications/progress"
2145 ))
2146 .unwrap(),
2147 json!({
2148 "connectionId": "conn-1",
2149 "method": "notifications/progress"
2150 })
2151 );
2152
2153 let request_with_null_params: MessageMcpRequest = serde_json::from_value(json!({
2154 "connectionId": "conn-1",
2155 "method": "tools/list",
2156 "params": null
2157 }))
2158 .unwrap();
2159 assert_eq!(request_with_null_params.params, None);
2160 }
2161
2162 #[cfg(feature = "unstable_auth_methods")]
2163 #[test]
2164 fn test_auth_capabilities_serialize_terminal_support_as_object() {
2165 use serde_json::json;
2166
2167 let capabilities = AuthCapabilities::new().terminal(TerminalAuthCapabilities::new());
2168
2169 assert_eq!(
2170 serde_json::to_value(&capabilities).unwrap(),
2171 json!({
2172 "terminal": {}
2173 })
2174 );
2175
2176 let deserialized: AuthCapabilities = serde_json::from_value(json!({
2177 "terminal": false
2178 }))
2179 .unwrap();
2180 assert!(deserialized.terminal.is_none());
2181 }
2182}