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;
17use super::{
18 AbsolutePath, ContentBlock, ExtNotification, ExtRequest, ExtResponse, Meta, PlanUpdate,
19 SessionConfigOption, SessionId, StopReason, TerminalId, TerminalOutputChunk, TerminalUpdate,
20 ToolCallContentChunk, ToolCallId, ToolCallUpdate,
21};
22#[cfg(feature = "unstable_elicitation")]
23use super::{
24 CompleteElicitationNotification, CreateElicitationRequest, CreateElicitationResponse,
25 ElicitationCapabilities,
26};
27use crate::{IntoMaybeUndefined, IntoOption, MaybeUndefined, SkipListener};
28
29#[cfg(feature = "unstable_mcp_over_acp")]
30use super::mcp::{
31 ConnectMcpRequest, ConnectMcpResponse, DisconnectMcpRequest, DisconnectMcpResponse,
32 MCP_CONNECT_METHOD_NAME, MCP_DISCONNECT_METHOD_NAME, MCP_MESSAGE_METHOD_NAME,
33 MessageMcpNotification, MessageMcpRequest, MessageMcpResponse,
34};
35
36#[cfg(feature = "unstable_nes")]
37use super::{ClientNesCapabilities, PositionEncodingKind};
38
39#[serde_as]
47#[skip_serializing_none]
48#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
49#[schemars(extend("x-side" = "client", "x-method" = SESSION_UPDATE_NOTIFICATION))]
50#[serde(rename_all = "camelCase")]
51#[non_exhaustive]
52pub struct UpdateSessionNotification {
53 pub session_id: SessionId,
55 pub update: SessionUpdate,
57 #[serde_as(deserialize_as = "DefaultOnError")]
63 #[schemars(extend("x-deserialize-default-on-error" = true))]
64 #[serde(default)]
65 #[serde(rename = "_meta")]
66 pub meta: Option<Meta>,
67}
68
69impl UpdateSessionNotification {
70 #[must_use]
72 pub fn new(session_id: impl Into<SessionId>, update: SessionUpdate) -> Self {
73 Self {
74 session_id: session_id.into(),
75 update,
76 meta: None,
77 }
78 }
79
80 #[must_use]
86 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
87 self.meta = meta.into_option();
88 self
89 }
90}
91
92#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
98#[serde(tag = "sessionUpdate", rename_all = "snake_case")]
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),
127 ToolCallContentChunk(ToolCallContentChunk),
129 ToolCallUpdate(ToolCallUpdate),
131 TerminalUpdate(TerminalUpdate),
133 TerminalOutputChunk(TerminalOutputChunk),
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 | "terminal_update"
247 | "terminal_output_chunk"
248 | "plan_update"
249 | "available_commands_update"
250 | "config_option_update"
251 | "session_info_update"
252 | "usage_update"
253 )
254}
255
256fn other_session_update_schema(schema: &mut Schema) {
257 super::schema_util::reject_known_string_discriminators(
258 schema,
259 "sessionUpdate",
260 &[
261 "user_message_chunk",
262 "user_message",
263 "agent_message_chunk",
264 "agent_message",
265 "agent_thought_chunk",
266 "agent_thought",
267 "state_update",
268 "tool_call_content_chunk",
269 "tool_call_update",
270 "terminal_update",
271 "terminal_output_chunk",
272 "plan_update",
273 "available_commands_update",
274 "config_option_update",
275 "session_info_update",
276 #[cfg(feature = "unstable_plan_operations")]
277 "plan_removed",
278 "usage_update",
279 ],
280 );
281}
282
283#[serde_as]
285#[skip_serializing_none]
286#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
287#[serde(rename_all = "camelCase")]
288#[non_exhaustive]
289pub struct ConfigOptionUpdate {
290 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, SkipListener>>")]
292 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
293 pub config_options: Vec<SessionConfigOption>,
294 #[serde_as(deserialize_as = "DefaultOnError")]
300 #[schemars(extend("x-deserialize-default-on-error" = true))]
301 #[serde(default)]
302 #[serde(rename = "_meta")]
303 pub meta: Option<Meta>,
304}
305
306impl ConfigOptionUpdate {
307 #[must_use]
309 pub fn new(config_options: Vec<SessionConfigOption>) -> Self {
310 Self {
311 config_options,
312 meta: None,
313 }
314 }
315
316 #[must_use]
322 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
323 self.meta = meta.into_option();
324 self
325 }
326}
327
328#[serde_as]
336#[skip_serializing_none]
337#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
338#[serde(rename_all = "camelCase")]
339#[non_exhaustive]
340pub struct SessionInfoUpdate {
341 #[serde_as(deserialize_as = "DefaultOnError")]
343 #[schemars(extend("x-deserialize-default-on-error" = true))]
344 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
345 pub title: MaybeUndefined<String>,
346 #[serde_as(deserialize_as = "DefaultOnError")]
348 #[schemars(extend("x-deserialize-default-on-error" = true, "format" = "date-time"))]
349 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
350 pub updated_at: MaybeUndefined<String>,
351 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
357 #[schemars(extend("x-deserialize-default-on-error" = true))]
358 #[serde(
359 rename = "_meta",
360 default,
361 skip_serializing_if = "MaybeUndefined::is_undefined"
362 )]
363 pub meta: MaybeUndefined<Meta>,
364}
365
366impl SessionInfoUpdate {
367 #[must_use]
369 pub fn new() -> Self {
370 Self::default()
371 }
372
373 #[must_use]
375 pub fn title(mut self, title: impl IntoMaybeUndefined<String>) -> Self {
376 self.title = title.into_maybe_undefined();
377 self
378 }
379
380 #[must_use]
382 pub fn updated_at(mut self, updated_at: impl IntoMaybeUndefined<String>) -> Self {
383 self.updated_at = updated_at.into_maybe_undefined();
384 self
385 }
386
387 #[must_use]
393 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
394 self.meta = meta.into_maybe_undefined();
395 self
396 }
397}
398
399#[serde_as]
401#[skip_serializing_none]
402#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
403#[serde(rename_all = "camelCase")]
404#[non_exhaustive]
405pub struct UsageUpdate {
406 pub used: u64,
408 pub size: u64,
410 #[serde_as(deserialize_as = "DefaultOnError")]
412 #[schemars(extend("x-deserialize-default-on-error" = true))]
413 #[serde(default)]
414 pub cost: Option<Cost>,
415 #[serde_as(deserialize_as = "DefaultOnError")]
421 #[schemars(extend("x-deserialize-default-on-error" = true))]
422 #[serde(default)]
423 #[serde(rename = "_meta")]
424 pub meta: Option<Meta>,
425}
426
427impl UsageUpdate {
428 #[must_use]
430 pub fn new(used: u64, size: u64) -> Self {
431 Self {
432 used,
433 size,
434 cost: None,
435 meta: None,
436 }
437 }
438
439 #[must_use]
441 pub fn cost(mut self, cost: impl IntoOption<Cost>) -> Self {
442 self.cost = cost.into_option();
443 self
444 }
445
446 #[must_use]
452 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
453 self.meta = meta.into_option();
454 self
455 }
456}
457
458#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
463#[serde(tag = "state", rename_all = "snake_case")]
464#[non_exhaustive]
465pub enum StateUpdate {
466 Running(RunningStateUpdate),
468 Idle(IdleStateUpdate),
470 RequiresAction(RequiresActionStateUpdate),
472 #[serde(untagged)]
478 Other(OtherStateUpdate),
479}
480
481#[serde_as]
483#[skip_serializing_none]
484#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
485#[serde(rename_all = "camelCase")]
486#[non_exhaustive]
487pub struct RunningStateUpdate {
488 #[serde_as(deserialize_as = "DefaultOnError")]
494 #[schemars(extend("x-deserialize-default-on-error" = true))]
495 #[serde(default)]
496 #[serde(rename = "_meta")]
497 pub meta: Option<Meta>,
498}
499
500impl RunningStateUpdate {
501 #[must_use]
503 pub fn new() -> Self {
504 Self::default()
505 }
506
507 #[must_use]
513 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
514 self.meta = meta.into_option();
515 self
516 }
517}
518
519#[serde_as]
521#[skip_serializing_none]
522#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
523#[serde(rename_all = "camelCase")]
524#[non_exhaustive]
525pub struct IdleStateUpdate {
526 #[serde_as(deserialize_as = "DefaultOnError")]
531 #[schemars(extend("x-deserialize-default-on-error" = true))]
532 #[serde(default)]
533 pub stop_reason: Option<StopReason>,
534 #[cfg(feature = "unstable_end_turn_token_usage")]
543 #[serde_as(deserialize_as = "DefaultOnError")]
544 #[schemars(extend("x-deserialize-default-on-error" = true))]
545 #[serde(default)]
546 pub usage: Option<Usage>,
547 #[serde_as(deserialize_as = "DefaultOnError")]
553 #[schemars(extend("x-deserialize-default-on-error" = true))]
554 #[serde(default)]
555 #[serde(rename = "_meta")]
556 pub meta: Option<Meta>,
557}
558
559impl IdleStateUpdate {
560 #[must_use]
562 pub fn new() -> Self {
563 Self::default()
564 }
565
566 #[must_use]
568 pub fn stop_reason(mut self, stop_reason: impl IntoOption<StopReason>) -> Self {
569 self.stop_reason = stop_reason.into_option();
570 self
571 }
572
573 #[cfg(feature = "unstable_end_turn_token_usage")]
579 #[must_use]
580 pub fn usage(mut self, usage: impl IntoOption<Usage>) -> Self {
581 self.usage = usage.into_option();
582 self
583 }
584
585 #[must_use]
591 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
592 self.meta = meta.into_option();
593 self
594 }
595}
596
597#[serde_as]
599#[skip_serializing_none]
600#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
601#[serde(rename_all = "camelCase")]
602#[non_exhaustive]
603pub struct RequiresActionStateUpdate {
604 #[serde_as(deserialize_as = "DefaultOnError")]
610 #[schemars(extend("x-deserialize-default-on-error" = true))]
611 #[serde(default)]
612 #[serde(rename = "_meta")]
613 pub meta: Option<Meta>,
614}
615
616impl RequiresActionStateUpdate {
617 #[must_use]
619 pub fn new() -> Self {
620 Self::default()
621 }
622
623 #[must_use]
629 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
630 self.meta = meta.into_option();
631 self
632 }
633}
634
635#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq)]
640#[schemars(inline)]
641#[schemars(transform = other_state_update_schema)]
642#[serde(rename_all = "camelCase")]
643#[non_exhaustive]
644pub struct OtherStateUpdate {
645 #[serde(rename = "state")]
651 pub state: String,
652 #[serde(flatten)]
654 pub fields: BTreeMap<String, serde_json::Value>,
655}
656
657impl OtherStateUpdate {
658 #[must_use]
660 pub fn new(state: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
661 fields.remove("state");
662 Self {
663 state: state.into(),
664 fields,
665 }
666 }
667}
668
669impl<'de> Deserialize<'de> for OtherStateUpdate {
670 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
671 where
672 D: serde::Deserializer<'de>,
673 {
674 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
675 let state = fields
676 .remove("state")
677 .ok_or_else(|| serde::de::Error::missing_field("state"))?;
678 let serde_json::Value::String(state) = state else {
679 return Err(serde::de::Error::custom("`state` must be a string"));
680 };
681
682 if is_known_state_update(&state) {
683 return Err(serde::de::Error::custom(format!(
684 "known state update `{state}` did not match its schema"
685 )));
686 }
687
688 Ok(Self { state, fields })
689 }
690}
691
692fn is_known_state_update(state: &str) -> bool {
693 matches!(state, "running" | "idle" | "requires_action")
694}
695
696fn other_state_update_schema(schema: &mut Schema) {
697 super::schema_util::reject_known_string_discriminators(
698 schema,
699 "state",
700 &["running", "idle", "requires_action"],
701 );
702}
703
704#[serde_as]
706#[skip_serializing_none]
707#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
708#[serde(rename_all = "camelCase")]
709#[non_exhaustive]
710pub struct Cost {
711 pub amount: f64,
713 #[schemars(pattern(r"^[A-Z]{3}$"))]
715 pub currency: String,
716 #[serde_as(deserialize_as = "DefaultOnError")]
722 #[schemars(extend("x-deserialize-default-on-error" = true))]
723 #[serde(default)]
724 #[serde(rename = "_meta")]
725 pub meta: Option<Meta>,
726}
727
728impl Cost {
729 #[must_use]
731 pub fn new(amount: f64, currency: impl Into<String>) -> Self {
732 Self {
733 amount,
734 currency: currency.into(),
735 meta: None,
736 }
737 }
738
739 #[must_use]
745 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
746 self.meta = meta.into_option();
747 self
748 }
749}
750
751#[serde_as]
753#[skip_serializing_none]
754#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
755#[serde(rename_all = "camelCase")]
756#[non_exhaustive]
757pub struct ContentChunk {
758 pub message_id: MessageId,
763 pub content: ContentBlock,
765 #[serde_as(deserialize_as = "DefaultOnError")]
771 #[schemars(extend("x-deserialize-default-on-error" = true))]
772 #[serde(default)]
773 #[serde(rename = "_meta")]
774 pub meta: Option<Meta>,
775}
776
777impl ContentChunk {
778 #[must_use]
780 pub fn new(content: ContentBlock, message_id: impl Into<MessageId>) -> Self {
781 Self {
782 content,
783 message_id: message_id.into(),
784 meta: None,
785 }
786 }
787
788 #[must_use]
794 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
795 self.meta = meta.into_option();
796 self
797 }
798}
799
800#[serde_as]
814#[skip_serializing_none]
815#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
816#[serde(rename_all = "camelCase")]
817#[non_exhaustive]
818pub struct UserMessage {
819 pub message_id: MessageId,
821 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<VecSkipError<_, SkipListener>>>")]
823 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
824 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
825 pub content: MaybeUndefined<Vec<ContentBlock>>,
826 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
832 #[schemars(extend("x-deserialize-default-on-error" = true))]
833 #[serde(
834 rename = "_meta",
835 default,
836 skip_serializing_if = "MaybeUndefined::is_undefined"
837 )]
838 pub meta: MaybeUndefined<Meta>,
839}
840
841impl UserMessage {
842 #[must_use]
844 pub fn new(message_id: impl Into<MessageId>) -> Self {
845 Self {
846 message_id: message_id.into(),
847 content: MaybeUndefined::Undefined,
848 meta: MaybeUndefined::Undefined,
849 }
850 }
851
852 #[must_use]
854 pub fn content(mut self, content: impl IntoMaybeUndefined<Vec<ContentBlock>>) -> Self {
855 self.content = content.into_maybe_undefined();
856 self
857 }
858
859 #[must_use]
865 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
866 self.meta = meta.into_maybe_undefined();
867 self
868 }
869}
870
871#[serde_as]
885#[skip_serializing_none]
886#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
887#[serde(rename_all = "camelCase")]
888#[non_exhaustive]
889pub struct AgentMessage {
890 pub message_id: MessageId,
892 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<VecSkipError<_, SkipListener>>>")]
894 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
895 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
896 pub content: MaybeUndefined<Vec<ContentBlock>>,
897 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
903 #[schemars(extend("x-deserialize-default-on-error" = true))]
904 #[serde(
905 rename = "_meta",
906 default,
907 skip_serializing_if = "MaybeUndefined::is_undefined"
908 )]
909 pub meta: MaybeUndefined<Meta>,
910}
911
912impl AgentMessage {
913 #[must_use]
915 pub fn new(message_id: impl Into<MessageId>) -> Self {
916 Self {
917 message_id: message_id.into(),
918 content: MaybeUndefined::Undefined,
919 meta: MaybeUndefined::Undefined,
920 }
921 }
922
923 #[must_use]
925 pub fn content(mut self, content: impl IntoMaybeUndefined<Vec<ContentBlock>>) -> Self {
926 self.content = content.into_maybe_undefined();
927 self
928 }
929
930 #[must_use]
936 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
937 self.meta = meta.into_maybe_undefined();
938 self
939 }
940}
941
942#[serde_as]
956#[skip_serializing_none]
957#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
958#[serde(rename_all = "camelCase")]
959#[non_exhaustive]
960pub struct AgentThought {
961 pub message_id: MessageId,
963 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<VecSkipError<_, SkipListener>>>")]
965 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
966 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
967 pub content: MaybeUndefined<Vec<ContentBlock>>,
968 #[serde_as(deserialize_as = "DefaultOnError<MaybeUndefined<_>>")]
974 #[schemars(extend("x-deserialize-default-on-error" = true))]
975 #[serde(
976 rename = "_meta",
977 default,
978 skip_serializing_if = "MaybeUndefined::is_undefined"
979 )]
980 pub meta: MaybeUndefined<Meta>,
981}
982
983impl AgentThought {
984 #[must_use]
986 pub fn new(message_id: impl Into<MessageId>) -> Self {
987 Self {
988 message_id: message_id.into(),
989 content: MaybeUndefined::Undefined,
990 meta: MaybeUndefined::Undefined,
991 }
992 }
993
994 #[must_use]
996 pub fn content(mut self, content: impl IntoMaybeUndefined<Vec<ContentBlock>>) -> Self {
997 self.content = content.into_maybe_undefined();
998 self
999 }
1000
1001 #[must_use]
1007 pub fn meta(mut self, meta: impl IntoMaybeUndefined<Meta>) -> Self {
1008 self.meta = meta.into_maybe_undefined();
1009 self
1010 }
1011}
1012
1013#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash, Display, From)]
1015#[serde(transparent)]
1016#[from(forward)]
1017#[non_exhaustive]
1018pub struct MessageId(pub Arc<str>);
1019
1020impl MessageId {
1021 #[must_use]
1023 pub fn new(id: impl Into<Self>) -> Self {
1024 id.into()
1025 }
1026}
1027
1028#[serde_as]
1030#[skip_serializing_none]
1031#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1032#[serde(rename_all = "camelCase")]
1033#[non_exhaustive]
1034pub struct AvailableCommandsUpdate {
1035 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, SkipListener>>")]
1037 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
1038 pub available_commands: Vec<AvailableCommand>,
1039 #[serde_as(deserialize_as = "DefaultOnError")]
1045 #[schemars(extend("x-deserialize-default-on-error" = true))]
1046 #[serde(default)]
1047 #[serde(rename = "_meta")]
1048 pub meta: Option<Meta>,
1049}
1050
1051impl AvailableCommandsUpdate {
1052 #[must_use]
1054 pub fn new(available_commands: Vec<AvailableCommand>) -> Self {
1055 Self {
1056 available_commands,
1057 meta: None,
1058 }
1059 }
1060
1061 #[must_use]
1067 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1068 self.meta = meta.into_option();
1069 self
1070 }
1071}
1072
1073#[serde_as]
1075#[skip_serializing_none]
1076#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1077#[serde(rename_all = "camelCase")]
1078#[non_exhaustive]
1079pub struct AvailableCommand {
1080 pub name: String,
1082 pub description: String,
1084 #[serde_as(deserialize_as = "DefaultOnError")]
1086 #[schemars(extend("x-deserialize-default-on-error" = true))]
1087 #[serde(default)]
1088 pub input: Option<AvailableCommandInput>,
1089 #[serde_as(deserialize_as = "DefaultOnError")]
1095 #[schemars(extend("x-deserialize-default-on-error" = true))]
1096 #[serde(default)]
1097 #[serde(rename = "_meta")]
1098 pub meta: Option<Meta>,
1099}
1100
1101impl AvailableCommand {
1102 #[must_use]
1104 pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
1105 Self {
1106 name: name.into(),
1107 description: description.into(),
1108 input: None,
1109 meta: None,
1110 }
1111 }
1112
1113 #[must_use]
1115 pub fn input(mut self, input: impl IntoOption<AvailableCommandInput>) -> Self {
1116 self.input = input.into_option();
1117 self
1118 }
1119
1120 #[must_use]
1126 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1127 self.meta = meta.into_option();
1128 self
1129 }
1130}
1131
1132#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1134#[serde(tag = "type", rename_all = "snake_case")]
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#[non_exhaustive]
1364pub enum RequestPermissionSubject {
1365 ToolCall(Box<ToolCallPermissionSubject>),
1367 Command(CommandPermissionSubject),
1369 #[serde(untagged)]
1380 Other(OtherRequestPermissionSubject),
1381}
1382
1383impl From<ToolCallPermissionSubject> for RequestPermissionSubject {
1384 fn from(subject: ToolCallPermissionSubject) -> Self {
1385 Self::ToolCall(Box::new(subject))
1386 }
1387}
1388
1389impl From<ToolCallUpdate> for RequestPermissionSubject {
1390 fn from(tool_call: ToolCallUpdate) -> Self {
1391 ToolCallPermissionSubject::new(tool_call).into()
1392 }
1393}
1394
1395impl From<CommandPermissionSubject> for RequestPermissionSubject {
1396 fn from(subject: CommandPermissionSubject) -> Self {
1397 Self::Command(subject)
1398 }
1399}
1400
1401#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1403#[serde(rename_all = "camelCase")]
1404#[non_exhaustive]
1405pub struct ToolCallPermissionSubject {
1406 pub tool_call: ToolCallUpdate,
1408}
1409
1410impl ToolCallPermissionSubject {
1411 #[must_use]
1413 pub fn new(tool_call: ToolCallUpdate) -> Self {
1414 Self { tool_call }
1415 }
1416}
1417
1418#[serde_as]
1420#[skip_serializing_none]
1421#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1422#[serde(rename_all = "camelCase")]
1423#[non_exhaustive]
1424pub struct CommandPermissionSubject {
1425 pub command: String,
1427 pub cwd: AbsolutePath,
1429 #[serde_as(deserialize_as = "DefaultOnError")]
1431 #[schemars(extend("x-deserialize-default-on-error" = true))]
1432 #[serde(default)]
1433 pub tool_call_id: Option<ToolCallId>,
1434 #[serde_as(deserialize_as = "DefaultOnError")]
1436 #[schemars(extend("x-deserialize-default-on-error" = true))]
1437 #[serde(default)]
1438 pub terminal_id: Option<TerminalId>,
1439 #[serde_as(deserialize_as = "DefaultOnError")]
1445 #[schemars(extend("x-deserialize-default-on-error" = true))]
1446 #[serde(default)]
1447 #[serde(rename = "_meta")]
1448 pub meta: Option<Meta>,
1449}
1450
1451impl CommandPermissionSubject {
1452 #[must_use]
1454 pub fn new(command: impl Into<String>, cwd: impl Into<AbsolutePath>) -> Self {
1455 Self {
1456 command: command.into(),
1457 cwd: cwd.into(),
1458 tool_call_id: None,
1459 terminal_id: None,
1460 meta: None,
1461 }
1462 }
1463
1464 #[must_use]
1466 pub fn tool_call_id(mut self, tool_call_id: impl IntoOption<ToolCallId>) -> Self {
1467 self.tool_call_id = tool_call_id.into_option();
1468 self
1469 }
1470
1471 #[must_use]
1473 pub fn terminal_id(mut self, terminal_id: impl IntoOption<TerminalId>) -> Self {
1474 self.terminal_id = terminal_id.into_option();
1475 self
1476 }
1477
1478 #[must_use]
1480 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1481 self.meta = meta.into_option();
1482 self
1483 }
1484}
1485
1486#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq)]
1488#[schemars(inline)]
1489#[schemars(transform = other_request_permission_subject_schema)]
1490#[serde(rename_all = "camelCase")]
1491#[non_exhaustive]
1492pub struct OtherRequestPermissionSubject {
1493 #[serde(rename = "type")]
1499 pub type_: String,
1500 #[serde(flatten)]
1502 pub fields: BTreeMap<String, serde_json::Value>,
1503}
1504
1505impl OtherRequestPermissionSubject {
1506 #[must_use]
1508 pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
1509 fields.remove("type");
1510 Self {
1511 type_: type_.into(),
1512 fields,
1513 }
1514 }
1515}
1516
1517impl<'de> Deserialize<'de> for OtherRequestPermissionSubject {
1518 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1519 where
1520 D: serde::Deserializer<'de>,
1521 {
1522 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1523 let type_ = fields
1524 .remove("type")
1525 .ok_or_else(|| serde::de::Error::missing_field("type"))?;
1526 let serde_json::Value::String(type_) = type_ else {
1527 return Err(serde::de::Error::custom("`type` must be a string"));
1528 };
1529
1530 if is_known_request_permission_subject_type(&type_) {
1531 return Err(serde::de::Error::custom(format!(
1532 "known request permission subject `{type_}` did not match its schema"
1533 )));
1534 }
1535
1536 Ok(Self { type_, fields })
1537 }
1538}
1539
1540fn is_known_request_permission_subject_type(type_: &str) -> bool {
1541 matches!(type_, "tool_call" | "command")
1542}
1543
1544fn other_request_permission_subject_schema(schema: &mut Schema) {
1545 super::schema_util::reject_known_string_discriminators(
1546 schema,
1547 "type",
1548 &["tool_call", "command"],
1549 );
1550}
1551
1552#[serde_as]
1554#[skip_serializing_none]
1555#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1556#[serde(rename_all = "camelCase")]
1557#[non_exhaustive]
1558pub struct PermissionOption {
1559 pub option_id: PermissionOptionId,
1561 pub name: String,
1563 pub kind: PermissionOptionKind,
1565 #[serde_as(deserialize_as = "DefaultOnError")]
1571 #[schemars(extend("x-deserialize-default-on-error" = true))]
1572 #[serde(default)]
1573 #[serde(rename = "_meta")]
1574 pub meta: Option<Meta>,
1575}
1576
1577impl PermissionOption {
1578 #[must_use]
1580 pub fn new(
1581 option_id: impl Into<PermissionOptionId>,
1582 name: impl Into<String>,
1583 kind: PermissionOptionKind,
1584 ) -> Self {
1585 Self {
1586 option_id: option_id.into(),
1587 name: name.into(),
1588 kind,
1589 meta: None,
1590 }
1591 }
1592
1593 #[must_use]
1599 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1600 self.meta = meta.into_option();
1601 self
1602 }
1603}
1604
1605#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash, Display, From)]
1607#[serde(transparent)]
1608#[from(forward)]
1609#[non_exhaustive]
1610pub struct PermissionOptionId(pub Arc<str>);
1611
1612impl PermissionOptionId {
1613 #[must_use]
1615 pub fn new(id: impl Into<Self>) -> Self {
1616 id.into()
1617 }
1618}
1619
1620#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1624#[serde(rename_all = "snake_case")]
1625#[non_exhaustive]
1626pub enum PermissionOptionKind {
1627 AllowOnce,
1629 AllowAlways,
1631 RejectOnce,
1633 RejectAlways,
1635 #[serde(untagged)]
1641 Other(String),
1642}
1643
1644#[serde_as]
1646#[skip_serializing_none]
1647#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1648#[schemars(extend("x-side" = "client", "x-method" = SESSION_REQUEST_PERMISSION_METHOD_NAME))]
1649#[serde(rename_all = "camelCase")]
1650#[non_exhaustive]
1651pub struct RequestPermissionResponse {
1652 pub outcome: RequestPermissionOutcome,
1654 #[serde_as(deserialize_as = "DefaultOnError")]
1660 #[schemars(extend("x-deserialize-default-on-error" = true))]
1661 #[serde(default)]
1662 #[serde(rename = "_meta")]
1663 pub meta: Option<Meta>,
1664}
1665
1666impl RequestPermissionResponse {
1667 #[must_use]
1669 pub fn new(outcome: RequestPermissionOutcome) -> Self {
1670 Self {
1671 outcome,
1672 meta: None,
1673 }
1674 }
1675
1676 #[must_use]
1682 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1683 self.meta = meta.into_option();
1684 self
1685 }
1686}
1687
1688#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1690#[serde(tag = "outcome", rename_all = "snake_case")]
1691#[non_exhaustive]
1692pub enum RequestPermissionOutcome {
1693 Cancelled,
1701 #[serde(rename_all = "camelCase")]
1703 Selected(SelectedPermissionOutcome),
1704 #[serde(untagged)]
1715 Other(OtherRequestPermissionOutcome),
1716}
1717
1718#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq, Eq)]
1724#[schemars(inline)]
1725#[schemars(transform = other_request_permission_outcome_schema)]
1726#[serde(rename_all = "camelCase")]
1727#[non_exhaustive]
1728pub struct OtherRequestPermissionOutcome {
1729 pub outcome: String,
1735 #[serde(flatten)]
1737 pub fields: BTreeMap<String, serde_json::Value>,
1738}
1739
1740impl OtherRequestPermissionOutcome {
1741 #[must_use]
1743 pub fn new(
1744 outcome: impl Into<String>,
1745 mut fields: BTreeMap<String, serde_json::Value>,
1746 ) -> Self {
1747 fields.remove("outcome");
1748 Self {
1749 outcome: outcome.into(),
1750 fields,
1751 }
1752 }
1753}
1754
1755impl<'de> Deserialize<'de> for OtherRequestPermissionOutcome {
1756 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1757 where
1758 D: serde::Deserializer<'de>,
1759 {
1760 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1761 let outcome = fields
1762 .remove("outcome")
1763 .ok_or_else(|| serde::de::Error::missing_field("outcome"))?;
1764 let serde_json::Value::String(outcome) = outcome else {
1765 return Err(serde::de::Error::custom("`outcome` must be a string"));
1766 };
1767
1768 if is_known_request_permission_outcome(&outcome) {
1769 return Err(serde::de::Error::custom(format!(
1770 "known request permission outcome `{outcome}` did not match its schema"
1771 )));
1772 }
1773
1774 Ok(Self { outcome, fields })
1775 }
1776}
1777
1778fn is_known_request_permission_outcome(outcome: &str) -> bool {
1779 matches!(outcome, "cancelled" | "selected")
1780}
1781
1782fn other_request_permission_outcome_schema(schema: &mut Schema) {
1783 super::schema_util::reject_known_string_discriminators(
1784 schema,
1785 "outcome",
1786 &["cancelled", "selected"],
1787 );
1788}
1789
1790#[serde_as]
1792#[skip_serializing_none]
1793#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1794#[serde(rename_all = "camelCase")]
1795#[non_exhaustive]
1796pub struct SelectedPermissionOutcome {
1797 pub option_id: PermissionOptionId,
1799 #[serde_as(deserialize_as = "DefaultOnError")]
1805 #[schemars(extend("x-deserialize-default-on-error" = true))]
1806 #[serde(default)]
1807 #[serde(rename = "_meta")]
1808 pub meta: Option<Meta>,
1809}
1810
1811impl SelectedPermissionOutcome {
1812 #[must_use]
1814 pub fn new(option_id: impl Into<PermissionOptionId>) -> Self {
1815 Self {
1816 option_id: option_id.into(),
1817 meta: None,
1818 }
1819 }
1820
1821 #[must_use]
1827 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1828 self.meta = meta.into_option();
1829 self
1830 }
1831}
1832
1833#[serde_as]
1842#[skip_serializing_none]
1843#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1844#[serde(rename_all = "camelCase")]
1845#[non_exhaustive]
1846pub struct ClientCapabilities {
1847 #[cfg(feature = "unstable_auth_methods")]
1858 #[serde_as(deserialize_as = "DefaultOnError")]
1859 #[schemars(extend("x-deserialize-default-on-error" = true))]
1860 #[serde(default)]
1861 pub auth: Option<AuthCapabilities>,
1862 #[cfg(feature = "unstable_elicitation")]
1872 #[serde_as(deserialize_as = "DefaultOnError")]
1873 #[schemars(extend("x-deserialize-default-on-error" = true))]
1874 #[serde(default)]
1875 pub elicitation: Option<ElicitationCapabilities>,
1876 #[cfg(feature = "unstable_nes")]
1885 #[serde_as(deserialize_as = "DefaultOnError")]
1886 #[schemars(extend("x-deserialize-default-on-error" = true))]
1887 #[serde(default)]
1888 pub nes: Option<ClientNesCapabilities>,
1889 #[cfg(feature = "unstable_nes")]
1895 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, SkipListener>>")]
1896 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
1897 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1898 pub position_encodings: Vec<PositionEncodingKind>,
1899
1900 #[serde_as(deserialize_as = "DefaultOnError")]
1906 #[schemars(extend("x-deserialize-default-on-error" = true))]
1907 #[serde(default)]
1908 #[serde(rename = "_meta")]
1909 pub meta: Option<Meta>,
1910}
1911
1912impl ClientCapabilities {
1913 #[must_use]
1915 pub fn new() -> Self {
1916 Self::default()
1917 }
1918
1919 #[cfg(feature = "unstable_auth_methods")]
1927 #[must_use]
1928 pub fn auth(mut self, auth: impl IntoOption<AuthCapabilities>) -> Self {
1929 self.auth = auth.into_option();
1930 self
1931 }
1932
1933 #[cfg(feature = "unstable_elicitation")]
1940 #[must_use]
1941 pub fn elicitation(mut self, elicitation: impl IntoOption<ElicitationCapabilities>) -> Self {
1942 self.elicitation = elicitation.into_option();
1943 self
1944 }
1945
1946 #[cfg(feature = "unstable_nes")]
1950 #[must_use]
1951 pub fn nes(mut self, nes: impl IntoOption<ClientNesCapabilities>) -> Self {
1952 self.nes = nes.into_option();
1953 self
1954 }
1955
1956 #[cfg(feature = "unstable_nes")]
1960 #[must_use]
1961 pub fn position_encodings(mut self, position_encodings: Vec<PositionEncodingKind>) -> Self {
1962 self.position_encodings = position_encodings;
1963 self
1964 }
1965
1966 #[must_use]
1972 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1973 self.meta = meta.into_option();
1974 self
1975 }
1976}
1977
1978#[cfg(feature = "unstable_auth_methods")]
1988#[serde_as]
1989#[skip_serializing_none]
1990#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1991#[serde(rename_all = "camelCase")]
1992#[non_exhaustive]
1993pub struct AuthCapabilities {
1994 #[serde_as(deserialize_as = "DefaultOnError")]
1999 #[schemars(extend("x-deserialize-default-on-error" = true))]
2000 #[serde(default)]
2001 pub terminal: Option<TerminalAuthCapabilities>,
2002 #[serde_as(deserialize_as = "DefaultOnError")]
2008 #[schemars(extend("x-deserialize-default-on-error" = true))]
2009 #[serde(default)]
2010 #[serde(rename = "_meta")]
2011 pub meta: Option<Meta>,
2012}
2013
2014#[cfg(feature = "unstable_auth_methods")]
2015impl AuthCapabilities {
2016 #[must_use]
2018 pub fn new() -> Self {
2019 Self::default()
2020 }
2021
2022 #[must_use]
2027 pub fn terminal(mut self, terminal: impl IntoOption<TerminalAuthCapabilities>) -> Self {
2028 self.terminal = terminal.into_option();
2029 self
2030 }
2031
2032 #[must_use]
2038 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2039 self.meta = meta.into_option();
2040 self
2041 }
2042}
2043
2044#[cfg(feature = "unstable_auth_methods")]
2052#[serde_as]
2053#[skip_serializing_none]
2054#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
2055#[non_exhaustive]
2056pub struct TerminalAuthCapabilities {
2057 #[serde_as(deserialize_as = "DefaultOnError")]
2063 #[schemars(extend("x-deserialize-default-on-error" = true))]
2064 #[serde(default)]
2065 #[serde(rename = "_meta")]
2066 pub meta: Option<Meta>,
2067}
2068
2069#[cfg(feature = "unstable_auth_methods")]
2070impl TerminalAuthCapabilities {
2071 #[must_use]
2073 pub fn new() -> Self {
2074 Self::default()
2075 }
2076
2077 #[must_use]
2083 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2084 self.meta = meta.into_option();
2085 self
2086 }
2087}
2088
2089#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2095#[non_exhaustive]
2096pub struct ClientMethodNames {
2097 pub session_request_permission: &'static str,
2099 pub session_update: &'static str,
2101 #[cfg(feature = "unstable_mcp_over_acp")]
2103 pub mcp_connect: &'static str,
2104 #[cfg(feature = "unstable_mcp_over_acp")]
2106 pub mcp_message: &'static str,
2107 #[cfg(feature = "unstable_mcp_over_acp")]
2109 pub mcp_disconnect: &'static str,
2110 #[cfg(feature = "unstable_elicitation")]
2112 pub elicitation_create: &'static str,
2113 #[cfg(feature = "unstable_elicitation")]
2115 pub elicitation_complete: &'static str,
2116}
2117
2118pub const CLIENT_METHOD_NAMES: ClientMethodNames = ClientMethodNames {
2120 session_update: SESSION_UPDATE_NOTIFICATION,
2121 session_request_permission: SESSION_REQUEST_PERMISSION_METHOD_NAME,
2122 #[cfg(feature = "unstable_mcp_over_acp")]
2123 mcp_connect: MCP_CONNECT_METHOD_NAME,
2124 #[cfg(feature = "unstable_mcp_over_acp")]
2125 mcp_message: MCP_MESSAGE_METHOD_NAME,
2126 #[cfg(feature = "unstable_mcp_over_acp")]
2127 mcp_disconnect: MCP_DISCONNECT_METHOD_NAME,
2128 #[cfg(feature = "unstable_elicitation")]
2129 elicitation_create: ELICITATION_CREATE_METHOD_NAME,
2130 #[cfg(feature = "unstable_elicitation")]
2131 elicitation_complete: ELICITATION_COMPLETE_NOTIFICATION,
2132};
2133
2134pub(crate) const SESSION_UPDATE_NOTIFICATION: &str = "session/update";
2136pub(crate) const SESSION_REQUEST_PERMISSION_METHOD_NAME: &str = "session/request_permission";
2138#[cfg(feature = "unstable_elicitation")]
2140pub(crate) const ELICITATION_CREATE_METHOD_NAME: &str = "elicitation/create";
2141#[cfg(feature = "unstable_elicitation")]
2143pub(crate) const ELICITATION_COMPLETE_NOTIFICATION: &str = "elicitation/complete";
2144
2145#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
2152#[serde(untagged)]
2153#[schemars(inline)]
2154#[non_exhaustive]
2155pub enum AgentRequest {
2156 RequestPermissionRequest(Box<RequestPermissionRequest>),
2167 #[cfg(feature = "unstable_elicitation")]
2173 CreateElicitationRequest(Box<CreateElicitationRequest>),
2174 #[cfg(feature = "unstable_mcp_over_acp")]
2180 ConnectMcpRequest(Box<ConnectMcpRequest>),
2181 #[cfg(feature = "unstable_mcp_over_acp")]
2187 MessageMcpRequest(Box<MessageMcpRequest>),
2188 #[cfg(feature = "unstable_mcp_over_acp")]
2194 DisconnectMcpRequest(Box<DisconnectMcpRequest>),
2195 ExtMethodRequest(Box<ExtRequest>),
2203}
2204
2205impl AgentRequest {
2206 #[must_use]
2208 pub fn method(&self) -> &str {
2209 match self {
2210 Self::RequestPermissionRequest(_) => CLIENT_METHOD_NAMES.session_request_permission,
2211 #[cfg(feature = "unstable_elicitation")]
2212 Self::CreateElicitationRequest(_) => CLIENT_METHOD_NAMES.elicitation_create,
2213 #[cfg(feature = "unstable_mcp_over_acp")]
2214 Self::ConnectMcpRequest(_) => CLIENT_METHOD_NAMES.mcp_connect,
2215 #[cfg(feature = "unstable_mcp_over_acp")]
2216 Self::MessageMcpRequest(_) => CLIENT_METHOD_NAMES.mcp_message,
2217 #[cfg(feature = "unstable_mcp_over_acp")]
2218 Self::DisconnectMcpRequest(_) => CLIENT_METHOD_NAMES.mcp_disconnect,
2219 Self::ExtMethodRequest(ext_request) => &ext_request.method,
2220 }
2221 }
2222}
2223
2224#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
2231#[serde(untagged)]
2232#[schemars(inline)]
2233#[non_exhaustive]
2234pub enum ClientResponse {
2235 RequestPermissionResponse(Box<RequestPermissionResponse>),
2237 #[cfg(feature = "unstable_elicitation")]
2239 CreateElicitationResponse(Box<CreateElicitationResponse>),
2240 #[cfg(feature = "unstable_mcp_over_acp")]
2242 ConnectMcpResponse(Box<ConnectMcpResponse>),
2243 #[cfg(feature = "unstable_mcp_over_acp")]
2245 DisconnectMcpResponse(#[serde(default)] Box<DisconnectMcpResponse>),
2246 #[cfg(feature = "unstable_mcp_over_acp")]
2248 MessageMcpResponse(Box<MessageMcpResponse>),
2249 ExtMethodResponse(Box<ExtResponse>),
2251}
2252
2253#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
2260#[serde(untagged)]
2261#[schemars(inline)]
2262#[non_exhaustive]
2263pub enum AgentNotification {
2264 UpdateSessionNotification(Box<UpdateSessionNotification>),
2277 #[cfg(feature = "unstable_elicitation")]
2283 CompleteElicitationNotification(Box<CompleteElicitationNotification>),
2284 #[cfg(feature = "unstable_mcp_over_acp")]
2290 MessageMcpNotification(Box<MessageMcpNotification>),
2291 ExtNotification(Box<ExtNotification>),
2299}
2300
2301impl AgentNotification {
2302 #[must_use]
2304 pub fn method(&self) -> &str {
2305 match self {
2306 Self::UpdateSessionNotification(_) => CLIENT_METHOD_NAMES.session_update,
2307 #[cfg(feature = "unstable_elicitation")]
2308 Self::CompleteElicitationNotification(_) => CLIENT_METHOD_NAMES.elicitation_complete,
2309 #[cfg(feature = "unstable_mcp_over_acp")]
2310 Self::MessageMcpNotification(_) => CLIENT_METHOD_NAMES.mcp_message,
2311 Self::ExtNotification(ext_notification) => &ext_notification.method,
2312 }
2313 }
2314}
2315
2316#[cfg(test)]
2317mod tests {
2318 use super::*;
2319
2320 #[cfg(feature = "unstable_auth_methods")]
2321 #[test]
2322 fn test_client_capabilities_auth_defaults_on_malformed_value() {
2323 use serde_json::json;
2324
2325 let capabilities: ClientCapabilities = serde_json::from_value(json!({
2326 "auth": false
2327 }))
2328 .unwrap();
2329
2330 assert_eq!(capabilities.auth, None);
2331 }
2332
2333 #[test]
2334 fn test_serialization_behavior() {
2335 use serde_json::json;
2336
2337 assert_eq!(
2338 serde_json::from_value::<SessionInfoUpdate>(json!({})).unwrap(),
2339 SessionInfoUpdate {
2340 title: MaybeUndefined::Undefined,
2341 updated_at: MaybeUndefined::Undefined,
2342 meta: MaybeUndefined::Undefined
2343 }
2344 );
2345 assert_eq!(
2346 serde_json::from_value::<SessionInfoUpdate>(json!({"title": null, "updatedAt": null}))
2347 .unwrap(),
2348 SessionInfoUpdate {
2349 title: MaybeUndefined::Null,
2350 updated_at: MaybeUndefined::Null,
2351 meta: MaybeUndefined::Undefined
2352 }
2353 );
2354 assert_eq!(
2355 serde_json::from_value::<SessionInfoUpdate>(
2356 json!({"title": "title", "updatedAt": "timestamp"})
2357 )
2358 .unwrap(),
2359 SessionInfoUpdate {
2360 title: MaybeUndefined::Value("title".to_string()),
2361 updated_at: MaybeUndefined::Value("timestamp".to_string()),
2362 meta: MaybeUndefined::Undefined
2363 }
2364 );
2365
2366 let clear_meta =
2367 serde_json::from_value::<SessionInfoUpdate>(json!({"_meta": null})).unwrap();
2368 assert_eq!(clear_meta.meta, MaybeUndefined::Null);
2369
2370 let mut meta = Meta::new();
2371 meta.insert("source".to_string(), json!("session-info"));
2372
2373 assert_eq!(
2374 serde_json::from_value::<SessionInfoUpdate>(json!({"_meta": {
2375 "source": "session-info"
2376 }}))
2377 .unwrap()
2378 .meta,
2379 MaybeUndefined::Value(meta.clone())
2380 );
2381
2382 assert_eq!(
2383 serde_json::to_value(SessionInfoUpdate::new()).unwrap(),
2384 json!({})
2385 );
2386
2387 assert_eq!(
2388 serde_json::to_value(SessionInfoUpdate::new().meta(None::<Meta>)).unwrap(),
2389 json!({"_meta": null})
2390 );
2391
2392 assert_eq!(
2393 serde_json::to_value(SessionInfoUpdate::new().meta(meta)).unwrap(),
2394 json!({"_meta": {
2395 "source": "session-info"
2396 }})
2397 );
2398 assert_eq!(
2399 serde_json::to_value(SessionInfoUpdate::new().title("title")).unwrap(),
2400 json!({"title": "title"})
2401 );
2402 assert_eq!(
2403 serde_json::to_value(SessionInfoUpdate::new().title(None)).unwrap(),
2404 json!({"title": null})
2405 );
2406 assert_eq!(
2407 serde_json::to_value(
2408 SessionInfoUpdate::new()
2409 .title("title")
2410 .title(MaybeUndefined::Undefined)
2411 )
2412 .unwrap(),
2413 json!({})
2414 );
2415 }
2416
2417 #[test]
2418 fn test_content_chunk_message_id_serialization() {
2419 use serde_json::json;
2420
2421 assert_eq!(
2422 serde_json::to_value(SessionUpdate::AgentMessageChunk(ContentChunk::new(
2423 ContentBlock::Text(crate::v2::TextContent::new("Hello")),
2424 "msg_agent_c42b9",
2425 )))
2426 .unwrap(),
2427 json!({
2428 "sessionUpdate": "agent_message_chunk",
2429 "messageId": "msg_agent_c42b9",
2430 "content": {
2431 "type": "text",
2432 "text": "Hello"
2433 }
2434 })
2435 );
2436
2437 let err = serde_json::from_value::<ContentChunk>(json!({
2438 "content": {
2439 "type": "text",
2440 "text": "Hello"
2441 }
2442 }))
2443 .unwrap_err();
2444
2445 assert!(err.to_string().contains("messageId"), "{err}");
2446 }
2447
2448 #[test]
2449 fn test_tool_call_content_chunk_serialization() {
2450 use serde_json::json;
2451
2452 assert_eq!(
2453 serde_json::to_value(SessionUpdate::ToolCallContentChunk(
2454 ToolCallContentChunk::new(
2455 "call_001",
2456 crate::v2::ContentBlock::Text(crate::v2::TextContent::new("partial output")),
2457 )
2458 ))
2459 .unwrap(),
2460 json!({
2461 "sessionUpdate": "tool_call_content_chunk",
2462 "toolCallId": "call_001",
2463 "content": {
2464 "type": "content",
2465 "content": {
2466 "type": "text",
2467 "text": "partial output"
2468 }
2469 }
2470 })
2471 );
2472
2473 let err = serde_json::from_value::<ToolCallContentChunk>(json!({
2474 "content": {
2475 "type": "content",
2476 "content": {
2477 "type": "text",
2478 "text": "partial output"
2479 }
2480 }
2481 }))
2482 .unwrap_err();
2483
2484 assert!(err.to_string().contains("toolCallId"), "{err}");
2485 }
2486
2487 #[test]
2488 fn test_full_message_serialization() {
2489 use serde_json::json;
2490
2491 assert_eq!(
2492 serde_json::to_value(SessionUpdate::UserMessage(
2493 UserMessage::new("msg_user_8f7a1").content(vec![ContentBlock::Text(
2494 crate::v2::TextContent::new("Hello")
2495 )])
2496 ))
2497 .unwrap(),
2498 json!({
2499 "sessionUpdate": "user_message",
2500 "messageId": "msg_user_8f7a1",
2501 "content": [
2502 {
2503 "type": "text",
2504 "text": "Hello"
2505 }
2506 ]
2507 })
2508 );
2509
2510 assert_eq!(
2511 serde_json::to_value(SessionUpdate::AgentMessage(
2512 AgentMessage::new("msg_agent_c42b9").content(vec![ContentBlock::Text(
2513 crate::v2::TextContent::new("Hello")
2514 )])
2515 ))
2516 .unwrap(),
2517 json!({
2518 "sessionUpdate": "agent_message",
2519 "messageId": "msg_agent_c42b9",
2520 "content": [
2521 {
2522 "type": "text",
2523 "text": "Hello"
2524 }
2525 ]
2526 })
2527 );
2528
2529 assert_eq!(
2530 serde_json::to_value(SessionUpdate::AgentThought(
2531 AgentThought::new("msg_thought_a12").content(vec![ContentBlock::Text(
2532 crate::v2::TextContent::new("Need to inspect the call sites first.")
2533 )])
2534 ))
2535 .unwrap(),
2536 json!({
2537 "sessionUpdate": "agent_thought",
2538 "messageId": "msg_thought_a12",
2539 "content": [
2540 {
2541 "type": "text",
2542 "text": "Need to inspect the call sites first."
2543 }
2544 ]
2545 })
2546 );
2547 }
2548
2549 #[test]
2550 fn test_message_upsert_serialization() {
2551 use serde_json::json;
2552
2553 assert_eq!(
2554 serde_json::to_value(SessionUpdate::UserMessage(
2555 UserMessage::new("msg_empty").content(Vec::<ContentBlock>::new())
2556 ))
2557 .unwrap(),
2558 json!({
2559 "sessionUpdate": "user_message",
2560 "messageId": "msg_empty",
2561 "content": []
2562 })
2563 );
2564
2565 let empty = serde_json::from_value::<UserMessage>(json!({
2566 "messageId": "msg_empty",
2567 "content": []
2568 }))
2569 .unwrap();
2570 assert!(matches!(
2571 empty.content,
2572 MaybeUndefined::Value(ref content) if content.is_empty()
2573 ));
2574
2575 let patch = serde_json::from_value::<AgentMessage>(json!({
2576 "messageId": "msg_agent_c42b9"
2577 }))
2578 .unwrap();
2579 assert_eq!(patch.content, MaybeUndefined::Undefined);
2580 assert_eq!(patch.meta, MaybeUndefined::Undefined);
2581
2582 let malformed_meta = serde_json::from_value::<AgentMessage>(json!({
2583 "messageId": "msg_agent_c42b9",
2584 "_meta": false
2585 }))
2586 .unwrap();
2587 assert_eq!(malformed_meta.meta, MaybeUndefined::Undefined);
2588
2589 let patch = serde_json::from_value::<AgentThought>(json!({
2590 "messageId": "msg_thought_a12"
2591 }))
2592 .unwrap();
2593 assert_eq!(patch.content, MaybeUndefined::Undefined);
2594
2595 let clear = serde_json::from_value::<UserMessage>(json!({
2596 "messageId": "msg_user_8f7a1",
2597 "content": null
2598 }))
2599 .unwrap();
2600 assert_eq!(clear.content, MaybeUndefined::Null);
2601
2602 let clear_meta = serde_json::from_value::<UserMessage>(json!({
2603 "messageId": "msg_user_8f7a1",
2604 "_meta": null
2605 }))
2606 .unwrap();
2607 assert_eq!(clear_meta.meta, MaybeUndefined::Null);
2608
2609 let mut meta = Meta::new();
2610 meta.insert("source".to_string(), json!("replay"));
2611
2612 assert_eq!(
2613 serde_json::to_value(SessionUpdate::UserMessage(
2614 UserMessage::new("msg_user_8f7a1").meta(meta)
2615 ))
2616 .unwrap(),
2617 json!({
2618 "sessionUpdate": "user_message",
2619 "messageId": "msg_user_8f7a1",
2620 "_meta": {
2621 "source": "replay"
2622 }
2623 })
2624 );
2625
2626 assert_eq!(
2627 serde_json::to_value(SessionUpdate::UserMessage(
2628 UserMessage::new("msg_user_8f7a1").meta(None::<Meta>)
2629 ))
2630 .unwrap(),
2631 json!({
2632 "sessionUpdate": "user_message",
2633 "messageId": "msg_user_8f7a1",
2634 "_meta": null
2635 })
2636 );
2637 }
2638
2639 #[test]
2640 fn test_usage_update_serialization() {
2641 use serde_json::json;
2642
2643 assert_eq!(
2644 serde_json::to_value(SessionUpdate::UsageUpdate(UsageUpdate::new(
2645 53_000, 200_000
2646 )))
2647 .unwrap(),
2648 json!({
2649 "sessionUpdate": "usage_update",
2650 "used": 53000,
2651 "size": 200_000
2652 })
2653 );
2654
2655 assert_eq!(
2656 serde_json::to_value(SessionUpdate::UsageUpdate(
2657 UsageUpdate::new(53_000, 200_000).cost(Cost::new(0.045, "USD"))
2658 ))
2659 .unwrap(),
2660 json!({
2661 "sessionUpdate": "usage_update",
2662 "used": 53000,
2663 "size": 200_000,
2664 "cost": {
2665 "amount": 0.045,
2666 "currency": "USD"
2667 }
2668 })
2669 );
2670
2671 let SessionUpdate::UsageUpdate(update) = serde_json::from_value(json!({
2672 "sessionUpdate": "usage_update",
2673 "used": 53000,
2674 "size": 200_000,
2675 "cost": null
2676 }))
2677 .unwrap() else {
2678 panic!("expected usage update");
2679 };
2680
2681 assert_eq!(update.cost, None);
2682 }
2683
2684 #[test]
2685 fn test_state_update_serialization() {
2686 use serde_json::json;
2687
2688 assert_eq!(
2689 serde_json::to_value(SessionUpdate::StateUpdate(StateUpdate::Running(
2690 RunningStateUpdate::new()
2691 )))
2692 .unwrap(),
2693 json!({
2694 "sessionUpdate": "state_update",
2695 "state": "running"
2696 })
2697 );
2698
2699 assert_eq!(
2700 serde_json::to_value(SessionUpdate::StateUpdate(StateUpdate::Idle(
2701 IdleStateUpdate::new().stop_reason(StopReason::EndTurn)
2702 )))
2703 .unwrap(),
2704 json!({
2705 "sessionUpdate": "state_update",
2706 "state": "idle",
2707 "stopReason": "end_turn"
2708 })
2709 );
2710
2711 let SessionUpdate::StateUpdate(update) = serde_json::from_value(json!({
2712 "sessionUpdate": "state_update",
2713 "state": "requires_action"
2714 }))
2715 .unwrap() else {
2716 panic!("expected state update");
2717 };
2718
2719 assert!(matches!(update, StateUpdate::RequiresAction(_)));
2720
2721 let SessionUpdate::StateUpdate(StateUpdate::Idle(update)) = serde_json::from_value(json!({
2722 "sessionUpdate": "state_update",
2723 "state": "idle",
2724 "stopReason": null
2725 }))
2726 .unwrap() else {
2727 panic!("expected idle state update");
2728 };
2729
2730 assert_eq!(update.stop_reason, None);
2731
2732 let SessionUpdate::StateUpdate(StateUpdate::Other(update)) =
2733 serde_json::from_value(json!({
2734 "sessionUpdate": "state_update",
2735 "state": "_paused",
2736 "label": "Paused"
2737 }))
2738 .unwrap()
2739 else {
2740 panic!("expected unknown state update");
2741 };
2742
2743 assert_eq!(update.state, "_paused");
2744 assert_eq!(update.fields["label"], json!("Paused"));
2745 }
2746
2747 #[test]
2748 fn session_update_preserves_unknown_variant() {
2749 use serde_json::json;
2750
2751 let update: SessionUpdate = serde_json::from_value(json!({
2752 "sessionUpdate": "_status_badge",
2753 "label": "Indexing",
2754 "progress": 0.5
2755 }))
2756 .unwrap();
2757
2758 let SessionUpdate::Other(unknown) = update else {
2759 panic!("expected unknown session update");
2760 };
2761
2762 assert_eq!(unknown.session_update, "_status_badge");
2763 assert_eq!(unknown.fields.get("label"), Some(&json!("Indexing")));
2764 assert_eq!(unknown.fields.get("progress"), Some(&json!(0.5)));
2765
2766 assert_eq!(
2767 serde_json::to_value(SessionUpdate::Other(unknown)).unwrap(),
2768 json!({
2769 "sessionUpdate": "_status_badge",
2770 "label": "Indexing",
2771 "progress": 0.5
2772 })
2773 );
2774 }
2775
2776 #[test]
2777 fn terminal_session_updates_use_known_discriminators() {
2778 use serde_json::json;
2779
2780 assert_eq!(
2781 serde_json::to_value(SessionUpdate::TerminalUpdate(
2782 TerminalUpdate::new("term_1").command("cargo test")
2783 ))
2784 .unwrap(),
2785 json!({
2786 "sessionUpdate": "terminal_update",
2787 "terminalId": "term_1",
2788 "command": "cargo test"
2789 })
2790 );
2791 assert_eq!(
2792 serde_json::to_value(SessionUpdate::TerminalOutputChunk(
2793 TerminalOutputChunk::new("term_1", "dGVzdAo=")
2794 ))
2795 .unwrap(),
2796 json!({
2797 "sessionUpdate": "terminal_output_chunk",
2798 "terminalId": "term_1",
2799 "data": "dGVzdAo="
2800 })
2801 );
2802 }
2803
2804 #[test]
2805 fn session_update_does_not_hide_malformed_known_terminal_variants() {
2806 use serde_json::json;
2807
2808 assert!(
2809 serde_json::from_value::<SessionUpdate>(json!({
2810 "sessionUpdate": "terminal_update"
2811 }))
2812 .is_err()
2813 );
2814 assert!(
2815 serde_json::from_value::<SessionUpdate>(json!({
2816 "sessionUpdate": "terminal_output_chunk",
2817 "terminalId": "term_1"
2818 }))
2819 .is_err()
2820 );
2821 }
2822
2823 #[test]
2824 fn test_plan_update_serialization() {
2825 use serde_json::json;
2826
2827 let plan_update =
2828 SessionUpdate::PlanUpdate(PlanUpdate::new(crate::v2::PlanUpdateContent::items(
2829 "plan-1",
2830 vec![crate::v2::PlanEntry::new(
2831 "Step 1",
2832 crate::v2::PlanEntryPriority::High,
2833 crate::v2::PlanEntryStatus::Pending,
2834 )],
2835 )));
2836
2837 assert_eq!(
2838 serde_json::to_value(plan_update).unwrap(),
2839 json!({
2840 "sessionUpdate": "plan_update",
2841 "plan": {
2842 "type": "items",
2843 "planId": "plan-1",
2844 "entries": [
2845 {
2846 "content": "Step 1",
2847 "priority": "high",
2848 "status": "pending"
2849 }
2850 ]
2851 }
2852 })
2853 );
2854 }
2855
2856 #[cfg(feature = "unstable_plan_operations")]
2857 #[test]
2858 fn test_plan_removed_serialization() {
2859 use serde_json::json;
2860
2861 assert_eq!(
2862 serde_json::to_value(SessionUpdate::PlanRemoved(PlanRemoved::new("plan-1"))).unwrap(),
2863 json!({
2864 "sessionUpdate": "plan_removed",
2865 "planId": "plan-1"
2866 })
2867 );
2868 }
2869
2870 #[test]
2871 fn available_command_input_preserves_unknown_typed_variant() {
2872 use serde_json::json;
2873
2874 let input: AvailableCommandInput = serde_json::from_value(json!({
2875 "type": "_choices",
2876 "hint": "Pick one",
2877 "options": ["fast", "careful"]
2878 }))
2879 .unwrap();
2880
2881 let AvailableCommandInput::Other(unknown) = input else {
2882 panic!("expected unknown command input");
2883 };
2884
2885 assert_eq!(unknown.type_, "_choices");
2886 assert_eq!(unknown.fields.get("hint"), Some(&json!("Pick one")));
2887 assert_eq!(
2888 unknown.fields.get("options"),
2889 Some(&json!(["fast", "careful"]))
2890 );
2891 assert_eq!(
2892 serde_json::to_value(AvailableCommandInput::Other(unknown)).unwrap(),
2893 json!({
2894 "type": "_choices",
2895 "hint": "Pick one",
2896 "options": ["fast", "careful"]
2897 })
2898 );
2899 }
2900
2901 #[test]
2902 fn available_command_input_text_uses_type_discriminator() {
2903 use serde_json::json;
2904
2905 let input = AvailableCommandInput::Text(TextCommandInput::new("Describe changes"));
2906
2907 let json = serde_json::to_value(&input).unwrap();
2908 assert_eq!(
2909 json,
2910 json!({
2911 "type": "text",
2912 "hint": "Describe changes"
2913 })
2914 );
2915
2916 let roundtripped: AvailableCommandInput = serde_json::from_value(json).unwrap();
2917 assert!(matches!(roundtripped, AvailableCommandInput::Text(_)));
2918 }
2919
2920 #[test]
2921 fn request_permission_subject_tool_call_uses_type_discriminator() {
2922 use serde_json::json;
2923
2924 let subject = RequestPermissionSubject::from(ToolCallUpdate::new("call_001"));
2925
2926 let json = serde_json::to_value(&subject).unwrap();
2927 assert_eq!(
2928 json,
2929 json!({
2930 "type": "tool_call",
2931 "toolCall": {
2932 "toolCallId": "call_001"
2933 }
2934 })
2935 );
2936
2937 let roundtripped: RequestPermissionSubject = serde_json::from_value(json).unwrap();
2938 assert!(matches!(
2939 roundtripped,
2940 RequestPermissionSubject::ToolCall(_)
2941 ));
2942 }
2943
2944 #[test]
2945 fn request_permission_subject_command_uses_type_discriminator() {
2946 use serde_json::json;
2947
2948 let mut meta = Meta::new();
2949 meta.insert("source".to_string(), json!("shell"));
2950 let subject = RequestPermissionSubject::from(
2951 CommandPermissionSubject::new("cargo test", "/workspace/project")
2952 .tool_call_id("call_001")
2953 .terminal_id("term_1")
2954 .meta(meta),
2955 );
2956
2957 let json = serde_json::to_value(&subject).unwrap();
2958 assert_eq!(
2959 json,
2960 json!({
2961 "type": "command",
2962 "command": "cargo test",
2963 "cwd": "/workspace/project",
2964 "toolCallId": "call_001",
2965 "terminalId": "term_1",
2966 "_meta": {
2967 "source": "shell"
2968 }
2969 })
2970 );
2971
2972 let roundtripped: RequestPermissionSubject = serde_json::from_value(json).unwrap();
2973 assert!(matches!(roundtripped, RequestPermissionSubject::Command(_)));
2974 }
2975
2976 #[test]
2977 fn command_permission_subject_treats_optional_association_nulls_as_omitted() {
2978 use serde_json::json;
2979
2980 let subject: RequestPermissionSubject = serde_json::from_value(json!({
2981 "type": "command",
2982 "command": "cargo test",
2983 "cwd": "/workspace/project",
2984 "toolCallId": null,
2985 "terminalId": null,
2986 "_meta": null
2987 }))
2988 .unwrap();
2989
2990 let RequestPermissionSubject::Command(subject) = subject else {
2991 panic!("expected command permission subject");
2992 };
2993 assert_eq!(subject.cwd, AbsolutePath::new("/workspace/project"));
2994 assert_eq!(subject.tool_call_id, None);
2995 assert_eq!(subject.terminal_id, None);
2996 assert_eq!(subject.meta, None);
2997 assert_eq!(
2998 serde_json::to_value(RequestPermissionSubject::Command(subject)).unwrap(),
2999 json!({
3000 "type": "command",
3001 "command": "cargo test",
3002 "cwd": "/workspace/project"
3003 })
3004 );
3005 }
3006
3007 #[test]
3008 fn request_permission_subject_preserves_unknown_variant() {
3009 use serde_json::json;
3010
3011 let subject: RequestPermissionSubject = serde_json::from_value(json!({
3012 "type": "_review",
3013 "reason": "needs-review",
3014 "retryAfterSeconds": 30
3015 }))
3016 .unwrap();
3017
3018 let RequestPermissionSubject::Other(unknown) = subject else {
3019 panic!("expected unknown permission subject");
3020 };
3021
3022 assert_eq!(unknown.type_, "_review");
3023 assert_eq!(unknown.fields.get("reason"), Some(&json!("needs-review")));
3024 assert_eq!(unknown.fields.get("retryAfterSeconds"), Some(&json!(30)));
3025 assert_eq!(
3026 serde_json::to_value(RequestPermissionSubject::Other(unknown)).unwrap(),
3027 json!({
3028 "type": "_review",
3029 "reason": "needs-review",
3030 "retryAfterSeconds": 30
3031 })
3032 );
3033 }
3034
3035 #[test]
3036 fn request_permission_subject_unknown_does_not_hide_malformed_known_variant() {
3037 use serde_json::json;
3038
3039 assert!(
3040 serde_json::from_value::<RequestPermissionSubject>(json!({
3041 "type": "tool_call"
3042 }))
3043 .is_err()
3044 );
3045 assert!(
3046 serde_json::from_value::<RequestPermissionSubject>(json!({
3047 "type": 1
3048 }))
3049 .is_err()
3050 );
3051 assert!(
3052 serde_json::from_value::<RequestPermissionSubject>(json!({
3053 "type": "command",
3054 "cwd": "/workspace/project"
3055 }))
3056 .is_err()
3057 );
3058 assert!(
3059 serde_json::from_value::<RequestPermissionSubject>(json!({
3060 "type": "command",
3061 "command": "cargo test"
3062 }))
3063 .is_err()
3064 );
3065 assert!(
3066 serde_json::from_value::<RequestPermissionSubject>(json!({
3067 "type": "command",
3068 "command": "cargo test",
3069 "cwd": null
3070 }))
3071 .is_err()
3072 );
3073 }
3074
3075 #[test]
3076 fn request_permission_title_and_description_are_separate_from_tool_call_content() {
3077 use serde_json::json;
3078
3079 let request =
3080 RequestPermissionRequest::new("sess_abc123def456", "Approve file edit?", Vec::new())
3081 .description("Allow this tool to edit src/main.rs?")
3082 .subject(RequestPermissionSubject::from(ToolCallUpdate::new(
3083 "call_001",
3084 )));
3085
3086 assert_eq!(
3087 serde_json::to_value(request).unwrap(),
3088 json!({
3089 "sessionId": "sess_abc123def456",
3090 "title": "Approve file edit?",
3091 "description": "Allow this tool to edit src/main.rs?",
3092 "subject": {
3093 "type": "tool_call",
3094 "toolCall": {
3095 "toolCallId": "call_001"
3096 }
3097 },
3098 "options": []
3099 })
3100 );
3101 }
3102
3103 #[test]
3104 fn request_permission_requires_title_and_allows_missing_subject() {
3105 use serde_json::json;
3106
3107 let request = RequestPermissionRequest::new(
3108 "sess_abc123def456",
3109 "Approve elevated permissions?",
3110 Vec::new(),
3111 );
3112
3113 assert_eq!(
3114 serde_json::to_value(request).unwrap(),
3115 json!({
3116 "sessionId": "sess_abc123def456",
3117 "title": "Approve elevated permissions?",
3118 "options": []
3119 })
3120 );
3121
3122 let missing_subject: RequestPermissionRequest = serde_json::from_value(json!({
3123 "sessionId": "sess_abc123def456",
3124 "title": "Approve elevated permissions?",
3125 "options": []
3126 }))
3127 .unwrap();
3128 assert!(missing_subject.subject.is_none());
3129
3130 let null_subject: RequestPermissionRequest = serde_json::from_value(json!({
3131 "sessionId": "sess_abc123def456",
3132 "title": "Approve elevated permissions?",
3133 "subject": null,
3134 "options": []
3135 }))
3136 .unwrap();
3137 assert!(null_subject.subject.is_none());
3138
3139 assert!(
3140 serde_json::from_value::<RequestPermissionRequest>(json!({
3141 "sessionId": "sess_abc123def456",
3142 "options": []
3143 }))
3144 .is_err()
3145 );
3146 }
3147
3148 #[test]
3149 fn request_permission_outcome_preserves_unknown_variant() {
3150 use serde_json::json;
3151
3152 let outcome: RequestPermissionOutcome = serde_json::from_value(json!({
3153 "outcome": "_defer",
3154 "reason": "needs-review",
3155 "retryAfterSeconds": 30
3156 }))
3157 .unwrap();
3158
3159 let RequestPermissionOutcome::Other(unknown) = outcome else {
3160 panic!("expected unknown permission outcome");
3161 };
3162
3163 assert_eq!(unknown.outcome, "_defer");
3164 assert_eq!(unknown.fields.get("reason"), Some(&json!("needs-review")));
3165 assert_eq!(unknown.fields.get("retryAfterSeconds"), Some(&json!(30)));
3166 assert_eq!(
3167 serde_json::to_value(RequestPermissionOutcome::Other(unknown)).unwrap(),
3168 json!({
3169 "outcome": "_defer",
3170 "reason": "needs-review",
3171 "retryAfterSeconds": 30
3172 })
3173 );
3174 }
3175
3176 #[test]
3177 fn request_permission_outcome_unknown_does_not_hide_malformed_known_variant() {
3178 use serde_json::json;
3179
3180 assert!(
3181 serde_json::from_value::<RequestPermissionOutcome>(json!({
3182 "outcome": "selected"
3183 }))
3184 .is_err()
3185 );
3186 assert!(
3187 serde_json::from_value::<RequestPermissionOutcome>(json!({
3188 "outcome": 1
3189 }))
3190 .is_err()
3191 );
3192 }
3193
3194 #[test]
3195 fn available_command_input_unknown_does_not_hide_malformed_text_variant() {
3196 use serde_json::json;
3197
3198 assert!(serde_json::from_value::<AvailableCommandInput>(json!({})).is_err());
3199 assert!(
3200 serde_json::from_value::<AvailableCommandInput>(json!({
3201 "hint": "Pick one"
3202 }))
3203 .is_err()
3204 );
3205 assert!(
3206 serde_json::from_value::<AvailableCommandInput>(json!({
3207 "type": 1,
3208 "hint": "Pick one"
3209 }))
3210 .is_err()
3211 );
3212 assert!(
3213 serde_json::from_value::<OtherAvailableCommandInput>(json!({
3214 "type": "text",
3215 "hint": "Pick one"
3216 }))
3217 .is_err()
3218 );
3219 }
3220
3221 #[cfg(feature = "unstable_nes")]
3222 #[test]
3223 fn test_client_capabilities_position_encodings_serialization() {
3224 use serde_json::json;
3225
3226 let capabilities = ClientCapabilities::new().position_encodings(vec![
3227 PositionEncodingKind::Utf32,
3228 PositionEncodingKind::Utf16,
3229 ]);
3230 let json = serde_json::to_value(&capabilities).unwrap();
3231
3232 assert_eq!(json["positionEncodings"], json!(["utf-32", "utf-16"]));
3233 }
3234
3235 #[cfg(feature = "unstable_mcp_over_acp")]
3236 #[test]
3237 fn test_agent_mcp_request_method_names() {
3238 use serde_json::json;
3239
3240 let params: serde_json::Map<String, serde_json::Value> =
3241 [("cursor".to_string(), json!("abc"))].into_iter().collect();
3242
3243 assert_eq!(CLIENT_METHOD_NAMES.mcp_connect, "mcp/connect");
3244 assert_eq!(CLIENT_METHOD_NAMES.mcp_message, "mcp/message");
3245 assert_eq!(CLIENT_METHOD_NAMES.mcp_disconnect, "mcp/disconnect");
3246
3247 assert_eq!(
3248 AgentRequest::ConnectMcpRequest(Box::new(ConnectMcpRequest::new("server-1"))).method(),
3249 "mcp/connect"
3250 );
3251 assert_eq!(
3252 AgentRequest::MessageMcpRequest(Box::new(MessageMcpRequest::new(
3253 "conn-1",
3254 "tools/list"
3255 )))
3256 .method(),
3257 "mcp/message"
3258 );
3259 assert_eq!(
3260 AgentRequest::DisconnectMcpRequest(Box::new(DisconnectMcpRequest::new("conn-1")))
3261 .method(),
3262 "mcp/disconnect"
3263 );
3264 assert_eq!(
3265 AgentNotification::MessageMcpNotification(Box::new(MessageMcpNotification::new(
3266 "conn-1",
3267 "notifications/progress"
3268 )))
3269 .method(),
3270 "mcp/message"
3271 );
3272
3273 assert_eq!(
3274 serde_json::to_value(ConnectMcpRequest::new("server-1")).unwrap(),
3275 json!({ "serverId": "server-1" })
3276 );
3277 assert_eq!(
3278 serde_json::to_value(ConnectMcpResponse::new("conn-1")).unwrap(),
3279 json!({ "connectionId": "conn-1" })
3280 );
3281 assert_eq!(
3282 serde_json::to_value(MessageMcpRequest::new("conn-1", "tools/list").params(params))
3283 .unwrap(),
3284 json!({
3285 "connectionId": "conn-1",
3286 "method": "tools/list",
3287 "params": { "cursor": "abc" }
3288 })
3289 );
3290 assert_eq!(
3291 serde_json::to_value(DisconnectMcpRequest::new("conn-1")).unwrap(),
3292 json!({ "connectionId": "conn-1" })
3293 );
3294 assert_eq!(
3295 serde_json::to_value(MessageMcpNotification::new(
3296 "conn-1",
3297 "notifications/progress"
3298 ))
3299 .unwrap(),
3300 json!({
3301 "connectionId": "conn-1",
3302 "method": "notifications/progress"
3303 })
3304 );
3305
3306 let request_with_null_params: MessageMcpRequest = serde_json::from_value(json!({
3307 "connectionId": "conn-1",
3308 "method": "tools/list",
3309 "params": null
3310 }))
3311 .unwrap();
3312 assert_eq!(request_with_null_params.params, None);
3313 }
3314
3315 #[cfg(feature = "unstable_auth_methods")]
3316 #[test]
3317 fn test_auth_capabilities_serialize_terminal_support_as_object() {
3318 use serde_json::json;
3319
3320 let capabilities = AuthCapabilities::new().terminal(TerminalAuthCapabilities::new());
3321
3322 assert_eq!(
3323 serde_json::to_value(&capabilities).unwrap(),
3324 json!({
3325 "terminal": {}
3326 })
3327 );
3328
3329 let deserialized: AuthCapabilities = serde_json::from_value(json!({
3330 "terminal": false
3331 }))
3332 .unwrap();
3333 assert!(deserialized.terminal.is_none());
3334 }
3335
3336 #[test]
3337 fn request_permission_request_rejects_malformed_options() {
3338 use serde_json::json;
3339
3340 assert!(
3341 serde_json::from_value::<RequestPermissionRequest>(json!({
3342 "sessionId": "sess-1",
3343 "title": "Run tool?",
3344 "options": "not-an-array"
3345 }))
3346 .is_err()
3347 );
3348 assert!(
3349 serde_json::from_value::<RequestPermissionRequest>(json!({
3350 "sessionId": "sess-1",
3351 "title": "Run tool?",
3352 "options": [{"optionId": "allow"}]
3353 }))
3354 .is_err()
3355 );
3356 }
3357
3358 #[cfg(feature = "unstable_plan_operations")]
3359 #[test]
3360 fn malformed_plan_removed_is_not_hidden_as_unknown_update() {
3361 use serde_json::json;
3362
3363 assert!(
3364 serde_json::from_value::<SessionUpdate>(json!({
3365 "sessionUpdate": "plan_removed"
3366 }))
3367 .is_err()
3368 );
3369 }
3370}