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,
113 pub info: Implementation,
115 #[serde_as(deserialize_as = "DefaultOnError")]
117 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
118 #[serde(default)]
119 pub capabilities: AgentCapabilities,
120 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
126 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
127 #[serde(default, skip_serializing_if = "Vec::is_empty")]
128 pub auth_methods: Vec<AuthMethod>,
129 #[serde_as(deserialize_as = "DefaultOnError")]
135 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
136 #[serde(default)]
137 #[serde(rename = "_meta")]
138 pub meta: Option<Meta>,
139}
140
141impl InitializeResponse {
142 #[must_use]
144 pub fn new(protocol_version: ProtocolVersion, info: Implementation) -> Self {
145 Self {
146 protocol_version,
147 capabilities: AgentCapabilities::default(),
148 auth_methods: vec![],
149 info,
150 meta: None,
151 }
152 }
153
154 #[must_use]
156 pub fn capabilities(mut self, capabilities: AgentCapabilities) -> Self {
157 self.capabilities = capabilities;
158 self
159 }
160
161 #[must_use]
166 pub fn auth_methods(mut self, auth_methods: Vec<AuthMethod>) -> Self {
167 self.auth_methods = auth_methods;
168 self
169 }
170
171 #[must_use]
177 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
178 self.meta = meta.into_option();
179 self
180 }
181}
182
183#[serde_as]
187#[skip_serializing_none]
188#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
189#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
190#[serde(rename_all = "camelCase")]
191#[non_exhaustive]
192pub struct Implementation {
193 pub name: String,
196 #[serde_as(deserialize_as = "DefaultOnError")]
203 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
204 #[serde(default)]
205 pub title: Option<String>,
206 pub version: String,
209 #[serde_as(deserialize_as = "DefaultOnError")]
215 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
216 #[serde(default)]
217 #[serde(rename = "_meta")]
218 pub meta: Option<Meta>,
219}
220
221impl Implementation {
222 #[must_use]
224 pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
225 Self {
226 name: name.into(),
227 title: None,
228 version: version.into(),
229 meta: None,
230 }
231 }
232
233 #[must_use]
238 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
239 self.title = title.into_option();
240 self
241 }
242
243 #[must_use]
249 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
250 self.meta = meta.into_option();
251 self
252 }
253}
254
255#[serde_as]
265#[skip_serializing_none]
266#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
267#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
268#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = AUTH_LOGIN_METHOD_NAME)))]
269#[serde(rename_all = "camelCase")]
270#[non_exhaustive]
271pub struct LoginAuthRequest {
272 pub method_id: AuthMethodId,
275 #[serde_as(deserialize_as = "DefaultOnError")]
281 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
282 #[serde(default)]
283 #[serde(rename = "_meta")]
284 pub meta: Option<Meta>,
285}
286
287impl LoginAuthRequest {
288 #[must_use]
290 pub fn new(method_id: impl Into<AuthMethodId>) -> Self {
291 Self {
292 method_id: method_id.into(),
293 meta: None,
294 }
295 }
296
297 #[must_use]
303 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
304 self.meta = meta.into_option();
305 self
306 }
307}
308
309crate::serde_util::default_on_null! {
310 #[serde_as]
312 #[skip_serializing_none]
313 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
314 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
315 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = AUTH_LOGIN_METHOD_NAME)))]
316 #[serde(rename_all = "camelCase")]
317 #[non_exhaustive]
318 pub struct LoginAuthResponse {
319 #[serde_as(deserialize_as = "DefaultOnError")]
325 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
326 #[serde(default)]
327 #[serde(rename = "_meta")]
328 pub meta: Option<Meta>,
329 }
330}
331
332impl LoginAuthResponse {
333 #[must_use]
335 pub fn new() -> Self {
336 Self::default()
337 }
338
339 #[must_use]
345 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
346 self.meta = meta.into_option();
347 self
348 }
349}
350
351crate::serde_util::default_on_null! {
354 #[serde_as]
362 #[skip_serializing_none]
363 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
364 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
365 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = AUTH_LOGOUT_METHOD_NAME)))]
366 #[serde(rename_all = "camelCase")]
367 #[non_exhaustive]
368 pub struct LogoutAuthRequest {
369 #[serde_as(deserialize_as = "DefaultOnError")]
375 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
376 #[serde(default)]
377 #[serde(rename = "_meta")]
378 pub meta: Option<Meta>,
379 }
380}
381
382impl LogoutAuthRequest {
383 #[must_use]
385 pub fn new() -> Self {
386 Self::default()
387 }
388
389 #[must_use]
395 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
396 self.meta = meta.into_option();
397 self
398 }
399}
400
401crate::serde_util::default_on_null! {
402 #[serde_as]
404 #[skip_serializing_none]
405 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
406 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
407 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = AUTH_LOGOUT_METHOD_NAME)))]
408 #[serde(rename_all = "camelCase")]
409 #[non_exhaustive]
410 pub struct LogoutAuthResponse {
411 #[serde_as(deserialize_as = "DefaultOnError")]
417 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
418 #[serde(default)]
419 #[serde(rename = "_meta")]
420 pub meta: Option<Meta>,
421 }
422}
423
424impl LogoutAuthResponse {
425 #[must_use]
427 pub fn new() -> Self {
428 Self::default()
429 }
430
431 #[must_use]
437 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
438 self.meta = meta.into_option();
439 self
440 }
441}
442
443#[serde_as]
449#[skip_serializing_none]
450#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
451#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
452#[serde(rename_all = "camelCase")]
453#[non_exhaustive]
454pub struct AgentAuthCapabilities {
455 #[serde_as(deserialize_as = "DefaultOnError")]
461 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
462 #[serde(default)]
463 #[serde(rename = "_meta")]
464 pub meta: Option<Meta>,
465}
466
467impl AgentAuthCapabilities {
468 #[must_use]
470 pub fn new() -> Self {
471 Self::default()
472 }
473
474 #[must_use]
480 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
481 self.meta = meta.into_option();
482 self
483 }
484}
485
486#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
488#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
489#[serde(transparent)]
490#[from(forward)]
491#[non_exhaustive]
492pub struct AuthMethodId(pub Arc<str>);
493
494impl AuthMethodId {
495 #[must_use]
497 pub fn new(id: impl Into<Self>) -> Self {
498 id.into()
499 }
500}
501
502#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
506#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
507#[serde(tag = "type", rename_all = "snake_case")]
508#[non_exhaustive]
509pub enum AuthMethod {
510 Terminal(AuthMethodTerminal),
513 Agent(AuthMethodAgent),
517 #[serde(untagged)]
527 Other(OtherAuthMethod),
528}
529
530impl AuthMethod {
531 #[must_use]
533 pub fn method_id(&self) -> &AuthMethodId {
534 match self {
535 Self::Agent(a) => &a.method_id,
536 Self::Other(a) => &a.method_id,
537 Self::Terminal(t) => &t.method_id,
538 }
539 }
540
541 #[must_use]
543 pub fn name(&self) -> &str {
544 match self {
545 Self::Agent(a) => &a.name,
546 Self::Other(a) => &a.name,
547 Self::Terminal(t) => &t.name,
548 }
549 }
550
551 #[must_use]
553 pub fn description(&self) -> Option<&str> {
554 match self {
555 Self::Agent(a) => a.description.as_deref(),
556 Self::Other(a) => a
557 .fields
558 .get("description")
559 .and_then(serde_json::Value::as_str),
560 Self::Terminal(t) => t.description.as_deref(),
561 }
562 }
563
564 #[must_use]
570 pub fn meta(&self) -> Option<&Meta> {
571 match self {
572 Self::Agent(a) => a.meta.as_ref(),
573 Self::Other(a) => a.fields.get("_meta").and_then(serde_json::Value::as_object),
574 Self::Terminal(t) => t.meta.as_ref(),
575 }
576 }
577}
578
579#[serde_as]
581#[skip_serializing_none]
582#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
583#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
584#[cfg_attr(feature = "schemars", schemars(inline))]
585#[cfg_attr(feature = "schemars", schemars(transform = other_auth_method_schema))]
586#[serde(rename_all = "camelCase")]
587#[non_exhaustive]
588pub struct OtherAuthMethod {
589 #[serde(rename = "type")]
595 pub type_: String,
596 pub method_id: AuthMethodId,
598 pub name: String,
600 #[serde(flatten)]
603 pub fields: BTreeMap<String, serde_json::Value>,
604}
605
606impl OtherAuthMethod {
607 #[must_use]
609 pub fn new(
610 type_: impl Into<String>,
611 method_id: impl Into<AuthMethodId>,
612 name: impl Into<String>,
613 mut fields: BTreeMap<String, serde_json::Value>,
614 ) -> Self {
615 fields.remove("type");
616 fields.remove("methodId");
617 fields.remove("name");
618 Self {
619 type_: type_.into(),
620 method_id: method_id.into(),
621 name: name.into(),
622 fields,
623 }
624 }
625}
626
627impl<'de> Deserialize<'de> for OtherAuthMethod {
628 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
629 where
630 D: serde::Deserializer<'de>,
631 {
632 #[derive(Deserialize)]
633 #[serde(rename_all = "camelCase")]
634 struct RawOtherAuthMethod {
635 #[serde(rename = "type")]
636 type_: String,
637 method_id: AuthMethodId,
638 name: String,
639 #[serde(flatten)]
640 fields: BTreeMap<String, serde_json::Value>,
641 }
642
643 let raw = RawOtherAuthMethod::deserialize(deserializer)?;
644 if is_known_auth_method_type(&raw.type_) {
645 return Err(serde::de::Error::custom(format!(
646 "known authentication method `{}` did not match its schema",
647 raw.type_
648 )));
649 }
650
651 Ok(Self {
652 type_: raw.type_,
653 method_id: raw.method_id,
654 name: raw.name,
655 fields: raw.fields,
656 })
657 }
658}
659
660fn is_known_auth_method_type(type_: &str) -> bool {
661 matches!(type_, "agent" | "terminal")
662}
663
664#[cfg(feature = "schemars")]
665fn other_auth_method_schema(schema: &mut Schema) {
666 super::schema_util::reject_known_string_discriminators(schema, "type", &["agent", "terminal"]);
667}
668
669#[serde_as]
673#[skip_serializing_none]
674#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
675#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
676#[serde(rename_all = "camelCase")]
677#[non_exhaustive]
678pub struct AuthMethodAgent {
679 pub method_id: AuthMethodId,
681 pub name: String,
683 #[serde_as(deserialize_as = "DefaultOnError")]
687 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
688 #[serde(default)]
689 pub description: Option<String>,
690 #[serde_as(deserialize_as = "DefaultOnError")]
696 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
697 #[serde(default)]
698 #[serde(rename = "_meta")]
699 pub meta: Option<Meta>,
700}
701
702impl AuthMethodAgent {
703 #[must_use]
705 pub fn new(method_id: impl Into<AuthMethodId>, name: impl Into<String>) -> Self {
706 Self {
707 method_id: method_id.into(),
708 name: name.into(),
709 description: None,
710 meta: None,
711 }
712 }
713
714 #[must_use]
716 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
717 self.description = description.into_option();
718 self
719 }
720
721 #[must_use]
727 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
728 self.meta = meta.into_option();
729 self
730 }
731}
732
733#[serde_as]
741#[skip_serializing_none]
742#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
743#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
744#[serde(rename_all = "camelCase")]
745#[non_exhaustive]
746pub struct AuthMethodTerminal {
747 pub method_id: AuthMethodId,
749 pub name: String,
751 #[serde_as(deserialize_as = "DefaultOnError")]
755 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
756 #[serde(default)]
757 pub description: Option<String>,
758 #[serde_as(deserialize_as = "DefaultOnNull")]
760 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-null" = true)))]
761 #[serde(default, skip_serializing_if = "Vec::is_empty")]
762 pub args: Vec<String>,
763 #[serde_as(deserialize_as = "DefaultOnNull")]
767 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-null" = true)))]
768 #[serde(default, skip_serializing_if = "Vec::is_empty")]
769 pub env: Vec<EnvVariable>,
770 #[serde_as(deserialize_as = "DefaultOnError")]
776 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
777 #[serde(default)]
778 #[serde(rename = "_meta")]
779 pub meta: Option<Meta>,
780}
781
782impl AuthMethodTerminal {
783 #[must_use]
785 pub fn new(method_id: impl Into<AuthMethodId>, name: impl Into<String>) -> Self {
786 Self {
787 method_id: method_id.into(),
788 name: name.into(),
789 description: None,
790 args: Vec::new(),
791 env: Vec::new(),
792 meta: None,
793 }
794 }
795
796 #[must_use]
798 pub fn args(mut self, args: Vec<String>) -> Self {
799 self.args = args;
800 self
801 }
802
803 #[must_use]
807 pub fn env(mut self, env: Vec<EnvVariable>) -> Self {
808 self.env = env;
809 self
810 }
811
812 #[must_use]
814 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
815 self.description = description.into_option();
816 self
817 }
818
819 #[must_use]
825 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
826 self.meta = meta.into_option();
827 self
828 }
829}
830
831#[serde_as]
837#[skip_serializing_none]
838#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
839#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
840#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = SESSION_NEW_METHOD_NAME)))]
841#[serde(rename_all = "camelCase")]
842#[non_exhaustive]
843pub struct NewSessionRequest {
844 pub cwd: AbsolutePath,
846 #[serde_as(deserialize_as = "DefaultOnNull")]
852 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-null" = true)))]
853 #[serde(default, skip_serializing_if = "Vec::is_empty")]
854 pub additional_directories: Vec<AbsolutePath>,
855 #[serde_as(deserialize_as = "DefaultOnNull")]
857 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-null" = true)))]
858 #[serde(default, skip_serializing_if = "Vec::is_empty")]
859 pub mcp_servers: Vec<McpServer>,
860 #[cfg(feature = "unstable_session_system_prompt")]
870 #[serde(default)]
871 pub system_prompt: Option<SystemPrompt>,
872 #[serde_as(deserialize_as = "DefaultOnError")]
878 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
879 #[serde(default)]
880 #[serde(rename = "_meta")]
881 pub meta: Option<Meta>,
882}
883
884impl NewSessionRequest {
885 #[must_use]
887 pub fn new(cwd: impl Into<AbsolutePath>) -> Self {
888 Self {
889 cwd: cwd.into(),
890 additional_directories: vec![],
891 mcp_servers: vec![],
892 #[cfg(feature = "unstable_session_system_prompt")]
893 system_prompt: None,
894 meta: None,
895 }
896 }
897
898 #[must_use]
900 pub fn additional_directories<I, P>(mut self, additional_directories: I) -> Self
901 where
902 I: IntoIterator<Item = P>,
903 P: Into<AbsolutePath>,
904 {
905 self.additional_directories = additional_directories.into_iter().map(Into::into).collect();
906 self
907 }
908
909 #[must_use]
911 pub fn mcp_servers(mut self, mcp_servers: Vec<McpServer>) -> Self {
912 self.mcp_servers = mcp_servers;
913 self
914 }
915
916 #[cfg(feature = "unstable_session_system_prompt")]
922 #[must_use]
923 pub fn system_prompt(mut self, system_prompt: impl IntoOption<SystemPrompt>) -> Self {
924 self.system_prompt = system_prompt.into_option();
925 self
926 }
927
928 #[must_use]
934 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
935 self.meta = meta.into_option();
936 self
937 }
938}
939
940#[serde_as]
948#[skip_serializing_none]
949#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
950#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
951#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = SESSION_NEW_METHOD_NAME)))]
952#[serde(rename_all = "camelCase")]
953#[non_exhaustive]
954pub struct NewSessionResponse {
955 pub session_id: SessionId,
959 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
965 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
966 #[serde(default, skip_serializing_if = "Vec::is_empty")]
967 pub config_options: Vec<SessionConfigOption>,
968 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
974 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
975 #[serde(default, skip_serializing_if = "Vec::is_empty")]
976 pub available_commands: Vec<AvailableCommand>,
977 #[serde_as(deserialize_as = "DefaultOnError")]
983 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
984 #[serde(default)]
985 #[serde(rename = "_meta")]
986 pub meta: Option<Meta>,
987}
988
989impl NewSessionResponse {
990 #[must_use]
992 pub fn new(session_id: impl Into<SessionId>) -> Self {
993 Self {
994 session_id: session_id.into(),
995 config_options: Vec::new(),
996 available_commands: Vec::new(),
997 meta: None,
998 }
999 }
1000
1001 #[must_use]
1007 pub fn config_options(mut self, config_options: Vec<SessionConfigOption>) -> Self {
1008 self.config_options = config_options;
1009 self
1010 }
1011
1012 #[must_use]
1014 pub fn available_commands(mut self, available_commands: Vec<AvailableCommand>) -> Self {
1015 self.available_commands = available_commands;
1016 self
1017 }
1018
1019 #[must_use]
1025 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1026 self.meta = meta.into_option();
1027 self
1028 }
1029}
1030
1031#[cfg(feature = "unstable_session_fork")]
1044#[serde_as]
1045#[skip_serializing_none]
1046#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1047#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1048#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = SESSION_FORK_METHOD_NAME)))]
1049#[serde(rename_all = "camelCase")]
1050#[non_exhaustive]
1051pub struct ForkSessionRequest {
1052 pub session_id: SessionId,
1054 pub cwd: AbsolutePath,
1056 #[serde_as(deserialize_as = "DefaultOnNull")]
1062 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-null" = true)))]
1063 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1064 pub additional_directories: Vec<AbsolutePath>,
1065 #[serde_as(deserialize_as = "DefaultOnNull")]
1067 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-null" = true)))]
1068 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1069 pub mcp_servers: Vec<McpServer>,
1070 #[cfg(feature = "unstable_session_system_prompt")]
1082 #[serde(default)]
1083 pub system_prompt: Option<SystemPrompt>,
1084 #[serde_as(deserialize_as = "DefaultOnError")]
1090 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1091 #[serde(default)]
1092 #[serde(rename = "_meta")]
1093 pub meta: Option<Meta>,
1094}
1095
1096#[cfg(feature = "unstable_session_fork")]
1097impl ForkSessionRequest {
1098 #[must_use]
1100 pub fn new(session_id: impl Into<SessionId>, cwd: impl Into<AbsolutePath>) -> Self {
1101 Self {
1102 session_id: session_id.into(),
1103 cwd: cwd.into(),
1104 additional_directories: vec![],
1105 mcp_servers: vec![],
1106 #[cfg(feature = "unstable_session_system_prompt")]
1107 system_prompt: None,
1108 meta: None,
1109 }
1110 }
1111
1112 #[must_use]
1114 pub fn additional_directories<I, P>(mut self, additional_directories: I) -> Self
1115 where
1116 I: IntoIterator<Item = P>,
1117 P: Into<AbsolutePath>,
1118 {
1119 self.additional_directories = additional_directories.into_iter().map(Into::into).collect();
1120 self
1121 }
1122
1123 #[must_use]
1125 pub fn mcp_servers(mut self, mcp_servers: Vec<McpServer>) -> Self {
1126 self.mcp_servers = mcp_servers;
1127 self
1128 }
1129
1130 #[cfg(feature = "unstable_session_system_prompt")]
1136 #[must_use]
1137 pub fn system_prompt(mut self, system_prompt: impl IntoOption<SystemPrompt>) -> Self {
1138 self.system_prompt = system_prompt.into_option();
1139 self
1140 }
1141
1142 #[must_use]
1148 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1149 self.meta = meta.into_option();
1150 self
1151 }
1152}
1153
1154#[cfg(feature = "unstable_session_fork")]
1160#[serde_as]
1161#[skip_serializing_none]
1162#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1163#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1164#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = SESSION_FORK_METHOD_NAME)))]
1165#[serde(rename_all = "camelCase")]
1166#[non_exhaustive]
1167pub struct ForkSessionResponse {
1168 pub session_id: SessionId,
1170 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
1176 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
1177 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1178 pub config_options: Vec<SessionConfigOption>,
1179 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
1185 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
1186 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1187 pub available_commands: Vec<AvailableCommand>,
1188 #[serde_as(deserialize_as = "DefaultOnError")]
1194 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1195 #[serde(default)]
1196 #[serde(rename = "_meta")]
1197 pub meta: Option<Meta>,
1198}
1199
1200#[cfg(feature = "unstable_session_fork")]
1201impl ForkSessionResponse {
1202 #[must_use]
1204 pub fn new(session_id: impl Into<SessionId>) -> Self {
1205 Self {
1206 session_id: session_id.into(),
1207 config_options: Vec::new(),
1208 available_commands: Vec::new(),
1209 meta: None,
1210 }
1211 }
1212
1213 #[must_use]
1219 pub fn config_options(mut self, config_options: Vec<SessionConfigOption>) -> Self {
1220 self.config_options = config_options;
1221 self
1222 }
1223
1224 #[must_use]
1226 pub fn available_commands(mut self, available_commands: Vec<AvailableCommand>) -> Self {
1227 self.available_commands = available_commands;
1228 self
1229 }
1230
1231 #[must_use]
1237 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1238 self.meta = meta.into_option();
1239 self
1240 }
1241}
1242
1243#[serde_as]
1250#[skip_serializing_none]
1251#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1252#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1253#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = SESSION_RESUME_METHOD_NAME)))]
1254#[serde(rename_all = "camelCase")]
1255#[non_exhaustive]
1256pub struct ResumeSessionRequest {
1257 pub session_id: SessionId,
1259 pub cwd: AbsolutePath,
1264 #[serde_as(deserialize_as = "DefaultOnNull")]
1271 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-null" = true)))]
1272 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1273 pub additional_directories: Vec<AbsolutePath>,
1274 #[serde_as(deserialize_as = "DefaultOnNull")]
1279 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-null" = true)))]
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(flatten)]
1481 pub fields: BTreeMap<String, serde_json::Value>,
1482}
1483
1484impl OtherReplayFrom {
1485 #[must_use]
1487 pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
1488 fields.remove("type");
1489 Self {
1490 type_: type_.into(),
1491 fields,
1492 }
1493 }
1494}
1495
1496impl<'de> Deserialize<'de> for OtherReplayFrom {
1497 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1498 where
1499 D: serde::Deserializer<'de>,
1500 {
1501 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1502 let type_ = fields
1503 .remove("type")
1504 .ok_or_else(|| serde::de::Error::missing_field("type"))?;
1505 let serde_json::Value::String(type_) = type_ else {
1506 return Err(serde::de::Error::custom("`type` must be a string"));
1507 };
1508
1509 if is_known_replay_from_type(&type_) {
1510 return Err(serde::de::Error::custom(format!(
1511 "known replay cursor `{type_}` did not match its schema"
1512 )));
1513 }
1514
1515 Ok(Self { type_, fields })
1516 }
1517}
1518
1519fn is_known_replay_from_type(type_: &str) -> bool {
1520 matches!(type_, "start")
1521}
1522
1523#[cfg(feature = "schemars")]
1524fn other_replay_from_schema(schema: &mut Schema) {
1525 super::schema_util::reject_known_string_discriminators(schema, "type", &["start"]);
1526}
1527
1528crate::serde_util::default_on_null! {
1529 #[serde_as]
1537 #[skip_serializing_none]
1538 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1539 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
1540 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = SESSION_RESUME_METHOD_NAME)))]
1541 #[serde(rename_all = "camelCase")]
1542 #[non_exhaustive]
1543 pub struct ResumeSessionResponse {
1544 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
1550 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
1551 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1552 pub config_options: Vec<SessionConfigOption>,
1553 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
1559 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
1560 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1561 pub available_commands: Vec<AvailableCommand>,
1562 #[serde_as(deserialize_as = "DefaultOnError")]
1568 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1569 #[serde(default)]
1570 #[serde(rename = "_meta")]
1571 pub meta: Option<Meta>,
1572 }
1573}
1574
1575impl ResumeSessionResponse {
1576 #[must_use]
1578 pub fn new() -> Self {
1579 Self::default()
1580 }
1581
1582 #[must_use]
1588 pub fn config_options(mut self, config_options: Vec<SessionConfigOption>) -> Self {
1589 self.config_options = config_options;
1590 self
1591 }
1592
1593 #[must_use]
1595 pub fn available_commands(mut self, available_commands: Vec<AvailableCommand>) -> Self {
1596 self.available_commands = available_commands;
1597 self
1598 }
1599
1600 #[must_use]
1606 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1607 self.meta = meta.into_option();
1608 self
1609 }
1610}
1611
1612#[serde_as]
1635#[skip_serializing_none]
1636#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1637#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1638#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = SESSION_CLOSE_METHOD_NAME)))]
1639#[serde(rename_all = "camelCase")]
1640#[non_exhaustive]
1641pub struct CloseSessionRequest {
1642 pub session_id: SessionId,
1644 #[serde_as(deserialize_as = "DefaultOnError")]
1650 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1651 #[serde(default)]
1652 #[serde(rename = "_meta")]
1653 pub meta: Option<Meta>,
1654}
1655
1656impl CloseSessionRequest {
1657 #[must_use]
1659 pub fn new(session_id: impl Into<SessionId>) -> Self {
1660 Self {
1661 session_id: session_id.into(),
1662 meta: None,
1663 }
1664 }
1665
1666 #[must_use]
1672 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1673 self.meta = meta.into_option();
1674 self
1675 }
1676}
1677
1678crate::serde_util::default_on_null! {
1679 #[serde_as]
1681 #[skip_serializing_none]
1682 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1683 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
1684 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = SESSION_CLOSE_METHOD_NAME)))]
1685 #[serde(rename_all = "camelCase")]
1686 #[non_exhaustive]
1687 pub struct CloseSessionResponse {
1688 #[serde_as(deserialize_as = "DefaultOnError")]
1694 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1695 #[serde(default)]
1696 #[serde(rename = "_meta")]
1697 pub meta: Option<Meta>,
1698 }
1699}
1700
1701impl CloseSessionResponse {
1702 #[must_use]
1704 pub fn new() -> Self {
1705 Self::default()
1706 }
1707
1708 #[must_use]
1714 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1715 self.meta = meta.into_option();
1716 self
1717 }
1718}
1719
1720#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1724#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
1725#[serde(transparent)]
1726#[from(Arc<str>, String, &str, &mut str, Box<str>, Cow<'_, str>)]
1727#[non_exhaustive]
1728pub struct SessionListCursor(pub Arc<str>);
1729
1730impl SessionListCursor {
1731 #[must_use]
1733 pub fn new(cursor: impl Into<Self>) -> Self {
1734 cursor.into()
1735 }
1736}
1737
1738impl AsRef<str> for SessionListCursor {
1739 fn as_ref(&self) -> &str {
1740 &self.0
1741 }
1742}
1743
1744impl From<&String> for SessionListCursor {
1745 fn from(cursor: &String) -> Self {
1746 Self(cursor.as_str().into())
1747 }
1748}
1749
1750macro_rules! impl_session_list_cursor_option_conversion {
1751 ($source:ty) => {
1752 impl IntoOption<SessionListCursor> for $source {
1753 fn into_option(self) -> Option<SessionListCursor> {
1754 Some(self.into())
1755 }
1756 }
1757 };
1758}
1759
1760impl_session_list_cursor_option_conversion!(Arc<str>);
1761impl_session_list_cursor_option_conversion!(String);
1762impl_session_list_cursor_option_conversion!(&str);
1763impl_session_list_cursor_option_conversion!(&mut str);
1764impl_session_list_cursor_option_conversion!(&String);
1765impl_session_list_cursor_option_conversion!(Box<str>);
1766impl_session_list_cursor_option_conversion!(Cow<'_, str>);
1767
1768crate::serde_util::default_on_null! {
1769 #[serde_as]
1771 #[skip_serializing_none]
1772 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1773 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
1774 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = SESSION_LIST_METHOD_NAME)))]
1775 #[serde(rename_all = "camelCase")]
1776 #[non_exhaustive]
1777 pub struct ListSessionsRequest {
1778 #[serde(default)]
1785 pub cwd: Option<AbsolutePath>,
1786 #[serde(default)]
1790 pub cursor: Option<SessionListCursor>,
1791 #[serde_as(deserialize_as = "DefaultOnError")]
1797 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1798 #[serde(default)]
1799 #[serde(rename = "_meta")]
1800 pub meta: Option<Meta>,
1801 }
1802}
1803
1804impl ListSessionsRequest {
1805 #[must_use]
1807 pub fn new() -> Self {
1808 Self::default()
1809 }
1810
1811 #[must_use]
1813 pub fn cwd(mut self, cwd: impl IntoOption<AbsolutePath>) -> Self {
1814 self.cwd = cwd.into_option();
1815 self
1816 }
1817
1818 #[must_use]
1820 pub fn cursor(mut self, cursor: impl IntoOption<SessionListCursor>) -> Self {
1821 self.cursor = cursor.into_option();
1822 self
1823 }
1824
1825 #[must_use]
1831 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1832 self.meta = meta.into_option();
1833 self
1834 }
1835}
1836
1837#[serde_as]
1839#[skip_serializing_none]
1840#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1841#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1842#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = SESSION_LIST_METHOD_NAME)))]
1843#[serde(rename_all = "camelCase")]
1844#[non_exhaustive]
1845pub struct ListSessionsResponse {
1846 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
1848 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
1849 pub sessions: Vec<SessionInfo>,
1850 #[serde(default)]
1854 pub next_cursor: Option<SessionListCursor>,
1855 #[serde_as(deserialize_as = "DefaultOnError")]
1861 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1862 #[serde(default)]
1863 #[serde(rename = "_meta")]
1864 pub meta: Option<Meta>,
1865}
1866
1867impl ListSessionsResponse {
1868 #[must_use]
1870 pub fn new(sessions: Vec<SessionInfo>) -> Self {
1871 Self {
1872 sessions,
1873 next_cursor: None,
1874 meta: None,
1875 }
1876 }
1877
1878 #[must_use]
1880 pub fn next_cursor(mut self, next_cursor: impl IntoOption<SessionListCursor>) -> Self {
1881 self.next_cursor = next_cursor.into_option();
1882 self
1883 }
1884
1885 #[must_use]
1891 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1892 self.meta = meta.into_option();
1893 self
1894 }
1895}
1896
1897#[serde_as]
1903#[skip_serializing_none]
1904#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1905#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1906#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = SESSION_DELETE_METHOD_NAME)))]
1907#[serde(rename_all = "camelCase")]
1908#[non_exhaustive]
1909pub struct DeleteSessionRequest {
1910 pub session_id: SessionId,
1912 #[serde_as(deserialize_as = "DefaultOnError")]
1918 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1919 #[serde(default)]
1920 #[serde(rename = "_meta")]
1921 pub meta: Option<Meta>,
1922}
1923
1924impl DeleteSessionRequest {
1925 #[must_use]
1927 pub fn new(session_id: impl Into<SessionId>) -> Self {
1928 Self {
1929 session_id: session_id.into(),
1930 meta: None,
1931 }
1932 }
1933
1934 #[must_use]
1940 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1941 self.meta = meta.into_option();
1942 self
1943 }
1944}
1945
1946crate::serde_util::default_on_null! {
1947 #[serde_as]
1949 #[skip_serializing_none]
1950 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1951 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
1952 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = SESSION_DELETE_METHOD_NAME)))]
1953 #[serde(rename_all = "camelCase")]
1954 #[non_exhaustive]
1955 pub struct DeleteSessionResponse {
1956 #[serde_as(deserialize_as = "DefaultOnError")]
1962 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1963 #[serde(default)]
1964 #[serde(rename = "_meta")]
1965 pub meta: Option<Meta>,
1966 }
1967}
1968
1969impl DeleteSessionResponse {
1970 #[must_use]
1972 pub fn new() -> Self {
1973 Self::default()
1974 }
1975
1976 #[must_use]
1982 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1983 self.meta = meta.into_option();
1984 self
1985 }
1986}
1987
1988#[serde_as]
1990#[skip_serializing_none]
1991#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1992#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1993#[serde(rename_all = "camelCase")]
1994#[non_exhaustive]
1995pub struct SessionInfo {
1996 pub session_id: SessionId,
1998 pub cwd: AbsolutePath,
2000 #[serde_as(deserialize_as = "DefaultOnNull")]
2006 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-null" = true)))]
2007 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2008 pub additional_directories: Vec<AbsolutePath>,
2009
2010 #[serde_as(deserialize_as = "DefaultOnError")]
2014 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2015 #[serde(default)]
2016 pub title: Option<String>,
2017 #[serde_as(deserialize_as = "DefaultOnError")]
2021 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "format" = "date-time")))]
2022 #[serde(default)]
2023 pub updated_at: Option<String>,
2024 #[serde_as(deserialize_as = "DefaultOnError")]
2030 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2031 #[serde(default)]
2032 #[serde(rename = "_meta")]
2033 pub meta: Option<Meta>,
2034}
2035
2036impl SessionInfo {
2037 #[must_use]
2039 pub fn new(session_id: impl Into<SessionId>, cwd: impl Into<AbsolutePath>) -> Self {
2040 Self {
2041 session_id: session_id.into(),
2042 cwd: cwd.into(),
2043 additional_directories: vec![],
2044 title: None,
2045 updated_at: None,
2046 meta: None,
2047 }
2048 }
2049
2050 #[must_use]
2052 pub fn additional_directories<I, P>(mut self, additional_directories: I) -> Self
2053 where
2054 I: IntoIterator<Item = P>,
2055 P: Into<AbsolutePath>,
2056 {
2057 self.additional_directories = additional_directories.into_iter().map(Into::into).collect();
2058 self
2059 }
2060
2061 #[must_use]
2063 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
2064 self.title = title.into_option();
2065 self
2066 }
2067
2068 #[must_use]
2070 pub fn updated_at(mut self, updated_at: impl IntoOption<String>) -> Self {
2071 self.updated_at = updated_at.into_option();
2072 self
2073 }
2074
2075 #[must_use]
2081 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2082 self.meta = meta.into_option();
2083 self
2084 }
2085}
2086
2087#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2091#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, From, Display)]
2092#[serde(transparent)]
2093#[from(forward)]
2094#[non_exhaustive]
2095pub struct SessionConfigId(pub Arc<str>);
2096
2097impl SessionConfigId {
2098 #[must_use]
2100 pub fn new(id: impl Into<Self>) -> Self {
2101 id.into()
2102 }
2103}
2104
2105#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2107#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, From, Display)]
2108#[serde(transparent)]
2109#[from(forward)]
2110#[non_exhaustive]
2111pub struct SessionConfigValueId(pub Arc<str>);
2112
2113impl SessionConfigValueId {
2114 #[must_use]
2116 pub fn new(id: impl Into<Self>) -> Self {
2117 id.into()
2118 }
2119}
2120
2121#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2123#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, From, Display)]
2124#[serde(transparent)]
2125#[from(forward)]
2126#[non_exhaustive]
2127pub struct SessionConfigGroupId(pub Arc<str>);
2128
2129impl SessionConfigGroupId {
2130 #[must_use]
2132 pub fn new(id: impl Into<Self>) -> Self {
2133 id.into()
2134 }
2135}
2136
2137#[serde_as]
2139#[skip_serializing_none]
2140#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2141#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2142#[serde(rename_all = "camelCase")]
2143#[non_exhaustive]
2144pub struct SessionConfigSelectOption {
2145 pub value: SessionConfigValueId,
2147 pub name: String,
2149 #[serde_as(deserialize_as = "DefaultOnError")]
2153 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2154 #[serde(default)]
2155 pub description: Option<String>,
2156 #[serde_as(deserialize_as = "DefaultOnError")]
2162 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2163 #[serde(default)]
2164 #[serde(rename = "_meta")]
2165 pub meta: Option<Meta>,
2166}
2167
2168impl SessionConfigSelectOption {
2169 #[must_use]
2171 pub fn new(value: impl Into<SessionConfigValueId>, name: impl Into<String>) -> Self {
2172 Self {
2173 value: value.into(),
2174 name: name.into(),
2175 description: None,
2176 meta: None,
2177 }
2178 }
2179
2180 #[must_use]
2182 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
2183 self.description = description.into_option();
2184 self
2185 }
2186
2187 #[must_use]
2193 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2194 self.meta = meta.into_option();
2195 self
2196 }
2197}
2198
2199#[serde_as]
2201#[skip_serializing_none]
2202#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2203#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2204#[serde(rename_all = "camelCase")]
2205#[non_exhaustive]
2206pub struct SessionConfigSelectGroup {
2207 pub group_id: SessionConfigGroupId,
2209 pub name: String,
2211 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
2213 #[cfg_attr(feature = "schemars", schemars(length(min = 1), extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
2214 pub options: Vec<SessionConfigSelectOption>,
2215 #[serde_as(deserialize_as = "DefaultOnError")]
2221 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2222 #[serde(default)]
2223 #[serde(rename = "_meta")]
2224 pub meta: Option<Meta>,
2225}
2226
2227impl SessionConfigSelectGroup {
2228 #[must_use]
2230 pub fn new(
2231 group_id: impl Into<SessionConfigGroupId>,
2232 name: impl Into<String>,
2233 options: Vec<SessionConfigSelectOption>,
2234 ) -> Self {
2235 Self {
2236 group_id: group_id.into(),
2237 name: name.into(),
2238 options,
2239 meta: None,
2240 }
2241 }
2242
2243 #[must_use]
2249 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2250 self.meta = meta.into_option();
2251 self
2252 }
2253}
2254
2255#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2257#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2258#[serde(untagged)]
2259#[non_exhaustive]
2260pub enum SessionConfigSelectOptions {
2261 Ungrouped(
2263 #[cfg_attr(feature = "schemars", schemars(length(min = 1)))] Vec<SessionConfigSelectOption>,
2264 ),
2265 Grouped(
2267 #[cfg_attr(feature = "schemars", schemars(length(min = 1)))] Vec<SessionConfigSelectGroup>,
2268 ),
2269}
2270
2271impl From<Vec<SessionConfigSelectOption>> for SessionConfigSelectOptions {
2272 fn from(options: Vec<SessionConfigSelectOption>) -> Self {
2273 SessionConfigSelectOptions::Ungrouped(options)
2274 }
2275}
2276
2277impl From<Vec<SessionConfigSelectGroup>> for SessionConfigSelectOptions {
2278 fn from(groups: Vec<SessionConfigSelectGroup>) -> Self {
2279 SessionConfigSelectOptions::Grouped(groups)
2280 }
2281}
2282
2283#[skip_serializing_none]
2285#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2286#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2287#[serde(rename_all = "camelCase")]
2288#[non_exhaustive]
2289pub struct SessionConfigSelect {
2290 pub current_value: SessionConfigValueId,
2292 pub options: SessionConfigSelectOptions,
2295}
2296
2297impl SessionConfigSelect {
2298 #[must_use]
2300 pub fn new(
2301 current_value: impl Into<SessionConfigValueId>,
2302 options: impl Into<SessionConfigSelectOptions>,
2303 ) -> Self {
2304 Self {
2305 current_value: current_value.into(),
2306 options: options.into(),
2307 }
2308 }
2309}
2310
2311#[skip_serializing_none]
2313#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2314#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2315#[serde(rename_all = "camelCase")]
2316#[non_exhaustive]
2317pub struct SessionConfigBoolean {
2318 pub current_value: bool,
2320}
2321
2322impl SessionConfigBoolean {
2323 #[must_use]
2325 pub fn new(current_value: bool) -> Self {
2326 Self { current_value }
2327 }
2328}
2329
2330#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2340#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2341#[serde(rename_all = "snake_case")]
2342#[non_exhaustive]
2343pub enum SessionConfigOptionCategory {
2344 Mode,
2346 Model,
2348 ModelConfig,
2350 ThoughtLevel,
2352 #[serde(untagged)]
2358 Other(String),
2359}
2360
2361#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2363#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2364#[serde(tag = "type", rename_all = "snake_case")]
2365#[non_exhaustive]
2366pub enum SessionConfigKind {
2367 Select(SessionConfigSelect),
2369 Boolean(SessionConfigBoolean),
2371 #[serde(untagged)]
2381 Other(OtherSessionConfigKind),
2382}
2383
2384#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2386#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
2387#[cfg_attr(feature = "schemars", schemars(inline))]
2388#[cfg_attr(feature = "schemars", schemars(transform = other_session_config_kind_schema))]
2389#[serde(rename_all = "camelCase")]
2390#[non_exhaustive]
2391pub struct OtherSessionConfigKind {
2392 #[serde(rename = "type")]
2398 pub type_: String,
2399 #[serde(flatten)]
2401 pub fields: BTreeMap<String, serde_json::Value>,
2402}
2403
2404impl OtherSessionConfigKind {
2405 #[must_use]
2407 pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
2408 fields.remove("type");
2409 fields.remove("_meta");
2410 Self {
2411 type_: type_.into(),
2412 fields,
2413 }
2414 }
2415}
2416
2417impl<'de> Deserialize<'de> for OtherSessionConfigKind {
2418 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2419 where
2420 D: serde::Deserializer<'de>,
2421 {
2422 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
2423 let type_ = fields
2424 .remove("type")
2425 .ok_or_else(|| serde::de::Error::missing_field("type"))?;
2426 let serde_json::Value::String(type_) = type_ else {
2427 return Err(serde::de::Error::custom("`type` must be a string"));
2428 };
2429
2430 if is_known_session_config_kind_type(&type_) {
2431 return Err(serde::de::Error::custom(format!(
2432 "known session configuration option `{type_}` did not match its schema"
2433 )));
2434 }
2435
2436 Ok(Self { type_, fields })
2437 }
2438}
2439
2440fn is_known_session_config_kind_type(type_: &str) -> bool {
2441 matches!(type_, "select" | "boolean")
2442}
2443
2444#[cfg(feature = "schemars")]
2445fn other_session_config_kind_schema(schema: &mut Schema) {
2446 super::schema_util::reject_known_string_discriminators(schema, "type", &["select", "boolean"]);
2447}
2448
2449#[serde_as]
2451#[skip_serializing_none]
2452#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2453#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2454#[serde(rename_all = "camelCase")]
2455#[non_exhaustive]
2456pub struct SessionConfigOption {
2457 pub config_id: SessionConfigId,
2459 pub name: String,
2461 #[serde_as(deserialize_as = "DefaultOnError")]
2465 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2466 #[serde(default)]
2467 pub description: Option<String>,
2468 #[serde_as(deserialize_as = "DefaultOnError")]
2472 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2473 #[serde(default)]
2474 pub category: Option<SessionConfigOptionCategory>,
2475 #[serde(flatten)]
2477 pub kind: SessionConfigKind,
2478 #[serde_as(deserialize_as = "DefaultOnError")]
2484 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2485 #[serde(default)]
2486 #[serde(rename = "_meta")]
2487 pub meta: Option<Meta>,
2488}
2489
2490impl SessionConfigOption {
2491 #[must_use]
2493 pub fn new(
2494 config_id: impl Into<SessionConfigId>,
2495 name: impl Into<String>,
2496 kind: SessionConfigKind,
2497 ) -> Self {
2498 Self {
2499 config_id: config_id.into(),
2500 name: name.into(),
2501 description: None,
2502 category: None,
2503 kind,
2504 meta: None,
2505 }
2506 }
2507
2508 #[must_use]
2510 pub fn select(
2511 config_id: impl Into<SessionConfigId>,
2512 name: impl Into<String>,
2513 current_value: impl Into<SessionConfigValueId>,
2514 options: impl Into<SessionConfigSelectOptions>,
2515 ) -> Self {
2516 Self::new(
2517 config_id,
2518 name,
2519 SessionConfigKind::Select(SessionConfigSelect::new(current_value, options)),
2520 )
2521 }
2522
2523 #[must_use]
2525 pub fn boolean(
2526 config_id: impl Into<SessionConfigId>,
2527 name: impl Into<String>,
2528 current_value: bool,
2529 ) -> Self {
2530 Self::new(
2531 config_id,
2532 name,
2533 SessionConfigKind::Boolean(SessionConfigBoolean::new(current_value)),
2534 )
2535 }
2536
2537 #[must_use]
2539 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
2540 self.description = description.into_option();
2541 self
2542 }
2543
2544 #[must_use]
2546 pub fn category(mut self, category: impl IntoOption<SessionConfigOptionCategory>) -> Self {
2547 self.category = category.into_option();
2548 self
2549 }
2550
2551 #[must_use]
2557 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2558 self.meta = meta.into_option();
2559 self
2560 }
2561}
2562
2563#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2572#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
2573#[serde(tag = "type", rename_all = "snake_case")]
2574#[non_exhaustive]
2575pub enum SessionConfigOptionValue {
2576 Id {
2578 value: SessionConfigValueId,
2580 },
2581 Boolean {
2583 value: bool,
2585 },
2586 #[serde(untagged)]
2595 Other(OtherSessionConfigOptionValue),
2596}
2597
2598#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2600#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
2601#[cfg_attr(feature = "schemars", schemars(inline))]
2602#[cfg_attr(feature = "schemars", schemars(transform = other_session_config_option_value_schema))]
2603#[serde(rename_all = "camelCase")]
2604#[non_exhaustive]
2605pub struct OtherSessionConfigOptionValue {
2606 #[serde(rename = "type")]
2612 pub type_: String,
2613 pub value: serde_json::Value,
2615 #[serde(flatten)]
2617 pub fields: BTreeMap<String, serde_json::Value>,
2618}
2619
2620impl OtherSessionConfigOptionValue {
2621 #[must_use]
2623 pub fn new(
2624 type_: impl Into<String>,
2625 value: serde_json::Value,
2626 mut fields: BTreeMap<String, serde_json::Value>,
2627 ) -> Self {
2628 fields.remove("type");
2629 fields.remove("value");
2630 fields.remove("_meta");
2631 Self {
2632 type_: type_.into(),
2633 value,
2634 fields,
2635 }
2636 }
2637}
2638
2639impl<'de> Deserialize<'de> for OtherSessionConfigOptionValue {
2640 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2641 where
2642 D: serde::Deserializer<'de>,
2643 {
2644 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
2645 let type_ = fields
2646 .remove("type")
2647 .ok_or_else(|| serde::de::Error::missing_field("type"))?;
2648 let serde_json::Value::String(type_) = type_ else {
2649 return Err(serde::de::Error::custom("`type` must be a string"));
2650 };
2651
2652 if is_known_session_config_option_value_type(&type_) {
2653 return Err(serde::de::Error::custom(format!(
2654 "known session configuration option value `{type_}` did not match its schema"
2655 )));
2656 }
2657
2658 let value = fields
2659 .remove("value")
2660 .ok_or_else(|| serde::de::Error::missing_field("value"))?;
2661
2662 Ok(Self {
2663 type_,
2664 value,
2665 fields,
2666 })
2667 }
2668}
2669
2670impl<'de> Deserialize<'de> for SessionConfigOptionValue {
2671 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2672 where
2673 D: serde::Deserializer<'de>,
2674 {
2675 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
2676 let type_ = fields.remove("type");
2677 let value = fields
2678 .remove("value")
2679 .ok_or_else(|| serde::de::Error::missing_field("value"))?;
2680
2681 let type_ = type_.ok_or_else(|| serde::de::Error::missing_field("type"))?;
2682
2683 let serde_json::Value::String(type_) = type_ else {
2684 return Err(serde::de::Error::custom("`type` must be a string"));
2685 };
2686
2687 match type_.as_str() {
2688 "id" => {
2689 let value = serde_json::from_value(value).map_err(|error| {
2690 serde::de::Error::custom(format!(
2691 "`value` must be a string for `type: id`: {error}"
2692 ))
2693 })?;
2694 Ok(Self::Id { value })
2695 }
2696 "boolean" => {
2697 let value = serde_json::from_value(value).map_err(|error| {
2698 serde::de::Error::custom(format!(
2699 "`value` must be a boolean for `type: boolean`: {error}"
2700 ))
2701 })?;
2702 Ok(Self::Boolean { value })
2703 }
2704 _ => Ok(Self::Other(OtherSessionConfigOptionValue {
2705 type_,
2706 value,
2707 fields,
2708 })),
2709 }
2710 }
2711}
2712
2713fn is_known_session_config_option_value_type(type_: &str) -> bool {
2714 matches!(type_, "id" | "boolean")
2715}
2716
2717#[cfg(feature = "schemars")]
2718fn other_session_config_option_value_schema(schema: &mut Schema) {
2719 super::schema_util::reject_known_string_discriminators(schema, "type", &["id", "boolean"]);
2720}
2721
2722impl SessionConfigOptionValue {
2723 #[must_use]
2725 pub fn id(id: impl Into<SessionConfigValueId>) -> Self {
2726 Self::Id { value: id.into() }
2727 }
2728
2729 #[must_use]
2731 pub fn boolean(val: bool) -> Self {
2732 Self::Boolean { value: val }
2733 }
2734
2735 #[must_use]
2738 pub fn as_id(&self) -> Option<&SessionConfigValueId> {
2739 match self {
2740 Self::Id { value } => Some(value),
2741 _ => None,
2742 }
2743 }
2744
2745 #[must_use]
2747 pub fn as_bool(&self) -> Option<bool> {
2748 match self {
2749 Self::Boolean { value } => Some(*value),
2750 _ => None,
2751 }
2752 }
2753}
2754
2755impl From<SessionConfigValueId> for SessionConfigOptionValue {
2756 fn from(value: SessionConfigValueId) -> Self {
2757 Self::Id { value }
2758 }
2759}
2760
2761impl From<bool> for SessionConfigOptionValue {
2762 fn from(value: bool) -> Self {
2763 Self::Boolean { value }
2764 }
2765}
2766
2767impl From<&str> for SessionConfigOptionValue {
2768 fn from(value: &str) -> Self {
2769 Self::Id {
2770 value: SessionConfigValueId::new(value),
2771 }
2772 }
2773}
2774
2775#[serde_as]
2783#[skip_serializing_none]
2784#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2785#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2786#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = SESSION_SET_CONFIG_OPTION_METHOD_NAME)))]
2787#[serde(rename_all = "camelCase")]
2788#[non_exhaustive]
2789pub struct SetSessionConfigOptionRequest {
2790 pub session_id: SessionId,
2792 pub config_id: SessionConfigId,
2794 #[serde(flatten)]
2798 pub value: SessionConfigOptionValue,
2799 #[serde_as(deserialize_as = "DefaultOnError")]
2805 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2806 #[serde(default)]
2807 #[serde(rename = "_meta")]
2808 pub meta: Option<Meta>,
2809}
2810
2811impl SetSessionConfigOptionRequest {
2812 #[must_use]
2814 pub fn new(
2815 session_id: impl Into<SessionId>,
2816 config_id: impl Into<SessionConfigId>,
2817 value: impl Into<SessionConfigOptionValue>,
2818 ) -> Self {
2819 Self {
2820 session_id: session_id.into(),
2821 config_id: config_id.into(),
2822 value: value.into(),
2823 meta: None,
2824 }
2825 }
2826
2827 #[must_use]
2833 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2834 self.meta = meta.into_option();
2835 self
2836 }
2837}
2838
2839#[serde_as]
2841#[skip_serializing_none]
2842#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2843#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2844#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = SESSION_SET_CONFIG_OPTION_METHOD_NAME)))]
2845#[serde(rename_all = "camelCase")]
2846#[non_exhaustive]
2847pub struct SetSessionConfigOptionResponse {
2848 #[serde_as(deserialize_as = "VecSkipError<_>")]
2850 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-skip-invalid-items" = true)))]
2851 pub config_options: Vec<SessionConfigOption>,
2852 #[serde_as(deserialize_as = "DefaultOnError")]
2858 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2859 #[serde(default)]
2860 #[serde(rename = "_meta")]
2861 pub meta: Option<Meta>,
2862}
2863
2864impl SetSessionConfigOptionResponse {
2865 #[must_use]
2867 pub fn new(config_options: Vec<SessionConfigOption>) -> Self {
2868 Self {
2869 config_options,
2870 meta: None,
2871 }
2872 }
2873
2874 #[must_use]
2880 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2881 self.meta = meta.into_option();
2882 self
2883 }
2884}
2885
2886#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2895#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2896#[serde(tag = "type", rename_all = "snake_case")]
2897#[non_exhaustive]
2898pub enum McpServer {
2899 Http(McpServerHttp),
2903 #[cfg(feature = "unstable_mcp_over_acp")]
2912 Acp(McpServerAcp),
2913 Stdio(McpServerStdio),
2917 #[serde(untagged)]
2928 Other(OtherMcpServer),
2929}
2930
2931#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2933#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
2934#[cfg_attr(feature = "schemars", schemars(inline))]
2935#[cfg_attr(feature = "schemars", schemars(transform = other_mcp_server_schema))]
2936#[serde(rename_all = "camelCase")]
2937#[non_exhaustive]
2938pub struct OtherMcpServer {
2939 #[serde(rename = "type")]
2945 pub type_: String,
2946 #[serde(flatten)]
2948 pub fields: BTreeMap<String, serde_json::Value>,
2949}
2950
2951impl OtherMcpServer {
2952 #[must_use]
2954 pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
2955 fields.remove("type");
2956 Self {
2957 type_: type_.into(),
2958 fields,
2959 }
2960 }
2961}
2962
2963impl<'de> Deserialize<'de> for OtherMcpServer {
2964 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2965 where
2966 D: serde::Deserializer<'de>,
2967 {
2968 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
2969 let type_ = fields
2970 .remove("type")
2971 .ok_or_else(|| serde::de::Error::missing_field("type"))?;
2972 let serde_json::Value::String(type_) = type_ else {
2973 return Err(serde::de::Error::custom("`type` must be a string"));
2974 };
2975
2976 if is_known_mcp_server_type(&type_) {
2977 return Err(serde::de::Error::custom(format!(
2978 "known MCP server transport `{type_}` did not match its schema"
2979 )));
2980 }
2981
2982 Ok(Self { type_, fields })
2983 }
2984}
2985
2986fn is_known_mcp_server_type(type_: &str) -> bool {
2987 match type_ {
2988 "http" | "stdio" => true,
2989 #[cfg(feature = "unstable_mcp_over_acp")]
2990 "acp" => true,
2991 _ => false,
2992 }
2993}
2994
2995#[cfg(feature = "schemars")]
2996fn other_mcp_server_schema(schema: &mut Schema) {
2997 super::schema_util::reject_known_string_discriminators(
2998 schema,
2999 "type",
3000 &[
3001 "http",
3002 "stdio",
3003 #[cfg(feature = "unstable_mcp_over_acp")]
3004 "acp",
3005 ],
3006 );
3007}
3008
3009#[serde_as]
3011#[skip_serializing_none]
3012#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3013#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3014#[serde(rename_all = "camelCase")]
3015#[non_exhaustive]
3016pub struct McpServerHttp {
3017 pub name: String,
3019 #[cfg_attr(feature = "schemars", schemars(url))]
3021 pub url: String,
3022 #[serde_as(deserialize_as = "DefaultOnNull")]
3025 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-null" = true)))]
3026 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3027 pub headers: Vec<HttpHeader>,
3028 #[serde_as(deserialize_as = "DefaultOnError")]
3034 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3035 #[serde(default)]
3036 #[serde(rename = "_meta")]
3037 pub meta: Option<Meta>,
3038}
3039
3040impl McpServerHttp {
3041 #[must_use]
3043 pub fn new(name: impl Into<String>, url: impl Into<String>) -> Self {
3044 Self {
3045 name: name.into(),
3046 url: url.into(),
3047 headers: Vec::new(),
3048 meta: None,
3049 }
3050 }
3051
3052 #[must_use]
3055 pub fn headers(mut self, headers: Vec<HttpHeader>) -> Self {
3056 self.headers = headers;
3057 self
3058 }
3059
3060 #[must_use]
3066 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3067 self.meta = meta.into_option();
3068 self
3069 }
3070}
3071
3072#[cfg(feature = "unstable_mcp_over_acp")]
3081#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3082#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
3083#[serde(transparent)]
3084#[from(forward)]
3085#[non_exhaustive]
3086pub struct McpServerAcpId(pub Arc<str>);
3087
3088#[cfg(feature = "unstable_mcp_over_acp")]
3089impl McpServerAcpId {
3090 #[must_use]
3092 pub fn new(id: impl Into<Self>) -> Self {
3093 id.into()
3094 }
3095}
3096
3097#[serde_as]
3106#[skip_serializing_none]
3107#[cfg(feature = "unstable_mcp_over_acp")]
3108#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3109#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3110#[serde(rename_all = "camelCase")]
3111#[non_exhaustive]
3112pub struct McpServerAcp {
3113 pub name: String,
3115 pub server_id: McpServerAcpId,
3120 #[serde_as(deserialize_as = "DefaultOnError")]
3126 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3127 #[serde(default)]
3128 #[serde(rename = "_meta")]
3129 pub meta: Option<Meta>,
3130}
3131
3132#[cfg(feature = "unstable_mcp_over_acp")]
3133impl McpServerAcp {
3134 #[must_use]
3136 pub fn new(name: impl Into<String>, server_id: impl Into<McpServerAcpId>) -> Self {
3137 Self {
3138 name: name.into(),
3139 server_id: server_id.into(),
3140 meta: None,
3141 }
3142 }
3143
3144 #[must_use]
3150 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3151 self.meta = meta.into_option();
3152 self
3153 }
3154}
3155
3156#[serde_as]
3158#[skip_serializing_none]
3159#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3160#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3161#[serde(rename_all = "camelCase")]
3162#[non_exhaustive]
3163pub struct McpServerStdio {
3164 pub name: String,
3166 pub command: AbsolutePath,
3168 #[serde_as(deserialize_as = "DefaultOnNull")]
3170 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-null" = true)))]
3171 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3172 pub args: Vec<String>,
3173 #[serde_as(deserialize_as = "DefaultOnNull")]
3176 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-null" = true)))]
3177 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3178 pub env: Vec<EnvVariable>,
3179 #[serde_as(deserialize_as = "DefaultOnError")]
3185 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3186 #[serde(default)]
3187 #[serde(rename = "_meta")]
3188 pub meta: Option<Meta>,
3189}
3190
3191impl McpServerStdio {
3192 #[must_use]
3194 pub fn new(name: impl Into<String>, command: impl Into<AbsolutePath>) -> Self {
3195 Self {
3196 name: name.into(),
3197 command: command.into(),
3198 args: Vec::new(),
3199 env: Vec::new(),
3200 meta: None,
3201 }
3202 }
3203
3204 #[must_use]
3206 pub fn args(mut self, args: Vec<String>) -> Self {
3207 self.args = args;
3208 self
3209 }
3210
3211 #[must_use]
3214 pub fn env(mut self, env: Vec<EnvVariable>) -> Self {
3215 self.env = env;
3216 self
3217 }
3218
3219 #[must_use]
3225 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3226 self.meta = meta.into_option();
3227 self
3228 }
3229}
3230
3231#[serde_as]
3233#[skip_serializing_none]
3234#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3235#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3236#[serde(rename_all = "camelCase")]
3237#[non_exhaustive]
3238pub struct EnvVariable {
3239 pub name: String,
3241 pub value: String,
3243 #[serde_as(deserialize_as = "DefaultOnError")]
3249 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3250 #[serde(default)]
3251 #[serde(rename = "_meta")]
3252 pub meta: Option<Meta>,
3253}
3254
3255impl EnvVariable {
3256 #[must_use]
3258 pub fn new(name: impl Into<String>, value: impl Into<String>) -> Self {
3259 Self {
3260 name: name.into(),
3261 value: value.into(),
3262 meta: None,
3263 }
3264 }
3265
3266 #[must_use]
3272 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3273 self.meta = meta.into_option();
3274 self
3275 }
3276}
3277
3278#[serde_as]
3280#[skip_serializing_none]
3281#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3282#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3283#[serde(rename_all = "camelCase")]
3284#[non_exhaustive]
3285pub struct HttpHeader {
3286 pub name: String,
3288 pub value: String,
3290 #[serde_as(deserialize_as = "DefaultOnError")]
3296 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3297 #[serde(default)]
3298 #[serde(rename = "_meta")]
3299 pub meta: Option<Meta>,
3300}
3301
3302impl HttpHeader {
3303 #[must_use]
3305 pub fn new(name: impl Into<String>, value: impl Into<String>) -> Self {
3306 Self {
3307 name: name.into(),
3308 value: value.into(),
3309 meta: None,
3310 }
3311 }
3312
3313 #[must_use]
3319 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3320 self.meta = meta.into_option();
3321 self
3322 }
3323}
3324
3325#[cfg(feature = "unstable_session_system_prompt")]
3338#[serde_as]
3339#[skip_serializing_none]
3340#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3341#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3342#[serde(rename_all = "camelCase")]
3343#[non_exhaustive]
3344pub struct SystemPrompt {
3345 pub mode: SystemPromptMode,
3350 pub content: Vec<ContentBlock>,
3357 #[serde_as(deserialize_as = "DefaultOnError")]
3363 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3364 #[serde(default)]
3365 #[serde(rename = "_meta")]
3366 pub meta: Option<Meta>,
3367}
3368
3369#[cfg(feature = "unstable_session_system_prompt")]
3370impl SystemPrompt {
3371 #[must_use]
3376 pub fn new(content: Vec<ContentBlock>) -> Self {
3377 Self {
3378 mode: SystemPromptMode::default(),
3379 content,
3380 meta: None,
3381 }
3382 }
3383
3384 #[must_use]
3386 pub fn mode(mut self, mode: SystemPromptMode) -> Self {
3387 self.mode = mode;
3388 self
3389 }
3390
3391 #[must_use]
3397 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3398 self.meta = meta.into_option();
3399 self
3400 }
3401}
3402
3403#[cfg(feature = "unstable_session_system_prompt")]
3413#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3414#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
3415#[serde(rename_all = "snake_case")]
3416#[non_exhaustive]
3417pub enum SystemPromptMode {
3418 #[default]
3425 Append,
3426 Override,
3435 #[serde(untagged)]
3441 Other(String),
3442}
3443
3444#[serde_as]
3452#[skip_serializing_none]
3453#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3454#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3455#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = SESSION_PROMPT_METHOD_NAME)))]
3456#[serde(rename_all = "camelCase")]
3457#[non_exhaustive]
3458pub struct PromptRequest {
3459 pub session_id: SessionId,
3461 #[cfg_attr(feature = "schemars", schemars(length(min = 1)))]
3478 pub prompt: Vec<ContentBlock>,
3479 #[serde_as(deserialize_as = "DefaultOnError")]
3485 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3486 #[serde(default)]
3487 #[serde(rename = "_meta")]
3488 pub meta: Option<Meta>,
3489}
3490
3491impl PromptRequest {
3492 #[must_use]
3494 pub fn new(session_id: impl Into<SessionId>, prompt: Vec<ContentBlock>) -> Self {
3495 Self {
3496 session_id: session_id.into(),
3497 prompt,
3498 meta: None,
3499 }
3500 }
3501
3502 #[must_use]
3508 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3509 self.meta = meta.into_option();
3510 self
3511 }
3512}
3513
3514#[serde_as]
3525#[skip_serializing_none]
3526#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3527#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3528#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = SESSION_PROMPT_METHOD_NAME)))]
3529#[serde(rename_all = "camelCase")]
3530#[non_exhaustive]
3531pub struct PromptResponse {
3532 pub message_id: MessageId,
3540 #[serde_as(deserialize_as = "DefaultOnError")]
3546 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3547 #[serde(default)]
3548 #[serde(rename = "_meta")]
3549 pub meta: Option<Meta>,
3550}
3551
3552impl PromptResponse {
3553 #[must_use]
3555 pub fn new(message_id: impl Into<MessageId>) -> Self {
3556 Self {
3557 message_id: message_id.into(),
3558 meta: None,
3559 }
3560 }
3561
3562 #[must_use]
3568 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3569 self.meta = meta.into_option();
3570 self
3571 }
3572}
3573
3574#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3578#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
3579#[serde(tag = "stopReason", rename_all = "snake_case")]
3580#[non_exhaustive]
3581pub enum StopReason {
3582 EndTurn,
3585 MaxTokens,
3587 MaxTurnRequests,
3590 Refusal,
3592 Cancelled,
3603 Error(ErrorStopReason),
3609 #[serde(untagged)]
3615 Other(OtherStopReason),
3616}
3617
3618impl From<ErrorStopReason> for StopReason {
3619 fn from(value: ErrorStopReason) -> Self {
3620 Self::Error(value)
3621 }
3622}
3623
3624#[serde_as]
3626#[skip_serializing_none]
3627#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3628#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
3629#[serde(rename_all = "camelCase")]
3630#[non_exhaustive]
3631pub struct ErrorStopReason {
3632 #[serde_as(deserialize_as = "DefaultOnError")]
3637 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3638 #[serde(default)]
3639 pub error: Option<Box<Error>>,
3640}
3641
3642impl ErrorStopReason {
3643 #[must_use]
3645 pub fn new() -> Self {
3646 Self::default()
3647 }
3648
3649 #[must_use]
3651 pub fn error(mut self, error: impl IntoOption<Error>) -> Self {
3652 self.error = error.into_option().map(Box::new);
3653 self
3654 }
3655}
3656
3657#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3659#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
3660#[cfg_attr(feature = "schemars", schemars(inline))]
3661#[cfg_attr(feature = "schemars", schemars(transform = other_stop_reason_schema))]
3662#[serde(rename_all = "camelCase")]
3663#[non_exhaustive]
3664pub struct OtherStopReason {
3665 pub stop_reason: String,
3671 #[serde(flatten)]
3673 pub fields: BTreeMap<String, serde_json::Value>,
3674}
3675
3676impl OtherStopReason {
3677 #[must_use]
3679 pub fn new(
3680 stop_reason: impl Into<String>,
3681 mut fields: BTreeMap<String, serde_json::Value>,
3682 ) -> Self {
3683 remove_idle_state_update_fields(&mut fields);
3684 Self {
3685 stop_reason: stop_reason.into(),
3686 fields,
3687 }
3688 }
3689}
3690
3691impl<'de> Deserialize<'de> for OtherStopReason {
3692 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3693 where
3694 D: serde::Deserializer<'de>,
3695 {
3696 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
3697 let stop_reason = fields
3698 .remove("stopReason")
3699 .ok_or_else(|| serde::de::Error::missing_field("stopReason"))?;
3700 let serde_json::Value::String(stop_reason) = stop_reason else {
3701 return Err(serde::de::Error::custom("`stopReason` must be a string"));
3702 };
3703
3704 if KNOWN_STOP_REASONS.contains(&stop_reason.as_str()) {
3705 return Err(serde::de::Error::custom(format!(
3706 "known stop reason `{stop_reason}` did not match its schema"
3707 )));
3708 }
3709 remove_idle_state_update_fields(&mut fields);
3710
3711 Ok(Self {
3712 stop_reason,
3713 fields,
3714 })
3715 }
3716}
3717
3718const KNOWN_STOP_REASONS: &[&str] = &[
3719 "end_turn",
3720 "max_tokens",
3721 "max_turn_requests",
3722 "refusal",
3723 "cancelled",
3724 "error",
3725];
3726
3727fn remove_idle_state_update_fields(fields: &mut BTreeMap<String, serde_json::Value>) {
3729 fields.remove("stopReason");
3730 fields.remove("usage");
3731 fields.remove("_meta");
3732}
3733
3734#[cfg(feature = "schemars")]
3735fn other_stop_reason_schema(schema: &mut Schema) {
3736 super::schema_util::reject_known_string_discriminators(
3737 schema,
3738 "stopReason",
3739 KNOWN_STOP_REASONS,
3740 );
3741}
3742
3743#[cfg(feature = "unstable_end_turn_token_usage")]
3749#[serde_as]
3750#[skip_serializing_none]
3751#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3752#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3753#[serde(rename_all = "camelCase")]
3754#[non_exhaustive]
3755pub struct Usage {
3756 pub total_tokens: u64,
3758 pub input_tokens: u64,
3760 pub output_tokens: u64,
3762 #[serde_as(deserialize_as = "DefaultOnError")]
3766 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3767 #[serde(default)]
3768 pub thought_tokens: Option<u64>,
3769 #[serde_as(deserialize_as = "DefaultOnError")]
3773 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3774 #[serde(default)]
3775 pub cached_read_tokens: Option<u64>,
3776 #[serde_as(deserialize_as = "DefaultOnError")]
3780 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3781 #[serde(default)]
3782 pub cached_write_tokens: Option<u64>,
3783 #[serde_as(deserialize_as = "DefaultOnError")]
3789 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3790 #[serde(default)]
3791 #[serde(rename = "_meta")]
3792 pub meta: Option<Meta>,
3793}
3794
3795#[cfg(feature = "unstable_end_turn_token_usage")]
3796impl Usage {
3797 #[must_use]
3799 pub fn new(total_tokens: u64, input_tokens: u64, output_tokens: u64) -> Self {
3800 Self {
3801 total_tokens,
3802 input_tokens,
3803 output_tokens,
3804 thought_tokens: None,
3805 cached_read_tokens: None,
3806 cached_write_tokens: None,
3807 meta: None,
3808 }
3809 }
3810
3811 #[must_use]
3813 pub fn thought_tokens(mut self, thought_tokens: impl IntoOption<u64>) -> Self {
3814 self.thought_tokens = thought_tokens.into_option();
3815 self
3816 }
3817
3818 #[must_use]
3820 pub fn cached_read_tokens(mut self, cached_read_tokens: impl IntoOption<u64>) -> Self {
3821 self.cached_read_tokens = cached_read_tokens.into_option();
3822 self
3823 }
3824
3825 #[must_use]
3827 pub fn cached_write_tokens(mut self, cached_write_tokens: impl IntoOption<u64>) -> Self {
3828 self.cached_write_tokens = cached_write_tokens.into_option();
3829 self
3830 }
3831
3832 #[must_use]
3838 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3839 self.meta = meta.into_option();
3840 self
3841 }
3842}
3843
3844#[cfg(feature = "unstable_llm_providers")]
3857#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3858#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3859#[serde(rename_all = "snake_case")]
3860#[non_exhaustive]
3861#[expect(clippy::doc_markdown)]
3862pub enum LlmProtocol {
3863 Anthropic,
3865 #[serde(rename = "openai")]
3867 OpenAi,
3868 Azure,
3870 Vertex,
3872 Bedrock,
3874 #[serde(untagged)]
3880 Other(String),
3881}
3882
3883#[cfg(feature = "unstable_llm_providers")]
3889#[serde_as]
3890#[skip_serializing_none]
3891#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3892#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3893#[serde(rename_all = "camelCase")]
3894#[non_exhaustive]
3895pub struct ProviderCurrentConfig {
3896 pub api_type: LlmProtocol,
3898 #[cfg_attr(feature = "schemars", schemars(url))]
3900 pub base_url: String,
3901 #[serde_as(deserialize_as = "DefaultOnError")]
3907 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3908 #[serde(default)]
3909 #[serde(rename = "_meta")]
3910 pub meta: Option<Meta>,
3911}
3912
3913#[cfg(feature = "unstable_llm_providers")]
3914impl ProviderCurrentConfig {
3915 #[must_use]
3917 pub fn new(api_type: LlmProtocol, base_url: impl Into<String>) -> Self {
3918 Self {
3919 api_type,
3920 base_url: base_url.into(),
3921 meta: None,
3922 }
3923 }
3924
3925 #[must_use]
3931 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3932 self.meta = meta.into_option();
3933 self
3934 }
3935}
3936
3937#[cfg(feature = "unstable_llm_providers")]
3943#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3944#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
3945#[serde(transparent)]
3946#[from(forward)]
3947#[non_exhaustive]
3948pub struct ProviderId(pub Arc<str>);
3949
3950#[cfg(feature = "unstable_llm_providers")]
3951impl ProviderId {
3952 #[must_use]
3954 pub fn new(id: impl Into<Self>) -> Self {
3955 id.into()
3956 }
3957}
3958
3959#[cfg(feature = "unstable_llm_providers")]
3965#[serde_as]
3966#[skip_serializing_none]
3967#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3968#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3969#[serde(rename_all = "camelCase")]
3970#[non_exhaustive]
3971pub struct ProviderInfo {
3972 pub provider_id: ProviderId,
3974 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
3976 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
3977 pub supported: Vec<LlmProtocol>,
3978 pub required: bool,
3981 #[serde(default)]
3984 pub current: Option<ProviderCurrentConfig>,
3985 #[serde_as(deserialize_as = "DefaultOnError")]
3991 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3992 #[serde(default)]
3993 #[serde(rename = "_meta")]
3994 pub meta: Option<Meta>,
3995}
3996
3997#[cfg(feature = "unstable_llm_providers")]
3998impl ProviderInfo {
3999 #[must_use]
4001 pub fn new(
4002 provider_id: impl Into<ProviderId>,
4003 supported: Vec<LlmProtocol>,
4004 required: bool,
4005 current: impl IntoOption<ProviderCurrentConfig>,
4006 ) -> Self {
4007 Self {
4008 provider_id: provider_id.into(),
4009 supported,
4010 required,
4011 current: current.into_option(),
4012 meta: None,
4013 }
4014 }
4015
4016 #[must_use]
4022 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
4023 self.meta = meta.into_option();
4024 self
4025 }
4026}
4027
4028#[cfg(feature = "unstable_llm_providers")]
4029crate::serde_util::default_on_null! {
4030 #[serde_as]
4036 #[skip_serializing_none]
4037 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4038 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
4039 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = PROVIDERS_LIST_METHOD_NAME)))]
4040 #[serde(rename_all = "camelCase")]
4041 #[non_exhaustive]
4042 pub struct ListProvidersRequest {
4043 #[serde_as(deserialize_as = "DefaultOnError")]
4049 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4050 #[serde(default)]
4051 #[serde(rename = "_meta")]
4052 pub meta: Option<Meta>,
4053 }
4054}
4055
4056#[cfg(feature = "unstable_llm_providers")]
4057impl ListProvidersRequest {
4058 #[must_use]
4060 pub fn new() -> Self {
4061 Self::default()
4062 }
4063
4064 #[must_use]
4070 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
4071 self.meta = meta.into_option();
4072 self
4073 }
4074}
4075
4076#[cfg(feature = "unstable_llm_providers")]
4082#[serde_as]
4083#[skip_serializing_none]
4084#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4085#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4086#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = PROVIDERS_LIST_METHOD_NAME)))]
4087#[serde(rename_all = "camelCase")]
4088#[non_exhaustive]
4089pub struct ListProvidersResponse {
4090 pub providers: Vec<ProviderInfo>,
4092 #[serde_as(deserialize_as = "DefaultOnError")]
4098 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4099 #[serde(default)]
4100 #[serde(rename = "_meta")]
4101 pub meta: Option<Meta>,
4102}
4103
4104#[cfg(feature = "unstable_llm_providers")]
4105impl ListProvidersResponse {
4106 #[must_use]
4108 pub fn new(providers: Vec<ProviderInfo>) -> Self {
4109 Self {
4110 providers,
4111 meta: None,
4112 }
4113 }
4114
4115 #[must_use]
4121 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
4122 self.meta = meta.into_option();
4123 self
4124 }
4125}
4126
4127#[cfg(feature = "unstable_llm_providers")]
4135#[serde_as]
4136#[skip_serializing_none]
4137#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4138#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
4139#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = PROVIDERS_SET_METHOD_NAME)))]
4140#[serde(rename_all = "camelCase")]
4141#[non_exhaustive]
4142pub struct SetProviderRequest {
4143 pub provider_id: ProviderId,
4145 pub api_type: LlmProtocol,
4147 #[cfg_attr(feature = "schemars", schemars(url))]
4149 pub base_url: String,
4150 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
4153 pub headers: HashMap<String, String>,
4154 #[serde_as(deserialize_as = "DefaultOnError")]
4160 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4161 #[serde(default)]
4162 #[serde(rename = "_meta")]
4163 pub meta: Option<Meta>,
4164}
4165
4166#[cfg(feature = "unstable_llm_providers")]
4167impl SetProviderRequest {
4168 #[must_use]
4170 pub fn new(
4171 provider_id: impl Into<ProviderId>,
4172 api_type: LlmProtocol,
4173 base_url: impl Into<String>,
4174 ) -> Self {
4175 Self {
4176 provider_id: provider_id.into(),
4177 api_type,
4178 base_url: base_url.into(),
4179 headers: HashMap::new(),
4180 meta: None,
4181 }
4182 }
4183
4184 #[must_use]
4187 pub fn headers(mut self, headers: HashMap<String, String>) -> Self {
4188 self.headers = headers;
4189 self
4190 }
4191
4192 #[must_use]
4198 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
4199 self.meta = meta.into_option();
4200 self
4201 }
4202}
4203
4204#[cfg(feature = "unstable_llm_providers")]
4205crate::serde_util::default_on_null! {
4206 #[serde_as]
4212 #[skip_serializing_none]
4213 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4214 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
4215 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = PROVIDERS_SET_METHOD_NAME)))]
4216 #[serde(rename_all = "camelCase")]
4217 #[non_exhaustive]
4218 pub struct SetProviderResponse {
4219 #[serde_as(deserialize_as = "DefaultOnError")]
4225 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4226 #[serde(default)]
4227 #[serde(rename = "_meta")]
4228 pub meta: Option<Meta>,
4229 }
4230}
4231
4232#[cfg(feature = "unstable_llm_providers")]
4233impl SetProviderResponse {
4234 #[must_use]
4236 pub fn new() -> Self {
4237 Self::default()
4238 }
4239
4240 #[must_use]
4246 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
4247 self.meta = meta.into_option();
4248 self
4249 }
4250}
4251
4252#[cfg(feature = "unstable_llm_providers")]
4258#[serde_as]
4259#[skip_serializing_none]
4260#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4261#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4262#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = PROVIDERS_DISABLE_METHOD_NAME)))]
4263#[serde(rename_all = "camelCase")]
4264#[non_exhaustive]
4265pub struct DisableProviderRequest {
4266 pub provider_id: ProviderId,
4268 #[serde_as(deserialize_as = "DefaultOnError")]
4274 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4275 #[serde(default)]
4276 #[serde(rename = "_meta")]
4277 pub meta: Option<Meta>,
4278}
4279
4280#[cfg(feature = "unstable_llm_providers")]
4281impl DisableProviderRequest {
4282 #[must_use]
4284 pub fn new(provider_id: impl Into<ProviderId>) -> Self {
4285 Self {
4286 provider_id: provider_id.into(),
4287 meta: None,
4288 }
4289 }
4290
4291 #[must_use]
4297 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
4298 self.meta = meta.into_option();
4299 self
4300 }
4301}
4302
4303#[cfg(feature = "unstable_llm_providers")]
4304crate::serde_util::default_on_null! {
4305 #[serde_as]
4311 #[skip_serializing_none]
4312 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4313 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
4314 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = PROVIDERS_DISABLE_METHOD_NAME)))]
4315 #[serde(rename_all = "camelCase")]
4316 #[non_exhaustive]
4317 pub struct DisableProviderResponse {
4318 #[serde_as(deserialize_as = "DefaultOnError")]
4324 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4325 #[serde(default)]
4326 #[serde(rename = "_meta")]
4327 pub meta: Option<Meta>,
4328 }
4329}
4330
4331#[cfg(feature = "unstable_llm_providers")]
4332impl DisableProviderResponse {
4333 #[must_use]
4335 pub fn new() -> Self {
4336 Self::default()
4337 }
4338
4339 #[must_use]
4345 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
4346 self.meta = meta.into_option();
4347 self
4348 }
4349}
4350
4351#[serde_as]
4360#[skip_serializing_none]
4361#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4362#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4363#[serde(rename_all = "camelCase")]
4364#[non_exhaustive]
4365pub struct AgentCapabilities {
4366 #[serde_as(deserialize_as = "DefaultOnError")]
4374 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4375 #[serde(default)]
4376 pub session: Option<SessionCapabilities>,
4377 #[serde_as(deserialize_as = "DefaultOnError")]
4384 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4385 #[serde(default)]
4386 pub auth: Option<AgentAuthCapabilities>,
4387 #[cfg(feature = "unstable_llm_providers")]
4396 #[serde_as(deserialize_as = "DefaultOnError")]
4397 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4398 #[serde(default)]
4399 pub providers: Option<ProvidersCapabilities>,
4400 #[serde_as(deserialize_as = "DefaultOnError")]
4406 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4407 #[serde(default)]
4408 #[serde(rename = "_meta")]
4409 pub meta: Option<Meta>,
4410}
4411
4412impl AgentCapabilities {
4413 #[must_use]
4415 pub fn new() -> Self {
4416 Self::default()
4417 }
4418
4419 #[must_use]
4427 pub fn session(mut self, session: impl IntoOption<SessionCapabilities>) -> Self {
4428 self.session = session.into_option();
4429 self
4430 }
4431
4432 #[must_use]
4436 pub fn auth(mut self, auth: impl IntoOption<AgentAuthCapabilities>) -> Self {
4437 self.auth = auth.into_option();
4438 self
4439 }
4440
4441 #[cfg(feature = "unstable_llm_providers")]
4447 #[must_use]
4448 pub fn providers(mut self, providers: impl IntoOption<ProvidersCapabilities>) -> Self {
4449 self.providers = providers.into_option();
4450 self
4451 }
4452
4453 #[must_use]
4459 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
4460 self.meta = meta.into_option();
4461 self
4462 }
4463}
4464
4465#[cfg(feature = "unstable_llm_providers")]
4473#[serde_as]
4474#[skip_serializing_none]
4475#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4476#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4477#[non_exhaustive]
4478pub struct ProvidersCapabilities {
4479 #[serde_as(deserialize_as = "DefaultOnError")]
4485 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4486 #[serde(default)]
4487 #[serde(rename = "_meta")]
4488 pub meta: Option<Meta>,
4489}
4490
4491#[cfg(feature = "unstable_llm_providers")]
4492impl ProvidersCapabilities {
4493 #[must_use]
4495 pub fn new() -> Self {
4496 Self::default()
4497 }
4498
4499 #[must_use]
4505 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
4506 self.meta = meta.into_option();
4507 self
4508 }
4509}
4510
4511#[serde_as]
4523#[skip_serializing_none]
4524#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4525#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4526#[serde(rename_all = "camelCase")]
4527#[non_exhaustive]
4528pub struct SessionCapabilities {
4529 #[serde_as(deserialize_as = "DefaultOnError")]
4535 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4536 #[serde(default)]
4537 pub prompt: Option<PromptCapabilities>,
4538 #[serde_as(deserialize_as = "DefaultOnError")]
4543 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4544 #[serde(default)]
4545 pub mcp: Option<McpCapabilities>,
4546 #[serde_as(deserialize_as = "DefaultOnError")]
4551 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4552 #[serde(default)]
4553 pub delete: Option<SessionDeleteCapabilities>,
4554 #[serde_as(deserialize_as = "DefaultOnError")]
4563 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4564 #[serde(default)]
4565 pub additional_directories: Option<SessionAdditionalDirectoriesCapabilities>,
4566 #[cfg(feature = "unstable_session_system_prompt")]
4575 #[serde_as(deserialize_as = "DefaultOnError")]
4576 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4577 #[serde(default)]
4578 pub system_prompt: Option<SessionSystemPromptCapabilities>,
4579 #[cfg(feature = "unstable_session_fork")]
4588 #[serde_as(deserialize_as = "DefaultOnError")]
4589 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4590 #[serde(default)]
4591 pub fork: Option<SessionForkCapabilities>,
4592 #[serde_as(deserialize_as = "DefaultOnError")]
4598 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4599 #[serde(default)]
4600 #[serde(rename = "_meta")]
4601 pub meta: Option<Meta>,
4602}
4603
4604impl SessionCapabilities {
4605 #[must_use]
4607 pub fn new() -> Self {
4608 Self::default()
4609 }
4610
4611 #[must_use]
4617 pub fn prompt(mut self, prompt: impl IntoOption<PromptCapabilities>) -> Self {
4618 self.prompt = prompt.into_option();
4619 self
4620 }
4621
4622 #[must_use]
4627 pub fn mcp(mut self, mcp: impl IntoOption<McpCapabilities>) -> Self {
4628 self.mcp = mcp.into_option();
4629 self
4630 }
4631
4632 #[must_use]
4637 pub fn delete(mut self, delete: impl IntoOption<SessionDeleteCapabilities>) -> Self {
4638 self.delete = delete.into_option();
4639 self
4640 }
4641
4642 #[must_use]
4651 pub fn additional_directories(
4652 mut self,
4653 additional_directories: impl IntoOption<SessionAdditionalDirectoriesCapabilities>,
4654 ) -> Self {
4655 self.additional_directories = additional_directories.into_option();
4656 self
4657 }
4658
4659 #[cfg(feature = "unstable_session_system_prompt")]
4668 #[must_use]
4669 pub fn system_prompt(
4670 mut self,
4671 system_prompt: impl IntoOption<SessionSystemPromptCapabilities>,
4672 ) -> Self {
4673 self.system_prompt = system_prompt.into_option();
4674 self
4675 }
4676
4677 #[cfg(feature = "unstable_session_fork")]
4678 #[must_use]
4683 pub fn fork(mut self, fork: impl IntoOption<SessionForkCapabilities>) -> Self {
4684 self.fork = fork.into_option();
4685 self
4686 }
4687
4688 #[must_use]
4694 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
4695 self.meta = meta.into_option();
4696 self
4697 }
4698}
4699
4700#[serde_as]
4704#[skip_serializing_none]
4705#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4706#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4707#[non_exhaustive]
4708pub struct SessionDeleteCapabilities {
4709 #[serde_as(deserialize_as = "DefaultOnError")]
4715 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4716 #[serde(default)]
4717 #[serde(rename = "_meta")]
4718 pub meta: Option<Meta>,
4719}
4720
4721impl SessionDeleteCapabilities {
4722 #[must_use]
4724 pub fn new() -> Self {
4725 Self::default()
4726 }
4727
4728 #[must_use]
4734 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
4735 self.meta = meta.into_option();
4736 self
4737 }
4738}
4739
4740#[serde_as]
4747#[skip_serializing_none]
4748#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4749#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4750#[non_exhaustive]
4751pub struct SessionAdditionalDirectoriesCapabilities {
4752 #[serde_as(deserialize_as = "DefaultOnError")]
4758 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4759 #[serde(default)]
4760 #[serde(rename = "_meta")]
4761 pub meta: Option<Meta>,
4762}
4763
4764impl SessionAdditionalDirectoriesCapabilities {
4765 #[must_use]
4767 pub fn new() -> Self {
4768 Self::default()
4769 }
4770
4771 #[must_use]
4777 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
4778 self.meta = meta.into_option();
4779 self
4780 }
4781}
4782
4783#[cfg(feature = "unstable_session_system_prompt")]
4792#[serde_as]
4793#[skip_serializing_none]
4794#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4795#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4796#[serde(rename_all = "camelCase")]
4797#[non_exhaustive]
4798pub struct SessionSystemPromptCapabilities {
4799 #[serde_as(deserialize_as = "DefaultOnError")]
4803 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4804 #[serde(default)]
4805 pub mode: Option<SessionSystemPromptModeCapabilities>,
4806 #[serde_as(deserialize_as = "DefaultOnError")]
4812 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4813 #[serde(default)]
4814 #[serde(rename = "_meta")]
4815 pub meta: Option<Meta>,
4816}
4817
4818#[cfg(feature = "unstable_session_system_prompt")]
4819impl SessionSystemPromptCapabilities {
4820 #[must_use]
4822 pub fn new() -> Self {
4823 Self::default()
4824 }
4825
4826 #[must_use]
4830 pub fn mode(mut self, mode: impl IntoOption<SessionSystemPromptModeCapabilities>) -> Self {
4831 self.mode = mode.into_option();
4832 self
4833 }
4834
4835 #[must_use]
4841 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
4842 self.meta = meta.into_option();
4843 self
4844 }
4845}
4846
4847#[cfg(feature = "unstable_session_system_prompt")]
4856#[serde_as]
4857#[skip_serializing_none]
4858#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4859#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4860#[serde(rename_all = "camelCase")]
4861#[non_exhaustive]
4862pub struct SessionSystemPromptModeCapabilities {
4863 #[serde_as(deserialize_as = "DefaultOnError")]
4869 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4870 #[serde(default, rename = "override")]
4871 pub override_: Option<SessionSystemPromptOverrideCapabilities>,
4872 #[serde_as(deserialize_as = "DefaultOnError")]
4878 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4879 #[serde(default)]
4880 #[serde(rename = "_meta")]
4881 pub meta: Option<Meta>,
4882}
4883
4884#[cfg(feature = "unstable_session_system_prompt")]
4885impl SessionSystemPromptModeCapabilities {
4886 #[must_use]
4888 pub fn new() -> Self {
4889 Self::default()
4890 }
4891
4892 #[must_use]
4898 pub fn override_(
4899 mut self,
4900 override_: impl IntoOption<SessionSystemPromptOverrideCapabilities>,
4901 ) -> Self {
4902 self.override_ = override_.into_option();
4903 self
4904 }
4905
4906 #[must_use]
4912 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
4913 self.meta = meta.into_option();
4914 self
4915 }
4916}
4917
4918#[cfg(feature = "unstable_session_system_prompt")]
4927#[serde_as]
4928#[skip_serializing_none]
4929#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4930#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4931#[non_exhaustive]
4932pub struct SessionSystemPromptOverrideCapabilities {
4933 #[serde_as(deserialize_as = "DefaultOnError")]
4939 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4940 #[serde(default)]
4941 #[serde(rename = "_meta")]
4942 pub meta: Option<Meta>,
4943}
4944
4945#[cfg(feature = "unstable_session_system_prompt")]
4946impl SessionSystemPromptOverrideCapabilities {
4947 #[must_use]
4949 pub fn new() -> Self {
4950 Self::default()
4951 }
4952
4953 #[must_use]
4959 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
4960 self.meta = meta.into_option();
4961 self
4962 }
4963}
4964
4965#[cfg(feature = "unstable_session_fork")]
4973#[serde_as]
4974#[skip_serializing_none]
4975#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4976#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4977#[non_exhaustive]
4978pub struct SessionForkCapabilities {
4979 #[serde_as(deserialize_as = "DefaultOnError")]
4985 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4986 #[serde(default)]
4987 #[serde(rename = "_meta")]
4988 pub meta: Option<Meta>,
4989}
4990
4991#[cfg(feature = "unstable_session_fork")]
4992impl SessionForkCapabilities {
4993 #[must_use]
4995 pub fn new() -> Self {
4996 Self::default()
4997 }
4998
4999 #[must_use]
5005 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
5006 self.meta = meta.into_option();
5007 self
5008 }
5009}
5010
5011#[serde_as]
5024#[skip_serializing_none]
5025#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
5026#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
5027#[serde(rename_all = "camelCase")]
5028#[non_exhaustive]
5029pub struct PromptCapabilities {
5030 #[serde_as(deserialize_as = "DefaultOnError")]
5035 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
5036 #[serde(default)]
5037 pub image: Option<PromptImageCapabilities>,
5038 #[serde_as(deserialize_as = "DefaultOnError")]
5043 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
5044 #[serde(default)]
5045 pub audio: Option<PromptAudioCapabilities>,
5046 #[serde_as(deserialize_as = "DefaultOnError")]
5054 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
5055 #[serde(default)]
5056 pub embedded_context: Option<PromptEmbeddedContextCapabilities>,
5057 #[serde_as(deserialize_as = "DefaultOnError")]
5063 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
5064 #[serde(default)]
5065 #[serde(rename = "_meta")]
5066 pub meta: Option<Meta>,
5067}
5068
5069impl PromptCapabilities {
5070 #[must_use]
5072 pub fn new() -> Self {
5073 Self::default()
5074 }
5075
5076 #[must_use]
5081 pub fn image(mut self, image: impl IntoOption<PromptImageCapabilities>) -> Self {
5082 self.image = image.into_option();
5083 self
5084 }
5085
5086 #[must_use]
5091 pub fn audio(mut self, audio: impl IntoOption<PromptAudioCapabilities>) -> Self {
5092 self.audio = audio.into_option();
5093 self
5094 }
5095
5096 #[must_use]
5104 pub fn embedded_context(
5105 mut self,
5106 embedded_context: impl IntoOption<PromptEmbeddedContextCapabilities>,
5107 ) -> Self {
5108 self.embedded_context = embedded_context.into_option();
5109 self
5110 }
5111
5112 #[must_use]
5118 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
5119 self.meta = meta.into_option();
5120 self
5121 }
5122}
5123
5124#[serde_as]
5128#[skip_serializing_none]
5129#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
5130#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
5131#[non_exhaustive]
5132pub struct PromptImageCapabilities {
5133 #[serde_as(deserialize_as = "DefaultOnError")]
5139 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
5140 #[serde(default)]
5141 #[serde(rename = "_meta")]
5142 pub meta: Option<Meta>,
5143}
5144
5145impl PromptImageCapabilities {
5146 #[must_use]
5148 pub fn new() -> Self {
5149 Self::default()
5150 }
5151
5152 #[must_use]
5158 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
5159 self.meta = meta.into_option();
5160 self
5161 }
5162}
5163
5164#[serde_as]
5168#[skip_serializing_none]
5169#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
5170#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
5171#[non_exhaustive]
5172pub struct PromptAudioCapabilities {
5173 #[serde_as(deserialize_as = "DefaultOnError")]
5179 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
5180 #[serde(default)]
5181 #[serde(rename = "_meta")]
5182 pub meta: Option<Meta>,
5183}
5184
5185impl PromptAudioCapabilities {
5186 #[must_use]
5188 pub fn new() -> Self {
5189 Self::default()
5190 }
5191
5192 #[must_use]
5198 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
5199 self.meta = meta.into_option();
5200 self
5201 }
5202}
5203
5204#[serde_as]
5208#[skip_serializing_none]
5209#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
5210#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
5211#[non_exhaustive]
5212pub struct PromptEmbeddedContextCapabilities {
5213 #[serde_as(deserialize_as = "DefaultOnError")]
5219 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
5220 #[serde(default)]
5221 #[serde(rename = "_meta")]
5222 pub meta: Option<Meta>,
5223}
5224
5225impl PromptEmbeddedContextCapabilities {
5226 #[must_use]
5228 pub fn new() -> Self {
5229 Self::default()
5230 }
5231
5232 #[must_use]
5238 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
5239 self.meta = meta.into_option();
5240 self
5241 }
5242}
5243
5244#[serde_as]
5246#[skip_serializing_none]
5247#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
5248#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
5249#[serde(rename_all = "camelCase")]
5250#[non_exhaustive]
5251pub struct McpCapabilities {
5252 #[serde_as(deserialize_as = "DefaultOnError")]
5257 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
5258 #[serde(default)]
5259 pub stdio: Option<McpStdioCapabilities>,
5260 #[serde_as(deserialize_as = "DefaultOnError")]
5265 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
5266 #[serde(default)]
5267 pub http: Option<McpHttpCapabilities>,
5268 #[cfg(feature = "unstable_mcp_over_acp")]
5277 #[serde_as(deserialize_as = "DefaultOnError")]
5278 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
5279 #[serde(default)]
5280 pub acp: Option<McpAcpCapabilities>,
5281 #[serde_as(deserialize_as = "DefaultOnError")]
5287 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
5288 #[serde(default)]
5289 #[serde(rename = "_meta")]
5290 pub meta: Option<Meta>,
5291}
5292
5293impl McpCapabilities {
5294 #[must_use]
5296 pub fn new() -> Self {
5297 Self::default()
5298 }
5299
5300 #[must_use]
5305 pub fn stdio(mut self, stdio: impl IntoOption<McpStdioCapabilities>) -> Self {
5306 self.stdio = stdio.into_option();
5307 self
5308 }
5309
5310 #[must_use]
5315 pub fn http(mut self, http: impl IntoOption<McpHttpCapabilities>) -> Self {
5316 self.http = http.into_option();
5317 self
5318 }
5319
5320 #[cfg(feature = "unstable_mcp_over_acp")]
5326 #[must_use]
5330 pub fn acp(mut self, acp: impl IntoOption<McpAcpCapabilities>) -> Self {
5331 self.acp = acp.into_option();
5332 self
5333 }
5334
5335 #[must_use]
5341 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
5342 self.meta = meta.into_option();
5343 self
5344 }
5345}
5346
5347#[serde_as]
5351#[skip_serializing_none]
5352#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
5353#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
5354#[non_exhaustive]
5355pub struct McpStdioCapabilities {
5356 #[serde_as(deserialize_as = "DefaultOnError")]
5362 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
5363 #[serde(default)]
5364 #[serde(rename = "_meta")]
5365 pub meta: Option<Meta>,
5366}
5367
5368impl McpStdioCapabilities {
5369 #[must_use]
5371 pub fn new() -> Self {
5372 Self::default()
5373 }
5374
5375 #[must_use]
5381 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
5382 self.meta = meta.into_option();
5383 self
5384 }
5385}
5386
5387#[serde_as]
5391#[skip_serializing_none]
5392#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
5393#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
5394#[non_exhaustive]
5395pub struct McpHttpCapabilities {
5396 #[serde_as(deserialize_as = "DefaultOnError")]
5402 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
5403 #[serde(default)]
5404 #[serde(rename = "_meta")]
5405 pub meta: Option<Meta>,
5406}
5407
5408impl McpHttpCapabilities {
5409 #[must_use]
5411 pub fn new() -> Self {
5412 Self::default()
5413 }
5414
5415 #[must_use]
5421 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
5422 self.meta = meta.into_option();
5423 self
5424 }
5425}
5426
5427#[cfg(feature = "unstable_mcp_over_acp")]
5435#[serde_as]
5436#[skip_serializing_none]
5437#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
5438#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
5439#[non_exhaustive]
5440pub struct McpAcpCapabilities {
5441 #[serde_as(deserialize_as = "DefaultOnError")]
5447 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
5448 #[serde(default)]
5449 #[serde(rename = "_meta")]
5450 pub meta: Option<Meta>,
5451}
5452
5453#[cfg(feature = "unstable_mcp_over_acp")]
5454impl McpAcpCapabilities {
5455 #[must_use]
5457 pub fn new() -> Self {
5458 Self::default()
5459 }
5460
5461 #[must_use]
5467 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
5468 self.meta = meta.into_option();
5469 self
5470 }
5471}
5472
5473#[serde_as]
5477#[skip_serializing_none]
5478#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
5479#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
5480#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = SESSION_CANCEL_METHOD_NAME)))]
5481#[serde(rename_all = "camelCase")]
5482#[non_exhaustive]
5483pub struct CancelSessionNotification {
5484 pub session_id: SessionId,
5486 #[serde_as(deserialize_as = "DefaultOnError")]
5492 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
5493 #[serde(default)]
5494 #[serde(rename = "_meta")]
5495 pub meta: Option<Meta>,
5496}
5497
5498impl CancelSessionNotification {
5499 #[must_use]
5501 pub fn new(session_id: impl Into<SessionId>) -> Self {
5502 Self {
5503 session_id: session_id.into(),
5504 meta: None,
5505 }
5506 }
5507
5508 #[must_use]
5514 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
5515 self.meta = meta.into_option();
5516 self
5517 }
5518}
5519
5520#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
5526#[non_exhaustive]
5527pub struct AgentMethodNames {
5528 pub initialize: &'static str,
5530 pub auth_login: &'static str,
5532 #[cfg(feature = "unstable_llm_providers")]
5534 pub providers_list: &'static str,
5535 #[cfg(feature = "unstable_llm_providers")]
5537 pub providers_set: &'static str,
5538 #[cfg(feature = "unstable_llm_providers")]
5540 pub providers_disable: &'static str,
5541 pub session_new: &'static str,
5543 pub session_set_config_option: &'static str,
5545 pub session_prompt: &'static str,
5547 pub session_cancel: &'static str,
5549 #[cfg(feature = "unstable_mcp_over_acp")]
5551 pub mcp_message: &'static str,
5552 pub session_list: &'static str,
5554 pub session_delete: &'static str,
5556 #[cfg(feature = "unstable_session_fork")]
5558 pub session_fork: &'static str,
5559 pub session_resume: &'static str,
5561 pub session_close: &'static str,
5563 pub auth_logout: &'static str,
5565}
5566
5567pub const AGENT_METHOD_NAMES: AgentMethodNames = AgentMethodNames {
5569 initialize: INITIALIZE_METHOD_NAME,
5570 auth_login: AUTH_LOGIN_METHOD_NAME,
5571 #[cfg(feature = "unstable_llm_providers")]
5572 providers_list: PROVIDERS_LIST_METHOD_NAME,
5573 #[cfg(feature = "unstable_llm_providers")]
5574 providers_set: PROVIDERS_SET_METHOD_NAME,
5575 #[cfg(feature = "unstable_llm_providers")]
5576 providers_disable: PROVIDERS_DISABLE_METHOD_NAME,
5577 session_new: SESSION_NEW_METHOD_NAME,
5578 session_set_config_option: SESSION_SET_CONFIG_OPTION_METHOD_NAME,
5579 session_prompt: SESSION_PROMPT_METHOD_NAME,
5580 session_cancel: SESSION_CANCEL_METHOD_NAME,
5581 #[cfg(feature = "unstable_mcp_over_acp")]
5582 mcp_message: MCP_MESSAGE_METHOD_NAME,
5583 session_list: SESSION_LIST_METHOD_NAME,
5584 session_delete: SESSION_DELETE_METHOD_NAME,
5585 #[cfg(feature = "unstable_session_fork")]
5586 session_fork: SESSION_FORK_METHOD_NAME,
5587 session_resume: SESSION_RESUME_METHOD_NAME,
5588 session_close: SESSION_CLOSE_METHOD_NAME,
5589 auth_logout: AUTH_LOGOUT_METHOD_NAME,
5590};
5591
5592pub(crate) const INITIALIZE_METHOD_NAME: &str = "initialize";
5594pub(crate) const AUTH_LOGIN_METHOD_NAME: &str = "auth/login";
5596#[cfg(feature = "unstable_llm_providers")]
5598pub(crate) const PROVIDERS_LIST_METHOD_NAME: &str = "providers/list";
5599#[cfg(feature = "unstable_llm_providers")]
5601pub(crate) const PROVIDERS_SET_METHOD_NAME: &str = "providers/set";
5602#[cfg(feature = "unstable_llm_providers")]
5604pub(crate) const PROVIDERS_DISABLE_METHOD_NAME: &str = "providers/disable";
5605pub(crate) const SESSION_NEW_METHOD_NAME: &str = "session/new";
5607pub(crate) const SESSION_SET_CONFIG_OPTION_METHOD_NAME: &str = "session/set_config_option";
5609pub(crate) const SESSION_PROMPT_METHOD_NAME: &str = "session/prompt";
5611pub(crate) const SESSION_CANCEL_METHOD_NAME: &str = "session/cancel";
5613pub(crate) const SESSION_LIST_METHOD_NAME: &str = "session/list";
5615pub(crate) const SESSION_DELETE_METHOD_NAME: &str = "session/delete";
5617#[cfg(feature = "unstable_session_fork")]
5619pub(crate) const SESSION_FORK_METHOD_NAME: &str = "session/fork";
5620pub(crate) const SESSION_RESUME_METHOD_NAME: &str = "session/resume";
5622pub(crate) const SESSION_CLOSE_METHOD_NAME: &str = "session/close";
5624pub(crate) const AUTH_LOGOUT_METHOD_NAME: &str = "auth/logout";
5626
5627#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
5634#[derive(Clone, Debug, Serialize, Deserialize)]
5635#[serde(untagged)]
5636#[cfg_attr(feature = "schemars", schemars(inline))]
5637#[non_exhaustive]
5638pub enum ClientRequest {
5639 InitializeRequest(Box<InitializeRequest>),
5653 LoginAuthRequest(Box<LoginAuthRequest>),
5668 #[cfg(feature = "unstable_llm_providers")]
5674 ListProvidersRequest(Box<ListProvidersRequest>),
5675 #[cfg(feature = "unstable_llm_providers")]
5681 SetProviderRequest(Box<SetProviderRequest>),
5682 #[cfg(feature = "unstable_llm_providers")]
5688 DisableProviderRequest(Box<DisableProviderRequest>),
5689 LogoutAuthRequest(Box<LogoutAuthRequest>),
5699 NewSessionRequest(Box<NewSessionRequest>),
5712 ListSessionsRequest(Box<ListSessionsRequest>),
5716 DeleteSessionRequest(Box<DeleteSessionRequest>),
5720 #[cfg(feature = "unstable_session_fork")]
5721 ForkSessionRequest(Box<ForkSessionRequest>),
5737 ResumeSessionRequest(Box<ResumeSessionRequest>),
5743 CloseSessionRequest(Box<CloseSessionRequest>),
5748 SetSessionConfigOptionRequest(Box<SetSessionConfigOptionRequest>),
5750 PromptRequest(Box<PromptRequest>),
5772 ExtMethodRequest(Box<ExtRequest>),
5779}
5780
5781impl ClientRequest {
5782 #[must_use]
5784 pub fn method(&self) -> &str {
5785 match self {
5786 Self::InitializeRequest(_) => AGENT_METHOD_NAMES.initialize,
5787 Self::LoginAuthRequest(_) => AGENT_METHOD_NAMES.auth_login,
5788 #[cfg(feature = "unstable_llm_providers")]
5789 Self::ListProvidersRequest(_) => AGENT_METHOD_NAMES.providers_list,
5790 #[cfg(feature = "unstable_llm_providers")]
5791 Self::SetProviderRequest(_) => AGENT_METHOD_NAMES.providers_set,
5792 #[cfg(feature = "unstable_llm_providers")]
5793 Self::DisableProviderRequest(_) => AGENT_METHOD_NAMES.providers_disable,
5794 Self::LogoutAuthRequest(_) => AGENT_METHOD_NAMES.auth_logout,
5795 Self::NewSessionRequest(_) => AGENT_METHOD_NAMES.session_new,
5796 Self::ListSessionsRequest(_) => AGENT_METHOD_NAMES.session_list,
5797 Self::DeleteSessionRequest(_) => AGENT_METHOD_NAMES.session_delete,
5798 #[cfg(feature = "unstable_session_fork")]
5799 Self::ForkSessionRequest(_) => AGENT_METHOD_NAMES.session_fork,
5800 Self::ResumeSessionRequest(_) => AGENT_METHOD_NAMES.session_resume,
5801 Self::CloseSessionRequest(_) => AGENT_METHOD_NAMES.session_close,
5802 Self::SetSessionConfigOptionRequest(_) => AGENT_METHOD_NAMES.session_set_config_option,
5803 Self::PromptRequest(_) => AGENT_METHOD_NAMES.session_prompt,
5804 Self::ExtMethodRequest(ext_request) => &ext_request.method,
5805 }
5806 }
5807}
5808
5809#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
5816#[derive(Clone, Debug, Serialize, Deserialize)]
5817#[serde(untagged)]
5818#[cfg_attr(feature = "schemars", schemars(inline))]
5819#[non_exhaustive]
5820pub enum AgentResponse {
5821 InitializeResponse(Box<InitializeResponse>),
5823 LoginAuthResponse(#[serde(default)] Box<LoginAuthResponse>),
5825 #[cfg(feature = "unstable_llm_providers")]
5827 ListProvidersResponse(Box<ListProvidersResponse>),
5828 #[cfg(feature = "unstable_llm_providers")]
5830 SetProviderResponse(#[serde(default)] Box<SetProviderResponse>),
5831 #[cfg(feature = "unstable_llm_providers")]
5833 DisableProviderResponse(#[serde(default)] Box<DisableProviderResponse>),
5834 LogoutAuthResponse(#[serde(default)] Box<LogoutAuthResponse>),
5836 NewSessionResponse(Box<NewSessionResponse>),
5838 ListSessionsResponse(Box<ListSessionsResponse>),
5840 DeleteSessionResponse(#[serde(default)] Box<DeleteSessionResponse>),
5842 #[cfg(feature = "unstable_session_fork")]
5844 ForkSessionResponse(Box<ForkSessionResponse>),
5845 ResumeSessionResponse(#[serde(default)] Box<ResumeSessionResponse>),
5847 CloseSessionResponse(#[serde(default)] Box<CloseSessionResponse>),
5849 SetSessionConfigOptionResponse(Box<SetSessionConfigOptionResponse>),
5851 PromptResponse(Box<PromptResponse>),
5853 ExtMethodResponse(Box<ExtResponse>),
5855}
5856
5857#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
5864#[derive(Clone, Debug, Serialize, Deserialize)]
5865#[serde(untagged)]
5866#[cfg_attr(feature = "schemars", schemars(inline))]
5867#[non_exhaustive]
5868pub enum ClientNotification {
5869 CancelSessionNotification(Box<CancelSessionNotification>),
5894 #[cfg(feature = "unstable_mcp_over_acp")]
5900 MessageMcpNotification(Box<MessageMcpNotification>),
5901 ExtNotification(Box<ExtNotification>),
5908}
5909
5910impl ClientNotification {
5911 #[must_use]
5913 pub fn method(&self) -> &str {
5914 match self {
5915 Self::CancelSessionNotification(_) => AGENT_METHOD_NAMES.session_cancel,
5916 #[cfg(feature = "unstable_mcp_over_acp")]
5917 Self::MessageMcpNotification(_) => AGENT_METHOD_NAMES.mcp_message,
5918 Self::ExtNotification(ext_notification) => &ext_notification.method,
5919 }
5920 }
5921}
5922
5923#[cfg(test)]
5924mod test_serialization {
5925 use std::path::PathBuf;
5926
5927 use super::*;
5928 use serde_json::json;
5929
5930 fn test_meta() -> Meta {
5931 json!({ "source": "test" }).as_object().unwrap().clone()
5932 }
5933
5934 #[test]
5935 fn list_sessions_cursor_and_session_directories_are_strict() {
5936 for cursor in [json!(null), json!("next")] {
5937 let response = json!({ "sessions": [], "nextCursor": cursor });
5938 serde_json::from_value::<ListSessionsResponse>(response).unwrap();
5939 }
5940 let malformed = json!({ "sessions": [], "nextCursor": 42 });
5941 assert!(serde_json::from_value::<ListSessionsResponse>(malformed).is_err());
5942
5943 let session = |dirs: serde_json::Value| json!({ "sessionId": "s", "cwd": "/w", "additionalDirectories": dirs });
5944 let info: SessionInfo = serde_json::from_value(session(json!(null))).unwrap();
5945 assert_eq!(info.additional_directories, Vec::<AbsolutePath>::new());
5946 for bad in [json!(["/a", 42]), json!("/a")] {
5947 assert!(serde_json::from_value::<SessionInfo>(session(bad)).is_err());
5948 }
5949 let response = json!({ "sessions": [session(json!(["/a", 42]))] });
5951 let response: ListSessionsResponse = serde_json::from_value(response).unwrap();
5952 assert_eq!(response.sessions, Vec::<SessionInfo>::new());
5953 }
5954
5955 #[test]
5956 fn other_fallbacks_preserve_optional_fields_verbatim() {
5957 let valid = json!({ "type": "_custom", "methodId": "m", "name": "M",
5958 "description": "d", "_meta": { "k": 1 } });
5959 let method: AuthMethod = serde_json::from_value(valid.clone()).unwrap();
5960 assert_eq!(method.description(), Some("d"));
5961 assert_eq!(
5962 method.meta(),
5963 Some(&json!({ "k": 1 }).as_object().unwrap().clone())
5964 );
5965 assert_eq!(serde_json::to_value(&method).unwrap(), valid);
5966
5967 let malformed = json!({ "type": "_custom", "methodId": "m", "name": "M",
5969 "description": 42, "_meta": "bad", "extra": true });
5970 let method: AuthMethod = serde_json::from_value(malformed.clone()).unwrap();
5971 assert_eq!(method.description(), None);
5972 assert_eq!(method.meta(), None);
5973 assert_eq!(serde_json::to_value(&method).unwrap(), malformed);
5974
5975 let replay = json!({ "type": "_custom", "_meta": "bad", "cursor": 3 });
5976 let decoded: ReplayFrom = serde_json::from_value(replay.clone()).unwrap();
5977 assert_eq!(serde_json::to_value(&decoded).unwrap(), replay);
5978 }
5979
5980 fn serialized_meta_key_count(value: &impl serde::Serialize) -> usize {
5981 serde_json::to_string(value)
5982 .unwrap()
5983 .matches("\"_meta\"")
5984 .count()
5985 }
5986
5987 #[test]
5988 fn prompt_response_without_metadata_round_trips() {
5989 let response = PromptResponse::new(MessageId::new("message-1"));
5990 let serialized = serde_json::to_value(&response).unwrap();
5991 assert_eq!(serialized, json!({ "messageId": "message-1" }));
5992 assert_eq!(
5993 serde_json::from_value::<PromptResponse>(serialized).unwrap(),
5994 response
5995 );
5996 assert_eq!(
5997 serde_json::from_value::<PromptResponse>(json!({
5998 "messageId": "message-1",
5999 "_meta": null
6000 }))
6001 .unwrap(),
6002 response
6003 );
6004 }
6005
6006 #[test]
6007 fn prompt_response_requires_message_id_and_preserves_metadata_tolerance() {
6008 let response = PromptResponse::new("message-1").meta(test_meta());
6009 let serialized = serde_json::to_value(&response).unwrap();
6010 assert_eq!(
6011 serialized,
6012 json!({
6013 "messageId": "message-1",
6014 "_meta": { "source": "test" }
6015 })
6016 );
6017
6018 let deserialized: PromptResponse = serde_json::from_value(serialized).unwrap();
6019 assert_eq!(deserialized.message_id, MessageId::new("message-1"));
6020 assert_eq!(deserialized.meta, Some(test_meta()));
6021
6022 let malformed_meta: PromptResponse = serde_json::from_value(json!({
6023 "messageId": "message-2",
6024 "_meta": false
6025 }))
6026 .unwrap();
6027 assert_eq!(malformed_meta.message_id, MessageId::new("message-2"));
6028 assert_eq!(malformed_meta.meta, None);
6029
6030 for invalid in [
6031 json!({}),
6032 json!({ "messageId": null }),
6033 json!({ "messageId": 1 }),
6034 json!({ "messageId": false }),
6035 json!({ "messageId": {} }),
6036 json!({ "messageId": [] }),
6037 ] {
6038 assert!(
6039 serde_json::from_value::<PromptResponse>(invalid).is_err(),
6040 "missing, null, and non-string message IDs must be rejected"
6041 );
6042 }
6043 }
6044
6045 #[cfg(feature = "schemars")]
6046 #[test]
6047 fn prompt_response_schema_requires_non_null_string_message_id_reference() {
6048 let schema = serde_json::to_value(schemars::schema_for!(PromptResponse)).unwrap();
6049
6050 assert_eq!(schema["required"], json!(["messageId"]));
6051 assert_eq!(
6052 schema["properties"]["messageId"]["$ref"],
6053 "#/$defs/MessageId"
6054 );
6055 assert_eq!(schema["$defs"]["MessageId"]["type"], "string");
6056 }
6057
6058 #[test]
6059 fn test_initialize_capabilities_default_on_malformed_values() {
6060 let request: InitializeRequest = serde_json::from_value(json!({
6061 "protocolVersion": 2,
6062 "capabilities": false,
6063 "info": {
6064 "name": "client",
6065 "version": "1.0.0"
6066 }
6067 }))
6068 .unwrap();
6069 assert_eq!(request.capabilities, ClientCapabilities::default());
6070
6071 let response: InitializeResponse = serde_json::from_value(json!({
6072 "protocolVersion": 2,
6073 "capabilities": false,
6074 "info": {
6075 "name": "agent",
6076 "version": "1.0.0"
6077 }
6078 }))
6079 .unwrap();
6080 assert_eq!(response.capabilities, AgentCapabilities::default());
6081 }
6082
6083 #[test]
6084 fn test_agent_capabilities_default_on_malformed_values() {
6085 let capabilities: AgentCapabilities = serde_json::from_value(json!({
6086 "session": false,
6087 "auth": false
6088 }))
6089 .unwrap();
6090
6091 assert!(capabilities.session.is_none());
6092 assert_eq!(capabilities.auth, None);
6093 }
6094
6095 #[test]
6096 fn test_mcp_server_stdio_serialization() {
6097 let server = McpServer::Stdio(
6098 McpServerStdio::new("test-server", "/usr/bin/server")
6099 .args(vec!["--port".to_string(), "3000".to_string()])
6100 .env(vec![EnvVariable::new("API_KEY", "secret123")]),
6101 );
6102
6103 let json = serde_json::to_value(&server).unwrap();
6104 assert_eq!(
6105 json,
6106 json!({
6107 "type": "stdio",
6108 "name": "test-server",
6109 "command": "/usr/bin/server",
6110 "args": ["--port", "3000"],
6111 "env": [
6112 {
6113 "name": "API_KEY",
6114 "value": "secret123"
6115 }
6116 ]
6117 })
6118 );
6119
6120 let deserialized: McpServer = serde_json::from_value(json).unwrap();
6121 match deserialized {
6122 McpServer::Stdio(McpServerStdio {
6123 name,
6124 command,
6125 args,
6126 env,
6127 meta: _,
6128 }) => {
6129 assert_eq!(name, "test-server");
6130 assert_eq!(command, AbsolutePath::new("/usr/bin/server"));
6131 assert_eq!(args, vec!["--port", "3000"]);
6132 assert_eq!(env.len(), 1);
6133 assert_eq!(env[0].name, "API_KEY");
6134 assert_eq!(env[0].value, "secret123");
6135 }
6136 _ => panic!("Expected Stdio variant"),
6137 }
6138 }
6139
6140 #[test]
6141 fn test_mcp_server_empty_arrays_are_optional() {
6142 let stdio = McpServer::Stdio(McpServerStdio::new("test-server", "/usr/bin/server"));
6143 assert_eq!(
6144 serde_json::to_value(&stdio).unwrap(),
6145 json!({
6146 "type": "stdio",
6147 "name": "test-server",
6148 "command": "/usr/bin/server"
6149 })
6150 );
6151
6152 let McpServer::Stdio(McpServerStdio { args, env, .. }) =
6153 serde_json::from_value::<McpServer>(json!({
6154 "type": "stdio",
6155 "name": "test-server",
6156 "command": "/usr/bin/server"
6157 }))
6158 .unwrap()
6159 else {
6160 panic!("Expected Stdio variant");
6161 };
6162 assert_eq!(args, Vec::<String>::new());
6163 assert_eq!(env, []);
6164
6165 let http = McpServer::Http(McpServerHttp::new("http-server", "https://api.example.com"));
6166 assert_eq!(
6167 serde_json::to_value(&http).unwrap(),
6168 json!({
6169 "type": "http",
6170 "name": "http-server",
6171 "url": "https://api.example.com"
6172 })
6173 );
6174
6175 let McpServer::Http(McpServerHttp { headers, .. }) =
6176 serde_json::from_value::<McpServer>(json!({
6177 "type": "http",
6178 "name": "http-server",
6179 "url": "https://api.example.com"
6180 }))
6181 .unwrap()
6182 else {
6183 panic!("Expected Http variant");
6184 };
6185 assert_eq!(headers, []);
6186 }
6187
6188 #[test]
6189 fn test_mcp_server_unknown_transport_serialization() {
6190 let json = json!({
6191 "type": "websocket",
6192 "name": "future-server",
6193 "url": "wss://example.com/mcp",
6194 "protocolVersion": "2026-01-01"
6195 });
6196
6197 let deserialized: McpServer = serde_json::from_value(json.clone()).unwrap();
6198 let McpServer::Other(OtherMcpServer { type_, fields }) = &deserialized else {
6199 panic!("Expected Other variant");
6200 };
6201
6202 assert_eq!(type_, "websocket");
6203 assert_eq!(fields["name"], "future-server");
6204 assert_eq!(fields["url"], "wss://example.com/mcp");
6205 assert_eq!(fields["protocolVersion"], "2026-01-01");
6206 assert_eq!(serde_json::to_value(&deserialized).unwrap(), json);
6207 }
6208
6209 #[test]
6210 fn test_mcp_server_stdio_requires_type() {
6211 let result = serde_json::from_value::<McpServer>(json!({
6212 "name": "test-server",
6213 "command": "/usr/bin/server",
6214 "args": [],
6215 "env": []
6216 }));
6217
6218 assert!(result.is_err());
6219 }
6220
6221 #[test]
6222 fn test_mcp_server_unknown_does_not_hide_malformed_known_transport() {
6223 let result = serde_json::from_value::<McpServer>(json!({
6224 "type": "stdio",
6225 "name": "test-server",
6226 "args": [],
6227 "env": []
6228 }));
6229
6230 assert!(result.is_err());
6231 }
6232
6233 #[test]
6234 fn test_mcp_server_http_serialization() {
6235 let server = McpServer::Http(
6236 McpServerHttp::new("http-server", "https://api.example.com").headers(vec![
6237 HttpHeader::new("Authorization", "Bearer token123"),
6238 HttpHeader::new("Content-Type", "application/json"),
6239 ]),
6240 );
6241
6242 let json = serde_json::to_value(&server).unwrap();
6243 assert_eq!(
6244 json,
6245 json!({
6246 "type": "http",
6247 "name": "http-server",
6248 "url": "https://api.example.com",
6249 "headers": [
6250 {
6251 "name": "Authorization",
6252 "value": "Bearer token123"
6253 },
6254 {
6255 "name": "Content-Type",
6256 "value": "application/json"
6257 }
6258 ]
6259 })
6260 );
6261
6262 let deserialized: McpServer = serde_json::from_value(json).unwrap();
6263 match deserialized {
6264 McpServer::Http(McpServerHttp {
6265 name,
6266 url,
6267 headers,
6268 meta: _,
6269 }) => {
6270 assert_eq!(name, "http-server");
6271 assert_eq!(url, "https://api.example.com");
6272 assert_eq!(headers.len(), 2);
6273 assert_eq!(headers[0].name, "Authorization");
6274 assert_eq!(headers[0].value, "Bearer token123");
6275 assert_eq!(headers[1].name, "Content-Type");
6276 assert_eq!(headers[1].value, "application/json");
6277 }
6278 _ => panic!("Expected Http variant"),
6279 }
6280 }
6281
6282 #[cfg(feature = "schemars")]
6283 #[test]
6284 fn mcp_server_http_schema_marks_url_as_uri() {
6285 let schema = serde_json::to_value(schemars::schema_for!(McpServerHttp)).unwrap();
6286
6287 assert_eq!(schema["properties"]["url"]["format"], "uri");
6288 }
6289
6290 #[cfg(feature = "unstable_mcp_over_acp")]
6291 #[test]
6292 fn test_client_mcp_message_method_names() {
6293 use serde_json::json;
6294
6295 assert_eq!(AGENT_METHOD_NAMES.mcp_message, "mcp/message");
6296
6297 let notification =
6298 MessageMcpNotification::new("server-1", "req-1", "notifications/progress");
6299 assert_eq!(
6300 ClientNotification::MessageMcpNotification(Box::new(notification.clone())).method(),
6301 "mcp/message"
6302 );
6303 assert_eq!(
6304 serde_json::to_value(notification).unwrap(),
6305 json!({
6306 "serverId": "server-1",
6307 "requestId": "req-1",
6308 "method": "notifications/progress"
6309 })
6310 );
6311 let notification: MessageMcpNotification = serde_json::from_value(json!({
6312 "serverId": "server-1", "requestId": "req-1", "method": "notifications/progress",
6313 "params": null, "_meta": null
6314 }))
6315 .unwrap();
6316 assert_eq!(notification.params, None);
6317 assert_eq!(notification.meta, None);
6318 for key in ["serverId", "requestId", "method"] {
6319 let mut value = json!({"serverId":"server-1", "requestId":"req-1", "method":"notifications/progress"});
6320 value.as_object_mut().unwrap().remove(key);
6321 assert!(serde_json::from_value::<MessageMcpNotification>(value).is_err());
6322 }
6323 }
6324
6325 #[test]
6326 fn test_auth_method_names() {
6327 assert_eq!(AGENT_METHOD_NAMES.auth_login, "auth/login");
6328 assert_eq!(AGENT_METHOD_NAMES.auth_logout, "auth/logout");
6329
6330 assert_eq!(
6331 ClientRequest::LoginAuthRequest(Box::new(LoginAuthRequest::new("agent-login")))
6332 .method(),
6333 "auth/login"
6334 );
6335 assert_eq!(
6336 ClientRequest::LogoutAuthRequest(Box::new(LogoutAuthRequest::new())).method(),
6337 "auth/logout"
6338 );
6339 }
6340
6341 #[test]
6342 fn test_session_config_option_category_known_variants() {
6343 assert_eq!(
6345 serde_json::to_value(&SessionConfigOptionCategory::Mode).unwrap(),
6346 json!("mode")
6347 );
6348 assert_eq!(
6349 serde_json::to_value(&SessionConfigOptionCategory::Model).unwrap(),
6350 json!("model")
6351 );
6352 assert_eq!(
6353 serde_json::to_value(&SessionConfigOptionCategory::ModelConfig).unwrap(),
6354 json!("model_config")
6355 );
6356 assert_eq!(
6357 serde_json::to_value(&SessionConfigOptionCategory::ThoughtLevel).unwrap(),
6358 json!("thought_level")
6359 );
6360
6361 assert_eq!(
6363 serde_json::from_str::<SessionConfigOptionCategory>("\"mode\"").unwrap(),
6364 SessionConfigOptionCategory::Mode
6365 );
6366 assert_eq!(
6367 serde_json::from_str::<SessionConfigOptionCategory>("\"model\"").unwrap(),
6368 SessionConfigOptionCategory::Model
6369 );
6370 assert_eq!(
6371 serde_json::from_str::<SessionConfigOptionCategory>("\"model_config\"").unwrap(),
6372 SessionConfigOptionCategory::ModelConfig
6373 );
6374 assert_eq!(
6375 serde_json::from_str::<SessionConfigOptionCategory>("\"thought_level\"").unwrap(),
6376 SessionConfigOptionCategory::ThoughtLevel
6377 );
6378 }
6379
6380 #[test]
6381 fn test_session_config_option_category_unknown_variants() {
6382 let unknown: SessionConfigOptionCategory =
6384 serde_json::from_str("\"some_future_category\"").unwrap();
6385 assert_eq!(
6386 unknown,
6387 SessionConfigOptionCategory::Other("some_future_category".to_string())
6388 );
6389
6390 let json = serde_json::to_value(&unknown).unwrap();
6392 assert_eq!(json, json!("some_future_category"));
6393 }
6394
6395 #[test]
6396 fn test_session_config_option_category_custom_categories() {
6397 let custom: SessionConfigOptionCategory =
6399 serde_json::from_str("\"_my_custom_category\"").unwrap();
6400 assert_eq!(
6401 custom,
6402 SessionConfigOptionCategory::Other("_my_custom_category".to_string())
6403 );
6404
6405 let json = serde_json::to_value(&custom).unwrap();
6407 assert_eq!(json, json!("_my_custom_category"));
6408
6409 let deserialized: SessionConfigOptionCategory = serde_json::from_value(json).unwrap();
6411 assert_eq!(
6412 deserialized,
6413 SessionConfigOptionCategory::Other("_my_custom_category".to_string()),
6414 );
6415 }
6416
6417 fn test_config_option() -> SessionConfigOption {
6418 SessionConfigOption::select(
6419 "mode",
6420 "Mode",
6421 "ask",
6422 vec![SessionConfigSelectOption::new("ask", "Ask")],
6423 )
6424 }
6425
6426 #[test]
6427 fn test_session_response_initial_state_default_empty_and_skip_serializing() {
6428 assert_eq!(
6429 serde_json::to_value(NewSessionResponse::new("sess")).unwrap(),
6430 json!({ "sessionId": "sess" })
6431 );
6432 assert_eq!(
6433 serde_json::to_value(ResumeSessionResponse::new()).unwrap(),
6434 json!({})
6435 );
6436 #[cfg(feature = "unstable_session_fork")]
6437 assert_eq!(
6438 serde_json::to_value(ForkSessionResponse::new("fork")).unwrap(),
6439 json!({ "sessionId": "fork" })
6440 );
6441
6442 let json = serde_json::to_value(
6443 NewSessionResponse::new("sess").config_options(vec![test_config_option()]),
6444 )
6445 .unwrap();
6446 assert_eq!(json["configOptions"].as_array().unwrap().len(), 1);
6447 }
6448
6449 #[test]
6450 fn test_session_response_config_options_deserialize_missing_null_and_invalid() {
6451 let missing: NewSessionResponse =
6452 serde_json::from_value(json!({ "sessionId": "sess" })).unwrap();
6453 assert_eq!(missing.config_options, []);
6454
6455 let null: NewSessionResponse = serde_json::from_value(json!({
6456 "sessionId": "sess",
6457 "configOptions": null
6458 }))
6459 .unwrap();
6460 assert_eq!(null.config_options, []);
6461
6462 let wrong_shape: NewSessionResponse = serde_json::from_value(json!({
6463 "sessionId": "sess",
6464 "configOptions": "oops"
6465 }))
6466 .unwrap();
6467 assert_eq!(wrong_shape.config_options, []);
6468
6469 let valid_option = serde_json::to_value(test_config_option()).unwrap();
6470 let mixed: NewSessionResponse = serde_json::from_value(json!({
6471 "sessionId": "sess",
6472 "configOptions": ["oops", valid_option]
6473 }))
6474 .unwrap();
6475 assert_eq!(mixed.config_options.len(), 1);
6476
6477 let resume: ResumeSessionResponse = serde_json::from_value(json!({})).unwrap();
6478 assert_eq!(resume.config_options, []);
6479 #[cfg(feature = "unstable_session_fork")]
6480 {
6481 let fork: ForkSessionResponse =
6482 serde_json::from_value(json!({ "sessionId": "fork" })).unwrap();
6483 assert_eq!(fork.config_options, []);
6484 }
6485 }
6486
6487 #[test]
6488 fn test_session_response_available_commands_roundtrip_with_config_options() {
6489 use super::super::{AvailableCommandInput, Command, TextCommandInput};
6490
6491 let commands = vec![
6492 AvailableCommand::Command(
6493 Command::new("web", "Search the web")
6494 .input(AvailableCommandInput::Text(TextCommandInput::new("Query"))),
6495 ),
6496 AvailableCommand::Command(Command::new("test", "Run project tests")),
6497 ];
6498 let mut expected = json!({
6499 "sessionId": "sess",
6500 "configOptions": [test_config_option()],
6501 "availableCommands": [
6502 {
6503 "type": "command",
6504 "name": "web",
6505 "description": "Search the web",
6506 "input": { "type": "text", "hint": "Query" }
6507 },
6508 { "type": "command", "name": "test", "description": "Run project tests" }
6509 ]
6510 });
6511
6512 let new = NewSessionResponse::new("sess")
6513 .config_options(vec![test_config_option()])
6514 .available_commands(commands.clone());
6515 assert_eq!(serde_json::to_value(&new).unwrap(), expected);
6516 assert_eq!(
6517 serde_json::from_value::<NewSessionResponse>(expected.clone()).unwrap(),
6518 new
6519 );
6520
6521 expected.as_object_mut().unwrap().remove("sessionId");
6522 let resume = ResumeSessionResponse::new()
6523 .config_options(vec![test_config_option()])
6524 .available_commands(commands.clone());
6525 assert_eq!(serde_json::to_value(&resume).unwrap(), expected);
6526 assert_eq!(
6527 serde_json::from_value::<ResumeSessionResponse>(expected.clone()).unwrap(),
6528 resume
6529 );
6530
6531 #[cfg(feature = "unstable_session_fork")]
6532 {
6533 expected["sessionId"] = json!("fork");
6534 let fork = ForkSessionResponse::new("fork")
6535 .config_options(vec![test_config_option()])
6536 .available_commands(commands);
6537 assert_eq!(serde_json::to_value(&fork).unwrap(), expected);
6538 assert_eq!(
6539 serde_json::from_value::<ForkSessionResponse>(expected).unwrap(),
6540 fork
6541 );
6542 }
6543 }
6544
6545 #[test]
6546 fn test_session_response_available_commands_deserialize_missing_null_and_invalid() {
6547 let command =
6548 AvailableCommand::Command(super::super::Command::new("test", "Run project tests"));
6549 let config_options = vec![test_config_option()];
6550
6551 for (commands, expected) in [
6552 (None, vec![]),
6553 (Some(json!(null)), vec![]),
6554 (Some(json!([])), vec![]),
6555 (Some(json!("oops")), vec![]),
6556 (Some(json!({})), vec![]),
6557 (Some(json!([null, {}, { "name": "invalid" }])), vec![]),
6558 (
6559 Some(json!([{ "name": "test", "description": "Run project tests" }])),
6560 vec![],
6561 ),
6562 (
6563 Some(json!([null, command, { "name": "invalid" }])),
6564 vec![command.clone()],
6565 ),
6566 ] {
6567 let mut value = json!({
6568 "sessionId": "sess",
6569 "configOptions": config_options
6570 });
6571 if let Some(commands) = commands {
6572 value["availableCommands"] = commands;
6573 }
6574
6575 let new: NewSessionResponse = serde_json::from_value(value.clone()).unwrap();
6576 assert_eq!(new.available_commands, expected);
6577 assert_eq!(new.config_options, config_options);
6578
6579 #[cfg(feature = "unstable_session_fork")]
6580 {
6581 let fork: ForkSessionResponse = serde_json::from_value(value.clone()).unwrap();
6582 assert_eq!(fork.available_commands, expected);
6583 assert_eq!(fork.config_options, config_options);
6584 }
6585
6586 value.as_object_mut().unwrap().remove("sessionId");
6587 let resume: ResumeSessionResponse = serde_json::from_value(value).unwrap();
6588 assert_eq!(resume.available_commands, expected);
6589 assert_eq!(resume.config_options, config_options);
6590 }
6591
6592 let resume: ResumeSessionResponse = serde_json::from_value(json!(null)).unwrap();
6593 assert_eq!(resume.available_commands, []);
6594 }
6595
6596 #[test]
6597 fn test_resume_session_replay_from_serialization() {
6598 assert_eq!(
6599 serde_json::to_value(ResumeSessionRequest::new(
6600 "sess_abc123",
6601 "/home/user/project"
6602 ))
6603 .unwrap(),
6604 json!({
6605 "sessionId": "sess_abc123",
6606 "cwd": "/home/user/project"
6607 })
6608 );
6609 assert_eq!(
6610 serde_json::to_value(
6611 ResumeSessionRequest::new("sess_abc123", "/home/user/project")
6612 .replay_from(ReplayFrom::from(ReplayFromStart::new()))
6613 )
6614 .unwrap(),
6615 json!({
6616 "sessionId": "sess_abc123",
6617 "cwd": "/home/user/project",
6618 "replayFrom": {
6619 "type": "start"
6620 }
6621 })
6622 );
6623
6624 let replay: ResumeSessionRequest = serde_json::from_value(json!({
6625 "sessionId": "sess_abc123",
6626 "cwd": "/home/user/project",
6627 "replayFrom": {
6628 "type": "start"
6629 }
6630 }))
6631 .unwrap();
6632 assert!(matches!(replay.replay_from, Some(ReplayFrom::Start(_))));
6633
6634 let none: ResumeSessionRequest = serde_json::from_value(json!({
6635 "sessionId": "sess_abc123",
6636 "cwd": "/home/user/project",
6637 "replayFrom": null
6638 }))
6639 .unwrap();
6640 assert!(none.replay_from.is_none());
6641 }
6642
6643 #[test]
6644 fn test_auth_method_agent_serialization() {
6645 let method = AuthMethod::Agent(AuthMethodAgent::new("default-auth", "Default Auth"));
6646
6647 let json = serde_json::to_value(&method).unwrap();
6648 assert_eq!(
6649 json,
6650 json!({
6651 "methodId": "default-auth",
6652 "name": "Default Auth",
6653 "type": "agent"
6654 })
6655 );
6656 assert!(!json.as_object().unwrap().contains_key("description"));
6658
6659 let deserialized: AuthMethod = serde_json::from_value(json).unwrap();
6660 match deserialized {
6661 AuthMethod::Agent(AuthMethodAgent {
6662 method_id, name, ..
6663 }) => {
6664 assert_eq!(method_id.0.as_ref(), "default-auth");
6665 assert_eq!(name, "Default Auth");
6666 }
6667 _ => panic!("Expected Agent variant"),
6668 }
6669 }
6670
6671 #[test]
6672 fn test_auth_method_agent_deserialization() {
6673 let json = json!({
6674 "methodId": "agent-auth",
6675 "name": "Agent Auth",
6676 "type": "agent"
6677 });
6678
6679 let deserialized: AuthMethod = serde_json::from_value(json).unwrap();
6680 assert!(matches!(deserialized, AuthMethod::Agent(_)));
6681 }
6682
6683 #[test]
6684 fn test_auth_method_agent_requires_type() {
6685 assert!(
6686 serde_json::from_value::<AuthMethod>(json!({
6687 "methodId": "agent-auth",
6688 "name": "Agent Auth"
6689 }))
6690 .is_err()
6691 );
6692 }
6693
6694 #[test]
6695 fn test_auth_method_agent_rejects_null_type() {
6696 assert!(
6697 serde_json::from_value::<AuthMethod>(json!({
6698 "methodId": "agent-auth",
6699 "name": "Agent Auth",
6700 "type": null
6701 }))
6702 .is_err()
6703 );
6704 }
6705
6706 #[test]
6707 fn test_auth_method_unknown_does_not_hide_malformed_agent() {
6708 assert!(
6709 serde_json::from_value::<AuthMethod>(json!({
6710 "methodId": "agent-auth",
6711 "type": "agent"
6712 }))
6713 .is_err()
6714 );
6715 assert!(
6716 serde_json::from_value::<AuthMethod>(json!({
6717 "methodId": "api-key",
6718 "type": "env_var",
6719 "vars": [{"name": "API_KEY"}]
6720 }))
6721 .is_err()
6722 );
6723 }
6724
6725 #[test]
6726 fn test_auth_method_unknown_variant_roundtrip() {
6727 let method: AuthMethod = serde_json::from_value(json!({
6728 "methodId": "oauth",
6729 "name": "OAuth",
6730 "type": "_oauth",
6731 "authorizationUrl": "https://example.com/auth"
6732 }))
6733 .unwrap();
6734
6735 assert_eq!(method.method_id().0.as_ref(), "oauth");
6736 assert_eq!(method.name(), "OAuth");
6737 let AuthMethod::Other(unknown) = method else {
6738 panic!("expected unknown auth method");
6739 };
6740 assert_eq!(unknown.type_, "_oauth");
6741 assert_eq!(
6742 unknown.fields.get("authorizationUrl"),
6743 Some(&json!("https://example.com/auth"))
6744 );
6745
6746 assert_eq!(
6747 serde_json::to_value(AuthMethod::Other(unknown)).unwrap(),
6748 json!({
6749 "methodId": "oauth",
6750 "name": "OAuth",
6751 "type": "_oauth",
6752 "authorizationUrl": "https://example.com/auth"
6753 })
6754 );
6755 }
6756
6757 #[test]
6758 fn test_auth_method_unknown_does_not_hide_malformed_known_variant() {
6759 assert!(
6760 serde_json::from_value::<AuthMethod>(json!({
6761 "methodId": "terminal-auth",
6762 "type": "terminal"
6763 }))
6764 .is_err()
6765 );
6766 }
6767
6768 #[test]
6769 fn test_session_delete_serialization() {
6770 assert_eq!(AGENT_METHOD_NAMES.session_delete, "session/delete");
6771 assert_eq!(
6772 ClientRequest::DeleteSessionRequest(Box::new(DeleteSessionRequest::new("sess_abc123")))
6773 .method(),
6774 "session/delete"
6775 );
6776 assert_eq!(
6777 serde_json::to_value(DeleteSessionRequest::new("sess_abc123")).unwrap(),
6778 json!({
6779 "sessionId": "sess_abc123"
6780 })
6781 );
6782 assert_eq!(
6783 serde_json::to_value(DeleteSessionResponse::new()).unwrap(),
6784 json!({})
6785 );
6786 assert_eq!(
6787 serde_json::to_value(
6788 SessionCapabilities::new().delete(SessionDeleteCapabilities::new())
6789 )
6790 .unwrap(),
6791 json!({
6792 "delete": {}
6793 })
6794 );
6795 }
6796 #[test]
6797 fn test_session_additional_directories_serialization() {
6798 assert_eq!(
6799 serde_json::to_value(NewSessionRequest::new("/home/user/project")).unwrap(),
6800 json!({
6801 "cwd": "/home/user/project",
6802 })
6803 );
6804 assert_eq!(
6805 serde_json::to_value(
6806 NewSessionRequest::new("/home/user/project").additional_directories(vec![
6807 PathBuf::from("/home/user/shared-lib"),
6808 PathBuf::from("/home/user/product-docs"),
6809 ])
6810 )
6811 .unwrap(),
6812 json!({
6813 "cwd": "/home/user/project",
6814 "additionalDirectories": [
6815 "/home/user/shared-lib",
6816 "/home/user/product-docs"
6817 ],
6818 })
6819 );
6820 assert_eq!(
6821 serde_json::to_value(ResumeSessionRequest::new(
6822 "sess_abc123",
6823 "/home/user/project"
6824 ))
6825 .unwrap(),
6826 json!({
6827 "sessionId": "sess_abc123",
6828 "cwd": "/home/user/project",
6829 })
6830 );
6831 assert_eq!(
6832 serde_json::from_value::<ResumeSessionRequest>(json!({
6833 "sessionId": "sess_abc123",
6834 "cwd": "/home/user/project"
6835 }))
6836 .unwrap()
6837 .mcp_servers,
6838 Vec::<McpServer>::new()
6839 );
6840 assert_eq!(
6841 serde_json::from_value::<ResumeSessionRequest>(json!({
6842 "sessionId": "sess_abc123",
6843 "cwd": "/home/user/project",
6844 "mcpServers": null
6845 }))
6846 .unwrap()
6847 .mcp_servers,
6848 Vec::<McpServer>::new()
6849 );
6850 assert_eq!(
6851 serde_json::to_value(SessionInfo::new("sess_abc123", "/home/user/project")).unwrap(),
6852 json!({
6853 "sessionId": "sess_abc123",
6854 "cwd": "/home/user/project"
6855 })
6856 );
6857 assert_eq!(
6858 serde_json::to_value(
6859 SessionInfo::new("sess_abc123", "/home/user/project").additional_directories(vec![
6860 PathBuf::from("/home/user/shared-lib"),
6861 PathBuf::from("/home/user/product-docs"),
6862 ])
6863 )
6864 .unwrap(),
6865 json!({
6866 "sessionId": "sess_abc123",
6867 "cwd": "/home/user/project",
6868 "additionalDirectories": [
6869 "/home/user/shared-lib",
6870 "/home/user/product-docs"
6871 ]
6872 })
6873 );
6874 assert_eq!(
6875 serde_json::from_value::<SessionInfo>(json!({
6876 "sessionId": "sess_abc123",
6877 "cwd": "/home/user/project"
6878 }))
6879 .unwrap()
6880 .additional_directories,
6881 Vec::<AbsolutePath>::new()
6882 );
6883 }
6884 #[test]
6885 fn test_session_additional_directories_capabilities_serialization() {
6886 assert_eq!(
6887 serde_json::to_value(
6888 SessionCapabilities::new()
6889 .additional_directories(SessionAdditionalDirectoriesCapabilities::new())
6890 )
6891 .unwrap(),
6892 json!({
6893 "additionalDirectories": {}
6894 })
6895 );
6896 }
6897
6898 #[cfg(feature = "unstable_session_system_prompt")]
6899 #[test]
6900 fn test_session_system_prompt_capabilities_serialization() {
6901 assert_eq!(
6902 serde_json::to_value(
6903 SessionCapabilities::new().system_prompt(SessionSystemPromptCapabilities::new())
6904 )
6905 .unwrap(),
6906 json!({ "systemPrompt": {} })
6907 );
6908 assert_eq!(
6909 serde_json::to_value(
6910 SessionCapabilities::new().system_prompt(
6911 SessionSystemPromptCapabilities::new().mode(
6912 SessionSystemPromptModeCapabilities::new()
6913 .override_(SessionSystemPromptOverrideCapabilities::new())
6914 )
6915 )
6916 )
6917 .unwrap(),
6918 json!({ "systemPrompt": { "mode": { "override": {} } } })
6919 );
6920 assert_eq!(
6921 serde_json::from_value::<SessionCapabilities>(
6922 json!({ "systemPrompt": { "mode": { "override": {} } } })
6923 )
6924 .unwrap()
6925 .system_prompt
6926 .and_then(|capabilities| capabilities.mode)
6927 .and_then(|mode| mode.override_),
6928 Some(SessionSystemPromptOverrideCapabilities::new())
6929 );
6930
6931 assert_eq!(
6933 serde_json::from_value::<SessionCapabilities>(json!({ "systemPrompt": "yes" }))
6934 .unwrap()
6935 .system_prompt,
6936 None
6937 );
6938 assert_eq!(
6939 serde_json::from_value::<SessionCapabilities>(
6940 json!({ "systemPrompt": { "mode": { "override": true } } })
6941 )
6942 .unwrap()
6943 .system_prompt,
6944 Some(
6945 SessionSystemPromptCapabilities::new()
6946 .mode(SessionSystemPromptModeCapabilities::new())
6947 )
6948 );
6949 assert_eq!(
6950 serde_json::from_value::<SessionCapabilities>(
6951 json!({ "systemPrompt": { "mode": "override" } })
6952 )
6953 .unwrap()
6954 .system_prompt,
6955 Some(SessionSystemPromptCapabilities::new())
6956 );
6957 }
6958
6959 #[cfg(feature = "unstable_session_system_prompt")]
6960 #[test]
6961 fn test_system_prompt_lifecycle_request_serialization() {
6962 let system_prompt = SystemPrompt::new(vec![ContentBlock::from(
6963 "You are a code review specialist.",
6964 )]);
6965 let system_prompt_json = json!({
6966 "mode": "append",
6967 "content": [{ "type": "text", "text": "You are a code review specialist." }]
6968 });
6969
6970 let request =
6971 NewSessionRequest::new("/home/user/project").system_prompt(system_prompt.clone());
6972 let request_json = json!({
6973 "cwd": "/home/user/project",
6974 "systemPrompt": system_prompt_json
6975 });
6976 assert_eq!(serde_json::to_value(&request).unwrap(), request_json);
6977 assert_eq!(
6978 serde_json::from_value::<NewSessionRequest>(request_json).unwrap(),
6979 request
6980 );
6981
6982 assert_eq!(
6983 serde_json::to_value(
6984 ResumeSessionRequest::new("sess_abc123", "/home/user/project")
6985 .system_prompt(system_prompt.clone())
6986 )
6987 .unwrap()["systemPrompt"],
6988 system_prompt_json
6989 );
6990 #[cfg(feature = "unstable_session_fork")]
6991 assert_eq!(
6992 serde_json::to_value(
6993 ForkSessionRequest::new("sess_abc123", "/home/user/project")
6994 .system_prompt(system_prompt.clone())
6995 )
6996 .unwrap()["systemPrompt"],
6997 system_prompt_json
6998 );
6999
7000 let override_prompt =
7001 SystemPrompt::new(vec![ContentBlock::from("...")]).mode(SystemPromptMode::Override);
7002 let override_json = json!({
7003 "mode": "override",
7004 "content": [{ "type": "text", "text": "..." }]
7005 });
7006 assert_eq!(
7007 serde_json::to_value(&override_prompt).unwrap(),
7008 override_json
7009 );
7010 assert_eq!(
7011 serde_json::from_value::<SystemPrompt>(override_json).unwrap(),
7012 override_prompt
7013 );
7014 }
7015
7016 #[cfg(feature = "unstable_session_system_prompt")]
7019 #[test]
7020 fn test_system_prompt_new_defaults_to_append() {
7021 let system_prompt = SystemPrompt::new(vec![ContentBlock::from("...")]);
7022 assert_eq!(system_prompt.mode, SystemPromptMode::Append);
7023 assert_eq!(
7024 serde_json::to_value(&system_prompt).unwrap(),
7025 json!({ "mode": "append", "content": [{ "type": "text", "text": "..." }] })
7026 );
7027 }
7028
7029 #[cfg(feature = "unstable_session_system_prompt")]
7033 #[test]
7034 fn test_system_prompt_preserves_unknown_modes() {
7035 for mode in ["_vendor_mode", "prepend"] {
7036 let system_prompt_json =
7037 json!({ "mode": mode, "content": [{ "type": "text", "text": "..." }] });
7038 let system_prompt: SystemPrompt =
7039 serde_json::from_value(system_prompt_json.clone()).unwrap();
7040 assert_eq!(
7041 system_prompt.mode,
7042 SystemPromptMode::Other(mode.to_string())
7043 );
7044 assert_eq!(
7045 serde_json::to_value(&system_prompt).unwrap(),
7046 system_prompt_json
7047 );
7048 }
7049 }
7050
7051 #[cfg(feature = "unstable_session_system_prompt")]
7055 fn assert_system_prompt_is_strict<T>(base: &serde_json::Value)
7056 where
7057 T: serde::de::DeserializeOwned + PartialEq + std::fmt::Debug,
7058 {
7059 let name = std::any::type_name::<T>();
7060 let with = |system_prompt: serde_json::Value| {
7061 let mut request = base.clone();
7062 request["systemPrompt"] = system_prompt;
7063 serde_json::from_value::<T>(request)
7064 };
7065
7066 for system_prompt in [
7067 json!("You are a code review specialist."),
7068 json!({ "content": [{ "type": "text", "text": "..." }] }),
7069 json!({ "mode": null, "content": [{ "type": "text", "text": "..." }] }),
7070 json!({ "mode": 1, "content": [{ "type": "text", "text": "..." }] }),
7071 json!({ "mode": "append" }),
7072 json!({ "mode": "append", "content": "..." }),
7073 json!({ "mode": "append", "content": [{ "type": "text" }] }),
7074 ] {
7075 assert!(
7076 with(system_prompt.clone()).is_err(),
7077 "{name}.systemPrompt: {system_prompt}"
7078 );
7079 }
7080
7081 assert_eq!(
7082 with(serde_json::Value::Null).unwrap(),
7083 serde_json::from_value::<T>(base.clone()).unwrap(),
7084 "{name}.systemPrompt"
7085 );
7086 }
7087
7088 #[cfg(feature = "unstable_session_system_prompt")]
7089 #[test]
7090 fn test_session_setup_requests_reject_malformed_system_prompts() {
7091 assert!(
7092 serde_json::to_value(NewSessionRequest::new("/repo"))
7093 .unwrap()
7094 .get("systemPrompt")
7095 .is_none()
7096 );
7097 assert_system_prompt_is_strict::<NewSessionRequest>(&json!({ "cwd": "/repo" }));
7098 assert_system_prompt_is_strict::<ResumeSessionRequest>(&json!({
7099 "sessionId": "sess-1",
7100 "cwd": "/repo"
7101 }));
7102 #[cfg(feature = "unstable_session_fork")]
7103 assert_system_prompt_is_strict::<ForkSessionRequest>(&json!({
7104 "sessionId": "sess-1",
7105 "cwd": "/repo"
7106 }));
7107 }
7108
7109 #[cfg(feature = "unstable_session_system_prompt")]
7112 #[test]
7113 fn test_system_prompt_preserves_unknown_content_blocks() {
7114 let request: NewSessionRequest = serde_json::from_value(json!({
7115 "cwd": "/home/user/project",
7116 "systemPrompt": {
7117 "mode": "append",
7118 "content": [{ "type": "_vendor_video", "uri": "https://example.com/intro.mp4" }]
7119 }
7120 }))
7121 .unwrap();
7122 let content = request.system_prompt.unwrap().content;
7123 assert_eq!(content.len(), 1);
7124 assert!(matches!(content[0], ContentBlock::Other(_)));
7125 }
7126
7127 #[test]
7128 fn test_auth_method_terminal_serialization() {
7129 let method = AuthMethod::Terminal(AuthMethodTerminal::new("tui-auth", "Terminal Auth"));
7130
7131 let json = serde_json::to_value(&method).unwrap();
7132 assert_eq!(
7133 json,
7134 json!({
7135 "methodId": "tui-auth",
7136 "name": "Terminal Auth",
7137 "type": "terminal"
7138 })
7139 );
7140 assert!(!json.as_object().unwrap().contains_key("args"));
7142 assert!(!json.as_object().unwrap().contains_key("env"));
7143
7144 let deserialized: AuthMethod = serde_json::from_value(json).unwrap();
7145 match deserialized {
7146 AuthMethod::Terminal(AuthMethodTerminal { args, env, .. }) => {
7147 assert_eq!(args, Vec::<String>::new());
7148 assert_eq!(env, []);
7149 }
7150 _ => panic!("Expected Terminal variant"),
7151 }
7152 }
7153
7154 #[test]
7155 fn test_auth_method_terminal_with_args_and_env_serialization() {
7156 let method = AuthMethod::Terminal(
7157 AuthMethodTerminal::new("tui-auth", "Terminal Auth")
7158 .args(vec!["--interactive".to_string(), "--color".to_string()])
7159 .env(vec![EnvVariable::new("TERM", "xterm-256color")]),
7160 );
7161
7162 let json = serde_json::to_value(&method).unwrap();
7163 assert_eq!(
7164 json,
7165 json!({
7166 "methodId": "tui-auth",
7167 "name": "Terminal Auth",
7168 "type": "terminal",
7169 "args": ["--interactive", "--color"],
7170 "env": [
7171 {
7172 "name": "TERM",
7173 "value": "xterm-256color"
7174 }
7175 ]
7176 })
7177 );
7178
7179 let deserialized: AuthMethod = serde_json::from_value(json).unwrap();
7180 match deserialized {
7181 AuthMethod::Terminal(AuthMethodTerminal { args, env, .. }) => {
7182 assert_eq!(args, vec!["--interactive", "--color"]);
7183 assert_eq!(env.len(), 1);
7184 assert_eq!(env[0].name, "TERM");
7185 assert_eq!(env[0].value, "xterm-256color");
7186 }
7187 _ => panic!("Expected Terminal variant"),
7188 }
7189 }
7190
7191 #[test]
7192 fn test_session_config_option_id_serialize() {
7193 let val = SessionConfigOptionValue::id("model-1");
7194 let json = serde_json::to_value(&val).unwrap();
7195 assert_eq!(json, json!({ "type": "id", "value": "model-1" }));
7196 }
7197
7198 #[test]
7199 fn test_session_config_option_value_boolean_serialize() {
7200 let val = SessionConfigOptionValue::boolean(true);
7201 let json = serde_json::to_value(&val).unwrap();
7202 assert_eq!(json, json!({ "type": "boolean", "value": true }));
7203 }
7204
7205 #[test]
7206 fn test_session_config_option_value_deserialize_id() {
7207 let json = json!({ "type": "id", "value": "model-1" });
7208 let val: SessionConfigOptionValue = serde_json::from_value(json).unwrap();
7209 assert_eq!(val, SessionConfigOptionValue::id("model-1"));
7210 assert_eq!(val.as_id().unwrap().to_string(), "model-1");
7211 }
7212
7213 #[test]
7214 fn test_session_config_option_value_deserialize_requires_type() {
7215 let json = json!({ "value": "model-1" });
7216 let result = serde_json::from_value::<SessionConfigOptionValue>(json);
7217 assert!(result.is_err());
7218 }
7219
7220 #[test]
7221 fn test_session_config_option_value_deserialize_boolean() {
7222 let json = json!({ "type": "boolean", "value": true });
7223 let val: SessionConfigOptionValue = serde_json::from_value(json).unwrap();
7224 assert_eq!(val, SessionConfigOptionValue::boolean(true));
7225 assert_eq!(val.as_bool(), Some(true));
7226 }
7227
7228 #[test]
7229 fn test_session_config_option_value_deserialize_boolean_false() {
7230 let json = json!({ "type": "boolean", "value": false });
7231 let val: SessionConfigOptionValue = serde_json::from_value(json).unwrap();
7232 assert_eq!(val, SessionConfigOptionValue::boolean(false));
7233 assert_eq!(val.as_bool(), Some(false));
7234 }
7235
7236 #[test]
7237 fn test_session_config_option_value_deserialize_unknown_type_with_string_value() {
7238 let json = json!({
7239 "type": "text",
7240 "value": "freeform input",
7241 "maxLength": 200
7242 });
7243 let val: SessionConfigOptionValue = serde_json::from_value(json).unwrap();
7244 let SessionConfigOptionValue::Other(unknown) = val else {
7245 panic!("Expected Other variant");
7246 };
7247 assert_eq!(unknown.type_, "text");
7248 assert_eq!(unknown.value, json!("freeform input"));
7249 assert_eq!(unknown.fields["maxLength"], json!(200));
7250 }
7251
7252 #[test]
7253 fn test_session_config_option_value_deserialize_unknown_type_with_object_value() {
7254 let json = json!({
7255 "type": "range",
7256 "value": { "min": 1, "max": 5 }
7257 });
7258 let val: SessionConfigOptionValue = serde_json::from_value(json).unwrap();
7259 let SessionConfigOptionValue::Other(unknown) = val else {
7260 panic!("Expected Other variant");
7261 };
7262 assert_eq!(unknown.type_, "range");
7263 assert_eq!(unknown.value, json!({ "min": 1, "max": 5 }));
7264 }
7265
7266 #[test]
7267 fn test_session_config_option_value_roundtrip_id() {
7268 let original = SessionConfigOptionValue::id("option-a");
7269 let json = serde_json::to_value(&original).unwrap();
7270 let roundtripped: SessionConfigOptionValue = serde_json::from_value(json).unwrap();
7271 assert_eq!(original, roundtripped);
7272 }
7273
7274 #[test]
7275 fn test_session_config_option_value_roundtrip_boolean() {
7276 let original = SessionConfigOptionValue::boolean(false);
7277 let json = serde_json::to_value(&original).unwrap();
7278 let roundtripped: SessionConfigOptionValue = serde_json::from_value(json).unwrap();
7279 assert_eq!(original, roundtripped);
7280 }
7281
7282 #[test]
7283 fn test_session_config_option_value_roundtrip_other() {
7284 let mut fields = BTreeMap::new();
7285 fields.insert("maxLength".to_string(), json!(200));
7286 let original = SessionConfigOptionValue::Other(OtherSessionConfigOptionValue::new(
7287 "text",
7288 json!("freeform input"),
7289 fields,
7290 ));
7291 let json = serde_json::to_value(&original).unwrap();
7292 let roundtripped: SessionConfigOptionValue = serde_json::from_value(json).unwrap();
7293 assert_eq!(original, roundtripped);
7294 }
7295
7296 #[test]
7297 fn test_session_config_option_value_type_mismatch_boolean_with_string() {
7298 let json = json!({ "type": "boolean", "value": "not a bool" });
7299 let result = serde_json::from_value::<SessionConfigOptionValue>(json);
7300 assert!(result.is_err());
7301 }
7302
7303 #[test]
7304 fn test_session_config_option_value_from_impls() {
7305 let from_str: SessionConfigOptionValue = "model-1".into();
7306 assert_eq!(from_str.as_id().unwrap().to_string(), "model-1");
7307
7308 let from_id: SessionConfigOptionValue = SessionConfigValueId::new("model-2").into();
7309 assert_eq!(from_id.as_id().unwrap().to_string(), "model-2");
7310
7311 let from_bool: SessionConfigOptionValue = true.into();
7312 assert_eq!(from_bool.as_bool(), Some(true));
7313 }
7314
7315 #[test]
7316 fn test_set_session_config_option_request_id() {
7317 let req = SetSessionConfigOptionRequest::new("sess_1", "model", "model-1");
7318 let json = serde_json::to_value(&req).unwrap();
7319 assert_eq!(
7320 json,
7321 json!({
7322 "sessionId": "sess_1",
7323 "configId": "model",
7324 "type": "id",
7325 "value": "model-1"
7326 })
7327 );
7328 }
7329
7330 #[test]
7331 fn test_set_session_config_option_request_boolean() {
7332 let req = SetSessionConfigOptionRequest::new("sess_1", "brave_mode", true);
7333 let json = serde_json::to_value(&req).unwrap();
7334 assert_eq!(
7335 json,
7336 json!({
7337 "sessionId": "sess_1",
7338 "configId": "brave_mode",
7339 "type": "boolean",
7340 "value": true
7341 })
7342 );
7343 }
7344
7345 #[test]
7346 fn test_set_session_config_option_request_deserialize_requires_type() {
7347 let json = json!({
7348 "sessionId": "sess_1",
7349 "configId": "model",
7350 "value": "model-1"
7351 });
7352 let result = serde_json::from_value::<SetSessionConfigOptionRequest>(json);
7353 assert!(result.is_err());
7354 }
7355
7356 #[test]
7357 fn test_set_session_config_option_request_deserialize_boolean() {
7358 let json = json!({
7359 "sessionId": "sess_1",
7360 "configId": "brave_mode",
7361 "type": "boolean",
7362 "value": true
7363 });
7364 let req: SetSessionConfigOptionRequest = serde_json::from_value(json).unwrap();
7365 assert_eq!(req.value.as_bool(), Some(true));
7366 }
7367
7368 #[test]
7369 fn test_set_session_config_option_request_roundtrip_id() {
7370 let original = SetSessionConfigOptionRequest::new("s", "c", "v");
7371 let json = serde_json::to_value(&original).unwrap();
7372 let roundtripped: SetSessionConfigOptionRequest = serde_json::from_value(json).unwrap();
7373 assert_eq!(original, roundtripped);
7374 }
7375
7376 #[test]
7377 fn test_set_session_config_option_request_roundtrip_boolean() {
7378 let original = SetSessionConfigOptionRequest::new("s", "c", false);
7379 let json = serde_json::to_value(&original).unwrap();
7380 let roundtripped: SetSessionConfigOptionRequest = serde_json::from_value(json).unwrap();
7381 assert_eq!(original, roundtripped);
7382 }
7383
7384 #[test]
7385 fn test_session_config_boolean_serialization() {
7386 let cfg = SessionConfigBoolean::new(true);
7387 let json = serde_json::to_value(&cfg).unwrap();
7388 assert_eq!(json, json!({ "currentValue": true }));
7389
7390 let deserialized: SessionConfigBoolean = serde_json::from_value(json).unwrap();
7391 assert!(deserialized.current_value);
7392 }
7393
7394 #[test]
7395 fn test_session_config_option_boolean_variant() {
7396 let opt = SessionConfigOption::boolean("brave_mode", "Brave Mode", false)
7397 .description("Skip confirmation prompts")
7398 .meta(test_meta());
7399 assert_eq!(serialized_meta_key_count(&opt), 1);
7400
7401 let json = serde_json::to_value(&opt).unwrap();
7402 assert_eq!(
7403 json,
7404 json!({
7405 "configId": "brave_mode",
7406 "name": "Brave Mode",
7407 "description": "Skip confirmation prompts",
7408 "type": "boolean",
7409 "currentValue": false,
7410 "_meta": {
7411 "source": "test"
7412 }
7413 })
7414 );
7415
7416 let deserialized: SessionConfigOption = serde_json::from_value(json).unwrap();
7417 assert_eq!(deserialized.config_id.to_string(), "brave_mode");
7418 assert_eq!(deserialized.name, "Brave Mode");
7419 match deserialized.kind {
7420 SessionConfigKind::Boolean(ref b) => assert!(!b.current_value),
7421 _ => panic!("Expected Boolean kind"),
7422 }
7423 }
7424
7425 #[test]
7426 fn test_session_config_option_select_still_works() {
7427 let opt = SessionConfigOption::select(
7429 "model",
7430 "Model",
7431 "model-1",
7432 vec![
7433 SessionConfigSelectOption::new("model-1", "Model 1"),
7434 SessionConfigSelectOption::new("model-2", "Model 2"),
7435 ],
7436 )
7437 .meta(test_meta());
7438 assert_eq!(serialized_meta_key_count(&opt), 1);
7439
7440 let json = serde_json::to_value(&opt).unwrap();
7441 assert_eq!(json["type"], "select");
7442 assert_eq!(json["currentValue"], "model-1");
7443 assert_eq!(json["options"].as_array().unwrap().len(), 2);
7444 assert_eq!(json["_meta"]["source"], "test");
7445
7446 let deserialized: SessionConfigOption = serde_json::from_value(json).unwrap();
7447 match deserialized.kind {
7448 SessionConfigKind::Select(ref s) => {
7449 assert_eq!(s.current_value.to_string(), "model-1");
7450 }
7451 _ => panic!("Expected Select kind"),
7452 }
7453 }
7454
7455 #[test]
7456 fn test_session_config_option_unknown_kind_roundtrip() {
7457 let option: SessionConfigOption = serde_json::from_value(json!({
7458 "configId": "verbosity",
7459 "name": "Verbosity",
7460 "type": "_slider",
7461 "currentValue": 3,
7462 "min": 0,
7463 "max": 5,
7464 "_meta": {
7465 "source": "test"
7466 }
7467 }))
7468 .unwrap();
7469
7470 assert_eq!(option.config_id.to_string(), "verbosity");
7471 assert_eq!(option.meta.as_ref().unwrap()["source"], "test");
7472 let SessionConfigKind::Other(unknown) = &option.kind else {
7473 panic!("expected unknown config kind");
7474 };
7475 assert_eq!(unknown.type_, "_slider");
7476 assert_eq!(unknown.fields.get("currentValue"), Some(&json!(3)));
7477 assert!(!unknown.fields.contains_key("_meta"));
7478 assert_eq!(serialized_meta_key_count(&option), 1);
7479
7480 let json = serde_json::to_value(&option).unwrap();
7481 assert_eq!(json["type"], "_slider");
7482 assert_eq!(json["currentValue"], 3);
7483 assert_eq!(json["min"], 0);
7484 assert_eq!(json["max"], 5);
7485 assert_eq!(json["_meta"]["source"], "test");
7486 }
7487
7488 #[test]
7489 fn test_session_config_option_unknown_kind_does_not_duplicate_flattened_meta() {
7490 let mut fields = std::collections::BTreeMap::new();
7491 fields.insert("currentValue".to_string(), json!(3));
7492 fields.insert("_meta".to_string(), json!({ "inner": "ignored" }));
7493
7494 let option = SessionConfigOption::new(
7495 "verbosity",
7496 "Verbosity",
7497 SessionConfigKind::Other(OtherSessionConfigKind::new("_slider", fields)),
7498 )
7499 .meta(test_meta());
7500
7501 let SessionConfigKind::Other(unknown) = &option.kind else {
7502 panic!("expected unknown config kind");
7503 };
7504 assert!(!unknown.fields.contains_key("_meta"));
7505 assert_eq!(serialized_meta_key_count(&option), 1);
7506
7507 let json = serde_json::to_value(&option).unwrap();
7508 assert_eq!(json["type"], "_slider");
7509 assert_eq!(json["currentValue"], 3);
7510 assert_eq!(json["_meta"]["source"], "test");
7511 }
7512
7513 #[test]
7514 fn test_session_config_option_unknown_does_not_hide_malformed_known_kind() {
7515 assert!(
7516 serde_json::from_value::<SessionConfigOption>(json!({
7517 "configId": "model",
7518 "name": "Model",
7519 "type": "select"
7520 }))
7521 .is_err()
7522 );
7523 }
7524
7525 #[cfg(feature = "unstable_llm_providers")]
7526 #[test]
7527 fn test_llm_protocol_known_variants() {
7528 assert_eq!(
7529 serde_json::to_value(&LlmProtocol::Anthropic).unwrap(),
7530 json!("anthropic")
7531 );
7532 assert_eq!(
7533 serde_json::to_value(&LlmProtocol::OpenAi).unwrap(),
7534 json!("openai")
7535 );
7536 assert_eq!(
7537 serde_json::to_value(&LlmProtocol::Azure).unwrap(),
7538 json!("azure")
7539 );
7540 assert_eq!(
7541 serde_json::to_value(&LlmProtocol::Vertex).unwrap(),
7542 json!("vertex")
7543 );
7544 assert_eq!(
7545 serde_json::to_value(&LlmProtocol::Bedrock).unwrap(),
7546 json!("bedrock")
7547 );
7548
7549 assert_eq!(
7550 serde_json::from_str::<LlmProtocol>("\"anthropic\"").unwrap(),
7551 LlmProtocol::Anthropic
7552 );
7553 assert_eq!(
7554 serde_json::from_str::<LlmProtocol>("\"openai\"").unwrap(),
7555 LlmProtocol::OpenAi
7556 );
7557 assert_eq!(
7558 serde_json::from_str::<LlmProtocol>("\"azure\"").unwrap(),
7559 LlmProtocol::Azure
7560 );
7561 assert_eq!(
7562 serde_json::from_str::<LlmProtocol>("\"vertex\"").unwrap(),
7563 LlmProtocol::Vertex
7564 );
7565 assert_eq!(
7566 serde_json::from_str::<LlmProtocol>("\"bedrock\"").unwrap(),
7567 LlmProtocol::Bedrock
7568 );
7569 }
7570
7571 #[cfg(feature = "unstable_llm_providers")]
7572 #[test]
7573 fn test_llm_protocol_unknown_variant() {
7574 let unknown: LlmProtocol = serde_json::from_str("\"cohere\"").unwrap();
7575 assert_eq!(unknown, LlmProtocol::Other("cohere".to_string()));
7576
7577 let json = serde_json::to_value(&unknown).unwrap();
7578 assert_eq!(json, json!("cohere"));
7579 }
7580
7581 #[cfg(feature = "unstable_llm_providers")]
7582 #[test]
7583 fn test_provider_current_config_serialization() {
7584 let config =
7585 ProviderCurrentConfig::new(LlmProtocol::Anthropic, "https://api.anthropic.com");
7586
7587 let json = serde_json::to_value(&config).unwrap();
7588 assert_eq!(
7589 json,
7590 json!({
7591 "apiType": "anthropic",
7592 "baseUrl": "https://api.anthropic.com"
7593 })
7594 );
7595
7596 let deserialized: ProviderCurrentConfig = serde_json::from_value(json).unwrap();
7597 assert_eq!(deserialized.api_type, LlmProtocol::Anthropic);
7598 assert_eq!(deserialized.base_url, "https://api.anthropic.com");
7599 }
7600
7601 #[cfg(feature = "unstable_llm_providers")]
7602 #[test]
7603 fn test_provider_info_with_current_config() {
7604 let info = ProviderInfo::new(
7605 "main",
7606 vec![LlmProtocol::Anthropic, LlmProtocol::OpenAi],
7607 true,
7608 Some(ProviderCurrentConfig::new(
7609 LlmProtocol::Anthropic,
7610 "https://api.anthropic.com",
7611 )),
7612 );
7613
7614 let json = serde_json::to_value(&info).unwrap();
7615 assert_eq!(
7616 json,
7617 json!({
7618 "providerId": "main",
7619 "supported": ["anthropic", "openai"],
7620 "required": true,
7621 "current": {
7622 "apiType": "anthropic",
7623 "baseUrl": "https://api.anthropic.com"
7624 }
7625 })
7626 );
7627
7628 let deserialized: ProviderInfo = serde_json::from_value(json).unwrap();
7629 assert_eq!(deserialized.provider_id.to_string(), "main");
7630 assert_eq!(deserialized.supported.len(), 2);
7631 assert!(deserialized.required);
7632 assert!(deserialized.current.is_some());
7633 assert_eq!(
7634 deserialized.current.as_ref().unwrap().api_type,
7635 LlmProtocol::Anthropic
7636 );
7637 }
7638
7639 #[cfg(feature = "unstable_llm_providers")]
7640 #[test]
7641 fn test_provider_info_disabled() {
7642 let info = ProviderInfo::new(
7643 "secondary",
7644 vec![LlmProtocol::OpenAi],
7645 false,
7646 None::<ProviderCurrentConfig>,
7647 );
7648
7649 let json = serde_json::to_value(&info).unwrap();
7650 assert_eq!(
7651 json,
7652 json!({
7653 "providerId": "secondary",
7654 "supported": ["openai"],
7655 "required": false
7656 })
7657 );
7658
7659 let deserialized: ProviderInfo = serde_json::from_value(json).unwrap();
7660 assert_eq!(deserialized.provider_id.to_string(), "secondary");
7661 assert!(!deserialized.required);
7662 assert!(deserialized.current.is_none());
7663 }
7664
7665 #[cfg(feature = "unstable_llm_providers")]
7666 #[test]
7667 fn test_provider_info_missing_current_defaults_to_none() {
7668 let json = json!({
7670 "providerId": "main",
7671 "supported": ["anthropic"],
7672 "required": true
7673 });
7674 let deserialized: ProviderInfo = serde_json::from_value(json).unwrap();
7675 assert!(deserialized.current.is_none());
7676 }
7677
7678 #[cfg(feature = "unstable_llm_providers")]
7679 #[test]
7680 fn test_provider_info_explicit_null_current_decodes_to_none() {
7681 let json = json!({
7685 "providerId": "main",
7686 "supported": ["anthropic"],
7687 "required": true,
7688 "current": null
7689 });
7690 let deserialized: ProviderInfo = serde_json::from_value(json).unwrap();
7691 assert!(deserialized.current.is_none());
7692 }
7693
7694 #[cfg(feature = "unstable_llm_providers")]
7695 #[test]
7696 fn test_list_providers_response_serialization() {
7697 let response = ListProvidersResponse::new(vec![ProviderInfo::new(
7698 "main",
7699 vec![LlmProtocol::Anthropic],
7700 true,
7701 Some(ProviderCurrentConfig::new(
7702 LlmProtocol::Anthropic,
7703 "https://api.anthropic.com",
7704 )),
7705 )]);
7706
7707 let json = serde_json::to_value(&response).unwrap();
7708 assert_eq!(json["providers"].as_array().unwrap().len(), 1);
7709 assert_eq!(json["providers"][0]["providerId"], "main");
7710
7711 let deserialized: ListProvidersResponse = serde_json::from_value(json).unwrap();
7712 assert_eq!(deserialized.providers.len(), 1);
7713 }
7714
7715 #[cfg(feature = "unstable_llm_providers")]
7716 #[test]
7717 fn test_set_provider_request_serialization() {
7718 use std::collections::HashMap;
7719
7720 let mut headers = HashMap::new();
7721 headers.insert("Authorization".to_string(), "Bearer sk-test".to_string());
7722
7723 let request =
7724 SetProviderRequest::new("main", LlmProtocol::OpenAi, "https://api.openai.com/v1")
7725 .headers(headers);
7726
7727 let json = serde_json::to_value(&request).unwrap();
7728 assert_eq!(
7729 json,
7730 json!({
7731 "providerId": "main",
7732 "apiType": "openai",
7733 "baseUrl": "https://api.openai.com/v1",
7734 "headers": {
7735 "Authorization": "Bearer sk-test"
7736 }
7737 })
7738 );
7739
7740 let deserialized: SetProviderRequest = serde_json::from_value(json).unwrap();
7741 assert_eq!(deserialized.provider_id.to_string(), "main");
7742 assert_eq!(deserialized.api_type, LlmProtocol::OpenAi);
7743 assert_eq!(deserialized.base_url, "https://api.openai.com/v1");
7744 assert_eq!(deserialized.headers.len(), 1);
7745 assert_eq!(
7746 deserialized.headers.get("Authorization").unwrap(),
7747 "Bearer sk-test"
7748 );
7749 }
7750
7751 #[cfg(feature = "unstable_llm_providers")]
7752 #[test]
7753 fn test_set_provider_request_omits_empty_headers() {
7754 let request =
7755 SetProviderRequest::new("main", LlmProtocol::Anthropic, "https://api.anthropic.com");
7756
7757 let json = serde_json::to_value(&request).unwrap();
7758 assert!(!json.as_object().unwrap().contains_key("headers"));
7760 }
7761
7762 #[cfg(feature = "unstable_llm_providers")]
7763 #[test]
7764 fn test_disable_provider_request_serialization() {
7765 let request = DisableProviderRequest::new("secondary");
7766
7767 let json = serde_json::to_value(&request).unwrap();
7768 assert_eq!(json, json!({ "providerId": "secondary" }));
7769
7770 let deserialized: DisableProviderRequest = serde_json::from_value(json).unwrap();
7771 assert_eq!(deserialized.provider_id.to_string(), "secondary");
7772 }
7773
7774 #[cfg(feature = "unstable_llm_providers")]
7775 #[test]
7776 fn test_providers_capabilities_serialization() {
7777 let caps = ProvidersCapabilities::new();
7778
7779 let json = serde_json::to_value(&caps).unwrap();
7780 assert_eq!(json, json!({}));
7781
7782 let deserialized: ProvidersCapabilities = serde_json::from_value(json).unwrap();
7783 assert!(deserialized.meta.is_none());
7784 }
7785
7786 #[cfg(feature = "unstable_llm_providers")]
7787 #[test]
7788 fn test_agent_capabilities_with_providers() {
7789 let caps = AgentCapabilities::new().providers(ProvidersCapabilities::new());
7790
7791 let json = serde_json::to_value(&caps).unwrap();
7792 assert_eq!(json["providers"], json!({}));
7793
7794 let deserialized: AgentCapabilities = serde_json::from_value(json).unwrap();
7795 assert!(deserialized.providers.is_some());
7796 }
7797
7798 #[test]
7799 fn test_agent_capabilities_session_is_explicit() {
7800 let json = serde_json::to_value(AgentCapabilities::new()).unwrap();
7801 assert!(json.get("session").is_none());
7802
7803 let caps = AgentCapabilities::new().session(
7804 SessionCapabilities::new()
7805 .prompt(PromptCapabilities::new().image(PromptImageCapabilities::new()))
7806 .mcp(McpCapabilities::new().stdio(McpStdioCapabilities::new())),
7807 );
7808
7809 assert_eq!(
7810 serde_json::to_value(&caps).unwrap(),
7811 json!({
7812 "session": {
7813 "prompt": {
7814 "image": {}
7815 },
7816 "mcp": {
7817 "stdio": {}
7818 }
7819 }
7820 })
7821 );
7822
7823 let deserialized: AgentCapabilities = serde_json::from_value(json!({
7824 "session": false
7825 }))
7826 .unwrap();
7827 assert!(deserialized.session.is_none());
7828 }
7829
7830 #[test]
7831 fn test_prompt_capabilities_serialize_supported_content_as_objects() {
7832 let caps = PromptCapabilities::new()
7833 .image(PromptImageCapabilities::new())
7834 .audio(PromptAudioCapabilities::new())
7835 .embedded_context(PromptEmbeddedContextCapabilities::new());
7836
7837 assert_eq!(
7838 serde_json::to_value(&caps).unwrap(),
7839 json!({
7840 "image": {},
7841 "audio": {},
7842 "embeddedContext": {}
7843 })
7844 );
7845
7846 let deserialized: PromptCapabilities = serde_json::from_value(json!({
7847 "image": null,
7848 "audio": false,
7849 "embeddedContext": {}
7850 }))
7851 .unwrap();
7852 assert!(deserialized.image.is_none());
7853 assert!(deserialized.audio.is_none());
7854 assert!(deserialized.embedded_context.is_some());
7855 }
7856
7857 #[test]
7858 fn test_mcp_capabilities_serialize_supported_transports_as_objects() {
7859 let caps = McpCapabilities::new()
7860 .stdio(McpStdioCapabilities::new())
7861 .http(McpHttpCapabilities::new());
7862
7863 assert_eq!(
7864 serde_json::to_value(&caps).unwrap(),
7865 json!({
7866 "stdio": {},
7867 "http": {}
7868 })
7869 );
7870
7871 let deserialized: McpCapabilities = serde_json::from_value(json!({
7872 "stdio": null,
7873 "http": false
7874 }))
7875 .unwrap();
7876 assert!(deserialized.stdio.is_none());
7877 assert!(deserialized.http.is_none());
7878 }
7879
7880 #[cfg(feature = "unstable_mcp_over_acp")]
7881 #[test]
7882 fn test_mcp_capabilities_serialize_acp_support_as_object() {
7883 let caps = McpCapabilities::new().acp(McpAcpCapabilities::new());
7884
7885 assert_eq!(
7886 serde_json::to_value(&caps).unwrap(),
7887 json!({
7888 "acp": {}
7889 })
7890 );
7891 }
7892
7893 #[test]
7894 fn prompt_request_rejects_malformed_content_block() {
7895 use serde_json::json;
7896
7897 assert!(
7898 serde_json::from_value::<PromptRequest>(json!({
7899 "sessionId": "sess-1",
7900 "prompt": [{"type": "text"}]
7901 }))
7902 .is_err()
7903 );
7904 }
7905
7906 #[test]
7907 fn prompt_request_rejects_non_array_prompt() {
7908 use serde_json::json;
7909
7910 assert!(
7911 serde_json::from_value::<PromptRequest>(json!({
7912 "sessionId": "sess-1",
7913 "prompt": "hello"
7914 }))
7915 .is_err()
7916 );
7917 }
7918
7919 fn assert_session_setup_lists_are_strict<T>(base: &serde_json::Value)
7920 where
7921 T: serde::de::DeserializeOwned + PartialEq + std::fmt::Debug,
7922 {
7923 let name = std::any::type_name::<T>();
7924 let with = |field: &str, value: serde_json::Value| {
7925 let mut request = base.clone();
7926 request[field] = value;
7927 serde_json::from_value::<T>(request)
7928 };
7929
7930 for (field, value) in [
7931 (
7933 "mcpServers",
7934 json!([
7935 {"type": "stdio", "name": "github", "command": "/usr/bin/gh-mcp", "env": [{"name": "GITHUB_TOKEN", "value": null}]},
7936 {"type": "stdio", "name": "fs", "command": "/usr/bin/fs-mcp"}
7937 ]),
7938 ),
7939 ("mcpServers", json!({"type": "stdio", "name": "fs"})),
7940 ("additionalDirectories", json!(["/repo/lib", 42])),
7941 ("additionalDirectories", json!("/repo/lib")),
7942 ] {
7943 assert!(
7944 with(field, value.clone()).is_err(),
7945 "{name}.{field}: {value}"
7946 );
7947 }
7948
7949 let empty = serde_json::from_value::<T>(base.clone()).unwrap();
7950 for field in ["mcpServers", "additionalDirectories"] {
7951 assert_eq!(
7952 with(field, serde_json::Value::Null).unwrap(),
7953 empty,
7954 "{name}.{field}"
7955 );
7956 }
7957 }
7958
7959 #[test]
7960 fn session_setup_requests_reject_malformed_mcp_servers_and_directories() {
7961 assert_session_setup_lists_are_strict::<NewSessionRequest>(&json!({"cwd": "/repo"}));
7962 assert_session_setup_lists_are_strict::<ResumeSessionRequest>(&json!({
7963 "sessionId": "sess-1",
7964 "cwd": "/repo"
7965 }));
7966 #[cfg(feature = "unstable_session_fork")]
7967 assert_session_setup_lists_are_strict::<ForkSessionRequest>(&json!({
7968 "sessionId": "sess-1",
7969 "cwd": "/repo"
7970 }));
7971 }
7972
7973 #[test]
7974 fn resume_session_request_rejects_malformed_replay_from() {
7975 let request = |replay_from: serde_json::Value| {
7976 serde_json::from_value::<ResumeSessionRequest>(json!({
7977 "sessionId": "sess-1",
7978 "cwd": "/repo",
7979 "replayFrom": replay_from
7980 }))
7981 };
7982
7983 assert!(request(json!("start")).is_err());
7985 assert_eq!(request(serde_json::Value::Null).unwrap().replay_from, None);
7986 assert!(matches!(
7987 request(json!({"type": "start"})).unwrap().replay_from,
7988 Some(ReplayFrom::Start(_))
7989 ));
7990 }
7991
7992 #[test]
7993 fn mcp_server_lists_treat_null_as_empty() {
7994 let servers: Vec<McpServer> = serde_json::from_value(json!([
7995 {"type": "stdio", "name": "fs", "command": "/usr/bin/fs-mcp", "args": null, "env": null},
7996 {"type": "http", "name": "api", "url": "https://example.com/mcp", "headers": null}
7997 ]))
7998 .unwrap();
7999
8000 assert_eq!(
8001 servers,
8002 vec![
8003 McpServer::Stdio(McpServerStdio::new("fs", "/usr/bin/fs-mcp")),
8004 McpServer::Http(McpServerHttp::new("api", "https://example.com/mcp")),
8005 ]
8006 );
8007 }
8008
8009 #[test]
8010 fn terminal_auth_methods_reject_malformed_launch_config() {
8011 let response: InitializeResponse = serde_json::from_value(json!({
8015 "protocolVersion": 2,
8016 "info": {"name": "agent", "version": "1.0.0"},
8017 "authMethods": [
8018 {"type": "terminal", "methodId": "bad-args", "name": "Log in", "args": ["auth", "--port", 8123]},
8019 {"type": "terminal", "methodId": "bad-env", "name": "Log in", "env": [{"name": "DEBUG", "value": 1}]},
8020 {"type": "terminal", "methodId": "null-config", "name": "Log in", "args": null, "env": null},
8021 {"type": "agent", "methodId": "agent", "name": "Agent login"}
8022 ]
8023 }))
8024 .unwrap();
8025
8026 assert_eq!(
8027 response.auth_methods,
8028 vec![
8029 AuthMethod::Terminal(AuthMethodTerminal::new("null-config", "Log in")),
8030 AuthMethod::Agent(AuthMethodAgent::new("agent", "Agent login")),
8031 ]
8032 );
8033 }
8034}