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 matches!(
232 session_update,
233 "user_message_chunk"
234 | "user_message"
235 | "agent_message_chunk"
236 | "agent_message"
237 | "agent_thought_chunk"
238 | "agent_thought"
239 | "state_update"
240 | "tool_call_content_chunk"
241 | "tool_call_update"
242 | "plan_update"
243 | "available_commands_update"
244 | "config_option_update"
245 | "session_info_update"
246 | "usage_update"
247 )
248}
249
250fn other_session_update_schema(schema: &mut Schema) {
251 super::schema_util::reject_known_string_discriminators(
252 schema,
253 "sessionUpdate",
254 &[
255 "user_message_chunk",
256 "user_message",
257 "agent_message_chunk",
258 "agent_message",
259 "agent_thought_chunk",
260 "agent_thought",
261 "state_update",
262 "tool_call_content_chunk",
263 "tool_call_update",
264 "plan_update",
265 "available_commands_update",
266 "config_option_update",
267 "session_info_update",
268 #[cfg(feature = "unstable_plan_operations")]
269 "plan_removed",
270 "usage_update",
271 ],
272 );
273}
274
275#[serde_as]
277#[skip_serializing_none]
278#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
279#[serde(rename_all = "camelCase")]
280#[non_exhaustive]
281pub struct ConfigOptionUpdate {
282 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, SkipListener>>")]
284 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
285 pub config_options: Vec<SessionConfigOption>,
286 #[serde_as(deserialize_as = "DefaultOnError")]
292 #[schemars(extend("x-deserialize-default-on-error" = true))]
293 #[serde(default)]
294 #[serde(rename = "_meta")]
295 pub meta: Option<Meta>,
296}
297
298impl ConfigOptionUpdate {
299 #[must_use]
301 pub fn new(config_options: Vec<SessionConfigOption>) -> Self {
302 Self {
303 config_options,
304 meta: None,
305 }
306 }
307
308 #[must_use]
314 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
315 self.meta = meta.into_option();
316 self
317 }
318}
319
320#[serde_as]
325#[skip_serializing_none]
326#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
327#[serde(rename_all = "camelCase")]
328#[non_exhaustive]
329pub struct SessionInfoUpdate {
330 #[serde_as(deserialize_as = "DefaultOnError")]
332 #[schemars(extend("x-deserialize-default-on-error" = true))]
333 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
334 pub title: MaybeUndefined<String>,
335 #[serde_as(deserialize_as = "DefaultOnError")]
337 #[schemars(extend("x-deserialize-default-on-error" = true))]
338 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
339 pub updated_at: MaybeUndefined<String>,
340 #[serde_as(deserialize_as = "DefaultOnError")]
346 #[schemars(extend("x-deserialize-default-on-error" = true))]
347 #[serde(default)]
348 #[serde(rename = "_meta")]
349 pub meta: Option<Meta>,
350}
351
352impl SessionInfoUpdate {
353 #[must_use]
355 pub fn new() -> Self {
356 Self::default()
357 }
358
359 #[must_use]
361 pub fn title(mut self, title: impl IntoMaybeUndefined<String>) -> Self {
362 self.title = title.into_maybe_undefined();
363 self
364 }
365
366 #[must_use]
368 pub fn updated_at(mut self, updated_at: impl IntoMaybeUndefined<String>) -> Self {
369 self.updated_at = updated_at.into_maybe_undefined();
370 self
371 }
372
373 #[must_use]
379 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
380 self.meta = meta.into_option();
381 self
382 }
383}
384
385#[serde_as]
387#[skip_serializing_none]
388#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
389#[serde(rename_all = "camelCase")]
390#[non_exhaustive]
391pub struct UsageUpdate {
392 pub used: u64,
394 pub size: u64,
396 #[serde_as(deserialize_as = "DefaultOnError")]
398 #[schemars(extend("x-deserialize-default-on-error" = true))]
399 #[serde(default)]
400 pub cost: Option<Cost>,
401 #[serde_as(deserialize_as = "DefaultOnError")]
407 #[schemars(extend("x-deserialize-default-on-error" = true))]
408 #[serde(default)]
409 #[serde(rename = "_meta")]
410 pub meta: Option<Meta>,
411}
412
413impl UsageUpdate {
414 #[must_use]
416 pub fn new(used: u64, size: u64) -> Self {
417 Self {
418 used,
419 size,
420 cost: None,
421 meta: None,
422 }
423 }
424
425 #[must_use]
427 pub fn cost(mut self, cost: impl IntoOption<Cost>) -> Self {
428 self.cost = cost.into_option();
429 self
430 }
431
432 #[must_use]
438 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
439 self.meta = meta.into_option();
440 self
441 }
442}
443
444#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
451#[serde(tag = "state", rename_all = "snake_case")]
452#[schemars(extend("discriminator" = {"propertyName": "state"}))]
453#[non_exhaustive]
454pub enum StateUpdate {
455 Running(RunningStateUpdate),
457 Idle(IdleStateUpdate),
459 RequiresAction(RequiresActionStateUpdate),
461 #[serde(untagged)]
467 Other(OtherStateUpdate),
468}
469
470#[serde_as]
472#[skip_serializing_none]
473#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
474#[serde(rename_all = "camelCase")]
475#[non_exhaustive]
476pub struct RunningStateUpdate {
477 #[serde_as(deserialize_as = "DefaultOnError")]
483 #[schemars(extend("x-deserialize-default-on-error" = true))]
484 #[serde(default)]
485 #[serde(rename = "_meta")]
486 pub meta: Option<Meta>,
487}
488
489impl RunningStateUpdate {
490 #[must_use]
492 pub fn new() -> Self {
493 Self::default()
494 }
495
496 #[must_use]
502 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
503 self.meta = meta.into_option();
504 self
505 }
506}
507
508#[serde_as]
510#[skip_serializing_none]
511#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
512#[serde(rename_all = "camelCase")]
513#[non_exhaustive]
514pub struct IdleStateUpdate {
515 #[serde_as(deserialize_as = "DefaultOnError")]
520 #[schemars(extend("x-deserialize-default-on-error" = true))]
521 #[serde(default)]
522 pub stop_reason: Option<StopReason>,
523 #[cfg(feature = "unstable_end_turn_token_usage")]
532 #[serde_as(deserialize_as = "DefaultOnError")]
533 #[schemars(extend("x-deserialize-default-on-error" = true))]
534 #[serde(default)]
535 pub usage: Option<Usage>,
536 #[serde_as(deserialize_as = "DefaultOnError")]
542 #[schemars(extend("x-deserialize-default-on-error" = true))]
543 #[serde(default)]
544 #[serde(rename = "_meta")]
545 pub meta: Option<Meta>,
546}
547
548impl IdleStateUpdate {
549 #[must_use]
551 pub fn new() -> Self {
552 Self::default()
553 }
554
555 #[must_use]
557 pub fn stop_reason(mut self, stop_reason: impl IntoOption<StopReason>) -> Self {
558 self.stop_reason = stop_reason.into_option();
559 self
560 }
561
562 #[cfg(feature = "unstable_end_turn_token_usage")]
568 #[must_use]
569 pub fn usage(mut self, usage: impl IntoOption<Usage>) -> Self {
570 self.usage = usage.into_option();
571 self
572 }
573
574 #[must_use]
580 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
581 self.meta = meta.into_option();
582 self
583 }
584}
585
586#[serde_as]
588#[skip_serializing_none]
589#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
590#[serde(rename_all = "camelCase")]
591#[non_exhaustive]
592pub struct RequiresActionStateUpdate {
593 #[serde_as(deserialize_as = "DefaultOnError")]
599 #[schemars(extend("x-deserialize-default-on-error" = true))]
600 #[serde(default)]
601 #[serde(rename = "_meta")]
602 pub meta: Option<Meta>,
603}
604
605impl RequiresActionStateUpdate {
606 #[must_use]
608 pub fn new() -> Self {
609 Self::default()
610 }
611
612 #[must_use]
618 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
619 self.meta = meta.into_option();
620 self
621 }
622}
623
624#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq)]
629#[schemars(inline)]
630#[schemars(transform = other_state_update_schema)]
631#[serde(rename_all = "camelCase")]
632#[non_exhaustive]
633pub struct OtherStateUpdate {
634 #[serde(rename = "state")]
640 pub state: String,
641 #[serde(flatten)]
643 pub fields: BTreeMap<String, serde_json::Value>,
644}
645
646impl OtherStateUpdate {
647 #[must_use]
649 pub fn new(state: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
650 fields.remove("state");
651 Self {
652 state: state.into(),
653 fields,
654 }
655 }
656}
657
658impl<'de> Deserialize<'de> for OtherStateUpdate {
659 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
660 where
661 D: serde::Deserializer<'de>,
662 {
663 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
664 let state = fields
665 .remove("state")
666 .ok_or_else(|| serde::de::Error::missing_field("state"))?;
667 let serde_json::Value::String(state) = state else {
668 return Err(serde::de::Error::custom("`state` must be a string"));
669 };
670
671 if is_known_state_update(&state) {
672 return Err(serde::de::Error::custom(format!(
673 "known state update `{state}` did not match its schema"
674 )));
675 }
676
677 Ok(Self { state, fields })
678 }
679}
680
681fn is_known_state_update(state: &str) -> bool {
682 matches!(state, "running" | "idle" | "requires_action")
683}
684
685fn other_state_update_schema(schema: &mut Schema) {
686 super::schema_util::reject_known_string_discriminators(
687 schema,
688 "state",
689 &["running", "idle", "requires_action"],
690 );
691}
692
693#[serde_as]
695#[skip_serializing_none]
696#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
697#[serde(rename_all = "camelCase")]
698#[non_exhaustive]
699pub struct Cost {
700 pub amount: f64,
702 pub currency: String,
704 #[serde_as(deserialize_as = "DefaultOnError")]
710 #[schemars(extend("x-deserialize-default-on-error" = true))]
711 #[serde(default)]
712 #[serde(rename = "_meta")]
713 pub meta: Option<Meta>,
714}
715
716impl Cost {
717 #[must_use]
719 pub fn new(amount: f64, currency: impl Into<String>) -> Self {
720 Self {
721 amount,
722 currency: currency.into(),
723 meta: None,
724 }
725 }
726
727 #[must_use]
733 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
734 self.meta = meta.into_option();
735 self
736 }
737}
738
739#[serde_as]
741#[skip_serializing_none]
742#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
743#[serde(rename_all = "camelCase")]
744#[non_exhaustive]
745pub struct ContentChunk {
746 pub message_id: MessageId,
751 pub content: ContentBlock,
753 #[serde_as(deserialize_as = "DefaultOnError")]
759 #[schemars(extend("x-deserialize-default-on-error" = true))]
760 #[serde(default)]
761 #[serde(rename = "_meta")]
762 pub meta: Option<Meta>,
763}
764
765impl ContentChunk {
766 #[must_use]
768 pub fn new(content: ContentBlock, message_id: impl Into<MessageId>) -> Self {
769 Self {
770 content,
771 message_id: message_id.into(),
772 meta: None,
773 }
774 }
775
776 #[must_use]
782 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
783 self.meta = meta.into_option();
784 self
785 }
786}
787
788#[serde_as]
802#[skip_serializing_none]
803#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
804#[serde(rename_all = "camelCase")]
805#[non_exhaustive]
806pub struct UserMessage {
807 pub message_id: MessageId,
809 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<VecSkipError<_, SkipListener>>>")]
811 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
812 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
813 pub content: MaybeUndefined<Vec<ContentBlock>>,
814 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
820 #[schemars(extend("x-deserialize-default-on-error" = true))]
821 #[serde(
822 rename = "_meta",
823 default,
824 skip_serializing_if = "MaybeUndefined::is_undefined"
825 )]
826 pub meta: MaybeUndefined<Meta>,
827}
828
829impl UserMessage {
830 #[must_use]
832 pub fn new(message_id: impl Into<MessageId>) -> Self {
833 Self {
834 message_id: message_id.into(),
835 content: MaybeUndefined::Undefined,
836 meta: MaybeUndefined::Undefined,
837 }
838 }
839
840 #[must_use]
842 pub fn content(mut self, content: impl IntoMaybeUndefined<Vec<ContentBlock>>) -> Self {
843 self.content = content.into_maybe_undefined();
844 self
845 }
846
847 #[must_use]
853 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
854 self.meta = meta.into_maybe_undefined();
855 self
856 }
857}
858
859#[serde_as]
873#[skip_serializing_none]
874#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
875#[serde(rename_all = "camelCase")]
876#[non_exhaustive]
877pub struct AgentMessage {
878 pub message_id: MessageId,
880 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<VecSkipError<_, SkipListener>>>")]
882 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
883 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
884 pub content: MaybeUndefined<Vec<ContentBlock>>,
885 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
891 #[schemars(extend("x-deserialize-default-on-error" = true))]
892 #[serde(
893 rename = "_meta",
894 default,
895 skip_serializing_if = "MaybeUndefined::is_undefined"
896 )]
897 pub meta: MaybeUndefined<Meta>,
898}
899
900impl AgentMessage {
901 #[must_use]
903 pub fn new(message_id: impl Into<MessageId>) -> Self {
904 Self {
905 message_id: message_id.into(),
906 content: MaybeUndefined::Undefined,
907 meta: MaybeUndefined::Undefined,
908 }
909 }
910
911 #[must_use]
913 pub fn content(mut self, content: impl IntoMaybeUndefined<Vec<ContentBlock>>) -> Self {
914 self.content = content.into_maybe_undefined();
915 self
916 }
917
918 #[must_use]
924 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
925 self.meta = meta.into_maybe_undefined();
926 self
927 }
928}
929
930#[serde_as]
944#[skip_serializing_none]
945#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
946#[serde(rename_all = "camelCase")]
947#[non_exhaustive]
948pub struct AgentThought {
949 pub message_id: MessageId,
951 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<VecSkipError<_, SkipListener>>>")]
953 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
954 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
955 pub content: MaybeUndefined<Vec<ContentBlock>>,
956 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
962 #[schemars(extend("x-deserialize-default-on-error" = true))]
963 #[serde(
964 rename = "_meta",
965 default,
966 skip_serializing_if = "MaybeUndefined::is_undefined"
967 )]
968 pub meta: MaybeUndefined<Meta>,
969}
970
971impl AgentThought {
972 #[must_use]
974 pub fn new(message_id: impl Into<MessageId>) -> Self {
975 Self {
976 message_id: message_id.into(),
977 content: MaybeUndefined::Undefined,
978 meta: MaybeUndefined::Undefined,
979 }
980 }
981
982 #[must_use]
984 pub fn content(mut self, content: impl IntoMaybeUndefined<Vec<ContentBlock>>) -> Self {
985 self.content = content.into_maybe_undefined();
986 self
987 }
988
989 #[must_use]
995 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
996 self.meta = meta.into_maybe_undefined();
997 self
998 }
999}
1000
1001#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash, Display, From)]
1003#[serde(transparent)]
1004#[from(Arc<str>, String, &'static str)]
1005#[non_exhaustive]
1006pub struct MessageId(pub Arc<str>);
1007
1008impl MessageId {
1009 #[must_use]
1011 pub fn new(id: impl Into<Arc<str>>) -> Self {
1012 Self(id.into())
1013 }
1014}
1015
1016#[serde_as]
1018#[skip_serializing_none]
1019#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1020#[serde(rename_all = "camelCase")]
1021#[non_exhaustive]
1022pub struct AvailableCommandsUpdate {
1023 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, SkipListener>>")]
1025 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
1026 pub available_commands: Vec<AvailableCommand>,
1027 #[serde_as(deserialize_as = "DefaultOnError")]
1033 #[schemars(extend("x-deserialize-default-on-error" = true))]
1034 #[serde(default)]
1035 #[serde(rename = "_meta")]
1036 pub meta: Option<Meta>,
1037}
1038
1039impl AvailableCommandsUpdate {
1040 #[must_use]
1042 pub fn new(available_commands: Vec<AvailableCommand>) -> Self {
1043 Self {
1044 available_commands,
1045 meta: None,
1046 }
1047 }
1048
1049 #[must_use]
1055 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1056 self.meta = meta.into_option();
1057 self
1058 }
1059}
1060
1061#[serde_as]
1063#[skip_serializing_none]
1064#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1065#[serde(rename_all = "camelCase")]
1066#[non_exhaustive]
1067pub struct AvailableCommand {
1068 pub name: String,
1070 pub description: String,
1072 #[serde_as(deserialize_as = "DefaultOnError")]
1074 #[schemars(extend("x-deserialize-default-on-error" = true))]
1075 #[serde(default)]
1076 pub input: Option<AvailableCommandInput>,
1077 #[serde_as(deserialize_as = "DefaultOnError")]
1083 #[schemars(extend("x-deserialize-default-on-error" = true))]
1084 #[serde(default)]
1085 #[serde(rename = "_meta")]
1086 pub meta: Option<Meta>,
1087}
1088
1089impl AvailableCommand {
1090 #[must_use]
1092 pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
1093 Self {
1094 name: name.into(),
1095 description: description.into(),
1096 input: None,
1097 meta: None,
1098 }
1099 }
1100
1101 #[must_use]
1103 pub fn input(mut self, input: impl IntoOption<AvailableCommandInput>) -> Self {
1104 self.input = input.into_option();
1105 self
1106 }
1107
1108 #[must_use]
1114 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1115 self.meta = meta.into_option();
1116 self
1117 }
1118}
1119
1120#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1122#[serde(untagged, rename_all = "camelCase")]
1123#[non_exhaustive]
1124pub enum AvailableCommandInput {
1125 Unstructured(UnstructuredCommandInput),
1127 Other(OtherAvailableCommandInput),
1138}
1139
1140#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq, Eq)]
1142#[schemars(inline)]
1143#[serde(rename_all = "camelCase")]
1144#[non_exhaustive]
1145pub struct OtherAvailableCommandInput {
1146 #[serde(rename = "type")]
1152 pub type_: String,
1153 #[serde(flatten)]
1155 pub fields: BTreeMap<String, serde_json::Value>,
1156}
1157
1158impl OtherAvailableCommandInput {
1159 #[must_use]
1161 pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
1162 fields.remove("type");
1163 Self {
1164 type_: type_.into(),
1165 fields,
1166 }
1167 }
1168}
1169
1170impl<'de> Deserialize<'de> for OtherAvailableCommandInput {
1171 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1172 where
1173 D: serde::Deserializer<'de>,
1174 {
1175 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1176 let type_ = fields
1177 .remove("type")
1178 .ok_or_else(|| serde::de::Error::missing_field("type"))?;
1179 let serde_json::Value::String(type_) = type_ else {
1180 return Err(serde::de::Error::custom("`type` must be a string"));
1181 };
1182
1183 Ok(Self { type_, fields })
1184 }
1185}
1186
1187#[serde_as]
1189#[skip_serializing_none]
1190#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq, Eq)]
1191#[schemars(transform = unstructured_command_input_schema)]
1192#[serde(rename_all = "camelCase")]
1193#[non_exhaustive]
1194pub struct UnstructuredCommandInput {
1195 pub hint: String,
1197 #[serde_as(deserialize_as = "DefaultOnError")]
1203 #[schemars(extend("x-deserialize-default-on-error" = true))]
1204 #[serde(default)]
1205 #[serde(rename = "_meta")]
1206 pub meta: Option<Meta>,
1207}
1208
1209impl UnstructuredCommandInput {
1210 #[must_use]
1212 pub fn new(hint: impl Into<String>) -> Self {
1213 Self {
1214 hint: hint.into(),
1215 meta: None,
1216 }
1217 }
1218
1219 #[must_use]
1225 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1226 self.meta = meta.into_option();
1227 self
1228 }
1229}
1230
1231impl<'de> Deserialize<'de> for UnstructuredCommandInput {
1232 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1233 where
1234 D: serde::Deserializer<'de>,
1235 {
1236 #[derive(Deserialize)]
1237 #[serde(rename_all = "camelCase")]
1238 struct RawUnstructuredCommandInput {
1239 hint: String,
1240 #[serde(rename = "_meta")]
1241 meta: Option<Meta>,
1242 #[serde(flatten)]
1243 fields: BTreeMap<String, serde_json::Value>,
1244 }
1245
1246 let raw = RawUnstructuredCommandInput::deserialize(deserializer)?;
1247 if raw.fields.contains_key("type") {
1248 return Err(serde::de::Error::custom(
1249 "unstructured command input cannot include a `type` field",
1250 ));
1251 }
1252
1253 Ok(Self {
1254 hint: raw.hint,
1255 meta: raw.meta,
1256 })
1257 }
1258}
1259
1260fn unstructured_command_input_schema(schema: &mut Schema) {
1261 super::schema_util::reject_property(schema, "type");
1262}
1263
1264#[serde_as]
1272#[skip_serializing_none]
1273#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1274#[schemars(extend("x-side" = "client", "x-method" = SESSION_REQUEST_PERMISSION_METHOD_NAME))]
1275#[serde(rename_all = "camelCase")]
1276#[non_exhaustive]
1277pub struct RequestPermissionRequest {
1278 pub session_id: SessionId,
1280 pub tool_call: ToolCallUpdate,
1282 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, SkipListener>>")]
1284 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
1285 pub options: Vec<PermissionOption>,
1286 #[serde_as(deserialize_as = "DefaultOnError")]
1292 #[schemars(extend("x-deserialize-default-on-error" = true))]
1293 #[serde(default)]
1294 #[serde(rename = "_meta")]
1295 pub meta: Option<Meta>,
1296}
1297
1298impl RequestPermissionRequest {
1299 #[must_use]
1301 pub fn new(
1302 session_id: impl Into<SessionId>,
1303 tool_call: ToolCallUpdate,
1304 options: Vec<PermissionOption>,
1305 ) -> Self {
1306 Self {
1307 session_id: session_id.into(),
1308 tool_call,
1309 options,
1310 meta: None,
1311 }
1312 }
1313
1314 #[must_use]
1320 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1321 self.meta = meta.into_option();
1322 self
1323 }
1324}
1325
1326#[serde_as]
1328#[skip_serializing_none]
1329#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1330#[serde(rename_all = "camelCase")]
1331#[non_exhaustive]
1332pub struct PermissionOption {
1333 pub option_id: PermissionOptionId,
1335 pub name: String,
1337 pub kind: PermissionOptionKind,
1339 #[serde_as(deserialize_as = "DefaultOnError")]
1345 #[schemars(extend("x-deserialize-default-on-error" = true))]
1346 #[serde(default)]
1347 #[serde(rename = "_meta")]
1348 pub meta: Option<Meta>,
1349}
1350
1351impl PermissionOption {
1352 #[must_use]
1354 pub fn new(
1355 option_id: impl Into<PermissionOptionId>,
1356 name: impl Into<String>,
1357 kind: PermissionOptionKind,
1358 ) -> Self {
1359 Self {
1360 option_id: option_id.into(),
1361 name: name.into(),
1362 kind,
1363 meta: None,
1364 }
1365 }
1366
1367 #[must_use]
1373 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1374 self.meta = meta.into_option();
1375 self
1376 }
1377}
1378
1379#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash, Display, From)]
1381#[serde(transparent)]
1382#[from(Arc<str>, String, &'static str)]
1383#[non_exhaustive]
1384pub struct PermissionOptionId(pub Arc<str>);
1385
1386impl PermissionOptionId {
1387 #[must_use]
1389 pub fn new(id: impl Into<Arc<str>>) -> Self {
1390 Self(id.into())
1391 }
1392}
1393
1394#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1398#[serde(rename_all = "snake_case")]
1399#[non_exhaustive]
1400pub enum PermissionOptionKind {
1401 AllowOnce,
1403 AllowAlways,
1405 RejectOnce,
1407 RejectAlways,
1409 #[serde(untagged)]
1415 Other(String),
1416}
1417
1418#[serde_as]
1420#[skip_serializing_none]
1421#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1422#[schemars(extend("x-side" = "client", "x-method" = SESSION_REQUEST_PERMISSION_METHOD_NAME))]
1423#[serde(rename_all = "camelCase")]
1424#[non_exhaustive]
1425pub struct RequestPermissionResponse {
1426 pub outcome: RequestPermissionOutcome,
1428 #[serde_as(deserialize_as = "DefaultOnError")]
1434 #[schemars(extend("x-deserialize-default-on-error" = true))]
1435 #[serde(default)]
1436 #[serde(rename = "_meta")]
1437 pub meta: Option<Meta>,
1438}
1439
1440impl RequestPermissionResponse {
1441 #[must_use]
1443 pub fn new(outcome: RequestPermissionOutcome) -> Self {
1444 Self {
1445 outcome,
1446 meta: None,
1447 }
1448 }
1449
1450 #[must_use]
1456 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1457 self.meta = meta.into_option();
1458 self
1459 }
1460}
1461
1462#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1464#[serde(tag = "outcome", rename_all = "snake_case")]
1465#[schemars(extend("discriminator" = {"propertyName": "outcome"}))]
1466#[non_exhaustive]
1467pub enum RequestPermissionOutcome {
1468 Cancelled,
1476 #[serde(rename_all = "camelCase")]
1478 Selected(SelectedPermissionOutcome),
1479}
1480
1481#[serde_as]
1483#[skip_serializing_none]
1484#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1485#[serde(rename_all = "camelCase")]
1486#[non_exhaustive]
1487pub struct SelectedPermissionOutcome {
1488 pub option_id: PermissionOptionId,
1490 #[serde_as(deserialize_as = "DefaultOnError")]
1496 #[schemars(extend("x-deserialize-default-on-error" = true))]
1497 #[serde(default)]
1498 #[serde(rename = "_meta")]
1499 pub meta: Option<Meta>,
1500}
1501
1502impl SelectedPermissionOutcome {
1503 #[must_use]
1505 pub fn new(option_id: impl Into<PermissionOptionId>) -> Self {
1506 Self {
1507 option_id: option_id.into(),
1508 meta: None,
1509 }
1510 }
1511
1512 #[must_use]
1518 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1519 self.meta = meta.into_option();
1520 self
1521 }
1522}
1523
1524#[serde_as]
1533#[skip_serializing_none]
1534#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1535#[serde(rename_all = "camelCase")]
1536#[non_exhaustive]
1537pub struct ClientCapabilities {
1538 #[cfg(feature = "unstable_auth_methods")]
1546 #[serde_as(deserialize_as = "DefaultOnError")]
1547 #[schemars(extend("x-deserialize-default-on-error" = true))]
1548 #[serde(default)]
1549 pub auth: Option<AuthCapabilities>,
1550 #[cfg(feature = "unstable_elicitation")]
1557 #[serde_as(deserialize_as = "DefaultOnError")]
1558 #[schemars(extend("x-deserialize-default-on-error" = true))]
1559 #[serde(default)]
1560 pub elicitation: Option<ElicitationCapabilities>,
1561 #[cfg(feature = "unstable_nes")]
1567 #[serde_as(deserialize_as = "DefaultOnError")]
1568 #[schemars(extend("x-deserialize-default-on-error" = true))]
1569 #[serde(default)]
1570 pub nes: Option<ClientNesCapabilities>,
1571 #[cfg(feature = "unstable_nes")]
1577 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, SkipListener>>")]
1578 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
1579 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1580 pub position_encodings: Vec<PositionEncodingKind>,
1581
1582 #[serde_as(deserialize_as = "DefaultOnError")]
1588 #[schemars(extend("x-deserialize-default-on-error" = true))]
1589 #[serde(default)]
1590 #[serde(rename = "_meta")]
1591 pub meta: Option<Meta>,
1592}
1593
1594impl ClientCapabilities {
1595 #[must_use]
1597 pub fn new() -> Self {
1598 Self::default()
1599 }
1600
1601 #[cfg(feature = "unstable_auth_methods")]
1609 #[must_use]
1610 pub fn auth(mut self, auth: impl IntoOption<AuthCapabilities>) -> Self {
1611 self.auth = auth.into_option();
1612 self
1613 }
1614
1615 #[cfg(feature = "unstable_elicitation")]
1622 #[must_use]
1623 pub fn elicitation(mut self, elicitation: impl IntoOption<ElicitationCapabilities>) -> Self {
1624 self.elicitation = elicitation.into_option();
1625 self
1626 }
1627
1628 #[cfg(feature = "unstable_nes")]
1632 #[must_use]
1633 pub fn nes(mut self, nes: impl IntoOption<ClientNesCapabilities>) -> Self {
1634 self.nes = nes.into_option();
1635 self
1636 }
1637
1638 #[cfg(feature = "unstable_nes")]
1642 #[must_use]
1643 pub fn position_encodings(mut self, position_encodings: Vec<PositionEncodingKind>) -> Self {
1644 self.position_encodings = position_encodings;
1645 self
1646 }
1647
1648 #[must_use]
1654 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1655 self.meta = meta.into_option();
1656 self
1657 }
1658}
1659
1660#[cfg(feature = "unstable_auth_methods")]
1670#[serde_as]
1671#[skip_serializing_none]
1672#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1673#[serde(rename_all = "camelCase")]
1674#[non_exhaustive]
1675pub struct AuthCapabilities {
1676 #[serde_as(deserialize_as = "DefaultOnError")]
1681 #[schemars(extend("x-deserialize-default-on-error" = true))]
1682 #[serde(default)]
1683 pub terminal: Option<TerminalAuthCapabilities>,
1684 #[serde_as(deserialize_as = "DefaultOnError")]
1690 #[schemars(extend("x-deserialize-default-on-error" = true))]
1691 #[serde(default)]
1692 #[serde(rename = "_meta")]
1693 pub meta: Option<Meta>,
1694}
1695
1696#[cfg(feature = "unstable_auth_methods")]
1697impl AuthCapabilities {
1698 #[must_use]
1700 pub fn new() -> Self {
1701 Self::default()
1702 }
1703
1704 #[must_use]
1709 pub fn terminal(mut self, terminal: impl IntoOption<TerminalAuthCapabilities>) -> Self {
1710 self.terminal = terminal.into_option();
1711 self
1712 }
1713
1714 #[must_use]
1720 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1721 self.meta = meta.into_option();
1722 self
1723 }
1724}
1725
1726#[cfg(feature = "unstable_auth_methods")]
1734#[serde_as]
1735#[skip_serializing_none]
1736#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1737#[non_exhaustive]
1738pub struct TerminalAuthCapabilities {
1739 #[serde_as(deserialize_as = "DefaultOnError")]
1745 #[schemars(extend("x-deserialize-default-on-error" = true))]
1746 #[serde(default)]
1747 #[serde(rename = "_meta")]
1748 pub meta: Option<Meta>,
1749}
1750
1751#[cfg(feature = "unstable_auth_methods")]
1752impl TerminalAuthCapabilities {
1753 #[must_use]
1755 pub fn new() -> Self {
1756 Self::default()
1757 }
1758
1759 #[must_use]
1765 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1766 self.meta = meta.into_option();
1767 self
1768 }
1769}
1770
1771#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1777#[non_exhaustive]
1778pub struct ClientMethodNames {
1779 pub session_request_permission: &'static str,
1781 pub session_update: &'static str,
1783 #[cfg(feature = "unstable_mcp_over_acp")]
1785 pub mcp_connect: &'static str,
1786 #[cfg(feature = "unstable_mcp_over_acp")]
1788 pub mcp_message: &'static str,
1789 #[cfg(feature = "unstable_mcp_over_acp")]
1791 pub mcp_disconnect: &'static str,
1792 #[cfg(feature = "unstable_elicitation")]
1794 pub elicitation_create: &'static str,
1795 #[cfg(feature = "unstable_elicitation")]
1797 pub elicitation_complete: &'static str,
1798}
1799
1800pub const CLIENT_METHOD_NAMES: ClientMethodNames = ClientMethodNames {
1802 session_update: SESSION_UPDATE_NOTIFICATION,
1803 session_request_permission: SESSION_REQUEST_PERMISSION_METHOD_NAME,
1804 #[cfg(feature = "unstable_mcp_over_acp")]
1805 mcp_connect: MCP_CONNECT_METHOD_NAME,
1806 #[cfg(feature = "unstable_mcp_over_acp")]
1807 mcp_message: MCP_MESSAGE_METHOD_NAME,
1808 #[cfg(feature = "unstable_mcp_over_acp")]
1809 mcp_disconnect: MCP_DISCONNECT_METHOD_NAME,
1810 #[cfg(feature = "unstable_elicitation")]
1811 elicitation_create: ELICITATION_CREATE_METHOD_NAME,
1812 #[cfg(feature = "unstable_elicitation")]
1813 elicitation_complete: ELICITATION_COMPLETE_NOTIFICATION,
1814};
1815
1816pub(crate) const SESSION_UPDATE_NOTIFICATION: &str = "session/update";
1818pub(crate) const SESSION_REQUEST_PERMISSION_METHOD_NAME: &str = "session/request_permission";
1820#[cfg(feature = "unstable_elicitation")]
1822pub(crate) const ELICITATION_CREATE_METHOD_NAME: &str = "elicitation/create";
1823#[cfg(feature = "unstable_elicitation")]
1825pub(crate) const ELICITATION_COMPLETE_NOTIFICATION: &str = "elicitation/complete";
1826
1827#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
1834#[serde(untagged)]
1835#[schemars(inline)]
1836#[non_exhaustive]
1837pub enum AgentRequest {
1838 RequestPermissionRequest(Box<RequestPermissionRequest>),
1849 #[cfg(feature = "unstable_elicitation")]
1855 CreateElicitationRequest(CreateElicitationRequest),
1856 #[cfg(feature = "unstable_mcp_over_acp")]
1862 ConnectMcpRequest(ConnectMcpRequest),
1863 #[cfg(feature = "unstable_mcp_over_acp")]
1869 MessageMcpRequest(MessageMcpRequest),
1870 #[cfg(feature = "unstable_mcp_over_acp")]
1876 DisconnectMcpRequest(DisconnectMcpRequest),
1877 ExtMethodRequest(ExtRequest),
1885}
1886
1887impl AgentRequest {
1888 #[must_use]
1890 pub fn method(&self) -> &str {
1891 match self {
1892 Self::RequestPermissionRequest(_) => CLIENT_METHOD_NAMES.session_request_permission,
1893 #[cfg(feature = "unstable_elicitation")]
1894 Self::CreateElicitationRequest(_) => CLIENT_METHOD_NAMES.elicitation_create,
1895 #[cfg(feature = "unstable_mcp_over_acp")]
1896 Self::ConnectMcpRequest(_) => CLIENT_METHOD_NAMES.mcp_connect,
1897 #[cfg(feature = "unstable_mcp_over_acp")]
1898 Self::MessageMcpRequest(_) => CLIENT_METHOD_NAMES.mcp_message,
1899 #[cfg(feature = "unstable_mcp_over_acp")]
1900 Self::DisconnectMcpRequest(_) => CLIENT_METHOD_NAMES.mcp_disconnect,
1901 Self::ExtMethodRequest(ext_request) => &ext_request.method,
1902 }
1903 }
1904}
1905
1906#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
1913#[serde(untagged)]
1914#[schemars(inline)]
1915#[non_exhaustive]
1916pub enum ClientResponse {
1917 RequestPermissionResponse(RequestPermissionResponse),
1919 #[cfg(feature = "unstable_elicitation")]
1921 CreateElicitationResponse(CreateElicitationResponse),
1922 #[cfg(feature = "unstable_mcp_over_acp")]
1924 ConnectMcpResponse(ConnectMcpResponse),
1925 #[cfg(feature = "unstable_mcp_over_acp")]
1927 DisconnectMcpResponse(#[serde(default)] DisconnectMcpResponse),
1928 #[cfg(feature = "unstable_mcp_over_acp")]
1930 MessageMcpResponse(MessageMcpResponse),
1931 ExtMethodResponse(ExtResponse),
1933}
1934
1935#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
1942#[serde(untagged)]
1943#[schemars(inline)]
1944#[non_exhaustive]
1945pub enum AgentNotification {
1946 UpdateSessionNotification(Box<UpdateSessionNotification>),
1959 #[cfg(feature = "unstable_elicitation")]
1965 CompleteElicitationNotification(CompleteElicitationNotification),
1966 #[cfg(feature = "unstable_mcp_over_acp")]
1972 MessageMcpNotification(MessageMcpNotification),
1973 ExtNotification(ExtNotification),
1981}
1982
1983impl AgentNotification {
1984 #[must_use]
1986 pub fn method(&self) -> &str {
1987 match self {
1988 Self::UpdateSessionNotification(_) => CLIENT_METHOD_NAMES.session_update,
1989 #[cfg(feature = "unstable_elicitation")]
1990 Self::CompleteElicitationNotification(_) => CLIENT_METHOD_NAMES.elicitation_complete,
1991 #[cfg(feature = "unstable_mcp_over_acp")]
1992 Self::MessageMcpNotification(_) => CLIENT_METHOD_NAMES.mcp_message,
1993 Self::ExtNotification(ext_notification) => &ext_notification.method,
1994 }
1995 }
1996}
1997
1998#[cfg(test)]
1999mod tests {
2000 use super::*;
2001
2002 #[cfg(feature = "unstable_auth_methods")]
2003 #[test]
2004 fn test_client_capabilities_auth_defaults_on_malformed_value() {
2005 use serde_json::json;
2006
2007 let capabilities: ClientCapabilities = serde_json::from_value(json!({
2008 "auth": false
2009 }))
2010 .unwrap();
2011
2012 assert_eq!(capabilities.auth, None);
2013 }
2014
2015 #[test]
2016 fn test_serialization_behavior() {
2017 use serde_json::json;
2018
2019 assert_eq!(
2020 serde_json::from_value::<SessionInfoUpdate>(json!({})).unwrap(),
2021 SessionInfoUpdate {
2022 title: MaybeUndefined::Undefined,
2023 updated_at: MaybeUndefined::Undefined,
2024 meta: None
2025 }
2026 );
2027 assert_eq!(
2028 serde_json::from_value::<SessionInfoUpdate>(json!({"title": null, "updatedAt": null}))
2029 .unwrap(),
2030 SessionInfoUpdate {
2031 title: MaybeUndefined::Null,
2032 updated_at: MaybeUndefined::Null,
2033 meta: None
2034 }
2035 );
2036 assert_eq!(
2037 serde_json::from_value::<SessionInfoUpdate>(
2038 json!({"title": "title", "updatedAt": "timestamp"})
2039 )
2040 .unwrap(),
2041 SessionInfoUpdate {
2042 title: MaybeUndefined::Value("title".to_string()),
2043 updated_at: MaybeUndefined::Value("timestamp".to_string()),
2044 meta: None
2045 }
2046 );
2047
2048 assert_eq!(
2049 serde_json::to_value(SessionInfoUpdate::new()).unwrap(),
2050 json!({})
2051 );
2052 assert_eq!(
2053 serde_json::to_value(SessionInfoUpdate::new().title("title")).unwrap(),
2054 json!({"title": "title"})
2055 );
2056 assert_eq!(
2057 serde_json::to_value(SessionInfoUpdate::new().title(None)).unwrap(),
2058 json!({"title": null})
2059 );
2060 assert_eq!(
2061 serde_json::to_value(
2062 SessionInfoUpdate::new()
2063 .title("title")
2064 .title(MaybeUndefined::Undefined)
2065 )
2066 .unwrap(),
2067 json!({})
2068 );
2069 }
2070
2071 #[test]
2072 fn test_content_chunk_message_id_serialization() {
2073 use serde_json::json;
2074
2075 assert_eq!(
2076 serde_json::to_value(SessionUpdate::AgentMessageChunk(ContentChunk::new(
2077 ContentBlock::Text(crate::v2::TextContent::new("Hello")),
2078 "msg_agent_c42b9",
2079 )))
2080 .unwrap(),
2081 json!({
2082 "sessionUpdate": "agent_message_chunk",
2083 "messageId": "msg_agent_c42b9",
2084 "content": {
2085 "type": "text",
2086 "text": "Hello"
2087 }
2088 })
2089 );
2090
2091 let err = serde_json::from_value::<ContentChunk>(json!({
2092 "content": {
2093 "type": "text",
2094 "text": "Hello"
2095 }
2096 }))
2097 .unwrap_err();
2098
2099 assert!(err.to_string().contains("messageId"), "{err}");
2100 }
2101
2102 #[test]
2103 fn test_tool_call_content_chunk_serialization() {
2104 use serde_json::json;
2105
2106 assert_eq!(
2107 serde_json::to_value(SessionUpdate::ToolCallContentChunk(
2108 ToolCallContentChunk::new(
2109 "call_001",
2110 crate::v2::ContentBlock::Text(crate::v2::TextContent::new("partial output")),
2111 )
2112 ))
2113 .unwrap(),
2114 json!({
2115 "sessionUpdate": "tool_call_content_chunk",
2116 "toolCallId": "call_001",
2117 "content": {
2118 "type": "content",
2119 "content": {
2120 "type": "text",
2121 "text": "partial output"
2122 }
2123 }
2124 })
2125 );
2126
2127 let err = serde_json::from_value::<ToolCallContentChunk>(json!({
2128 "content": {
2129 "type": "content",
2130 "content": {
2131 "type": "text",
2132 "text": "partial output"
2133 }
2134 }
2135 }))
2136 .unwrap_err();
2137
2138 assert!(err.to_string().contains("toolCallId"), "{err}");
2139 }
2140
2141 #[test]
2142 fn test_full_message_serialization() {
2143 use serde_json::json;
2144
2145 assert_eq!(
2146 serde_json::to_value(SessionUpdate::UserMessage(
2147 UserMessage::new("msg_user_8f7a1").content(vec![ContentBlock::Text(
2148 crate::v2::TextContent::new("Hello")
2149 )])
2150 ))
2151 .unwrap(),
2152 json!({
2153 "sessionUpdate": "user_message",
2154 "messageId": "msg_user_8f7a1",
2155 "content": [
2156 {
2157 "type": "text",
2158 "text": "Hello"
2159 }
2160 ]
2161 })
2162 );
2163
2164 assert_eq!(
2165 serde_json::to_value(SessionUpdate::AgentMessage(
2166 AgentMessage::new("msg_agent_c42b9").content(vec![ContentBlock::Text(
2167 crate::v2::TextContent::new("Hello")
2168 )])
2169 ))
2170 .unwrap(),
2171 json!({
2172 "sessionUpdate": "agent_message",
2173 "messageId": "msg_agent_c42b9",
2174 "content": [
2175 {
2176 "type": "text",
2177 "text": "Hello"
2178 }
2179 ]
2180 })
2181 );
2182
2183 assert_eq!(
2184 serde_json::to_value(SessionUpdate::AgentThought(
2185 AgentThought::new("msg_thought_a12").content(vec![ContentBlock::Text(
2186 crate::v2::TextContent::new("Need to inspect the call sites first.")
2187 )])
2188 ))
2189 .unwrap(),
2190 json!({
2191 "sessionUpdate": "agent_thought",
2192 "messageId": "msg_thought_a12",
2193 "content": [
2194 {
2195 "type": "text",
2196 "text": "Need to inspect the call sites first."
2197 }
2198 ]
2199 })
2200 );
2201 }
2202
2203 #[test]
2204 fn test_message_upsert_serialization() {
2205 use serde_json::json;
2206
2207 assert_eq!(
2208 serde_json::to_value(SessionUpdate::UserMessage(
2209 UserMessage::new("msg_empty").content(Vec::<ContentBlock>::new())
2210 ))
2211 .unwrap(),
2212 json!({
2213 "sessionUpdate": "user_message",
2214 "messageId": "msg_empty",
2215 "content": []
2216 })
2217 );
2218
2219 let empty = serde_json::from_value::<UserMessage>(json!({
2220 "messageId": "msg_empty",
2221 "content": []
2222 }))
2223 .unwrap();
2224 assert!(matches!(
2225 empty.content,
2226 MaybeUndefined::Value(ref content) if content.is_empty()
2227 ));
2228
2229 let patch = serde_json::from_value::<AgentMessage>(json!({
2230 "messageId": "msg_agent_c42b9"
2231 }))
2232 .unwrap();
2233 assert_eq!(patch.content, MaybeUndefined::Undefined);
2234 assert_eq!(patch.meta, MaybeUndefined::Undefined);
2235
2236 let malformed_meta = serde_json::from_value::<AgentMessage>(json!({
2237 "messageId": "msg_agent_c42b9",
2238 "_meta": false
2239 }))
2240 .unwrap();
2241 assert_eq!(malformed_meta.meta, MaybeUndefined::Undefined);
2242
2243 let patch = serde_json::from_value::<AgentThought>(json!({
2244 "messageId": "msg_thought_a12"
2245 }))
2246 .unwrap();
2247 assert_eq!(patch.content, MaybeUndefined::Undefined);
2248
2249 let clear = serde_json::from_value::<UserMessage>(json!({
2250 "messageId": "msg_user_8f7a1",
2251 "content": null
2252 }))
2253 .unwrap();
2254 assert_eq!(clear.content, MaybeUndefined::Null);
2255
2256 let clear_meta = serde_json::from_value::<UserMessage>(json!({
2257 "messageId": "msg_user_8f7a1",
2258 "_meta": null
2259 }))
2260 .unwrap();
2261 assert_eq!(clear_meta.meta, MaybeUndefined::Null);
2262
2263 let mut meta = Meta::new();
2264 meta.insert("source".to_string(), json!("replay"));
2265
2266 assert_eq!(
2267 serde_json::to_value(SessionUpdate::UserMessage(
2268 UserMessage::new("msg_user_8f7a1").meta(meta)
2269 ))
2270 .unwrap(),
2271 json!({
2272 "sessionUpdate": "user_message",
2273 "messageId": "msg_user_8f7a1",
2274 "_meta": {
2275 "source": "replay"
2276 }
2277 })
2278 );
2279
2280 assert_eq!(
2281 serde_json::to_value(SessionUpdate::UserMessage(
2282 UserMessage::new("msg_user_8f7a1").meta(None::<Meta>)
2283 ))
2284 .unwrap(),
2285 json!({
2286 "sessionUpdate": "user_message",
2287 "messageId": "msg_user_8f7a1",
2288 "_meta": null
2289 })
2290 );
2291 }
2292
2293 #[test]
2294 fn test_usage_update_serialization() {
2295 use serde_json::json;
2296
2297 assert_eq!(
2298 serde_json::to_value(SessionUpdate::UsageUpdate(UsageUpdate::new(
2299 53_000, 200_000
2300 )))
2301 .unwrap(),
2302 json!({
2303 "sessionUpdate": "usage_update",
2304 "used": 53000,
2305 "size": 200_000
2306 })
2307 );
2308
2309 assert_eq!(
2310 serde_json::to_value(SessionUpdate::UsageUpdate(
2311 UsageUpdate::new(53_000, 200_000).cost(Cost::new(0.045, "USD"))
2312 ))
2313 .unwrap(),
2314 json!({
2315 "sessionUpdate": "usage_update",
2316 "used": 53000,
2317 "size": 200_000,
2318 "cost": {
2319 "amount": 0.045,
2320 "currency": "USD"
2321 }
2322 })
2323 );
2324
2325 let SessionUpdate::UsageUpdate(update) = serde_json::from_value(json!({
2326 "sessionUpdate": "usage_update",
2327 "used": 53000,
2328 "size": 200_000,
2329 "cost": null
2330 }))
2331 .unwrap() else {
2332 panic!("expected usage update");
2333 };
2334
2335 assert_eq!(update.cost, None);
2336 }
2337
2338 #[test]
2339 fn test_state_update_serialization() {
2340 use serde_json::json;
2341
2342 assert_eq!(
2343 serde_json::to_value(SessionUpdate::StateUpdate(StateUpdate::Running(
2344 RunningStateUpdate::new()
2345 )))
2346 .unwrap(),
2347 json!({
2348 "sessionUpdate": "state_update",
2349 "state": "running"
2350 })
2351 );
2352
2353 assert_eq!(
2354 serde_json::to_value(SessionUpdate::StateUpdate(StateUpdate::Idle(
2355 IdleStateUpdate::new().stop_reason(StopReason::EndTurn)
2356 )))
2357 .unwrap(),
2358 json!({
2359 "sessionUpdate": "state_update",
2360 "state": "idle",
2361 "stopReason": "end_turn"
2362 })
2363 );
2364
2365 let SessionUpdate::StateUpdate(update) = serde_json::from_value(json!({
2366 "sessionUpdate": "state_update",
2367 "state": "requires_action"
2368 }))
2369 .unwrap() else {
2370 panic!("expected state update");
2371 };
2372
2373 assert!(matches!(update, StateUpdate::RequiresAction(_)));
2374
2375 let SessionUpdate::StateUpdate(StateUpdate::Idle(update)) = serde_json::from_value(json!({
2376 "sessionUpdate": "state_update",
2377 "state": "idle",
2378 "stopReason": null
2379 }))
2380 .unwrap() else {
2381 panic!("expected idle state update");
2382 };
2383
2384 assert_eq!(update.stop_reason, None);
2385
2386 let SessionUpdate::StateUpdate(StateUpdate::Other(update)) =
2387 serde_json::from_value(json!({
2388 "sessionUpdate": "state_update",
2389 "state": "_paused",
2390 "label": "Paused"
2391 }))
2392 .unwrap()
2393 else {
2394 panic!("expected unknown state update");
2395 };
2396
2397 assert_eq!(update.state, "_paused");
2398 assert_eq!(update.fields["label"], json!("Paused"));
2399 }
2400
2401 #[test]
2402 fn session_update_preserves_unknown_variant() {
2403 use serde_json::json;
2404
2405 let update: SessionUpdate = serde_json::from_value(json!({
2406 "sessionUpdate": "_status_badge",
2407 "label": "Indexing",
2408 "progress": 0.5
2409 }))
2410 .unwrap();
2411
2412 let SessionUpdate::Other(unknown) = update else {
2413 panic!("expected unknown session update");
2414 };
2415
2416 assert_eq!(unknown.session_update, "_status_badge");
2417 assert_eq!(unknown.fields.get("label"), Some(&json!("Indexing")));
2418 assert_eq!(unknown.fields.get("progress"), Some(&json!(0.5)));
2419
2420 assert_eq!(
2421 serde_json::to_value(SessionUpdate::Other(unknown)).unwrap(),
2422 json!({
2423 "sessionUpdate": "_status_badge",
2424 "label": "Indexing",
2425 "progress": 0.5
2426 })
2427 );
2428 }
2429
2430 #[test]
2431 fn test_plan_update_serialization() {
2432 use serde_json::json;
2433
2434 let plan_update =
2435 SessionUpdate::PlanUpdate(PlanUpdate::new(crate::v2::PlanUpdateContent::items(
2436 "plan-1",
2437 vec![crate::v2::PlanEntry::new(
2438 "Step 1",
2439 crate::v2::PlanEntryPriority::High,
2440 crate::v2::PlanEntryStatus::Pending,
2441 )],
2442 )));
2443
2444 assert_eq!(
2445 serde_json::to_value(plan_update).unwrap(),
2446 json!({
2447 "sessionUpdate": "plan_update",
2448 "plan": {
2449 "type": "items",
2450 "id": "plan-1",
2451 "entries": [
2452 {
2453 "content": "Step 1",
2454 "priority": "high",
2455 "status": "pending"
2456 }
2457 ]
2458 }
2459 })
2460 );
2461 }
2462
2463 #[cfg(feature = "unstable_plan_operations")]
2464 #[test]
2465 fn test_plan_removed_serialization() {
2466 use serde_json::json;
2467
2468 assert_eq!(
2469 serde_json::to_value(SessionUpdate::PlanRemoved(PlanRemoved::new("plan-1"))).unwrap(),
2470 json!({
2471 "sessionUpdate": "plan_removed",
2472 "id": "plan-1"
2473 })
2474 );
2475 }
2476
2477 #[test]
2478 fn available_command_input_preserves_unknown_typed_variant() {
2479 use serde_json::json;
2480
2481 let input: AvailableCommandInput = serde_json::from_value(json!({
2482 "type": "_choices",
2483 "hint": "Pick one",
2484 "options": ["fast", "careful"]
2485 }))
2486 .unwrap();
2487
2488 let AvailableCommandInput::Other(unknown) = input else {
2489 panic!("expected unknown command input");
2490 };
2491
2492 assert_eq!(unknown.type_, "_choices");
2493 assert_eq!(unknown.fields.get("hint"), Some(&json!("Pick one")));
2494 assert_eq!(
2495 unknown.fields.get("options"),
2496 Some(&json!(["fast", "careful"]))
2497 );
2498 assert_eq!(
2499 serde_json::to_value(AvailableCommandInput::Other(unknown)).unwrap(),
2500 json!({
2501 "type": "_choices",
2502 "hint": "Pick one",
2503 "options": ["fast", "careful"]
2504 })
2505 );
2506 }
2507
2508 #[test]
2509 fn available_command_input_unknown_does_not_hide_malformed_unstructured_variant() {
2510 use serde_json::json;
2511
2512 assert!(serde_json::from_value::<AvailableCommandInput>(json!({})).is_err());
2513 assert!(
2514 serde_json::from_value::<AvailableCommandInput>(json!({
2515 "type": 1,
2516 "hint": "Pick one"
2517 }))
2518 .is_err()
2519 );
2520 }
2521
2522 #[cfg(feature = "unstable_nes")]
2523 #[test]
2524 fn test_client_capabilities_position_encodings_serialization() {
2525 use serde_json::json;
2526
2527 let capabilities = ClientCapabilities::new().position_encodings(vec![
2528 PositionEncodingKind::Utf32,
2529 PositionEncodingKind::Utf16,
2530 ]);
2531 let json = serde_json::to_value(&capabilities).unwrap();
2532
2533 assert_eq!(json["positionEncodings"], json!(["utf-32", "utf-16"]));
2534 }
2535
2536 #[cfg(feature = "unstable_mcp_over_acp")]
2537 #[test]
2538 fn test_agent_mcp_request_method_names() {
2539 use serde_json::json;
2540
2541 let params: serde_json::Map<String, serde_json::Value> =
2542 [("cursor".to_string(), json!("abc"))].into_iter().collect();
2543
2544 assert_eq!(CLIENT_METHOD_NAMES.mcp_connect, "mcp/connect");
2545 assert_eq!(CLIENT_METHOD_NAMES.mcp_message, "mcp/message");
2546 assert_eq!(CLIENT_METHOD_NAMES.mcp_disconnect, "mcp/disconnect");
2547
2548 assert_eq!(
2549 AgentRequest::ConnectMcpRequest(ConnectMcpRequest::new("server-1")).method(),
2550 "mcp/connect"
2551 );
2552 assert_eq!(
2553 AgentRequest::MessageMcpRequest(MessageMcpRequest::new("conn-1", "tools/list"))
2554 .method(),
2555 "mcp/message"
2556 );
2557 assert_eq!(
2558 AgentRequest::DisconnectMcpRequest(DisconnectMcpRequest::new("conn-1")).method(),
2559 "mcp/disconnect"
2560 );
2561 assert_eq!(
2562 AgentNotification::MessageMcpNotification(MessageMcpNotification::new(
2563 "conn-1",
2564 "notifications/progress"
2565 ))
2566 .method(),
2567 "mcp/message"
2568 );
2569
2570 assert_eq!(
2571 serde_json::to_value(ConnectMcpRequest::new("server-1")).unwrap(),
2572 json!({ "acpId": "server-1" })
2573 );
2574 assert_eq!(
2575 serde_json::to_value(ConnectMcpResponse::new("conn-1")).unwrap(),
2576 json!({ "connectionId": "conn-1" })
2577 );
2578 assert_eq!(
2579 serde_json::to_value(MessageMcpRequest::new("conn-1", "tools/list").params(params))
2580 .unwrap(),
2581 json!({
2582 "connectionId": "conn-1",
2583 "method": "tools/list",
2584 "params": { "cursor": "abc" }
2585 })
2586 );
2587 assert_eq!(
2588 serde_json::to_value(DisconnectMcpRequest::new("conn-1")).unwrap(),
2589 json!({ "connectionId": "conn-1" })
2590 );
2591 assert_eq!(
2592 serde_json::to_value(MessageMcpNotification::new(
2593 "conn-1",
2594 "notifications/progress"
2595 ))
2596 .unwrap(),
2597 json!({
2598 "connectionId": "conn-1",
2599 "method": "notifications/progress"
2600 })
2601 );
2602
2603 let request_with_null_params: MessageMcpRequest = serde_json::from_value(json!({
2604 "connectionId": "conn-1",
2605 "method": "tools/list",
2606 "params": null
2607 }))
2608 .unwrap();
2609 assert_eq!(request_with_null_params.params, None);
2610 }
2611
2612 #[cfg(feature = "unstable_auth_methods")]
2613 #[test]
2614 fn test_auth_capabilities_serialize_terminal_support_as_object() {
2615 use serde_json::json;
2616
2617 let capabilities = AuthCapabilities::new().terminal(TerminalAuthCapabilities::new());
2618
2619 assert_eq!(
2620 serde_json::to_value(&capabilities).unwrap(),
2621 json!({
2622 "terminal": {}
2623 })
2624 );
2625
2626 let deserialized: AuthCapabilities = serde_json::from_value(json!({
2627 "terminal": false
2628 }))
2629 .unwrap();
2630 assert!(deserialized.terminal.is_none());
2631 }
2632}