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#[serde_as]
46#[skip_serializing_none]
47#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
48#[schemars(extend("x-side" = "client", "x-method" = SESSION_UPDATE_NOTIFICATION))]
49#[serde(rename_all = "camelCase")]
50#[non_exhaustive]
51pub struct UpdateSessionNotification {
52 pub session_id: SessionId,
54 pub update: SessionUpdate,
56 #[serde_as(deserialize_as = "DefaultOnError")]
62 #[schemars(extend("x-deserialize-default-on-error" = true))]
63 #[serde(default)]
64 #[serde(rename = "_meta")]
65 pub meta: Option<Meta>,
66}
67
68impl UpdateSessionNotification {
69 #[must_use]
71 pub fn new(session_id: impl Into<SessionId>, update: SessionUpdate) -> Self {
72 Self {
73 session_id: session_id.into(),
74 update,
75 meta: None,
76 }
77 }
78
79 #[must_use]
85 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
86 self.meta = meta.into_option();
87 self
88 }
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
97#[serde(tag = "sessionUpdate", rename_all = "snake_case")]
98#[schemars(extend("discriminator" = {"propertyName": "sessionUpdate"}))]
99#[non_exhaustive]
100pub enum SessionUpdate {
101 UserMessageChunk(ContentChunk),
103 UserMessage(UserMessage),
109 AgentMessageChunk(ContentChunk),
111 AgentMessage(AgentMessage),
117 AgentThoughtChunk(ContentChunk),
119 AgentThought(AgentThought),
125 StateUpdate(StateUpdate),
131 ToolCallContentChunk(ToolCallContentChunk),
133 ToolCallUpdate(ToolCallUpdate),
135 PlanUpdate(PlanUpdate),
138 #[cfg(feature = "unstable_plan_operations")]
144 PlanRemoved(PlanRemoved),
145 AvailableCommandsUpdate(AvailableCommandsUpdate),
147 ConfigOptionUpdate(ConfigOptionUpdate),
149 SessionInfoUpdate(SessionInfoUpdate),
151 UsageUpdate(UsageUpdate),
153 #[serde(untagged)]
163 Other(OtherSessionUpdate),
164}
165
166#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq)]
172#[schemars(inline)]
173#[schemars(transform = other_session_update_schema)]
174#[serde(rename_all = "camelCase")]
175#[non_exhaustive]
176pub struct OtherSessionUpdate {
177 #[serde(rename = "sessionUpdate")]
183 pub session_update: String,
184 #[serde(flatten)]
186 pub fields: BTreeMap<String, serde_json::Value>,
187}
188
189impl OtherSessionUpdate {
190 #[must_use]
192 pub fn new(
193 session_update: impl Into<String>,
194 mut fields: BTreeMap<String, serde_json::Value>,
195 ) -> Self {
196 fields.remove("sessionUpdate");
197 Self {
198 session_update: session_update.into(),
199 fields,
200 }
201 }
202}
203
204impl<'de> Deserialize<'de> for OtherSessionUpdate {
205 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
206 where
207 D: serde::Deserializer<'de>,
208 {
209 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
210 let session_update = fields
211 .remove("sessionUpdate")
212 .ok_or_else(|| serde::de::Error::missing_field("sessionUpdate"))?;
213 let serde_json::Value::String(session_update) = session_update else {
214 return Err(serde::de::Error::custom("`sessionUpdate` must be a string"));
215 };
216
217 if is_known_session_update(&session_update) {
218 return Err(serde::de::Error::custom(format!(
219 "known session update `{session_update}` did not match its schema"
220 )));
221 }
222
223 Ok(Self {
224 session_update,
225 fields,
226 })
227 }
228}
229
230fn is_known_session_update(session_update: &str) -> bool {
231 #[cfg(feature = "unstable_plan_operations")]
232 if session_update == "plan_removed" {
233 return true;
234 }
235 matches!(
236 session_update,
237 "user_message_chunk"
238 | "user_message"
239 | "agent_message_chunk"
240 | "agent_message"
241 | "agent_thought_chunk"
242 | "agent_thought"
243 | "state_update"
244 | "tool_call_content_chunk"
245 | "tool_call_update"
246 | "plan_update"
247 | "available_commands_update"
248 | "config_option_update"
249 | "session_info_update"
250 | "usage_update"
251 )
252}
253
254fn other_session_update_schema(schema: &mut Schema) {
255 super::schema_util::reject_known_string_discriminators(
256 schema,
257 "sessionUpdate",
258 &[
259 "user_message_chunk",
260 "user_message",
261 "agent_message_chunk",
262 "agent_message",
263 "agent_thought_chunk",
264 "agent_thought",
265 "state_update",
266 "tool_call_content_chunk",
267 "tool_call_update",
268 "plan_update",
269 "available_commands_update",
270 "config_option_update",
271 "session_info_update",
272 #[cfg(feature = "unstable_plan_operations")]
273 "plan_removed",
274 "usage_update",
275 ],
276 );
277}
278
279#[serde_as]
281#[skip_serializing_none]
282#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
283#[serde(rename_all = "camelCase")]
284#[non_exhaustive]
285pub struct ConfigOptionUpdate {
286 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, SkipListener>>")]
288 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
289 pub config_options: Vec<SessionConfigOption>,
290 #[serde_as(deserialize_as = "DefaultOnError")]
296 #[schemars(extend("x-deserialize-default-on-error" = true))]
297 #[serde(default)]
298 #[serde(rename = "_meta")]
299 pub meta: Option<Meta>,
300}
301
302impl ConfigOptionUpdate {
303 #[must_use]
305 pub fn new(config_options: Vec<SessionConfigOption>) -> Self {
306 Self {
307 config_options,
308 meta: None,
309 }
310 }
311
312 #[must_use]
318 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
319 self.meta = meta.into_option();
320 self
321 }
322}
323
324#[serde_as]
332#[skip_serializing_none]
333#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
334#[serde(rename_all = "camelCase")]
335#[non_exhaustive]
336pub struct SessionInfoUpdate {
337 #[serde_as(deserialize_as = "DefaultOnError")]
339 #[schemars(extend("x-deserialize-default-on-error" = true))]
340 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
341 pub title: MaybeUndefined<String>,
342 #[serde_as(deserialize_as = "DefaultOnError")]
344 #[schemars(extend("x-deserialize-default-on-error" = true, "format" = "date-time"))]
345 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
346 pub updated_at: MaybeUndefined<String>,
347 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
353 #[schemars(extend("x-deserialize-default-on-error" = true))]
354 #[serde(
355 rename = "_meta",
356 default,
357 skip_serializing_if = "MaybeUndefined::is_undefined"
358 )]
359 pub meta: MaybeUndefined<Meta>,
360}
361
362impl SessionInfoUpdate {
363 #[must_use]
365 pub fn new() -> Self {
366 Self::default()
367 }
368
369 #[must_use]
371 pub fn title(mut self, title: impl IntoMaybeUndefined<String>) -> Self {
372 self.title = title.into_maybe_undefined();
373 self
374 }
375
376 #[must_use]
378 pub fn updated_at(mut self, updated_at: impl IntoMaybeUndefined<String>) -> Self {
379 self.updated_at = updated_at.into_maybe_undefined();
380 self
381 }
382
383 #[must_use]
389 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
390 self.meta = meta.into_maybe_undefined();
391 self
392 }
393}
394
395#[serde_as]
397#[skip_serializing_none]
398#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
399#[serde(rename_all = "camelCase")]
400#[non_exhaustive]
401pub struct UsageUpdate {
402 pub used: u64,
404 pub size: u64,
406 #[serde_as(deserialize_as = "DefaultOnError")]
408 #[schemars(extend("x-deserialize-default-on-error" = true))]
409 #[serde(default)]
410 pub cost: Option<Cost>,
411 #[serde_as(deserialize_as = "DefaultOnError")]
417 #[schemars(extend("x-deserialize-default-on-error" = true))]
418 #[serde(default)]
419 #[serde(rename = "_meta")]
420 pub meta: Option<Meta>,
421}
422
423impl UsageUpdate {
424 #[must_use]
426 pub fn new(used: u64, size: u64) -> Self {
427 Self {
428 used,
429 size,
430 cost: None,
431 meta: None,
432 }
433 }
434
435 #[must_use]
437 pub fn cost(mut self, cost: impl IntoOption<Cost>) -> Self {
438 self.cost = cost.into_option();
439 self
440 }
441
442 #[must_use]
448 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
449 self.meta = meta.into_option();
450 self
451 }
452}
453
454#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
461#[serde(tag = "state", rename_all = "snake_case")]
462#[schemars(extend("discriminator" = {"propertyName": "state"}))]
463#[non_exhaustive]
464pub enum StateUpdate {
465 Running(RunningStateUpdate),
467 Idle(IdleStateUpdate),
469 RequiresAction(RequiresActionStateUpdate),
471 #[serde(untagged)]
477 Other(OtherStateUpdate),
478}
479
480#[serde_as]
482#[skip_serializing_none]
483#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
484#[serde(rename_all = "camelCase")]
485#[non_exhaustive]
486pub struct RunningStateUpdate {
487 #[serde_as(deserialize_as = "DefaultOnError")]
493 #[schemars(extend("x-deserialize-default-on-error" = true))]
494 #[serde(default)]
495 #[serde(rename = "_meta")]
496 pub meta: Option<Meta>,
497}
498
499impl RunningStateUpdate {
500 #[must_use]
502 pub fn new() -> Self {
503 Self::default()
504 }
505
506 #[must_use]
512 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
513 self.meta = meta.into_option();
514 self
515 }
516}
517
518#[serde_as]
520#[skip_serializing_none]
521#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
522#[serde(rename_all = "camelCase")]
523#[non_exhaustive]
524pub struct IdleStateUpdate {
525 #[serde_as(deserialize_as = "DefaultOnError")]
530 #[schemars(extend("x-deserialize-default-on-error" = true))]
531 #[serde(default)]
532 pub stop_reason: Option<StopReason>,
533 #[cfg(feature = "unstable_end_turn_token_usage")]
542 #[serde_as(deserialize_as = "DefaultOnError")]
543 #[schemars(extend("x-deserialize-default-on-error" = true))]
544 #[serde(default)]
545 pub usage: Option<Usage>,
546 #[serde_as(deserialize_as = "DefaultOnError")]
552 #[schemars(extend("x-deserialize-default-on-error" = true))]
553 #[serde(default)]
554 #[serde(rename = "_meta")]
555 pub meta: Option<Meta>,
556}
557
558impl IdleStateUpdate {
559 #[must_use]
561 pub fn new() -> Self {
562 Self::default()
563 }
564
565 #[must_use]
567 pub fn stop_reason(mut self, stop_reason: impl IntoOption<StopReason>) -> Self {
568 self.stop_reason = stop_reason.into_option();
569 self
570 }
571
572 #[cfg(feature = "unstable_end_turn_token_usage")]
578 #[must_use]
579 pub fn usage(mut self, usage: impl IntoOption<Usage>) -> Self {
580 self.usage = usage.into_option();
581 self
582 }
583
584 #[must_use]
590 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
591 self.meta = meta.into_option();
592 self
593 }
594}
595
596#[serde_as]
598#[skip_serializing_none]
599#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
600#[serde(rename_all = "camelCase")]
601#[non_exhaustive]
602pub struct RequiresActionStateUpdate {
603 #[serde_as(deserialize_as = "DefaultOnError")]
609 #[schemars(extend("x-deserialize-default-on-error" = true))]
610 #[serde(default)]
611 #[serde(rename = "_meta")]
612 pub meta: Option<Meta>,
613}
614
615impl RequiresActionStateUpdate {
616 #[must_use]
618 pub fn new() -> Self {
619 Self::default()
620 }
621
622 #[must_use]
628 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
629 self.meta = meta.into_option();
630 self
631 }
632}
633
634#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq)]
639#[schemars(inline)]
640#[schemars(transform = other_state_update_schema)]
641#[serde(rename_all = "camelCase")]
642#[non_exhaustive]
643pub struct OtherStateUpdate {
644 #[serde(rename = "state")]
650 pub state: String,
651 #[serde(flatten)]
653 pub fields: BTreeMap<String, serde_json::Value>,
654}
655
656impl OtherStateUpdate {
657 #[must_use]
659 pub fn new(state: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
660 fields.remove("state");
661 Self {
662 state: state.into(),
663 fields,
664 }
665 }
666}
667
668impl<'de> Deserialize<'de> for OtherStateUpdate {
669 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
670 where
671 D: serde::Deserializer<'de>,
672 {
673 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
674 let state = fields
675 .remove("state")
676 .ok_or_else(|| serde::de::Error::missing_field("state"))?;
677 let serde_json::Value::String(state) = state else {
678 return Err(serde::de::Error::custom("`state` must be a string"));
679 };
680
681 if is_known_state_update(&state) {
682 return Err(serde::de::Error::custom(format!(
683 "known state update `{state}` did not match its schema"
684 )));
685 }
686
687 Ok(Self { state, fields })
688 }
689}
690
691fn is_known_state_update(state: &str) -> bool {
692 matches!(state, "running" | "idle" | "requires_action")
693}
694
695fn other_state_update_schema(schema: &mut Schema) {
696 super::schema_util::reject_known_string_discriminators(
697 schema,
698 "state",
699 &["running", "idle", "requires_action"],
700 );
701}
702
703#[serde_as]
705#[skip_serializing_none]
706#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
707#[serde(rename_all = "camelCase")]
708#[non_exhaustive]
709pub struct Cost {
710 pub amount: f64,
712 #[schemars(pattern(r"^[A-Z]{3}$"))]
714 pub currency: String,
715 #[serde_as(deserialize_as = "DefaultOnError")]
721 #[schemars(extend("x-deserialize-default-on-error" = true))]
722 #[serde(default)]
723 #[serde(rename = "_meta")]
724 pub meta: Option<Meta>,
725}
726
727impl Cost {
728 #[must_use]
730 pub fn new(amount: f64, currency: impl Into<String>) -> Self {
731 Self {
732 amount,
733 currency: currency.into(),
734 meta: None,
735 }
736 }
737
738 #[must_use]
744 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
745 self.meta = meta.into_option();
746 self
747 }
748}
749
750#[serde_as]
752#[skip_serializing_none]
753#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
754#[serde(rename_all = "camelCase")]
755#[non_exhaustive]
756pub struct ContentChunk {
757 pub message_id: MessageId,
762 pub content: ContentBlock,
764 #[serde_as(deserialize_as = "DefaultOnError")]
770 #[schemars(extend("x-deserialize-default-on-error" = true))]
771 #[serde(default)]
772 #[serde(rename = "_meta")]
773 pub meta: Option<Meta>,
774}
775
776impl ContentChunk {
777 #[must_use]
779 pub fn new(content: ContentBlock, message_id: impl Into<MessageId>) -> Self {
780 Self {
781 content,
782 message_id: message_id.into(),
783 meta: None,
784 }
785 }
786
787 #[must_use]
793 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
794 self.meta = meta.into_option();
795 self
796 }
797}
798
799#[serde_as]
813#[skip_serializing_none]
814#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
815#[serde(rename_all = "camelCase")]
816#[non_exhaustive]
817pub struct UserMessage {
818 pub message_id: MessageId,
820 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<VecSkipError<_, SkipListener>>>")]
822 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
823 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
824 pub content: MaybeUndefined<Vec<ContentBlock>>,
825 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
831 #[schemars(extend("x-deserialize-default-on-error" = true))]
832 #[serde(
833 rename = "_meta",
834 default,
835 skip_serializing_if = "MaybeUndefined::is_undefined"
836 )]
837 pub meta: MaybeUndefined<Meta>,
838}
839
840impl UserMessage {
841 #[must_use]
843 pub fn new(message_id: impl Into<MessageId>) -> Self {
844 Self {
845 message_id: message_id.into(),
846 content: MaybeUndefined::Undefined,
847 meta: MaybeUndefined::Undefined,
848 }
849 }
850
851 #[must_use]
853 pub fn content(mut self, content: impl IntoMaybeUndefined<Vec<ContentBlock>>) -> Self {
854 self.content = content.into_maybe_undefined();
855 self
856 }
857
858 #[must_use]
864 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
865 self.meta = meta.into_maybe_undefined();
866 self
867 }
868}
869
870#[serde_as]
884#[skip_serializing_none]
885#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
886#[serde(rename_all = "camelCase")]
887#[non_exhaustive]
888pub struct AgentMessage {
889 pub message_id: MessageId,
891 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<VecSkipError<_, SkipListener>>>")]
893 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
894 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
895 pub content: MaybeUndefined<Vec<ContentBlock>>,
896 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
902 #[schemars(extend("x-deserialize-default-on-error" = true))]
903 #[serde(
904 rename = "_meta",
905 default,
906 skip_serializing_if = "MaybeUndefined::is_undefined"
907 )]
908 pub meta: MaybeUndefined<Meta>,
909}
910
911impl AgentMessage {
912 #[must_use]
914 pub fn new(message_id: impl Into<MessageId>) -> Self {
915 Self {
916 message_id: message_id.into(),
917 content: MaybeUndefined::Undefined,
918 meta: MaybeUndefined::Undefined,
919 }
920 }
921
922 #[must_use]
924 pub fn content(mut self, content: impl IntoMaybeUndefined<Vec<ContentBlock>>) -> Self {
925 self.content = content.into_maybe_undefined();
926 self
927 }
928
929 #[must_use]
935 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
936 self.meta = meta.into_maybe_undefined();
937 self
938 }
939}
940
941#[serde_as]
955#[skip_serializing_none]
956#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
957#[serde(rename_all = "camelCase")]
958#[non_exhaustive]
959pub struct AgentThought {
960 pub message_id: MessageId,
962 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<VecSkipError<_, SkipListener>>>")]
964 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
965 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
966 pub content: MaybeUndefined<Vec<ContentBlock>>,
967 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
973 #[schemars(extend("x-deserialize-default-on-error" = true))]
974 #[serde(
975 rename = "_meta",
976 default,
977 skip_serializing_if = "MaybeUndefined::is_undefined"
978 )]
979 pub meta: MaybeUndefined<Meta>,
980}
981
982impl AgentThought {
983 #[must_use]
985 pub fn new(message_id: impl Into<MessageId>) -> Self {
986 Self {
987 message_id: message_id.into(),
988 content: MaybeUndefined::Undefined,
989 meta: MaybeUndefined::Undefined,
990 }
991 }
992
993 #[must_use]
995 pub fn content(mut self, content: impl IntoMaybeUndefined<Vec<ContentBlock>>) -> Self {
996 self.content = content.into_maybe_undefined();
997 self
998 }
999
1000 #[must_use]
1006 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
1007 self.meta = meta.into_maybe_undefined();
1008 self
1009 }
1010}
1011
1012#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash, Display, From)]
1014#[serde(transparent)]
1015#[from(Arc<str>, String, &'static str)]
1016#[non_exhaustive]
1017pub struct MessageId(pub Arc<str>);
1018
1019impl MessageId {
1020 #[must_use]
1022 pub fn new(id: impl Into<Arc<str>>) -> Self {
1023 Self(id.into())
1024 }
1025}
1026
1027#[serde_as]
1029#[skip_serializing_none]
1030#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1031#[serde(rename_all = "camelCase")]
1032#[non_exhaustive]
1033pub struct AvailableCommandsUpdate {
1034 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, SkipListener>>")]
1036 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
1037 pub available_commands: Vec<AvailableCommand>,
1038 #[serde_as(deserialize_as = "DefaultOnError")]
1044 #[schemars(extend("x-deserialize-default-on-error" = true))]
1045 #[serde(default)]
1046 #[serde(rename = "_meta")]
1047 pub meta: Option<Meta>,
1048}
1049
1050impl AvailableCommandsUpdate {
1051 #[must_use]
1053 pub fn new(available_commands: Vec<AvailableCommand>) -> Self {
1054 Self {
1055 available_commands,
1056 meta: None,
1057 }
1058 }
1059
1060 #[must_use]
1066 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1067 self.meta = meta.into_option();
1068 self
1069 }
1070}
1071
1072#[serde_as]
1074#[skip_serializing_none]
1075#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1076#[serde(rename_all = "camelCase")]
1077#[non_exhaustive]
1078pub struct AvailableCommand {
1079 pub name: String,
1081 pub description: String,
1083 #[serde_as(deserialize_as = "DefaultOnError")]
1085 #[schemars(extend("x-deserialize-default-on-error" = true))]
1086 #[serde(default)]
1087 pub input: Option<AvailableCommandInput>,
1088 #[serde_as(deserialize_as = "DefaultOnError")]
1094 #[schemars(extend("x-deserialize-default-on-error" = true))]
1095 #[serde(default)]
1096 #[serde(rename = "_meta")]
1097 pub meta: Option<Meta>,
1098}
1099
1100impl AvailableCommand {
1101 #[must_use]
1103 pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
1104 Self {
1105 name: name.into(),
1106 description: description.into(),
1107 input: None,
1108 meta: None,
1109 }
1110 }
1111
1112 #[must_use]
1114 pub fn input(mut self, input: impl IntoOption<AvailableCommandInput>) -> Self {
1115 self.input = input.into_option();
1116 self
1117 }
1118
1119 #[must_use]
1125 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1126 self.meta = meta.into_option();
1127 self
1128 }
1129}
1130
1131#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1133#[serde(tag = "type", rename_all = "snake_case")]
1134#[schemars(extend("discriminator" = {"propertyName": "type"}))]
1135#[non_exhaustive]
1136pub enum AvailableCommandInput {
1137 #[serde(rename = "text")]
1139 Text(TextCommandInput),
1140 #[serde(untagged)]
1151 Other(OtherAvailableCommandInput),
1152}
1153
1154#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq, Eq)]
1156#[schemars(inline)]
1157#[schemars(transform = other_available_command_input_schema)]
1158#[serde(rename_all = "camelCase")]
1159#[non_exhaustive]
1160pub struct OtherAvailableCommandInput {
1161 #[serde(rename = "type")]
1167 pub type_: String,
1168 #[serde(flatten)]
1170 pub fields: BTreeMap<String, serde_json::Value>,
1171}
1172
1173impl OtherAvailableCommandInput {
1174 #[must_use]
1176 pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
1177 fields.remove("type");
1178 Self {
1179 type_: type_.into(),
1180 fields,
1181 }
1182 }
1183}
1184
1185impl<'de> Deserialize<'de> for OtherAvailableCommandInput {
1186 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1187 where
1188 D: serde::Deserializer<'de>,
1189 {
1190 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1191 let type_ = fields
1192 .remove("type")
1193 .ok_or_else(|| serde::de::Error::missing_field("type"))?;
1194 let serde_json::Value::String(type_) = type_ else {
1195 return Err(serde::de::Error::custom("`type` must be a string"));
1196 };
1197
1198 if is_known_available_command_input_type(&type_) {
1199 return Err(serde::de::Error::custom(format!(
1200 "known available command input type `{type_}` did not match its schema"
1201 )));
1202 }
1203
1204 Ok(Self { type_, fields })
1205 }
1206}
1207
1208const KNOWN_AVAILABLE_COMMAND_INPUT_TYPES: &[&str] = &["text"];
1209
1210fn is_known_available_command_input_type(type_: &str) -> bool {
1211 KNOWN_AVAILABLE_COMMAND_INPUT_TYPES.contains(&type_)
1212}
1213
1214fn other_available_command_input_schema(schema: &mut Schema) {
1215 super::schema_util::reject_known_string_discriminators(
1216 schema,
1217 "type",
1218 KNOWN_AVAILABLE_COMMAND_INPUT_TYPES,
1219 );
1220}
1221
1222#[serde_as]
1224#[skip_serializing_none]
1225#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1226#[serde(rename_all = "camelCase")]
1227#[non_exhaustive]
1228pub struct TextCommandInput {
1229 pub hint: String,
1231 #[serde_as(deserialize_as = "DefaultOnError")]
1237 #[schemars(extend("x-deserialize-default-on-error" = true))]
1238 #[serde(default)]
1239 #[serde(rename = "_meta")]
1240 pub meta: Option<Meta>,
1241}
1242
1243impl TextCommandInput {
1244 #[must_use]
1246 pub fn new(hint: impl Into<String>) -> Self {
1247 Self {
1248 hint: hint.into(),
1249 meta: None,
1250 }
1251 }
1252
1253 #[must_use]
1259 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1260 self.meta = meta.into_option();
1261 self
1262 }
1263}
1264
1265#[serde_as]
1273#[skip_serializing_none]
1274#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1275#[schemars(extend("x-side" = "client", "x-method" = SESSION_REQUEST_PERMISSION_METHOD_NAME))]
1276#[serde(rename_all = "camelCase")]
1277#[non_exhaustive]
1278pub struct RequestPermissionRequest {
1279 pub session_id: SessionId,
1281 pub title: String,
1286 #[serde_as(deserialize_as = "DefaultOnError")]
1292 #[schemars(extend("x-deserialize-default-on-error" = true))]
1293 #[serde(default)]
1294 pub description: Option<String>,
1295 #[serde(default)]
1299 pub subject: Option<RequestPermissionSubject>,
1300 #[schemars(length(min = 1))]
1303 pub options: Vec<PermissionOption>,
1304 #[serde_as(deserialize_as = "DefaultOnError")]
1310 #[schemars(extend("x-deserialize-default-on-error" = true))]
1311 #[serde(default)]
1312 #[serde(rename = "_meta")]
1313 pub meta: Option<Meta>,
1314}
1315
1316impl RequestPermissionRequest {
1317 #[must_use]
1319 pub fn new(
1320 session_id: impl Into<SessionId>,
1321 title: impl Into<String>,
1322 options: Vec<PermissionOption>,
1323 ) -> Self {
1324 Self {
1325 session_id: session_id.into(),
1326 title: title.into(),
1327 description: None,
1328 subject: None,
1329 options,
1330 meta: None,
1331 }
1332 }
1333
1334 #[must_use]
1336 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
1337 self.description = description.into_option();
1338 self
1339 }
1340
1341 #[must_use]
1343 pub fn subject(mut self, subject: impl IntoOption<RequestPermissionSubject>) -> Self {
1344 self.subject = subject.into_option();
1345 self
1346 }
1347
1348 #[must_use]
1354 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1355 self.meta = meta.into_option();
1356 self
1357 }
1358}
1359
1360#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1362#[serde(tag = "type", rename_all = "snake_case")]
1363#[schemars(extend("discriminator" = {"propertyName": "type"}))]
1364#[non_exhaustive]
1365pub enum RequestPermissionSubject {
1366 ToolCall(Box<ToolCallPermissionSubject>),
1368 #[serde(untagged)]
1379 Other(OtherRequestPermissionSubject),
1380}
1381
1382impl From<ToolCallPermissionSubject> for RequestPermissionSubject {
1383 fn from(subject: ToolCallPermissionSubject) -> Self {
1384 Self::ToolCall(Box::new(subject))
1385 }
1386}
1387
1388impl From<ToolCallUpdate> for RequestPermissionSubject {
1389 fn from(tool_call: ToolCallUpdate) -> Self {
1390 ToolCallPermissionSubject::new(tool_call).into()
1391 }
1392}
1393
1394#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1396#[serde(rename_all = "camelCase")]
1397#[non_exhaustive]
1398pub struct ToolCallPermissionSubject {
1399 pub tool_call: ToolCallUpdate,
1401}
1402
1403impl ToolCallPermissionSubject {
1404 #[must_use]
1406 pub fn new(tool_call: ToolCallUpdate) -> Self {
1407 Self { tool_call }
1408 }
1409}
1410
1411#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq)]
1413#[schemars(inline)]
1414#[schemars(transform = other_request_permission_subject_schema)]
1415#[serde(rename_all = "camelCase")]
1416#[non_exhaustive]
1417pub struct OtherRequestPermissionSubject {
1418 #[serde(rename = "type")]
1424 pub type_: String,
1425 #[serde(flatten)]
1427 pub fields: BTreeMap<String, serde_json::Value>,
1428}
1429
1430impl OtherRequestPermissionSubject {
1431 #[must_use]
1433 pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
1434 fields.remove("type");
1435 Self {
1436 type_: type_.into(),
1437 fields,
1438 }
1439 }
1440}
1441
1442impl<'de> Deserialize<'de> for OtherRequestPermissionSubject {
1443 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1444 where
1445 D: serde::Deserializer<'de>,
1446 {
1447 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1448 let type_ = fields
1449 .remove("type")
1450 .ok_or_else(|| serde::de::Error::missing_field("type"))?;
1451 let serde_json::Value::String(type_) = type_ else {
1452 return Err(serde::de::Error::custom("`type` must be a string"));
1453 };
1454
1455 if is_known_request_permission_subject_type(&type_) {
1456 return Err(serde::de::Error::custom(format!(
1457 "known request permission subject `{type_}` did not match its schema"
1458 )));
1459 }
1460
1461 Ok(Self { type_, fields })
1462 }
1463}
1464
1465fn is_known_request_permission_subject_type(type_: &str) -> bool {
1466 matches!(type_, "tool_call")
1467}
1468
1469fn other_request_permission_subject_schema(schema: &mut Schema) {
1470 super::schema_util::reject_known_string_discriminators(schema, "type", &["tool_call"]);
1471}
1472
1473#[serde_as]
1475#[skip_serializing_none]
1476#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1477#[serde(rename_all = "camelCase")]
1478#[non_exhaustive]
1479pub struct PermissionOption {
1480 pub option_id: PermissionOptionId,
1482 pub name: String,
1484 pub kind: PermissionOptionKind,
1486 #[serde_as(deserialize_as = "DefaultOnError")]
1492 #[schemars(extend("x-deserialize-default-on-error" = true))]
1493 #[serde(default)]
1494 #[serde(rename = "_meta")]
1495 pub meta: Option<Meta>,
1496}
1497
1498impl PermissionOption {
1499 #[must_use]
1501 pub fn new(
1502 option_id: impl Into<PermissionOptionId>,
1503 name: impl Into<String>,
1504 kind: PermissionOptionKind,
1505 ) -> Self {
1506 Self {
1507 option_id: option_id.into(),
1508 name: name.into(),
1509 kind,
1510 meta: None,
1511 }
1512 }
1513
1514 #[must_use]
1520 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1521 self.meta = meta.into_option();
1522 self
1523 }
1524}
1525
1526#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash, Display, From)]
1528#[serde(transparent)]
1529#[from(Arc<str>, String, &'static str)]
1530#[non_exhaustive]
1531pub struct PermissionOptionId(pub Arc<str>);
1532
1533impl PermissionOptionId {
1534 #[must_use]
1536 pub fn new(id: impl Into<Arc<str>>) -> Self {
1537 Self(id.into())
1538 }
1539}
1540
1541#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1545#[serde(rename_all = "snake_case")]
1546#[non_exhaustive]
1547pub enum PermissionOptionKind {
1548 AllowOnce,
1550 AllowAlways,
1552 RejectOnce,
1554 RejectAlways,
1556 #[serde(untagged)]
1562 Other(String),
1563}
1564
1565#[serde_as]
1567#[skip_serializing_none]
1568#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1569#[schemars(extend("x-side" = "client", "x-method" = SESSION_REQUEST_PERMISSION_METHOD_NAME))]
1570#[serde(rename_all = "camelCase")]
1571#[non_exhaustive]
1572pub struct RequestPermissionResponse {
1573 pub outcome: RequestPermissionOutcome,
1575 #[serde_as(deserialize_as = "DefaultOnError")]
1581 #[schemars(extend("x-deserialize-default-on-error" = true))]
1582 #[serde(default)]
1583 #[serde(rename = "_meta")]
1584 pub meta: Option<Meta>,
1585}
1586
1587impl RequestPermissionResponse {
1588 #[must_use]
1590 pub fn new(outcome: RequestPermissionOutcome) -> Self {
1591 Self {
1592 outcome,
1593 meta: None,
1594 }
1595 }
1596
1597 #[must_use]
1603 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1604 self.meta = meta.into_option();
1605 self
1606 }
1607}
1608
1609#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1611#[serde(tag = "outcome", rename_all = "snake_case")]
1612#[schemars(extend("discriminator" = {"propertyName": "outcome"}))]
1613#[non_exhaustive]
1614pub enum RequestPermissionOutcome {
1615 Cancelled,
1623 #[serde(rename_all = "camelCase")]
1625 Selected(SelectedPermissionOutcome),
1626 #[serde(untagged)]
1637 Other(OtherRequestPermissionOutcome),
1638}
1639
1640#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq, Eq)]
1646#[schemars(inline)]
1647#[schemars(transform = other_request_permission_outcome_schema)]
1648#[serde(rename_all = "camelCase")]
1649#[non_exhaustive]
1650pub struct OtherRequestPermissionOutcome {
1651 pub outcome: String,
1657 #[serde(flatten)]
1659 pub fields: BTreeMap<String, serde_json::Value>,
1660}
1661
1662impl OtherRequestPermissionOutcome {
1663 #[must_use]
1665 pub fn new(
1666 outcome: impl Into<String>,
1667 mut fields: BTreeMap<String, serde_json::Value>,
1668 ) -> Self {
1669 fields.remove("outcome");
1670 Self {
1671 outcome: outcome.into(),
1672 fields,
1673 }
1674 }
1675}
1676
1677impl<'de> Deserialize<'de> for OtherRequestPermissionOutcome {
1678 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1679 where
1680 D: serde::Deserializer<'de>,
1681 {
1682 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1683 let outcome = fields
1684 .remove("outcome")
1685 .ok_or_else(|| serde::de::Error::missing_field("outcome"))?;
1686 let serde_json::Value::String(outcome) = outcome else {
1687 return Err(serde::de::Error::custom("`outcome` must be a string"));
1688 };
1689
1690 if is_known_request_permission_outcome(&outcome) {
1691 return Err(serde::de::Error::custom(format!(
1692 "known request permission outcome `{outcome}` did not match its schema"
1693 )));
1694 }
1695
1696 Ok(Self { outcome, fields })
1697 }
1698}
1699
1700fn is_known_request_permission_outcome(outcome: &str) -> bool {
1701 matches!(outcome, "cancelled" | "selected")
1702}
1703
1704fn other_request_permission_outcome_schema(schema: &mut Schema) {
1705 super::schema_util::reject_known_string_discriminators(
1706 schema,
1707 "outcome",
1708 &["cancelled", "selected"],
1709 );
1710}
1711
1712#[serde_as]
1714#[skip_serializing_none]
1715#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1716#[serde(rename_all = "camelCase")]
1717#[non_exhaustive]
1718pub struct SelectedPermissionOutcome {
1719 pub option_id: PermissionOptionId,
1721 #[serde_as(deserialize_as = "DefaultOnError")]
1727 #[schemars(extend("x-deserialize-default-on-error" = true))]
1728 #[serde(default)]
1729 #[serde(rename = "_meta")]
1730 pub meta: Option<Meta>,
1731}
1732
1733impl SelectedPermissionOutcome {
1734 #[must_use]
1736 pub fn new(option_id: impl Into<PermissionOptionId>) -> Self {
1737 Self {
1738 option_id: option_id.into(),
1739 meta: None,
1740 }
1741 }
1742
1743 #[must_use]
1749 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1750 self.meta = meta.into_option();
1751 self
1752 }
1753}
1754
1755#[serde_as]
1764#[skip_serializing_none]
1765#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1766#[serde(rename_all = "camelCase")]
1767#[non_exhaustive]
1768pub struct ClientCapabilities {
1769 #[cfg(feature = "unstable_auth_methods")]
1780 #[serde_as(deserialize_as = "DefaultOnError")]
1781 #[schemars(extend("x-deserialize-default-on-error" = true))]
1782 #[serde(default)]
1783 pub auth: Option<AuthCapabilities>,
1784 #[cfg(feature = "unstable_elicitation")]
1794 #[serde_as(deserialize_as = "DefaultOnError")]
1795 #[schemars(extend("x-deserialize-default-on-error" = true))]
1796 #[serde(default)]
1797 pub elicitation: Option<ElicitationCapabilities>,
1798 #[cfg(feature = "unstable_nes")]
1807 #[serde_as(deserialize_as = "DefaultOnError")]
1808 #[schemars(extend("x-deserialize-default-on-error" = true))]
1809 #[serde(default)]
1810 pub nes: Option<ClientNesCapabilities>,
1811 #[cfg(feature = "unstable_nes")]
1817 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, SkipListener>>")]
1818 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
1819 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1820 pub position_encodings: Vec<PositionEncodingKind>,
1821
1822 #[serde_as(deserialize_as = "DefaultOnError")]
1828 #[schemars(extend("x-deserialize-default-on-error" = true))]
1829 #[serde(default)]
1830 #[serde(rename = "_meta")]
1831 pub meta: Option<Meta>,
1832}
1833
1834impl ClientCapabilities {
1835 #[must_use]
1837 pub fn new() -> Self {
1838 Self::default()
1839 }
1840
1841 #[cfg(feature = "unstable_auth_methods")]
1849 #[must_use]
1850 pub fn auth(mut self, auth: impl IntoOption<AuthCapabilities>) -> Self {
1851 self.auth = auth.into_option();
1852 self
1853 }
1854
1855 #[cfg(feature = "unstable_elicitation")]
1862 #[must_use]
1863 pub fn elicitation(mut self, elicitation: impl IntoOption<ElicitationCapabilities>) -> Self {
1864 self.elicitation = elicitation.into_option();
1865 self
1866 }
1867
1868 #[cfg(feature = "unstable_nes")]
1872 #[must_use]
1873 pub fn nes(mut self, nes: impl IntoOption<ClientNesCapabilities>) -> Self {
1874 self.nes = nes.into_option();
1875 self
1876 }
1877
1878 #[cfg(feature = "unstable_nes")]
1882 #[must_use]
1883 pub fn position_encodings(mut self, position_encodings: Vec<PositionEncodingKind>) -> Self {
1884 self.position_encodings = position_encodings;
1885 self
1886 }
1887
1888 #[must_use]
1894 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1895 self.meta = meta.into_option();
1896 self
1897 }
1898}
1899
1900#[cfg(feature = "unstable_auth_methods")]
1910#[serde_as]
1911#[skip_serializing_none]
1912#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1913#[serde(rename_all = "camelCase")]
1914#[non_exhaustive]
1915pub struct AuthCapabilities {
1916 #[serde_as(deserialize_as = "DefaultOnError")]
1921 #[schemars(extend("x-deserialize-default-on-error" = true))]
1922 #[serde(default)]
1923 pub terminal: Option<TerminalAuthCapabilities>,
1924 #[serde_as(deserialize_as = "DefaultOnError")]
1930 #[schemars(extend("x-deserialize-default-on-error" = true))]
1931 #[serde(default)]
1932 #[serde(rename = "_meta")]
1933 pub meta: Option<Meta>,
1934}
1935
1936#[cfg(feature = "unstable_auth_methods")]
1937impl AuthCapabilities {
1938 #[must_use]
1940 pub fn new() -> Self {
1941 Self::default()
1942 }
1943
1944 #[must_use]
1949 pub fn terminal(mut self, terminal: impl IntoOption<TerminalAuthCapabilities>) -> Self {
1950 self.terminal = terminal.into_option();
1951 self
1952 }
1953
1954 #[must_use]
1960 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1961 self.meta = meta.into_option();
1962 self
1963 }
1964}
1965
1966#[cfg(feature = "unstable_auth_methods")]
1974#[serde_as]
1975#[skip_serializing_none]
1976#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1977#[non_exhaustive]
1978pub struct TerminalAuthCapabilities {
1979 #[serde_as(deserialize_as = "DefaultOnError")]
1985 #[schemars(extend("x-deserialize-default-on-error" = true))]
1986 #[serde(default)]
1987 #[serde(rename = "_meta")]
1988 pub meta: Option<Meta>,
1989}
1990
1991#[cfg(feature = "unstable_auth_methods")]
1992impl TerminalAuthCapabilities {
1993 #[must_use]
1995 pub fn new() -> Self {
1996 Self::default()
1997 }
1998
1999 #[must_use]
2005 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2006 self.meta = meta.into_option();
2007 self
2008 }
2009}
2010
2011#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2017#[non_exhaustive]
2018pub struct ClientMethodNames {
2019 pub session_request_permission: &'static str,
2021 pub session_update: &'static str,
2023 #[cfg(feature = "unstable_mcp_over_acp")]
2025 pub mcp_connect: &'static str,
2026 #[cfg(feature = "unstable_mcp_over_acp")]
2028 pub mcp_message: &'static str,
2029 #[cfg(feature = "unstable_mcp_over_acp")]
2031 pub mcp_disconnect: &'static str,
2032 #[cfg(feature = "unstable_elicitation")]
2034 pub elicitation_create: &'static str,
2035 #[cfg(feature = "unstable_elicitation")]
2037 pub elicitation_complete: &'static str,
2038}
2039
2040pub const CLIENT_METHOD_NAMES: ClientMethodNames = ClientMethodNames {
2042 session_update: SESSION_UPDATE_NOTIFICATION,
2043 session_request_permission: SESSION_REQUEST_PERMISSION_METHOD_NAME,
2044 #[cfg(feature = "unstable_mcp_over_acp")]
2045 mcp_connect: MCP_CONNECT_METHOD_NAME,
2046 #[cfg(feature = "unstable_mcp_over_acp")]
2047 mcp_message: MCP_MESSAGE_METHOD_NAME,
2048 #[cfg(feature = "unstable_mcp_over_acp")]
2049 mcp_disconnect: MCP_DISCONNECT_METHOD_NAME,
2050 #[cfg(feature = "unstable_elicitation")]
2051 elicitation_create: ELICITATION_CREATE_METHOD_NAME,
2052 #[cfg(feature = "unstable_elicitation")]
2053 elicitation_complete: ELICITATION_COMPLETE_NOTIFICATION,
2054};
2055
2056pub(crate) const SESSION_UPDATE_NOTIFICATION: &str = "session/update";
2058pub(crate) const SESSION_REQUEST_PERMISSION_METHOD_NAME: &str = "session/request_permission";
2060#[cfg(feature = "unstable_elicitation")]
2062pub(crate) const ELICITATION_CREATE_METHOD_NAME: &str = "elicitation/create";
2063#[cfg(feature = "unstable_elicitation")]
2065pub(crate) const ELICITATION_COMPLETE_NOTIFICATION: &str = "elicitation/complete";
2066
2067#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
2074#[serde(untagged)]
2075#[schemars(inline)]
2076#[non_exhaustive]
2077pub enum AgentRequest {
2078 RequestPermissionRequest(Box<RequestPermissionRequest>),
2089 #[cfg(feature = "unstable_elicitation")]
2095 CreateElicitationRequest(Box<CreateElicitationRequest>),
2096 #[cfg(feature = "unstable_mcp_over_acp")]
2102 ConnectMcpRequest(Box<ConnectMcpRequest>),
2103 #[cfg(feature = "unstable_mcp_over_acp")]
2109 MessageMcpRequest(Box<MessageMcpRequest>),
2110 #[cfg(feature = "unstable_mcp_over_acp")]
2116 DisconnectMcpRequest(Box<DisconnectMcpRequest>),
2117 ExtMethodRequest(Box<ExtRequest>),
2125}
2126
2127impl AgentRequest {
2128 #[must_use]
2130 pub fn method(&self) -> &str {
2131 match self {
2132 Self::RequestPermissionRequest(_) => CLIENT_METHOD_NAMES.session_request_permission,
2133 #[cfg(feature = "unstable_elicitation")]
2134 Self::CreateElicitationRequest(_) => CLIENT_METHOD_NAMES.elicitation_create,
2135 #[cfg(feature = "unstable_mcp_over_acp")]
2136 Self::ConnectMcpRequest(_) => CLIENT_METHOD_NAMES.mcp_connect,
2137 #[cfg(feature = "unstable_mcp_over_acp")]
2138 Self::MessageMcpRequest(_) => CLIENT_METHOD_NAMES.mcp_message,
2139 #[cfg(feature = "unstable_mcp_over_acp")]
2140 Self::DisconnectMcpRequest(_) => CLIENT_METHOD_NAMES.mcp_disconnect,
2141 Self::ExtMethodRequest(ext_request) => &ext_request.method,
2142 }
2143 }
2144}
2145
2146#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
2153#[serde(untagged)]
2154#[schemars(inline)]
2155#[non_exhaustive]
2156pub enum ClientResponse {
2157 RequestPermissionResponse(Box<RequestPermissionResponse>),
2159 #[cfg(feature = "unstable_elicitation")]
2161 CreateElicitationResponse(Box<CreateElicitationResponse>),
2162 #[cfg(feature = "unstable_mcp_over_acp")]
2164 ConnectMcpResponse(Box<ConnectMcpResponse>),
2165 #[cfg(feature = "unstable_mcp_over_acp")]
2167 DisconnectMcpResponse(#[serde(default)] Box<DisconnectMcpResponse>),
2168 #[cfg(feature = "unstable_mcp_over_acp")]
2170 MessageMcpResponse(Box<MessageMcpResponse>),
2171 ExtMethodResponse(Box<ExtResponse>),
2173}
2174
2175#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
2182#[serde(untagged)]
2183#[schemars(inline)]
2184#[non_exhaustive]
2185pub enum AgentNotification {
2186 UpdateSessionNotification(Box<UpdateSessionNotification>),
2199 #[cfg(feature = "unstable_elicitation")]
2205 CompleteElicitationNotification(Box<CompleteElicitationNotification>),
2206 #[cfg(feature = "unstable_mcp_over_acp")]
2212 MessageMcpNotification(Box<MessageMcpNotification>),
2213 ExtNotification(Box<ExtNotification>),
2221}
2222
2223impl AgentNotification {
2224 #[must_use]
2226 pub fn method(&self) -> &str {
2227 match self {
2228 Self::UpdateSessionNotification(_) => CLIENT_METHOD_NAMES.session_update,
2229 #[cfg(feature = "unstable_elicitation")]
2230 Self::CompleteElicitationNotification(_) => CLIENT_METHOD_NAMES.elicitation_complete,
2231 #[cfg(feature = "unstable_mcp_over_acp")]
2232 Self::MessageMcpNotification(_) => CLIENT_METHOD_NAMES.mcp_message,
2233 Self::ExtNotification(ext_notification) => &ext_notification.method,
2234 }
2235 }
2236}
2237
2238#[cfg(test)]
2239mod tests {
2240 use super::*;
2241
2242 #[cfg(feature = "unstable_auth_methods")]
2243 #[test]
2244 fn test_client_capabilities_auth_defaults_on_malformed_value() {
2245 use serde_json::json;
2246
2247 let capabilities: ClientCapabilities = serde_json::from_value(json!({
2248 "auth": false
2249 }))
2250 .unwrap();
2251
2252 assert_eq!(capabilities.auth, None);
2253 }
2254
2255 #[test]
2256 fn test_serialization_behavior() {
2257 use serde_json::json;
2258
2259 assert_eq!(
2260 serde_json::from_value::<SessionInfoUpdate>(json!({})).unwrap(),
2261 SessionInfoUpdate {
2262 title: MaybeUndefined::Undefined,
2263 updated_at: MaybeUndefined::Undefined,
2264 meta: MaybeUndefined::Undefined
2265 }
2266 );
2267 assert_eq!(
2268 serde_json::from_value::<SessionInfoUpdate>(json!({"title": null, "updatedAt": null}))
2269 .unwrap(),
2270 SessionInfoUpdate {
2271 title: MaybeUndefined::Null,
2272 updated_at: MaybeUndefined::Null,
2273 meta: MaybeUndefined::Undefined
2274 }
2275 );
2276 assert_eq!(
2277 serde_json::from_value::<SessionInfoUpdate>(
2278 json!({"title": "title", "updatedAt": "timestamp"})
2279 )
2280 .unwrap(),
2281 SessionInfoUpdate {
2282 title: MaybeUndefined::Value("title".to_string()),
2283 updated_at: MaybeUndefined::Value("timestamp".to_string()),
2284 meta: MaybeUndefined::Undefined
2285 }
2286 );
2287
2288 let clear_meta =
2289 serde_json::from_value::<SessionInfoUpdate>(json!({"_meta": null})).unwrap();
2290 assert_eq!(clear_meta.meta, MaybeUndefined::Null);
2291
2292 let mut meta = Meta::new();
2293 meta.insert("source".to_string(), json!("session-info"));
2294
2295 assert_eq!(
2296 serde_json::from_value::<SessionInfoUpdate>(json!({"_meta": {
2297 "source": "session-info"
2298 }}))
2299 .unwrap()
2300 .meta,
2301 MaybeUndefined::Value(meta.clone())
2302 );
2303
2304 assert_eq!(
2305 serde_json::to_value(SessionInfoUpdate::new()).unwrap(),
2306 json!({})
2307 );
2308
2309 assert_eq!(
2310 serde_json::to_value(SessionInfoUpdate::new().meta(None::<Meta>)).unwrap(),
2311 json!({"_meta": null})
2312 );
2313
2314 assert_eq!(
2315 serde_json::to_value(SessionInfoUpdate::new().meta(meta)).unwrap(),
2316 json!({"_meta": {
2317 "source": "session-info"
2318 }})
2319 );
2320 assert_eq!(
2321 serde_json::to_value(SessionInfoUpdate::new().title("title")).unwrap(),
2322 json!({"title": "title"})
2323 );
2324 assert_eq!(
2325 serde_json::to_value(SessionInfoUpdate::new().title(None)).unwrap(),
2326 json!({"title": null})
2327 );
2328 assert_eq!(
2329 serde_json::to_value(
2330 SessionInfoUpdate::new()
2331 .title("title")
2332 .title(MaybeUndefined::Undefined)
2333 )
2334 .unwrap(),
2335 json!({})
2336 );
2337 }
2338
2339 #[test]
2340 fn test_content_chunk_message_id_serialization() {
2341 use serde_json::json;
2342
2343 assert_eq!(
2344 serde_json::to_value(SessionUpdate::AgentMessageChunk(ContentChunk::new(
2345 ContentBlock::Text(crate::v2::TextContent::new("Hello")),
2346 "msg_agent_c42b9",
2347 )))
2348 .unwrap(),
2349 json!({
2350 "sessionUpdate": "agent_message_chunk",
2351 "messageId": "msg_agent_c42b9",
2352 "content": {
2353 "type": "text",
2354 "text": "Hello"
2355 }
2356 })
2357 );
2358
2359 let err = serde_json::from_value::<ContentChunk>(json!({
2360 "content": {
2361 "type": "text",
2362 "text": "Hello"
2363 }
2364 }))
2365 .unwrap_err();
2366
2367 assert!(err.to_string().contains("messageId"), "{err}");
2368 }
2369
2370 #[test]
2371 fn test_tool_call_content_chunk_serialization() {
2372 use serde_json::json;
2373
2374 assert_eq!(
2375 serde_json::to_value(SessionUpdate::ToolCallContentChunk(
2376 ToolCallContentChunk::new(
2377 "call_001",
2378 crate::v2::ContentBlock::Text(crate::v2::TextContent::new("partial output")),
2379 )
2380 ))
2381 .unwrap(),
2382 json!({
2383 "sessionUpdate": "tool_call_content_chunk",
2384 "toolCallId": "call_001",
2385 "content": {
2386 "type": "content",
2387 "content": {
2388 "type": "text",
2389 "text": "partial output"
2390 }
2391 }
2392 })
2393 );
2394
2395 let err = serde_json::from_value::<ToolCallContentChunk>(json!({
2396 "content": {
2397 "type": "content",
2398 "content": {
2399 "type": "text",
2400 "text": "partial output"
2401 }
2402 }
2403 }))
2404 .unwrap_err();
2405
2406 assert!(err.to_string().contains("toolCallId"), "{err}");
2407 }
2408
2409 #[test]
2410 fn test_full_message_serialization() {
2411 use serde_json::json;
2412
2413 assert_eq!(
2414 serde_json::to_value(SessionUpdate::UserMessage(
2415 UserMessage::new("msg_user_8f7a1").content(vec![ContentBlock::Text(
2416 crate::v2::TextContent::new("Hello")
2417 )])
2418 ))
2419 .unwrap(),
2420 json!({
2421 "sessionUpdate": "user_message",
2422 "messageId": "msg_user_8f7a1",
2423 "content": [
2424 {
2425 "type": "text",
2426 "text": "Hello"
2427 }
2428 ]
2429 })
2430 );
2431
2432 assert_eq!(
2433 serde_json::to_value(SessionUpdate::AgentMessage(
2434 AgentMessage::new("msg_agent_c42b9").content(vec![ContentBlock::Text(
2435 crate::v2::TextContent::new("Hello")
2436 )])
2437 ))
2438 .unwrap(),
2439 json!({
2440 "sessionUpdate": "agent_message",
2441 "messageId": "msg_agent_c42b9",
2442 "content": [
2443 {
2444 "type": "text",
2445 "text": "Hello"
2446 }
2447 ]
2448 })
2449 );
2450
2451 assert_eq!(
2452 serde_json::to_value(SessionUpdate::AgentThought(
2453 AgentThought::new("msg_thought_a12").content(vec![ContentBlock::Text(
2454 crate::v2::TextContent::new("Need to inspect the call sites first.")
2455 )])
2456 ))
2457 .unwrap(),
2458 json!({
2459 "sessionUpdate": "agent_thought",
2460 "messageId": "msg_thought_a12",
2461 "content": [
2462 {
2463 "type": "text",
2464 "text": "Need to inspect the call sites first."
2465 }
2466 ]
2467 })
2468 );
2469 }
2470
2471 #[test]
2472 fn test_message_upsert_serialization() {
2473 use serde_json::json;
2474
2475 assert_eq!(
2476 serde_json::to_value(SessionUpdate::UserMessage(
2477 UserMessage::new("msg_empty").content(Vec::<ContentBlock>::new())
2478 ))
2479 .unwrap(),
2480 json!({
2481 "sessionUpdate": "user_message",
2482 "messageId": "msg_empty",
2483 "content": []
2484 })
2485 );
2486
2487 let empty = serde_json::from_value::<UserMessage>(json!({
2488 "messageId": "msg_empty",
2489 "content": []
2490 }))
2491 .unwrap();
2492 assert!(matches!(
2493 empty.content,
2494 MaybeUndefined::Value(ref content) if content.is_empty()
2495 ));
2496
2497 let patch = serde_json::from_value::<AgentMessage>(json!({
2498 "messageId": "msg_agent_c42b9"
2499 }))
2500 .unwrap();
2501 assert_eq!(patch.content, MaybeUndefined::Undefined);
2502 assert_eq!(patch.meta, MaybeUndefined::Undefined);
2503
2504 let malformed_meta = serde_json::from_value::<AgentMessage>(json!({
2505 "messageId": "msg_agent_c42b9",
2506 "_meta": false
2507 }))
2508 .unwrap();
2509 assert_eq!(malformed_meta.meta, MaybeUndefined::Undefined);
2510
2511 let patch = serde_json::from_value::<AgentThought>(json!({
2512 "messageId": "msg_thought_a12"
2513 }))
2514 .unwrap();
2515 assert_eq!(patch.content, MaybeUndefined::Undefined);
2516
2517 let clear = serde_json::from_value::<UserMessage>(json!({
2518 "messageId": "msg_user_8f7a1",
2519 "content": null
2520 }))
2521 .unwrap();
2522 assert_eq!(clear.content, MaybeUndefined::Null);
2523
2524 let clear_meta = serde_json::from_value::<UserMessage>(json!({
2525 "messageId": "msg_user_8f7a1",
2526 "_meta": null
2527 }))
2528 .unwrap();
2529 assert_eq!(clear_meta.meta, MaybeUndefined::Null);
2530
2531 let mut meta = Meta::new();
2532 meta.insert("source".to_string(), json!("replay"));
2533
2534 assert_eq!(
2535 serde_json::to_value(SessionUpdate::UserMessage(
2536 UserMessage::new("msg_user_8f7a1").meta(meta)
2537 ))
2538 .unwrap(),
2539 json!({
2540 "sessionUpdate": "user_message",
2541 "messageId": "msg_user_8f7a1",
2542 "_meta": {
2543 "source": "replay"
2544 }
2545 })
2546 );
2547
2548 assert_eq!(
2549 serde_json::to_value(SessionUpdate::UserMessage(
2550 UserMessage::new("msg_user_8f7a1").meta(None::<Meta>)
2551 ))
2552 .unwrap(),
2553 json!({
2554 "sessionUpdate": "user_message",
2555 "messageId": "msg_user_8f7a1",
2556 "_meta": null
2557 })
2558 );
2559 }
2560
2561 #[test]
2562 fn test_usage_update_serialization() {
2563 use serde_json::json;
2564
2565 assert_eq!(
2566 serde_json::to_value(SessionUpdate::UsageUpdate(UsageUpdate::new(
2567 53_000, 200_000
2568 )))
2569 .unwrap(),
2570 json!({
2571 "sessionUpdate": "usage_update",
2572 "used": 53000,
2573 "size": 200_000
2574 })
2575 );
2576
2577 assert_eq!(
2578 serde_json::to_value(SessionUpdate::UsageUpdate(
2579 UsageUpdate::new(53_000, 200_000).cost(Cost::new(0.045, "USD"))
2580 ))
2581 .unwrap(),
2582 json!({
2583 "sessionUpdate": "usage_update",
2584 "used": 53000,
2585 "size": 200_000,
2586 "cost": {
2587 "amount": 0.045,
2588 "currency": "USD"
2589 }
2590 })
2591 );
2592
2593 let SessionUpdate::UsageUpdate(update) = serde_json::from_value(json!({
2594 "sessionUpdate": "usage_update",
2595 "used": 53000,
2596 "size": 200_000,
2597 "cost": null
2598 }))
2599 .unwrap() else {
2600 panic!("expected usage update");
2601 };
2602
2603 assert_eq!(update.cost, None);
2604 }
2605
2606 #[test]
2607 fn test_state_update_serialization() {
2608 use serde_json::json;
2609
2610 assert_eq!(
2611 serde_json::to_value(SessionUpdate::StateUpdate(StateUpdate::Running(
2612 RunningStateUpdate::new()
2613 )))
2614 .unwrap(),
2615 json!({
2616 "sessionUpdate": "state_update",
2617 "state": "running"
2618 })
2619 );
2620
2621 assert_eq!(
2622 serde_json::to_value(SessionUpdate::StateUpdate(StateUpdate::Idle(
2623 IdleStateUpdate::new().stop_reason(StopReason::EndTurn)
2624 )))
2625 .unwrap(),
2626 json!({
2627 "sessionUpdate": "state_update",
2628 "state": "idle",
2629 "stopReason": "end_turn"
2630 })
2631 );
2632
2633 let SessionUpdate::StateUpdate(update) = serde_json::from_value(json!({
2634 "sessionUpdate": "state_update",
2635 "state": "requires_action"
2636 }))
2637 .unwrap() else {
2638 panic!("expected state update");
2639 };
2640
2641 assert!(matches!(update, StateUpdate::RequiresAction(_)));
2642
2643 let SessionUpdate::StateUpdate(StateUpdate::Idle(update)) = serde_json::from_value(json!({
2644 "sessionUpdate": "state_update",
2645 "state": "idle",
2646 "stopReason": null
2647 }))
2648 .unwrap() else {
2649 panic!("expected idle state update");
2650 };
2651
2652 assert_eq!(update.stop_reason, None);
2653
2654 let SessionUpdate::StateUpdate(StateUpdate::Other(update)) =
2655 serde_json::from_value(json!({
2656 "sessionUpdate": "state_update",
2657 "state": "_paused",
2658 "label": "Paused"
2659 }))
2660 .unwrap()
2661 else {
2662 panic!("expected unknown state update");
2663 };
2664
2665 assert_eq!(update.state, "_paused");
2666 assert_eq!(update.fields["label"], json!("Paused"));
2667 }
2668
2669 #[test]
2670 fn session_update_preserves_unknown_variant() {
2671 use serde_json::json;
2672
2673 let update: SessionUpdate = serde_json::from_value(json!({
2674 "sessionUpdate": "_status_badge",
2675 "label": "Indexing",
2676 "progress": 0.5
2677 }))
2678 .unwrap();
2679
2680 let SessionUpdate::Other(unknown) = update else {
2681 panic!("expected unknown session update");
2682 };
2683
2684 assert_eq!(unknown.session_update, "_status_badge");
2685 assert_eq!(unknown.fields.get("label"), Some(&json!("Indexing")));
2686 assert_eq!(unknown.fields.get("progress"), Some(&json!(0.5)));
2687
2688 assert_eq!(
2689 serde_json::to_value(SessionUpdate::Other(unknown)).unwrap(),
2690 json!({
2691 "sessionUpdate": "_status_badge",
2692 "label": "Indexing",
2693 "progress": 0.5
2694 })
2695 );
2696 }
2697
2698 #[test]
2699 fn test_plan_update_serialization() {
2700 use serde_json::json;
2701
2702 let plan_update =
2703 SessionUpdate::PlanUpdate(PlanUpdate::new(crate::v2::PlanUpdateContent::items(
2704 "plan-1",
2705 vec![crate::v2::PlanEntry::new(
2706 "Step 1",
2707 crate::v2::PlanEntryPriority::High,
2708 crate::v2::PlanEntryStatus::Pending,
2709 )],
2710 )));
2711
2712 assert_eq!(
2713 serde_json::to_value(plan_update).unwrap(),
2714 json!({
2715 "sessionUpdate": "plan_update",
2716 "plan": {
2717 "type": "items",
2718 "planId": "plan-1",
2719 "entries": [
2720 {
2721 "content": "Step 1",
2722 "priority": "high",
2723 "status": "pending"
2724 }
2725 ]
2726 }
2727 })
2728 );
2729 }
2730
2731 #[cfg(feature = "unstable_plan_operations")]
2732 #[test]
2733 fn test_plan_removed_serialization() {
2734 use serde_json::json;
2735
2736 assert_eq!(
2737 serde_json::to_value(SessionUpdate::PlanRemoved(PlanRemoved::new("plan-1"))).unwrap(),
2738 json!({
2739 "sessionUpdate": "plan_removed",
2740 "planId": "plan-1"
2741 })
2742 );
2743 }
2744
2745 #[test]
2746 fn available_command_input_preserves_unknown_typed_variant() {
2747 use serde_json::json;
2748
2749 let input: AvailableCommandInput = serde_json::from_value(json!({
2750 "type": "_choices",
2751 "hint": "Pick one",
2752 "options": ["fast", "careful"]
2753 }))
2754 .unwrap();
2755
2756 let AvailableCommandInput::Other(unknown) = input else {
2757 panic!("expected unknown command input");
2758 };
2759
2760 assert_eq!(unknown.type_, "_choices");
2761 assert_eq!(unknown.fields.get("hint"), Some(&json!("Pick one")));
2762 assert_eq!(
2763 unknown.fields.get("options"),
2764 Some(&json!(["fast", "careful"]))
2765 );
2766 assert_eq!(
2767 serde_json::to_value(AvailableCommandInput::Other(unknown)).unwrap(),
2768 json!({
2769 "type": "_choices",
2770 "hint": "Pick one",
2771 "options": ["fast", "careful"]
2772 })
2773 );
2774 }
2775
2776 #[test]
2777 fn available_command_input_text_uses_type_discriminator() {
2778 use serde_json::json;
2779
2780 let input = AvailableCommandInput::Text(TextCommandInput::new("Describe changes"));
2781
2782 let json = serde_json::to_value(&input).unwrap();
2783 assert_eq!(
2784 json,
2785 json!({
2786 "type": "text",
2787 "hint": "Describe changes"
2788 })
2789 );
2790
2791 let roundtripped: AvailableCommandInput = serde_json::from_value(json).unwrap();
2792 assert!(matches!(roundtripped, AvailableCommandInput::Text(_)));
2793 }
2794
2795 #[test]
2796 fn request_permission_subject_tool_call_uses_type_discriminator() {
2797 use serde_json::json;
2798
2799 let subject = RequestPermissionSubject::from(ToolCallUpdate::new("call_001"));
2800
2801 let json = serde_json::to_value(&subject).unwrap();
2802 assert_eq!(
2803 json,
2804 json!({
2805 "type": "tool_call",
2806 "toolCall": {
2807 "toolCallId": "call_001"
2808 }
2809 })
2810 );
2811
2812 let roundtripped: RequestPermissionSubject = serde_json::from_value(json).unwrap();
2813 assert!(matches!(
2814 roundtripped,
2815 RequestPermissionSubject::ToolCall(_)
2816 ));
2817 }
2818
2819 #[test]
2820 fn request_permission_subject_preserves_unknown_variant() {
2821 use serde_json::json;
2822
2823 let subject: RequestPermissionSubject = serde_json::from_value(json!({
2824 "type": "_review",
2825 "reason": "needs-review",
2826 "retryAfterSeconds": 30
2827 }))
2828 .unwrap();
2829
2830 let RequestPermissionSubject::Other(unknown) = subject else {
2831 panic!("expected unknown permission subject");
2832 };
2833
2834 assert_eq!(unknown.type_, "_review");
2835 assert_eq!(unknown.fields.get("reason"), Some(&json!("needs-review")));
2836 assert_eq!(unknown.fields.get("retryAfterSeconds"), Some(&json!(30)));
2837 assert_eq!(
2838 serde_json::to_value(RequestPermissionSubject::Other(unknown)).unwrap(),
2839 json!({
2840 "type": "_review",
2841 "reason": "needs-review",
2842 "retryAfterSeconds": 30
2843 })
2844 );
2845 }
2846
2847 #[test]
2848 fn request_permission_subject_unknown_does_not_hide_malformed_known_variant() {
2849 use serde_json::json;
2850
2851 assert!(
2852 serde_json::from_value::<RequestPermissionSubject>(json!({
2853 "type": "tool_call"
2854 }))
2855 .is_err()
2856 );
2857 assert!(
2858 serde_json::from_value::<RequestPermissionSubject>(json!({
2859 "type": 1
2860 }))
2861 .is_err()
2862 );
2863 }
2864
2865 #[test]
2866 fn request_permission_title_and_description_are_separate_from_tool_call_content() {
2867 use serde_json::json;
2868
2869 let request =
2870 RequestPermissionRequest::new("sess_abc123def456", "Approve file edit?", Vec::new())
2871 .description("Allow this tool to edit src/main.rs?")
2872 .subject(RequestPermissionSubject::from(ToolCallUpdate::new(
2873 "call_001",
2874 )));
2875
2876 assert_eq!(
2877 serde_json::to_value(request).unwrap(),
2878 json!({
2879 "sessionId": "sess_abc123def456",
2880 "title": "Approve file edit?",
2881 "description": "Allow this tool to edit src/main.rs?",
2882 "subject": {
2883 "type": "tool_call",
2884 "toolCall": {
2885 "toolCallId": "call_001"
2886 }
2887 },
2888 "options": []
2889 })
2890 );
2891 }
2892
2893 #[test]
2894 fn request_permission_requires_title_and_allows_missing_subject() {
2895 use serde_json::json;
2896
2897 let request = RequestPermissionRequest::new(
2898 "sess_abc123def456",
2899 "Approve elevated permissions?",
2900 Vec::new(),
2901 );
2902
2903 assert_eq!(
2904 serde_json::to_value(request).unwrap(),
2905 json!({
2906 "sessionId": "sess_abc123def456",
2907 "title": "Approve elevated permissions?",
2908 "options": []
2909 })
2910 );
2911
2912 let missing_subject: RequestPermissionRequest = serde_json::from_value(json!({
2913 "sessionId": "sess_abc123def456",
2914 "title": "Approve elevated permissions?",
2915 "options": []
2916 }))
2917 .unwrap();
2918 assert!(missing_subject.subject.is_none());
2919
2920 let null_subject: RequestPermissionRequest = serde_json::from_value(json!({
2921 "sessionId": "sess_abc123def456",
2922 "title": "Approve elevated permissions?",
2923 "subject": null,
2924 "options": []
2925 }))
2926 .unwrap();
2927 assert!(null_subject.subject.is_none());
2928
2929 assert!(
2930 serde_json::from_value::<RequestPermissionRequest>(json!({
2931 "sessionId": "sess_abc123def456",
2932 "options": []
2933 }))
2934 .is_err()
2935 );
2936 }
2937
2938 #[test]
2939 fn request_permission_outcome_preserves_unknown_variant() {
2940 use serde_json::json;
2941
2942 let outcome: RequestPermissionOutcome = serde_json::from_value(json!({
2943 "outcome": "_defer",
2944 "reason": "needs-review",
2945 "retryAfterSeconds": 30
2946 }))
2947 .unwrap();
2948
2949 let RequestPermissionOutcome::Other(unknown) = outcome else {
2950 panic!("expected unknown permission outcome");
2951 };
2952
2953 assert_eq!(unknown.outcome, "_defer");
2954 assert_eq!(unknown.fields.get("reason"), Some(&json!("needs-review")));
2955 assert_eq!(unknown.fields.get("retryAfterSeconds"), Some(&json!(30)));
2956 assert_eq!(
2957 serde_json::to_value(RequestPermissionOutcome::Other(unknown)).unwrap(),
2958 json!({
2959 "outcome": "_defer",
2960 "reason": "needs-review",
2961 "retryAfterSeconds": 30
2962 })
2963 );
2964 }
2965
2966 #[test]
2967 fn request_permission_outcome_unknown_does_not_hide_malformed_known_variant() {
2968 use serde_json::json;
2969
2970 assert!(
2971 serde_json::from_value::<RequestPermissionOutcome>(json!({
2972 "outcome": "selected"
2973 }))
2974 .is_err()
2975 );
2976 assert!(
2977 serde_json::from_value::<RequestPermissionOutcome>(json!({
2978 "outcome": 1
2979 }))
2980 .is_err()
2981 );
2982 }
2983
2984 #[test]
2985 fn available_command_input_unknown_does_not_hide_malformed_text_variant() {
2986 use serde_json::json;
2987
2988 assert!(serde_json::from_value::<AvailableCommandInput>(json!({})).is_err());
2989 assert!(
2990 serde_json::from_value::<AvailableCommandInput>(json!({
2991 "hint": "Pick one"
2992 }))
2993 .is_err()
2994 );
2995 assert!(
2996 serde_json::from_value::<AvailableCommandInput>(json!({
2997 "type": 1,
2998 "hint": "Pick one"
2999 }))
3000 .is_err()
3001 );
3002 assert!(
3003 serde_json::from_value::<OtherAvailableCommandInput>(json!({
3004 "type": "text",
3005 "hint": "Pick one"
3006 }))
3007 .is_err()
3008 );
3009 }
3010
3011 #[cfg(feature = "unstable_nes")]
3012 #[test]
3013 fn test_client_capabilities_position_encodings_serialization() {
3014 use serde_json::json;
3015
3016 let capabilities = ClientCapabilities::new().position_encodings(vec![
3017 PositionEncodingKind::Utf32,
3018 PositionEncodingKind::Utf16,
3019 ]);
3020 let json = serde_json::to_value(&capabilities).unwrap();
3021
3022 assert_eq!(json["positionEncodings"], json!(["utf-32", "utf-16"]));
3023 }
3024
3025 #[cfg(feature = "unstable_mcp_over_acp")]
3026 #[test]
3027 fn test_agent_mcp_request_method_names() {
3028 use serde_json::json;
3029
3030 let params: serde_json::Map<String, serde_json::Value> =
3031 [("cursor".to_string(), json!("abc"))].into_iter().collect();
3032
3033 assert_eq!(CLIENT_METHOD_NAMES.mcp_connect, "mcp/connect");
3034 assert_eq!(CLIENT_METHOD_NAMES.mcp_message, "mcp/message");
3035 assert_eq!(CLIENT_METHOD_NAMES.mcp_disconnect, "mcp/disconnect");
3036
3037 assert_eq!(
3038 AgentRequest::ConnectMcpRequest(Box::new(ConnectMcpRequest::new("server-1"))).method(),
3039 "mcp/connect"
3040 );
3041 assert_eq!(
3042 AgentRequest::MessageMcpRequest(Box::new(MessageMcpRequest::new(
3043 "conn-1",
3044 "tools/list"
3045 )))
3046 .method(),
3047 "mcp/message"
3048 );
3049 assert_eq!(
3050 AgentRequest::DisconnectMcpRequest(Box::new(DisconnectMcpRequest::new("conn-1")))
3051 .method(),
3052 "mcp/disconnect"
3053 );
3054 assert_eq!(
3055 AgentNotification::MessageMcpNotification(Box::new(MessageMcpNotification::new(
3056 "conn-1",
3057 "notifications/progress"
3058 )))
3059 .method(),
3060 "mcp/message"
3061 );
3062
3063 assert_eq!(
3064 serde_json::to_value(ConnectMcpRequest::new("server-1")).unwrap(),
3065 json!({ "serverId": "server-1" })
3066 );
3067 assert_eq!(
3068 serde_json::to_value(ConnectMcpResponse::new("conn-1")).unwrap(),
3069 json!({ "connectionId": "conn-1" })
3070 );
3071 assert_eq!(
3072 serde_json::to_value(MessageMcpRequest::new("conn-1", "tools/list").params(params))
3073 .unwrap(),
3074 json!({
3075 "connectionId": "conn-1",
3076 "method": "tools/list",
3077 "params": { "cursor": "abc" }
3078 })
3079 );
3080 assert_eq!(
3081 serde_json::to_value(DisconnectMcpRequest::new("conn-1")).unwrap(),
3082 json!({ "connectionId": "conn-1" })
3083 );
3084 assert_eq!(
3085 serde_json::to_value(MessageMcpNotification::new(
3086 "conn-1",
3087 "notifications/progress"
3088 ))
3089 .unwrap(),
3090 json!({
3091 "connectionId": "conn-1",
3092 "method": "notifications/progress"
3093 })
3094 );
3095
3096 let request_with_null_params: MessageMcpRequest = serde_json::from_value(json!({
3097 "connectionId": "conn-1",
3098 "method": "tools/list",
3099 "params": null
3100 }))
3101 .unwrap();
3102 assert_eq!(request_with_null_params.params, None);
3103 }
3104
3105 #[cfg(feature = "unstable_auth_methods")]
3106 #[test]
3107 fn test_auth_capabilities_serialize_terminal_support_as_object() {
3108 use serde_json::json;
3109
3110 let capabilities = AuthCapabilities::new().terminal(TerminalAuthCapabilities::new());
3111
3112 assert_eq!(
3113 serde_json::to_value(&capabilities).unwrap(),
3114 json!({
3115 "terminal": {}
3116 })
3117 );
3118
3119 let deserialized: AuthCapabilities = serde_json::from_value(json!({
3120 "terminal": false
3121 }))
3122 .unwrap();
3123 assert!(deserialized.terminal.is_none());
3124 }
3125
3126 #[test]
3127 fn request_permission_request_rejects_malformed_options() {
3128 use serde_json::json;
3129
3130 assert!(
3131 serde_json::from_value::<RequestPermissionRequest>(json!({
3132 "sessionId": "sess-1",
3133 "title": "Run tool?",
3134 "options": "not-an-array"
3135 }))
3136 .is_err()
3137 );
3138 assert!(
3139 serde_json::from_value::<RequestPermissionRequest>(json!({
3140 "sessionId": "sess-1",
3141 "title": "Run tool?",
3142 "options": [{"optionId": "allow"}]
3143 }))
3144 .is_err()
3145 );
3146 }
3147
3148 #[cfg(feature = "unstable_plan_operations")]
3149 #[test]
3150 fn malformed_plan_removed_is_not_hidden_as_unknown_update() {
3151 use serde_json::json;
3152
3153 assert!(
3154 serde_json::from_value::<SessionUpdate>(json!({
3155 "sessionUpdate": "plan_removed"
3156 }))
3157 .is_err()
3158 );
3159 }
3160}