1use std::{borrow::Cow, collections::BTreeMap, sync::Arc};
7
8#[cfg(feature = "unstable_llm_providers")]
9use std::collections::HashMap;
10
11use derive_more::{Display, From};
12#[cfg(feature = "schemars")]
13use schemars::Schema;
14use serde::{Deserialize, Serialize};
15use serde_with::{DefaultOnError, DefaultOnNull, VecSkipError, serde_as, skip_serializing_none};
16
17use super::{
18 AbsolutePath, AvailableCommand, ClientCapabilities, ContentBlock, Error, ExtNotification,
19 ExtRequest, ExtResponse, MessageId, Meta, SessionId,
20};
21use crate::{IntoOption, ProtocolVersion};
22
23#[cfg(feature = "unstable_mcp_over_acp")]
24use super::mcp::{MCP_MESSAGE_METHOD_NAME, MessageMcpNotification};
25
26#[serde_as]
34#[skip_serializing_none]
35#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
36#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
37#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = INITIALIZE_METHOD_NAME)))]
38#[serde(rename_all = "camelCase")]
39#[non_exhaustive]
40pub struct InitializeRequest {
41 pub protocol_version: ProtocolVersion,
43 pub info: Implementation,
45 #[serde_as(deserialize_as = "DefaultOnError")]
47 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
48 #[serde(default)]
49 pub capabilities: ClientCapabilities,
50 #[serde_as(deserialize_as = "DefaultOnError")]
56 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
57 #[serde(default)]
58 #[serde(rename = "_meta")]
59 pub meta: Option<Meta>,
60}
61
62impl InitializeRequest {
63 #[must_use]
65 pub fn new(protocol_version: ProtocolVersion, info: Implementation) -> Self {
66 Self {
67 protocol_version,
68 capabilities: ClientCapabilities::default(),
69 info,
70 meta: None,
71 }
72 }
73
74 #[must_use]
76 pub fn capabilities(mut self, capabilities: ClientCapabilities) -> Self {
77 self.capabilities = capabilities;
78 self
79 }
80
81 #[must_use]
87 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
88 self.meta = meta.into_option();
89 self
90 }
91}
92
93#[serde_as]
99#[skip_serializing_none]
100#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
101#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
102#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = INITIALIZE_METHOD_NAME)))]
103#[serde(rename_all = "camelCase")]
104#[non_exhaustive]
105pub struct InitializeResponse {
106 pub protocol_version: ProtocolVersion,
111 pub info: Implementation,
113 #[serde_as(deserialize_as = "DefaultOnError")]
115 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
116 #[serde(default)]
117 pub capabilities: AgentCapabilities,
118 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
124 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
125 #[serde(default, skip_serializing_if = "Vec::is_empty")]
126 pub auth_methods: Vec<AuthMethod>,
127 #[serde_as(deserialize_as = "DefaultOnError")]
133 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
134 #[serde(default)]
135 #[serde(rename = "_meta")]
136 pub meta: Option<Meta>,
137}
138
139impl InitializeResponse {
140 #[must_use]
142 pub fn new(protocol_version: ProtocolVersion, info: Implementation) -> Self {
143 Self {
144 protocol_version,
145 capabilities: AgentCapabilities::default(),
146 auth_methods: vec![],
147 info,
148 meta: None,
149 }
150 }
151
152 #[must_use]
154 pub fn capabilities(mut self, capabilities: AgentCapabilities) -> Self {
155 self.capabilities = capabilities;
156 self
157 }
158
159 #[must_use]
164 pub fn auth_methods(mut self, auth_methods: Vec<AuthMethod>) -> Self {
165 self.auth_methods = auth_methods;
166 self
167 }
168
169 #[must_use]
175 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
176 self.meta = meta.into_option();
177 self
178 }
179}
180
181#[serde_as]
185#[skip_serializing_none]
186#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
187#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
188#[serde(rename_all = "camelCase")]
189#[non_exhaustive]
190pub struct Implementation {
191 pub name: String,
194 #[serde_as(deserialize_as = "DefaultOnError")]
199 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
200 #[serde(default)]
201 pub title: Option<String>,
202 pub version: String,
205 #[serde_as(deserialize_as = "DefaultOnError")]
211 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
212 #[serde(default)]
213 #[serde(rename = "_meta")]
214 pub meta: Option<Meta>,
215}
216
217impl Implementation {
218 #[must_use]
220 pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
221 Self {
222 name: name.into(),
223 title: None,
224 version: version.into(),
225 meta: None,
226 }
227 }
228
229 #[must_use]
234 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
235 self.title = title.into_option();
236 self
237 }
238
239 #[must_use]
245 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
246 self.meta = meta.into_option();
247 self
248 }
249}
250
251#[serde_as]
261#[skip_serializing_none]
262#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
263#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
264#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = AUTH_LOGIN_METHOD_NAME)))]
265#[serde(rename_all = "camelCase")]
266#[non_exhaustive]
267pub struct LoginAuthRequest {
268 pub method_id: AuthMethodId,
271 #[serde_as(deserialize_as = "DefaultOnError")]
277 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
278 #[serde(default)]
279 #[serde(rename = "_meta")]
280 pub meta: Option<Meta>,
281}
282
283impl LoginAuthRequest {
284 #[must_use]
286 pub fn new(method_id: impl Into<AuthMethodId>) -> Self {
287 Self {
288 method_id: method_id.into(),
289 meta: None,
290 }
291 }
292
293 #[must_use]
299 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
300 self.meta = meta.into_option();
301 self
302 }
303}
304
305crate::serde_util::default_on_null! {
306 #[serde_as]
308 #[skip_serializing_none]
309 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
310 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
311 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = AUTH_LOGIN_METHOD_NAME)))]
312 #[serde(rename_all = "camelCase")]
313 #[non_exhaustive]
314 pub struct LoginAuthResponse {
315 #[serde_as(deserialize_as = "DefaultOnError")]
321 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
322 #[serde(default)]
323 #[serde(rename = "_meta")]
324 pub meta: Option<Meta>,
325 }
326}
327
328impl LoginAuthResponse {
329 #[must_use]
331 pub fn new() -> Self {
332 Self::default()
333 }
334
335 #[must_use]
341 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
342 self.meta = meta.into_option();
343 self
344 }
345}
346
347crate::serde_util::default_on_null! {
350 #[serde_as]
358 #[skip_serializing_none]
359 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
360 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
361 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = AUTH_LOGOUT_METHOD_NAME)))]
362 #[serde(rename_all = "camelCase")]
363 #[non_exhaustive]
364 pub struct LogoutAuthRequest {
365 #[serde_as(deserialize_as = "DefaultOnError")]
371 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
372 #[serde(default)]
373 #[serde(rename = "_meta")]
374 pub meta: Option<Meta>,
375 }
376}
377
378impl LogoutAuthRequest {
379 #[must_use]
381 pub fn new() -> Self {
382 Self::default()
383 }
384
385 #[must_use]
391 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
392 self.meta = meta.into_option();
393 self
394 }
395}
396
397crate::serde_util::default_on_null! {
398 #[serde_as]
400 #[skip_serializing_none]
401 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
402 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
403 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = AUTH_LOGOUT_METHOD_NAME)))]
404 #[serde(rename_all = "camelCase")]
405 #[non_exhaustive]
406 pub struct LogoutAuthResponse {
407 #[serde_as(deserialize_as = "DefaultOnError")]
413 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
414 #[serde(default)]
415 #[serde(rename = "_meta")]
416 pub meta: Option<Meta>,
417 }
418}
419
420impl LogoutAuthResponse {
421 #[must_use]
423 pub fn new() -> Self {
424 Self::default()
425 }
426
427 #[must_use]
433 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
434 self.meta = meta.into_option();
435 self
436 }
437}
438
439#[serde_as]
445#[skip_serializing_none]
446#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
447#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
448#[serde(rename_all = "camelCase")]
449#[non_exhaustive]
450pub struct AgentAuthCapabilities {
451 #[serde_as(deserialize_as = "DefaultOnError")]
457 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
458 #[serde(default)]
459 #[serde(rename = "_meta")]
460 pub meta: Option<Meta>,
461}
462
463impl AgentAuthCapabilities {
464 #[must_use]
466 pub fn new() -> Self {
467 Self::default()
468 }
469
470 #[must_use]
476 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
477 self.meta = meta.into_option();
478 self
479 }
480}
481
482#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
484#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
485#[serde(transparent)]
486#[from(forward)]
487#[non_exhaustive]
488pub struct AuthMethodId(pub Arc<str>);
489
490impl AuthMethodId {
491 #[must_use]
493 pub fn new(id: impl Into<Self>) -> Self {
494 id.into()
495 }
496}
497
498#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
502#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
503#[serde(tag = "type", rename_all = "snake_case")]
504#[non_exhaustive]
505pub enum AuthMethod {
506 Terminal(AuthMethodTerminal),
509 Agent(AuthMethodAgent),
513 #[serde(untagged)]
523 Other(OtherAuthMethod),
524}
525
526impl AuthMethod {
527 #[must_use]
529 pub fn method_id(&self) -> &AuthMethodId {
530 match self {
531 Self::Agent(a) => &a.method_id,
532 Self::Other(a) => &a.method_id,
533 Self::Terminal(t) => &t.method_id,
534 }
535 }
536
537 #[must_use]
539 pub fn name(&self) -> &str {
540 match self {
541 Self::Agent(a) => &a.name,
542 Self::Other(a) => &a.name,
543 Self::Terminal(t) => &t.name,
544 }
545 }
546
547 #[must_use]
549 pub fn description(&self) -> Option<&str> {
550 match self {
551 Self::Agent(a) => a.description.as_deref(),
552 Self::Other(a) => a.description.as_deref(),
553 Self::Terminal(t) => t.description.as_deref(),
554 }
555 }
556
557 #[must_use]
563 pub fn meta(&self) -> Option<&Meta> {
564 match self {
565 Self::Agent(a) => a.meta.as_ref(),
566 Self::Other(a) => a.meta.as_ref(),
567 Self::Terminal(t) => t.meta.as_ref(),
568 }
569 }
570}
571
572#[serde_as]
574#[skip_serializing_none]
575#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
576#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
577#[cfg_attr(feature = "schemars", schemars(inline))]
578#[cfg_attr(feature = "schemars", schemars(transform = other_auth_method_schema))]
579#[serde(rename_all = "camelCase")]
580#[non_exhaustive]
581pub struct OtherAuthMethod {
582 #[serde(rename = "type")]
588 pub type_: String,
589 pub method_id: AuthMethodId,
591 pub name: String,
593 #[serde_as(deserialize_as = "DefaultOnError")]
595 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
596 #[serde(default)]
597 pub description: Option<String>,
598 #[serde_as(deserialize_as = "DefaultOnError")]
604 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
605 #[serde(default)]
606 #[serde(rename = "_meta")]
607 pub meta: Option<Meta>,
608 #[serde(flatten)]
610 pub fields: BTreeMap<String, serde_json::Value>,
611}
612
613impl OtherAuthMethod {
614 #[must_use]
616 pub fn new(
617 type_: impl Into<String>,
618 method_id: impl Into<AuthMethodId>,
619 name: impl Into<String>,
620 mut fields: BTreeMap<String, serde_json::Value>,
621 ) -> Self {
622 fields.remove("type");
623 fields.remove("methodId");
624 fields.remove("name");
625 fields.remove("description");
626 fields.remove("_meta");
627 Self {
628 type_: type_.into(),
629 method_id: method_id.into(),
630 name: name.into(),
631 description: None,
632 meta: None,
633 fields,
634 }
635 }
636
637 #[must_use]
639 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
640 self.description = description.into_option();
641 self
642 }
643
644 #[must_use]
650 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
651 self.meta = meta.into_option();
652 self
653 }
654}
655
656impl<'de> Deserialize<'de> for OtherAuthMethod {
657 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
658 where
659 D: serde::Deserializer<'de>,
660 {
661 #[derive(Deserialize)]
662 #[serde(rename_all = "camelCase")]
663 struct RawOtherAuthMethod {
664 #[serde(rename = "type")]
665 type_: String,
666 method_id: AuthMethodId,
667 name: String,
668 description: Option<String>,
669 #[serde(rename = "_meta")]
670 meta: Option<Meta>,
671 #[serde(flatten)]
672 fields: BTreeMap<String, serde_json::Value>,
673 }
674
675 let raw = RawOtherAuthMethod::deserialize(deserializer)?;
676 if is_known_auth_method_type(&raw.type_) {
677 return Err(serde::de::Error::custom(format!(
678 "known authentication method `{}` did not match its schema",
679 raw.type_
680 )));
681 }
682
683 Ok(Self {
684 type_: raw.type_,
685 method_id: raw.method_id,
686 name: raw.name,
687 description: raw.description,
688 meta: raw.meta,
689 fields: raw.fields,
690 })
691 }
692}
693
694fn is_known_auth_method_type(type_: &str) -> bool {
695 matches!(type_, "agent" | "terminal")
696}
697
698#[cfg(feature = "schemars")]
699fn other_auth_method_schema(schema: &mut Schema) {
700 super::schema_util::reject_known_string_discriminators(schema, "type", &["agent", "terminal"]);
701}
702
703#[serde_as]
707#[skip_serializing_none]
708#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
709#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
710#[serde(rename_all = "camelCase")]
711#[non_exhaustive]
712pub struct AuthMethodAgent {
713 pub method_id: AuthMethodId,
715 pub name: String,
717 #[serde_as(deserialize_as = "DefaultOnError")]
719 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
720 #[serde(default)]
721 pub description: Option<String>,
722 #[serde_as(deserialize_as = "DefaultOnError")]
728 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
729 #[serde(default)]
730 #[serde(rename = "_meta")]
731 pub meta: Option<Meta>,
732}
733
734impl AuthMethodAgent {
735 #[must_use]
737 pub fn new(method_id: impl Into<AuthMethodId>, name: impl Into<String>) -> Self {
738 Self {
739 method_id: method_id.into(),
740 name: name.into(),
741 description: None,
742 meta: None,
743 }
744 }
745
746 #[must_use]
748 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
749 self.description = description.into_option();
750 self
751 }
752
753 #[must_use]
759 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
760 self.meta = meta.into_option();
761 self
762 }
763}
764
765#[serde_as]
773#[skip_serializing_none]
774#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
775#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
776#[serde(rename_all = "camelCase")]
777#[non_exhaustive]
778pub struct AuthMethodTerminal {
779 pub method_id: AuthMethodId,
781 pub name: String,
783 #[serde_as(deserialize_as = "DefaultOnError")]
785 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
786 #[serde(default)]
787 pub description: Option<String>,
788 #[serde_as(deserialize_as = "DefaultOnNull")]
790 #[serde(default, skip_serializing_if = "Vec::is_empty")]
791 pub args: Vec<String>,
792 #[serde_as(deserialize_as = "DefaultOnNull")]
796 #[serde(default, skip_serializing_if = "Vec::is_empty")]
797 pub env: Vec<EnvVariable>,
798 #[serde_as(deserialize_as = "DefaultOnError")]
804 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
805 #[serde(default)]
806 #[serde(rename = "_meta")]
807 pub meta: Option<Meta>,
808}
809
810impl AuthMethodTerminal {
811 #[must_use]
813 pub fn new(method_id: impl Into<AuthMethodId>, name: impl Into<String>) -> Self {
814 Self {
815 method_id: method_id.into(),
816 name: name.into(),
817 description: None,
818 args: Vec::new(),
819 env: Vec::new(),
820 meta: None,
821 }
822 }
823
824 #[must_use]
826 pub fn args(mut self, args: Vec<String>) -> Self {
827 self.args = args;
828 self
829 }
830
831 #[must_use]
835 pub fn env(mut self, env: Vec<EnvVariable>) -> Self {
836 self.env = env;
837 self
838 }
839
840 #[must_use]
842 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
843 self.description = description.into_option();
844 self
845 }
846
847 #[must_use]
853 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
854 self.meta = meta.into_option();
855 self
856 }
857}
858
859#[serde_as]
865#[skip_serializing_none]
866#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
867#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
868#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = SESSION_NEW_METHOD_NAME)))]
869#[serde(rename_all = "camelCase")]
870#[non_exhaustive]
871pub struct NewSessionRequest {
872 pub cwd: AbsolutePath,
874 #[serde_as(deserialize_as = "DefaultOnNull")]
880 #[serde(default, skip_serializing_if = "Vec::is_empty")]
881 pub additional_directories: Vec<AbsolutePath>,
882 #[serde_as(deserialize_as = "DefaultOnNull")]
884 #[serde(default, skip_serializing_if = "Vec::is_empty")]
885 pub mcp_servers: Vec<McpServer>,
886 #[cfg(feature = "unstable_session_system_prompt")]
896 #[serde(default)]
897 pub system_prompt: Option<SystemPrompt>,
898 #[serde_as(deserialize_as = "DefaultOnError")]
904 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
905 #[serde(default)]
906 #[serde(rename = "_meta")]
907 pub meta: Option<Meta>,
908}
909
910impl NewSessionRequest {
911 #[must_use]
913 pub fn new(cwd: impl Into<AbsolutePath>) -> Self {
914 Self {
915 cwd: cwd.into(),
916 additional_directories: vec![],
917 mcp_servers: vec![],
918 #[cfg(feature = "unstable_session_system_prompt")]
919 system_prompt: None,
920 meta: None,
921 }
922 }
923
924 #[must_use]
926 pub fn additional_directories<I, P>(mut self, additional_directories: I) -> Self
927 where
928 I: IntoIterator<Item = P>,
929 P: Into<AbsolutePath>,
930 {
931 self.additional_directories = additional_directories.into_iter().map(Into::into).collect();
932 self
933 }
934
935 #[must_use]
937 pub fn mcp_servers(mut self, mcp_servers: Vec<McpServer>) -> Self {
938 self.mcp_servers = mcp_servers;
939 self
940 }
941
942 #[cfg(feature = "unstable_session_system_prompt")]
948 #[must_use]
949 pub fn system_prompt(mut self, system_prompt: impl IntoOption<SystemPrompt>) -> Self {
950 self.system_prompt = system_prompt.into_option();
951 self
952 }
953
954 #[must_use]
960 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
961 self.meta = meta.into_option();
962 self
963 }
964}
965
966#[serde_as]
974#[skip_serializing_none]
975#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
976#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
977#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = SESSION_NEW_METHOD_NAME)))]
978#[serde(rename_all = "camelCase")]
979#[non_exhaustive]
980pub struct NewSessionResponse {
981 pub session_id: SessionId,
985 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
987 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
988 #[serde(default, skip_serializing_if = "Vec::is_empty")]
989 pub config_options: Vec<SessionConfigOption>,
990 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
996 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
997 #[serde(default, skip_serializing_if = "Vec::is_empty")]
998 pub available_commands: Vec<AvailableCommand>,
999 #[serde_as(deserialize_as = "DefaultOnError")]
1005 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1006 #[serde(default)]
1007 #[serde(rename = "_meta")]
1008 pub meta: Option<Meta>,
1009}
1010
1011impl NewSessionResponse {
1012 #[must_use]
1014 pub fn new(session_id: impl Into<SessionId>) -> Self {
1015 Self {
1016 session_id: session_id.into(),
1017 config_options: Vec::new(),
1018 available_commands: Vec::new(),
1019 meta: None,
1020 }
1021 }
1022
1023 #[must_use]
1025 pub fn config_options(mut self, config_options: Vec<SessionConfigOption>) -> Self {
1026 self.config_options = config_options;
1027 self
1028 }
1029
1030 #[must_use]
1032 pub fn available_commands(mut self, available_commands: Vec<AvailableCommand>) -> Self {
1033 self.available_commands = available_commands;
1034 self
1035 }
1036
1037 #[must_use]
1043 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1044 self.meta = meta.into_option();
1045 self
1046 }
1047}
1048
1049#[cfg(feature = "unstable_session_fork")]
1062#[serde_as]
1063#[skip_serializing_none]
1064#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1065#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1066#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = SESSION_FORK_METHOD_NAME)))]
1067#[serde(rename_all = "camelCase")]
1068#[non_exhaustive]
1069pub struct ForkSessionRequest {
1070 pub session_id: SessionId,
1072 pub cwd: AbsolutePath,
1074 #[serde_as(deserialize_as = "DefaultOnNull")]
1080 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1081 pub additional_directories: Vec<AbsolutePath>,
1082 #[serde_as(deserialize_as = "DefaultOnNull")]
1084 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1085 pub mcp_servers: Vec<McpServer>,
1086 #[cfg(feature = "unstable_session_system_prompt")]
1098 #[serde(default)]
1099 pub system_prompt: Option<SystemPrompt>,
1100 #[serde_as(deserialize_as = "DefaultOnError")]
1106 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1107 #[serde(default)]
1108 #[serde(rename = "_meta")]
1109 pub meta: Option<Meta>,
1110}
1111
1112#[cfg(feature = "unstable_session_fork")]
1113impl ForkSessionRequest {
1114 #[must_use]
1116 pub fn new(session_id: impl Into<SessionId>, cwd: impl Into<AbsolutePath>) -> Self {
1117 Self {
1118 session_id: session_id.into(),
1119 cwd: cwd.into(),
1120 additional_directories: vec![],
1121 mcp_servers: vec![],
1122 #[cfg(feature = "unstable_session_system_prompt")]
1123 system_prompt: None,
1124 meta: None,
1125 }
1126 }
1127
1128 #[must_use]
1130 pub fn additional_directories<I, P>(mut self, additional_directories: I) -> Self
1131 where
1132 I: IntoIterator<Item = P>,
1133 P: Into<AbsolutePath>,
1134 {
1135 self.additional_directories = additional_directories.into_iter().map(Into::into).collect();
1136 self
1137 }
1138
1139 #[must_use]
1141 pub fn mcp_servers(mut self, mcp_servers: Vec<McpServer>) -> Self {
1142 self.mcp_servers = mcp_servers;
1143 self
1144 }
1145
1146 #[cfg(feature = "unstable_session_system_prompt")]
1152 #[must_use]
1153 pub fn system_prompt(mut self, system_prompt: impl IntoOption<SystemPrompt>) -> Self {
1154 self.system_prompt = system_prompt.into_option();
1155 self
1156 }
1157
1158 #[must_use]
1164 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1165 self.meta = meta.into_option();
1166 self
1167 }
1168}
1169
1170#[cfg(feature = "unstable_session_fork")]
1176#[serde_as]
1177#[skip_serializing_none]
1178#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1179#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1180#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = SESSION_FORK_METHOD_NAME)))]
1181#[serde(rename_all = "camelCase")]
1182#[non_exhaustive]
1183pub struct ForkSessionResponse {
1184 pub session_id: SessionId,
1186 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
1188 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
1189 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1190 pub config_options: Vec<SessionConfigOption>,
1191 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
1197 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
1198 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1199 pub available_commands: Vec<AvailableCommand>,
1200 #[serde_as(deserialize_as = "DefaultOnError")]
1206 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1207 #[serde(default)]
1208 #[serde(rename = "_meta")]
1209 pub meta: Option<Meta>,
1210}
1211
1212#[cfg(feature = "unstable_session_fork")]
1213impl ForkSessionResponse {
1214 #[must_use]
1216 pub fn new(session_id: impl Into<SessionId>) -> Self {
1217 Self {
1218 session_id: session_id.into(),
1219 config_options: Vec::new(),
1220 available_commands: Vec::new(),
1221 meta: None,
1222 }
1223 }
1224
1225 #[must_use]
1227 pub fn config_options(mut self, config_options: Vec<SessionConfigOption>) -> Self {
1228 self.config_options = config_options;
1229 self
1230 }
1231
1232 #[must_use]
1234 pub fn available_commands(mut self, available_commands: Vec<AvailableCommand>) -> Self {
1235 self.available_commands = available_commands;
1236 self
1237 }
1238
1239 #[must_use]
1245 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1246 self.meta = meta.into_option();
1247 self
1248 }
1249}
1250
1251#[serde_as]
1258#[skip_serializing_none]
1259#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1260#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1261#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = SESSION_RESUME_METHOD_NAME)))]
1262#[serde(rename_all = "camelCase")]
1263#[non_exhaustive]
1264pub struct ResumeSessionRequest {
1265 pub session_id: SessionId,
1267 pub cwd: AbsolutePath,
1269 #[serde_as(deserialize_as = "DefaultOnNull")]
1276 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1277 pub additional_directories: Vec<AbsolutePath>,
1278 #[serde_as(deserialize_as = "DefaultOnNull")]
1280 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1281 pub mcp_servers: Vec<McpServer>,
1282 #[serde(default)]
1294 pub replay_from: Option<ReplayFrom>,
1295 #[cfg(feature = "unstable_session_system_prompt")]
1307 #[serde(default)]
1308 pub system_prompt: Option<SystemPrompt>,
1309 #[serde_as(deserialize_as = "DefaultOnError")]
1315 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1316 #[serde(default)]
1317 #[serde(rename = "_meta")]
1318 pub meta: Option<Meta>,
1319}
1320
1321impl ResumeSessionRequest {
1322 #[must_use]
1324 pub fn new(session_id: impl Into<SessionId>, cwd: impl Into<AbsolutePath>) -> Self {
1325 Self {
1326 session_id: session_id.into(),
1327 cwd: cwd.into(),
1328 additional_directories: vec![],
1329 mcp_servers: vec![],
1330 replay_from: None,
1331 #[cfg(feature = "unstable_session_system_prompt")]
1332 system_prompt: None,
1333 meta: None,
1334 }
1335 }
1336
1337 #[must_use]
1339 pub fn additional_directories<I, P>(mut self, additional_directories: I) -> Self
1340 where
1341 I: IntoIterator<Item = P>,
1342 P: Into<AbsolutePath>,
1343 {
1344 self.additional_directories = additional_directories.into_iter().map(Into::into).collect();
1345 self
1346 }
1347
1348 #[must_use]
1350 pub fn mcp_servers(mut self, mcp_servers: Vec<McpServer>) -> Self {
1351 self.mcp_servers = mcp_servers;
1352 self
1353 }
1354
1355 #[must_use]
1363 pub fn replay_from(mut self, replay_from: impl IntoOption<ReplayFrom>) -> Self {
1364 self.replay_from = replay_from.into_option();
1365 self
1366 }
1367
1368 #[cfg(feature = "unstable_session_system_prompt")]
1374 #[must_use]
1375 pub fn system_prompt(mut self, system_prompt: impl IntoOption<SystemPrompt>) -> Self {
1376 self.system_prompt = system_prompt.into_option();
1377 self
1378 }
1379
1380 #[must_use]
1386 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1387 self.meta = meta.into_option();
1388 self
1389 }
1390}
1391
1392#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1396#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1397#[serde(tag = "type", rename_all = "snake_case")]
1398#[non_exhaustive]
1399pub enum ReplayFrom {
1400 Start(ReplayFromStart),
1402 #[serde(untagged)]
1413 Other(OtherReplayFrom),
1414}
1415
1416impl From<ReplayFromStart> for ReplayFrom {
1417 fn from(replay_from: ReplayFromStart) -> Self {
1418 Self::Start(replay_from)
1419 }
1420}
1421
1422#[serde_as]
1424#[skip_serializing_none]
1425#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1426#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1427#[serde(rename_all = "camelCase")]
1428#[non_exhaustive]
1429pub struct ReplayFromStart {
1430 #[serde_as(deserialize_as = "DefaultOnError")]
1436 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1437 #[serde(default)]
1438 #[serde(rename = "_meta")]
1439 pub meta: Option<Meta>,
1440}
1441
1442impl ReplayFromStart {
1443 #[must_use]
1445 pub fn new() -> Self {
1446 Self::default()
1447 }
1448
1449 #[must_use]
1455 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1456 self.meta = meta.into_option();
1457 self
1458 }
1459}
1460
1461#[serde_as]
1463#[skip_serializing_none]
1464#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1465#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
1466#[cfg_attr(feature = "schemars", schemars(inline))]
1467#[cfg_attr(feature = "schemars", schemars(transform = other_replay_from_schema))]
1468#[serde(rename_all = "camelCase")]
1469#[non_exhaustive]
1470pub struct OtherReplayFrom {
1471 #[serde(rename = "type")]
1477 pub type_: String,
1478 #[serde_as(deserialize_as = "DefaultOnError")]
1484 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1485 #[serde(default)]
1486 #[serde(rename = "_meta")]
1487 pub meta: Option<Meta>,
1488 #[serde(flatten)]
1490 pub fields: BTreeMap<String, serde_json::Value>,
1491}
1492
1493impl OtherReplayFrom {
1494 #[must_use]
1496 pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
1497 fields.remove("type");
1498 fields.remove("_meta");
1499 Self {
1500 type_: type_.into(),
1501 meta: None,
1502 fields,
1503 }
1504 }
1505
1506 #[must_use]
1512 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1513 self.meta = meta.into_option();
1514 self
1515 }
1516}
1517
1518impl<'de> Deserialize<'de> for OtherReplayFrom {
1519 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1520 where
1521 D: serde::Deserializer<'de>,
1522 {
1523 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1524 let type_ = fields
1525 .remove("type")
1526 .ok_or_else(|| serde::de::Error::missing_field("type"))?;
1527 let serde_json::Value::String(type_) = type_ else {
1528 return Err(serde::de::Error::custom("`type` must be a string"));
1529 };
1530
1531 if is_known_replay_from_type(&type_) {
1532 return Err(serde::de::Error::custom(format!(
1533 "known replay cursor `{type_}` did not match its schema"
1534 )));
1535 }
1536
1537 let meta = fields
1538 .remove("_meta")
1539 .and_then(|value| serde_json::from_value(value).ok());
1540
1541 Ok(Self {
1542 type_,
1543 meta,
1544 fields,
1545 })
1546 }
1547}
1548
1549fn is_known_replay_from_type(type_: &str) -> bool {
1550 matches!(type_, "start")
1551}
1552
1553#[cfg(feature = "schemars")]
1554fn other_replay_from_schema(schema: &mut Schema) {
1555 super::schema_util::reject_known_string_discriminators(schema, "type", &["start"]);
1556}
1557
1558crate::serde_util::default_on_null! {
1559 #[serde_as]
1567 #[skip_serializing_none]
1568 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1569 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
1570 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = SESSION_RESUME_METHOD_NAME)))]
1571 #[serde(rename_all = "camelCase")]
1572 #[non_exhaustive]
1573 pub struct ResumeSessionResponse {
1574 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
1576 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
1577 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1578 pub config_options: Vec<SessionConfigOption>,
1579 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
1585 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
1586 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1587 pub available_commands: Vec<AvailableCommand>,
1588 #[serde_as(deserialize_as = "DefaultOnError")]
1594 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1595 #[serde(default)]
1596 #[serde(rename = "_meta")]
1597 pub meta: Option<Meta>,
1598 }
1599}
1600
1601impl ResumeSessionResponse {
1602 #[must_use]
1604 pub fn new() -> Self {
1605 Self::default()
1606 }
1607
1608 #[must_use]
1610 pub fn config_options(mut self, config_options: Vec<SessionConfigOption>) -> Self {
1611 self.config_options = config_options;
1612 self
1613 }
1614
1615 #[must_use]
1617 pub fn available_commands(mut self, available_commands: Vec<AvailableCommand>) -> Self {
1618 self.available_commands = available_commands;
1619 self
1620 }
1621
1622 #[must_use]
1628 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1629 self.meta = meta.into_option();
1630 self
1631 }
1632}
1633
1634#[serde_as]
1653#[skip_serializing_none]
1654#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1655#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1656#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = SESSION_CLOSE_METHOD_NAME)))]
1657#[serde(rename_all = "camelCase")]
1658#[non_exhaustive]
1659pub struct CloseSessionRequest {
1660 pub session_id: SessionId,
1662 #[serde_as(deserialize_as = "DefaultOnError")]
1668 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1669 #[serde(default)]
1670 #[serde(rename = "_meta")]
1671 pub meta: Option<Meta>,
1672}
1673
1674impl CloseSessionRequest {
1675 #[must_use]
1677 pub fn new(session_id: impl Into<SessionId>) -> Self {
1678 Self {
1679 session_id: session_id.into(),
1680 meta: None,
1681 }
1682 }
1683
1684 #[must_use]
1690 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1691 self.meta = meta.into_option();
1692 self
1693 }
1694}
1695
1696crate::serde_util::default_on_null! {
1697 #[serde_as]
1699 #[skip_serializing_none]
1700 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1701 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
1702 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = SESSION_CLOSE_METHOD_NAME)))]
1703 #[serde(rename_all = "camelCase")]
1704 #[non_exhaustive]
1705 pub struct CloseSessionResponse {
1706 #[serde_as(deserialize_as = "DefaultOnError")]
1712 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1713 #[serde(default)]
1714 #[serde(rename = "_meta")]
1715 pub meta: Option<Meta>,
1716 }
1717}
1718
1719impl CloseSessionResponse {
1720 #[must_use]
1722 pub fn new() -> Self {
1723 Self::default()
1724 }
1725
1726 #[must_use]
1732 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1733 self.meta = meta.into_option();
1734 self
1735 }
1736}
1737
1738#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1742#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
1743#[serde(transparent)]
1744#[from(Arc<str>, String, &str, &mut str, Box<str>, Cow<'_, str>)]
1745#[non_exhaustive]
1746pub struct SessionListCursor(pub Arc<str>);
1747
1748impl SessionListCursor {
1749 #[must_use]
1751 pub fn new(cursor: impl Into<Self>) -> Self {
1752 cursor.into()
1753 }
1754}
1755
1756impl AsRef<str> for SessionListCursor {
1757 fn as_ref(&self) -> &str {
1758 &self.0
1759 }
1760}
1761
1762impl From<&String> for SessionListCursor {
1763 fn from(cursor: &String) -> Self {
1764 Self(cursor.as_str().into())
1765 }
1766}
1767
1768macro_rules! impl_session_list_cursor_option_conversion {
1769 ($source:ty) => {
1770 impl IntoOption<SessionListCursor> for $source {
1771 fn into_option(self) -> Option<SessionListCursor> {
1772 Some(self.into())
1773 }
1774 }
1775 };
1776}
1777
1778impl_session_list_cursor_option_conversion!(Arc<str>);
1779impl_session_list_cursor_option_conversion!(String);
1780impl_session_list_cursor_option_conversion!(&str);
1781impl_session_list_cursor_option_conversion!(&mut str);
1782impl_session_list_cursor_option_conversion!(&String);
1783impl_session_list_cursor_option_conversion!(Box<str>);
1784impl_session_list_cursor_option_conversion!(Cow<'_, str>);
1785
1786crate::serde_util::default_on_null! {
1787 #[serde_as]
1789 #[skip_serializing_none]
1790 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1791 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
1792 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = SESSION_LIST_METHOD_NAME)))]
1793 #[serde(rename_all = "camelCase")]
1794 #[non_exhaustive]
1795 pub struct ListSessionsRequest {
1796 #[serde(default)]
1798 pub cwd: Option<AbsolutePath>,
1799 #[serde(default)]
1801 pub cursor: Option<SessionListCursor>,
1802 #[serde_as(deserialize_as = "DefaultOnError")]
1808 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1809 #[serde(default)]
1810 #[serde(rename = "_meta")]
1811 pub meta: Option<Meta>,
1812 }
1813}
1814
1815impl ListSessionsRequest {
1816 #[must_use]
1818 pub fn new() -> Self {
1819 Self::default()
1820 }
1821
1822 #[must_use]
1824 pub fn cwd(mut self, cwd: impl IntoOption<AbsolutePath>) -> Self {
1825 self.cwd = cwd.into_option();
1826 self
1827 }
1828
1829 #[must_use]
1831 pub fn cursor(mut self, cursor: impl IntoOption<SessionListCursor>) -> Self {
1832 self.cursor = cursor.into_option();
1833 self
1834 }
1835
1836 #[must_use]
1842 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1843 self.meta = meta.into_option();
1844 self
1845 }
1846}
1847
1848#[serde_as]
1850#[skip_serializing_none]
1851#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1852#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1853#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = SESSION_LIST_METHOD_NAME)))]
1854#[serde(rename_all = "camelCase")]
1855#[non_exhaustive]
1856pub struct ListSessionsResponse {
1857 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
1859 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
1860 pub sessions: Vec<SessionInfo>,
1861 #[serde_as(deserialize_as = "DefaultOnError")]
1864 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1865 #[serde(default)]
1866 pub next_cursor: Option<SessionListCursor>,
1867 #[serde_as(deserialize_as = "DefaultOnError")]
1873 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1874 #[serde(default)]
1875 #[serde(rename = "_meta")]
1876 pub meta: Option<Meta>,
1877}
1878
1879impl ListSessionsResponse {
1880 #[must_use]
1882 pub fn new(sessions: Vec<SessionInfo>) -> Self {
1883 Self {
1884 sessions,
1885 next_cursor: None,
1886 meta: None,
1887 }
1888 }
1889
1890 #[must_use]
1892 pub fn next_cursor(mut self, next_cursor: impl IntoOption<SessionListCursor>) -> Self {
1893 self.next_cursor = next_cursor.into_option();
1894 self
1895 }
1896
1897 #[must_use]
1903 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1904 self.meta = meta.into_option();
1905 self
1906 }
1907}
1908
1909#[serde_as]
1915#[skip_serializing_none]
1916#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1917#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1918#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = SESSION_DELETE_METHOD_NAME)))]
1919#[serde(rename_all = "camelCase")]
1920#[non_exhaustive]
1921pub struct DeleteSessionRequest {
1922 pub session_id: SessionId,
1924 #[serde_as(deserialize_as = "DefaultOnError")]
1930 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1931 #[serde(default)]
1932 #[serde(rename = "_meta")]
1933 pub meta: Option<Meta>,
1934}
1935
1936impl DeleteSessionRequest {
1937 #[must_use]
1939 pub fn new(session_id: impl Into<SessionId>) -> Self {
1940 Self {
1941 session_id: session_id.into(),
1942 meta: None,
1943 }
1944 }
1945
1946 #[must_use]
1952 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1953 self.meta = meta.into_option();
1954 self
1955 }
1956}
1957
1958crate::serde_util::default_on_null! {
1959 #[serde_as]
1961 #[skip_serializing_none]
1962 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1963 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
1964 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = SESSION_DELETE_METHOD_NAME)))]
1965 #[serde(rename_all = "camelCase")]
1966 #[non_exhaustive]
1967 pub struct DeleteSessionResponse {
1968 #[serde_as(deserialize_as = "DefaultOnError")]
1974 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1975 #[serde(default)]
1976 #[serde(rename = "_meta")]
1977 pub meta: Option<Meta>,
1978 }
1979}
1980
1981impl DeleteSessionResponse {
1982 #[must_use]
1984 pub fn new() -> Self {
1985 Self::default()
1986 }
1987
1988 #[must_use]
1994 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1995 self.meta = meta.into_option();
1996 self
1997 }
1998}
1999
2000#[serde_as]
2002#[skip_serializing_none]
2003#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2004#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2005#[serde(rename_all = "camelCase")]
2006#[non_exhaustive]
2007pub struct SessionInfo {
2008 pub session_id: SessionId,
2010 pub cwd: AbsolutePath,
2012 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
2018 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
2019 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2020 pub additional_directories: Vec<AbsolutePath>,
2021
2022 #[serde_as(deserialize_as = "DefaultOnError")]
2024 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2025 #[serde(default)]
2026 pub title: Option<String>,
2027 #[serde_as(deserialize_as = "DefaultOnError")]
2029 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "format" = "date-time")))]
2030 #[serde(default)]
2031 pub updated_at: Option<String>,
2032 #[serde_as(deserialize_as = "DefaultOnError")]
2038 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2039 #[serde(default)]
2040 #[serde(rename = "_meta")]
2041 pub meta: Option<Meta>,
2042}
2043
2044impl SessionInfo {
2045 #[must_use]
2047 pub fn new(session_id: impl Into<SessionId>, cwd: impl Into<AbsolutePath>) -> Self {
2048 Self {
2049 session_id: session_id.into(),
2050 cwd: cwd.into(),
2051 additional_directories: vec![],
2052 title: None,
2053 updated_at: None,
2054 meta: None,
2055 }
2056 }
2057
2058 #[must_use]
2060 pub fn additional_directories<I, P>(mut self, additional_directories: I) -> Self
2061 where
2062 I: IntoIterator<Item = P>,
2063 P: Into<AbsolutePath>,
2064 {
2065 self.additional_directories = additional_directories.into_iter().map(Into::into).collect();
2066 self
2067 }
2068
2069 #[must_use]
2071 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
2072 self.title = title.into_option();
2073 self
2074 }
2075
2076 #[must_use]
2078 pub fn updated_at(mut self, updated_at: impl IntoOption<String>) -> Self {
2079 self.updated_at = updated_at.into_option();
2080 self
2081 }
2082
2083 #[must_use]
2089 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2090 self.meta = meta.into_option();
2091 self
2092 }
2093}
2094
2095#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2099#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, From, Display)]
2100#[serde(transparent)]
2101#[from(forward)]
2102#[non_exhaustive]
2103pub struct SessionConfigId(pub Arc<str>);
2104
2105impl SessionConfigId {
2106 #[must_use]
2108 pub fn new(id: impl Into<Self>) -> Self {
2109 id.into()
2110 }
2111}
2112
2113#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2115#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, From, Display)]
2116#[serde(transparent)]
2117#[from(forward)]
2118#[non_exhaustive]
2119pub struct SessionConfigValueId(pub Arc<str>);
2120
2121impl SessionConfigValueId {
2122 #[must_use]
2124 pub fn new(id: impl Into<Self>) -> Self {
2125 id.into()
2126 }
2127}
2128
2129#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2131#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, From, Display)]
2132#[serde(transparent)]
2133#[from(forward)]
2134#[non_exhaustive]
2135pub struct SessionConfigGroupId(pub Arc<str>);
2136
2137impl SessionConfigGroupId {
2138 #[must_use]
2140 pub fn new(id: impl Into<Self>) -> Self {
2141 id.into()
2142 }
2143}
2144
2145#[serde_as]
2147#[skip_serializing_none]
2148#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2149#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2150#[serde(rename_all = "camelCase")]
2151#[non_exhaustive]
2152pub struct SessionConfigSelectOption {
2153 pub value: SessionConfigValueId,
2155 pub name: String,
2157 #[serde_as(deserialize_as = "DefaultOnError")]
2159 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2160 #[serde(default)]
2161 pub description: Option<String>,
2162 #[serde_as(deserialize_as = "DefaultOnError")]
2168 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2169 #[serde(default)]
2170 #[serde(rename = "_meta")]
2171 pub meta: Option<Meta>,
2172}
2173
2174impl SessionConfigSelectOption {
2175 #[must_use]
2177 pub fn new(value: impl Into<SessionConfigValueId>, name: impl Into<String>) -> Self {
2178 Self {
2179 value: value.into(),
2180 name: name.into(),
2181 description: None,
2182 meta: None,
2183 }
2184 }
2185
2186 #[must_use]
2188 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
2189 self.description = description.into_option();
2190 self
2191 }
2192
2193 #[must_use]
2199 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2200 self.meta = meta.into_option();
2201 self
2202 }
2203}
2204
2205#[serde_as]
2207#[skip_serializing_none]
2208#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2209#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2210#[serde(rename_all = "camelCase")]
2211#[non_exhaustive]
2212pub struct SessionConfigSelectGroup {
2213 pub group_id: SessionConfigGroupId,
2215 pub name: String,
2217 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
2219 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
2220 pub options: Vec<SessionConfigSelectOption>,
2221 #[serde_as(deserialize_as = "DefaultOnError")]
2227 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2228 #[serde(default)]
2229 #[serde(rename = "_meta")]
2230 pub meta: Option<Meta>,
2231}
2232
2233impl SessionConfigSelectGroup {
2234 #[must_use]
2236 pub fn new(
2237 group_id: impl Into<SessionConfigGroupId>,
2238 name: impl Into<String>,
2239 options: Vec<SessionConfigSelectOption>,
2240 ) -> Self {
2241 Self {
2242 group_id: group_id.into(),
2243 name: name.into(),
2244 options,
2245 meta: None,
2246 }
2247 }
2248
2249 #[must_use]
2255 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2256 self.meta = meta.into_option();
2257 self
2258 }
2259}
2260
2261#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2263#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2264#[serde(untagged)]
2265#[non_exhaustive]
2266pub enum SessionConfigSelectOptions {
2267 Ungrouped(Vec<SessionConfigSelectOption>),
2269 Grouped(Vec<SessionConfigSelectGroup>),
2271}
2272
2273impl From<Vec<SessionConfigSelectOption>> for SessionConfigSelectOptions {
2274 fn from(options: Vec<SessionConfigSelectOption>) -> Self {
2275 SessionConfigSelectOptions::Ungrouped(options)
2276 }
2277}
2278
2279impl From<Vec<SessionConfigSelectGroup>> for SessionConfigSelectOptions {
2280 fn from(groups: Vec<SessionConfigSelectGroup>) -> Self {
2281 SessionConfigSelectOptions::Grouped(groups)
2282 }
2283}
2284
2285#[skip_serializing_none]
2287#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2288#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2289#[serde(rename_all = "camelCase")]
2290#[non_exhaustive]
2291pub struct SessionConfigSelect {
2292 pub current_value: SessionConfigValueId,
2294 pub options: SessionConfigSelectOptions,
2296}
2297
2298impl SessionConfigSelect {
2299 #[must_use]
2301 pub fn new(
2302 current_value: impl Into<SessionConfigValueId>,
2303 options: impl Into<SessionConfigSelectOptions>,
2304 ) -> Self {
2305 Self {
2306 current_value: current_value.into(),
2307 options: options.into(),
2308 }
2309 }
2310}
2311
2312#[skip_serializing_none]
2314#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2315#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2316#[serde(rename_all = "camelCase")]
2317#[non_exhaustive]
2318pub struct SessionConfigBoolean {
2319 pub current_value: bool,
2321}
2322
2323impl SessionConfigBoolean {
2324 #[must_use]
2326 pub fn new(current_value: bool) -> Self {
2327 Self { current_value }
2328 }
2329}
2330
2331#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2341#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2342#[serde(rename_all = "snake_case")]
2343#[non_exhaustive]
2344pub enum SessionConfigOptionCategory {
2345 Mode,
2347 Model,
2349 ModelConfig,
2351 ThoughtLevel,
2353 #[serde(untagged)]
2359 Other(String),
2360}
2361
2362#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2364#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2365#[serde(tag = "type", rename_all = "snake_case")]
2366#[non_exhaustive]
2367pub enum SessionConfigKind {
2368 Select(SessionConfigSelect),
2370 Boolean(SessionConfigBoolean),
2372 #[serde(untagged)]
2382 Other(OtherSessionConfigKind),
2383}
2384
2385#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2387#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
2388#[cfg_attr(feature = "schemars", schemars(inline))]
2389#[cfg_attr(feature = "schemars", schemars(transform = other_session_config_kind_schema))]
2390#[serde(rename_all = "camelCase")]
2391#[non_exhaustive]
2392pub struct OtherSessionConfigKind {
2393 #[serde(rename = "type")]
2399 pub type_: String,
2400 #[serde(flatten)]
2402 pub fields: BTreeMap<String, serde_json::Value>,
2403}
2404
2405impl OtherSessionConfigKind {
2406 #[must_use]
2408 pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
2409 fields.remove("type");
2410 fields.remove("_meta");
2411 Self {
2412 type_: type_.into(),
2413 fields,
2414 }
2415 }
2416}
2417
2418impl<'de> Deserialize<'de> for OtherSessionConfigKind {
2419 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2420 where
2421 D: serde::Deserializer<'de>,
2422 {
2423 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
2424 let type_ = fields
2425 .remove("type")
2426 .ok_or_else(|| serde::de::Error::missing_field("type"))?;
2427 let serde_json::Value::String(type_) = type_ else {
2428 return Err(serde::de::Error::custom("`type` must be a string"));
2429 };
2430
2431 if is_known_session_config_kind_type(&type_) {
2432 return Err(serde::de::Error::custom(format!(
2433 "known session configuration option `{type_}` did not match its schema"
2434 )));
2435 }
2436
2437 Ok(Self { type_, fields })
2438 }
2439}
2440
2441fn is_known_session_config_kind_type(type_: &str) -> bool {
2442 matches!(type_, "select" | "boolean")
2443}
2444
2445#[cfg(feature = "schemars")]
2446fn other_session_config_kind_schema(schema: &mut Schema) {
2447 super::schema_util::reject_known_string_discriminators(schema, "type", &["select", "boolean"]);
2448}
2449
2450#[serde_as]
2452#[skip_serializing_none]
2453#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2454#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2455#[serde(rename_all = "camelCase")]
2456#[non_exhaustive]
2457pub struct SessionConfigOption {
2458 pub config_id: SessionConfigId,
2460 pub name: String,
2462 #[serde_as(deserialize_as = "DefaultOnError")]
2464 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2465 #[serde(default)]
2466 pub description: Option<String>,
2467 #[serde_as(deserialize_as = "DefaultOnError")]
2469 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2470 #[serde(default)]
2471 pub category: Option<SessionConfigOptionCategory>,
2472 #[serde(flatten)]
2474 pub kind: SessionConfigKind,
2475 #[serde_as(deserialize_as = "DefaultOnError")]
2481 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2482 #[serde(default)]
2483 #[serde(rename = "_meta")]
2484 pub meta: Option<Meta>,
2485}
2486
2487impl SessionConfigOption {
2488 #[must_use]
2490 pub fn new(
2491 config_id: impl Into<SessionConfigId>,
2492 name: impl Into<String>,
2493 kind: SessionConfigKind,
2494 ) -> Self {
2495 Self {
2496 config_id: config_id.into(),
2497 name: name.into(),
2498 description: None,
2499 category: None,
2500 kind,
2501 meta: None,
2502 }
2503 }
2504
2505 #[must_use]
2507 pub fn select(
2508 config_id: impl Into<SessionConfigId>,
2509 name: impl Into<String>,
2510 current_value: impl Into<SessionConfigValueId>,
2511 options: impl Into<SessionConfigSelectOptions>,
2512 ) -> Self {
2513 Self::new(
2514 config_id,
2515 name,
2516 SessionConfigKind::Select(SessionConfigSelect::new(current_value, options)),
2517 )
2518 }
2519
2520 #[must_use]
2522 pub fn boolean(
2523 config_id: impl Into<SessionConfigId>,
2524 name: impl Into<String>,
2525 current_value: bool,
2526 ) -> Self {
2527 Self::new(
2528 config_id,
2529 name,
2530 SessionConfigKind::Boolean(SessionConfigBoolean::new(current_value)),
2531 )
2532 }
2533
2534 #[must_use]
2536 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
2537 self.description = description.into_option();
2538 self
2539 }
2540
2541 #[must_use]
2543 pub fn category(mut self, category: impl IntoOption<SessionConfigOptionCategory>) -> Self {
2544 self.category = category.into_option();
2545 self
2546 }
2547
2548 #[must_use]
2554 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2555 self.meta = meta.into_option();
2556 self
2557 }
2558}
2559
2560#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2569#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
2570#[serde(tag = "type", rename_all = "snake_case")]
2571#[non_exhaustive]
2572pub enum SessionConfigOptionValue {
2573 Id {
2575 value: SessionConfigValueId,
2577 },
2578 Boolean {
2580 value: bool,
2582 },
2583 #[serde(untagged)]
2589 Other(OtherSessionConfigOptionValue),
2590}
2591
2592#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2594#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
2595#[cfg_attr(feature = "schemars", schemars(inline))]
2596#[cfg_attr(feature = "schemars", schemars(transform = other_session_config_option_value_schema))]
2597#[serde(rename_all = "camelCase")]
2598#[non_exhaustive]
2599pub struct OtherSessionConfigOptionValue {
2600 #[serde(rename = "type")]
2606 pub type_: String,
2607 pub value: serde_json::Value,
2609 #[serde(flatten)]
2611 pub fields: BTreeMap<String, serde_json::Value>,
2612}
2613
2614impl OtherSessionConfigOptionValue {
2615 #[must_use]
2617 pub fn new(
2618 type_: impl Into<String>,
2619 value: serde_json::Value,
2620 mut fields: BTreeMap<String, serde_json::Value>,
2621 ) -> Self {
2622 fields.remove("type");
2623 fields.remove("value");
2624 fields.remove("_meta");
2625 Self {
2626 type_: type_.into(),
2627 value,
2628 fields,
2629 }
2630 }
2631}
2632
2633impl<'de> Deserialize<'de> for OtherSessionConfigOptionValue {
2634 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2635 where
2636 D: serde::Deserializer<'de>,
2637 {
2638 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
2639 let type_ = fields
2640 .remove("type")
2641 .ok_or_else(|| serde::de::Error::missing_field("type"))?;
2642 let serde_json::Value::String(type_) = type_ else {
2643 return Err(serde::de::Error::custom("`type` must be a string"));
2644 };
2645
2646 if is_known_session_config_option_value_type(&type_) {
2647 return Err(serde::de::Error::custom(format!(
2648 "known session configuration option value `{type_}` did not match its schema"
2649 )));
2650 }
2651
2652 let value = fields
2653 .remove("value")
2654 .ok_or_else(|| serde::de::Error::missing_field("value"))?;
2655
2656 Ok(Self {
2657 type_,
2658 value,
2659 fields,
2660 })
2661 }
2662}
2663
2664impl<'de> Deserialize<'de> for SessionConfigOptionValue {
2665 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2666 where
2667 D: serde::Deserializer<'de>,
2668 {
2669 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
2670 let type_ = fields.remove("type");
2671 let value = fields
2672 .remove("value")
2673 .ok_or_else(|| serde::de::Error::missing_field("value"))?;
2674
2675 let type_ = type_.ok_or_else(|| serde::de::Error::missing_field("type"))?;
2676
2677 let serde_json::Value::String(type_) = type_ else {
2678 return Err(serde::de::Error::custom("`type` must be a string"));
2679 };
2680
2681 match type_.as_str() {
2682 "id" => {
2683 let value = serde_json::from_value(value).map_err(|error| {
2684 serde::de::Error::custom(format!(
2685 "`value` must be a string for `type: id`: {error}"
2686 ))
2687 })?;
2688 Ok(Self::Id { value })
2689 }
2690 "boolean" => {
2691 let value = serde_json::from_value(value).map_err(|error| {
2692 serde::de::Error::custom(format!(
2693 "`value` must be a boolean for `type: boolean`: {error}"
2694 ))
2695 })?;
2696 Ok(Self::Boolean { value })
2697 }
2698 _ => Ok(Self::Other(OtherSessionConfigOptionValue {
2699 type_,
2700 value,
2701 fields,
2702 })),
2703 }
2704 }
2705}
2706
2707fn is_known_session_config_option_value_type(type_: &str) -> bool {
2708 matches!(type_, "id" | "boolean")
2709}
2710
2711#[cfg(feature = "schemars")]
2712fn other_session_config_option_value_schema(schema: &mut Schema) {
2713 super::schema_util::reject_known_string_discriminators(schema, "type", &["id", "boolean"]);
2714}
2715
2716impl SessionConfigOptionValue {
2717 #[must_use]
2719 pub fn id(id: impl Into<SessionConfigValueId>) -> Self {
2720 Self::Id { value: id.into() }
2721 }
2722
2723 #[must_use]
2725 pub fn boolean(val: bool) -> Self {
2726 Self::Boolean { value: val }
2727 }
2728
2729 #[must_use]
2732 pub fn as_id(&self) -> Option<&SessionConfigValueId> {
2733 match self {
2734 Self::Id { value } => Some(value),
2735 _ => None,
2736 }
2737 }
2738
2739 #[must_use]
2741 pub fn as_bool(&self) -> Option<bool> {
2742 match self {
2743 Self::Boolean { value } => Some(*value),
2744 _ => None,
2745 }
2746 }
2747}
2748
2749impl From<SessionConfigValueId> for SessionConfigOptionValue {
2750 fn from(value: SessionConfigValueId) -> Self {
2751 Self::Id { value }
2752 }
2753}
2754
2755impl From<bool> for SessionConfigOptionValue {
2756 fn from(value: bool) -> Self {
2757 Self::Boolean { value }
2758 }
2759}
2760
2761impl From<&str> for SessionConfigOptionValue {
2762 fn from(value: &str) -> Self {
2763 Self::Id {
2764 value: SessionConfigValueId::new(value),
2765 }
2766 }
2767}
2768
2769#[serde_as]
2771#[skip_serializing_none]
2772#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2773#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2774#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = SESSION_SET_CONFIG_OPTION_METHOD_NAME)))]
2775#[serde(rename_all = "camelCase")]
2776#[non_exhaustive]
2777pub struct SetSessionConfigOptionRequest {
2778 pub session_id: SessionId,
2780 pub config_id: SessionConfigId,
2782 #[serde(flatten)]
2786 pub value: SessionConfigOptionValue,
2787 #[serde_as(deserialize_as = "DefaultOnError")]
2793 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2794 #[serde(default)]
2795 #[serde(rename = "_meta")]
2796 pub meta: Option<Meta>,
2797}
2798
2799impl SetSessionConfigOptionRequest {
2800 #[must_use]
2802 pub fn new(
2803 session_id: impl Into<SessionId>,
2804 config_id: impl Into<SessionConfigId>,
2805 value: impl Into<SessionConfigOptionValue>,
2806 ) -> Self {
2807 Self {
2808 session_id: session_id.into(),
2809 config_id: config_id.into(),
2810 value: value.into(),
2811 meta: None,
2812 }
2813 }
2814
2815 #[must_use]
2821 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2822 self.meta = meta.into_option();
2823 self
2824 }
2825}
2826
2827#[serde_as]
2829#[skip_serializing_none]
2830#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2831#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2832#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = SESSION_SET_CONFIG_OPTION_METHOD_NAME)))]
2833#[serde(rename_all = "camelCase")]
2834#[non_exhaustive]
2835pub struct SetSessionConfigOptionResponse {
2836 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
2838 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
2839 pub config_options: Vec<SessionConfigOption>,
2840 #[serde_as(deserialize_as = "DefaultOnError")]
2846 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2847 #[serde(default)]
2848 #[serde(rename = "_meta")]
2849 pub meta: Option<Meta>,
2850}
2851
2852impl SetSessionConfigOptionResponse {
2853 #[must_use]
2855 pub fn new(config_options: Vec<SessionConfigOption>) -> Self {
2856 Self {
2857 config_options,
2858 meta: None,
2859 }
2860 }
2861
2862 #[must_use]
2868 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2869 self.meta = meta.into_option();
2870 self
2871 }
2872}
2873
2874#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2883#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2884#[serde(tag = "type", rename_all = "snake_case")]
2885#[non_exhaustive]
2886pub enum McpServer {
2887 Http(McpServerHttp),
2891 #[cfg(feature = "unstable_mcp_over_acp")]
2900 Acp(McpServerAcp),
2901 Stdio(McpServerStdio),
2905 #[serde(untagged)]
2915 Other(OtherMcpServer),
2916}
2917
2918#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2920#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
2921#[cfg_attr(feature = "schemars", schemars(inline))]
2922#[cfg_attr(feature = "schemars", schemars(transform = other_mcp_server_schema))]
2923#[serde(rename_all = "camelCase")]
2924#[non_exhaustive]
2925pub struct OtherMcpServer {
2926 #[serde(rename = "type")]
2932 pub type_: String,
2933 #[serde(flatten)]
2935 pub fields: BTreeMap<String, serde_json::Value>,
2936}
2937
2938impl OtherMcpServer {
2939 #[must_use]
2941 pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
2942 fields.remove("type");
2943 Self {
2944 type_: type_.into(),
2945 fields,
2946 }
2947 }
2948}
2949
2950impl<'de> Deserialize<'de> for OtherMcpServer {
2951 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2952 where
2953 D: serde::Deserializer<'de>,
2954 {
2955 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
2956 let type_ = fields
2957 .remove("type")
2958 .ok_or_else(|| serde::de::Error::missing_field("type"))?;
2959 let serde_json::Value::String(type_) = type_ else {
2960 return Err(serde::de::Error::custom("`type` must be a string"));
2961 };
2962
2963 if is_known_mcp_server_type(&type_) {
2964 return Err(serde::de::Error::custom(format!(
2965 "known MCP server transport `{type_}` did not match its schema"
2966 )));
2967 }
2968
2969 Ok(Self { type_, fields })
2970 }
2971}
2972
2973fn is_known_mcp_server_type(type_: &str) -> bool {
2974 match type_ {
2975 "http" | "stdio" => true,
2976 #[cfg(feature = "unstable_mcp_over_acp")]
2977 "acp" => true,
2978 _ => false,
2979 }
2980}
2981
2982#[cfg(feature = "schemars")]
2983fn other_mcp_server_schema(schema: &mut Schema) {
2984 super::schema_util::reject_known_string_discriminators(
2985 schema,
2986 "type",
2987 &[
2988 "http",
2989 "stdio",
2990 #[cfg(feature = "unstable_mcp_over_acp")]
2991 "acp",
2992 ],
2993 );
2994}
2995
2996#[serde_as]
2998#[skip_serializing_none]
2999#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3000#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3001#[serde(rename_all = "camelCase")]
3002#[non_exhaustive]
3003pub struct McpServerHttp {
3004 pub name: String,
3006 #[cfg_attr(feature = "schemars", schemars(url))]
3008 pub url: String,
3009 #[serde_as(deserialize_as = "DefaultOnNull")]
3011 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3012 pub headers: Vec<HttpHeader>,
3013 #[serde_as(deserialize_as = "DefaultOnError")]
3019 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3020 #[serde(default)]
3021 #[serde(rename = "_meta")]
3022 pub meta: Option<Meta>,
3023}
3024
3025impl McpServerHttp {
3026 #[must_use]
3028 pub fn new(name: impl Into<String>, url: impl Into<String>) -> Self {
3029 Self {
3030 name: name.into(),
3031 url: url.into(),
3032 headers: Vec::new(),
3033 meta: None,
3034 }
3035 }
3036
3037 #[must_use]
3039 pub fn headers(mut self, headers: Vec<HttpHeader>) -> Self {
3040 self.headers = headers;
3041 self
3042 }
3043
3044 #[must_use]
3050 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3051 self.meta = meta.into_option();
3052 self
3053 }
3054}
3055
3056#[cfg(feature = "unstable_mcp_over_acp")]
3065#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3066#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
3067#[serde(transparent)]
3068#[from(forward)]
3069#[non_exhaustive]
3070pub struct McpServerAcpId(pub Arc<str>);
3071
3072#[cfg(feature = "unstable_mcp_over_acp")]
3073impl McpServerAcpId {
3074 #[must_use]
3076 pub fn new(id: impl Into<Self>) -> Self {
3077 id.into()
3078 }
3079}
3080
3081#[serde_as]
3090#[skip_serializing_none]
3091#[cfg(feature = "unstable_mcp_over_acp")]
3092#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3093#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3094#[serde(rename_all = "camelCase")]
3095#[non_exhaustive]
3096pub struct McpServerAcp {
3097 pub name: String,
3099 pub server_id: McpServerAcpId,
3104 #[serde_as(deserialize_as = "DefaultOnError")]
3110 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3111 #[serde(default)]
3112 #[serde(rename = "_meta")]
3113 pub meta: Option<Meta>,
3114}
3115
3116#[cfg(feature = "unstable_mcp_over_acp")]
3117impl McpServerAcp {
3118 #[must_use]
3120 pub fn new(name: impl Into<String>, server_id: impl Into<McpServerAcpId>) -> Self {
3121 Self {
3122 name: name.into(),
3123 server_id: server_id.into(),
3124 meta: None,
3125 }
3126 }
3127
3128 #[must_use]
3134 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3135 self.meta = meta.into_option();
3136 self
3137 }
3138}
3139
3140#[serde_as]
3142#[skip_serializing_none]
3143#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3144#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3145#[serde(rename_all = "camelCase")]
3146#[non_exhaustive]
3147pub struct McpServerStdio {
3148 pub name: String,
3150 pub command: AbsolutePath,
3152 #[serde_as(deserialize_as = "DefaultOnNull")]
3154 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3155 pub args: Vec<String>,
3156 #[serde_as(deserialize_as = "DefaultOnNull")]
3158 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3159 pub env: Vec<EnvVariable>,
3160 #[serde_as(deserialize_as = "DefaultOnError")]
3166 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3167 #[serde(default)]
3168 #[serde(rename = "_meta")]
3169 pub meta: Option<Meta>,
3170}
3171
3172impl McpServerStdio {
3173 #[must_use]
3175 pub fn new(name: impl Into<String>, command: impl Into<AbsolutePath>) -> Self {
3176 Self {
3177 name: name.into(),
3178 command: command.into(),
3179 args: Vec::new(),
3180 env: Vec::new(),
3181 meta: None,
3182 }
3183 }
3184
3185 #[must_use]
3187 pub fn args(mut self, args: Vec<String>) -> Self {
3188 self.args = args;
3189 self
3190 }
3191
3192 #[must_use]
3194 pub fn env(mut self, env: Vec<EnvVariable>) -> Self {
3195 self.env = env;
3196 self
3197 }
3198
3199 #[must_use]
3205 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3206 self.meta = meta.into_option();
3207 self
3208 }
3209}
3210
3211#[serde_as]
3213#[skip_serializing_none]
3214#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3215#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3216#[serde(rename_all = "camelCase")]
3217#[non_exhaustive]
3218pub struct EnvVariable {
3219 pub name: String,
3221 pub value: String,
3223 #[serde_as(deserialize_as = "DefaultOnError")]
3229 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3230 #[serde(default)]
3231 #[serde(rename = "_meta")]
3232 pub meta: Option<Meta>,
3233}
3234
3235impl EnvVariable {
3236 #[must_use]
3238 pub fn new(name: impl Into<String>, value: impl Into<String>) -> Self {
3239 Self {
3240 name: name.into(),
3241 value: value.into(),
3242 meta: None,
3243 }
3244 }
3245
3246 #[must_use]
3252 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3253 self.meta = meta.into_option();
3254 self
3255 }
3256}
3257
3258#[serde_as]
3260#[skip_serializing_none]
3261#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3262#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3263#[serde(rename_all = "camelCase")]
3264#[non_exhaustive]
3265pub struct HttpHeader {
3266 pub name: String,
3268 pub value: String,
3270 #[serde_as(deserialize_as = "DefaultOnError")]
3276 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3277 #[serde(default)]
3278 #[serde(rename = "_meta")]
3279 pub meta: Option<Meta>,
3280}
3281
3282impl HttpHeader {
3283 #[must_use]
3285 pub fn new(name: impl Into<String>, value: impl Into<String>) -> Self {
3286 Self {
3287 name: name.into(),
3288 value: value.into(),
3289 meta: None,
3290 }
3291 }
3292
3293 #[must_use]
3299 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3300 self.meta = meta.into_option();
3301 self
3302 }
3303}
3304
3305#[cfg(feature = "unstable_session_system_prompt")]
3318#[serde_as]
3319#[skip_serializing_none]
3320#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3321#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3322#[serde(rename_all = "camelCase")]
3323#[non_exhaustive]
3324pub struct SystemPrompt {
3325 pub mode: SystemPromptMode,
3332 pub content: Vec<ContentBlock>,
3340 #[serde_as(deserialize_as = "DefaultOnError")]
3346 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3347 #[serde(default)]
3348 #[serde(rename = "_meta")]
3349 pub meta: Option<Meta>,
3350}
3351
3352#[cfg(feature = "unstable_session_system_prompt")]
3353impl SystemPrompt {
3354 #[must_use]
3359 pub fn new(content: Vec<ContentBlock>) -> Self {
3360 Self {
3361 mode: SystemPromptMode::default(),
3362 content,
3363 meta: None,
3364 }
3365 }
3366
3367 #[must_use]
3369 pub fn mode(mut self, mode: SystemPromptMode) -> Self {
3370 self.mode = mode;
3371 self
3372 }
3373
3374 #[must_use]
3380 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3381 self.meta = meta.into_option();
3382 self
3383 }
3384}
3385
3386#[cfg(feature = "unstable_session_system_prompt")]
3396#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3397#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
3398#[serde(rename_all = "snake_case")]
3399#[non_exhaustive]
3400pub enum SystemPromptMode {
3401 #[default]
3408 Append,
3409 Override,
3418 #[serde(untagged)]
3424 Other(String),
3425}
3426
3427#[serde_as]
3435#[skip_serializing_none]
3436#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3437#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3438#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = SESSION_PROMPT_METHOD_NAME)))]
3439#[serde(rename_all = "camelCase")]
3440#[non_exhaustive]
3441pub struct PromptRequest {
3442 pub session_id: SessionId,
3444 pub prompt: Vec<ContentBlock>,
3458 #[serde_as(deserialize_as = "DefaultOnError")]
3464 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3465 #[serde(default)]
3466 #[serde(rename = "_meta")]
3467 pub meta: Option<Meta>,
3468}
3469
3470impl PromptRequest {
3471 #[must_use]
3473 pub fn new(session_id: impl Into<SessionId>, prompt: Vec<ContentBlock>) -> Self {
3474 Self {
3475 session_id: session_id.into(),
3476 prompt,
3477 meta: None,
3478 }
3479 }
3480
3481 #[must_use]
3487 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3488 self.meta = meta.into_option();
3489 self
3490 }
3491}
3492
3493#[serde_as]
3504#[skip_serializing_none]
3505#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3506#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3507#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = SESSION_PROMPT_METHOD_NAME)))]
3508#[serde(rename_all = "camelCase")]
3509#[non_exhaustive]
3510pub struct PromptResponse {
3511 pub message_id: MessageId,
3519 #[serde_as(deserialize_as = "DefaultOnError")]
3525 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3526 #[serde(default)]
3527 #[serde(rename = "_meta")]
3528 pub meta: Option<Meta>,
3529}
3530
3531impl PromptResponse {
3532 #[must_use]
3534 pub fn new(message_id: impl Into<MessageId>) -> Self {
3535 Self {
3536 message_id: message_id.into(),
3537 meta: None,
3538 }
3539 }
3540
3541 #[must_use]
3547 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3548 self.meta = meta.into_option();
3549 self
3550 }
3551}
3552
3553#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3557#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
3558#[serde(tag = "stopReason", rename_all = "snake_case")]
3559#[non_exhaustive]
3560pub enum StopReason {
3561 EndTurn,
3564 MaxTokens,
3566 MaxTurnRequests,
3569 Refusal,
3573 Cancelled,
3580 Error(ErrorStopReason),
3586 #[serde(untagged)]
3592 Other(OtherStopReason),
3593}
3594
3595impl From<ErrorStopReason> for StopReason {
3596 fn from(value: ErrorStopReason) -> Self {
3597 Self::Error(value)
3598 }
3599}
3600
3601#[serde_as]
3603#[skip_serializing_none]
3604#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3605#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
3606#[serde(rename_all = "camelCase")]
3607#[non_exhaustive]
3608pub struct ErrorStopReason {
3609 #[serde_as(deserialize_as = "DefaultOnError")]
3614 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3615 #[serde(default)]
3616 pub error: Option<Box<Error>>,
3617}
3618
3619impl ErrorStopReason {
3620 #[must_use]
3622 pub fn new() -> Self {
3623 Self::default()
3624 }
3625
3626 #[must_use]
3628 pub fn error(mut self, error: impl IntoOption<Error>) -> Self {
3629 self.error = error.into_option().map(Box::new);
3630 self
3631 }
3632}
3633
3634#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3636#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
3637#[cfg_attr(feature = "schemars", schemars(inline))]
3638#[cfg_attr(feature = "schemars", schemars(transform = other_stop_reason_schema))]
3639#[serde(rename_all = "camelCase")]
3640#[non_exhaustive]
3641pub struct OtherStopReason {
3642 pub stop_reason: String,
3648 #[serde(flatten)]
3650 pub fields: BTreeMap<String, serde_json::Value>,
3651}
3652
3653impl OtherStopReason {
3654 #[must_use]
3656 pub fn new(
3657 stop_reason: impl Into<String>,
3658 mut fields: BTreeMap<String, serde_json::Value>,
3659 ) -> Self {
3660 remove_idle_state_update_fields(&mut fields);
3661 Self {
3662 stop_reason: stop_reason.into(),
3663 fields,
3664 }
3665 }
3666}
3667
3668impl<'de> Deserialize<'de> for OtherStopReason {
3669 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3670 where
3671 D: serde::Deserializer<'de>,
3672 {
3673 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
3674 let stop_reason = fields
3675 .remove("stopReason")
3676 .ok_or_else(|| serde::de::Error::missing_field("stopReason"))?;
3677 let serde_json::Value::String(stop_reason) = stop_reason else {
3678 return Err(serde::de::Error::custom("`stopReason` must be a string"));
3679 };
3680
3681 if KNOWN_STOP_REASONS.contains(&stop_reason.as_str()) {
3682 return Err(serde::de::Error::custom(format!(
3683 "known stop reason `{stop_reason}` did not match its schema"
3684 )));
3685 }
3686 remove_idle_state_update_fields(&mut fields);
3687
3688 Ok(Self {
3689 stop_reason,
3690 fields,
3691 })
3692 }
3693}
3694
3695const KNOWN_STOP_REASONS: &[&str] = &[
3696 "end_turn",
3697 "max_tokens",
3698 "max_turn_requests",
3699 "refusal",
3700 "cancelled",
3701 "error",
3702];
3703
3704fn remove_idle_state_update_fields(fields: &mut BTreeMap<String, serde_json::Value>) {
3706 fields.remove("stopReason");
3707 fields.remove("usage");
3708 fields.remove("_meta");
3709}
3710
3711#[cfg(feature = "schemars")]
3712fn other_stop_reason_schema(schema: &mut Schema) {
3713 super::schema_util::reject_known_string_discriminators(
3714 schema,
3715 "stopReason",
3716 KNOWN_STOP_REASONS,
3717 );
3718}
3719
3720#[cfg(feature = "unstable_end_turn_token_usage")]
3726#[serde_as]
3727#[skip_serializing_none]
3728#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3729#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3730#[serde(rename_all = "camelCase")]
3731#[non_exhaustive]
3732pub struct Usage {
3733 pub total_tokens: u64,
3735 pub input_tokens: u64,
3737 pub output_tokens: u64,
3739 #[serde_as(deserialize_as = "DefaultOnError")]
3741 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3742 #[serde(default)]
3743 pub thought_tokens: Option<u64>,
3744 #[serde_as(deserialize_as = "DefaultOnError")]
3746 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3747 #[serde(default)]
3748 pub cached_read_tokens: Option<u64>,
3749 #[serde_as(deserialize_as = "DefaultOnError")]
3751 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3752 #[serde(default)]
3753 pub cached_write_tokens: Option<u64>,
3754 #[serde_as(deserialize_as = "DefaultOnError")]
3760 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3761 #[serde(default)]
3762 #[serde(rename = "_meta")]
3763 pub meta: Option<Meta>,
3764}
3765
3766#[cfg(feature = "unstable_end_turn_token_usage")]
3767impl Usage {
3768 #[must_use]
3770 pub fn new(total_tokens: u64, input_tokens: u64, output_tokens: u64) -> Self {
3771 Self {
3772 total_tokens,
3773 input_tokens,
3774 output_tokens,
3775 thought_tokens: None,
3776 cached_read_tokens: None,
3777 cached_write_tokens: None,
3778 meta: None,
3779 }
3780 }
3781
3782 #[must_use]
3784 pub fn thought_tokens(mut self, thought_tokens: impl IntoOption<u64>) -> Self {
3785 self.thought_tokens = thought_tokens.into_option();
3786 self
3787 }
3788
3789 #[must_use]
3791 pub fn cached_read_tokens(mut self, cached_read_tokens: impl IntoOption<u64>) -> Self {
3792 self.cached_read_tokens = cached_read_tokens.into_option();
3793 self
3794 }
3795
3796 #[must_use]
3798 pub fn cached_write_tokens(mut self, cached_write_tokens: impl IntoOption<u64>) -> Self {
3799 self.cached_write_tokens = cached_write_tokens.into_option();
3800 self
3801 }
3802
3803 #[must_use]
3809 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3810 self.meta = meta.into_option();
3811 self
3812 }
3813}
3814
3815#[cfg(feature = "unstable_llm_providers")]
3828#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3829#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3830#[serde(rename_all = "snake_case")]
3831#[non_exhaustive]
3832#[expect(clippy::doc_markdown)]
3833pub enum LlmProtocol {
3834 Anthropic,
3836 #[serde(rename = "openai")]
3838 OpenAi,
3839 Azure,
3841 Vertex,
3843 Bedrock,
3845 #[serde(untagged)]
3851 Other(String),
3852}
3853
3854#[cfg(feature = "unstable_llm_providers")]
3860#[serde_as]
3861#[skip_serializing_none]
3862#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3863#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3864#[serde(rename_all = "camelCase")]
3865#[non_exhaustive]
3866pub struct ProviderCurrentConfig {
3867 pub api_type: LlmProtocol,
3869 #[cfg_attr(feature = "schemars", schemars(url))]
3871 pub base_url: String,
3872 #[serde_as(deserialize_as = "DefaultOnError")]
3878 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3879 #[serde(default)]
3880 #[serde(rename = "_meta")]
3881 pub meta: Option<Meta>,
3882}
3883
3884#[cfg(feature = "unstable_llm_providers")]
3885impl ProviderCurrentConfig {
3886 #[must_use]
3888 pub fn new(api_type: LlmProtocol, base_url: impl Into<String>) -> Self {
3889 Self {
3890 api_type,
3891 base_url: base_url.into(),
3892 meta: None,
3893 }
3894 }
3895
3896 #[must_use]
3902 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3903 self.meta = meta.into_option();
3904 self
3905 }
3906}
3907
3908#[cfg(feature = "unstable_llm_providers")]
3914#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3915#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
3916#[serde(transparent)]
3917#[from(forward)]
3918#[non_exhaustive]
3919pub struct ProviderId(pub Arc<str>);
3920
3921#[cfg(feature = "unstable_llm_providers")]
3922impl ProviderId {
3923 #[must_use]
3925 pub fn new(id: impl Into<Self>) -> Self {
3926 id.into()
3927 }
3928}
3929
3930#[cfg(feature = "unstable_llm_providers")]
3936#[serde_as]
3937#[skip_serializing_none]
3938#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3939#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3940#[serde(rename_all = "camelCase")]
3941#[non_exhaustive]
3942pub struct ProviderInfo {
3943 pub provider_id: ProviderId,
3945 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
3947 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
3948 pub supported: Vec<LlmProtocol>,
3949 pub required: bool,
3952 #[serde(default)]
3955 pub current: Option<ProviderCurrentConfig>,
3956 #[serde_as(deserialize_as = "DefaultOnError")]
3962 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3963 #[serde(default)]
3964 #[serde(rename = "_meta")]
3965 pub meta: Option<Meta>,
3966}
3967
3968#[cfg(feature = "unstable_llm_providers")]
3969impl ProviderInfo {
3970 #[must_use]
3972 pub fn new(
3973 provider_id: impl Into<ProviderId>,
3974 supported: Vec<LlmProtocol>,
3975 required: bool,
3976 current: impl IntoOption<ProviderCurrentConfig>,
3977 ) -> Self {
3978 Self {
3979 provider_id: provider_id.into(),
3980 supported,
3981 required,
3982 current: current.into_option(),
3983 meta: None,
3984 }
3985 }
3986
3987 #[must_use]
3993 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3994 self.meta = meta.into_option();
3995 self
3996 }
3997}
3998
3999#[cfg(feature = "unstable_llm_providers")]
4000crate::serde_util::default_on_null! {
4001 #[serde_as]
4007 #[skip_serializing_none]
4008 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4009 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
4010 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = PROVIDERS_LIST_METHOD_NAME)))]
4011 #[serde(rename_all = "camelCase")]
4012 #[non_exhaustive]
4013 pub struct ListProvidersRequest {
4014 #[serde_as(deserialize_as = "DefaultOnError")]
4020 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4021 #[serde(default)]
4022 #[serde(rename = "_meta")]
4023 pub meta: Option<Meta>,
4024 }
4025}
4026
4027#[cfg(feature = "unstable_llm_providers")]
4028impl ListProvidersRequest {
4029 #[must_use]
4031 pub fn new() -> Self {
4032 Self::default()
4033 }
4034
4035 #[must_use]
4041 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
4042 self.meta = meta.into_option();
4043 self
4044 }
4045}
4046
4047#[cfg(feature = "unstable_llm_providers")]
4053#[serde_as]
4054#[skip_serializing_none]
4055#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4056#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4057#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = PROVIDERS_LIST_METHOD_NAME)))]
4058#[serde(rename_all = "camelCase")]
4059#[non_exhaustive]
4060pub struct ListProvidersResponse {
4061 pub providers: Vec<ProviderInfo>,
4063 #[serde_as(deserialize_as = "DefaultOnError")]
4069 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4070 #[serde(default)]
4071 #[serde(rename = "_meta")]
4072 pub meta: Option<Meta>,
4073}
4074
4075#[cfg(feature = "unstable_llm_providers")]
4076impl ListProvidersResponse {
4077 #[must_use]
4079 pub fn new(providers: Vec<ProviderInfo>) -> Self {
4080 Self {
4081 providers,
4082 meta: None,
4083 }
4084 }
4085
4086 #[must_use]
4092 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
4093 self.meta = meta.into_option();
4094 self
4095 }
4096}
4097
4098#[cfg(feature = "unstable_llm_providers")]
4106#[serde_as]
4107#[skip_serializing_none]
4108#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4109#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
4110#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = PROVIDERS_SET_METHOD_NAME)))]
4111#[serde(rename_all = "camelCase")]
4112#[non_exhaustive]
4113pub struct SetProviderRequest {
4114 pub provider_id: ProviderId,
4116 pub api_type: LlmProtocol,
4118 #[cfg_attr(feature = "schemars", schemars(url))]
4120 pub base_url: String,
4121 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
4124 pub headers: HashMap<String, String>,
4125 #[serde_as(deserialize_as = "DefaultOnError")]
4131 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4132 #[serde(default)]
4133 #[serde(rename = "_meta")]
4134 pub meta: Option<Meta>,
4135}
4136
4137#[cfg(feature = "unstable_llm_providers")]
4138impl SetProviderRequest {
4139 #[must_use]
4141 pub fn new(
4142 provider_id: impl Into<ProviderId>,
4143 api_type: LlmProtocol,
4144 base_url: impl Into<String>,
4145 ) -> Self {
4146 Self {
4147 provider_id: provider_id.into(),
4148 api_type,
4149 base_url: base_url.into(),
4150 headers: HashMap::new(),
4151 meta: None,
4152 }
4153 }
4154
4155 #[must_use]
4158 pub fn headers(mut self, headers: HashMap<String, String>) -> Self {
4159 self.headers = headers;
4160 self
4161 }
4162
4163 #[must_use]
4169 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
4170 self.meta = meta.into_option();
4171 self
4172 }
4173}
4174
4175#[cfg(feature = "unstable_llm_providers")]
4176crate::serde_util::default_on_null! {
4177 #[serde_as]
4183 #[skip_serializing_none]
4184 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4185 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
4186 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = PROVIDERS_SET_METHOD_NAME)))]
4187 #[serde(rename_all = "camelCase")]
4188 #[non_exhaustive]
4189 pub struct SetProviderResponse {
4190 #[serde_as(deserialize_as = "DefaultOnError")]
4196 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4197 #[serde(default)]
4198 #[serde(rename = "_meta")]
4199 pub meta: Option<Meta>,
4200 }
4201}
4202
4203#[cfg(feature = "unstable_llm_providers")]
4204impl SetProviderResponse {
4205 #[must_use]
4207 pub fn new() -> Self {
4208 Self::default()
4209 }
4210
4211 #[must_use]
4217 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
4218 self.meta = meta.into_option();
4219 self
4220 }
4221}
4222
4223#[cfg(feature = "unstable_llm_providers")]
4229#[serde_as]
4230#[skip_serializing_none]
4231#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4232#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4233#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = PROVIDERS_DISABLE_METHOD_NAME)))]
4234#[serde(rename_all = "camelCase")]
4235#[non_exhaustive]
4236pub struct DisableProviderRequest {
4237 pub provider_id: ProviderId,
4239 #[serde_as(deserialize_as = "DefaultOnError")]
4245 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4246 #[serde(default)]
4247 #[serde(rename = "_meta")]
4248 pub meta: Option<Meta>,
4249}
4250
4251#[cfg(feature = "unstable_llm_providers")]
4252impl DisableProviderRequest {
4253 #[must_use]
4255 pub fn new(provider_id: impl Into<ProviderId>) -> Self {
4256 Self {
4257 provider_id: provider_id.into(),
4258 meta: None,
4259 }
4260 }
4261
4262 #[must_use]
4268 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
4269 self.meta = meta.into_option();
4270 self
4271 }
4272}
4273
4274#[cfg(feature = "unstable_llm_providers")]
4275crate::serde_util::default_on_null! {
4276 #[serde_as]
4282 #[skip_serializing_none]
4283 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4284 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
4285 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = PROVIDERS_DISABLE_METHOD_NAME)))]
4286 #[serde(rename_all = "camelCase")]
4287 #[non_exhaustive]
4288 pub struct DisableProviderResponse {
4289 #[serde_as(deserialize_as = "DefaultOnError")]
4295 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4296 #[serde(default)]
4297 #[serde(rename = "_meta")]
4298 pub meta: Option<Meta>,
4299 }
4300}
4301
4302#[cfg(feature = "unstable_llm_providers")]
4303impl DisableProviderResponse {
4304 #[must_use]
4306 pub fn new() -> Self {
4307 Self::default()
4308 }
4309
4310 #[must_use]
4316 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
4317 self.meta = meta.into_option();
4318 self
4319 }
4320}
4321
4322#[serde_as]
4331#[skip_serializing_none]
4332#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4333#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4334#[serde(rename_all = "camelCase")]
4335#[non_exhaustive]
4336pub struct AgentCapabilities {
4337 #[serde_as(deserialize_as = "DefaultOnError")]
4345 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4346 #[serde(default)]
4347 pub session: Option<SessionCapabilities>,
4348 #[serde_as(deserialize_as = "DefaultOnError")]
4355 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4356 #[serde(default)]
4357 pub auth: Option<AgentAuthCapabilities>,
4358 #[cfg(feature = "unstable_llm_providers")]
4367 #[serde_as(deserialize_as = "DefaultOnError")]
4368 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4369 #[serde(default)]
4370 pub providers: Option<ProvidersCapabilities>,
4371 #[serde_as(deserialize_as = "DefaultOnError")]
4377 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4378 #[serde(default)]
4379 #[serde(rename = "_meta")]
4380 pub meta: Option<Meta>,
4381}
4382
4383impl AgentCapabilities {
4384 #[must_use]
4386 pub fn new() -> Self {
4387 Self::default()
4388 }
4389
4390 #[must_use]
4398 pub fn session(mut self, session: impl IntoOption<SessionCapabilities>) -> Self {
4399 self.session = session.into_option();
4400 self
4401 }
4402
4403 #[must_use]
4407 pub fn auth(mut self, auth: impl IntoOption<AgentAuthCapabilities>) -> Self {
4408 self.auth = auth.into_option();
4409 self
4410 }
4411
4412 #[cfg(feature = "unstable_llm_providers")]
4418 #[must_use]
4419 pub fn providers(mut self, providers: impl IntoOption<ProvidersCapabilities>) -> Self {
4420 self.providers = providers.into_option();
4421 self
4422 }
4423
4424 #[must_use]
4430 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
4431 self.meta = meta.into_option();
4432 self
4433 }
4434}
4435
4436#[cfg(feature = "unstable_llm_providers")]
4444#[serde_as]
4445#[skip_serializing_none]
4446#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4447#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4448#[non_exhaustive]
4449pub struct ProvidersCapabilities {
4450 #[serde_as(deserialize_as = "DefaultOnError")]
4456 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4457 #[serde(default)]
4458 #[serde(rename = "_meta")]
4459 pub meta: Option<Meta>,
4460}
4461
4462#[cfg(feature = "unstable_llm_providers")]
4463impl ProvidersCapabilities {
4464 #[must_use]
4466 pub fn new() -> Self {
4467 Self::default()
4468 }
4469
4470 #[must_use]
4476 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
4477 self.meta = meta.into_option();
4478 self
4479 }
4480}
4481
4482#[serde_as]
4494#[skip_serializing_none]
4495#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4496#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4497#[serde(rename_all = "camelCase")]
4498#[non_exhaustive]
4499pub struct SessionCapabilities {
4500 #[serde_as(deserialize_as = "DefaultOnError")]
4506 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4507 #[serde(default)]
4508 pub prompt: Option<PromptCapabilities>,
4509 #[serde_as(deserialize_as = "DefaultOnError")]
4514 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4515 #[serde(default)]
4516 pub mcp: Option<McpCapabilities>,
4517 #[serde_as(deserialize_as = "DefaultOnError")]
4522 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4523 #[serde(default)]
4524 pub delete: Option<SessionDeleteCapabilities>,
4525 #[serde_as(deserialize_as = "DefaultOnError")]
4534 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4535 #[serde(default)]
4536 pub additional_directories: Option<SessionAdditionalDirectoriesCapabilities>,
4537 #[cfg(feature = "unstable_session_system_prompt")]
4546 #[serde_as(deserialize_as = "DefaultOnError")]
4547 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4548 #[serde(default)]
4549 pub system_prompt: Option<SessionSystemPromptCapabilities>,
4550 #[cfg(feature = "unstable_session_fork")]
4559 #[serde_as(deserialize_as = "DefaultOnError")]
4560 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4561 #[serde(default)]
4562 pub fork: Option<SessionForkCapabilities>,
4563 #[serde_as(deserialize_as = "DefaultOnError")]
4569 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4570 #[serde(default)]
4571 #[serde(rename = "_meta")]
4572 pub meta: Option<Meta>,
4573}
4574
4575impl SessionCapabilities {
4576 #[must_use]
4578 pub fn new() -> Self {
4579 Self::default()
4580 }
4581
4582 #[must_use]
4588 pub fn prompt(mut self, prompt: impl IntoOption<PromptCapabilities>) -> Self {
4589 self.prompt = prompt.into_option();
4590 self
4591 }
4592
4593 #[must_use]
4598 pub fn mcp(mut self, mcp: impl IntoOption<McpCapabilities>) -> Self {
4599 self.mcp = mcp.into_option();
4600 self
4601 }
4602
4603 #[must_use]
4608 pub fn delete(mut self, delete: impl IntoOption<SessionDeleteCapabilities>) -> Self {
4609 self.delete = delete.into_option();
4610 self
4611 }
4612
4613 #[must_use]
4622 pub fn additional_directories(
4623 mut self,
4624 additional_directories: impl IntoOption<SessionAdditionalDirectoriesCapabilities>,
4625 ) -> Self {
4626 self.additional_directories = additional_directories.into_option();
4627 self
4628 }
4629
4630 #[cfg(feature = "unstable_session_system_prompt")]
4639 #[must_use]
4640 pub fn system_prompt(
4641 mut self,
4642 system_prompt: impl IntoOption<SessionSystemPromptCapabilities>,
4643 ) -> Self {
4644 self.system_prompt = system_prompt.into_option();
4645 self
4646 }
4647
4648 #[cfg(feature = "unstable_session_fork")]
4649 #[must_use]
4654 pub fn fork(mut self, fork: impl IntoOption<SessionForkCapabilities>) -> Self {
4655 self.fork = fork.into_option();
4656 self
4657 }
4658
4659 #[must_use]
4665 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
4666 self.meta = meta.into_option();
4667 self
4668 }
4669}
4670
4671#[serde_as]
4675#[skip_serializing_none]
4676#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4677#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4678#[non_exhaustive]
4679pub struct SessionDeleteCapabilities {
4680 #[serde_as(deserialize_as = "DefaultOnError")]
4686 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4687 #[serde(default)]
4688 #[serde(rename = "_meta")]
4689 pub meta: Option<Meta>,
4690}
4691
4692impl SessionDeleteCapabilities {
4693 #[must_use]
4695 pub fn new() -> Self {
4696 Self::default()
4697 }
4698
4699 #[must_use]
4705 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
4706 self.meta = meta.into_option();
4707 self
4708 }
4709}
4710
4711#[serde_as]
4718#[skip_serializing_none]
4719#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4720#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4721#[non_exhaustive]
4722pub struct SessionAdditionalDirectoriesCapabilities {
4723 #[serde_as(deserialize_as = "DefaultOnError")]
4729 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4730 #[serde(default)]
4731 #[serde(rename = "_meta")]
4732 pub meta: Option<Meta>,
4733}
4734
4735impl SessionAdditionalDirectoriesCapabilities {
4736 #[must_use]
4738 pub fn new() -> Self {
4739 Self::default()
4740 }
4741
4742 #[must_use]
4748 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
4749 self.meta = meta.into_option();
4750 self
4751 }
4752}
4753
4754#[cfg(feature = "unstable_session_system_prompt")]
4763#[serde_as]
4764#[skip_serializing_none]
4765#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4766#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4767#[serde(rename_all = "camelCase")]
4768#[non_exhaustive]
4769pub struct SessionSystemPromptCapabilities {
4770 #[serde_as(deserialize_as = "DefaultOnError")]
4774 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4775 #[serde(default)]
4776 pub mode: Option<SessionSystemPromptModeCapabilities>,
4777 #[serde_as(deserialize_as = "DefaultOnError")]
4783 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4784 #[serde(default)]
4785 #[serde(rename = "_meta")]
4786 pub meta: Option<Meta>,
4787}
4788
4789#[cfg(feature = "unstable_session_system_prompt")]
4790impl SessionSystemPromptCapabilities {
4791 #[must_use]
4793 pub fn new() -> Self {
4794 Self::default()
4795 }
4796
4797 #[must_use]
4801 pub fn mode(mut self, mode: impl IntoOption<SessionSystemPromptModeCapabilities>) -> Self {
4802 self.mode = mode.into_option();
4803 self
4804 }
4805
4806 #[must_use]
4812 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
4813 self.meta = meta.into_option();
4814 self
4815 }
4816}
4817
4818#[cfg(feature = "unstable_session_system_prompt")]
4827#[serde_as]
4828#[skip_serializing_none]
4829#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4830#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4831#[serde(rename_all = "camelCase")]
4832#[non_exhaustive]
4833pub struct SessionSystemPromptModeCapabilities {
4834 #[serde_as(deserialize_as = "DefaultOnError")]
4840 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4841 #[serde(default, rename = "override")]
4842 pub override_: Option<SessionSystemPromptOverrideCapabilities>,
4843 #[serde_as(deserialize_as = "DefaultOnError")]
4849 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4850 #[serde(default)]
4851 #[serde(rename = "_meta")]
4852 pub meta: Option<Meta>,
4853}
4854
4855#[cfg(feature = "unstable_session_system_prompt")]
4856impl SessionSystemPromptModeCapabilities {
4857 #[must_use]
4859 pub fn new() -> Self {
4860 Self::default()
4861 }
4862
4863 #[must_use]
4869 pub fn override_(
4870 mut self,
4871 override_: impl IntoOption<SessionSystemPromptOverrideCapabilities>,
4872 ) -> Self {
4873 self.override_ = override_.into_option();
4874 self
4875 }
4876
4877 #[must_use]
4883 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
4884 self.meta = meta.into_option();
4885 self
4886 }
4887}
4888
4889#[cfg(feature = "unstable_session_system_prompt")]
4898#[serde_as]
4899#[skip_serializing_none]
4900#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4901#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4902#[non_exhaustive]
4903pub struct SessionSystemPromptOverrideCapabilities {
4904 #[serde_as(deserialize_as = "DefaultOnError")]
4910 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4911 #[serde(default)]
4912 #[serde(rename = "_meta")]
4913 pub meta: Option<Meta>,
4914}
4915
4916#[cfg(feature = "unstable_session_system_prompt")]
4917impl SessionSystemPromptOverrideCapabilities {
4918 #[must_use]
4920 pub fn new() -> Self {
4921 Self::default()
4922 }
4923
4924 #[must_use]
4930 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
4931 self.meta = meta.into_option();
4932 self
4933 }
4934}
4935
4936#[cfg(feature = "unstable_session_fork")]
4944#[serde_as]
4945#[skip_serializing_none]
4946#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4947#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4948#[non_exhaustive]
4949pub struct SessionForkCapabilities {
4950 #[serde_as(deserialize_as = "DefaultOnError")]
4956 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4957 #[serde(default)]
4958 #[serde(rename = "_meta")]
4959 pub meta: Option<Meta>,
4960}
4961
4962#[cfg(feature = "unstable_session_fork")]
4963impl SessionForkCapabilities {
4964 #[must_use]
4966 pub fn new() -> Self {
4967 Self::default()
4968 }
4969
4970 #[must_use]
4976 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
4977 self.meta = meta.into_option();
4978 self
4979 }
4980}
4981
4982#[serde_as]
4995#[skip_serializing_none]
4996#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4997#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4998#[serde(rename_all = "camelCase")]
4999#[non_exhaustive]
5000pub struct PromptCapabilities {
5001 #[serde_as(deserialize_as = "DefaultOnError")]
5006 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
5007 #[serde(default)]
5008 pub image: Option<PromptImageCapabilities>,
5009 #[serde_as(deserialize_as = "DefaultOnError")]
5014 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
5015 #[serde(default)]
5016 pub audio: Option<PromptAudioCapabilities>,
5017 #[serde_as(deserialize_as = "DefaultOnError")]
5025 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
5026 #[serde(default)]
5027 pub embedded_context: Option<PromptEmbeddedContextCapabilities>,
5028 #[serde_as(deserialize_as = "DefaultOnError")]
5034 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
5035 #[serde(default)]
5036 #[serde(rename = "_meta")]
5037 pub meta: Option<Meta>,
5038}
5039
5040impl PromptCapabilities {
5041 #[must_use]
5043 pub fn new() -> Self {
5044 Self::default()
5045 }
5046
5047 #[must_use]
5052 pub fn image(mut self, image: impl IntoOption<PromptImageCapabilities>) -> Self {
5053 self.image = image.into_option();
5054 self
5055 }
5056
5057 #[must_use]
5062 pub fn audio(mut self, audio: impl IntoOption<PromptAudioCapabilities>) -> Self {
5063 self.audio = audio.into_option();
5064 self
5065 }
5066
5067 #[must_use]
5075 pub fn embedded_context(
5076 mut self,
5077 embedded_context: impl IntoOption<PromptEmbeddedContextCapabilities>,
5078 ) -> Self {
5079 self.embedded_context = embedded_context.into_option();
5080 self
5081 }
5082
5083 #[must_use]
5089 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
5090 self.meta = meta.into_option();
5091 self
5092 }
5093}
5094
5095#[serde_as]
5099#[skip_serializing_none]
5100#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
5101#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
5102#[non_exhaustive]
5103pub struct PromptImageCapabilities {
5104 #[serde_as(deserialize_as = "DefaultOnError")]
5110 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
5111 #[serde(default)]
5112 #[serde(rename = "_meta")]
5113 pub meta: Option<Meta>,
5114}
5115
5116impl PromptImageCapabilities {
5117 #[must_use]
5119 pub fn new() -> Self {
5120 Self::default()
5121 }
5122
5123 #[must_use]
5129 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
5130 self.meta = meta.into_option();
5131 self
5132 }
5133}
5134
5135#[serde_as]
5139#[skip_serializing_none]
5140#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
5141#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
5142#[non_exhaustive]
5143pub struct PromptAudioCapabilities {
5144 #[serde_as(deserialize_as = "DefaultOnError")]
5150 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
5151 #[serde(default)]
5152 #[serde(rename = "_meta")]
5153 pub meta: Option<Meta>,
5154}
5155
5156impl PromptAudioCapabilities {
5157 #[must_use]
5159 pub fn new() -> Self {
5160 Self::default()
5161 }
5162
5163 #[must_use]
5169 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
5170 self.meta = meta.into_option();
5171 self
5172 }
5173}
5174
5175#[serde_as]
5179#[skip_serializing_none]
5180#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
5181#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
5182#[non_exhaustive]
5183pub struct PromptEmbeddedContextCapabilities {
5184 #[serde_as(deserialize_as = "DefaultOnError")]
5190 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
5191 #[serde(default)]
5192 #[serde(rename = "_meta")]
5193 pub meta: Option<Meta>,
5194}
5195
5196impl PromptEmbeddedContextCapabilities {
5197 #[must_use]
5199 pub fn new() -> Self {
5200 Self::default()
5201 }
5202
5203 #[must_use]
5209 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
5210 self.meta = meta.into_option();
5211 self
5212 }
5213}
5214
5215#[serde_as]
5217#[skip_serializing_none]
5218#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
5219#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
5220#[serde(rename_all = "camelCase")]
5221#[non_exhaustive]
5222pub struct McpCapabilities {
5223 #[serde_as(deserialize_as = "DefaultOnError")]
5228 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
5229 #[serde(default)]
5230 pub stdio: Option<McpStdioCapabilities>,
5231 #[serde_as(deserialize_as = "DefaultOnError")]
5236 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
5237 #[serde(default)]
5238 pub http: Option<McpHttpCapabilities>,
5239 #[cfg(feature = "unstable_mcp_over_acp")]
5248 #[serde_as(deserialize_as = "DefaultOnError")]
5249 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
5250 #[serde(default)]
5251 pub acp: Option<McpAcpCapabilities>,
5252 #[serde_as(deserialize_as = "DefaultOnError")]
5258 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
5259 #[serde(default)]
5260 #[serde(rename = "_meta")]
5261 pub meta: Option<Meta>,
5262}
5263
5264impl McpCapabilities {
5265 #[must_use]
5267 pub fn new() -> Self {
5268 Self::default()
5269 }
5270
5271 #[must_use]
5276 pub fn stdio(mut self, stdio: impl IntoOption<McpStdioCapabilities>) -> Self {
5277 self.stdio = stdio.into_option();
5278 self
5279 }
5280
5281 #[must_use]
5286 pub fn http(mut self, http: impl IntoOption<McpHttpCapabilities>) -> Self {
5287 self.http = http.into_option();
5288 self
5289 }
5290
5291 #[cfg(feature = "unstable_mcp_over_acp")]
5297 #[must_use]
5301 pub fn acp(mut self, acp: impl IntoOption<McpAcpCapabilities>) -> Self {
5302 self.acp = acp.into_option();
5303 self
5304 }
5305
5306 #[must_use]
5312 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
5313 self.meta = meta.into_option();
5314 self
5315 }
5316}
5317
5318#[serde_as]
5322#[skip_serializing_none]
5323#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
5324#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
5325#[non_exhaustive]
5326pub struct McpStdioCapabilities {
5327 #[serde_as(deserialize_as = "DefaultOnError")]
5333 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
5334 #[serde(default)]
5335 #[serde(rename = "_meta")]
5336 pub meta: Option<Meta>,
5337}
5338
5339impl McpStdioCapabilities {
5340 #[must_use]
5342 pub fn new() -> Self {
5343 Self::default()
5344 }
5345
5346 #[must_use]
5352 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
5353 self.meta = meta.into_option();
5354 self
5355 }
5356}
5357
5358#[serde_as]
5362#[skip_serializing_none]
5363#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
5364#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
5365#[non_exhaustive]
5366pub struct McpHttpCapabilities {
5367 #[serde_as(deserialize_as = "DefaultOnError")]
5373 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
5374 #[serde(default)]
5375 #[serde(rename = "_meta")]
5376 pub meta: Option<Meta>,
5377}
5378
5379impl McpHttpCapabilities {
5380 #[must_use]
5382 pub fn new() -> Self {
5383 Self::default()
5384 }
5385
5386 #[must_use]
5392 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
5393 self.meta = meta.into_option();
5394 self
5395 }
5396}
5397
5398#[cfg(feature = "unstable_mcp_over_acp")]
5406#[serde_as]
5407#[skip_serializing_none]
5408#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
5409#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
5410#[non_exhaustive]
5411pub struct McpAcpCapabilities {
5412 #[serde_as(deserialize_as = "DefaultOnError")]
5418 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
5419 #[serde(default)]
5420 #[serde(rename = "_meta")]
5421 pub meta: Option<Meta>,
5422}
5423
5424#[cfg(feature = "unstable_mcp_over_acp")]
5425impl McpAcpCapabilities {
5426 #[must_use]
5428 pub fn new() -> Self {
5429 Self::default()
5430 }
5431
5432 #[must_use]
5438 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
5439 self.meta = meta.into_option();
5440 self
5441 }
5442}
5443
5444#[serde_as]
5448#[skip_serializing_none]
5449#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
5450#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
5451#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = SESSION_CANCEL_METHOD_NAME)))]
5452#[serde(rename_all = "camelCase")]
5453#[non_exhaustive]
5454pub struct CancelSessionNotification {
5455 pub session_id: SessionId,
5457 #[serde_as(deserialize_as = "DefaultOnError")]
5463 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
5464 #[serde(default)]
5465 #[serde(rename = "_meta")]
5466 pub meta: Option<Meta>,
5467}
5468
5469impl CancelSessionNotification {
5470 #[must_use]
5472 pub fn new(session_id: impl Into<SessionId>) -> Self {
5473 Self {
5474 session_id: session_id.into(),
5475 meta: None,
5476 }
5477 }
5478
5479 #[must_use]
5485 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
5486 self.meta = meta.into_option();
5487 self
5488 }
5489}
5490
5491#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
5497#[non_exhaustive]
5498pub struct AgentMethodNames {
5499 pub initialize: &'static str,
5501 pub auth_login: &'static str,
5503 #[cfg(feature = "unstable_llm_providers")]
5505 pub providers_list: &'static str,
5506 #[cfg(feature = "unstable_llm_providers")]
5508 pub providers_set: &'static str,
5509 #[cfg(feature = "unstable_llm_providers")]
5511 pub providers_disable: &'static str,
5512 pub session_new: &'static str,
5514 pub session_set_config_option: &'static str,
5516 pub session_prompt: &'static str,
5518 pub session_cancel: &'static str,
5520 #[cfg(feature = "unstable_mcp_over_acp")]
5522 pub mcp_message: &'static str,
5523 pub session_list: &'static str,
5525 pub session_delete: &'static str,
5527 #[cfg(feature = "unstable_session_fork")]
5529 pub session_fork: &'static str,
5530 pub session_resume: &'static str,
5532 pub session_close: &'static str,
5534 pub auth_logout: &'static str,
5536}
5537
5538pub const AGENT_METHOD_NAMES: AgentMethodNames = AgentMethodNames {
5540 initialize: INITIALIZE_METHOD_NAME,
5541 auth_login: AUTH_LOGIN_METHOD_NAME,
5542 #[cfg(feature = "unstable_llm_providers")]
5543 providers_list: PROVIDERS_LIST_METHOD_NAME,
5544 #[cfg(feature = "unstable_llm_providers")]
5545 providers_set: PROVIDERS_SET_METHOD_NAME,
5546 #[cfg(feature = "unstable_llm_providers")]
5547 providers_disable: PROVIDERS_DISABLE_METHOD_NAME,
5548 session_new: SESSION_NEW_METHOD_NAME,
5549 session_set_config_option: SESSION_SET_CONFIG_OPTION_METHOD_NAME,
5550 session_prompt: SESSION_PROMPT_METHOD_NAME,
5551 session_cancel: SESSION_CANCEL_METHOD_NAME,
5552 #[cfg(feature = "unstable_mcp_over_acp")]
5553 mcp_message: MCP_MESSAGE_METHOD_NAME,
5554 session_list: SESSION_LIST_METHOD_NAME,
5555 session_delete: SESSION_DELETE_METHOD_NAME,
5556 #[cfg(feature = "unstable_session_fork")]
5557 session_fork: SESSION_FORK_METHOD_NAME,
5558 session_resume: SESSION_RESUME_METHOD_NAME,
5559 session_close: SESSION_CLOSE_METHOD_NAME,
5560 auth_logout: AUTH_LOGOUT_METHOD_NAME,
5561};
5562
5563pub(crate) const INITIALIZE_METHOD_NAME: &str = "initialize";
5565pub(crate) const AUTH_LOGIN_METHOD_NAME: &str = "auth/login";
5567#[cfg(feature = "unstable_llm_providers")]
5569pub(crate) const PROVIDERS_LIST_METHOD_NAME: &str = "providers/list";
5570#[cfg(feature = "unstable_llm_providers")]
5572pub(crate) const PROVIDERS_SET_METHOD_NAME: &str = "providers/set";
5573#[cfg(feature = "unstable_llm_providers")]
5575pub(crate) const PROVIDERS_DISABLE_METHOD_NAME: &str = "providers/disable";
5576pub(crate) const SESSION_NEW_METHOD_NAME: &str = "session/new";
5578pub(crate) const SESSION_SET_CONFIG_OPTION_METHOD_NAME: &str = "session/set_config_option";
5580pub(crate) const SESSION_PROMPT_METHOD_NAME: &str = "session/prompt";
5582pub(crate) const SESSION_CANCEL_METHOD_NAME: &str = "session/cancel";
5584pub(crate) const SESSION_LIST_METHOD_NAME: &str = "session/list";
5586pub(crate) const SESSION_DELETE_METHOD_NAME: &str = "session/delete";
5588#[cfg(feature = "unstable_session_fork")]
5590pub(crate) const SESSION_FORK_METHOD_NAME: &str = "session/fork";
5591pub(crate) const SESSION_RESUME_METHOD_NAME: &str = "session/resume";
5593pub(crate) const SESSION_CLOSE_METHOD_NAME: &str = "session/close";
5595pub(crate) const AUTH_LOGOUT_METHOD_NAME: &str = "auth/logout";
5597
5598#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
5605#[derive(Clone, Debug, Serialize, Deserialize)]
5606#[serde(untagged)]
5607#[cfg_attr(feature = "schemars", schemars(inline))]
5608#[non_exhaustive]
5609pub enum ClientRequest {
5610 InitializeRequest(Box<InitializeRequest>),
5621 LoginAuthRequest(Box<LoginAuthRequest>),
5636 #[cfg(feature = "unstable_llm_providers")]
5642 ListProvidersRequest(Box<ListProvidersRequest>),
5643 #[cfg(feature = "unstable_llm_providers")]
5649 SetProviderRequest(Box<SetProviderRequest>),
5650 #[cfg(feature = "unstable_llm_providers")]
5656 DisableProviderRequest(Box<DisableProviderRequest>),
5657 LogoutAuthRequest(Box<LogoutAuthRequest>),
5667 NewSessionRequest(Box<NewSessionRequest>),
5680 ListSessionsRequest(Box<ListSessionsRequest>),
5684 DeleteSessionRequest(Box<DeleteSessionRequest>),
5688 #[cfg(feature = "unstable_session_fork")]
5689 ForkSessionRequest(Box<ForkSessionRequest>),
5705 ResumeSessionRequest(Box<ResumeSessionRequest>),
5711 CloseSessionRequest(Box<CloseSessionRequest>),
5716 SetSessionConfigOptionRequest(Box<SetSessionConfigOptionRequest>),
5718 PromptRequest(Box<PromptRequest>),
5740 ExtMethodRequest(Box<ExtRequest>),
5747}
5748
5749impl ClientRequest {
5750 #[must_use]
5752 pub fn method(&self) -> &str {
5753 match self {
5754 Self::InitializeRequest(_) => AGENT_METHOD_NAMES.initialize,
5755 Self::LoginAuthRequest(_) => AGENT_METHOD_NAMES.auth_login,
5756 #[cfg(feature = "unstable_llm_providers")]
5757 Self::ListProvidersRequest(_) => AGENT_METHOD_NAMES.providers_list,
5758 #[cfg(feature = "unstable_llm_providers")]
5759 Self::SetProviderRequest(_) => AGENT_METHOD_NAMES.providers_set,
5760 #[cfg(feature = "unstable_llm_providers")]
5761 Self::DisableProviderRequest(_) => AGENT_METHOD_NAMES.providers_disable,
5762 Self::LogoutAuthRequest(_) => AGENT_METHOD_NAMES.auth_logout,
5763 Self::NewSessionRequest(_) => AGENT_METHOD_NAMES.session_new,
5764 Self::ListSessionsRequest(_) => AGENT_METHOD_NAMES.session_list,
5765 Self::DeleteSessionRequest(_) => AGENT_METHOD_NAMES.session_delete,
5766 #[cfg(feature = "unstable_session_fork")]
5767 Self::ForkSessionRequest(_) => AGENT_METHOD_NAMES.session_fork,
5768 Self::ResumeSessionRequest(_) => AGENT_METHOD_NAMES.session_resume,
5769 Self::CloseSessionRequest(_) => AGENT_METHOD_NAMES.session_close,
5770 Self::SetSessionConfigOptionRequest(_) => AGENT_METHOD_NAMES.session_set_config_option,
5771 Self::PromptRequest(_) => AGENT_METHOD_NAMES.session_prompt,
5772 Self::ExtMethodRequest(ext_request) => &ext_request.method,
5773 }
5774 }
5775}
5776
5777#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
5784#[derive(Clone, Debug, Serialize, Deserialize)]
5785#[serde(untagged)]
5786#[cfg_attr(feature = "schemars", schemars(inline))]
5787#[non_exhaustive]
5788pub enum AgentResponse {
5789 InitializeResponse(Box<InitializeResponse>),
5791 LoginAuthResponse(#[serde(default)] Box<LoginAuthResponse>),
5793 #[cfg(feature = "unstable_llm_providers")]
5795 ListProvidersResponse(Box<ListProvidersResponse>),
5796 #[cfg(feature = "unstable_llm_providers")]
5798 SetProviderResponse(#[serde(default)] Box<SetProviderResponse>),
5799 #[cfg(feature = "unstable_llm_providers")]
5801 DisableProviderResponse(#[serde(default)] Box<DisableProviderResponse>),
5802 LogoutAuthResponse(#[serde(default)] Box<LogoutAuthResponse>),
5804 NewSessionResponse(Box<NewSessionResponse>),
5806 ListSessionsResponse(Box<ListSessionsResponse>),
5808 DeleteSessionResponse(#[serde(default)] Box<DeleteSessionResponse>),
5810 #[cfg(feature = "unstable_session_fork")]
5812 ForkSessionResponse(Box<ForkSessionResponse>),
5813 ResumeSessionResponse(#[serde(default)] Box<ResumeSessionResponse>),
5815 CloseSessionResponse(#[serde(default)] Box<CloseSessionResponse>),
5817 SetSessionConfigOptionResponse(Box<SetSessionConfigOptionResponse>),
5819 PromptResponse(Box<PromptResponse>),
5821 ExtMethodResponse(Box<ExtResponse>),
5823}
5824
5825#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
5832#[derive(Clone, Debug, Serialize, Deserialize)]
5833#[serde(untagged)]
5834#[cfg_attr(feature = "schemars", schemars(inline))]
5835#[non_exhaustive]
5836pub enum ClientNotification {
5837 CancelSessionNotification(Box<CancelSessionNotification>),
5863 #[cfg(feature = "unstable_mcp_over_acp")]
5869 MessageMcpNotification(Box<MessageMcpNotification>),
5870 ExtNotification(Box<ExtNotification>),
5877}
5878
5879impl ClientNotification {
5880 #[must_use]
5882 pub fn method(&self) -> &str {
5883 match self {
5884 Self::CancelSessionNotification(_) => AGENT_METHOD_NAMES.session_cancel,
5885 #[cfg(feature = "unstable_mcp_over_acp")]
5886 Self::MessageMcpNotification(_) => AGENT_METHOD_NAMES.mcp_message,
5887 Self::ExtNotification(ext_notification) => &ext_notification.method,
5888 }
5889 }
5890}
5891
5892#[cfg(test)]
5893mod test_serialization {
5894 use std::path::PathBuf;
5895
5896 use super::*;
5897 use serde_json::json;
5898
5899 fn test_meta() -> Meta {
5900 json!({ "source": "test" }).as_object().unwrap().clone()
5901 }
5902
5903 fn serialized_meta_key_count(value: &impl serde::Serialize) -> usize {
5904 serde_json::to_string(value)
5905 .unwrap()
5906 .matches("\"_meta\"")
5907 .count()
5908 }
5909
5910 #[test]
5911 fn prompt_response_without_metadata_round_trips() {
5912 let response = PromptResponse::new(MessageId::new("message-1"));
5913 let serialized = serde_json::to_value(&response).unwrap();
5914 assert_eq!(serialized, json!({ "messageId": "message-1" }));
5915 assert_eq!(
5916 serde_json::from_value::<PromptResponse>(serialized).unwrap(),
5917 response
5918 );
5919 assert_eq!(
5920 serde_json::from_value::<PromptResponse>(json!({
5921 "messageId": "message-1",
5922 "_meta": null
5923 }))
5924 .unwrap(),
5925 response
5926 );
5927 }
5928
5929 #[test]
5930 fn prompt_response_requires_message_id_and_preserves_metadata_tolerance() {
5931 let response = PromptResponse::new("message-1").meta(test_meta());
5932 let serialized = serde_json::to_value(&response).unwrap();
5933 assert_eq!(
5934 serialized,
5935 json!({
5936 "messageId": "message-1",
5937 "_meta": { "source": "test" }
5938 })
5939 );
5940
5941 let deserialized: PromptResponse = serde_json::from_value(serialized).unwrap();
5942 assert_eq!(deserialized.message_id, MessageId::new("message-1"));
5943 assert_eq!(deserialized.meta, Some(test_meta()));
5944
5945 let malformed_meta: PromptResponse = serde_json::from_value(json!({
5946 "messageId": "message-2",
5947 "_meta": false
5948 }))
5949 .unwrap();
5950 assert_eq!(malformed_meta.message_id, MessageId::new("message-2"));
5951 assert_eq!(malformed_meta.meta, None);
5952
5953 for invalid in [
5954 json!({}),
5955 json!({ "messageId": null }),
5956 json!({ "messageId": 1 }),
5957 json!({ "messageId": false }),
5958 json!({ "messageId": {} }),
5959 json!({ "messageId": [] }),
5960 ] {
5961 assert!(
5962 serde_json::from_value::<PromptResponse>(invalid).is_err(),
5963 "missing, null, and non-string message IDs must be rejected"
5964 );
5965 }
5966 }
5967
5968 #[cfg(feature = "schemars")]
5969 #[test]
5970 fn prompt_response_schema_requires_non_null_string_message_id_reference() {
5971 let schema = serde_json::to_value(schemars::schema_for!(PromptResponse)).unwrap();
5972
5973 assert_eq!(schema["required"], json!(["messageId"]));
5974 assert_eq!(
5975 schema["properties"]["messageId"]["$ref"],
5976 "#/$defs/MessageId"
5977 );
5978 assert_eq!(schema["$defs"]["MessageId"]["type"], "string");
5979 }
5980
5981 #[test]
5982 fn test_initialize_capabilities_default_on_malformed_values() {
5983 let request: InitializeRequest = serde_json::from_value(json!({
5984 "protocolVersion": 2,
5985 "capabilities": false,
5986 "info": {
5987 "name": "client",
5988 "version": "1.0.0"
5989 }
5990 }))
5991 .unwrap();
5992 assert_eq!(request.capabilities, ClientCapabilities::default());
5993
5994 let response: InitializeResponse = serde_json::from_value(json!({
5995 "protocolVersion": 2,
5996 "capabilities": false,
5997 "info": {
5998 "name": "agent",
5999 "version": "1.0.0"
6000 }
6001 }))
6002 .unwrap();
6003 assert_eq!(response.capabilities, AgentCapabilities::default());
6004 }
6005
6006 #[test]
6007 fn test_agent_capabilities_default_on_malformed_values() {
6008 let capabilities: AgentCapabilities = serde_json::from_value(json!({
6009 "session": false,
6010 "auth": false
6011 }))
6012 .unwrap();
6013
6014 assert!(capabilities.session.is_none());
6015 assert_eq!(capabilities.auth, None);
6016 }
6017
6018 #[test]
6019 fn test_mcp_server_stdio_serialization() {
6020 let server = McpServer::Stdio(
6021 McpServerStdio::new("test-server", "/usr/bin/server")
6022 .args(vec!["--port".to_string(), "3000".to_string()])
6023 .env(vec![EnvVariable::new("API_KEY", "secret123")]),
6024 );
6025
6026 let json = serde_json::to_value(&server).unwrap();
6027 assert_eq!(
6028 json,
6029 json!({
6030 "type": "stdio",
6031 "name": "test-server",
6032 "command": "/usr/bin/server",
6033 "args": ["--port", "3000"],
6034 "env": [
6035 {
6036 "name": "API_KEY",
6037 "value": "secret123"
6038 }
6039 ]
6040 })
6041 );
6042
6043 let deserialized: McpServer = serde_json::from_value(json).unwrap();
6044 match deserialized {
6045 McpServer::Stdio(McpServerStdio {
6046 name,
6047 command,
6048 args,
6049 env,
6050 meta: _,
6051 }) => {
6052 assert_eq!(name, "test-server");
6053 assert_eq!(command, AbsolutePath::new("/usr/bin/server"));
6054 assert_eq!(args, vec!["--port", "3000"]);
6055 assert_eq!(env.len(), 1);
6056 assert_eq!(env[0].name, "API_KEY");
6057 assert_eq!(env[0].value, "secret123");
6058 }
6059 _ => panic!("Expected Stdio variant"),
6060 }
6061 }
6062
6063 #[test]
6064 fn test_mcp_server_empty_arrays_are_optional() {
6065 let stdio = McpServer::Stdio(McpServerStdio::new("test-server", "/usr/bin/server"));
6066 assert_eq!(
6067 serde_json::to_value(&stdio).unwrap(),
6068 json!({
6069 "type": "stdio",
6070 "name": "test-server",
6071 "command": "/usr/bin/server"
6072 })
6073 );
6074
6075 let McpServer::Stdio(McpServerStdio { args, env, .. }) =
6076 serde_json::from_value::<McpServer>(json!({
6077 "type": "stdio",
6078 "name": "test-server",
6079 "command": "/usr/bin/server"
6080 }))
6081 .unwrap()
6082 else {
6083 panic!("Expected Stdio variant");
6084 };
6085 assert_eq!(args, Vec::<String>::new());
6086 assert_eq!(env, []);
6087
6088 let http = McpServer::Http(McpServerHttp::new("http-server", "https://api.example.com"));
6089 assert_eq!(
6090 serde_json::to_value(&http).unwrap(),
6091 json!({
6092 "type": "http",
6093 "name": "http-server",
6094 "url": "https://api.example.com"
6095 })
6096 );
6097
6098 let McpServer::Http(McpServerHttp { headers, .. }) =
6099 serde_json::from_value::<McpServer>(json!({
6100 "type": "http",
6101 "name": "http-server",
6102 "url": "https://api.example.com"
6103 }))
6104 .unwrap()
6105 else {
6106 panic!("Expected Http variant");
6107 };
6108 assert_eq!(headers, []);
6109 }
6110
6111 #[test]
6112 fn test_mcp_server_unknown_transport_serialization() {
6113 let json = json!({
6114 "type": "websocket",
6115 "name": "future-server",
6116 "url": "wss://example.com/mcp",
6117 "protocolVersion": "2026-01-01"
6118 });
6119
6120 let deserialized: McpServer = serde_json::from_value(json.clone()).unwrap();
6121 let McpServer::Other(OtherMcpServer { type_, fields }) = &deserialized else {
6122 panic!("Expected Other variant");
6123 };
6124
6125 assert_eq!(type_, "websocket");
6126 assert_eq!(fields["name"], "future-server");
6127 assert_eq!(fields["url"], "wss://example.com/mcp");
6128 assert_eq!(fields["protocolVersion"], "2026-01-01");
6129 assert_eq!(serde_json::to_value(&deserialized).unwrap(), json);
6130 }
6131
6132 #[test]
6133 fn test_mcp_server_stdio_requires_type() {
6134 let result = serde_json::from_value::<McpServer>(json!({
6135 "name": "test-server",
6136 "command": "/usr/bin/server",
6137 "args": [],
6138 "env": []
6139 }));
6140
6141 assert!(result.is_err());
6142 }
6143
6144 #[test]
6145 fn test_mcp_server_unknown_does_not_hide_malformed_known_transport() {
6146 let result = serde_json::from_value::<McpServer>(json!({
6147 "type": "stdio",
6148 "name": "test-server",
6149 "args": [],
6150 "env": []
6151 }));
6152
6153 assert!(result.is_err());
6154 }
6155
6156 #[test]
6157 fn test_mcp_server_http_serialization() {
6158 let server = McpServer::Http(
6159 McpServerHttp::new("http-server", "https://api.example.com").headers(vec![
6160 HttpHeader::new("Authorization", "Bearer token123"),
6161 HttpHeader::new("Content-Type", "application/json"),
6162 ]),
6163 );
6164
6165 let json = serde_json::to_value(&server).unwrap();
6166 assert_eq!(
6167 json,
6168 json!({
6169 "type": "http",
6170 "name": "http-server",
6171 "url": "https://api.example.com",
6172 "headers": [
6173 {
6174 "name": "Authorization",
6175 "value": "Bearer token123"
6176 },
6177 {
6178 "name": "Content-Type",
6179 "value": "application/json"
6180 }
6181 ]
6182 })
6183 );
6184
6185 let deserialized: McpServer = serde_json::from_value(json).unwrap();
6186 match deserialized {
6187 McpServer::Http(McpServerHttp {
6188 name,
6189 url,
6190 headers,
6191 meta: _,
6192 }) => {
6193 assert_eq!(name, "http-server");
6194 assert_eq!(url, "https://api.example.com");
6195 assert_eq!(headers.len(), 2);
6196 assert_eq!(headers[0].name, "Authorization");
6197 assert_eq!(headers[0].value, "Bearer token123");
6198 assert_eq!(headers[1].name, "Content-Type");
6199 assert_eq!(headers[1].value, "application/json");
6200 }
6201 _ => panic!("Expected Http variant"),
6202 }
6203 }
6204
6205 #[cfg(feature = "schemars")]
6206 #[test]
6207 fn mcp_server_http_schema_marks_url_as_uri() {
6208 let schema = serde_json::to_value(schemars::schema_for!(McpServerHttp)).unwrap();
6209
6210 assert_eq!(schema["properties"]["url"]["format"], "uri");
6211 }
6212
6213 #[cfg(feature = "unstable_mcp_over_acp")]
6214 #[test]
6215 fn test_client_mcp_message_method_names() {
6216 use serde_json::json;
6217
6218 assert_eq!(AGENT_METHOD_NAMES.mcp_message, "mcp/message");
6219
6220 let notification =
6221 MessageMcpNotification::new("server-1", "req-1", "notifications/progress");
6222 assert_eq!(
6223 ClientNotification::MessageMcpNotification(Box::new(notification.clone())).method(),
6224 "mcp/message"
6225 );
6226 assert_eq!(
6227 serde_json::to_value(notification).unwrap(),
6228 json!({
6229 "serverId": "server-1",
6230 "requestId": "req-1",
6231 "method": "notifications/progress"
6232 })
6233 );
6234 let notification: MessageMcpNotification = serde_json::from_value(json!({
6235 "serverId": "server-1", "requestId": "req-1", "method": "notifications/progress",
6236 "params": null, "_meta": null
6237 }))
6238 .unwrap();
6239 assert_eq!(notification.params, None);
6240 assert_eq!(notification.meta, None);
6241 for key in ["serverId", "requestId", "method"] {
6242 let mut value = json!({"serverId":"server-1", "requestId":"req-1", "method":"notifications/progress"});
6243 value.as_object_mut().unwrap().remove(key);
6244 assert!(serde_json::from_value::<MessageMcpNotification>(value).is_err());
6245 }
6246 }
6247
6248 #[test]
6249 fn test_auth_method_names() {
6250 assert_eq!(AGENT_METHOD_NAMES.auth_login, "auth/login");
6251 assert_eq!(AGENT_METHOD_NAMES.auth_logout, "auth/logout");
6252
6253 assert_eq!(
6254 ClientRequest::LoginAuthRequest(Box::new(LoginAuthRequest::new("agent-login")))
6255 .method(),
6256 "auth/login"
6257 );
6258 assert_eq!(
6259 ClientRequest::LogoutAuthRequest(Box::new(LogoutAuthRequest::new())).method(),
6260 "auth/logout"
6261 );
6262 }
6263
6264 #[test]
6265 fn test_session_config_option_category_known_variants() {
6266 assert_eq!(
6268 serde_json::to_value(&SessionConfigOptionCategory::Mode).unwrap(),
6269 json!("mode")
6270 );
6271 assert_eq!(
6272 serde_json::to_value(&SessionConfigOptionCategory::Model).unwrap(),
6273 json!("model")
6274 );
6275 assert_eq!(
6276 serde_json::to_value(&SessionConfigOptionCategory::ModelConfig).unwrap(),
6277 json!("model_config")
6278 );
6279 assert_eq!(
6280 serde_json::to_value(&SessionConfigOptionCategory::ThoughtLevel).unwrap(),
6281 json!("thought_level")
6282 );
6283
6284 assert_eq!(
6286 serde_json::from_str::<SessionConfigOptionCategory>("\"mode\"").unwrap(),
6287 SessionConfigOptionCategory::Mode
6288 );
6289 assert_eq!(
6290 serde_json::from_str::<SessionConfigOptionCategory>("\"model\"").unwrap(),
6291 SessionConfigOptionCategory::Model
6292 );
6293 assert_eq!(
6294 serde_json::from_str::<SessionConfigOptionCategory>("\"model_config\"").unwrap(),
6295 SessionConfigOptionCategory::ModelConfig
6296 );
6297 assert_eq!(
6298 serde_json::from_str::<SessionConfigOptionCategory>("\"thought_level\"").unwrap(),
6299 SessionConfigOptionCategory::ThoughtLevel
6300 );
6301 }
6302
6303 #[test]
6304 fn test_session_config_option_category_unknown_variants() {
6305 let unknown: SessionConfigOptionCategory =
6307 serde_json::from_str("\"some_future_category\"").unwrap();
6308 assert_eq!(
6309 unknown,
6310 SessionConfigOptionCategory::Other("some_future_category".to_string())
6311 );
6312
6313 let json = serde_json::to_value(&unknown).unwrap();
6315 assert_eq!(json, json!("some_future_category"));
6316 }
6317
6318 #[test]
6319 fn test_session_config_option_category_custom_categories() {
6320 let custom: SessionConfigOptionCategory =
6322 serde_json::from_str("\"_my_custom_category\"").unwrap();
6323 assert_eq!(
6324 custom,
6325 SessionConfigOptionCategory::Other("_my_custom_category".to_string())
6326 );
6327
6328 let json = serde_json::to_value(&custom).unwrap();
6330 assert_eq!(json, json!("_my_custom_category"));
6331
6332 let deserialized: SessionConfigOptionCategory = serde_json::from_value(json).unwrap();
6334 assert_eq!(
6335 deserialized,
6336 SessionConfigOptionCategory::Other("_my_custom_category".to_string()),
6337 );
6338 }
6339
6340 fn test_config_option() -> SessionConfigOption {
6341 SessionConfigOption::select(
6342 "mode",
6343 "Mode",
6344 "ask",
6345 vec![SessionConfigSelectOption::new("ask", "Ask")],
6346 )
6347 }
6348
6349 #[test]
6350 fn test_session_response_initial_state_default_empty_and_skip_serializing() {
6351 assert_eq!(
6352 serde_json::to_value(NewSessionResponse::new("sess")).unwrap(),
6353 json!({ "sessionId": "sess" })
6354 );
6355 assert_eq!(
6356 serde_json::to_value(ResumeSessionResponse::new()).unwrap(),
6357 json!({})
6358 );
6359 #[cfg(feature = "unstable_session_fork")]
6360 assert_eq!(
6361 serde_json::to_value(ForkSessionResponse::new("fork")).unwrap(),
6362 json!({ "sessionId": "fork" })
6363 );
6364
6365 let json = serde_json::to_value(
6366 NewSessionResponse::new("sess").config_options(vec![test_config_option()]),
6367 )
6368 .unwrap();
6369 assert_eq!(json["configOptions"].as_array().unwrap().len(), 1);
6370 }
6371
6372 #[test]
6373 fn test_session_response_config_options_deserialize_missing_null_and_invalid() {
6374 let missing: NewSessionResponse =
6375 serde_json::from_value(json!({ "sessionId": "sess" })).unwrap();
6376 assert_eq!(missing.config_options, []);
6377
6378 let null: NewSessionResponse = serde_json::from_value(json!({
6379 "sessionId": "sess",
6380 "configOptions": null
6381 }))
6382 .unwrap();
6383 assert_eq!(null.config_options, []);
6384
6385 let wrong_shape: NewSessionResponse = serde_json::from_value(json!({
6386 "sessionId": "sess",
6387 "configOptions": "oops"
6388 }))
6389 .unwrap();
6390 assert_eq!(wrong_shape.config_options, []);
6391
6392 let valid_option = serde_json::to_value(test_config_option()).unwrap();
6393 let mixed: NewSessionResponse = serde_json::from_value(json!({
6394 "sessionId": "sess",
6395 "configOptions": ["oops", valid_option]
6396 }))
6397 .unwrap();
6398 assert_eq!(mixed.config_options.len(), 1);
6399
6400 let resume: ResumeSessionResponse = serde_json::from_value(json!({})).unwrap();
6401 assert_eq!(resume.config_options, []);
6402 #[cfg(feature = "unstable_session_fork")]
6403 {
6404 let fork: ForkSessionResponse =
6405 serde_json::from_value(json!({ "sessionId": "fork" })).unwrap();
6406 assert_eq!(fork.config_options, []);
6407 }
6408 }
6409
6410 #[test]
6411 fn test_session_response_available_commands_roundtrip_with_config_options() {
6412 use super::super::{AvailableCommandInput, TextCommandInput};
6413
6414 let commands = vec![
6415 AvailableCommand::new("web", "Search the web")
6416 .input(AvailableCommandInput::Text(TextCommandInput::new("Query"))),
6417 AvailableCommand::new("test", "Run project tests"),
6418 ];
6419 let mut expected = json!({
6420 "sessionId": "sess",
6421 "configOptions": [test_config_option()],
6422 "availableCommands": [
6423 {
6424 "name": "web",
6425 "description": "Search the web",
6426 "input": { "type": "text", "hint": "Query" }
6427 },
6428 { "name": "test", "description": "Run project tests" }
6429 ]
6430 });
6431
6432 let new = NewSessionResponse::new("sess")
6433 .config_options(vec![test_config_option()])
6434 .available_commands(commands.clone());
6435 assert_eq!(serde_json::to_value(&new).unwrap(), expected);
6436 assert_eq!(
6437 serde_json::from_value::<NewSessionResponse>(expected.clone()).unwrap(),
6438 new
6439 );
6440
6441 expected.as_object_mut().unwrap().remove("sessionId");
6442 let resume = ResumeSessionResponse::new()
6443 .config_options(vec![test_config_option()])
6444 .available_commands(commands.clone());
6445 assert_eq!(serde_json::to_value(&resume).unwrap(), expected);
6446 assert_eq!(
6447 serde_json::from_value::<ResumeSessionResponse>(expected.clone()).unwrap(),
6448 resume
6449 );
6450
6451 #[cfg(feature = "unstable_session_fork")]
6452 {
6453 expected["sessionId"] = json!("fork");
6454 let fork = ForkSessionResponse::new("fork")
6455 .config_options(vec![test_config_option()])
6456 .available_commands(commands);
6457 assert_eq!(serde_json::to_value(&fork).unwrap(), expected);
6458 assert_eq!(
6459 serde_json::from_value::<ForkSessionResponse>(expected).unwrap(),
6460 fork
6461 );
6462 }
6463 }
6464
6465 #[test]
6466 fn test_session_response_available_commands_deserialize_missing_null_and_invalid() {
6467 let command = AvailableCommand::new("test", "Run project tests");
6468 let config_options = vec![test_config_option()];
6469
6470 for (commands, expected) in [
6471 (None, vec![]),
6472 (Some(json!(null)), vec![]),
6473 (Some(json!([])), vec![]),
6474 (Some(json!("oops")), vec![]),
6475 (Some(json!({})), vec![]),
6476 (Some(json!([null, {}, { "name": "invalid" }])), vec![]),
6477 (
6478 Some(json!([null, command, { "name": "invalid" }])),
6479 vec![command.clone()],
6480 ),
6481 ] {
6482 let mut value = json!({
6483 "sessionId": "sess",
6484 "configOptions": config_options
6485 });
6486 if let Some(commands) = commands {
6487 value["availableCommands"] = commands;
6488 }
6489
6490 let new: NewSessionResponse = serde_json::from_value(value.clone()).unwrap();
6491 assert_eq!(new.available_commands, expected);
6492 assert_eq!(new.config_options, config_options);
6493
6494 #[cfg(feature = "unstable_session_fork")]
6495 {
6496 let fork: ForkSessionResponse = serde_json::from_value(value.clone()).unwrap();
6497 assert_eq!(fork.available_commands, expected);
6498 assert_eq!(fork.config_options, config_options);
6499 }
6500
6501 value.as_object_mut().unwrap().remove("sessionId");
6502 let resume: ResumeSessionResponse = serde_json::from_value(value).unwrap();
6503 assert_eq!(resume.available_commands, expected);
6504 assert_eq!(resume.config_options, config_options);
6505 }
6506
6507 let resume: ResumeSessionResponse = serde_json::from_value(json!(null)).unwrap();
6508 assert_eq!(resume.available_commands, []);
6509 }
6510
6511 #[test]
6512 fn test_resume_session_replay_from_serialization() {
6513 assert_eq!(
6514 serde_json::to_value(ResumeSessionRequest::new(
6515 "sess_abc123",
6516 "/home/user/project"
6517 ))
6518 .unwrap(),
6519 json!({
6520 "sessionId": "sess_abc123",
6521 "cwd": "/home/user/project"
6522 })
6523 );
6524 assert_eq!(
6525 serde_json::to_value(
6526 ResumeSessionRequest::new("sess_abc123", "/home/user/project")
6527 .replay_from(ReplayFrom::from(ReplayFromStart::new()))
6528 )
6529 .unwrap(),
6530 json!({
6531 "sessionId": "sess_abc123",
6532 "cwd": "/home/user/project",
6533 "replayFrom": {
6534 "type": "start"
6535 }
6536 })
6537 );
6538
6539 let replay: ResumeSessionRequest = serde_json::from_value(json!({
6540 "sessionId": "sess_abc123",
6541 "cwd": "/home/user/project",
6542 "replayFrom": {
6543 "type": "start"
6544 }
6545 }))
6546 .unwrap();
6547 assert!(matches!(replay.replay_from, Some(ReplayFrom::Start(_))));
6548
6549 let none: ResumeSessionRequest = serde_json::from_value(json!({
6550 "sessionId": "sess_abc123",
6551 "cwd": "/home/user/project",
6552 "replayFrom": null
6553 }))
6554 .unwrap();
6555 assert!(none.replay_from.is_none());
6556 }
6557
6558 #[test]
6559 fn test_auth_method_agent_serialization() {
6560 let method = AuthMethod::Agent(AuthMethodAgent::new("default-auth", "Default Auth"));
6561
6562 let json = serde_json::to_value(&method).unwrap();
6563 assert_eq!(
6564 json,
6565 json!({
6566 "methodId": "default-auth",
6567 "name": "Default Auth",
6568 "type": "agent"
6569 })
6570 );
6571 assert!(!json.as_object().unwrap().contains_key("description"));
6573
6574 let deserialized: AuthMethod = serde_json::from_value(json).unwrap();
6575 match deserialized {
6576 AuthMethod::Agent(AuthMethodAgent {
6577 method_id, name, ..
6578 }) => {
6579 assert_eq!(method_id.0.as_ref(), "default-auth");
6580 assert_eq!(name, "Default Auth");
6581 }
6582 _ => panic!("Expected Agent variant"),
6583 }
6584 }
6585
6586 #[test]
6587 fn test_auth_method_agent_deserialization() {
6588 let json = json!({
6589 "methodId": "agent-auth",
6590 "name": "Agent Auth",
6591 "type": "agent"
6592 });
6593
6594 let deserialized: AuthMethod = serde_json::from_value(json).unwrap();
6595 assert!(matches!(deserialized, AuthMethod::Agent(_)));
6596 }
6597
6598 #[test]
6599 fn test_auth_method_agent_requires_type() {
6600 assert!(
6601 serde_json::from_value::<AuthMethod>(json!({
6602 "methodId": "agent-auth",
6603 "name": "Agent Auth"
6604 }))
6605 .is_err()
6606 );
6607 }
6608
6609 #[test]
6610 fn test_auth_method_agent_rejects_null_type() {
6611 assert!(
6612 serde_json::from_value::<AuthMethod>(json!({
6613 "methodId": "agent-auth",
6614 "name": "Agent Auth",
6615 "type": null
6616 }))
6617 .is_err()
6618 );
6619 }
6620
6621 #[test]
6622 fn test_auth_method_unknown_does_not_hide_malformed_agent() {
6623 assert!(
6624 serde_json::from_value::<AuthMethod>(json!({
6625 "methodId": "agent-auth",
6626 "type": "agent"
6627 }))
6628 .is_err()
6629 );
6630 assert!(
6631 serde_json::from_value::<AuthMethod>(json!({
6632 "methodId": "api-key",
6633 "type": "env_var",
6634 "vars": [{"name": "API_KEY"}]
6635 }))
6636 .is_err()
6637 );
6638 }
6639
6640 #[test]
6641 fn test_auth_method_unknown_variant_roundtrip() {
6642 let method: AuthMethod = serde_json::from_value(json!({
6643 "methodId": "oauth",
6644 "name": "OAuth",
6645 "type": "_oauth",
6646 "authorizationUrl": "https://example.com/auth"
6647 }))
6648 .unwrap();
6649
6650 assert_eq!(method.method_id().0.as_ref(), "oauth");
6651 assert_eq!(method.name(), "OAuth");
6652 let AuthMethod::Other(unknown) = method else {
6653 panic!("expected unknown auth method");
6654 };
6655 assert_eq!(unknown.type_, "_oauth");
6656 assert_eq!(
6657 unknown.fields.get("authorizationUrl"),
6658 Some(&json!("https://example.com/auth"))
6659 );
6660
6661 assert_eq!(
6662 serde_json::to_value(AuthMethod::Other(unknown)).unwrap(),
6663 json!({
6664 "methodId": "oauth",
6665 "name": "OAuth",
6666 "type": "_oauth",
6667 "authorizationUrl": "https://example.com/auth"
6668 })
6669 );
6670 }
6671
6672 #[test]
6673 fn test_auth_method_unknown_does_not_hide_malformed_known_variant() {
6674 assert!(
6675 serde_json::from_value::<AuthMethod>(json!({
6676 "methodId": "terminal-auth",
6677 "type": "terminal"
6678 }))
6679 .is_err()
6680 );
6681 }
6682
6683 #[test]
6684 fn test_session_delete_serialization() {
6685 assert_eq!(AGENT_METHOD_NAMES.session_delete, "session/delete");
6686 assert_eq!(
6687 ClientRequest::DeleteSessionRequest(Box::new(DeleteSessionRequest::new("sess_abc123")))
6688 .method(),
6689 "session/delete"
6690 );
6691 assert_eq!(
6692 serde_json::to_value(DeleteSessionRequest::new("sess_abc123")).unwrap(),
6693 json!({
6694 "sessionId": "sess_abc123"
6695 })
6696 );
6697 assert_eq!(
6698 serde_json::to_value(DeleteSessionResponse::new()).unwrap(),
6699 json!({})
6700 );
6701 assert_eq!(
6702 serde_json::to_value(
6703 SessionCapabilities::new().delete(SessionDeleteCapabilities::new())
6704 )
6705 .unwrap(),
6706 json!({
6707 "delete": {}
6708 })
6709 );
6710 }
6711 #[test]
6712 fn test_session_additional_directories_serialization() {
6713 assert_eq!(
6714 serde_json::to_value(NewSessionRequest::new("/home/user/project")).unwrap(),
6715 json!({
6716 "cwd": "/home/user/project",
6717 })
6718 );
6719 assert_eq!(
6720 serde_json::to_value(
6721 NewSessionRequest::new("/home/user/project").additional_directories(vec![
6722 PathBuf::from("/home/user/shared-lib"),
6723 PathBuf::from("/home/user/product-docs"),
6724 ])
6725 )
6726 .unwrap(),
6727 json!({
6728 "cwd": "/home/user/project",
6729 "additionalDirectories": [
6730 "/home/user/shared-lib",
6731 "/home/user/product-docs"
6732 ],
6733 })
6734 );
6735 assert_eq!(
6736 serde_json::to_value(ResumeSessionRequest::new(
6737 "sess_abc123",
6738 "/home/user/project"
6739 ))
6740 .unwrap(),
6741 json!({
6742 "sessionId": "sess_abc123",
6743 "cwd": "/home/user/project",
6744 })
6745 );
6746 assert_eq!(
6747 serde_json::from_value::<ResumeSessionRequest>(json!({
6748 "sessionId": "sess_abc123",
6749 "cwd": "/home/user/project"
6750 }))
6751 .unwrap()
6752 .mcp_servers,
6753 Vec::<McpServer>::new()
6754 );
6755 assert_eq!(
6756 serde_json::from_value::<ResumeSessionRequest>(json!({
6757 "sessionId": "sess_abc123",
6758 "cwd": "/home/user/project",
6759 "mcpServers": null
6760 }))
6761 .unwrap()
6762 .mcp_servers,
6763 Vec::<McpServer>::new()
6764 );
6765 assert_eq!(
6766 serde_json::to_value(SessionInfo::new("sess_abc123", "/home/user/project")).unwrap(),
6767 json!({
6768 "sessionId": "sess_abc123",
6769 "cwd": "/home/user/project"
6770 })
6771 );
6772 assert_eq!(
6773 serde_json::to_value(
6774 SessionInfo::new("sess_abc123", "/home/user/project").additional_directories(vec![
6775 PathBuf::from("/home/user/shared-lib"),
6776 PathBuf::from("/home/user/product-docs"),
6777 ])
6778 )
6779 .unwrap(),
6780 json!({
6781 "sessionId": "sess_abc123",
6782 "cwd": "/home/user/project",
6783 "additionalDirectories": [
6784 "/home/user/shared-lib",
6785 "/home/user/product-docs"
6786 ]
6787 })
6788 );
6789 assert_eq!(
6790 serde_json::from_value::<SessionInfo>(json!({
6791 "sessionId": "sess_abc123",
6792 "cwd": "/home/user/project"
6793 }))
6794 .unwrap()
6795 .additional_directories,
6796 Vec::<AbsolutePath>::new()
6797 );
6798 }
6799 #[test]
6800 fn test_session_additional_directories_capabilities_serialization() {
6801 assert_eq!(
6802 serde_json::to_value(
6803 SessionCapabilities::new()
6804 .additional_directories(SessionAdditionalDirectoriesCapabilities::new())
6805 )
6806 .unwrap(),
6807 json!({
6808 "additionalDirectories": {}
6809 })
6810 );
6811 }
6812
6813 #[cfg(feature = "unstable_session_system_prompt")]
6814 #[test]
6815 fn test_session_system_prompt_capabilities_serialization() {
6816 assert_eq!(
6817 serde_json::to_value(
6818 SessionCapabilities::new().system_prompt(SessionSystemPromptCapabilities::new())
6819 )
6820 .unwrap(),
6821 json!({ "systemPrompt": {} })
6822 );
6823 assert_eq!(
6824 serde_json::to_value(
6825 SessionCapabilities::new().system_prompt(
6826 SessionSystemPromptCapabilities::new().mode(
6827 SessionSystemPromptModeCapabilities::new()
6828 .override_(SessionSystemPromptOverrideCapabilities::new())
6829 )
6830 )
6831 )
6832 .unwrap(),
6833 json!({ "systemPrompt": { "mode": { "override": {} } } })
6834 );
6835 assert_eq!(
6836 serde_json::from_value::<SessionCapabilities>(
6837 json!({ "systemPrompt": { "mode": { "override": {} } } })
6838 )
6839 .unwrap()
6840 .system_prompt
6841 .and_then(|capabilities| capabilities.mode)
6842 .and_then(|mode| mode.override_),
6843 Some(SessionSystemPromptOverrideCapabilities::new())
6844 );
6845
6846 assert_eq!(
6848 serde_json::from_value::<SessionCapabilities>(json!({ "systemPrompt": "yes" }))
6849 .unwrap()
6850 .system_prompt,
6851 None
6852 );
6853 assert_eq!(
6854 serde_json::from_value::<SessionCapabilities>(
6855 json!({ "systemPrompt": { "mode": { "override": true } } })
6856 )
6857 .unwrap()
6858 .system_prompt,
6859 Some(
6860 SessionSystemPromptCapabilities::new()
6861 .mode(SessionSystemPromptModeCapabilities::new())
6862 )
6863 );
6864 assert_eq!(
6865 serde_json::from_value::<SessionCapabilities>(
6866 json!({ "systemPrompt": { "mode": "override" } })
6867 )
6868 .unwrap()
6869 .system_prompt,
6870 Some(SessionSystemPromptCapabilities::new())
6871 );
6872 }
6873
6874 #[cfg(feature = "unstable_session_system_prompt")]
6875 #[test]
6876 fn test_system_prompt_lifecycle_request_serialization() {
6877 let system_prompt = SystemPrompt::new(vec![ContentBlock::from(
6878 "You are a code review specialist.",
6879 )]);
6880 let system_prompt_json = json!({
6881 "mode": "append",
6882 "content": [{ "type": "text", "text": "You are a code review specialist." }]
6883 });
6884
6885 let request =
6886 NewSessionRequest::new("/home/user/project").system_prompt(system_prompt.clone());
6887 let request_json = json!({
6888 "cwd": "/home/user/project",
6889 "systemPrompt": system_prompt_json
6890 });
6891 assert_eq!(serde_json::to_value(&request).unwrap(), request_json);
6892 assert_eq!(
6893 serde_json::from_value::<NewSessionRequest>(request_json).unwrap(),
6894 request
6895 );
6896
6897 assert_eq!(
6898 serde_json::to_value(
6899 ResumeSessionRequest::new("sess_abc123", "/home/user/project")
6900 .system_prompt(system_prompt.clone())
6901 )
6902 .unwrap()["systemPrompt"],
6903 system_prompt_json
6904 );
6905 #[cfg(feature = "unstable_session_fork")]
6906 assert_eq!(
6907 serde_json::to_value(
6908 ForkSessionRequest::new("sess_abc123", "/home/user/project")
6909 .system_prompt(system_prompt.clone())
6910 )
6911 .unwrap()["systemPrompt"],
6912 system_prompt_json
6913 );
6914
6915 let override_prompt =
6916 SystemPrompt::new(vec![ContentBlock::from("...")]).mode(SystemPromptMode::Override);
6917 let override_json = json!({
6918 "mode": "override",
6919 "content": [{ "type": "text", "text": "..." }]
6920 });
6921 assert_eq!(
6922 serde_json::to_value(&override_prompt).unwrap(),
6923 override_json
6924 );
6925 assert_eq!(
6926 serde_json::from_value::<SystemPrompt>(override_json).unwrap(),
6927 override_prompt
6928 );
6929 }
6930
6931 #[cfg(feature = "unstable_session_system_prompt")]
6934 #[test]
6935 fn test_system_prompt_new_defaults_to_append() {
6936 let system_prompt = SystemPrompt::new(vec![ContentBlock::from("...")]);
6937 assert_eq!(system_prompt.mode, SystemPromptMode::Append);
6938 assert_eq!(
6939 serde_json::to_value(&system_prompt).unwrap(),
6940 json!({ "mode": "append", "content": [{ "type": "text", "text": "..." }] })
6941 );
6942 }
6943
6944 #[cfg(feature = "unstable_session_system_prompt")]
6948 #[test]
6949 fn test_system_prompt_preserves_unknown_modes() {
6950 for mode in ["_vendor_mode", "prepend"] {
6951 let system_prompt_json =
6952 json!({ "mode": mode, "content": [{ "type": "text", "text": "..." }] });
6953 let system_prompt: SystemPrompt =
6954 serde_json::from_value(system_prompt_json.clone()).unwrap();
6955 assert_eq!(
6956 system_prompt.mode,
6957 SystemPromptMode::Other(mode.to_string())
6958 );
6959 assert_eq!(
6960 serde_json::to_value(&system_prompt).unwrap(),
6961 system_prompt_json
6962 );
6963 }
6964 }
6965
6966 #[cfg(feature = "unstable_session_system_prompt")]
6970 fn assert_system_prompt_is_strict<T>(base: &serde_json::Value)
6971 where
6972 T: serde::de::DeserializeOwned + PartialEq + std::fmt::Debug,
6973 {
6974 let name = std::any::type_name::<T>();
6975 let with = |system_prompt: serde_json::Value| {
6976 let mut request = base.clone();
6977 request["systemPrompt"] = system_prompt;
6978 serde_json::from_value::<T>(request)
6979 };
6980
6981 for system_prompt in [
6982 json!("You are a code review specialist."),
6983 json!({ "content": [{ "type": "text", "text": "..." }] }),
6984 json!({ "mode": null, "content": [{ "type": "text", "text": "..." }] }),
6985 json!({ "mode": 1, "content": [{ "type": "text", "text": "..." }] }),
6986 json!({ "mode": "append" }),
6987 json!({ "mode": "append", "content": "..." }),
6988 json!({ "mode": "append", "content": [{ "type": "text" }] }),
6989 ] {
6990 assert!(
6991 with(system_prompt.clone()).is_err(),
6992 "{name}.systemPrompt: {system_prompt}"
6993 );
6994 }
6995
6996 assert_eq!(
6997 with(serde_json::Value::Null).unwrap(),
6998 serde_json::from_value::<T>(base.clone()).unwrap(),
6999 "{name}.systemPrompt"
7000 );
7001 }
7002
7003 #[cfg(feature = "unstable_session_system_prompt")]
7004 #[test]
7005 fn test_session_setup_requests_reject_malformed_system_prompts() {
7006 assert!(
7007 serde_json::to_value(NewSessionRequest::new("/repo"))
7008 .unwrap()
7009 .get("systemPrompt")
7010 .is_none()
7011 );
7012 assert_system_prompt_is_strict::<NewSessionRequest>(&json!({ "cwd": "/repo" }));
7013 assert_system_prompt_is_strict::<ResumeSessionRequest>(&json!({
7014 "sessionId": "sess-1",
7015 "cwd": "/repo"
7016 }));
7017 #[cfg(feature = "unstable_session_fork")]
7018 assert_system_prompt_is_strict::<ForkSessionRequest>(&json!({
7019 "sessionId": "sess-1",
7020 "cwd": "/repo"
7021 }));
7022 }
7023
7024 #[cfg(feature = "unstable_session_system_prompt")]
7027 #[test]
7028 fn test_system_prompt_preserves_unknown_content_blocks() {
7029 let request: NewSessionRequest = serde_json::from_value(json!({
7030 "cwd": "/home/user/project",
7031 "systemPrompt": {
7032 "mode": "append",
7033 "content": [{ "type": "_vendor_video", "uri": "https://example.com/intro.mp4" }]
7034 }
7035 }))
7036 .unwrap();
7037 let content = request.system_prompt.unwrap().content;
7038 assert_eq!(content.len(), 1);
7039 assert!(matches!(content[0], ContentBlock::Other(_)));
7040 }
7041
7042 #[test]
7043 fn test_auth_method_terminal_serialization() {
7044 let method = AuthMethod::Terminal(AuthMethodTerminal::new("tui-auth", "Terminal Auth"));
7045
7046 let json = serde_json::to_value(&method).unwrap();
7047 assert_eq!(
7048 json,
7049 json!({
7050 "methodId": "tui-auth",
7051 "name": "Terminal Auth",
7052 "type": "terminal"
7053 })
7054 );
7055 assert!(!json.as_object().unwrap().contains_key("args"));
7057 assert!(!json.as_object().unwrap().contains_key("env"));
7058
7059 let deserialized: AuthMethod = serde_json::from_value(json).unwrap();
7060 match deserialized {
7061 AuthMethod::Terminal(AuthMethodTerminal { args, env, .. }) => {
7062 assert_eq!(args, Vec::<String>::new());
7063 assert_eq!(env, []);
7064 }
7065 _ => panic!("Expected Terminal variant"),
7066 }
7067 }
7068
7069 #[test]
7070 fn test_auth_method_terminal_with_args_and_env_serialization() {
7071 let method = AuthMethod::Terminal(
7072 AuthMethodTerminal::new("tui-auth", "Terminal Auth")
7073 .args(vec!["--interactive".to_string(), "--color".to_string()])
7074 .env(vec![EnvVariable::new("TERM", "xterm-256color")]),
7075 );
7076
7077 let json = serde_json::to_value(&method).unwrap();
7078 assert_eq!(
7079 json,
7080 json!({
7081 "methodId": "tui-auth",
7082 "name": "Terminal Auth",
7083 "type": "terminal",
7084 "args": ["--interactive", "--color"],
7085 "env": [
7086 {
7087 "name": "TERM",
7088 "value": "xterm-256color"
7089 }
7090 ]
7091 })
7092 );
7093
7094 let deserialized: AuthMethod = serde_json::from_value(json).unwrap();
7095 match deserialized {
7096 AuthMethod::Terminal(AuthMethodTerminal { args, env, .. }) => {
7097 assert_eq!(args, vec!["--interactive", "--color"]);
7098 assert_eq!(env.len(), 1);
7099 assert_eq!(env[0].name, "TERM");
7100 assert_eq!(env[0].value, "xterm-256color");
7101 }
7102 _ => panic!("Expected Terminal variant"),
7103 }
7104 }
7105
7106 #[test]
7107 fn test_session_config_option_id_serialize() {
7108 let val = SessionConfigOptionValue::id("model-1");
7109 let json = serde_json::to_value(&val).unwrap();
7110 assert_eq!(json, json!({ "type": "id", "value": "model-1" }));
7111 }
7112
7113 #[test]
7114 fn test_session_config_option_value_boolean_serialize() {
7115 let val = SessionConfigOptionValue::boolean(true);
7116 let json = serde_json::to_value(&val).unwrap();
7117 assert_eq!(json, json!({ "type": "boolean", "value": true }));
7118 }
7119
7120 #[test]
7121 fn test_session_config_option_value_deserialize_id() {
7122 let json = json!({ "type": "id", "value": "model-1" });
7123 let val: SessionConfigOptionValue = serde_json::from_value(json).unwrap();
7124 assert_eq!(val, SessionConfigOptionValue::id("model-1"));
7125 assert_eq!(val.as_id().unwrap().to_string(), "model-1");
7126 }
7127
7128 #[test]
7129 fn test_session_config_option_value_deserialize_requires_type() {
7130 let json = json!({ "value": "model-1" });
7131 let result = serde_json::from_value::<SessionConfigOptionValue>(json);
7132 assert!(result.is_err());
7133 }
7134
7135 #[test]
7136 fn test_session_config_option_value_deserialize_boolean() {
7137 let json = json!({ "type": "boolean", "value": true });
7138 let val: SessionConfigOptionValue = serde_json::from_value(json).unwrap();
7139 assert_eq!(val, SessionConfigOptionValue::boolean(true));
7140 assert_eq!(val.as_bool(), Some(true));
7141 }
7142
7143 #[test]
7144 fn test_session_config_option_value_deserialize_boolean_false() {
7145 let json = json!({ "type": "boolean", "value": false });
7146 let val: SessionConfigOptionValue = serde_json::from_value(json).unwrap();
7147 assert_eq!(val, SessionConfigOptionValue::boolean(false));
7148 assert_eq!(val.as_bool(), Some(false));
7149 }
7150
7151 #[test]
7152 fn test_session_config_option_value_deserialize_unknown_type_with_string_value() {
7153 let json = json!({
7154 "type": "text",
7155 "value": "freeform input",
7156 "maxLength": 200
7157 });
7158 let val: SessionConfigOptionValue = serde_json::from_value(json).unwrap();
7159 let SessionConfigOptionValue::Other(unknown) = val else {
7160 panic!("Expected Other variant");
7161 };
7162 assert_eq!(unknown.type_, "text");
7163 assert_eq!(unknown.value, json!("freeform input"));
7164 assert_eq!(unknown.fields["maxLength"], json!(200));
7165 }
7166
7167 #[test]
7168 fn test_session_config_option_value_deserialize_unknown_type_with_object_value() {
7169 let json = json!({
7170 "type": "range",
7171 "value": { "min": 1, "max": 5 }
7172 });
7173 let val: SessionConfigOptionValue = serde_json::from_value(json).unwrap();
7174 let SessionConfigOptionValue::Other(unknown) = val else {
7175 panic!("Expected Other variant");
7176 };
7177 assert_eq!(unknown.type_, "range");
7178 assert_eq!(unknown.value, json!({ "min": 1, "max": 5 }));
7179 }
7180
7181 #[test]
7182 fn test_session_config_option_value_roundtrip_id() {
7183 let original = SessionConfigOptionValue::id("option-a");
7184 let json = serde_json::to_value(&original).unwrap();
7185 let roundtripped: SessionConfigOptionValue = serde_json::from_value(json).unwrap();
7186 assert_eq!(original, roundtripped);
7187 }
7188
7189 #[test]
7190 fn test_session_config_option_value_roundtrip_boolean() {
7191 let original = SessionConfigOptionValue::boolean(false);
7192 let json = serde_json::to_value(&original).unwrap();
7193 let roundtripped: SessionConfigOptionValue = serde_json::from_value(json).unwrap();
7194 assert_eq!(original, roundtripped);
7195 }
7196
7197 #[test]
7198 fn test_session_config_option_value_roundtrip_other() {
7199 let mut fields = BTreeMap::new();
7200 fields.insert("maxLength".to_string(), json!(200));
7201 let original = SessionConfigOptionValue::Other(OtherSessionConfigOptionValue::new(
7202 "text",
7203 json!("freeform input"),
7204 fields,
7205 ));
7206 let json = serde_json::to_value(&original).unwrap();
7207 let roundtripped: SessionConfigOptionValue = serde_json::from_value(json).unwrap();
7208 assert_eq!(original, roundtripped);
7209 }
7210
7211 #[test]
7212 fn test_session_config_option_value_type_mismatch_boolean_with_string() {
7213 let json = json!({ "type": "boolean", "value": "not a bool" });
7214 let result = serde_json::from_value::<SessionConfigOptionValue>(json);
7215 assert!(result.is_err());
7216 }
7217
7218 #[test]
7219 fn test_session_config_option_value_from_impls() {
7220 let from_str: SessionConfigOptionValue = "model-1".into();
7221 assert_eq!(from_str.as_id().unwrap().to_string(), "model-1");
7222
7223 let from_id: SessionConfigOptionValue = SessionConfigValueId::new("model-2").into();
7224 assert_eq!(from_id.as_id().unwrap().to_string(), "model-2");
7225
7226 let from_bool: SessionConfigOptionValue = true.into();
7227 assert_eq!(from_bool.as_bool(), Some(true));
7228 }
7229
7230 #[test]
7231 fn test_set_session_config_option_request_id() {
7232 let req = SetSessionConfigOptionRequest::new("sess_1", "model", "model-1");
7233 let json = serde_json::to_value(&req).unwrap();
7234 assert_eq!(
7235 json,
7236 json!({
7237 "sessionId": "sess_1",
7238 "configId": "model",
7239 "type": "id",
7240 "value": "model-1"
7241 })
7242 );
7243 }
7244
7245 #[test]
7246 fn test_set_session_config_option_request_boolean() {
7247 let req = SetSessionConfigOptionRequest::new("sess_1", "brave_mode", true);
7248 let json = serde_json::to_value(&req).unwrap();
7249 assert_eq!(
7250 json,
7251 json!({
7252 "sessionId": "sess_1",
7253 "configId": "brave_mode",
7254 "type": "boolean",
7255 "value": true
7256 })
7257 );
7258 }
7259
7260 #[test]
7261 fn test_set_session_config_option_request_deserialize_requires_type() {
7262 let json = json!({
7263 "sessionId": "sess_1",
7264 "configId": "model",
7265 "value": "model-1"
7266 });
7267 let result = serde_json::from_value::<SetSessionConfigOptionRequest>(json);
7268 assert!(result.is_err());
7269 }
7270
7271 #[test]
7272 fn test_set_session_config_option_request_deserialize_boolean() {
7273 let json = json!({
7274 "sessionId": "sess_1",
7275 "configId": "brave_mode",
7276 "type": "boolean",
7277 "value": true
7278 });
7279 let req: SetSessionConfigOptionRequest = serde_json::from_value(json).unwrap();
7280 assert_eq!(req.value.as_bool(), Some(true));
7281 }
7282
7283 #[test]
7284 fn test_set_session_config_option_request_roundtrip_id() {
7285 let original = SetSessionConfigOptionRequest::new("s", "c", "v");
7286 let json = serde_json::to_value(&original).unwrap();
7287 let roundtripped: SetSessionConfigOptionRequest = serde_json::from_value(json).unwrap();
7288 assert_eq!(original, roundtripped);
7289 }
7290
7291 #[test]
7292 fn test_set_session_config_option_request_roundtrip_boolean() {
7293 let original = SetSessionConfigOptionRequest::new("s", "c", false);
7294 let json = serde_json::to_value(&original).unwrap();
7295 let roundtripped: SetSessionConfigOptionRequest = serde_json::from_value(json).unwrap();
7296 assert_eq!(original, roundtripped);
7297 }
7298
7299 #[test]
7300 fn test_session_config_boolean_serialization() {
7301 let cfg = SessionConfigBoolean::new(true);
7302 let json = serde_json::to_value(&cfg).unwrap();
7303 assert_eq!(json, json!({ "currentValue": true }));
7304
7305 let deserialized: SessionConfigBoolean = serde_json::from_value(json).unwrap();
7306 assert!(deserialized.current_value);
7307 }
7308
7309 #[test]
7310 fn test_session_config_option_boolean_variant() {
7311 let opt = SessionConfigOption::boolean("brave_mode", "Brave Mode", false)
7312 .description("Skip confirmation prompts")
7313 .meta(test_meta());
7314 assert_eq!(serialized_meta_key_count(&opt), 1);
7315
7316 let json = serde_json::to_value(&opt).unwrap();
7317 assert_eq!(
7318 json,
7319 json!({
7320 "configId": "brave_mode",
7321 "name": "Brave Mode",
7322 "description": "Skip confirmation prompts",
7323 "type": "boolean",
7324 "currentValue": false,
7325 "_meta": {
7326 "source": "test"
7327 }
7328 })
7329 );
7330
7331 let deserialized: SessionConfigOption = serde_json::from_value(json).unwrap();
7332 assert_eq!(deserialized.config_id.to_string(), "brave_mode");
7333 assert_eq!(deserialized.name, "Brave Mode");
7334 match deserialized.kind {
7335 SessionConfigKind::Boolean(ref b) => assert!(!b.current_value),
7336 _ => panic!("Expected Boolean kind"),
7337 }
7338 }
7339
7340 #[test]
7341 fn test_session_config_option_select_still_works() {
7342 let opt = SessionConfigOption::select(
7344 "model",
7345 "Model",
7346 "model-1",
7347 vec![
7348 SessionConfigSelectOption::new("model-1", "Model 1"),
7349 SessionConfigSelectOption::new("model-2", "Model 2"),
7350 ],
7351 )
7352 .meta(test_meta());
7353 assert_eq!(serialized_meta_key_count(&opt), 1);
7354
7355 let json = serde_json::to_value(&opt).unwrap();
7356 assert_eq!(json["type"], "select");
7357 assert_eq!(json["currentValue"], "model-1");
7358 assert_eq!(json["options"].as_array().unwrap().len(), 2);
7359 assert_eq!(json["_meta"]["source"], "test");
7360
7361 let deserialized: SessionConfigOption = serde_json::from_value(json).unwrap();
7362 match deserialized.kind {
7363 SessionConfigKind::Select(ref s) => {
7364 assert_eq!(s.current_value.to_string(), "model-1");
7365 }
7366 _ => panic!("Expected Select kind"),
7367 }
7368 }
7369
7370 #[test]
7371 fn test_session_config_option_unknown_kind_roundtrip() {
7372 let option: SessionConfigOption = serde_json::from_value(json!({
7373 "configId": "verbosity",
7374 "name": "Verbosity",
7375 "type": "_slider",
7376 "currentValue": 3,
7377 "min": 0,
7378 "max": 5,
7379 "_meta": {
7380 "source": "test"
7381 }
7382 }))
7383 .unwrap();
7384
7385 assert_eq!(option.config_id.to_string(), "verbosity");
7386 assert_eq!(option.meta.as_ref().unwrap()["source"], "test");
7387 let SessionConfigKind::Other(unknown) = &option.kind else {
7388 panic!("expected unknown config kind");
7389 };
7390 assert_eq!(unknown.type_, "_slider");
7391 assert_eq!(unknown.fields.get("currentValue"), Some(&json!(3)));
7392 assert!(!unknown.fields.contains_key("_meta"));
7393 assert_eq!(serialized_meta_key_count(&option), 1);
7394
7395 let json = serde_json::to_value(&option).unwrap();
7396 assert_eq!(json["type"], "_slider");
7397 assert_eq!(json["currentValue"], 3);
7398 assert_eq!(json["min"], 0);
7399 assert_eq!(json["max"], 5);
7400 assert_eq!(json["_meta"]["source"], "test");
7401 }
7402
7403 #[test]
7404 fn test_session_config_option_unknown_kind_does_not_duplicate_flattened_meta() {
7405 let mut fields = std::collections::BTreeMap::new();
7406 fields.insert("currentValue".to_string(), json!(3));
7407 fields.insert("_meta".to_string(), json!({ "inner": "ignored" }));
7408
7409 let option = SessionConfigOption::new(
7410 "verbosity",
7411 "Verbosity",
7412 SessionConfigKind::Other(OtherSessionConfigKind::new("_slider", fields)),
7413 )
7414 .meta(test_meta());
7415
7416 let SessionConfigKind::Other(unknown) = &option.kind else {
7417 panic!("expected unknown config kind");
7418 };
7419 assert!(!unknown.fields.contains_key("_meta"));
7420 assert_eq!(serialized_meta_key_count(&option), 1);
7421
7422 let json = serde_json::to_value(&option).unwrap();
7423 assert_eq!(json["type"], "_slider");
7424 assert_eq!(json["currentValue"], 3);
7425 assert_eq!(json["_meta"]["source"], "test");
7426 }
7427
7428 #[test]
7429 fn test_session_config_option_unknown_does_not_hide_malformed_known_kind() {
7430 assert!(
7431 serde_json::from_value::<SessionConfigOption>(json!({
7432 "configId": "model",
7433 "name": "Model",
7434 "type": "select"
7435 }))
7436 .is_err()
7437 );
7438 }
7439
7440 #[cfg(feature = "unstable_llm_providers")]
7441 #[test]
7442 fn test_llm_protocol_known_variants() {
7443 assert_eq!(
7444 serde_json::to_value(&LlmProtocol::Anthropic).unwrap(),
7445 json!("anthropic")
7446 );
7447 assert_eq!(
7448 serde_json::to_value(&LlmProtocol::OpenAi).unwrap(),
7449 json!("openai")
7450 );
7451 assert_eq!(
7452 serde_json::to_value(&LlmProtocol::Azure).unwrap(),
7453 json!("azure")
7454 );
7455 assert_eq!(
7456 serde_json::to_value(&LlmProtocol::Vertex).unwrap(),
7457 json!("vertex")
7458 );
7459 assert_eq!(
7460 serde_json::to_value(&LlmProtocol::Bedrock).unwrap(),
7461 json!("bedrock")
7462 );
7463
7464 assert_eq!(
7465 serde_json::from_str::<LlmProtocol>("\"anthropic\"").unwrap(),
7466 LlmProtocol::Anthropic
7467 );
7468 assert_eq!(
7469 serde_json::from_str::<LlmProtocol>("\"openai\"").unwrap(),
7470 LlmProtocol::OpenAi
7471 );
7472 assert_eq!(
7473 serde_json::from_str::<LlmProtocol>("\"azure\"").unwrap(),
7474 LlmProtocol::Azure
7475 );
7476 assert_eq!(
7477 serde_json::from_str::<LlmProtocol>("\"vertex\"").unwrap(),
7478 LlmProtocol::Vertex
7479 );
7480 assert_eq!(
7481 serde_json::from_str::<LlmProtocol>("\"bedrock\"").unwrap(),
7482 LlmProtocol::Bedrock
7483 );
7484 }
7485
7486 #[cfg(feature = "unstable_llm_providers")]
7487 #[test]
7488 fn test_llm_protocol_unknown_variant() {
7489 let unknown: LlmProtocol = serde_json::from_str("\"cohere\"").unwrap();
7490 assert_eq!(unknown, LlmProtocol::Other("cohere".to_string()));
7491
7492 let json = serde_json::to_value(&unknown).unwrap();
7493 assert_eq!(json, json!("cohere"));
7494 }
7495
7496 #[cfg(feature = "unstable_llm_providers")]
7497 #[test]
7498 fn test_provider_current_config_serialization() {
7499 let config =
7500 ProviderCurrentConfig::new(LlmProtocol::Anthropic, "https://api.anthropic.com");
7501
7502 let json = serde_json::to_value(&config).unwrap();
7503 assert_eq!(
7504 json,
7505 json!({
7506 "apiType": "anthropic",
7507 "baseUrl": "https://api.anthropic.com"
7508 })
7509 );
7510
7511 let deserialized: ProviderCurrentConfig = serde_json::from_value(json).unwrap();
7512 assert_eq!(deserialized.api_type, LlmProtocol::Anthropic);
7513 assert_eq!(deserialized.base_url, "https://api.anthropic.com");
7514 }
7515
7516 #[cfg(feature = "unstable_llm_providers")]
7517 #[test]
7518 fn test_provider_info_with_current_config() {
7519 let info = ProviderInfo::new(
7520 "main",
7521 vec![LlmProtocol::Anthropic, LlmProtocol::OpenAi],
7522 true,
7523 Some(ProviderCurrentConfig::new(
7524 LlmProtocol::Anthropic,
7525 "https://api.anthropic.com",
7526 )),
7527 );
7528
7529 let json = serde_json::to_value(&info).unwrap();
7530 assert_eq!(
7531 json,
7532 json!({
7533 "providerId": "main",
7534 "supported": ["anthropic", "openai"],
7535 "required": true,
7536 "current": {
7537 "apiType": "anthropic",
7538 "baseUrl": "https://api.anthropic.com"
7539 }
7540 })
7541 );
7542
7543 let deserialized: ProviderInfo = serde_json::from_value(json).unwrap();
7544 assert_eq!(deserialized.provider_id.to_string(), "main");
7545 assert_eq!(deserialized.supported.len(), 2);
7546 assert!(deserialized.required);
7547 assert!(deserialized.current.is_some());
7548 assert_eq!(
7549 deserialized.current.as_ref().unwrap().api_type,
7550 LlmProtocol::Anthropic
7551 );
7552 }
7553
7554 #[cfg(feature = "unstable_llm_providers")]
7555 #[test]
7556 fn test_provider_info_disabled() {
7557 let info = ProviderInfo::new(
7558 "secondary",
7559 vec![LlmProtocol::OpenAi],
7560 false,
7561 None::<ProviderCurrentConfig>,
7562 );
7563
7564 let json = serde_json::to_value(&info).unwrap();
7565 assert_eq!(
7566 json,
7567 json!({
7568 "providerId": "secondary",
7569 "supported": ["openai"],
7570 "required": false
7571 })
7572 );
7573
7574 let deserialized: ProviderInfo = serde_json::from_value(json).unwrap();
7575 assert_eq!(deserialized.provider_id.to_string(), "secondary");
7576 assert!(!deserialized.required);
7577 assert!(deserialized.current.is_none());
7578 }
7579
7580 #[cfg(feature = "unstable_llm_providers")]
7581 #[test]
7582 fn test_provider_info_missing_current_defaults_to_none() {
7583 let json = json!({
7585 "providerId": "main",
7586 "supported": ["anthropic"],
7587 "required": true
7588 });
7589 let deserialized: ProviderInfo = serde_json::from_value(json).unwrap();
7590 assert!(deserialized.current.is_none());
7591 }
7592
7593 #[cfg(feature = "unstable_llm_providers")]
7594 #[test]
7595 fn test_provider_info_explicit_null_current_decodes_to_none() {
7596 let json = json!({
7600 "providerId": "main",
7601 "supported": ["anthropic"],
7602 "required": true,
7603 "current": null
7604 });
7605 let deserialized: ProviderInfo = serde_json::from_value(json).unwrap();
7606 assert!(deserialized.current.is_none());
7607 }
7608
7609 #[cfg(feature = "unstable_llm_providers")]
7610 #[test]
7611 fn test_list_providers_response_serialization() {
7612 let response = ListProvidersResponse::new(vec![ProviderInfo::new(
7613 "main",
7614 vec![LlmProtocol::Anthropic],
7615 true,
7616 Some(ProviderCurrentConfig::new(
7617 LlmProtocol::Anthropic,
7618 "https://api.anthropic.com",
7619 )),
7620 )]);
7621
7622 let json = serde_json::to_value(&response).unwrap();
7623 assert_eq!(json["providers"].as_array().unwrap().len(), 1);
7624 assert_eq!(json["providers"][0]["providerId"], "main");
7625
7626 let deserialized: ListProvidersResponse = serde_json::from_value(json).unwrap();
7627 assert_eq!(deserialized.providers.len(), 1);
7628 }
7629
7630 #[cfg(feature = "unstable_llm_providers")]
7631 #[test]
7632 fn test_set_provider_request_serialization() {
7633 use std::collections::HashMap;
7634
7635 let mut headers = HashMap::new();
7636 headers.insert("Authorization".to_string(), "Bearer sk-test".to_string());
7637
7638 let request =
7639 SetProviderRequest::new("main", LlmProtocol::OpenAi, "https://api.openai.com/v1")
7640 .headers(headers);
7641
7642 let json = serde_json::to_value(&request).unwrap();
7643 assert_eq!(
7644 json,
7645 json!({
7646 "providerId": "main",
7647 "apiType": "openai",
7648 "baseUrl": "https://api.openai.com/v1",
7649 "headers": {
7650 "Authorization": "Bearer sk-test"
7651 }
7652 })
7653 );
7654
7655 let deserialized: SetProviderRequest = serde_json::from_value(json).unwrap();
7656 assert_eq!(deserialized.provider_id.to_string(), "main");
7657 assert_eq!(deserialized.api_type, LlmProtocol::OpenAi);
7658 assert_eq!(deserialized.base_url, "https://api.openai.com/v1");
7659 assert_eq!(deserialized.headers.len(), 1);
7660 assert_eq!(
7661 deserialized.headers.get("Authorization").unwrap(),
7662 "Bearer sk-test"
7663 );
7664 }
7665
7666 #[cfg(feature = "unstable_llm_providers")]
7667 #[test]
7668 fn test_set_provider_request_omits_empty_headers() {
7669 let request =
7670 SetProviderRequest::new("main", LlmProtocol::Anthropic, "https://api.anthropic.com");
7671
7672 let json = serde_json::to_value(&request).unwrap();
7673 assert!(!json.as_object().unwrap().contains_key("headers"));
7675 }
7676
7677 #[cfg(feature = "unstable_llm_providers")]
7678 #[test]
7679 fn test_disable_provider_request_serialization() {
7680 let request = DisableProviderRequest::new("secondary");
7681
7682 let json = serde_json::to_value(&request).unwrap();
7683 assert_eq!(json, json!({ "providerId": "secondary" }));
7684
7685 let deserialized: DisableProviderRequest = serde_json::from_value(json).unwrap();
7686 assert_eq!(deserialized.provider_id.to_string(), "secondary");
7687 }
7688
7689 #[cfg(feature = "unstable_llm_providers")]
7690 #[test]
7691 fn test_providers_capabilities_serialization() {
7692 let caps = ProvidersCapabilities::new();
7693
7694 let json = serde_json::to_value(&caps).unwrap();
7695 assert_eq!(json, json!({}));
7696
7697 let deserialized: ProvidersCapabilities = serde_json::from_value(json).unwrap();
7698 assert!(deserialized.meta.is_none());
7699 }
7700
7701 #[cfg(feature = "unstable_llm_providers")]
7702 #[test]
7703 fn test_agent_capabilities_with_providers() {
7704 let caps = AgentCapabilities::new().providers(ProvidersCapabilities::new());
7705
7706 let json = serde_json::to_value(&caps).unwrap();
7707 assert_eq!(json["providers"], json!({}));
7708
7709 let deserialized: AgentCapabilities = serde_json::from_value(json).unwrap();
7710 assert!(deserialized.providers.is_some());
7711 }
7712
7713 #[test]
7714 fn test_agent_capabilities_session_is_explicit() {
7715 let json = serde_json::to_value(AgentCapabilities::new()).unwrap();
7716 assert!(json.get("session").is_none());
7717
7718 let caps = AgentCapabilities::new().session(
7719 SessionCapabilities::new()
7720 .prompt(PromptCapabilities::new().image(PromptImageCapabilities::new()))
7721 .mcp(McpCapabilities::new().stdio(McpStdioCapabilities::new())),
7722 );
7723
7724 assert_eq!(
7725 serde_json::to_value(&caps).unwrap(),
7726 json!({
7727 "session": {
7728 "prompt": {
7729 "image": {}
7730 },
7731 "mcp": {
7732 "stdio": {}
7733 }
7734 }
7735 })
7736 );
7737
7738 let deserialized: AgentCapabilities = serde_json::from_value(json!({
7739 "session": false
7740 }))
7741 .unwrap();
7742 assert!(deserialized.session.is_none());
7743 }
7744
7745 #[test]
7746 fn test_prompt_capabilities_serialize_supported_content_as_objects() {
7747 let caps = PromptCapabilities::new()
7748 .image(PromptImageCapabilities::new())
7749 .audio(PromptAudioCapabilities::new())
7750 .embedded_context(PromptEmbeddedContextCapabilities::new());
7751
7752 assert_eq!(
7753 serde_json::to_value(&caps).unwrap(),
7754 json!({
7755 "image": {},
7756 "audio": {},
7757 "embeddedContext": {}
7758 })
7759 );
7760
7761 let deserialized: PromptCapabilities = serde_json::from_value(json!({
7762 "image": null,
7763 "audio": false,
7764 "embeddedContext": {}
7765 }))
7766 .unwrap();
7767 assert!(deserialized.image.is_none());
7768 assert!(deserialized.audio.is_none());
7769 assert!(deserialized.embedded_context.is_some());
7770 }
7771
7772 #[test]
7773 fn test_mcp_capabilities_serialize_supported_transports_as_objects() {
7774 let caps = McpCapabilities::new()
7775 .stdio(McpStdioCapabilities::new())
7776 .http(McpHttpCapabilities::new());
7777
7778 assert_eq!(
7779 serde_json::to_value(&caps).unwrap(),
7780 json!({
7781 "stdio": {},
7782 "http": {}
7783 })
7784 );
7785
7786 let deserialized: McpCapabilities = serde_json::from_value(json!({
7787 "stdio": null,
7788 "http": false
7789 }))
7790 .unwrap();
7791 assert!(deserialized.stdio.is_none());
7792 assert!(deserialized.http.is_none());
7793 }
7794
7795 #[cfg(feature = "unstable_mcp_over_acp")]
7796 #[test]
7797 fn test_mcp_capabilities_serialize_acp_support_as_object() {
7798 let caps = McpCapabilities::new().acp(McpAcpCapabilities::new());
7799
7800 assert_eq!(
7801 serde_json::to_value(&caps).unwrap(),
7802 json!({
7803 "acp": {}
7804 })
7805 );
7806 }
7807
7808 #[test]
7809 fn prompt_request_rejects_malformed_content_block() {
7810 use serde_json::json;
7811
7812 assert!(
7813 serde_json::from_value::<PromptRequest>(json!({
7814 "sessionId": "sess-1",
7815 "prompt": [{"type": "text"}]
7816 }))
7817 .is_err()
7818 );
7819 }
7820
7821 #[test]
7822 fn prompt_request_rejects_non_array_prompt() {
7823 use serde_json::json;
7824
7825 assert!(
7826 serde_json::from_value::<PromptRequest>(json!({
7827 "sessionId": "sess-1",
7828 "prompt": "hello"
7829 }))
7830 .is_err()
7831 );
7832 }
7833
7834 fn assert_session_setup_lists_are_strict<T>(base: &serde_json::Value)
7835 where
7836 T: serde::de::DeserializeOwned + PartialEq + std::fmt::Debug,
7837 {
7838 let name = std::any::type_name::<T>();
7839 let with = |field: &str, value: serde_json::Value| {
7840 let mut request = base.clone();
7841 request[field] = value;
7842 serde_json::from_value::<T>(request)
7843 };
7844
7845 for (field, value) in [
7846 (
7848 "mcpServers",
7849 json!([
7850 {"type": "stdio", "name": "github", "command": "/usr/bin/gh-mcp", "env": [{"name": "GITHUB_TOKEN", "value": null}]},
7851 {"type": "stdio", "name": "fs", "command": "/usr/bin/fs-mcp"}
7852 ]),
7853 ),
7854 ("mcpServers", json!({"type": "stdio", "name": "fs"})),
7855 ("additionalDirectories", json!(["/repo/lib", 42])),
7856 ("additionalDirectories", json!("/repo/lib")),
7857 ] {
7858 assert!(
7859 with(field, value.clone()).is_err(),
7860 "{name}.{field}: {value}"
7861 );
7862 }
7863
7864 let empty = serde_json::from_value::<T>(base.clone()).unwrap();
7865 for field in ["mcpServers", "additionalDirectories"] {
7866 assert_eq!(
7867 with(field, serde_json::Value::Null).unwrap(),
7868 empty,
7869 "{name}.{field}"
7870 );
7871 }
7872 }
7873
7874 #[test]
7875 fn session_setup_requests_reject_malformed_mcp_servers_and_directories() {
7876 assert_session_setup_lists_are_strict::<NewSessionRequest>(&json!({"cwd": "/repo"}));
7877 assert_session_setup_lists_are_strict::<ResumeSessionRequest>(&json!({
7878 "sessionId": "sess-1",
7879 "cwd": "/repo"
7880 }));
7881 #[cfg(feature = "unstable_session_fork")]
7882 assert_session_setup_lists_are_strict::<ForkSessionRequest>(&json!({
7883 "sessionId": "sess-1",
7884 "cwd": "/repo"
7885 }));
7886 }
7887
7888 #[test]
7889 fn resume_session_request_rejects_malformed_replay_from() {
7890 let request = |replay_from: serde_json::Value| {
7891 serde_json::from_value::<ResumeSessionRequest>(json!({
7892 "sessionId": "sess-1",
7893 "cwd": "/repo",
7894 "replayFrom": replay_from
7895 }))
7896 };
7897
7898 assert!(request(json!("start")).is_err());
7900 assert_eq!(request(serde_json::Value::Null).unwrap().replay_from, None);
7901 assert!(matches!(
7902 request(json!({"type": "start"})).unwrap().replay_from,
7903 Some(ReplayFrom::Start(_))
7904 ));
7905 }
7906
7907 #[test]
7908 fn mcp_server_lists_treat_null_as_empty() {
7909 let servers: Vec<McpServer> = serde_json::from_value(json!([
7910 {"type": "stdio", "name": "fs", "command": "/usr/bin/fs-mcp", "args": null, "env": null},
7911 {"type": "http", "name": "api", "url": "https://example.com/mcp", "headers": null}
7912 ]))
7913 .unwrap();
7914
7915 assert_eq!(
7916 servers,
7917 vec![
7918 McpServer::Stdio(McpServerStdio::new("fs", "/usr/bin/fs-mcp")),
7919 McpServer::Http(McpServerHttp::new("api", "https://example.com/mcp")),
7920 ]
7921 );
7922 }
7923
7924 #[test]
7925 fn terminal_auth_methods_reject_malformed_launch_config() {
7926 let response: InitializeResponse = serde_json::from_value(json!({
7930 "protocolVersion": 2,
7931 "info": {"name": "agent", "version": "1.0.0"},
7932 "authMethods": [
7933 {"type": "terminal", "methodId": "bad-args", "name": "Log in", "args": ["auth", "--port", 8123]},
7934 {"type": "terminal", "methodId": "bad-env", "name": "Log in", "env": [{"name": "DEBUG", "value": 1}]},
7935 {"type": "terminal", "methodId": "null-config", "name": "Log in", "args": null, "env": null},
7936 {"type": "agent", "methodId": "agent", "name": "Agent login"}
7937 ]
7938 }))
7939 .unwrap();
7940
7941 assert_eq!(
7942 response.auth_methods,
7943 vec![
7944 AuthMethod::Terminal(AuthMethodTerminal::new("null-config", "Log in")),
7945 AuthMethod::Agent(AuthMethodAgent::new("agent", "Agent login")),
7946 ]
7947 );
7948 }
7949}