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, ToolCall, 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 AgentMessageChunk(ContentChunk),
98 AgentThoughtChunk(ContentChunk),
100 ToolCall(ToolCall),
102 ToolCallUpdate(ToolCallUpdate),
104 PlanUpdate(PlanUpdate),
107 #[cfg(feature = "unstable_plan_operations")]
113 PlanRemoved(PlanRemoved),
114 AvailableCommandsUpdate(AvailableCommandsUpdate),
116 ConfigOptionUpdate(ConfigOptionUpdate),
118 SessionInfoUpdate(SessionInfoUpdate),
120 UsageUpdate(UsageUpdate),
122 #[serde(untagged)]
132 Other(OtherSessionUpdate),
133}
134
135#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq)]
141#[schemars(inline)]
142#[schemars(transform = other_session_update_schema)]
143#[serde(rename_all = "camelCase")]
144#[non_exhaustive]
145pub struct OtherSessionUpdate {
146 #[serde(rename = "sessionUpdate")]
152 pub session_update: String,
153 #[serde(flatten)]
155 pub fields: BTreeMap<String, serde_json::Value>,
156}
157
158impl OtherSessionUpdate {
159 #[must_use]
160 pub fn new(
161 session_update: impl Into<String>,
162 mut fields: BTreeMap<String, serde_json::Value>,
163 ) -> Self {
164 fields.remove("sessionUpdate");
165 Self {
166 session_update: session_update.into(),
167 fields,
168 }
169 }
170}
171
172impl<'de> Deserialize<'de> for OtherSessionUpdate {
173 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
174 where
175 D: serde::Deserializer<'de>,
176 {
177 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
178 let session_update = fields
179 .remove("sessionUpdate")
180 .ok_or_else(|| serde::de::Error::missing_field("sessionUpdate"))?;
181 let serde_json::Value::String(session_update) = session_update else {
182 return Err(serde::de::Error::custom("`sessionUpdate` must be a string"));
183 };
184
185 if is_known_session_update(&session_update) {
186 return Err(serde::de::Error::custom(format!(
187 "known session update `{session_update}` did not match its schema"
188 )));
189 }
190
191 Ok(Self {
192 session_update,
193 fields,
194 })
195 }
196}
197
198fn is_known_session_update(session_update: &str) -> bool {
199 matches!(
200 session_update,
201 "user_message_chunk"
202 | "agent_message_chunk"
203 | "agent_thought_chunk"
204 | "tool_call"
205 | "tool_call_update"
206 | "plan_update"
207 | "available_commands_update"
208 | "config_option_update"
209 | "session_info_update"
210 | "usage_update"
211 )
212}
213
214fn other_session_update_schema(schema: &mut Schema) {
215 super::schema_util::reject_known_string_discriminators(
216 schema,
217 "sessionUpdate",
218 &[
219 "user_message_chunk",
220 "agent_message_chunk",
221 "agent_thought_chunk",
222 "tool_call",
223 "tool_call_update",
224 "plan_update",
225 "available_commands_update",
226 "config_option_update",
227 "session_info_update",
228 #[cfg(feature = "unstable_plan_operations")]
229 "plan_removed",
230 "usage_update",
231 ],
232 );
233}
234
235#[serde_as]
237#[skip_serializing_none]
238#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
239#[serde(rename_all = "camelCase")]
240#[non_exhaustive]
241pub struct ConfigOptionUpdate {
242 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, SkipListener>>")]
244 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
245 pub config_options: Vec<SessionConfigOption>,
246 #[serde(rename = "_meta")]
252 pub meta: Option<Meta>,
253}
254
255impl ConfigOptionUpdate {
256 #[must_use]
257 pub fn new(config_options: Vec<SessionConfigOption>) -> Self {
258 Self {
259 config_options,
260 meta: None,
261 }
262 }
263
264 #[must_use]
270 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
271 self.meta = meta.into_option();
272 self
273 }
274}
275
276#[skip_serializing_none]
281#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
282#[serde(rename_all = "camelCase")]
283#[non_exhaustive]
284pub struct SessionInfoUpdate {
285 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
287 pub title: MaybeUndefined<String>,
288 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
290 pub updated_at: MaybeUndefined<String>,
291 #[serde(rename = "_meta")]
297 pub meta: Option<Meta>,
298}
299
300impl SessionInfoUpdate {
301 #[must_use]
302 pub fn new() -> Self {
303 Self::default()
304 }
305
306 #[must_use]
308 pub fn title(mut self, title: impl IntoMaybeUndefined<String>) -> Self {
309 self.title = title.into_maybe_undefined();
310 self
311 }
312
313 #[must_use]
315 pub fn updated_at(mut self, updated_at: impl IntoMaybeUndefined<String>) -> Self {
316 self.updated_at = updated_at.into_maybe_undefined();
317 self
318 }
319
320 #[must_use]
326 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
327 self.meta = meta.into_option();
328 self
329 }
330}
331
332#[serde_as]
334#[skip_serializing_none]
335#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
336#[serde(rename_all = "camelCase")]
337#[non_exhaustive]
338pub struct UsageUpdate {
339 pub used: u64,
341 pub size: u64,
343 #[serde_as(deserialize_as = "DefaultOnError")]
345 #[schemars(extend("x-deserialize-default-on-error" = true))]
346 #[serde(default)]
347 pub cost: Option<Cost>,
348 #[serde(rename = "_meta")]
354 pub meta: Option<Meta>,
355}
356
357impl UsageUpdate {
358 #[must_use]
359 pub fn new(used: u64, size: u64) -> Self {
360 Self {
361 used,
362 size,
363 cost: None,
364 meta: None,
365 }
366 }
367
368 #[must_use]
370 pub fn cost(mut self, cost: impl IntoOption<Cost>) -> Self {
371 self.cost = cost.into_option();
372 self
373 }
374
375 #[must_use]
381 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
382 self.meta = meta.into_option();
383 self
384 }
385}
386
387#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
389#[serde(rename_all = "camelCase")]
390#[non_exhaustive]
391pub struct Cost {
392 pub amount: f64,
394 pub currency: String,
396}
397
398impl Cost {
399 #[must_use]
400 pub fn new(amount: f64, currency: impl Into<String>) -> Self {
401 Self {
402 amount,
403 currency: currency.into(),
404 }
405 }
406}
407
408#[skip_serializing_none]
410#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
411#[serde(rename_all = "camelCase")]
412#[non_exhaustive]
413pub struct ContentChunk {
414 pub content: ContentBlock,
416 pub message_id: MessageId,
421 #[serde(rename = "_meta")]
427 pub meta: Option<Meta>,
428}
429
430impl ContentChunk {
431 #[must_use]
432 pub fn new(content: ContentBlock, message_id: impl Into<MessageId>) -> Self {
433 Self {
434 content,
435 message_id: message_id.into(),
436 meta: None,
437 }
438 }
439
440 #[must_use]
445 pub fn message_id(mut self, message_id: impl Into<MessageId>) -> Self {
446 self.message_id = message_id.into();
447 self
448 }
449
450 #[must_use]
456 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
457 self.meta = meta.into_option();
458 self
459 }
460}
461
462#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash, Display, From)]
464#[serde(transparent)]
465#[from(Arc<str>, String, &'static str)]
466#[non_exhaustive]
467pub struct MessageId(pub Arc<str>);
468
469impl MessageId {
470 #[must_use]
471 pub fn new(id: impl Into<Arc<str>>) -> Self {
472 Self(id.into())
473 }
474}
475
476#[serde_as]
478#[skip_serializing_none]
479#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
480#[serde(rename_all = "camelCase")]
481#[non_exhaustive]
482pub struct AvailableCommandsUpdate {
483 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, SkipListener>>")]
485 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
486 pub available_commands: Vec<AvailableCommand>,
487 #[serde(rename = "_meta")]
493 pub meta: Option<Meta>,
494}
495
496impl AvailableCommandsUpdate {
497 #[must_use]
498 pub fn new(available_commands: Vec<AvailableCommand>) -> Self {
499 Self {
500 available_commands,
501 meta: None,
502 }
503 }
504
505 #[must_use]
511 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
512 self.meta = meta.into_option();
513 self
514 }
515}
516
517#[serde_as]
519#[skip_serializing_none]
520#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
521#[serde(rename_all = "camelCase")]
522#[non_exhaustive]
523pub struct AvailableCommand {
524 pub name: String,
526 pub description: String,
528 #[serde_as(deserialize_as = "DefaultOnError")]
530 #[schemars(extend("x-deserialize-default-on-error" = true))]
531 #[serde(default)]
532 pub input: Option<AvailableCommandInput>,
533 #[serde(rename = "_meta")]
539 pub meta: Option<Meta>,
540}
541
542impl AvailableCommand {
543 #[must_use]
544 pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
545 Self {
546 name: name.into(),
547 description: description.into(),
548 input: None,
549 meta: None,
550 }
551 }
552
553 #[must_use]
555 pub fn input(mut self, input: impl IntoOption<AvailableCommandInput>) -> Self {
556 self.input = input.into_option();
557 self
558 }
559
560 #[must_use]
566 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
567 self.meta = meta.into_option();
568 self
569 }
570}
571
572#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
574#[serde(untagged, rename_all = "camelCase")]
575#[non_exhaustive]
576pub enum AvailableCommandInput {
577 Unstructured(UnstructuredCommandInput),
579 Other(OtherAvailableCommandInput),
590}
591
592#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq, Eq)]
594#[schemars(inline)]
595#[serde(rename_all = "camelCase")]
596#[non_exhaustive]
597pub struct OtherAvailableCommandInput {
598 #[serde(rename = "type")]
604 pub type_: String,
605 #[serde(flatten)]
607 pub fields: BTreeMap<String, serde_json::Value>,
608}
609
610impl OtherAvailableCommandInput {
611 #[must_use]
612 pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
613 fields.remove("type");
614 Self {
615 type_: type_.into(),
616 fields,
617 }
618 }
619}
620
621impl<'de> Deserialize<'de> for OtherAvailableCommandInput {
622 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
623 where
624 D: serde::Deserializer<'de>,
625 {
626 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
627 let type_ = fields
628 .remove("type")
629 .ok_or_else(|| serde::de::Error::missing_field("type"))?;
630 let serde_json::Value::String(type_) = type_ else {
631 return Err(serde::de::Error::custom("`type` must be a string"));
632 };
633
634 Ok(Self { type_, fields })
635 }
636}
637
638#[skip_serializing_none]
640#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq, Eq)]
641#[schemars(transform = unstructured_command_input_schema)]
642#[serde(rename_all = "camelCase")]
643#[non_exhaustive]
644pub struct UnstructuredCommandInput {
645 pub hint: String,
647 #[serde(rename = "_meta")]
653 pub meta: Option<Meta>,
654}
655
656impl UnstructuredCommandInput {
657 #[must_use]
658 pub fn new(hint: impl Into<String>) -> Self {
659 Self {
660 hint: hint.into(),
661 meta: None,
662 }
663 }
664
665 #[must_use]
671 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
672 self.meta = meta.into_option();
673 self
674 }
675}
676
677impl<'de> Deserialize<'de> for UnstructuredCommandInput {
678 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
679 where
680 D: serde::Deserializer<'de>,
681 {
682 #[derive(Deserialize)]
683 #[serde(rename_all = "camelCase")]
684 struct RawUnstructuredCommandInput {
685 hint: String,
686 #[serde(rename = "_meta")]
687 meta: Option<Meta>,
688 #[serde(flatten)]
689 fields: BTreeMap<String, serde_json::Value>,
690 }
691
692 let raw = RawUnstructuredCommandInput::deserialize(deserializer)?;
693 if raw.fields.contains_key("type") {
694 return Err(serde::de::Error::custom(
695 "unstructured command input cannot include a `type` field",
696 ));
697 }
698
699 Ok(Self {
700 hint: raw.hint,
701 meta: raw.meta,
702 })
703 }
704}
705
706fn unstructured_command_input_schema(schema: &mut Schema) {
707 super::schema_util::reject_property(schema, "type");
708}
709
710#[skip_serializing_none]
718#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
719#[schemars(extend("x-side" = "client", "x-method" = SESSION_REQUEST_PERMISSION_METHOD_NAME))]
720#[serde(rename_all = "camelCase")]
721#[non_exhaustive]
722pub struct RequestPermissionRequest {
723 pub session_id: SessionId,
725 pub tool_call: ToolCallUpdate,
727 pub options: Vec<PermissionOption>,
729 #[serde(rename = "_meta")]
735 pub meta: Option<Meta>,
736}
737
738impl RequestPermissionRequest {
739 #[must_use]
740 pub fn new(
741 session_id: impl Into<SessionId>,
742 tool_call: ToolCallUpdate,
743 options: Vec<PermissionOption>,
744 ) -> Self {
745 Self {
746 session_id: session_id.into(),
747 tool_call,
748 options,
749 meta: None,
750 }
751 }
752
753 #[must_use]
759 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
760 self.meta = meta.into_option();
761 self
762 }
763}
764
765#[skip_serializing_none]
767#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
768#[serde(rename_all = "camelCase")]
769#[non_exhaustive]
770pub struct PermissionOption {
771 pub option_id: PermissionOptionId,
773 pub name: String,
775 pub kind: PermissionOptionKind,
777 #[serde(rename = "_meta")]
783 pub meta: Option<Meta>,
784}
785
786impl PermissionOption {
787 #[must_use]
788 pub fn new(
789 option_id: impl Into<PermissionOptionId>,
790 name: impl Into<String>,
791 kind: PermissionOptionKind,
792 ) -> Self {
793 Self {
794 option_id: option_id.into(),
795 name: name.into(),
796 kind,
797 meta: None,
798 }
799 }
800
801 #[must_use]
807 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
808 self.meta = meta.into_option();
809 self
810 }
811}
812
813#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash, Display, From)]
815#[serde(transparent)]
816#[from(Arc<str>, String, &'static str)]
817#[non_exhaustive]
818pub struct PermissionOptionId(pub Arc<str>);
819
820impl PermissionOptionId {
821 #[must_use]
822 pub fn new(id: impl Into<Arc<str>>) -> Self {
823 Self(id.into())
824 }
825}
826
827#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
831#[serde(rename_all = "snake_case")]
832#[non_exhaustive]
833pub enum PermissionOptionKind {
834 AllowOnce,
836 AllowAlways,
838 RejectOnce,
840 RejectAlways,
842 #[serde(untagged)]
848 Other(String),
849}
850
851#[skip_serializing_none]
853#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
854#[schemars(extend("x-side" = "client", "x-method" = SESSION_REQUEST_PERMISSION_METHOD_NAME))]
855#[serde(rename_all = "camelCase")]
856#[non_exhaustive]
857pub struct RequestPermissionResponse {
858 pub outcome: RequestPermissionOutcome,
861 #[serde(rename = "_meta")]
867 pub meta: Option<Meta>,
868}
869
870impl RequestPermissionResponse {
871 #[must_use]
872 pub fn new(outcome: RequestPermissionOutcome) -> Self {
873 Self {
874 outcome,
875 meta: None,
876 }
877 }
878
879 #[must_use]
885 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
886 self.meta = meta.into_option();
887 self
888 }
889}
890
891#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
893#[serde(tag = "outcome", rename_all = "snake_case")]
894#[schemars(extend("discriminator" = {"propertyName": "outcome"}))]
895#[non_exhaustive]
896pub enum RequestPermissionOutcome {
897 Cancelled,
905 #[serde(rename_all = "camelCase")]
907 Selected(SelectedPermissionOutcome),
908}
909
910#[skip_serializing_none]
912#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
913#[serde(rename_all = "camelCase")]
914#[non_exhaustive]
915pub struct SelectedPermissionOutcome {
916 pub option_id: PermissionOptionId,
918 #[serde(rename = "_meta")]
924 pub meta: Option<Meta>,
925}
926
927impl SelectedPermissionOutcome {
928 #[must_use]
929 pub fn new(option_id: impl Into<PermissionOptionId>) -> Self {
930 Self {
931 option_id: option_id.into(),
932 meta: None,
933 }
934 }
935
936 #[must_use]
942 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
943 self.meta = meta.into_option();
944 self
945 }
946}
947
948#[serde_as]
957#[skip_serializing_none]
958#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
959#[serde(rename_all = "camelCase")]
960#[non_exhaustive]
961pub struct ClientCapabilities {
962 #[cfg(feature = "unstable_auth_methods")]
970 #[serde(default)]
971 pub auth: AuthCapabilities,
972 #[cfg(feature = "unstable_elicitation")]
979 #[serde_as(deserialize_as = "DefaultOnError")]
980 #[schemars(extend("x-deserialize-default-on-error" = true))]
981 #[serde(default)]
982 pub elicitation: Option<ElicitationCapabilities>,
983 #[cfg(feature = "unstable_nes")]
989 #[serde_as(deserialize_as = "DefaultOnError")]
990 #[schemars(extend("x-deserialize-default-on-error" = true))]
991 #[serde(default)]
992 pub nes: Option<ClientNesCapabilities>,
993 #[cfg(feature = "unstable_nes")]
999 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, SkipListener>>")]
1000 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
1001 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1002 pub position_encodings: Vec<PositionEncodingKind>,
1003
1004 #[serde(rename = "_meta")]
1010 pub meta: Option<Meta>,
1011}
1012
1013impl ClientCapabilities {
1014 #[must_use]
1015 pub fn new() -> Self {
1016 Self::default()
1017 }
1018
1019 #[cfg(feature = "unstable_auth_methods")]
1027 #[must_use]
1028 pub fn auth(mut self, auth: AuthCapabilities) -> Self {
1029 self.auth = auth;
1030 self
1031 }
1032
1033 #[cfg(feature = "unstable_elicitation")]
1040 #[must_use]
1041 pub fn elicitation(mut self, elicitation: impl IntoOption<ElicitationCapabilities>) -> Self {
1042 self.elicitation = elicitation.into_option();
1043 self
1044 }
1045
1046 #[cfg(feature = "unstable_nes")]
1050 #[must_use]
1051 pub fn nes(mut self, nes: impl IntoOption<ClientNesCapabilities>) -> Self {
1052 self.nes = nes.into_option();
1053 self
1054 }
1055
1056 #[cfg(feature = "unstable_nes")]
1060 #[must_use]
1061 pub fn position_encodings(mut self, position_encodings: Vec<PositionEncodingKind>) -> Self {
1062 self.position_encodings = position_encodings;
1063 self
1064 }
1065
1066 #[must_use]
1072 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1073 self.meta = meta.into_option();
1074 self
1075 }
1076}
1077
1078#[cfg(feature = "unstable_auth_methods")]
1088#[serde_as]
1089#[skip_serializing_none]
1090#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1091#[serde(rename_all = "camelCase")]
1092#[non_exhaustive]
1093pub struct AuthCapabilities {
1094 #[serde_as(deserialize_as = "DefaultOnError")]
1099 #[schemars(extend("x-deserialize-default-on-error" = true))]
1100 #[serde(default)]
1101 pub terminal: Option<TerminalAuthCapabilities>,
1102 #[serde(rename = "_meta")]
1108 pub meta: Option<Meta>,
1109}
1110
1111#[cfg(feature = "unstable_auth_methods")]
1112impl AuthCapabilities {
1113 #[must_use]
1114 pub fn new() -> Self {
1115 Self::default()
1116 }
1117
1118 #[must_use]
1123 pub fn terminal(mut self, terminal: impl IntoOption<TerminalAuthCapabilities>) -> Self {
1124 self.terminal = terminal.into_option();
1125 self
1126 }
1127
1128 #[must_use]
1134 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1135 self.meta = meta.into_option();
1136 self
1137 }
1138}
1139
1140#[cfg(feature = "unstable_auth_methods")]
1148#[skip_serializing_none]
1149#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1150#[non_exhaustive]
1151pub struct TerminalAuthCapabilities {
1152 #[serde(rename = "_meta")]
1158 pub meta: Option<Meta>,
1159}
1160
1161#[cfg(feature = "unstable_auth_methods")]
1162impl TerminalAuthCapabilities {
1163 #[must_use]
1164 pub fn new() -> Self {
1165 Self::default()
1166 }
1167
1168 #[must_use]
1174 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1175 self.meta = meta.into_option();
1176 self
1177 }
1178}
1179
1180#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1186#[non_exhaustive]
1187pub struct ClientMethodNames {
1188 pub session_request_permission: &'static str,
1190 pub session_update: &'static str,
1192 #[cfg(feature = "unstable_mcp_over_acp")]
1194 pub mcp_connect: &'static str,
1195 #[cfg(feature = "unstable_mcp_over_acp")]
1197 pub mcp_message: &'static str,
1198 #[cfg(feature = "unstable_mcp_over_acp")]
1200 pub mcp_disconnect: &'static str,
1201 #[cfg(feature = "unstable_elicitation")]
1203 pub elicitation_create: &'static str,
1204 #[cfg(feature = "unstable_elicitation")]
1206 pub elicitation_complete: &'static str,
1207}
1208
1209pub const CLIENT_METHOD_NAMES: ClientMethodNames = ClientMethodNames {
1211 session_update: SESSION_UPDATE_NOTIFICATION,
1212 session_request_permission: SESSION_REQUEST_PERMISSION_METHOD_NAME,
1213 #[cfg(feature = "unstable_mcp_over_acp")]
1214 mcp_connect: MCP_CONNECT_METHOD_NAME,
1215 #[cfg(feature = "unstable_mcp_over_acp")]
1216 mcp_message: MCP_MESSAGE_METHOD_NAME,
1217 #[cfg(feature = "unstable_mcp_over_acp")]
1218 mcp_disconnect: MCP_DISCONNECT_METHOD_NAME,
1219 #[cfg(feature = "unstable_elicitation")]
1220 elicitation_create: ELICITATION_CREATE_METHOD_NAME,
1221 #[cfg(feature = "unstable_elicitation")]
1222 elicitation_complete: ELICITATION_COMPLETE_NOTIFICATION,
1223};
1224
1225pub(crate) const SESSION_UPDATE_NOTIFICATION: &str = "session/update";
1227pub(crate) const SESSION_REQUEST_PERMISSION_METHOD_NAME: &str = "session/request_permission";
1229#[cfg(feature = "unstable_elicitation")]
1231pub(crate) const ELICITATION_CREATE_METHOD_NAME: &str = "elicitation/create";
1232#[cfg(feature = "unstable_elicitation")]
1234pub(crate) const ELICITATION_COMPLETE_NOTIFICATION: &str = "elicitation/complete";
1235
1236#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
1243#[serde(untagged)]
1244#[schemars(inline)]
1245#[non_exhaustive]
1246pub enum AgentRequest {
1247 RequestPermissionRequest(RequestPermissionRequest),
1258 #[cfg(feature = "unstable_elicitation")]
1264 CreateElicitationRequest(CreateElicitationRequest),
1265 #[cfg(feature = "unstable_mcp_over_acp")]
1271 ConnectMcpRequest(ConnectMcpRequest),
1272 #[cfg(feature = "unstable_mcp_over_acp")]
1278 MessageMcpRequest(MessageMcpRequest),
1279 #[cfg(feature = "unstable_mcp_over_acp")]
1285 DisconnectMcpRequest(DisconnectMcpRequest),
1286 ExtMethodRequest(ExtRequest),
1294}
1295
1296impl AgentRequest {
1297 #[must_use]
1299 pub fn method(&self) -> &str {
1300 match self {
1301 Self::RequestPermissionRequest(_) => CLIENT_METHOD_NAMES.session_request_permission,
1302 #[cfg(feature = "unstable_elicitation")]
1303 Self::CreateElicitationRequest(_) => CLIENT_METHOD_NAMES.elicitation_create,
1304 #[cfg(feature = "unstable_mcp_over_acp")]
1305 Self::ConnectMcpRequest(_) => CLIENT_METHOD_NAMES.mcp_connect,
1306 #[cfg(feature = "unstable_mcp_over_acp")]
1307 Self::MessageMcpRequest(_) => CLIENT_METHOD_NAMES.mcp_message,
1308 #[cfg(feature = "unstable_mcp_over_acp")]
1309 Self::DisconnectMcpRequest(_) => CLIENT_METHOD_NAMES.mcp_disconnect,
1310 Self::ExtMethodRequest(ext_request) => &ext_request.method,
1311 }
1312 }
1313}
1314
1315#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
1322#[serde(untagged)]
1323#[schemars(inline)]
1324#[non_exhaustive]
1325pub enum ClientResponse {
1326 RequestPermissionResponse(RequestPermissionResponse),
1327 #[cfg(feature = "unstable_elicitation")]
1328 CreateElicitationResponse(CreateElicitationResponse),
1329 #[cfg(feature = "unstable_mcp_over_acp")]
1330 ConnectMcpResponse(ConnectMcpResponse),
1331 #[cfg(feature = "unstable_mcp_over_acp")]
1332 DisconnectMcpResponse(#[serde(default)] DisconnectMcpResponse),
1333 ExtMethodResponse(ExtResponse),
1334 #[cfg(feature = "unstable_mcp_over_acp")]
1335 MessageMcpResponse(MessageMcpResponse),
1336}
1337
1338#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
1345#[serde(untagged)]
1346#[expect(clippy::large_enum_variant)]
1347#[schemars(inline)]
1348#[non_exhaustive]
1349pub enum AgentNotification {
1350 SessionNotification(SessionNotification),
1362 #[cfg(feature = "unstable_elicitation")]
1368 CompleteElicitationNotification(CompleteElicitationNotification),
1369 #[cfg(feature = "unstable_mcp_over_acp")]
1375 MessageMcpNotification(MessageMcpNotification),
1376 ExtNotification(ExtNotification),
1384}
1385
1386impl AgentNotification {
1387 #[must_use]
1389 pub fn method(&self) -> &str {
1390 match self {
1391 Self::SessionNotification(_) => CLIENT_METHOD_NAMES.session_update,
1392 #[cfg(feature = "unstable_elicitation")]
1393 Self::CompleteElicitationNotification(_) => CLIENT_METHOD_NAMES.elicitation_complete,
1394 #[cfg(feature = "unstable_mcp_over_acp")]
1395 Self::MessageMcpNotification(_) => CLIENT_METHOD_NAMES.mcp_message,
1396 Self::ExtNotification(ext_notification) => &ext_notification.method,
1397 }
1398 }
1399}
1400
1401#[cfg(test)]
1402mod tests {
1403 use super::*;
1404
1405 #[test]
1406 fn test_serialization_behavior() {
1407 use serde_json::json;
1408
1409 assert_eq!(
1410 serde_json::from_value::<SessionInfoUpdate>(json!({})).unwrap(),
1411 SessionInfoUpdate {
1412 title: MaybeUndefined::Undefined,
1413 updated_at: MaybeUndefined::Undefined,
1414 meta: None
1415 }
1416 );
1417 assert_eq!(
1418 serde_json::from_value::<SessionInfoUpdate>(json!({"title": null, "updatedAt": null}))
1419 .unwrap(),
1420 SessionInfoUpdate {
1421 title: MaybeUndefined::Null,
1422 updated_at: MaybeUndefined::Null,
1423 meta: None
1424 }
1425 );
1426 assert_eq!(
1427 serde_json::from_value::<SessionInfoUpdate>(
1428 json!({"title": "title", "updatedAt": "timestamp"})
1429 )
1430 .unwrap(),
1431 SessionInfoUpdate {
1432 title: MaybeUndefined::Value("title".to_string()),
1433 updated_at: MaybeUndefined::Value("timestamp".to_string()),
1434 meta: None
1435 }
1436 );
1437
1438 assert_eq!(
1439 serde_json::to_value(SessionInfoUpdate::new()).unwrap(),
1440 json!({})
1441 );
1442 assert_eq!(
1443 serde_json::to_value(SessionInfoUpdate::new().title("title")).unwrap(),
1444 json!({"title": "title"})
1445 );
1446 assert_eq!(
1447 serde_json::to_value(SessionInfoUpdate::new().title(None)).unwrap(),
1448 json!({"title": null})
1449 );
1450 assert_eq!(
1451 serde_json::to_value(
1452 SessionInfoUpdate::new()
1453 .title("title")
1454 .title(MaybeUndefined::Undefined)
1455 )
1456 .unwrap(),
1457 json!({})
1458 );
1459 }
1460
1461 #[test]
1462 fn test_content_chunk_message_id_serialization() {
1463 use serde_json::json;
1464
1465 assert_eq!(
1466 serde_json::to_value(SessionUpdate::AgentMessageChunk(ContentChunk::new(
1467 ContentBlock::Text(crate::v2::TextContent::new("Hello")),
1468 "msg_agent_c42b9",
1469 )))
1470 .unwrap(),
1471 json!({
1472 "sessionUpdate": "agent_message_chunk",
1473 "messageId": "msg_agent_c42b9",
1474 "content": {
1475 "type": "text",
1476 "text": "Hello"
1477 }
1478 })
1479 );
1480
1481 let err = serde_json::from_value::<ContentChunk>(json!({
1482 "content": {
1483 "type": "text",
1484 "text": "Hello"
1485 }
1486 }))
1487 .unwrap_err();
1488
1489 assert!(err.to_string().contains("messageId"), "{err}");
1490 }
1491
1492 #[test]
1493 fn test_usage_update_serialization() {
1494 use serde_json::json;
1495
1496 assert_eq!(
1497 serde_json::to_value(SessionUpdate::UsageUpdate(UsageUpdate::new(
1498 53_000, 200_000
1499 )))
1500 .unwrap(),
1501 json!({
1502 "sessionUpdate": "usage_update",
1503 "used": 53000,
1504 "size": 200000
1505 })
1506 );
1507
1508 assert_eq!(
1509 serde_json::to_value(SessionUpdate::UsageUpdate(
1510 UsageUpdate::new(53_000, 200_000).cost(Cost::new(0.045, "USD"))
1511 ))
1512 .unwrap(),
1513 json!({
1514 "sessionUpdate": "usage_update",
1515 "used": 53000,
1516 "size": 200000,
1517 "cost": {
1518 "amount": 0.045,
1519 "currency": "USD"
1520 }
1521 })
1522 );
1523
1524 let SessionUpdate::UsageUpdate(update) = serde_json::from_value(json!({
1525 "sessionUpdate": "usage_update",
1526 "used": 53000,
1527 "size": 200000,
1528 "cost": null
1529 }))
1530 .unwrap() else {
1531 panic!("expected usage update");
1532 };
1533
1534 assert_eq!(update.cost, None);
1535 }
1536
1537 #[test]
1538 fn session_update_preserves_unknown_variant() {
1539 use serde_json::json;
1540
1541 let update: SessionUpdate = serde_json::from_value(json!({
1542 "sessionUpdate": "_status_badge",
1543 "label": "Indexing",
1544 "progress": 0.5
1545 }))
1546 .unwrap();
1547
1548 let SessionUpdate::Other(unknown) = update else {
1549 panic!("expected unknown session update");
1550 };
1551
1552 assert_eq!(unknown.session_update, "_status_badge");
1553 assert_eq!(unknown.fields.get("label"), Some(&json!("Indexing")));
1554 assert_eq!(unknown.fields.get("progress"), Some(&json!(0.5)));
1555
1556 assert_eq!(
1557 serde_json::to_value(SessionUpdate::Other(unknown)).unwrap(),
1558 json!({
1559 "sessionUpdate": "_status_badge",
1560 "label": "Indexing",
1561 "progress": 0.5
1562 })
1563 );
1564 }
1565
1566 #[test]
1567 fn test_plan_update_serialization() {
1568 use serde_json::json;
1569
1570 let plan_update =
1571 SessionUpdate::PlanUpdate(PlanUpdate::new(crate::v2::PlanUpdateContent::items(
1572 "plan-1",
1573 vec![crate::v2::PlanEntry::new(
1574 "Step 1",
1575 crate::v2::PlanEntryPriority::High,
1576 crate::v2::PlanEntryStatus::Pending,
1577 )],
1578 )));
1579
1580 assert_eq!(
1581 serde_json::to_value(plan_update).unwrap(),
1582 json!({
1583 "sessionUpdate": "plan_update",
1584 "plan": {
1585 "type": "items",
1586 "id": "plan-1",
1587 "entries": [
1588 {
1589 "content": "Step 1",
1590 "priority": "high",
1591 "status": "pending"
1592 }
1593 ]
1594 }
1595 })
1596 );
1597 }
1598
1599 #[cfg(feature = "unstable_plan_operations")]
1600 #[test]
1601 fn test_plan_removed_serialization() {
1602 use serde_json::json;
1603
1604 assert_eq!(
1605 serde_json::to_value(SessionUpdate::PlanRemoved(PlanRemoved::new("plan-1"))).unwrap(),
1606 json!({
1607 "sessionUpdate": "plan_removed",
1608 "id": "plan-1"
1609 })
1610 );
1611 }
1612
1613 #[test]
1614 fn available_command_input_preserves_unknown_typed_variant() {
1615 use serde_json::json;
1616
1617 let input: AvailableCommandInput = serde_json::from_value(json!({
1618 "type": "_choices",
1619 "hint": "Pick one",
1620 "options": ["fast", "careful"]
1621 }))
1622 .unwrap();
1623
1624 let AvailableCommandInput::Other(unknown) = input else {
1625 panic!("expected unknown command input");
1626 };
1627
1628 assert_eq!(unknown.type_, "_choices");
1629 assert_eq!(unknown.fields.get("hint"), Some(&json!("Pick one")));
1630 assert_eq!(
1631 unknown.fields.get("options"),
1632 Some(&json!(["fast", "careful"]))
1633 );
1634 assert_eq!(
1635 serde_json::to_value(AvailableCommandInput::Other(unknown)).unwrap(),
1636 json!({
1637 "type": "_choices",
1638 "hint": "Pick one",
1639 "options": ["fast", "careful"]
1640 })
1641 );
1642 }
1643
1644 #[test]
1645 fn available_command_input_unknown_does_not_hide_malformed_unstructured_variant() {
1646 use serde_json::json;
1647
1648 assert!(serde_json::from_value::<AvailableCommandInput>(json!({})).is_err());
1649 assert!(
1650 serde_json::from_value::<AvailableCommandInput>(json!({
1651 "type": 1,
1652 "hint": "Pick one"
1653 }))
1654 .is_err()
1655 );
1656 }
1657
1658 #[cfg(feature = "unstable_nes")]
1659 #[test]
1660 fn test_client_capabilities_position_encodings_serialization() {
1661 use serde_json::json;
1662
1663 let capabilities = ClientCapabilities::new().position_encodings(vec![
1664 PositionEncodingKind::Utf32,
1665 PositionEncodingKind::Utf16,
1666 ]);
1667 let json = serde_json::to_value(&capabilities).unwrap();
1668
1669 assert_eq!(json["positionEncodings"], json!(["utf-32", "utf-16"]));
1670 }
1671
1672 #[cfg(feature = "unstable_mcp_over_acp")]
1673 #[test]
1674 fn test_agent_mcp_request_method_names() {
1675 use serde_json::json;
1676
1677 let params: serde_json::Map<String, serde_json::Value> =
1678 [("cursor".to_string(), json!("abc"))].into_iter().collect();
1679
1680 assert_eq!(CLIENT_METHOD_NAMES.mcp_connect, "mcp/connect");
1681 assert_eq!(CLIENT_METHOD_NAMES.mcp_message, "mcp/message");
1682 assert_eq!(CLIENT_METHOD_NAMES.mcp_disconnect, "mcp/disconnect");
1683
1684 assert_eq!(
1685 AgentRequest::ConnectMcpRequest(ConnectMcpRequest::new("server-1")).method(),
1686 "mcp/connect"
1687 );
1688 assert_eq!(
1689 AgentRequest::MessageMcpRequest(MessageMcpRequest::new("conn-1", "tools/list"))
1690 .method(),
1691 "mcp/message"
1692 );
1693 assert_eq!(
1694 AgentRequest::DisconnectMcpRequest(DisconnectMcpRequest::new("conn-1")).method(),
1695 "mcp/disconnect"
1696 );
1697 assert_eq!(
1698 AgentNotification::MessageMcpNotification(MessageMcpNotification::new(
1699 "conn-1",
1700 "notifications/progress"
1701 ))
1702 .method(),
1703 "mcp/message"
1704 );
1705
1706 assert_eq!(
1707 serde_json::to_value(ConnectMcpRequest::new("server-1")).unwrap(),
1708 json!({ "acpId": "server-1" })
1709 );
1710 assert_eq!(
1711 serde_json::to_value(ConnectMcpResponse::new("conn-1")).unwrap(),
1712 json!({ "connectionId": "conn-1" })
1713 );
1714 assert_eq!(
1715 serde_json::to_value(MessageMcpRequest::new("conn-1", "tools/list").params(params))
1716 .unwrap(),
1717 json!({
1718 "connectionId": "conn-1",
1719 "method": "tools/list",
1720 "params": { "cursor": "abc" }
1721 })
1722 );
1723 assert_eq!(
1724 serde_json::to_value(DisconnectMcpRequest::new("conn-1")).unwrap(),
1725 json!({ "connectionId": "conn-1" })
1726 );
1727 assert_eq!(
1728 serde_json::to_value(MessageMcpNotification::new(
1729 "conn-1",
1730 "notifications/progress"
1731 ))
1732 .unwrap(),
1733 json!({
1734 "connectionId": "conn-1",
1735 "method": "notifications/progress"
1736 })
1737 );
1738
1739 let request_with_null_params: MessageMcpRequest = serde_json::from_value(json!({
1740 "connectionId": "conn-1",
1741 "method": "tools/list",
1742 "params": null
1743 }))
1744 .unwrap();
1745 assert_eq!(request_with_null_params.params, None);
1746 }
1747
1748 #[cfg(feature = "unstable_auth_methods")]
1749 #[test]
1750 fn test_auth_capabilities_serialize_terminal_support_as_object() {
1751 use serde_json::json;
1752
1753 let capabilities = AuthCapabilities::new().terminal(TerminalAuthCapabilities::new());
1754
1755 assert_eq!(
1756 serde_json::to_value(&capabilities).unwrap(),
1757 json!({
1758 "terminal": {}
1759 })
1760 );
1761
1762 let deserialized: AuthCapabilities = serde_json::from_value(json!({
1763 "terminal": false
1764 }))
1765 .unwrap();
1766 assert!(deserialized.terminal.is_none());
1767 }
1768}