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<_>>")]
961 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
962 #[serde(default, skip_serializing_if = "Vec::is_empty")]
963 pub config_options: Vec<SessionConfigOption>,
964 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
970 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
971 #[serde(default, skip_serializing_if = "Vec::is_empty")]
972 pub available_commands: Vec<AvailableCommand>,
973 #[serde_as(deserialize_as = "DefaultOnError")]
979 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
980 #[serde(default)]
981 #[serde(rename = "_meta")]
982 pub meta: Option<Meta>,
983}
984
985impl NewSessionResponse {
986 #[must_use]
988 pub fn new(session_id: impl Into<SessionId>) -> Self {
989 Self {
990 session_id: session_id.into(),
991 config_options: Vec::new(),
992 available_commands: Vec::new(),
993 meta: None,
994 }
995 }
996
997 #[must_use]
999 pub fn config_options(mut self, config_options: Vec<SessionConfigOption>) -> Self {
1000 self.config_options = config_options;
1001 self
1002 }
1003
1004 #[must_use]
1006 pub fn available_commands(mut self, available_commands: Vec<AvailableCommand>) -> Self {
1007 self.available_commands = available_commands;
1008 self
1009 }
1010
1011 #[must_use]
1017 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1018 self.meta = meta.into_option();
1019 self
1020 }
1021}
1022
1023#[cfg(feature = "unstable_session_fork")]
1036#[serde_as]
1037#[skip_serializing_none]
1038#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1039#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1040#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = SESSION_FORK_METHOD_NAME)))]
1041#[serde(rename_all = "camelCase")]
1042#[non_exhaustive]
1043pub struct ForkSessionRequest {
1044 pub session_id: SessionId,
1046 pub cwd: AbsolutePath,
1048 #[serde_as(deserialize_as = "DefaultOnNull")]
1054 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-null" = true)))]
1055 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1056 pub additional_directories: Vec<AbsolutePath>,
1057 #[serde_as(deserialize_as = "DefaultOnNull")]
1059 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-null" = true)))]
1060 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1061 pub mcp_servers: Vec<McpServer>,
1062 #[cfg(feature = "unstable_session_system_prompt")]
1074 #[serde(default)]
1075 pub system_prompt: Option<SystemPrompt>,
1076 #[serde_as(deserialize_as = "DefaultOnError")]
1082 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1083 #[serde(default)]
1084 #[serde(rename = "_meta")]
1085 pub meta: Option<Meta>,
1086}
1087
1088#[cfg(feature = "unstable_session_fork")]
1089impl ForkSessionRequest {
1090 #[must_use]
1092 pub fn new(session_id: impl Into<SessionId>, cwd: impl Into<AbsolutePath>) -> Self {
1093 Self {
1094 session_id: session_id.into(),
1095 cwd: cwd.into(),
1096 additional_directories: vec![],
1097 mcp_servers: vec![],
1098 #[cfg(feature = "unstable_session_system_prompt")]
1099 system_prompt: None,
1100 meta: None,
1101 }
1102 }
1103
1104 #[must_use]
1106 pub fn additional_directories<I, P>(mut self, additional_directories: I) -> Self
1107 where
1108 I: IntoIterator<Item = P>,
1109 P: Into<AbsolutePath>,
1110 {
1111 self.additional_directories = additional_directories.into_iter().map(Into::into).collect();
1112 self
1113 }
1114
1115 #[must_use]
1117 pub fn mcp_servers(mut self, mcp_servers: Vec<McpServer>) -> Self {
1118 self.mcp_servers = mcp_servers;
1119 self
1120 }
1121
1122 #[cfg(feature = "unstable_session_system_prompt")]
1128 #[must_use]
1129 pub fn system_prompt(mut self, system_prompt: impl IntoOption<SystemPrompt>) -> Self {
1130 self.system_prompt = system_prompt.into_option();
1131 self
1132 }
1133
1134 #[must_use]
1140 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1141 self.meta = meta.into_option();
1142 self
1143 }
1144}
1145
1146#[cfg(feature = "unstable_session_fork")]
1152#[serde_as]
1153#[skip_serializing_none]
1154#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1155#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1156#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = SESSION_FORK_METHOD_NAME)))]
1157#[serde(rename_all = "camelCase")]
1158#[non_exhaustive]
1159pub struct ForkSessionResponse {
1160 pub session_id: SessionId,
1162 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
1164 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
1165 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1166 pub config_options: Vec<SessionConfigOption>,
1167 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
1173 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
1174 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1175 pub available_commands: Vec<AvailableCommand>,
1176 #[serde_as(deserialize_as = "DefaultOnError")]
1182 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1183 #[serde(default)]
1184 #[serde(rename = "_meta")]
1185 pub meta: Option<Meta>,
1186}
1187
1188#[cfg(feature = "unstable_session_fork")]
1189impl ForkSessionResponse {
1190 #[must_use]
1192 pub fn new(session_id: impl Into<SessionId>) -> Self {
1193 Self {
1194 session_id: session_id.into(),
1195 config_options: Vec::new(),
1196 available_commands: Vec::new(),
1197 meta: None,
1198 }
1199 }
1200
1201 #[must_use]
1203 pub fn config_options(mut self, config_options: Vec<SessionConfigOption>) -> Self {
1204 self.config_options = config_options;
1205 self
1206 }
1207
1208 #[must_use]
1210 pub fn available_commands(mut self, available_commands: Vec<AvailableCommand>) -> Self {
1211 self.available_commands = available_commands;
1212 self
1213 }
1214
1215 #[must_use]
1221 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1222 self.meta = meta.into_option();
1223 self
1224 }
1225}
1226
1227#[serde_as]
1234#[skip_serializing_none]
1235#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1236#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1237#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = SESSION_RESUME_METHOD_NAME)))]
1238#[serde(rename_all = "camelCase")]
1239#[non_exhaustive]
1240pub struct ResumeSessionRequest {
1241 pub session_id: SessionId,
1243 pub cwd: AbsolutePath,
1245 #[serde_as(deserialize_as = "DefaultOnNull")]
1252 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-null" = true)))]
1253 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1254 pub additional_directories: Vec<AbsolutePath>,
1255 #[serde_as(deserialize_as = "DefaultOnNull")]
1257 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-null" = true)))]
1258 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1259 pub mcp_servers: Vec<McpServer>,
1260 #[serde(default)]
1272 pub replay_from: Option<ReplayFrom>,
1273 #[cfg(feature = "unstable_session_system_prompt")]
1285 #[serde(default)]
1286 pub system_prompt: Option<SystemPrompt>,
1287 #[serde_as(deserialize_as = "DefaultOnError")]
1293 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1294 #[serde(default)]
1295 #[serde(rename = "_meta")]
1296 pub meta: Option<Meta>,
1297}
1298
1299impl ResumeSessionRequest {
1300 #[must_use]
1302 pub fn new(session_id: impl Into<SessionId>, cwd: impl Into<AbsolutePath>) -> Self {
1303 Self {
1304 session_id: session_id.into(),
1305 cwd: cwd.into(),
1306 additional_directories: vec![],
1307 mcp_servers: vec![],
1308 replay_from: None,
1309 #[cfg(feature = "unstable_session_system_prompt")]
1310 system_prompt: None,
1311 meta: None,
1312 }
1313 }
1314
1315 #[must_use]
1317 pub fn additional_directories<I, P>(mut self, additional_directories: I) -> Self
1318 where
1319 I: IntoIterator<Item = P>,
1320 P: Into<AbsolutePath>,
1321 {
1322 self.additional_directories = additional_directories.into_iter().map(Into::into).collect();
1323 self
1324 }
1325
1326 #[must_use]
1328 pub fn mcp_servers(mut self, mcp_servers: Vec<McpServer>) -> Self {
1329 self.mcp_servers = mcp_servers;
1330 self
1331 }
1332
1333 #[must_use]
1341 pub fn replay_from(mut self, replay_from: impl IntoOption<ReplayFrom>) -> Self {
1342 self.replay_from = replay_from.into_option();
1343 self
1344 }
1345
1346 #[cfg(feature = "unstable_session_system_prompt")]
1352 #[must_use]
1353 pub fn system_prompt(mut self, system_prompt: impl IntoOption<SystemPrompt>) -> Self {
1354 self.system_prompt = system_prompt.into_option();
1355 self
1356 }
1357
1358 #[must_use]
1364 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1365 self.meta = meta.into_option();
1366 self
1367 }
1368}
1369
1370#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1374#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1375#[serde(tag = "type", rename_all = "snake_case")]
1376#[non_exhaustive]
1377pub enum ReplayFrom {
1378 Start(ReplayFromStart),
1380 #[serde(untagged)]
1391 Other(OtherReplayFrom),
1392}
1393
1394impl From<ReplayFromStart> for ReplayFrom {
1395 fn from(replay_from: ReplayFromStart) -> Self {
1396 Self::Start(replay_from)
1397 }
1398}
1399
1400#[serde_as]
1402#[skip_serializing_none]
1403#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1404#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1405#[serde(rename_all = "camelCase")]
1406#[non_exhaustive]
1407pub struct ReplayFromStart {
1408 #[serde_as(deserialize_as = "DefaultOnError")]
1414 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1415 #[serde(default)]
1416 #[serde(rename = "_meta")]
1417 pub meta: Option<Meta>,
1418}
1419
1420impl ReplayFromStart {
1421 #[must_use]
1423 pub fn new() -> Self {
1424 Self::default()
1425 }
1426
1427 #[must_use]
1433 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1434 self.meta = meta.into_option();
1435 self
1436 }
1437}
1438
1439#[serde_as]
1441#[skip_serializing_none]
1442#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1443#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
1444#[cfg_attr(feature = "schemars", schemars(inline))]
1445#[cfg_attr(feature = "schemars", schemars(transform = other_replay_from_schema))]
1446#[serde(rename_all = "camelCase")]
1447#[non_exhaustive]
1448pub struct OtherReplayFrom {
1449 #[serde(rename = "type")]
1455 pub type_: String,
1456 #[serde(flatten)]
1459 pub fields: BTreeMap<String, serde_json::Value>,
1460}
1461
1462impl OtherReplayFrom {
1463 #[must_use]
1465 pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
1466 fields.remove("type");
1467 Self {
1468 type_: type_.into(),
1469 fields,
1470 }
1471 }
1472}
1473
1474impl<'de> Deserialize<'de> for OtherReplayFrom {
1475 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1476 where
1477 D: serde::Deserializer<'de>,
1478 {
1479 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1480 let type_ = fields
1481 .remove("type")
1482 .ok_or_else(|| serde::de::Error::missing_field("type"))?;
1483 let serde_json::Value::String(type_) = type_ else {
1484 return Err(serde::de::Error::custom("`type` must be a string"));
1485 };
1486
1487 if is_known_replay_from_type(&type_) {
1488 return Err(serde::de::Error::custom(format!(
1489 "known replay cursor `{type_}` did not match its schema"
1490 )));
1491 }
1492
1493 Ok(Self { type_, fields })
1494 }
1495}
1496
1497fn is_known_replay_from_type(type_: &str) -> bool {
1498 matches!(type_, "start")
1499}
1500
1501#[cfg(feature = "schemars")]
1502fn other_replay_from_schema(schema: &mut Schema) {
1503 super::schema_util::reject_known_string_discriminators(schema, "type", &["start"]);
1504}
1505
1506crate::serde_util::default_on_null! {
1507 #[serde_as]
1515 #[skip_serializing_none]
1516 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1517 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
1518 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = SESSION_RESUME_METHOD_NAME)))]
1519 #[serde(rename_all = "camelCase")]
1520 #[non_exhaustive]
1521 pub struct ResumeSessionResponse {
1522 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
1524 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
1525 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1526 pub config_options: Vec<SessionConfigOption>,
1527 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
1533 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
1534 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1535 pub available_commands: Vec<AvailableCommand>,
1536 #[serde_as(deserialize_as = "DefaultOnError")]
1542 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1543 #[serde(default)]
1544 #[serde(rename = "_meta")]
1545 pub meta: Option<Meta>,
1546 }
1547}
1548
1549impl ResumeSessionResponse {
1550 #[must_use]
1552 pub fn new() -> Self {
1553 Self::default()
1554 }
1555
1556 #[must_use]
1558 pub fn config_options(mut self, config_options: Vec<SessionConfigOption>) -> Self {
1559 self.config_options = config_options;
1560 self
1561 }
1562
1563 #[must_use]
1565 pub fn available_commands(mut self, available_commands: Vec<AvailableCommand>) -> Self {
1566 self.available_commands = available_commands;
1567 self
1568 }
1569
1570 #[must_use]
1576 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1577 self.meta = meta.into_option();
1578 self
1579 }
1580}
1581
1582#[serde_as]
1601#[skip_serializing_none]
1602#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1603#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1604#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = SESSION_CLOSE_METHOD_NAME)))]
1605#[serde(rename_all = "camelCase")]
1606#[non_exhaustive]
1607pub struct CloseSessionRequest {
1608 pub session_id: SessionId,
1610 #[serde_as(deserialize_as = "DefaultOnError")]
1616 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1617 #[serde(default)]
1618 #[serde(rename = "_meta")]
1619 pub meta: Option<Meta>,
1620}
1621
1622impl CloseSessionRequest {
1623 #[must_use]
1625 pub fn new(session_id: impl Into<SessionId>) -> Self {
1626 Self {
1627 session_id: session_id.into(),
1628 meta: None,
1629 }
1630 }
1631
1632 #[must_use]
1638 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1639 self.meta = meta.into_option();
1640 self
1641 }
1642}
1643
1644crate::serde_util::default_on_null! {
1645 #[serde_as]
1647 #[skip_serializing_none]
1648 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1649 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
1650 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = SESSION_CLOSE_METHOD_NAME)))]
1651 #[serde(rename_all = "camelCase")]
1652 #[non_exhaustive]
1653 pub struct CloseSessionResponse {
1654 #[serde_as(deserialize_as = "DefaultOnError")]
1660 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1661 #[serde(default)]
1662 #[serde(rename = "_meta")]
1663 pub meta: Option<Meta>,
1664 }
1665}
1666
1667impl CloseSessionResponse {
1668 #[must_use]
1670 pub fn new() -> Self {
1671 Self::default()
1672 }
1673
1674 #[must_use]
1680 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1681 self.meta = meta.into_option();
1682 self
1683 }
1684}
1685
1686#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1690#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
1691#[serde(transparent)]
1692#[from(Arc<str>, String, &str, &mut str, Box<str>, Cow<'_, str>)]
1693#[non_exhaustive]
1694pub struct SessionListCursor(pub Arc<str>);
1695
1696impl SessionListCursor {
1697 #[must_use]
1699 pub fn new(cursor: impl Into<Self>) -> Self {
1700 cursor.into()
1701 }
1702}
1703
1704impl AsRef<str> for SessionListCursor {
1705 fn as_ref(&self) -> &str {
1706 &self.0
1707 }
1708}
1709
1710impl From<&String> for SessionListCursor {
1711 fn from(cursor: &String) -> Self {
1712 Self(cursor.as_str().into())
1713 }
1714}
1715
1716macro_rules! impl_session_list_cursor_option_conversion {
1717 ($source:ty) => {
1718 impl IntoOption<SessionListCursor> for $source {
1719 fn into_option(self) -> Option<SessionListCursor> {
1720 Some(self.into())
1721 }
1722 }
1723 };
1724}
1725
1726impl_session_list_cursor_option_conversion!(Arc<str>);
1727impl_session_list_cursor_option_conversion!(String);
1728impl_session_list_cursor_option_conversion!(&str);
1729impl_session_list_cursor_option_conversion!(&mut str);
1730impl_session_list_cursor_option_conversion!(&String);
1731impl_session_list_cursor_option_conversion!(Box<str>);
1732impl_session_list_cursor_option_conversion!(Cow<'_, str>);
1733
1734crate::serde_util::default_on_null! {
1735 #[serde_as]
1737 #[skip_serializing_none]
1738 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1739 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
1740 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = SESSION_LIST_METHOD_NAME)))]
1741 #[serde(rename_all = "camelCase")]
1742 #[non_exhaustive]
1743 pub struct ListSessionsRequest {
1744 #[serde(default)]
1748 pub cwd: Option<AbsolutePath>,
1749 #[serde(default)]
1753 pub cursor: Option<SessionListCursor>,
1754 #[serde_as(deserialize_as = "DefaultOnError")]
1760 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1761 #[serde(default)]
1762 #[serde(rename = "_meta")]
1763 pub meta: Option<Meta>,
1764 }
1765}
1766
1767impl ListSessionsRequest {
1768 #[must_use]
1770 pub fn new() -> Self {
1771 Self::default()
1772 }
1773
1774 #[must_use]
1776 pub fn cwd(mut self, cwd: impl IntoOption<AbsolutePath>) -> Self {
1777 self.cwd = cwd.into_option();
1778 self
1779 }
1780
1781 #[must_use]
1783 pub fn cursor(mut self, cursor: impl IntoOption<SessionListCursor>) -> Self {
1784 self.cursor = cursor.into_option();
1785 self
1786 }
1787
1788 #[must_use]
1794 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1795 self.meta = meta.into_option();
1796 self
1797 }
1798}
1799
1800#[serde_as]
1802#[skip_serializing_none]
1803#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1804#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1805#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = SESSION_LIST_METHOD_NAME)))]
1806#[serde(rename_all = "camelCase")]
1807#[non_exhaustive]
1808pub struct ListSessionsResponse {
1809 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
1811 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
1812 pub sessions: Vec<SessionInfo>,
1813 #[serde(default)]
1817 pub next_cursor: Option<SessionListCursor>,
1818 #[serde_as(deserialize_as = "DefaultOnError")]
1824 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1825 #[serde(default)]
1826 #[serde(rename = "_meta")]
1827 pub meta: Option<Meta>,
1828}
1829
1830impl ListSessionsResponse {
1831 #[must_use]
1833 pub fn new(sessions: Vec<SessionInfo>) -> Self {
1834 Self {
1835 sessions,
1836 next_cursor: None,
1837 meta: None,
1838 }
1839 }
1840
1841 #[must_use]
1843 pub fn next_cursor(mut self, next_cursor: impl IntoOption<SessionListCursor>) -> Self {
1844 self.next_cursor = next_cursor.into_option();
1845 self
1846 }
1847
1848 #[must_use]
1854 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1855 self.meta = meta.into_option();
1856 self
1857 }
1858}
1859
1860#[serde_as]
1866#[skip_serializing_none]
1867#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1868#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1869#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = SESSION_DELETE_METHOD_NAME)))]
1870#[serde(rename_all = "camelCase")]
1871#[non_exhaustive]
1872pub struct DeleteSessionRequest {
1873 pub session_id: SessionId,
1875 #[serde_as(deserialize_as = "DefaultOnError")]
1881 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1882 #[serde(default)]
1883 #[serde(rename = "_meta")]
1884 pub meta: Option<Meta>,
1885}
1886
1887impl DeleteSessionRequest {
1888 #[must_use]
1890 pub fn new(session_id: impl Into<SessionId>) -> Self {
1891 Self {
1892 session_id: session_id.into(),
1893 meta: None,
1894 }
1895 }
1896
1897 #[must_use]
1903 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1904 self.meta = meta.into_option();
1905 self
1906 }
1907}
1908
1909crate::serde_util::default_on_null! {
1910 #[serde_as]
1912 #[skip_serializing_none]
1913 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1914 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
1915 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = SESSION_DELETE_METHOD_NAME)))]
1916 #[serde(rename_all = "camelCase")]
1917 #[non_exhaustive]
1918 pub struct DeleteSessionResponse {
1919 #[serde_as(deserialize_as = "DefaultOnError")]
1925 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1926 #[serde(default)]
1927 #[serde(rename = "_meta")]
1928 pub meta: Option<Meta>,
1929 }
1930}
1931
1932impl DeleteSessionResponse {
1933 #[must_use]
1935 pub fn new() -> Self {
1936 Self::default()
1937 }
1938
1939 #[must_use]
1945 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1946 self.meta = meta.into_option();
1947 self
1948 }
1949}
1950
1951#[serde_as]
1953#[skip_serializing_none]
1954#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1955#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1956#[serde(rename_all = "camelCase")]
1957#[non_exhaustive]
1958pub struct SessionInfo {
1959 pub session_id: SessionId,
1961 pub cwd: AbsolutePath,
1963 #[serde_as(deserialize_as = "DefaultOnNull")]
1969 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1970 pub additional_directories: Vec<AbsolutePath>,
1971
1972 #[serde_as(deserialize_as = "DefaultOnError")]
1976 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1977 #[serde(default)]
1978 pub title: Option<String>,
1979 #[serde_as(deserialize_as = "DefaultOnError")]
1983 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "format" = "date-time")))]
1984 #[serde(default)]
1985 pub updated_at: Option<String>,
1986 #[serde_as(deserialize_as = "DefaultOnError")]
1992 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
1993 #[serde(default)]
1994 #[serde(rename = "_meta")]
1995 pub meta: Option<Meta>,
1996}
1997
1998impl SessionInfo {
1999 #[must_use]
2001 pub fn new(session_id: impl Into<SessionId>, cwd: impl Into<AbsolutePath>) -> Self {
2002 Self {
2003 session_id: session_id.into(),
2004 cwd: cwd.into(),
2005 additional_directories: vec![],
2006 title: None,
2007 updated_at: None,
2008 meta: None,
2009 }
2010 }
2011
2012 #[must_use]
2014 pub fn additional_directories<I, P>(mut self, additional_directories: I) -> Self
2015 where
2016 I: IntoIterator<Item = P>,
2017 P: Into<AbsolutePath>,
2018 {
2019 self.additional_directories = additional_directories.into_iter().map(Into::into).collect();
2020 self
2021 }
2022
2023 #[must_use]
2025 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
2026 self.title = title.into_option();
2027 self
2028 }
2029
2030 #[must_use]
2032 pub fn updated_at(mut self, updated_at: impl IntoOption<String>) -> Self {
2033 self.updated_at = updated_at.into_option();
2034 self
2035 }
2036
2037 #[must_use]
2043 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2044 self.meta = meta.into_option();
2045 self
2046 }
2047}
2048
2049#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2053#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, From, Display)]
2054#[serde(transparent)]
2055#[from(forward)]
2056#[non_exhaustive]
2057pub struct SessionConfigId(pub Arc<str>);
2058
2059impl SessionConfigId {
2060 #[must_use]
2062 pub fn new(id: impl Into<Self>) -> Self {
2063 id.into()
2064 }
2065}
2066
2067#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2069#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, From, Display)]
2070#[serde(transparent)]
2071#[from(forward)]
2072#[non_exhaustive]
2073pub struct SessionConfigValueId(pub Arc<str>);
2074
2075impl SessionConfigValueId {
2076 #[must_use]
2078 pub fn new(id: impl Into<Self>) -> Self {
2079 id.into()
2080 }
2081}
2082
2083#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2085#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, From, Display)]
2086#[serde(transparent)]
2087#[from(forward)]
2088#[non_exhaustive]
2089pub struct SessionConfigGroupId(pub Arc<str>);
2090
2091impl SessionConfigGroupId {
2092 #[must_use]
2094 pub fn new(id: impl Into<Self>) -> Self {
2095 id.into()
2096 }
2097}
2098
2099#[serde_as]
2101#[skip_serializing_none]
2102#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2103#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2104#[serde(rename_all = "camelCase")]
2105#[non_exhaustive]
2106pub struct SessionConfigSelectOption {
2107 pub value: SessionConfigValueId,
2109 pub name: String,
2111 #[serde_as(deserialize_as = "DefaultOnError")]
2115 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2116 #[serde(default)]
2117 pub description: Option<String>,
2118 #[serde_as(deserialize_as = "DefaultOnError")]
2124 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2125 #[serde(default)]
2126 #[serde(rename = "_meta")]
2127 pub meta: Option<Meta>,
2128}
2129
2130impl SessionConfigSelectOption {
2131 #[must_use]
2133 pub fn new(value: impl Into<SessionConfigValueId>, name: impl Into<String>) -> Self {
2134 Self {
2135 value: value.into(),
2136 name: name.into(),
2137 description: None,
2138 meta: None,
2139 }
2140 }
2141
2142 #[must_use]
2144 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
2145 self.description = description.into_option();
2146 self
2147 }
2148
2149 #[must_use]
2155 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2156 self.meta = meta.into_option();
2157 self
2158 }
2159}
2160
2161#[serde_as]
2163#[skip_serializing_none]
2164#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2165#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2166#[serde(rename_all = "camelCase")]
2167#[non_exhaustive]
2168pub struct SessionConfigSelectGroup {
2169 pub group_id: SessionConfigGroupId,
2171 pub name: String,
2173 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
2175 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
2176 pub options: Vec<SessionConfigSelectOption>,
2177 #[serde_as(deserialize_as = "DefaultOnError")]
2183 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2184 #[serde(default)]
2185 #[serde(rename = "_meta")]
2186 pub meta: Option<Meta>,
2187}
2188
2189impl SessionConfigSelectGroup {
2190 #[must_use]
2192 pub fn new(
2193 group_id: impl Into<SessionConfigGroupId>,
2194 name: impl Into<String>,
2195 options: Vec<SessionConfigSelectOption>,
2196 ) -> Self {
2197 Self {
2198 group_id: group_id.into(),
2199 name: name.into(),
2200 options,
2201 meta: None,
2202 }
2203 }
2204
2205 #[must_use]
2211 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2212 self.meta = meta.into_option();
2213 self
2214 }
2215}
2216
2217#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2219#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2220#[serde(untagged)]
2221#[non_exhaustive]
2222pub enum SessionConfigSelectOptions {
2223 Ungrouped(Vec<SessionConfigSelectOption>),
2225 Grouped(Vec<SessionConfigSelectGroup>),
2227}
2228
2229impl From<Vec<SessionConfigSelectOption>> for SessionConfigSelectOptions {
2230 fn from(options: Vec<SessionConfigSelectOption>) -> Self {
2231 SessionConfigSelectOptions::Ungrouped(options)
2232 }
2233}
2234
2235impl From<Vec<SessionConfigSelectGroup>> for SessionConfigSelectOptions {
2236 fn from(groups: Vec<SessionConfigSelectGroup>) -> Self {
2237 SessionConfigSelectOptions::Grouped(groups)
2238 }
2239}
2240
2241#[skip_serializing_none]
2243#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2244#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2245#[serde(rename_all = "camelCase")]
2246#[non_exhaustive]
2247pub struct SessionConfigSelect {
2248 pub current_value: SessionConfigValueId,
2250 pub options: SessionConfigSelectOptions,
2253}
2254
2255impl SessionConfigSelect {
2256 #[must_use]
2258 pub fn new(
2259 current_value: impl Into<SessionConfigValueId>,
2260 options: impl Into<SessionConfigSelectOptions>,
2261 ) -> Self {
2262 Self {
2263 current_value: current_value.into(),
2264 options: options.into(),
2265 }
2266 }
2267}
2268
2269#[skip_serializing_none]
2271#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2272#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2273#[serde(rename_all = "camelCase")]
2274#[non_exhaustive]
2275pub struct SessionConfigBoolean {
2276 pub current_value: bool,
2278}
2279
2280impl SessionConfigBoolean {
2281 #[must_use]
2283 pub fn new(current_value: bool) -> Self {
2284 Self { current_value }
2285 }
2286}
2287
2288#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2298#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2299#[serde(rename_all = "snake_case")]
2300#[non_exhaustive]
2301pub enum SessionConfigOptionCategory {
2302 Mode,
2304 Model,
2306 ModelConfig,
2308 ThoughtLevel,
2310 #[serde(untagged)]
2316 Other(String),
2317}
2318
2319#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2321#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2322#[serde(tag = "type", rename_all = "snake_case")]
2323#[non_exhaustive]
2324pub enum SessionConfigKind {
2325 Select(SessionConfigSelect),
2327 Boolean(SessionConfigBoolean),
2329 #[serde(untagged)]
2339 Other(OtherSessionConfigKind),
2340}
2341
2342#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2344#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
2345#[cfg_attr(feature = "schemars", schemars(inline))]
2346#[cfg_attr(feature = "schemars", schemars(transform = other_session_config_kind_schema))]
2347#[serde(rename_all = "camelCase")]
2348#[non_exhaustive]
2349pub struct OtherSessionConfigKind {
2350 #[serde(rename = "type")]
2356 pub type_: String,
2357 #[serde(flatten)]
2359 pub fields: BTreeMap<String, serde_json::Value>,
2360}
2361
2362impl OtherSessionConfigKind {
2363 #[must_use]
2365 pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
2366 fields.remove("type");
2367 fields.remove("_meta");
2368 Self {
2369 type_: type_.into(),
2370 fields,
2371 }
2372 }
2373}
2374
2375impl<'de> Deserialize<'de> for OtherSessionConfigKind {
2376 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2377 where
2378 D: serde::Deserializer<'de>,
2379 {
2380 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
2381 let type_ = fields
2382 .remove("type")
2383 .ok_or_else(|| serde::de::Error::missing_field("type"))?;
2384 let serde_json::Value::String(type_) = type_ else {
2385 return Err(serde::de::Error::custom("`type` must be a string"));
2386 };
2387
2388 if is_known_session_config_kind_type(&type_) {
2389 return Err(serde::de::Error::custom(format!(
2390 "known session configuration option `{type_}` did not match its schema"
2391 )));
2392 }
2393
2394 Ok(Self { type_, fields })
2395 }
2396}
2397
2398fn is_known_session_config_kind_type(type_: &str) -> bool {
2399 matches!(type_, "select" | "boolean")
2400}
2401
2402#[cfg(feature = "schemars")]
2403fn other_session_config_kind_schema(schema: &mut Schema) {
2404 super::schema_util::reject_known_string_discriminators(schema, "type", &["select", "boolean"]);
2405}
2406
2407#[serde_as]
2409#[skip_serializing_none]
2410#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2411#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2412#[serde(rename_all = "camelCase")]
2413#[non_exhaustive]
2414pub struct SessionConfigOption {
2415 pub config_id: SessionConfigId,
2417 pub name: String,
2419 #[serde_as(deserialize_as = "DefaultOnError")]
2423 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2424 #[serde(default)]
2425 pub description: Option<String>,
2426 #[serde_as(deserialize_as = "DefaultOnError")]
2430 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2431 #[serde(default)]
2432 pub category: Option<SessionConfigOptionCategory>,
2433 #[serde(flatten)]
2435 pub kind: SessionConfigKind,
2436 #[serde_as(deserialize_as = "DefaultOnError")]
2442 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2443 #[serde(default)]
2444 #[serde(rename = "_meta")]
2445 pub meta: Option<Meta>,
2446}
2447
2448impl SessionConfigOption {
2449 #[must_use]
2451 pub fn new(
2452 config_id: impl Into<SessionConfigId>,
2453 name: impl Into<String>,
2454 kind: SessionConfigKind,
2455 ) -> Self {
2456 Self {
2457 config_id: config_id.into(),
2458 name: name.into(),
2459 description: None,
2460 category: None,
2461 kind,
2462 meta: None,
2463 }
2464 }
2465
2466 #[must_use]
2468 pub fn select(
2469 config_id: impl Into<SessionConfigId>,
2470 name: impl Into<String>,
2471 current_value: impl Into<SessionConfigValueId>,
2472 options: impl Into<SessionConfigSelectOptions>,
2473 ) -> Self {
2474 Self::new(
2475 config_id,
2476 name,
2477 SessionConfigKind::Select(SessionConfigSelect::new(current_value, options)),
2478 )
2479 }
2480
2481 #[must_use]
2483 pub fn boolean(
2484 config_id: impl Into<SessionConfigId>,
2485 name: impl Into<String>,
2486 current_value: bool,
2487 ) -> Self {
2488 Self::new(
2489 config_id,
2490 name,
2491 SessionConfigKind::Boolean(SessionConfigBoolean::new(current_value)),
2492 )
2493 }
2494
2495 #[must_use]
2497 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
2498 self.description = description.into_option();
2499 self
2500 }
2501
2502 #[must_use]
2504 pub fn category(mut self, category: impl IntoOption<SessionConfigOptionCategory>) -> Self {
2505 self.category = category.into_option();
2506 self
2507 }
2508
2509 #[must_use]
2515 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2516 self.meta = meta.into_option();
2517 self
2518 }
2519}
2520
2521#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2530#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
2531#[serde(tag = "type", rename_all = "snake_case")]
2532#[non_exhaustive]
2533pub enum SessionConfigOptionValue {
2534 Id {
2536 value: SessionConfigValueId,
2538 },
2539 Boolean {
2541 value: bool,
2543 },
2544 #[serde(untagged)]
2553 Other(OtherSessionConfigOptionValue),
2554}
2555
2556#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2558#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
2559#[cfg_attr(feature = "schemars", schemars(inline))]
2560#[cfg_attr(feature = "schemars", schemars(transform = other_session_config_option_value_schema))]
2561#[serde(rename_all = "camelCase")]
2562#[non_exhaustive]
2563pub struct OtherSessionConfigOptionValue {
2564 #[serde(rename = "type")]
2570 pub type_: String,
2571 pub value: serde_json::Value,
2573 #[serde(flatten)]
2575 pub fields: BTreeMap<String, serde_json::Value>,
2576}
2577
2578impl OtherSessionConfigOptionValue {
2579 #[must_use]
2581 pub fn new(
2582 type_: impl Into<String>,
2583 value: serde_json::Value,
2584 mut fields: BTreeMap<String, serde_json::Value>,
2585 ) -> Self {
2586 fields.remove("type");
2587 fields.remove("value");
2588 fields.remove("_meta");
2589 Self {
2590 type_: type_.into(),
2591 value,
2592 fields,
2593 }
2594 }
2595}
2596
2597impl<'de> Deserialize<'de> for OtherSessionConfigOptionValue {
2598 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2599 where
2600 D: serde::Deserializer<'de>,
2601 {
2602 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
2603 let type_ = fields
2604 .remove("type")
2605 .ok_or_else(|| serde::de::Error::missing_field("type"))?;
2606 let serde_json::Value::String(type_) = type_ else {
2607 return Err(serde::de::Error::custom("`type` must be a string"));
2608 };
2609
2610 if is_known_session_config_option_value_type(&type_) {
2611 return Err(serde::de::Error::custom(format!(
2612 "known session configuration option value `{type_}` did not match its schema"
2613 )));
2614 }
2615
2616 let value = fields
2617 .remove("value")
2618 .ok_or_else(|| serde::de::Error::missing_field("value"))?;
2619
2620 Ok(Self {
2621 type_,
2622 value,
2623 fields,
2624 })
2625 }
2626}
2627
2628impl<'de> Deserialize<'de> for SessionConfigOptionValue {
2629 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2630 where
2631 D: serde::Deserializer<'de>,
2632 {
2633 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
2634 let type_ = fields.remove("type");
2635 let value = fields
2636 .remove("value")
2637 .ok_or_else(|| serde::de::Error::missing_field("value"))?;
2638
2639 let type_ = type_.ok_or_else(|| serde::de::Error::missing_field("type"))?;
2640
2641 let serde_json::Value::String(type_) = type_ else {
2642 return Err(serde::de::Error::custom("`type` must be a string"));
2643 };
2644
2645 match type_.as_str() {
2646 "id" => {
2647 let value = serde_json::from_value(value).map_err(|error| {
2648 serde::de::Error::custom(format!(
2649 "`value` must be a string for `type: id`: {error}"
2650 ))
2651 })?;
2652 Ok(Self::Id { value })
2653 }
2654 "boolean" => {
2655 let value = serde_json::from_value(value).map_err(|error| {
2656 serde::de::Error::custom(format!(
2657 "`value` must be a boolean for `type: boolean`: {error}"
2658 ))
2659 })?;
2660 Ok(Self::Boolean { value })
2661 }
2662 _ => Ok(Self::Other(OtherSessionConfigOptionValue {
2663 type_,
2664 value,
2665 fields,
2666 })),
2667 }
2668 }
2669}
2670
2671fn is_known_session_config_option_value_type(type_: &str) -> bool {
2672 matches!(type_, "id" | "boolean")
2673}
2674
2675#[cfg(feature = "schemars")]
2676fn other_session_config_option_value_schema(schema: &mut Schema) {
2677 super::schema_util::reject_known_string_discriminators(schema, "type", &["id", "boolean"]);
2678}
2679
2680impl SessionConfigOptionValue {
2681 #[must_use]
2683 pub fn id(id: impl Into<SessionConfigValueId>) -> Self {
2684 Self::Id { value: id.into() }
2685 }
2686
2687 #[must_use]
2689 pub fn boolean(val: bool) -> Self {
2690 Self::Boolean { value: val }
2691 }
2692
2693 #[must_use]
2696 pub fn as_id(&self) -> Option<&SessionConfigValueId> {
2697 match self {
2698 Self::Id { value } => Some(value),
2699 _ => None,
2700 }
2701 }
2702
2703 #[must_use]
2705 pub fn as_bool(&self) -> Option<bool> {
2706 match self {
2707 Self::Boolean { value } => Some(*value),
2708 _ => None,
2709 }
2710 }
2711}
2712
2713impl From<SessionConfigValueId> for SessionConfigOptionValue {
2714 fn from(value: SessionConfigValueId) -> Self {
2715 Self::Id { value }
2716 }
2717}
2718
2719impl From<bool> for SessionConfigOptionValue {
2720 fn from(value: bool) -> Self {
2721 Self::Boolean { value }
2722 }
2723}
2724
2725impl From<&str> for SessionConfigOptionValue {
2726 fn from(value: &str) -> Self {
2727 Self::Id {
2728 value: SessionConfigValueId::new(value),
2729 }
2730 }
2731}
2732
2733#[serde_as]
2741#[skip_serializing_none]
2742#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2743#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2744#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = SESSION_SET_CONFIG_OPTION_METHOD_NAME)))]
2745#[serde(rename_all = "camelCase")]
2746#[non_exhaustive]
2747pub struct SetSessionConfigOptionRequest {
2748 pub session_id: SessionId,
2750 pub config_id: SessionConfigId,
2752 #[serde(flatten)]
2756 pub value: SessionConfigOptionValue,
2757 #[serde_as(deserialize_as = "DefaultOnError")]
2763 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2764 #[serde(default)]
2765 #[serde(rename = "_meta")]
2766 pub meta: Option<Meta>,
2767}
2768
2769impl SetSessionConfigOptionRequest {
2770 #[must_use]
2772 pub fn new(
2773 session_id: impl Into<SessionId>,
2774 config_id: impl Into<SessionConfigId>,
2775 value: impl Into<SessionConfigOptionValue>,
2776 ) -> Self {
2777 Self {
2778 session_id: session_id.into(),
2779 config_id: config_id.into(),
2780 value: value.into(),
2781 meta: None,
2782 }
2783 }
2784
2785 #[must_use]
2791 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2792 self.meta = meta.into_option();
2793 self
2794 }
2795}
2796
2797#[serde_as]
2799#[skip_serializing_none]
2800#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2801#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2802#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = SESSION_SET_CONFIG_OPTION_METHOD_NAME)))]
2803#[serde(rename_all = "camelCase")]
2804#[non_exhaustive]
2805pub struct SetSessionConfigOptionResponse {
2806 #[serde_as(deserialize_as = "VecSkipError<_>")]
2808 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-skip-invalid-items" = true)))]
2809 pub config_options: Vec<SessionConfigOption>,
2810 #[serde_as(deserialize_as = "DefaultOnError")]
2816 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2817 #[serde(default)]
2818 #[serde(rename = "_meta")]
2819 pub meta: Option<Meta>,
2820}
2821
2822impl SetSessionConfigOptionResponse {
2823 #[must_use]
2825 pub fn new(config_options: Vec<SessionConfigOption>) -> Self {
2826 Self {
2827 config_options,
2828 meta: None,
2829 }
2830 }
2831
2832 #[must_use]
2838 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2839 self.meta = meta.into_option();
2840 self
2841 }
2842}
2843
2844#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2853#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2854#[serde(tag = "type", rename_all = "snake_case")]
2855#[non_exhaustive]
2856pub enum McpServer {
2857 Http(McpServerHttp),
2861 #[cfg(feature = "unstable_mcp_over_acp")]
2870 Acp(McpServerAcp),
2871 Stdio(McpServerStdio),
2875 #[serde(untagged)]
2886 Other(OtherMcpServer),
2887}
2888
2889#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2891#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
2892#[cfg_attr(feature = "schemars", schemars(inline))]
2893#[cfg_attr(feature = "schemars", schemars(transform = other_mcp_server_schema))]
2894#[serde(rename_all = "camelCase")]
2895#[non_exhaustive]
2896pub struct OtherMcpServer {
2897 #[serde(rename = "type")]
2903 pub type_: String,
2904 #[serde(flatten)]
2906 pub fields: BTreeMap<String, serde_json::Value>,
2907}
2908
2909impl OtherMcpServer {
2910 #[must_use]
2912 pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
2913 fields.remove("type");
2914 Self {
2915 type_: type_.into(),
2916 fields,
2917 }
2918 }
2919}
2920
2921impl<'de> Deserialize<'de> for OtherMcpServer {
2922 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2923 where
2924 D: serde::Deserializer<'de>,
2925 {
2926 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
2927 let type_ = fields
2928 .remove("type")
2929 .ok_or_else(|| serde::de::Error::missing_field("type"))?;
2930 let serde_json::Value::String(type_) = type_ else {
2931 return Err(serde::de::Error::custom("`type` must be a string"));
2932 };
2933
2934 if is_known_mcp_server_type(&type_) {
2935 return Err(serde::de::Error::custom(format!(
2936 "known MCP server transport `{type_}` did not match its schema"
2937 )));
2938 }
2939
2940 Ok(Self { type_, fields })
2941 }
2942}
2943
2944fn is_known_mcp_server_type(type_: &str) -> bool {
2945 match type_ {
2946 "http" | "stdio" => true,
2947 #[cfg(feature = "unstable_mcp_over_acp")]
2948 "acp" => true,
2949 _ => false,
2950 }
2951}
2952
2953#[cfg(feature = "schemars")]
2954fn other_mcp_server_schema(schema: &mut Schema) {
2955 super::schema_util::reject_known_string_discriminators(
2956 schema,
2957 "type",
2958 &[
2959 "http",
2960 "stdio",
2961 #[cfg(feature = "unstable_mcp_over_acp")]
2962 "acp",
2963 ],
2964 );
2965}
2966
2967#[serde_as]
2969#[skip_serializing_none]
2970#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2971#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2972#[serde(rename_all = "camelCase")]
2973#[non_exhaustive]
2974pub struct McpServerHttp {
2975 pub name: String,
2977 #[cfg_attr(feature = "schemars", schemars(url))]
2979 pub url: String,
2980 #[serde_as(deserialize_as = "DefaultOnNull")]
2982 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-null" = true)))]
2983 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2984 pub headers: Vec<HttpHeader>,
2985 #[serde_as(deserialize_as = "DefaultOnError")]
2991 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
2992 #[serde(default)]
2993 #[serde(rename = "_meta")]
2994 pub meta: Option<Meta>,
2995}
2996
2997impl McpServerHttp {
2998 #[must_use]
3000 pub fn new(name: impl Into<String>, url: impl Into<String>) -> Self {
3001 Self {
3002 name: name.into(),
3003 url: url.into(),
3004 headers: Vec::new(),
3005 meta: None,
3006 }
3007 }
3008
3009 #[must_use]
3011 pub fn headers(mut self, headers: Vec<HttpHeader>) -> Self {
3012 self.headers = headers;
3013 self
3014 }
3015
3016 #[must_use]
3022 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3023 self.meta = meta.into_option();
3024 self
3025 }
3026}
3027
3028#[cfg(feature = "unstable_mcp_over_acp")]
3037#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3038#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
3039#[serde(transparent)]
3040#[from(forward)]
3041#[non_exhaustive]
3042pub struct McpServerAcpId(pub Arc<str>);
3043
3044#[cfg(feature = "unstable_mcp_over_acp")]
3045impl McpServerAcpId {
3046 #[must_use]
3048 pub fn new(id: impl Into<Self>) -> Self {
3049 id.into()
3050 }
3051}
3052
3053#[serde_as]
3062#[skip_serializing_none]
3063#[cfg(feature = "unstable_mcp_over_acp")]
3064#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3065#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3066#[serde(rename_all = "camelCase")]
3067#[non_exhaustive]
3068pub struct McpServerAcp {
3069 pub name: String,
3071 pub server_id: McpServerAcpId,
3076 #[serde_as(deserialize_as = "DefaultOnError")]
3082 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3083 #[serde(default)]
3084 #[serde(rename = "_meta")]
3085 pub meta: Option<Meta>,
3086}
3087
3088#[cfg(feature = "unstable_mcp_over_acp")]
3089impl McpServerAcp {
3090 #[must_use]
3092 pub fn new(name: impl Into<String>, server_id: impl Into<McpServerAcpId>) -> Self {
3093 Self {
3094 name: name.into(),
3095 server_id: server_id.into(),
3096 meta: None,
3097 }
3098 }
3099
3100 #[must_use]
3106 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3107 self.meta = meta.into_option();
3108 self
3109 }
3110}
3111
3112#[serde_as]
3114#[skip_serializing_none]
3115#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3116#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3117#[serde(rename_all = "camelCase")]
3118#[non_exhaustive]
3119pub struct McpServerStdio {
3120 pub name: String,
3122 pub command: AbsolutePath,
3124 #[serde_as(deserialize_as = "DefaultOnNull")]
3126 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-null" = true)))]
3127 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3128 pub args: Vec<String>,
3129 #[serde_as(deserialize_as = "DefaultOnNull")]
3131 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-null" = true)))]
3132 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3133 pub env: Vec<EnvVariable>,
3134 #[serde_as(deserialize_as = "DefaultOnError")]
3140 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3141 #[serde(default)]
3142 #[serde(rename = "_meta")]
3143 pub meta: Option<Meta>,
3144}
3145
3146impl McpServerStdio {
3147 #[must_use]
3149 pub fn new(name: impl Into<String>, command: impl Into<AbsolutePath>) -> Self {
3150 Self {
3151 name: name.into(),
3152 command: command.into(),
3153 args: Vec::new(),
3154 env: Vec::new(),
3155 meta: None,
3156 }
3157 }
3158
3159 #[must_use]
3161 pub fn args(mut self, args: Vec<String>) -> Self {
3162 self.args = args;
3163 self
3164 }
3165
3166 #[must_use]
3168 pub fn env(mut self, env: Vec<EnvVariable>) -> Self {
3169 self.env = env;
3170 self
3171 }
3172
3173 #[must_use]
3179 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3180 self.meta = meta.into_option();
3181 self
3182 }
3183}
3184
3185#[serde_as]
3187#[skip_serializing_none]
3188#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3189#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3190#[serde(rename_all = "camelCase")]
3191#[non_exhaustive]
3192pub struct EnvVariable {
3193 pub name: String,
3195 pub value: String,
3197 #[serde_as(deserialize_as = "DefaultOnError")]
3203 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3204 #[serde(default)]
3205 #[serde(rename = "_meta")]
3206 pub meta: Option<Meta>,
3207}
3208
3209impl EnvVariable {
3210 #[must_use]
3212 pub fn new(name: impl Into<String>, value: impl Into<String>) -> Self {
3213 Self {
3214 name: name.into(),
3215 value: value.into(),
3216 meta: None,
3217 }
3218 }
3219
3220 #[must_use]
3226 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3227 self.meta = meta.into_option();
3228 self
3229 }
3230}
3231
3232#[serde_as]
3234#[skip_serializing_none]
3235#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3236#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3237#[serde(rename_all = "camelCase")]
3238#[non_exhaustive]
3239pub struct HttpHeader {
3240 pub name: String,
3242 pub value: String,
3244 #[serde_as(deserialize_as = "DefaultOnError")]
3250 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3251 #[serde(default)]
3252 #[serde(rename = "_meta")]
3253 pub meta: Option<Meta>,
3254}
3255
3256impl HttpHeader {
3257 #[must_use]
3259 pub fn new(name: impl Into<String>, value: impl Into<String>) -> Self {
3260 Self {
3261 name: name.into(),
3262 value: value.into(),
3263 meta: None,
3264 }
3265 }
3266
3267 #[must_use]
3273 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3274 self.meta = meta.into_option();
3275 self
3276 }
3277}
3278
3279#[cfg(feature = "unstable_session_system_prompt")]
3292#[serde_as]
3293#[skip_serializing_none]
3294#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3295#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3296#[serde(rename_all = "camelCase")]
3297#[non_exhaustive]
3298pub struct SystemPrompt {
3299 pub mode: SystemPromptMode,
3306 pub content: Vec<ContentBlock>,
3313 #[serde_as(deserialize_as = "DefaultOnError")]
3319 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3320 #[serde(default)]
3321 #[serde(rename = "_meta")]
3322 pub meta: Option<Meta>,
3323}
3324
3325#[cfg(feature = "unstable_session_system_prompt")]
3326impl SystemPrompt {
3327 #[must_use]
3332 pub fn new(content: Vec<ContentBlock>) -> Self {
3333 Self {
3334 mode: SystemPromptMode::default(),
3335 content,
3336 meta: None,
3337 }
3338 }
3339
3340 #[must_use]
3342 pub fn mode(mut self, mode: SystemPromptMode) -> Self {
3343 self.mode = mode;
3344 self
3345 }
3346
3347 #[must_use]
3353 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3354 self.meta = meta.into_option();
3355 self
3356 }
3357}
3358
3359#[cfg(feature = "unstable_session_system_prompt")]
3369#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3370#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
3371#[serde(rename_all = "snake_case")]
3372#[non_exhaustive]
3373pub enum SystemPromptMode {
3374 #[default]
3381 Append,
3382 Override,
3391 #[serde(untagged)]
3397 Other(String),
3398}
3399
3400#[serde_as]
3408#[skip_serializing_none]
3409#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3410#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3411#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = SESSION_PROMPT_METHOD_NAME)))]
3412#[serde(rename_all = "camelCase")]
3413#[non_exhaustive]
3414pub struct PromptRequest {
3415 pub session_id: SessionId,
3417 pub prompt: Vec<ContentBlock>,
3431 #[serde_as(deserialize_as = "DefaultOnError")]
3437 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3438 #[serde(default)]
3439 #[serde(rename = "_meta")]
3440 pub meta: Option<Meta>,
3441}
3442
3443impl PromptRequest {
3444 #[must_use]
3446 pub fn new(session_id: impl Into<SessionId>, prompt: Vec<ContentBlock>) -> Self {
3447 Self {
3448 session_id: session_id.into(),
3449 prompt,
3450 meta: None,
3451 }
3452 }
3453
3454 #[must_use]
3460 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3461 self.meta = meta.into_option();
3462 self
3463 }
3464}
3465
3466#[serde_as]
3477#[skip_serializing_none]
3478#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3479#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3480#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = SESSION_PROMPT_METHOD_NAME)))]
3481#[serde(rename_all = "camelCase")]
3482#[non_exhaustive]
3483pub struct PromptResponse {
3484 pub message_id: MessageId,
3492 #[serde_as(deserialize_as = "DefaultOnError")]
3498 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3499 #[serde(default)]
3500 #[serde(rename = "_meta")]
3501 pub meta: Option<Meta>,
3502}
3503
3504impl PromptResponse {
3505 #[must_use]
3507 pub fn new(message_id: impl Into<MessageId>) -> Self {
3508 Self {
3509 message_id: message_id.into(),
3510 meta: None,
3511 }
3512 }
3513
3514 #[must_use]
3520 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3521 self.meta = meta.into_option();
3522 self
3523 }
3524}
3525
3526#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3530#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
3531#[serde(tag = "stopReason", rename_all = "snake_case")]
3532#[non_exhaustive]
3533pub enum StopReason {
3534 EndTurn,
3537 MaxTokens,
3539 MaxTurnRequests,
3542 Refusal,
3546 Cancelled,
3555 Error(ErrorStopReason),
3561 #[serde(untagged)]
3567 Other(OtherStopReason),
3568}
3569
3570impl From<ErrorStopReason> for StopReason {
3571 fn from(value: ErrorStopReason) -> Self {
3572 Self::Error(value)
3573 }
3574}
3575
3576#[serde_as]
3578#[skip_serializing_none]
3579#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3580#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
3581#[serde(rename_all = "camelCase")]
3582#[non_exhaustive]
3583pub struct ErrorStopReason {
3584 #[serde_as(deserialize_as = "DefaultOnError")]
3589 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3590 #[serde(default)]
3591 pub error: Option<Box<Error>>,
3592}
3593
3594impl ErrorStopReason {
3595 #[must_use]
3597 pub fn new() -> Self {
3598 Self::default()
3599 }
3600
3601 #[must_use]
3603 pub fn error(mut self, error: impl IntoOption<Error>) -> Self {
3604 self.error = error.into_option().map(Box::new);
3605 self
3606 }
3607}
3608
3609#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3611#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
3612#[cfg_attr(feature = "schemars", schemars(inline))]
3613#[cfg_attr(feature = "schemars", schemars(transform = other_stop_reason_schema))]
3614#[serde(rename_all = "camelCase")]
3615#[non_exhaustive]
3616pub struct OtherStopReason {
3617 pub stop_reason: String,
3623 #[serde(flatten)]
3625 pub fields: BTreeMap<String, serde_json::Value>,
3626}
3627
3628impl OtherStopReason {
3629 #[must_use]
3631 pub fn new(
3632 stop_reason: impl Into<String>,
3633 mut fields: BTreeMap<String, serde_json::Value>,
3634 ) -> Self {
3635 remove_idle_state_update_fields(&mut fields);
3636 Self {
3637 stop_reason: stop_reason.into(),
3638 fields,
3639 }
3640 }
3641}
3642
3643impl<'de> Deserialize<'de> for OtherStopReason {
3644 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3645 where
3646 D: serde::Deserializer<'de>,
3647 {
3648 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
3649 let stop_reason = fields
3650 .remove("stopReason")
3651 .ok_or_else(|| serde::de::Error::missing_field("stopReason"))?;
3652 let serde_json::Value::String(stop_reason) = stop_reason else {
3653 return Err(serde::de::Error::custom("`stopReason` must be a string"));
3654 };
3655
3656 if KNOWN_STOP_REASONS.contains(&stop_reason.as_str()) {
3657 return Err(serde::de::Error::custom(format!(
3658 "known stop reason `{stop_reason}` did not match its schema"
3659 )));
3660 }
3661 remove_idle_state_update_fields(&mut fields);
3662
3663 Ok(Self {
3664 stop_reason,
3665 fields,
3666 })
3667 }
3668}
3669
3670const KNOWN_STOP_REASONS: &[&str] = &[
3671 "end_turn",
3672 "max_tokens",
3673 "max_turn_requests",
3674 "refusal",
3675 "cancelled",
3676 "error",
3677];
3678
3679fn remove_idle_state_update_fields(fields: &mut BTreeMap<String, serde_json::Value>) {
3681 fields.remove("stopReason");
3682 fields.remove("usage");
3683 fields.remove("_meta");
3684}
3685
3686#[cfg(feature = "schemars")]
3687fn other_stop_reason_schema(schema: &mut Schema) {
3688 super::schema_util::reject_known_string_discriminators(
3689 schema,
3690 "stopReason",
3691 KNOWN_STOP_REASONS,
3692 );
3693}
3694
3695#[cfg(feature = "unstable_end_turn_token_usage")]
3701#[serde_as]
3702#[skip_serializing_none]
3703#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3704#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3705#[serde(rename_all = "camelCase")]
3706#[non_exhaustive]
3707pub struct Usage {
3708 pub total_tokens: u64,
3710 pub input_tokens: u64,
3712 pub output_tokens: u64,
3714 #[serde_as(deserialize_as = "DefaultOnError")]
3718 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3719 #[serde(default)]
3720 pub thought_tokens: Option<u64>,
3721 #[serde_as(deserialize_as = "DefaultOnError")]
3725 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3726 #[serde(default)]
3727 pub cached_read_tokens: Option<u64>,
3728 #[serde_as(deserialize_as = "DefaultOnError")]
3732 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3733 #[serde(default)]
3734 pub cached_write_tokens: Option<u64>,
3735 #[serde_as(deserialize_as = "DefaultOnError")]
3741 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3742 #[serde(default)]
3743 #[serde(rename = "_meta")]
3744 pub meta: Option<Meta>,
3745}
3746
3747#[cfg(feature = "unstable_end_turn_token_usage")]
3748impl Usage {
3749 #[must_use]
3751 pub fn new(total_tokens: u64, input_tokens: u64, output_tokens: u64) -> Self {
3752 Self {
3753 total_tokens,
3754 input_tokens,
3755 output_tokens,
3756 thought_tokens: None,
3757 cached_read_tokens: None,
3758 cached_write_tokens: None,
3759 meta: None,
3760 }
3761 }
3762
3763 #[must_use]
3765 pub fn thought_tokens(mut self, thought_tokens: impl IntoOption<u64>) -> Self {
3766 self.thought_tokens = thought_tokens.into_option();
3767 self
3768 }
3769
3770 #[must_use]
3772 pub fn cached_read_tokens(mut self, cached_read_tokens: impl IntoOption<u64>) -> Self {
3773 self.cached_read_tokens = cached_read_tokens.into_option();
3774 self
3775 }
3776
3777 #[must_use]
3779 pub fn cached_write_tokens(mut self, cached_write_tokens: impl IntoOption<u64>) -> Self {
3780 self.cached_write_tokens = cached_write_tokens.into_option();
3781 self
3782 }
3783
3784 #[must_use]
3790 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3791 self.meta = meta.into_option();
3792 self
3793 }
3794}
3795
3796#[cfg(feature = "unstable_llm_providers")]
3809#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3810#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3811#[serde(rename_all = "snake_case")]
3812#[non_exhaustive]
3813#[expect(clippy::doc_markdown)]
3814pub enum LlmProtocol {
3815 Anthropic,
3817 #[serde(rename = "openai")]
3819 OpenAi,
3820 Azure,
3822 Vertex,
3824 Bedrock,
3826 #[serde(untagged)]
3832 Other(String),
3833}
3834
3835#[cfg(feature = "unstable_llm_providers")]
3841#[serde_as]
3842#[skip_serializing_none]
3843#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3844#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3845#[serde(rename_all = "camelCase")]
3846#[non_exhaustive]
3847pub struct ProviderCurrentConfig {
3848 pub api_type: LlmProtocol,
3850 #[cfg_attr(feature = "schemars", schemars(url))]
3852 pub base_url: String,
3853 #[serde_as(deserialize_as = "DefaultOnError")]
3859 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3860 #[serde(default)]
3861 #[serde(rename = "_meta")]
3862 pub meta: Option<Meta>,
3863}
3864
3865#[cfg(feature = "unstable_llm_providers")]
3866impl ProviderCurrentConfig {
3867 #[must_use]
3869 pub fn new(api_type: LlmProtocol, base_url: impl Into<String>) -> Self {
3870 Self {
3871 api_type,
3872 base_url: base_url.into(),
3873 meta: None,
3874 }
3875 }
3876
3877 #[must_use]
3883 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3884 self.meta = meta.into_option();
3885 self
3886 }
3887}
3888
3889#[cfg(feature = "unstable_llm_providers")]
3895#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3896#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
3897#[serde(transparent)]
3898#[from(forward)]
3899#[non_exhaustive]
3900pub struct ProviderId(pub Arc<str>);
3901
3902#[cfg(feature = "unstable_llm_providers")]
3903impl ProviderId {
3904 #[must_use]
3906 pub fn new(id: impl Into<Self>) -> Self {
3907 id.into()
3908 }
3909}
3910
3911#[cfg(feature = "unstable_llm_providers")]
3917#[serde_as]
3918#[skip_serializing_none]
3919#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3920#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3921#[serde(rename_all = "camelCase")]
3922#[non_exhaustive]
3923pub struct ProviderInfo {
3924 pub provider_id: ProviderId,
3926 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_>>")]
3928 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
3929 pub supported: Vec<LlmProtocol>,
3930 pub required: bool,
3933 #[serde(default)]
3936 pub current: Option<ProviderCurrentConfig>,
3937 #[serde_as(deserialize_as = "DefaultOnError")]
3943 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
3944 #[serde(default)]
3945 #[serde(rename = "_meta")]
3946 pub meta: Option<Meta>,
3947}
3948
3949#[cfg(feature = "unstable_llm_providers")]
3950impl ProviderInfo {
3951 #[must_use]
3953 pub fn new(
3954 provider_id: impl Into<ProviderId>,
3955 supported: Vec<LlmProtocol>,
3956 required: bool,
3957 current: impl IntoOption<ProviderCurrentConfig>,
3958 ) -> Self {
3959 Self {
3960 provider_id: provider_id.into(),
3961 supported,
3962 required,
3963 current: current.into_option(),
3964 meta: None,
3965 }
3966 }
3967
3968 #[must_use]
3974 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
3975 self.meta = meta.into_option();
3976 self
3977 }
3978}
3979
3980#[cfg(feature = "unstable_llm_providers")]
3981crate::serde_util::default_on_null! {
3982 #[serde_as]
3988 #[skip_serializing_none]
3989 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3990 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
3991 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = PROVIDERS_LIST_METHOD_NAME)))]
3992 #[serde(rename_all = "camelCase")]
3993 #[non_exhaustive]
3994 pub struct ListProvidersRequest {
3995 #[serde_as(deserialize_as = "DefaultOnError")]
4001 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4002 #[serde(default)]
4003 #[serde(rename = "_meta")]
4004 pub meta: Option<Meta>,
4005 }
4006}
4007
4008#[cfg(feature = "unstable_llm_providers")]
4009impl ListProvidersRequest {
4010 #[must_use]
4012 pub fn new() -> Self {
4013 Self::default()
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")]
4034#[serde_as]
4035#[skip_serializing_none]
4036#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4037#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4038#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = PROVIDERS_LIST_METHOD_NAME)))]
4039#[serde(rename_all = "camelCase")]
4040#[non_exhaustive]
4041pub struct ListProvidersResponse {
4042 pub providers: Vec<ProviderInfo>,
4044 #[serde_as(deserialize_as = "DefaultOnError")]
4050 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4051 #[serde(default)]
4052 #[serde(rename = "_meta")]
4053 pub meta: Option<Meta>,
4054}
4055
4056#[cfg(feature = "unstable_llm_providers")]
4057impl ListProvidersResponse {
4058 #[must_use]
4060 pub fn new(providers: Vec<ProviderInfo>) -> Self {
4061 Self {
4062 providers,
4063 meta: None,
4064 }
4065 }
4066
4067 #[must_use]
4073 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
4074 self.meta = meta.into_option();
4075 self
4076 }
4077}
4078
4079#[cfg(feature = "unstable_llm_providers")]
4087#[serde_as]
4088#[skip_serializing_none]
4089#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4090#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
4091#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = PROVIDERS_SET_METHOD_NAME)))]
4092#[serde(rename_all = "camelCase")]
4093#[non_exhaustive]
4094pub struct SetProviderRequest {
4095 pub provider_id: ProviderId,
4097 pub api_type: LlmProtocol,
4099 #[cfg_attr(feature = "schemars", schemars(url))]
4101 pub base_url: String,
4102 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
4105 pub headers: HashMap<String, String>,
4106 #[serde_as(deserialize_as = "DefaultOnError")]
4112 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4113 #[serde(default)]
4114 #[serde(rename = "_meta")]
4115 pub meta: Option<Meta>,
4116}
4117
4118#[cfg(feature = "unstable_llm_providers")]
4119impl SetProviderRequest {
4120 #[must_use]
4122 pub fn new(
4123 provider_id: impl Into<ProviderId>,
4124 api_type: LlmProtocol,
4125 base_url: impl Into<String>,
4126 ) -> Self {
4127 Self {
4128 provider_id: provider_id.into(),
4129 api_type,
4130 base_url: base_url.into(),
4131 headers: HashMap::new(),
4132 meta: None,
4133 }
4134 }
4135
4136 #[must_use]
4139 pub fn headers(mut self, headers: HashMap<String, String>) -> Self {
4140 self.headers = headers;
4141 self
4142 }
4143
4144 #[must_use]
4150 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
4151 self.meta = meta.into_option();
4152 self
4153 }
4154}
4155
4156#[cfg(feature = "unstable_llm_providers")]
4157crate::serde_util::default_on_null! {
4158 #[serde_as]
4164 #[skip_serializing_none]
4165 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4166 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
4167 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = PROVIDERS_SET_METHOD_NAME)))]
4168 #[serde(rename_all = "camelCase")]
4169 #[non_exhaustive]
4170 pub struct SetProviderResponse {
4171 #[serde_as(deserialize_as = "DefaultOnError")]
4177 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4178 #[serde(default)]
4179 #[serde(rename = "_meta")]
4180 pub meta: Option<Meta>,
4181 }
4182}
4183
4184#[cfg(feature = "unstable_llm_providers")]
4185impl SetProviderResponse {
4186 #[must_use]
4188 pub fn new() -> Self {
4189 Self::default()
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")]
4210#[serde_as]
4211#[skip_serializing_none]
4212#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4213#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4214#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = PROVIDERS_DISABLE_METHOD_NAME)))]
4215#[serde(rename_all = "camelCase")]
4216#[non_exhaustive]
4217pub struct DisableProviderRequest {
4218 pub provider_id: ProviderId,
4220 #[serde_as(deserialize_as = "DefaultOnError")]
4226 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4227 #[serde(default)]
4228 #[serde(rename = "_meta")]
4229 pub meta: Option<Meta>,
4230}
4231
4232#[cfg(feature = "unstable_llm_providers")]
4233impl DisableProviderRequest {
4234 #[must_use]
4236 pub fn new(provider_id: impl Into<ProviderId>) -> Self {
4237 Self {
4238 provider_id: provider_id.into(),
4239 meta: None,
4240 }
4241 }
4242
4243 #[must_use]
4249 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
4250 self.meta = meta.into_option();
4251 self
4252 }
4253}
4254
4255#[cfg(feature = "unstable_llm_providers")]
4256crate::serde_util::default_on_null! {
4257 #[serde_as]
4263 #[skip_serializing_none]
4264 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4265 #[derive(Default, Debug, Clone, Serialize, PartialEq, Eq)]
4266 #[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = PROVIDERS_DISABLE_METHOD_NAME)))]
4267 #[serde(rename_all = "camelCase")]
4268 #[non_exhaustive]
4269 pub struct DisableProviderResponse {
4270 #[serde_as(deserialize_as = "DefaultOnError")]
4276 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4277 #[serde(default)]
4278 #[serde(rename = "_meta")]
4279 pub meta: Option<Meta>,
4280 }
4281}
4282
4283#[cfg(feature = "unstable_llm_providers")]
4284impl DisableProviderResponse {
4285 #[must_use]
4287 pub fn new() -> Self {
4288 Self::default()
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#[serde_as]
4312#[skip_serializing_none]
4313#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4314#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4315#[serde(rename_all = "camelCase")]
4316#[non_exhaustive]
4317pub struct AgentCapabilities {
4318 #[serde_as(deserialize_as = "DefaultOnError")]
4326 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4327 #[serde(default)]
4328 pub session: Option<SessionCapabilities>,
4329 #[serde_as(deserialize_as = "DefaultOnError")]
4336 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4337 #[serde(default)]
4338 pub auth: Option<AgentAuthCapabilities>,
4339 #[cfg(feature = "unstable_llm_providers")]
4348 #[serde_as(deserialize_as = "DefaultOnError")]
4349 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4350 #[serde(default)]
4351 pub providers: Option<ProvidersCapabilities>,
4352 #[serde_as(deserialize_as = "DefaultOnError")]
4358 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4359 #[serde(default)]
4360 #[serde(rename = "_meta")]
4361 pub meta: Option<Meta>,
4362}
4363
4364impl AgentCapabilities {
4365 #[must_use]
4367 pub fn new() -> Self {
4368 Self::default()
4369 }
4370
4371 #[must_use]
4379 pub fn session(mut self, session: impl IntoOption<SessionCapabilities>) -> Self {
4380 self.session = session.into_option();
4381 self
4382 }
4383
4384 #[must_use]
4388 pub fn auth(mut self, auth: impl IntoOption<AgentAuthCapabilities>) -> Self {
4389 self.auth = auth.into_option();
4390 self
4391 }
4392
4393 #[cfg(feature = "unstable_llm_providers")]
4399 #[must_use]
4400 pub fn providers(mut self, providers: impl IntoOption<ProvidersCapabilities>) -> Self {
4401 self.providers = providers.into_option();
4402 self
4403 }
4404
4405 #[must_use]
4411 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
4412 self.meta = meta.into_option();
4413 self
4414 }
4415}
4416
4417#[cfg(feature = "unstable_llm_providers")]
4425#[serde_as]
4426#[skip_serializing_none]
4427#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4428#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4429#[non_exhaustive]
4430pub struct ProvidersCapabilities {
4431 #[serde_as(deserialize_as = "DefaultOnError")]
4437 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4438 #[serde(default)]
4439 #[serde(rename = "_meta")]
4440 pub meta: Option<Meta>,
4441}
4442
4443#[cfg(feature = "unstable_llm_providers")]
4444impl ProvidersCapabilities {
4445 #[must_use]
4447 pub fn new() -> Self {
4448 Self::default()
4449 }
4450
4451 #[must_use]
4457 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
4458 self.meta = meta.into_option();
4459 self
4460 }
4461}
4462
4463#[serde_as]
4475#[skip_serializing_none]
4476#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4477#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4478#[serde(rename_all = "camelCase")]
4479#[non_exhaustive]
4480pub struct SessionCapabilities {
4481 #[serde_as(deserialize_as = "DefaultOnError")]
4487 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4488 #[serde(default)]
4489 pub prompt: Option<PromptCapabilities>,
4490 #[serde_as(deserialize_as = "DefaultOnError")]
4495 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4496 #[serde(default)]
4497 pub mcp: Option<McpCapabilities>,
4498 #[serde_as(deserialize_as = "DefaultOnError")]
4503 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4504 #[serde(default)]
4505 pub delete: Option<SessionDeleteCapabilities>,
4506 #[serde_as(deserialize_as = "DefaultOnError")]
4515 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4516 #[serde(default)]
4517 pub additional_directories: Option<SessionAdditionalDirectoriesCapabilities>,
4518 #[cfg(feature = "unstable_session_system_prompt")]
4527 #[serde_as(deserialize_as = "DefaultOnError")]
4528 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4529 #[serde(default)]
4530 pub system_prompt: Option<SessionSystemPromptCapabilities>,
4531 #[cfg(feature = "unstable_session_fork")]
4540 #[serde_as(deserialize_as = "DefaultOnError")]
4541 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4542 #[serde(default)]
4543 pub fork: Option<SessionForkCapabilities>,
4544 #[serde_as(deserialize_as = "DefaultOnError")]
4550 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4551 #[serde(default)]
4552 #[serde(rename = "_meta")]
4553 pub meta: Option<Meta>,
4554}
4555
4556impl SessionCapabilities {
4557 #[must_use]
4559 pub fn new() -> Self {
4560 Self::default()
4561 }
4562
4563 #[must_use]
4569 pub fn prompt(mut self, prompt: impl IntoOption<PromptCapabilities>) -> Self {
4570 self.prompt = prompt.into_option();
4571 self
4572 }
4573
4574 #[must_use]
4579 pub fn mcp(mut self, mcp: impl IntoOption<McpCapabilities>) -> Self {
4580 self.mcp = mcp.into_option();
4581 self
4582 }
4583
4584 #[must_use]
4589 pub fn delete(mut self, delete: impl IntoOption<SessionDeleteCapabilities>) -> Self {
4590 self.delete = delete.into_option();
4591 self
4592 }
4593
4594 #[must_use]
4603 pub fn additional_directories(
4604 mut self,
4605 additional_directories: impl IntoOption<SessionAdditionalDirectoriesCapabilities>,
4606 ) -> Self {
4607 self.additional_directories = additional_directories.into_option();
4608 self
4609 }
4610
4611 #[cfg(feature = "unstable_session_system_prompt")]
4620 #[must_use]
4621 pub fn system_prompt(
4622 mut self,
4623 system_prompt: impl IntoOption<SessionSystemPromptCapabilities>,
4624 ) -> Self {
4625 self.system_prompt = system_prompt.into_option();
4626 self
4627 }
4628
4629 #[cfg(feature = "unstable_session_fork")]
4630 #[must_use]
4635 pub fn fork(mut self, fork: impl IntoOption<SessionForkCapabilities>) -> Self {
4636 self.fork = fork.into_option();
4637 self
4638 }
4639
4640 #[must_use]
4646 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
4647 self.meta = meta.into_option();
4648 self
4649 }
4650}
4651
4652#[serde_as]
4656#[skip_serializing_none]
4657#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4658#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4659#[non_exhaustive]
4660pub struct SessionDeleteCapabilities {
4661 #[serde_as(deserialize_as = "DefaultOnError")]
4667 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4668 #[serde(default)]
4669 #[serde(rename = "_meta")]
4670 pub meta: Option<Meta>,
4671}
4672
4673impl SessionDeleteCapabilities {
4674 #[must_use]
4676 pub fn new() -> Self {
4677 Self::default()
4678 }
4679
4680 #[must_use]
4686 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
4687 self.meta = meta.into_option();
4688 self
4689 }
4690}
4691
4692#[serde_as]
4699#[skip_serializing_none]
4700#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4701#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4702#[non_exhaustive]
4703pub struct SessionAdditionalDirectoriesCapabilities {
4704 #[serde_as(deserialize_as = "DefaultOnError")]
4710 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4711 #[serde(default)]
4712 #[serde(rename = "_meta")]
4713 pub meta: Option<Meta>,
4714}
4715
4716impl SessionAdditionalDirectoriesCapabilities {
4717 #[must_use]
4719 pub fn new() -> Self {
4720 Self::default()
4721 }
4722
4723 #[must_use]
4729 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
4730 self.meta = meta.into_option();
4731 self
4732 }
4733}
4734
4735#[cfg(feature = "unstable_session_system_prompt")]
4744#[serde_as]
4745#[skip_serializing_none]
4746#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4747#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4748#[serde(rename_all = "camelCase")]
4749#[non_exhaustive]
4750pub struct SessionSystemPromptCapabilities {
4751 #[serde_as(deserialize_as = "DefaultOnError")]
4755 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4756 #[serde(default)]
4757 pub mode: Option<SessionSystemPromptModeCapabilities>,
4758 #[serde_as(deserialize_as = "DefaultOnError")]
4764 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4765 #[serde(default)]
4766 #[serde(rename = "_meta")]
4767 pub meta: Option<Meta>,
4768}
4769
4770#[cfg(feature = "unstable_session_system_prompt")]
4771impl SessionSystemPromptCapabilities {
4772 #[must_use]
4774 pub fn new() -> Self {
4775 Self::default()
4776 }
4777
4778 #[must_use]
4782 pub fn mode(mut self, mode: impl IntoOption<SessionSystemPromptModeCapabilities>) -> Self {
4783 self.mode = mode.into_option();
4784 self
4785 }
4786
4787 #[must_use]
4793 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
4794 self.meta = meta.into_option();
4795 self
4796 }
4797}
4798
4799#[cfg(feature = "unstable_session_system_prompt")]
4808#[serde_as]
4809#[skip_serializing_none]
4810#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4811#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4812#[serde(rename_all = "camelCase")]
4813#[non_exhaustive]
4814pub struct SessionSystemPromptModeCapabilities {
4815 #[serde_as(deserialize_as = "DefaultOnError")]
4821 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4822 #[serde(default, rename = "override")]
4823 pub override_: Option<SessionSystemPromptOverrideCapabilities>,
4824 #[serde_as(deserialize_as = "DefaultOnError")]
4830 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4831 #[serde(default)]
4832 #[serde(rename = "_meta")]
4833 pub meta: Option<Meta>,
4834}
4835
4836#[cfg(feature = "unstable_session_system_prompt")]
4837impl SessionSystemPromptModeCapabilities {
4838 #[must_use]
4840 pub fn new() -> Self {
4841 Self::default()
4842 }
4843
4844 #[must_use]
4850 pub fn override_(
4851 mut self,
4852 override_: impl IntoOption<SessionSystemPromptOverrideCapabilities>,
4853 ) -> Self {
4854 self.override_ = override_.into_option();
4855 self
4856 }
4857
4858 #[must_use]
4864 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
4865 self.meta = meta.into_option();
4866 self
4867 }
4868}
4869
4870#[cfg(feature = "unstable_session_system_prompt")]
4879#[serde_as]
4880#[skip_serializing_none]
4881#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4882#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4883#[non_exhaustive]
4884pub struct SessionSystemPromptOverrideCapabilities {
4885 #[serde_as(deserialize_as = "DefaultOnError")]
4891 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4892 #[serde(default)]
4893 #[serde(rename = "_meta")]
4894 pub meta: Option<Meta>,
4895}
4896
4897#[cfg(feature = "unstable_session_system_prompt")]
4898impl SessionSystemPromptOverrideCapabilities {
4899 #[must_use]
4901 pub fn new() -> Self {
4902 Self::default()
4903 }
4904
4905 #[must_use]
4911 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
4912 self.meta = meta.into_option();
4913 self
4914 }
4915}
4916
4917#[cfg(feature = "unstable_session_fork")]
4925#[serde_as]
4926#[skip_serializing_none]
4927#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4928#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4929#[non_exhaustive]
4930pub struct SessionForkCapabilities {
4931 #[serde_as(deserialize_as = "DefaultOnError")]
4937 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4938 #[serde(default)]
4939 #[serde(rename = "_meta")]
4940 pub meta: Option<Meta>,
4941}
4942
4943#[cfg(feature = "unstable_session_fork")]
4944impl SessionForkCapabilities {
4945 #[must_use]
4947 pub fn new() -> Self {
4948 Self::default()
4949 }
4950
4951 #[must_use]
4957 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
4958 self.meta = meta.into_option();
4959 self
4960 }
4961}
4962
4963#[serde_as]
4976#[skip_serializing_none]
4977#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
4978#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4979#[serde(rename_all = "camelCase")]
4980#[non_exhaustive]
4981pub struct PromptCapabilities {
4982 #[serde_as(deserialize_as = "DefaultOnError")]
4987 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4988 #[serde(default)]
4989 pub image: Option<PromptImageCapabilities>,
4990 #[serde_as(deserialize_as = "DefaultOnError")]
4995 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
4996 #[serde(default)]
4997 pub audio: Option<PromptAudioCapabilities>,
4998 #[serde_as(deserialize_as = "DefaultOnError")]
5006 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
5007 #[serde(default)]
5008 pub embedded_context: Option<PromptEmbeddedContextCapabilities>,
5009 #[serde_as(deserialize_as = "DefaultOnError")]
5015 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
5016 #[serde(default)]
5017 #[serde(rename = "_meta")]
5018 pub meta: Option<Meta>,
5019}
5020
5021impl PromptCapabilities {
5022 #[must_use]
5024 pub fn new() -> Self {
5025 Self::default()
5026 }
5027
5028 #[must_use]
5033 pub fn image(mut self, image: impl IntoOption<PromptImageCapabilities>) -> Self {
5034 self.image = image.into_option();
5035 self
5036 }
5037
5038 #[must_use]
5043 pub fn audio(mut self, audio: impl IntoOption<PromptAudioCapabilities>) -> Self {
5044 self.audio = audio.into_option();
5045 self
5046 }
5047
5048 #[must_use]
5056 pub fn embedded_context(
5057 mut self,
5058 embedded_context: impl IntoOption<PromptEmbeddedContextCapabilities>,
5059 ) -> Self {
5060 self.embedded_context = embedded_context.into_option();
5061 self
5062 }
5063
5064 #[must_use]
5070 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
5071 self.meta = meta.into_option();
5072 self
5073 }
5074}
5075
5076#[serde_as]
5080#[skip_serializing_none]
5081#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
5082#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
5083#[non_exhaustive]
5084pub struct PromptImageCapabilities {
5085 #[serde_as(deserialize_as = "DefaultOnError")]
5091 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
5092 #[serde(default)]
5093 #[serde(rename = "_meta")]
5094 pub meta: Option<Meta>,
5095}
5096
5097impl PromptImageCapabilities {
5098 #[must_use]
5100 pub fn new() -> Self {
5101 Self::default()
5102 }
5103
5104 #[must_use]
5110 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
5111 self.meta = meta.into_option();
5112 self
5113 }
5114}
5115
5116#[serde_as]
5120#[skip_serializing_none]
5121#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
5122#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
5123#[non_exhaustive]
5124pub struct PromptAudioCapabilities {
5125 #[serde_as(deserialize_as = "DefaultOnError")]
5131 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
5132 #[serde(default)]
5133 #[serde(rename = "_meta")]
5134 pub meta: Option<Meta>,
5135}
5136
5137impl PromptAudioCapabilities {
5138 #[must_use]
5140 pub fn new() -> Self {
5141 Self::default()
5142 }
5143
5144 #[must_use]
5150 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
5151 self.meta = meta.into_option();
5152 self
5153 }
5154}
5155
5156#[serde_as]
5160#[skip_serializing_none]
5161#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
5162#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
5163#[non_exhaustive]
5164pub struct PromptEmbeddedContextCapabilities {
5165 #[serde_as(deserialize_as = "DefaultOnError")]
5171 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
5172 #[serde(default)]
5173 #[serde(rename = "_meta")]
5174 pub meta: Option<Meta>,
5175}
5176
5177impl PromptEmbeddedContextCapabilities {
5178 #[must_use]
5180 pub fn new() -> Self {
5181 Self::default()
5182 }
5183
5184 #[must_use]
5190 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
5191 self.meta = meta.into_option();
5192 self
5193 }
5194}
5195
5196#[serde_as]
5198#[skip_serializing_none]
5199#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
5200#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
5201#[serde(rename_all = "camelCase")]
5202#[non_exhaustive]
5203pub struct McpCapabilities {
5204 #[serde_as(deserialize_as = "DefaultOnError")]
5209 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
5210 #[serde(default)]
5211 pub stdio: Option<McpStdioCapabilities>,
5212 #[serde_as(deserialize_as = "DefaultOnError")]
5217 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
5218 #[serde(default)]
5219 pub http: Option<McpHttpCapabilities>,
5220 #[cfg(feature = "unstable_mcp_over_acp")]
5229 #[serde_as(deserialize_as = "DefaultOnError")]
5230 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
5231 #[serde(default)]
5232 pub acp: Option<McpAcpCapabilities>,
5233 #[serde_as(deserialize_as = "DefaultOnError")]
5239 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
5240 #[serde(default)]
5241 #[serde(rename = "_meta")]
5242 pub meta: Option<Meta>,
5243}
5244
5245impl McpCapabilities {
5246 #[must_use]
5248 pub fn new() -> Self {
5249 Self::default()
5250 }
5251
5252 #[must_use]
5257 pub fn stdio(mut self, stdio: impl IntoOption<McpStdioCapabilities>) -> Self {
5258 self.stdio = stdio.into_option();
5259 self
5260 }
5261
5262 #[must_use]
5267 pub fn http(mut self, http: impl IntoOption<McpHttpCapabilities>) -> Self {
5268 self.http = http.into_option();
5269 self
5270 }
5271
5272 #[cfg(feature = "unstable_mcp_over_acp")]
5278 #[must_use]
5282 pub fn acp(mut self, acp: impl IntoOption<McpAcpCapabilities>) -> Self {
5283 self.acp = acp.into_option();
5284 self
5285 }
5286
5287 #[must_use]
5293 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
5294 self.meta = meta.into_option();
5295 self
5296 }
5297}
5298
5299#[serde_as]
5303#[skip_serializing_none]
5304#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
5305#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
5306#[non_exhaustive]
5307pub struct McpStdioCapabilities {
5308 #[serde_as(deserialize_as = "DefaultOnError")]
5314 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
5315 #[serde(default)]
5316 #[serde(rename = "_meta")]
5317 pub meta: Option<Meta>,
5318}
5319
5320impl McpStdioCapabilities {
5321 #[must_use]
5323 pub fn new() -> Self {
5324 Self::default()
5325 }
5326
5327 #[must_use]
5333 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
5334 self.meta = meta.into_option();
5335 self
5336 }
5337}
5338
5339#[serde_as]
5343#[skip_serializing_none]
5344#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
5345#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
5346#[non_exhaustive]
5347pub struct McpHttpCapabilities {
5348 #[serde_as(deserialize_as = "DefaultOnError")]
5354 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
5355 #[serde(default)]
5356 #[serde(rename = "_meta")]
5357 pub meta: Option<Meta>,
5358}
5359
5360impl McpHttpCapabilities {
5361 #[must_use]
5363 pub fn new() -> Self {
5364 Self::default()
5365 }
5366
5367 #[must_use]
5373 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
5374 self.meta = meta.into_option();
5375 self
5376 }
5377}
5378
5379#[cfg(feature = "unstable_mcp_over_acp")]
5387#[serde_as]
5388#[skip_serializing_none]
5389#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
5390#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
5391#[non_exhaustive]
5392pub struct McpAcpCapabilities {
5393 #[serde_as(deserialize_as = "DefaultOnError")]
5399 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
5400 #[serde(default)]
5401 #[serde(rename = "_meta")]
5402 pub meta: Option<Meta>,
5403}
5404
5405#[cfg(feature = "unstable_mcp_over_acp")]
5406impl McpAcpCapabilities {
5407 #[must_use]
5409 pub fn new() -> Self {
5410 Self::default()
5411 }
5412
5413 #[must_use]
5419 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
5420 self.meta = meta.into_option();
5421 self
5422 }
5423}
5424
5425#[serde_as]
5429#[skip_serializing_none]
5430#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
5431#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
5432#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = SESSION_CANCEL_METHOD_NAME)))]
5433#[serde(rename_all = "camelCase")]
5434#[non_exhaustive]
5435pub struct CancelSessionNotification {
5436 pub session_id: SessionId,
5438 #[serde_as(deserialize_as = "DefaultOnError")]
5444 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
5445 #[serde(default)]
5446 #[serde(rename = "_meta")]
5447 pub meta: Option<Meta>,
5448}
5449
5450impl CancelSessionNotification {
5451 #[must_use]
5453 pub fn new(session_id: impl Into<SessionId>) -> Self {
5454 Self {
5455 session_id: session_id.into(),
5456 meta: None,
5457 }
5458 }
5459
5460 #[must_use]
5466 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
5467 self.meta = meta.into_option();
5468 self
5469 }
5470}
5471
5472#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
5478#[non_exhaustive]
5479pub struct AgentMethodNames {
5480 pub initialize: &'static str,
5482 pub auth_login: &'static str,
5484 #[cfg(feature = "unstable_llm_providers")]
5486 pub providers_list: &'static str,
5487 #[cfg(feature = "unstable_llm_providers")]
5489 pub providers_set: &'static str,
5490 #[cfg(feature = "unstable_llm_providers")]
5492 pub providers_disable: &'static str,
5493 pub session_new: &'static str,
5495 pub session_set_config_option: &'static str,
5497 pub session_prompt: &'static str,
5499 pub session_cancel: &'static str,
5501 #[cfg(feature = "unstable_mcp_over_acp")]
5503 pub mcp_message: &'static str,
5504 pub session_list: &'static str,
5506 pub session_delete: &'static str,
5508 #[cfg(feature = "unstable_session_fork")]
5510 pub session_fork: &'static str,
5511 pub session_resume: &'static str,
5513 pub session_close: &'static str,
5515 pub auth_logout: &'static str,
5517}
5518
5519pub const AGENT_METHOD_NAMES: AgentMethodNames = AgentMethodNames {
5521 initialize: INITIALIZE_METHOD_NAME,
5522 auth_login: AUTH_LOGIN_METHOD_NAME,
5523 #[cfg(feature = "unstable_llm_providers")]
5524 providers_list: PROVIDERS_LIST_METHOD_NAME,
5525 #[cfg(feature = "unstable_llm_providers")]
5526 providers_set: PROVIDERS_SET_METHOD_NAME,
5527 #[cfg(feature = "unstable_llm_providers")]
5528 providers_disable: PROVIDERS_DISABLE_METHOD_NAME,
5529 session_new: SESSION_NEW_METHOD_NAME,
5530 session_set_config_option: SESSION_SET_CONFIG_OPTION_METHOD_NAME,
5531 session_prompt: SESSION_PROMPT_METHOD_NAME,
5532 session_cancel: SESSION_CANCEL_METHOD_NAME,
5533 #[cfg(feature = "unstable_mcp_over_acp")]
5534 mcp_message: MCP_MESSAGE_METHOD_NAME,
5535 session_list: SESSION_LIST_METHOD_NAME,
5536 session_delete: SESSION_DELETE_METHOD_NAME,
5537 #[cfg(feature = "unstable_session_fork")]
5538 session_fork: SESSION_FORK_METHOD_NAME,
5539 session_resume: SESSION_RESUME_METHOD_NAME,
5540 session_close: SESSION_CLOSE_METHOD_NAME,
5541 auth_logout: AUTH_LOGOUT_METHOD_NAME,
5542};
5543
5544pub(crate) const INITIALIZE_METHOD_NAME: &str = "initialize";
5546pub(crate) const AUTH_LOGIN_METHOD_NAME: &str = "auth/login";
5548#[cfg(feature = "unstable_llm_providers")]
5550pub(crate) const PROVIDERS_LIST_METHOD_NAME: &str = "providers/list";
5551#[cfg(feature = "unstable_llm_providers")]
5553pub(crate) const PROVIDERS_SET_METHOD_NAME: &str = "providers/set";
5554#[cfg(feature = "unstable_llm_providers")]
5556pub(crate) const PROVIDERS_DISABLE_METHOD_NAME: &str = "providers/disable";
5557pub(crate) const SESSION_NEW_METHOD_NAME: &str = "session/new";
5559pub(crate) const SESSION_SET_CONFIG_OPTION_METHOD_NAME: &str = "session/set_config_option";
5561pub(crate) const SESSION_PROMPT_METHOD_NAME: &str = "session/prompt";
5563pub(crate) const SESSION_CANCEL_METHOD_NAME: &str = "session/cancel";
5565pub(crate) const SESSION_LIST_METHOD_NAME: &str = "session/list";
5567pub(crate) const SESSION_DELETE_METHOD_NAME: &str = "session/delete";
5569#[cfg(feature = "unstable_session_fork")]
5571pub(crate) const SESSION_FORK_METHOD_NAME: &str = "session/fork";
5572pub(crate) const SESSION_RESUME_METHOD_NAME: &str = "session/resume";
5574pub(crate) const SESSION_CLOSE_METHOD_NAME: &str = "session/close";
5576pub(crate) const AUTH_LOGOUT_METHOD_NAME: &str = "auth/logout";
5578
5579#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
5586#[derive(Clone, Debug, Serialize, Deserialize)]
5587#[serde(untagged)]
5588#[cfg_attr(feature = "schemars", schemars(inline))]
5589#[non_exhaustive]
5590pub enum ClientRequest {
5591 InitializeRequest(Box<InitializeRequest>),
5602 LoginAuthRequest(Box<LoginAuthRequest>),
5617 #[cfg(feature = "unstable_llm_providers")]
5623 ListProvidersRequest(Box<ListProvidersRequest>),
5624 #[cfg(feature = "unstable_llm_providers")]
5630 SetProviderRequest(Box<SetProviderRequest>),
5631 #[cfg(feature = "unstable_llm_providers")]
5637 DisableProviderRequest(Box<DisableProviderRequest>),
5638 LogoutAuthRequest(Box<LogoutAuthRequest>),
5648 NewSessionRequest(Box<NewSessionRequest>),
5661 ListSessionsRequest(Box<ListSessionsRequest>),
5665 DeleteSessionRequest(Box<DeleteSessionRequest>),
5669 #[cfg(feature = "unstable_session_fork")]
5670 ForkSessionRequest(Box<ForkSessionRequest>),
5686 ResumeSessionRequest(Box<ResumeSessionRequest>),
5692 CloseSessionRequest(Box<CloseSessionRequest>),
5697 SetSessionConfigOptionRequest(Box<SetSessionConfigOptionRequest>),
5699 PromptRequest(Box<PromptRequest>),
5721 ExtMethodRequest(Box<ExtRequest>),
5728}
5729
5730impl ClientRequest {
5731 #[must_use]
5733 pub fn method(&self) -> &str {
5734 match self {
5735 Self::InitializeRequest(_) => AGENT_METHOD_NAMES.initialize,
5736 Self::LoginAuthRequest(_) => AGENT_METHOD_NAMES.auth_login,
5737 #[cfg(feature = "unstable_llm_providers")]
5738 Self::ListProvidersRequest(_) => AGENT_METHOD_NAMES.providers_list,
5739 #[cfg(feature = "unstable_llm_providers")]
5740 Self::SetProviderRequest(_) => AGENT_METHOD_NAMES.providers_set,
5741 #[cfg(feature = "unstable_llm_providers")]
5742 Self::DisableProviderRequest(_) => AGENT_METHOD_NAMES.providers_disable,
5743 Self::LogoutAuthRequest(_) => AGENT_METHOD_NAMES.auth_logout,
5744 Self::NewSessionRequest(_) => AGENT_METHOD_NAMES.session_new,
5745 Self::ListSessionsRequest(_) => AGENT_METHOD_NAMES.session_list,
5746 Self::DeleteSessionRequest(_) => AGENT_METHOD_NAMES.session_delete,
5747 #[cfg(feature = "unstable_session_fork")]
5748 Self::ForkSessionRequest(_) => AGENT_METHOD_NAMES.session_fork,
5749 Self::ResumeSessionRequest(_) => AGENT_METHOD_NAMES.session_resume,
5750 Self::CloseSessionRequest(_) => AGENT_METHOD_NAMES.session_close,
5751 Self::SetSessionConfigOptionRequest(_) => AGENT_METHOD_NAMES.session_set_config_option,
5752 Self::PromptRequest(_) => AGENT_METHOD_NAMES.session_prompt,
5753 Self::ExtMethodRequest(ext_request) => &ext_request.method,
5754 }
5755 }
5756}
5757
5758#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
5765#[derive(Clone, Debug, Serialize, Deserialize)]
5766#[serde(untagged)]
5767#[cfg_attr(feature = "schemars", schemars(inline))]
5768#[non_exhaustive]
5769pub enum AgentResponse {
5770 InitializeResponse(Box<InitializeResponse>),
5772 LoginAuthResponse(#[serde(default)] Box<LoginAuthResponse>),
5774 #[cfg(feature = "unstable_llm_providers")]
5776 ListProvidersResponse(Box<ListProvidersResponse>),
5777 #[cfg(feature = "unstable_llm_providers")]
5779 SetProviderResponse(#[serde(default)] Box<SetProviderResponse>),
5780 #[cfg(feature = "unstable_llm_providers")]
5782 DisableProviderResponse(#[serde(default)] Box<DisableProviderResponse>),
5783 LogoutAuthResponse(#[serde(default)] Box<LogoutAuthResponse>),
5785 NewSessionResponse(Box<NewSessionResponse>),
5787 ListSessionsResponse(Box<ListSessionsResponse>),
5789 DeleteSessionResponse(#[serde(default)] Box<DeleteSessionResponse>),
5791 #[cfg(feature = "unstable_session_fork")]
5793 ForkSessionResponse(Box<ForkSessionResponse>),
5794 ResumeSessionResponse(#[serde(default)] Box<ResumeSessionResponse>),
5796 CloseSessionResponse(#[serde(default)] Box<CloseSessionResponse>),
5798 SetSessionConfigOptionResponse(Box<SetSessionConfigOptionResponse>),
5800 PromptResponse(Box<PromptResponse>),
5802 ExtMethodResponse(Box<ExtResponse>),
5804}
5805
5806#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
5813#[derive(Clone, Debug, Serialize, Deserialize)]
5814#[serde(untagged)]
5815#[cfg_attr(feature = "schemars", schemars(inline))]
5816#[non_exhaustive]
5817pub enum ClientNotification {
5818 CancelSessionNotification(Box<CancelSessionNotification>),
5843 #[cfg(feature = "unstable_mcp_over_acp")]
5849 MessageMcpNotification(Box<MessageMcpNotification>),
5850 ExtNotification(Box<ExtNotification>),
5857}
5858
5859impl ClientNotification {
5860 #[must_use]
5862 pub fn method(&self) -> &str {
5863 match self {
5864 Self::CancelSessionNotification(_) => AGENT_METHOD_NAMES.session_cancel,
5865 #[cfg(feature = "unstable_mcp_over_acp")]
5866 Self::MessageMcpNotification(_) => AGENT_METHOD_NAMES.mcp_message,
5867 Self::ExtNotification(ext_notification) => &ext_notification.method,
5868 }
5869 }
5870}
5871
5872#[cfg(test)]
5873mod test_serialization {
5874 use std::path::PathBuf;
5875
5876 use super::*;
5877 use serde_json::json;
5878
5879 fn test_meta() -> Meta {
5880 json!({ "source": "test" }).as_object().unwrap().clone()
5881 }
5882
5883 #[test]
5884 fn list_sessions_cursor_and_session_directories_are_strict() {
5885 for cursor in [json!(null), json!("next")] {
5886 let response = json!({ "sessions": [], "nextCursor": cursor });
5887 serde_json::from_value::<ListSessionsResponse>(response).unwrap();
5888 }
5889 let malformed = json!({ "sessions": [], "nextCursor": 42 });
5890 assert!(serde_json::from_value::<ListSessionsResponse>(malformed).is_err());
5891
5892 let session = |dirs: serde_json::Value| json!({ "sessionId": "s", "cwd": "/w", "additionalDirectories": dirs });
5893 let info: SessionInfo = serde_json::from_value(session(json!(null))).unwrap();
5894 assert_eq!(info.additional_directories, Vec::<AbsolutePath>::new());
5895 for bad in [json!(["/a", 42]), json!("/a")] {
5896 assert!(serde_json::from_value::<SessionInfo>(session(bad)).is_err());
5897 }
5898 let response = json!({ "sessions": [session(json!(["/a", 42]))] });
5900 let response: ListSessionsResponse = serde_json::from_value(response).unwrap();
5901 assert_eq!(response.sessions, Vec::<SessionInfo>::new());
5902 }
5903
5904 #[test]
5905 fn other_fallbacks_preserve_optional_fields_verbatim() {
5906 let valid = json!({ "type": "_custom", "methodId": "m", "name": "M",
5907 "description": "d", "_meta": { "k": 1 } });
5908 let method: AuthMethod = serde_json::from_value(valid.clone()).unwrap();
5909 assert_eq!(method.description(), Some("d"));
5910 assert_eq!(
5911 method.meta(),
5912 Some(&json!({ "k": 1 }).as_object().unwrap().clone())
5913 );
5914 assert_eq!(serde_json::to_value(&method).unwrap(), valid);
5915
5916 let malformed = json!({ "type": "_custom", "methodId": "m", "name": "M",
5918 "description": 42, "_meta": "bad", "extra": true });
5919 let method: AuthMethod = serde_json::from_value(malformed.clone()).unwrap();
5920 assert_eq!(method.description(), None);
5921 assert_eq!(method.meta(), None);
5922 assert_eq!(serde_json::to_value(&method).unwrap(), malformed);
5923
5924 let replay = json!({ "type": "_custom", "_meta": "bad", "cursor": 3 });
5925 let decoded: ReplayFrom = serde_json::from_value(replay.clone()).unwrap();
5926 assert_eq!(serde_json::to_value(&decoded).unwrap(), replay);
5927 }
5928
5929 fn serialized_meta_key_count(value: &impl serde::Serialize) -> usize {
5930 serde_json::to_string(value)
5931 .unwrap()
5932 .matches("\"_meta\"")
5933 .count()
5934 }
5935
5936 #[test]
5937 fn prompt_response_without_metadata_round_trips() {
5938 let response = PromptResponse::new(MessageId::new("message-1"));
5939 let serialized = serde_json::to_value(&response).unwrap();
5940 assert_eq!(serialized, json!({ "messageId": "message-1" }));
5941 assert_eq!(
5942 serde_json::from_value::<PromptResponse>(serialized).unwrap(),
5943 response
5944 );
5945 assert_eq!(
5946 serde_json::from_value::<PromptResponse>(json!({
5947 "messageId": "message-1",
5948 "_meta": null
5949 }))
5950 .unwrap(),
5951 response
5952 );
5953 }
5954
5955 #[test]
5956 fn prompt_response_requires_message_id_and_preserves_metadata_tolerance() {
5957 let response = PromptResponse::new("message-1").meta(test_meta());
5958 let serialized = serde_json::to_value(&response).unwrap();
5959 assert_eq!(
5960 serialized,
5961 json!({
5962 "messageId": "message-1",
5963 "_meta": { "source": "test" }
5964 })
5965 );
5966
5967 let deserialized: PromptResponse = serde_json::from_value(serialized).unwrap();
5968 assert_eq!(deserialized.message_id, MessageId::new("message-1"));
5969 assert_eq!(deserialized.meta, Some(test_meta()));
5970
5971 let malformed_meta: PromptResponse = serde_json::from_value(json!({
5972 "messageId": "message-2",
5973 "_meta": false
5974 }))
5975 .unwrap();
5976 assert_eq!(malformed_meta.message_id, MessageId::new("message-2"));
5977 assert_eq!(malformed_meta.meta, None);
5978
5979 for invalid in [
5980 json!({}),
5981 json!({ "messageId": null }),
5982 json!({ "messageId": 1 }),
5983 json!({ "messageId": false }),
5984 json!({ "messageId": {} }),
5985 json!({ "messageId": [] }),
5986 ] {
5987 assert!(
5988 serde_json::from_value::<PromptResponse>(invalid).is_err(),
5989 "missing, null, and non-string message IDs must be rejected"
5990 );
5991 }
5992 }
5993
5994 #[cfg(feature = "schemars")]
5995 #[test]
5996 fn prompt_response_schema_requires_non_null_string_message_id_reference() {
5997 let schema = serde_json::to_value(schemars::schema_for!(PromptResponse)).unwrap();
5998
5999 assert_eq!(schema["required"], json!(["messageId"]));
6000 assert_eq!(
6001 schema["properties"]["messageId"]["$ref"],
6002 "#/$defs/MessageId"
6003 );
6004 assert_eq!(schema["$defs"]["MessageId"]["type"], "string");
6005 }
6006
6007 #[test]
6008 fn test_initialize_capabilities_default_on_malformed_values() {
6009 let request: InitializeRequest = serde_json::from_value(json!({
6010 "protocolVersion": 2,
6011 "capabilities": false,
6012 "info": {
6013 "name": "client",
6014 "version": "1.0.0"
6015 }
6016 }))
6017 .unwrap();
6018 assert_eq!(request.capabilities, ClientCapabilities::default());
6019
6020 let response: InitializeResponse = serde_json::from_value(json!({
6021 "protocolVersion": 2,
6022 "capabilities": false,
6023 "info": {
6024 "name": "agent",
6025 "version": "1.0.0"
6026 }
6027 }))
6028 .unwrap();
6029 assert_eq!(response.capabilities, AgentCapabilities::default());
6030 }
6031
6032 #[test]
6033 fn test_agent_capabilities_default_on_malformed_values() {
6034 let capabilities: AgentCapabilities = serde_json::from_value(json!({
6035 "session": false,
6036 "auth": false
6037 }))
6038 .unwrap();
6039
6040 assert!(capabilities.session.is_none());
6041 assert_eq!(capabilities.auth, None);
6042 }
6043
6044 #[test]
6045 fn test_mcp_server_stdio_serialization() {
6046 let server = McpServer::Stdio(
6047 McpServerStdio::new("test-server", "/usr/bin/server")
6048 .args(vec!["--port".to_string(), "3000".to_string()])
6049 .env(vec![EnvVariable::new("API_KEY", "secret123")]),
6050 );
6051
6052 let json = serde_json::to_value(&server).unwrap();
6053 assert_eq!(
6054 json,
6055 json!({
6056 "type": "stdio",
6057 "name": "test-server",
6058 "command": "/usr/bin/server",
6059 "args": ["--port", "3000"],
6060 "env": [
6061 {
6062 "name": "API_KEY",
6063 "value": "secret123"
6064 }
6065 ]
6066 })
6067 );
6068
6069 let deserialized: McpServer = serde_json::from_value(json).unwrap();
6070 match deserialized {
6071 McpServer::Stdio(McpServerStdio {
6072 name,
6073 command,
6074 args,
6075 env,
6076 meta: _,
6077 }) => {
6078 assert_eq!(name, "test-server");
6079 assert_eq!(command, AbsolutePath::new("/usr/bin/server"));
6080 assert_eq!(args, vec!["--port", "3000"]);
6081 assert_eq!(env.len(), 1);
6082 assert_eq!(env[0].name, "API_KEY");
6083 assert_eq!(env[0].value, "secret123");
6084 }
6085 _ => panic!("Expected Stdio variant"),
6086 }
6087 }
6088
6089 #[test]
6090 fn test_mcp_server_empty_arrays_are_optional() {
6091 let stdio = McpServer::Stdio(McpServerStdio::new("test-server", "/usr/bin/server"));
6092 assert_eq!(
6093 serde_json::to_value(&stdio).unwrap(),
6094 json!({
6095 "type": "stdio",
6096 "name": "test-server",
6097 "command": "/usr/bin/server"
6098 })
6099 );
6100
6101 let McpServer::Stdio(McpServerStdio { args, env, .. }) =
6102 serde_json::from_value::<McpServer>(json!({
6103 "type": "stdio",
6104 "name": "test-server",
6105 "command": "/usr/bin/server"
6106 }))
6107 .unwrap()
6108 else {
6109 panic!("Expected Stdio variant");
6110 };
6111 assert_eq!(args, Vec::<String>::new());
6112 assert_eq!(env, []);
6113
6114 let http = McpServer::Http(McpServerHttp::new("http-server", "https://api.example.com"));
6115 assert_eq!(
6116 serde_json::to_value(&http).unwrap(),
6117 json!({
6118 "type": "http",
6119 "name": "http-server",
6120 "url": "https://api.example.com"
6121 })
6122 );
6123
6124 let McpServer::Http(McpServerHttp { headers, .. }) =
6125 serde_json::from_value::<McpServer>(json!({
6126 "type": "http",
6127 "name": "http-server",
6128 "url": "https://api.example.com"
6129 }))
6130 .unwrap()
6131 else {
6132 panic!("Expected Http variant");
6133 };
6134 assert_eq!(headers, []);
6135 }
6136
6137 #[test]
6138 fn test_mcp_server_unknown_transport_serialization() {
6139 let json = json!({
6140 "type": "websocket",
6141 "name": "future-server",
6142 "url": "wss://example.com/mcp",
6143 "protocolVersion": "2026-01-01"
6144 });
6145
6146 let deserialized: McpServer = serde_json::from_value(json.clone()).unwrap();
6147 let McpServer::Other(OtherMcpServer { type_, fields }) = &deserialized else {
6148 panic!("Expected Other variant");
6149 };
6150
6151 assert_eq!(type_, "websocket");
6152 assert_eq!(fields["name"], "future-server");
6153 assert_eq!(fields["url"], "wss://example.com/mcp");
6154 assert_eq!(fields["protocolVersion"], "2026-01-01");
6155 assert_eq!(serde_json::to_value(&deserialized).unwrap(), json);
6156 }
6157
6158 #[test]
6159 fn test_mcp_server_stdio_requires_type() {
6160 let result = serde_json::from_value::<McpServer>(json!({
6161 "name": "test-server",
6162 "command": "/usr/bin/server",
6163 "args": [],
6164 "env": []
6165 }));
6166
6167 assert!(result.is_err());
6168 }
6169
6170 #[test]
6171 fn test_mcp_server_unknown_does_not_hide_malformed_known_transport() {
6172 let result = serde_json::from_value::<McpServer>(json!({
6173 "type": "stdio",
6174 "name": "test-server",
6175 "args": [],
6176 "env": []
6177 }));
6178
6179 assert!(result.is_err());
6180 }
6181
6182 #[test]
6183 fn test_mcp_server_http_serialization() {
6184 let server = McpServer::Http(
6185 McpServerHttp::new("http-server", "https://api.example.com").headers(vec![
6186 HttpHeader::new("Authorization", "Bearer token123"),
6187 HttpHeader::new("Content-Type", "application/json"),
6188 ]),
6189 );
6190
6191 let json = serde_json::to_value(&server).unwrap();
6192 assert_eq!(
6193 json,
6194 json!({
6195 "type": "http",
6196 "name": "http-server",
6197 "url": "https://api.example.com",
6198 "headers": [
6199 {
6200 "name": "Authorization",
6201 "value": "Bearer token123"
6202 },
6203 {
6204 "name": "Content-Type",
6205 "value": "application/json"
6206 }
6207 ]
6208 })
6209 );
6210
6211 let deserialized: McpServer = serde_json::from_value(json).unwrap();
6212 match deserialized {
6213 McpServer::Http(McpServerHttp {
6214 name,
6215 url,
6216 headers,
6217 meta: _,
6218 }) => {
6219 assert_eq!(name, "http-server");
6220 assert_eq!(url, "https://api.example.com");
6221 assert_eq!(headers.len(), 2);
6222 assert_eq!(headers[0].name, "Authorization");
6223 assert_eq!(headers[0].value, "Bearer token123");
6224 assert_eq!(headers[1].name, "Content-Type");
6225 assert_eq!(headers[1].value, "application/json");
6226 }
6227 _ => panic!("Expected Http variant"),
6228 }
6229 }
6230
6231 #[cfg(feature = "schemars")]
6232 #[test]
6233 fn mcp_server_http_schema_marks_url_as_uri() {
6234 let schema = serde_json::to_value(schemars::schema_for!(McpServerHttp)).unwrap();
6235
6236 assert_eq!(schema["properties"]["url"]["format"], "uri");
6237 }
6238
6239 #[cfg(feature = "unstable_mcp_over_acp")]
6240 #[test]
6241 fn test_client_mcp_message_method_names() {
6242 use serde_json::json;
6243
6244 assert_eq!(AGENT_METHOD_NAMES.mcp_message, "mcp/message");
6245
6246 let notification =
6247 MessageMcpNotification::new("server-1", "req-1", "notifications/progress");
6248 assert_eq!(
6249 ClientNotification::MessageMcpNotification(Box::new(notification.clone())).method(),
6250 "mcp/message"
6251 );
6252 assert_eq!(
6253 serde_json::to_value(notification).unwrap(),
6254 json!({
6255 "serverId": "server-1",
6256 "requestId": "req-1",
6257 "method": "notifications/progress"
6258 })
6259 );
6260 let notification: MessageMcpNotification = serde_json::from_value(json!({
6261 "serverId": "server-1", "requestId": "req-1", "method": "notifications/progress",
6262 "params": null, "_meta": null
6263 }))
6264 .unwrap();
6265 assert_eq!(notification.params, None);
6266 assert_eq!(notification.meta, None);
6267 for key in ["serverId", "requestId", "method"] {
6268 let mut value = json!({"serverId":"server-1", "requestId":"req-1", "method":"notifications/progress"});
6269 value.as_object_mut().unwrap().remove(key);
6270 assert!(serde_json::from_value::<MessageMcpNotification>(value).is_err());
6271 }
6272 }
6273
6274 #[test]
6275 fn test_auth_method_names() {
6276 assert_eq!(AGENT_METHOD_NAMES.auth_login, "auth/login");
6277 assert_eq!(AGENT_METHOD_NAMES.auth_logout, "auth/logout");
6278
6279 assert_eq!(
6280 ClientRequest::LoginAuthRequest(Box::new(LoginAuthRequest::new("agent-login")))
6281 .method(),
6282 "auth/login"
6283 );
6284 assert_eq!(
6285 ClientRequest::LogoutAuthRequest(Box::new(LogoutAuthRequest::new())).method(),
6286 "auth/logout"
6287 );
6288 }
6289
6290 #[test]
6291 fn test_session_config_option_category_known_variants() {
6292 assert_eq!(
6294 serde_json::to_value(&SessionConfigOptionCategory::Mode).unwrap(),
6295 json!("mode")
6296 );
6297 assert_eq!(
6298 serde_json::to_value(&SessionConfigOptionCategory::Model).unwrap(),
6299 json!("model")
6300 );
6301 assert_eq!(
6302 serde_json::to_value(&SessionConfigOptionCategory::ModelConfig).unwrap(),
6303 json!("model_config")
6304 );
6305 assert_eq!(
6306 serde_json::to_value(&SessionConfigOptionCategory::ThoughtLevel).unwrap(),
6307 json!("thought_level")
6308 );
6309
6310 assert_eq!(
6312 serde_json::from_str::<SessionConfigOptionCategory>("\"mode\"").unwrap(),
6313 SessionConfigOptionCategory::Mode
6314 );
6315 assert_eq!(
6316 serde_json::from_str::<SessionConfigOptionCategory>("\"model\"").unwrap(),
6317 SessionConfigOptionCategory::Model
6318 );
6319 assert_eq!(
6320 serde_json::from_str::<SessionConfigOptionCategory>("\"model_config\"").unwrap(),
6321 SessionConfigOptionCategory::ModelConfig
6322 );
6323 assert_eq!(
6324 serde_json::from_str::<SessionConfigOptionCategory>("\"thought_level\"").unwrap(),
6325 SessionConfigOptionCategory::ThoughtLevel
6326 );
6327 }
6328
6329 #[test]
6330 fn test_session_config_option_category_unknown_variants() {
6331 let unknown: SessionConfigOptionCategory =
6333 serde_json::from_str("\"some_future_category\"").unwrap();
6334 assert_eq!(
6335 unknown,
6336 SessionConfigOptionCategory::Other("some_future_category".to_string())
6337 );
6338
6339 let json = serde_json::to_value(&unknown).unwrap();
6341 assert_eq!(json, json!("some_future_category"));
6342 }
6343
6344 #[test]
6345 fn test_session_config_option_category_custom_categories() {
6346 let custom: SessionConfigOptionCategory =
6348 serde_json::from_str("\"_my_custom_category\"").unwrap();
6349 assert_eq!(
6350 custom,
6351 SessionConfigOptionCategory::Other("_my_custom_category".to_string())
6352 );
6353
6354 let json = serde_json::to_value(&custom).unwrap();
6356 assert_eq!(json, json!("_my_custom_category"));
6357
6358 let deserialized: SessionConfigOptionCategory = serde_json::from_value(json).unwrap();
6360 assert_eq!(
6361 deserialized,
6362 SessionConfigOptionCategory::Other("_my_custom_category".to_string()),
6363 );
6364 }
6365
6366 fn test_config_option() -> SessionConfigOption {
6367 SessionConfigOption::select(
6368 "mode",
6369 "Mode",
6370 "ask",
6371 vec![SessionConfigSelectOption::new("ask", "Ask")],
6372 )
6373 }
6374
6375 #[test]
6376 fn test_session_response_initial_state_default_empty_and_skip_serializing() {
6377 assert_eq!(
6378 serde_json::to_value(NewSessionResponse::new("sess")).unwrap(),
6379 json!({ "sessionId": "sess" })
6380 );
6381 assert_eq!(
6382 serde_json::to_value(ResumeSessionResponse::new()).unwrap(),
6383 json!({})
6384 );
6385 #[cfg(feature = "unstable_session_fork")]
6386 assert_eq!(
6387 serde_json::to_value(ForkSessionResponse::new("fork")).unwrap(),
6388 json!({ "sessionId": "fork" })
6389 );
6390
6391 let json = serde_json::to_value(
6392 NewSessionResponse::new("sess").config_options(vec![test_config_option()]),
6393 )
6394 .unwrap();
6395 assert_eq!(json["configOptions"].as_array().unwrap().len(), 1);
6396 }
6397
6398 #[test]
6399 fn test_session_response_config_options_deserialize_missing_null_and_invalid() {
6400 let missing: NewSessionResponse =
6401 serde_json::from_value(json!({ "sessionId": "sess" })).unwrap();
6402 assert_eq!(missing.config_options, []);
6403
6404 let null: NewSessionResponse = serde_json::from_value(json!({
6405 "sessionId": "sess",
6406 "configOptions": null
6407 }))
6408 .unwrap();
6409 assert_eq!(null.config_options, []);
6410
6411 let wrong_shape: NewSessionResponse = serde_json::from_value(json!({
6412 "sessionId": "sess",
6413 "configOptions": "oops"
6414 }))
6415 .unwrap();
6416 assert_eq!(wrong_shape.config_options, []);
6417
6418 let valid_option = serde_json::to_value(test_config_option()).unwrap();
6419 let mixed: NewSessionResponse = serde_json::from_value(json!({
6420 "sessionId": "sess",
6421 "configOptions": ["oops", valid_option]
6422 }))
6423 .unwrap();
6424 assert_eq!(mixed.config_options.len(), 1);
6425
6426 let resume: ResumeSessionResponse = serde_json::from_value(json!({})).unwrap();
6427 assert_eq!(resume.config_options, []);
6428 #[cfg(feature = "unstable_session_fork")]
6429 {
6430 let fork: ForkSessionResponse =
6431 serde_json::from_value(json!({ "sessionId": "fork" })).unwrap();
6432 assert_eq!(fork.config_options, []);
6433 }
6434 }
6435
6436 #[test]
6437 fn test_session_response_available_commands_roundtrip_with_config_options() {
6438 use super::super::{AvailableCommandInput, Command, TextCommandInput};
6439
6440 let commands = vec![
6441 AvailableCommand::Command(
6442 Command::new("web", "Search the web")
6443 .input(AvailableCommandInput::Text(TextCommandInput::new("Query"))),
6444 ),
6445 AvailableCommand::Command(Command::new("test", "Run project tests")),
6446 ];
6447 let mut expected = json!({
6448 "sessionId": "sess",
6449 "configOptions": [test_config_option()],
6450 "availableCommands": [
6451 {
6452 "type": "command",
6453 "name": "web",
6454 "description": "Search the web",
6455 "input": { "type": "text", "hint": "Query" }
6456 },
6457 { "type": "command", "name": "test", "description": "Run project tests" }
6458 ]
6459 });
6460
6461 let new = NewSessionResponse::new("sess")
6462 .config_options(vec![test_config_option()])
6463 .available_commands(commands.clone());
6464 assert_eq!(serde_json::to_value(&new).unwrap(), expected);
6465 assert_eq!(
6466 serde_json::from_value::<NewSessionResponse>(expected.clone()).unwrap(),
6467 new
6468 );
6469
6470 expected.as_object_mut().unwrap().remove("sessionId");
6471 let resume = ResumeSessionResponse::new()
6472 .config_options(vec![test_config_option()])
6473 .available_commands(commands.clone());
6474 assert_eq!(serde_json::to_value(&resume).unwrap(), expected);
6475 assert_eq!(
6476 serde_json::from_value::<ResumeSessionResponse>(expected.clone()).unwrap(),
6477 resume
6478 );
6479
6480 #[cfg(feature = "unstable_session_fork")]
6481 {
6482 expected["sessionId"] = json!("fork");
6483 let fork = ForkSessionResponse::new("fork")
6484 .config_options(vec![test_config_option()])
6485 .available_commands(commands);
6486 assert_eq!(serde_json::to_value(&fork).unwrap(), expected);
6487 assert_eq!(
6488 serde_json::from_value::<ForkSessionResponse>(expected).unwrap(),
6489 fork
6490 );
6491 }
6492 }
6493
6494 #[test]
6495 fn test_session_response_available_commands_deserialize_missing_null_and_invalid() {
6496 let command =
6497 AvailableCommand::Command(super::super::Command::new("test", "Run project tests"));
6498 let config_options = vec![test_config_option()];
6499
6500 for (commands, expected) in [
6501 (None, vec![]),
6502 (Some(json!(null)), vec![]),
6503 (Some(json!([])), vec![]),
6504 (Some(json!("oops")), vec![]),
6505 (Some(json!({})), vec![]),
6506 (Some(json!([null, {}, { "name": "invalid" }])), vec![]),
6507 (
6508 Some(json!([{ "name": "test", "description": "Run project tests" }])),
6509 vec![],
6510 ),
6511 (
6512 Some(json!([null, command, { "name": "invalid" }])),
6513 vec![command.clone()],
6514 ),
6515 ] {
6516 let mut value = json!({
6517 "sessionId": "sess",
6518 "configOptions": config_options
6519 });
6520 if let Some(commands) = commands {
6521 value["availableCommands"] = commands;
6522 }
6523
6524 let new: NewSessionResponse = serde_json::from_value(value.clone()).unwrap();
6525 assert_eq!(new.available_commands, expected);
6526 assert_eq!(new.config_options, config_options);
6527
6528 #[cfg(feature = "unstable_session_fork")]
6529 {
6530 let fork: ForkSessionResponse = serde_json::from_value(value.clone()).unwrap();
6531 assert_eq!(fork.available_commands, expected);
6532 assert_eq!(fork.config_options, config_options);
6533 }
6534
6535 value.as_object_mut().unwrap().remove("sessionId");
6536 let resume: ResumeSessionResponse = serde_json::from_value(value).unwrap();
6537 assert_eq!(resume.available_commands, expected);
6538 assert_eq!(resume.config_options, config_options);
6539 }
6540
6541 let resume: ResumeSessionResponse = serde_json::from_value(json!(null)).unwrap();
6542 assert_eq!(resume.available_commands, []);
6543 }
6544
6545 #[test]
6546 fn test_resume_session_replay_from_serialization() {
6547 assert_eq!(
6548 serde_json::to_value(ResumeSessionRequest::new(
6549 "sess_abc123",
6550 "/home/user/project"
6551 ))
6552 .unwrap(),
6553 json!({
6554 "sessionId": "sess_abc123",
6555 "cwd": "/home/user/project"
6556 })
6557 );
6558 assert_eq!(
6559 serde_json::to_value(
6560 ResumeSessionRequest::new("sess_abc123", "/home/user/project")
6561 .replay_from(ReplayFrom::from(ReplayFromStart::new()))
6562 )
6563 .unwrap(),
6564 json!({
6565 "sessionId": "sess_abc123",
6566 "cwd": "/home/user/project",
6567 "replayFrom": {
6568 "type": "start"
6569 }
6570 })
6571 );
6572
6573 let replay: ResumeSessionRequest = serde_json::from_value(json!({
6574 "sessionId": "sess_abc123",
6575 "cwd": "/home/user/project",
6576 "replayFrom": {
6577 "type": "start"
6578 }
6579 }))
6580 .unwrap();
6581 assert!(matches!(replay.replay_from, Some(ReplayFrom::Start(_))));
6582
6583 let none: ResumeSessionRequest = serde_json::from_value(json!({
6584 "sessionId": "sess_abc123",
6585 "cwd": "/home/user/project",
6586 "replayFrom": null
6587 }))
6588 .unwrap();
6589 assert!(none.replay_from.is_none());
6590 }
6591
6592 #[test]
6593 fn test_auth_method_agent_serialization() {
6594 let method = AuthMethod::Agent(AuthMethodAgent::new("default-auth", "Default Auth"));
6595
6596 let json = serde_json::to_value(&method).unwrap();
6597 assert_eq!(
6598 json,
6599 json!({
6600 "methodId": "default-auth",
6601 "name": "Default Auth",
6602 "type": "agent"
6603 })
6604 );
6605 assert!(!json.as_object().unwrap().contains_key("description"));
6607
6608 let deserialized: AuthMethod = serde_json::from_value(json).unwrap();
6609 match deserialized {
6610 AuthMethod::Agent(AuthMethodAgent {
6611 method_id, name, ..
6612 }) => {
6613 assert_eq!(method_id.0.as_ref(), "default-auth");
6614 assert_eq!(name, "Default Auth");
6615 }
6616 _ => panic!("Expected Agent variant"),
6617 }
6618 }
6619
6620 #[test]
6621 fn test_auth_method_agent_deserialization() {
6622 let json = json!({
6623 "methodId": "agent-auth",
6624 "name": "Agent Auth",
6625 "type": "agent"
6626 });
6627
6628 let deserialized: AuthMethod = serde_json::from_value(json).unwrap();
6629 assert!(matches!(deserialized, AuthMethod::Agent(_)));
6630 }
6631
6632 #[test]
6633 fn test_auth_method_agent_requires_type() {
6634 assert!(
6635 serde_json::from_value::<AuthMethod>(json!({
6636 "methodId": "agent-auth",
6637 "name": "Agent Auth"
6638 }))
6639 .is_err()
6640 );
6641 }
6642
6643 #[test]
6644 fn test_auth_method_agent_rejects_null_type() {
6645 assert!(
6646 serde_json::from_value::<AuthMethod>(json!({
6647 "methodId": "agent-auth",
6648 "name": "Agent Auth",
6649 "type": null
6650 }))
6651 .is_err()
6652 );
6653 }
6654
6655 #[test]
6656 fn test_auth_method_unknown_does_not_hide_malformed_agent() {
6657 assert!(
6658 serde_json::from_value::<AuthMethod>(json!({
6659 "methodId": "agent-auth",
6660 "type": "agent"
6661 }))
6662 .is_err()
6663 );
6664 assert!(
6665 serde_json::from_value::<AuthMethod>(json!({
6666 "methodId": "api-key",
6667 "type": "env_var",
6668 "vars": [{"name": "API_KEY"}]
6669 }))
6670 .is_err()
6671 );
6672 }
6673
6674 #[test]
6675 fn test_auth_method_unknown_variant_roundtrip() {
6676 let method: AuthMethod = serde_json::from_value(json!({
6677 "methodId": "oauth",
6678 "name": "OAuth",
6679 "type": "_oauth",
6680 "authorizationUrl": "https://example.com/auth"
6681 }))
6682 .unwrap();
6683
6684 assert_eq!(method.method_id().0.as_ref(), "oauth");
6685 assert_eq!(method.name(), "OAuth");
6686 let AuthMethod::Other(unknown) = method else {
6687 panic!("expected unknown auth method");
6688 };
6689 assert_eq!(unknown.type_, "_oauth");
6690 assert_eq!(
6691 unknown.fields.get("authorizationUrl"),
6692 Some(&json!("https://example.com/auth"))
6693 );
6694
6695 assert_eq!(
6696 serde_json::to_value(AuthMethod::Other(unknown)).unwrap(),
6697 json!({
6698 "methodId": "oauth",
6699 "name": "OAuth",
6700 "type": "_oauth",
6701 "authorizationUrl": "https://example.com/auth"
6702 })
6703 );
6704 }
6705
6706 #[test]
6707 fn test_auth_method_unknown_does_not_hide_malformed_known_variant() {
6708 assert!(
6709 serde_json::from_value::<AuthMethod>(json!({
6710 "methodId": "terminal-auth",
6711 "type": "terminal"
6712 }))
6713 .is_err()
6714 );
6715 }
6716
6717 #[test]
6718 fn test_session_delete_serialization() {
6719 assert_eq!(AGENT_METHOD_NAMES.session_delete, "session/delete");
6720 assert_eq!(
6721 ClientRequest::DeleteSessionRequest(Box::new(DeleteSessionRequest::new("sess_abc123")))
6722 .method(),
6723 "session/delete"
6724 );
6725 assert_eq!(
6726 serde_json::to_value(DeleteSessionRequest::new("sess_abc123")).unwrap(),
6727 json!({
6728 "sessionId": "sess_abc123"
6729 })
6730 );
6731 assert_eq!(
6732 serde_json::to_value(DeleteSessionResponse::new()).unwrap(),
6733 json!({})
6734 );
6735 assert_eq!(
6736 serde_json::to_value(
6737 SessionCapabilities::new().delete(SessionDeleteCapabilities::new())
6738 )
6739 .unwrap(),
6740 json!({
6741 "delete": {}
6742 })
6743 );
6744 }
6745 #[test]
6746 fn test_session_additional_directories_serialization() {
6747 assert_eq!(
6748 serde_json::to_value(NewSessionRequest::new("/home/user/project")).unwrap(),
6749 json!({
6750 "cwd": "/home/user/project",
6751 })
6752 );
6753 assert_eq!(
6754 serde_json::to_value(
6755 NewSessionRequest::new("/home/user/project").additional_directories(vec![
6756 PathBuf::from("/home/user/shared-lib"),
6757 PathBuf::from("/home/user/product-docs"),
6758 ])
6759 )
6760 .unwrap(),
6761 json!({
6762 "cwd": "/home/user/project",
6763 "additionalDirectories": [
6764 "/home/user/shared-lib",
6765 "/home/user/product-docs"
6766 ],
6767 })
6768 );
6769 assert_eq!(
6770 serde_json::to_value(ResumeSessionRequest::new(
6771 "sess_abc123",
6772 "/home/user/project"
6773 ))
6774 .unwrap(),
6775 json!({
6776 "sessionId": "sess_abc123",
6777 "cwd": "/home/user/project",
6778 })
6779 );
6780 assert_eq!(
6781 serde_json::from_value::<ResumeSessionRequest>(json!({
6782 "sessionId": "sess_abc123",
6783 "cwd": "/home/user/project"
6784 }))
6785 .unwrap()
6786 .mcp_servers,
6787 Vec::<McpServer>::new()
6788 );
6789 assert_eq!(
6790 serde_json::from_value::<ResumeSessionRequest>(json!({
6791 "sessionId": "sess_abc123",
6792 "cwd": "/home/user/project",
6793 "mcpServers": null
6794 }))
6795 .unwrap()
6796 .mcp_servers,
6797 Vec::<McpServer>::new()
6798 );
6799 assert_eq!(
6800 serde_json::to_value(SessionInfo::new("sess_abc123", "/home/user/project")).unwrap(),
6801 json!({
6802 "sessionId": "sess_abc123",
6803 "cwd": "/home/user/project"
6804 })
6805 );
6806 assert_eq!(
6807 serde_json::to_value(
6808 SessionInfo::new("sess_abc123", "/home/user/project").additional_directories(vec![
6809 PathBuf::from("/home/user/shared-lib"),
6810 PathBuf::from("/home/user/product-docs"),
6811 ])
6812 )
6813 .unwrap(),
6814 json!({
6815 "sessionId": "sess_abc123",
6816 "cwd": "/home/user/project",
6817 "additionalDirectories": [
6818 "/home/user/shared-lib",
6819 "/home/user/product-docs"
6820 ]
6821 })
6822 );
6823 assert_eq!(
6824 serde_json::from_value::<SessionInfo>(json!({
6825 "sessionId": "sess_abc123",
6826 "cwd": "/home/user/project"
6827 }))
6828 .unwrap()
6829 .additional_directories,
6830 Vec::<AbsolutePath>::new()
6831 );
6832 }
6833 #[test]
6834 fn test_session_additional_directories_capabilities_serialization() {
6835 assert_eq!(
6836 serde_json::to_value(
6837 SessionCapabilities::new()
6838 .additional_directories(SessionAdditionalDirectoriesCapabilities::new())
6839 )
6840 .unwrap(),
6841 json!({
6842 "additionalDirectories": {}
6843 })
6844 );
6845 }
6846
6847 #[cfg(feature = "unstable_session_system_prompt")]
6848 #[test]
6849 fn test_session_system_prompt_capabilities_serialization() {
6850 assert_eq!(
6851 serde_json::to_value(
6852 SessionCapabilities::new().system_prompt(SessionSystemPromptCapabilities::new())
6853 )
6854 .unwrap(),
6855 json!({ "systemPrompt": {} })
6856 );
6857 assert_eq!(
6858 serde_json::to_value(
6859 SessionCapabilities::new().system_prompt(
6860 SessionSystemPromptCapabilities::new().mode(
6861 SessionSystemPromptModeCapabilities::new()
6862 .override_(SessionSystemPromptOverrideCapabilities::new())
6863 )
6864 )
6865 )
6866 .unwrap(),
6867 json!({ "systemPrompt": { "mode": { "override": {} } } })
6868 );
6869 assert_eq!(
6870 serde_json::from_value::<SessionCapabilities>(
6871 json!({ "systemPrompt": { "mode": { "override": {} } } })
6872 )
6873 .unwrap()
6874 .system_prompt
6875 .and_then(|capabilities| capabilities.mode)
6876 .and_then(|mode| mode.override_),
6877 Some(SessionSystemPromptOverrideCapabilities::new())
6878 );
6879
6880 assert_eq!(
6882 serde_json::from_value::<SessionCapabilities>(json!({ "systemPrompt": "yes" }))
6883 .unwrap()
6884 .system_prompt,
6885 None
6886 );
6887 assert_eq!(
6888 serde_json::from_value::<SessionCapabilities>(
6889 json!({ "systemPrompt": { "mode": { "override": true } } })
6890 )
6891 .unwrap()
6892 .system_prompt,
6893 Some(
6894 SessionSystemPromptCapabilities::new()
6895 .mode(SessionSystemPromptModeCapabilities::new())
6896 )
6897 );
6898 assert_eq!(
6899 serde_json::from_value::<SessionCapabilities>(
6900 json!({ "systemPrompt": { "mode": "override" } })
6901 )
6902 .unwrap()
6903 .system_prompt,
6904 Some(SessionSystemPromptCapabilities::new())
6905 );
6906 }
6907
6908 #[cfg(feature = "unstable_session_system_prompt")]
6909 #[test]
6910 fn test_system_prompt_lifecycle_request_serialization() {
6911 let system_prompt = SystemPrompt::new(vec![ContentBlock::from(
6912 "You are a code review specialist.",
6913 )]);
6914 let system_prompt_json = json!({
6915 "mode": "append",
6916 "content": [{ "type": "text", "text": "You are a code review specialist." }]
6917 });
6918
6919 let request =
6920 NewSessionRequest::new("/home/user/project").system_prompt(system_prompt.clone());
6921 let request_json = json!({
6922 "cwd": "/home/user/project",
6923 "systemPrompt": system_prompt_json
6924 });
6925 assert_eq!(serde_json::to_value(&request).unwrap(), request_json);
6926 assert_eq!(
6927 serde_json::from_value::<NewSessionRequest>(request_json).unwrap(),
6928 request
6929 );
6930
6931 assert_eq!(
6932 serde_json::to_value(
6933 ResumeSessionRequest::new("sess_abc123", "/home/user/project")
6934 .system_prompt(system_prompt.clone())
6935 )
6936 .unwrap()["systemPrompt"],
6937 system_prompt_json
6938 );
6939 #[cfg(feature = "unstable_session_fork")]
6940 assert_eq!(
6941 serde_json::to_value(
6942 ForkSessionRequest::new("sess_abc123", "/home/user/project")
6943 .system_prompt(system_prompt.clone())
6944 )
6945 .unwrap()["systemPrompt"],
6946 system_prompt_json
6947 );
6948
6949 let override_prompt =
6950 SystemPrompt::new(vec![ContentBlock::from("...")]).mode(SystemPromptMode::Override);
6951 let override_json = json!({
6952 "mode": "override",
6953 "content": [{ "type": "text", "text": "..." }]
6954 });
6955 assert_eq!(
6956 serde_json::to_value(&override_prompt).unwrap(),
6957 override_json
6958 );
6959 assert_eq!(
6960 serde_json::from_value::<SystemPrompt>(override_json).unwrap(),
6961 override_prompt
6962 );
6963 }
6964
6965 #[cfg(feature = "unstable_session_system_prompt")]
6968 #[test]
6969 fn test_system_prompt_new_defaults_to_append() {
6970 let system_prompt = SystemPrompt::new(vec![ContentBlock::from("...")]);
6971 assert_eq!(system_prompt.mode, SystemPromptMode::Append);
6972 assert_eq!(
6973 serde_json::to_value(&system_prompt).unwrap(),
6974 json!({ "mode": "append", "content": [{ "type": "text", "text": "..." }] })
6975 );
6976 }
6977
6978 #[cfg(feature = "unstable_session_system_prompt")]
6982 #[test]
6983 fn test_system_prompt_preserves_unknown_modes() {
6984 for mode in ["_vendor_mode", "prepend"] {
6985 let system_prompt_json =
6986 json!({ "mode": mode, "content": [{ "type": "text", "text": "..." }] });
6987 let system_prompt: SystemPrompt =
6988 serde_json::from_value(system_prompt_json.clone()).unwrap();
6989 assert_eq!(
6990 system_prompt.mode,
6991 SystemPromptMode::Other(mode.to_string())
6992 );
6993 assert_eq!(
6994 serde_json::to_value(&system_prompt).unwrap(),
6995 system_prompt_json
6996 );
6997 }
6998 }
6999
7000 #[cfg(feature = "unstable_session_system_prompt")]
7004 fn assert_system_prompt_is_strict<T>(base: &serde_json::Value)
7005 where
7006 T: serde::de::DeserializeOwned + PartialEq + std::fmt::Debug,
7007 {
7008 let name = std::any::type_name::<T>();
7009 let with = |system_prompt: serde_json::Value| {
7010 let mut request = base.clone();
7011 request["systemPrompt"] = system_prompt;
7012 serde_json::from_value::<T>(request)
7013 };
7014
7015 for system_prompt in [
7016 json!("You are a code review specialist."),
7017 json!({ "content": [{ "type": "text", "text": "..." }] }),
7018 json!({ "mode": null, "content": [{ "type": "text", "text": "..." }] }),
7019 json!({ "mode": 1, "content": [{ "type": "text", "text": "..." }] }),
7020 json!({ "mode": "append" }),
7021 json!({ "mode": "append", "content": "..." }),
7022 json!({ "mode": "append", "content": [{ "type": "text" }] }),
7023 ] {
7024 assert!(
7025 with(system_prompt.clone()).is_err(),
7026 "{name}.systemPrompt: {system_prompt}"
7027 );
7028 }
7029
7030 assert_eq!(
7031 with(serde_json::Value::Null).unwrap(),
7032 serde_json::from_value::<T>(base.clone()).unwrap(),
7033 "{name}.systemPrompt"
7034 );
7035 }
7036
7037 #[cfg(feature = "unstable_session_system_prompt")]
7038 #[test]
7039 fn test_session_setup_requests_reject_malformed_system_prompts() {
7040 assert!(
7041 serde_json::to_value(NewSessionRequest::new("/repo"))
7042 .unwrap()
7043 .get("systemPrompt")
7044 .is_none()
7045 );
7046 assert_system_prompt_is_strict::<NewSessionRequest>(&json!({ "cwd": "/repo" }));
7047 assert_system_prompt_is_strict::<ResumeSessionRequest>(&json!({
7048 "sessionId": "sess-1",
7049 "cwd": "/repo"
7050 }));
7051 #[cfg(feature = "unstable_session_fork")]
7052 assert_system_prompt_is_strict::<ForkSessionRequest>(&json!({
7053 "sessionId": "sess-1",
7054 "cwd": "/repo"
7055 }));
7056 }
7057
7058 #[cfg(feature = "unstable_session_system_prompt")]
7061 #[test]
7062 fn test_system_prompt_preserves_unknown_content_blocks() {
7063 let request: NewSessionRequest = serde_json::from_value(json!({
7064 "cwd": "/home/user/project",
7065 "systemPrompt": {
7066 "mode": "append",
7067 "content": [{ "type": "_vendor_video", "uri": "https://example.com/intro.mp4" }]
7068 }
7069 }))
7070 .unwrap();
7071 let content = request.system_prompt.unwrap().content;
7072 assert_eq!(content.len(), 1);
7073 assert!(matches!(content[0], ContentBlock::Other(_)));
7074 }
7075
7076 #[test]
7077 fn test_auth_method_terminal_serialization() {
7078 let method = AuthMethod::Terminal(AuthMethodTerminal::new("tui-auth", "Terminal Auth"));
7079
7080 let json = serde_json::to_value(&method).unwrap();
7081 assert_eq!(
7082 json,
7083 json!({
7084 "methodId": "tui-auth",
7085 "name": "Terminal Auth",
7086 "type": "terminal"
7087 })
7088 );
7089 assert!(!json.as_object().unwrap().contains_key("args"));
7091 assert!(!json.as_object().unwrap().contains_key("env"));
7092
7093 let deserialized: AuthMethod = serde_json::from_value(json).unwrap();
7094 match deserialized {
7095 AuthMethod::Terminal(AuthMethodTerminal { args, env, .. }) => {
7096 assert_eq!(args, Vec::<String>::new());
7097 assert_eq!(env, []);
7098 }
7099 _ => panic!("Expected Terminal variant"),
7100 }
7101 }
7102
7103 #[test]
7104 fn test_auth_method_terminal_with_args_and_env_serialization() {
7105 let method = AuthMethod::Terminal(
7106 AuthMethodTerminal::new("tui-auth", "Terminal Auth")
7107 .args(vec!["--interactive".to_string(), "--color".to_string()])
7108 .env(vec![EnvVariable::new("TERM", "xterm-256color")]),
7109 );
7110
7111 let json = serde_json::to_value(&method).unwrap();
7112 assert_eq!(
7113 json,
7114 json!({
7115 "methodId": "tui-auth",
7116 "name": "Terminal Auth",
7117 "type": "terminal",
7118 "args": ["--interactive", "--color"],
7119 "env": [
7120 {
7121 "name": "TERM",
7122 "value": "xterm-256color"
7123 }
7124 ]
7125 })
7126 );
7127
7128 let deserialized: AuthMethod = serde_json::from_value(json).unwrap();
7129 match deserialized {
7130 AuthMethod::Terminal(AuthMethodTerminal { args, env, .. }) => {
7131 assert_eq!(args, vec!["--interactive", "--color"]);
7132 assert_eq!(env.len(), 1);
7133 assert_eq!(env[0].name, "TERM");
7134 assert_eq!(env[0].value, "xterm-256color");
7135 }
7136 _ => panic!("Expected Terminal variant"),
7137 }
7138 }
7139
7140 #[test]
7141 fn test_session_config_option_id_serialize() {
7142 let val = SessionConfigOptionValue::id("model-1");
7143 let json = serde_json::to_value(&val).unwrap();
7144 assert_eq!(json, json!({ "type": "id", "value": "model-1" }));
7145 }
7146
7147 #[test]
7148 fn test_session_config_option_value_boolean_serialize() {
7149 let val = SessionConfigOptionValue::boolean(true);
7150 let json = serde_json::to_value(&val).unwrap();
7151 assert_eq!(json, json!({ "type": "boolean", "value": true }));
7152 }
7153
7154 #[test]
7155 fn test_session_config_option_value_deserialize_id() {
7156 let json = json!({ "type": "id", "value": "model-1" });
7157 let val: SessionConfigOptionValue = serde_json::from_value(json).unwrap();
7158 assert_eq!(val, SessionConfigOptionValue::id("model-1"));
7159 assert_eq!(val.as_id().unwrap().to_string(), "model-1");
7160 }
7161
7162 #[test]
7163 fn test_session_config_option_value_deserialize_requires_type() {
7164 let json = json!({ "value": "model-1" });
7165 let result = serde_json::from_value::<SessionConfigOptionValue>(json);
7166 assert!(result.is_err());
7167 }
7168
7169 #[test]
7170 fn test_session_config_option_value_deserialize_boolean() {
7171 let json = json!({ "type": "boolean", "value": true });
7172 let val: SessionConfigOptionValue = serde_json::from_value(json).unwrap();
7173 assert_eq!(val, SessionConfigOptionValue::boolean(true));
7174 assert_eq!(val.as_bool(), Some(true));
7175 }
7176
7177 #[test]
7178 fn test_session_config_option_value_deserialize_boolean_false() {
7179 let json = json!({ "type": "boolean", "value": false });
7180 let val: SessionConfigOptionValue = serde_json::from_value(json).unwrap();
7181 assert_eq!(val, SessionConfigOptionValue::boolean(false));
7182 assert_eq!(val.as_bool(), Some(false));
7183 }
7184
7185 #[test]
7186 fn test_session_config_option_value_deserialize_unknown_type_with_string_value() {
7187 let json = json!({
7188 "type": "text",
7189 "value": "freeform input",
7190 "maxLength": 200
7191 });
7192 let val: SessionConfigOptionValue = serde_json::from_value(json).unwrap();
7193 let SessionConfigOptionValue::Other(unknown) = val else {
7194 panic!("Expected Other variant");
7195 };
7196 assert_eq!(unknown.type_, "text");
7197 assert_eq!(unknown.value, json!("freeform input"));
7198 assert_eq!(unknown.fields["maxLength"], json!(200));
7199 }
7200
7201 #[test]
7202 fn test_session_config_option_value_deserialize_unknown_type_with_object_value() {
7203 let json = json!({
7204 "type": "range",
7205 "value": { "min": 1, "max": 5 }
7206 });
7207 let val: SessionConfigOptionValue = serde_json::from_value(json).unwrap();
7208 let SessionConfigOptionValue::Other(unknown) = val else {
7209 panic!("Expected Other variant");
7210 };
7211 assert_eq!(unknown.type_, "range");
7212 assert_eq!(unknown.value, json!({ "min": 1, "max": 5 }));
7213 }
7214
7215 #[test]
7216 fn test_session_config_option_value_roundtrip_id() {
7217 let original = SessionConfigOptionValue::id("option-a");
7218 let json = serde_json::to_value(&original).unwrap();
7219 let roundtripped: SessionConfigOptionValue = serde_json::from_value(json).unwrap();
7220 assert_eq!(original, roundtripped);
7221 }
7222
7223 #[test]
7224 fn test_session_config_option_value_roundtrip_boolean() {
7225 let original = SessionConfigOptionValue::boolean(false);
7226 let json = serde_json::to_value(&original).unwrap();
7227 let roundtripped: SessionConfigOptionValue = serde_json::from_value(json).unwrap();
7228 assert_eq!(original, roundtripped);
7229 }
7230
7231 #[test]
7232 fn test_session_config_option_value_roundtrip_other() {
7233 let mut fields = BTreeMap::new();
7234 fields.insert("maxLength".to_string(), json!(200));
7235 let original = SessionConfigOptionValue::Other(OtherSessionConfigOptionValue::new(
7236 "text",
7237 json!("freeform input"),
7238 fields,
7239 ));
7240 let json = serde_json::to_value(&original).unwrap();
7241 let roundtripped: SessionConfigOptionValue = serde_json::from_value(json).unwrap();
7242 assert_eq!(original, roundtripped);
7243 }
7244
7245 #[test]
7246 fn test_session_config_option_value_type_mismatch_boolean_with_string() {
7247 let json = json!({ "type": "boolean", "value": "not a bool" });
7248 let result = serde_json::from_value::<SessionConfigOptionValue>(json);
7249 assert!(result.is_err());
7250 }
7251
7252 #[test]
7253 fn test_session_config_option_value_from_impls() {
7254 let from_str: SessionConfigOptionValue = "model-1".into();
7255 assert_eq!(from_str.as_id().unwrap().to_string(), "model-1");
7256
7257 let from_id: SessionConfigOptionValue = SessionConfigValueId::new("model-2").into();
7258 assert_eq!(from_id.as_id().unwrap().to_string(), "model-2");
7259
7260 let from_bool: SessionConfigOptionValue = true.into();
7261 assert_eq!(from_bool.as_bool(), Some(true));
7262 }
7263
7264 #[test]
7265 fn test_set_session_config_option_request_id() {
7266 let req = SetSessionConfigOptionRequest::new("sess_1", "model", "model-1");
7267 let json = serde_json::to_value(&req).unwrap();
7268 assert_eq!(
7269 json,
7270 json!({
7271 "sessionId": "sess_1",
7272 "configId": "model",
7273 "type": "id",
7274 "value": "model-1"
7275 })
7276 );
7277 }
7278
7279 #[test]
7280 fn test_set_session_config_option_request_boolean() {
7281 let req = SetSessionConfigOptionRequest::new("sess_1", "brave_mode", true);
7282 let json = serde_json::to_value(&req).unwrap();
7283 assert_eq!(
7284 json,
7285 json!({
7286 "sessionId": "sess_1",
7287 "configId": "brave_mode",
7288 "type": "boolean",
7289 "value": true
7290 })
7291 );
7292 }
7293
7294 #[test]
7295 fn test_set_session_config_option_request_deserialize_requires_type() {
7296 let json = json!({
7297 "sessionId": "sess_1",
7298 "configId": "model",
7299 "value": "model-1"
7300 });
7301 let result = serde_json::from_value::<SetSessionConfigOptionRequest>(json);
7302 assert!(result.is_err());
7303 }
7304
7305 #[test]
7306 fn test_set_session_config_option_request_deserialize_boolean() {
7307 let json = json!({
7308 "sessionId": "sess_1",
7309 "configId": "brave_mode",
7310 "type": "boolean",
7311 "value": true
7312 });
7313 let req: SetSessionConfigOptionRequest = serde_json::from_value(json).unwrap();
7314 assert_eq!(req.value.as_bool(), Some(true));
7315 }
7316
7317 #[test]
7318 fn test_set_session_config_option_request_roundtrip_id() {
7319 let original = SetSessionConfigOptionRequest::new("s", "c", "v");
7320 let json = serde_json::to_value(&original).unwrap();
7321 let roundtripped: SetSessionConfigOptionRequest = serde_json::from_value(json).unwrap();
7322 assert_eq!(original, roundtripped);
7323 }
7324
7325 #[test]
7326 fn test_set_session_config_option_request_roundtrip_boolean() {
7327 let original = SetSessionConfigOptionRequest::new("s", "c", false);
7328 let json = serde_json::to_value(&original).unwrap();
7329 let roundtripped: SetSessionConfigOptionRequest = serde_json::from_value(json).unwrap();
7330 assert_eq!(original, roundtripped);
7331 }
7332
7333 #[test]
7334 fn test_session_config_boolean_serialization() {
7335 let cfg = SessionConfigBoolean::new(true);
7336 let json = serde_json::to_value(&cfg).unwrap();
7337 assert_eq!(json, json!({ "currentValue": true }));
7338
7339 let deserialized: SessionConfigBoolean = serde_json::from_value(json).unwrap();
7340 assert!(deserialized.current_value);
7341 }
7342
7343 #[test]
7344 fn test_session_config_option_boolean_variant() {
7345 let opt = SessionConfigOption::boolean("brave_mode", "Brave Mode", false)
7346 .description("Skip confirmation prompts")
7347 .meta(test_meta());
7348 assert_eq!(serialized_meta_key_count(&opt), 1);
7349
7350 let json = serde_json::to_value(&opt).unwrap();
7351 assert_eq!(
7352 json,
7353 json!({
7354 "configId": "brave_mode",
7355 "name": "Brave Mode",
7356 "description": "Skip confirmation prompts",
7357 "type": "boolean",
7358 "currentValue": false,
7359 "_meta": {
7360 "source": "test"
7361 }
7362 })
7363 );
7364
7365 let deserialized: SessionConfigOption = serde_json::from_value(json).unwrap();
7366 assert_eq!(deserialized.config_id.to_string(), "brave_mode");
7367 assert_eq!(deserialized.name, "Brave Mode");
7368 match deserialized.kind {
7369 SessionConfigKind::Boolean(ref b) => assert!(!b.current_value),
7370 _ => panic!("Expected Boolean kind"),
7371 }
7372 }
7373
7374 #[test]
7375 fn test_session_config_option_select_still_works() {
7376 let opt = SessionConfigOption::select(
7378 "model",
7379 "Model",
7380 "model-1",
7381 vec![
7382 SessionConfigSelectOption::new("model-1", "Model 1"),
7383 SessionConfigSelectOption::new("model-2", "Model 2"),
7384 ],
7385 )
7386 .meta(test_meta());
7387 assert_eq!(serialized_meta_key_count(&opt), 1);
7388
7389 let json = serde_json::to_value(&opt).unwrap();
7390 assert_eq!(json["type"], "select");
7391 assert_eq!(json["currentValue"], "model-1");
7392 assert_eq!(json["options"].as_array().unwrap().len(), 2);
7393 assert_eq!(json["_meta"]["source"], "test");
7394
7395 let deserialized: SessionConfigOption = serde_json::from_value(json).unwrap();
7396 match deserialized.kind {
7397 SessionConfigKind::Select(ref s) => {
7398 assert_eq!(s.current_value.to_string(), "model-1");
7399 }
7400 _ => panic!("Expected Select kind"),
7401 }
7402 }
7403
7404 #[test]
7405 fn test_session_config_option_unknown_kind_roundtrip() {
7406 let option: SessionConfigOption = serde_json::from_value(json!({
7407 "configId": "verbosity",
7408 "name": "Verbosity",
7409 "type": "_slider",
7410 "currentValue": 3,
7411 "min": 0,
7412 "max": 5,
7413 "_meta": {
7414 "source": "test"
7415 }
7416 }))
7417 .unwrap();
7418
7419 assert_eq!(option.config_id.to_string(), "verbosity");
7420 assert_eq!(option.meta.as_ref().unwrap()["source"], "test");
7421 let SessionConfigKind::Other(unknown) = &option.kind else {
7422 panic!("expected unknown config kind");
7423 };
7424 assert_eq!(unknown.type_, "_slider");
7425 assert_eq!(unknown.fields.get("currentValue"), Some(&json!(3)));
7426 assert!(!unknown.fields.contains_key("_meta"));
7427 assert_eq!(serialized_meta_key_count(&option), 1);
7428
7429 let json = serde_json::to_value(&option).unwrap();
7430 assert_eq!(json["type"], "_slider");
7431 assert_eq!(json["currentValue"], 3);
7432 assert_eq!(json["min"], 0);
7433 assert_eq!(json["max"], 5);
7434 assert_eq!(json["_meta"]["source"], "test");
7435 }
7436
7437 #[test]
7438 fn test_session_config_option_unknown_kind_does_not_duplicate_flattened_meta() {
7439 let mut fields = std::collections::BTreeMap::new();
7440 fields.insert("currentValue".to_string(), json!(3));
7441 fields.insert("_meta".to_string(), json!({ "inner": "ignored" }));
7442
7443 let option = SessionConfigOption::new(
7444 "verbosity",
7445 "Verbosity",
7446 SessionConfigKind::Other(OtherSessionConfigKind::new("_slider", fields)),
7447 )
7448 .meta(test_meta());
7449
7450 let SessionConfigKind::Other(unknown) = &option.kind else {
7451 panic!("expected unknown config kind");
7452 };
7453 assert!(!unknown.fields.contains_key("_meta"));
7454 assert_eq!(serialized_meta_key_count(&option), 1);
7455
7456 let json = serde_json::to_value(&option).unwrap();
7457 assert_eq!(json["type"], "_slider");
7458 assert_eq!(json["currentValue"], 3);
7459 assert_eq!(json["_meta"]["source"], "test");
7460 }
7461
7462 #[test]
7463 fn test_session_config_option_unknown_does_not_hide_malformed_known_kind() {
7464 assert!(
7465 serde_json::from_value::<SessionConfigOption>(json!({
7466 "configId": "model",
7467 "name": "Model",
7468 "type": "select"
7469 }))
7470 .is_err()
7471 );
7472 }
7473
7474 #[cfg(feature = "unstable_llm_providers")]
7475 #[test]
7476 fn test_llm_protocol_known_variants() {
7477 assert_eq!(
7478 serde_json::to_value(&LlmProtocol::Anthropic).unwrap(),
7479 json!("anthropic")
7480 );
7481 assert_eq!(
7482 serde_json::to_value(&LlmProtocol::OpenAi).unwrap(),
7483 json!("openai")
7484 );
7485 assert_eq!(
7486 serde_json::to_value(&LlmProtocol::Azure).unwrap(),
7487 json!("azure")
7488 );
7489 assert_eq!(
7490 serde_json::to_value(&LlmProtocol::Vertex).unwrap(),
7491 json!("vertex")
7492 );
7493 assert_eq!(
7494 serde_json::to_value(&LlmProtocol::Bedrock).unwrap(),
7495 json!("bedrock")
7496 );
7497
7498 assert_eq!(
7499 serde_json::from_str::<LlmProtocol>("\"anthropic\"").unwrap(),
7500 LlmProtocol::Anthropic
7501 );
7502 assert_eq!(
7503 serde_json::from_str::<LlmProtocol>("\"openai\"").unwrap(),
7504 LlmProtocol::OpenAi
7505 );
7506 assert_eq!(
7507 serde_json::from_str::<LlmProtocol>("\"azure\"").unwrap(),
7508 LlmProtocol::Azure
7509 );
7510 assert_eq!(
7511 serde_json::from_str::<LlmProtocol>("\"vertex\"").unwrap(),
7512 LlmProtocol::Vertex
7513 );
7514 assert_eq!(
7515 serde_json::from_str::<LlmProtocol>("\"bedrock\"").unwrap(),
7516 LlmProtocol::Bedrock
7517 );
7518 }
7519
7520 #[cfg(feature = "unstable_llm_providers")]
7521 #[test]
7522 fn test_llm_protocol_unknown_variant() {
7523 let unknown: LlmProtocol = serde_json::from_str("\"cohere\"").unwrap();
7524 assert_eq!(unknown, LlmProtocol::Other("cohere".to_string()));
7525
7526 let json = serde_json::to_value(&unknown).unwrap();
7527 assert_eq!(json, json!("cohere"));
7528 }
7529
7530 #[cfg(feature = "unstable_llm_providers")]
7531 #[test]
7532 fn test_provider_current_config_serialization() {
7533 let config =
7534 ProviderCurrentConfig::new(LlmProtocol::Anthropic, "https://api.anthropic.com");
7535
7536 let json = serde_json::to_value(&config).unwrap();
7537 assert_eq!(
7538 json,
7539 json!({
7540 "apiType": "anthropic",
7541 "baseUrl": "https://api.anthropic.com"
7542 })
7543 );
7544
7545 let deserialized: ProviderCurrentConfig = serde_json::from_value(json).unwrap();
7546 assert_eq!(deserialized.api_type, LlmProtocol::Anthropic);
7547 assert_eq!(deserialized.base_url, "https://api.anthropic.com");
7548 }
7549
7550 #[cfg(feature = "unstable_llm_providers")]
7551 #[test]
7552 fn test_provider_info_with_current_config() {
7553 let info = ProviderInfo::new(
7554 "main",
7555 vec![LlmProtocol::Anthropic, LlmProtocol::OpenAi],
7556 true,
7557 Some(ProviderCurrentConfig::new(
7558 LlmProtocol::Anthropic,
7559 "https://api.anthropic.com",
7560 )),
7561 );
7562
7563 let json = serde_json::to_value(&info).unwrap();
7564 assert_eq!(
7565 json,
7566 json!({
7567 "providerId": "main",
7568 "supported": ["anthropic", "openai"],
7569 "required": true,
7570 "current": {
7571 "apiType": "anthropic",
7572 "baseUrl": "https://api.anthropic.com"
7573 }
7574 })
7575 );
7576
7577 let deserialized: ProviderInfo = serde_json::from_value(json).unwrap();
7578 assert_eq!(deserialized.provider_id.to_string(), "main");
7579 assert_eq!(deserialized.supported.len(), 2);
7580 assert!(deserialized.required);
7581 assert!(deserialized.current.is_some());
7582 assert_eq!(
7583 deserialized.current.as_ref().unwrap().api_type,
7584 LlmProtocol::Anthropic
7585 );
7586 }
7587
7588 #[cfg(feature = "unstable_llm_providers")]
7589 #[test]
7590 fn test_provider_info_disabled() {
7591 let info = ProviderInfo::new(
7592 "secondary",
7593 vec![LlmProtocol::OpenAi],
7594 false,
7595 None::<ProviderCurrentConfig>,
7596 );
7597
7598 let json = serde_json::to_value(&info).unwrap();
7599 assert_eq!(
7600 json,
7601 json!({
7602 "providerId": "secondary",
7603 "supported": ["openai"],
7604 "required": false
7605 })
7606 );
7607
7608 let deserialized: ProviderInfo = serde_json::from_value(json).unwrap();
7609 assert_eq!(deserialized.provider_id.to_string(), "secondary");
7610 assert!(!deserialized.required);
7611 assert!(deserialized.current.is_none());
7612 }
7613
7614 #[cfg(feature = "unstable_llm_providers")]
7615 #[test]
7616 fn test_provider_info_missing_current_defaults_to_none() {
7617 let json = json!({
7619 "providerId": "main",
7620 "supported": ["anthropic"],
7621 "required": true
7622 });
7623 let deserialized: ProviderInfo = serde_json::from_value(json).unwrap();
7624 assert!(deserialized.current.is_none());
7625 }
7626
7627 #[cfg(feature = "unstable_llm_providers")]
7628 #[test]
7629 fn test_provider_info_explicit_null_current_decodes_to_none() {
7630 let json = json!({
7634 "providerId": "main",
7635 "supported": ["anthropic"],
7636 "required": true,
7637 "current": null
7638 });
7639 let deserialized: ProviderInfo = serde_json::from_value(json).unwrap();
7640 assert!(deserialized.current.is_none());
7641 }
7642
7643 #[cfg(feature = "unstable_llm_providers")]
7644 #[test]
7645 fn test_list_providers_response_serialization() {
7646 let response = ListProvidersResponse::new(vec![ProviderInfo::new(
7647 "main",
7648 vec![LlmProtocol::Anthropic],
7649 true,
7650 Some(ProviderCurrentConfig::new(
7651 LlmProtocol::Anthropic,
7652 "https://api.anthropic.com",
7653 )),
7654 )]);
7655
7656 let json = serde_json::to_value(&response).unwrap();
7657 assert_eq!(json["providers"].as_array().unwrap().len(), 1);
7658 assert_eq!(json["providers"][0]["providerId"], "main");
7659
7660 let deserialized: ListProvidersResponse = serde_json::from_value(json).unwrap();
7661 assert_eq!(deserialized.providers.len(), 1);
7662 }
7663
7664 #[cfg(feature = "unstable_llm_providers")]
7665 #[test]
7666 fn test_set_provider_request_serialization() {
7667 use std::collections::HashMap;
7668
7669 let mut headers = HashMap::new();
7670 headers.insert("Authorization".to_string(), "Bearer sk-test".to_string());
7671
7672 let request =
7673 SetProviderRequest::new("main", LlmProtocol::OpenAi, "https://api.openai.com/v1")
7674 .headers(headers);
7675
7676 let json = serde_json::to_value(&request).unwrap();
7677 assert_eq!(
7678 json,
7679 json!({
7680 "providerId": "main",
7681 "apiType": "openai",
7682 "baseUrl": "https://api.openai.com/v1",
7683 "headers": {
7684 "Authorization": "Bearer sk-test"
7685 }
7686 })
7687 );
7688
7689 let deserialized: SetProviderRequest = serde_json::from_value(json).unwrap();
7690 assert_eq!(deserialized.provider_id.to_string(), "main");
7691 assert_eq!(deserialized.api_type, LlmProtocol::OpenAi);
7692 assert_eq!(deserialized.base_url, "https://api.openai.com/v1");
7693 assert_eq!(deserialized.headers.len(), 1);
7694 assert_eq!(
7695 deserialized.headers.get("Authorization").unwrap(),
7696 "Bearer sk-test"
7697 );
7698 }
7699
7700 #[cfg(feature = "unstable_llm_providers")]
7701 #[test]
7702 fn test_set_provider_request_omits_empty_headers() {
7703 let request =
7704 SetProviderRequest::new("main", LlmProtocol::Anthropic, "https://api.anthropic.com");
7705
7706 let json = serde_json::to_value(&request).unwrap();
7707 assert!(!json.as_object().unwrap().contains_key("headers"));
7709 }
7710
7711 #[cfg(feature = "unstable_llm_providers")]
7712 #[test]
7713 fn test_disable_provider_request_serialization() {
7714 let request = DisableProviderRequest::new("secondary");
7715
7716 let json = serde_json::to_value(&request).unwrap();
7717 assert_eq!(json, json!({ "providerId": "secondary" }));
7718
7719 let deserialized: DisableProviderRequest = serde_json::from_value(json).unwrap();
7720 assert_eq!(deserialized.provider_id.to_string(), "secondary");
7721 }
7722
7723 #[cfg(feature = "unstable_llm_providers")]
7724 #[test]
7725 fn test_providers_capabilities_serialization() {
7726 let caps = ProvidersCapabilities::new();
7727
7728 let json = serde_json::to_value(&caps).unwrap();
7729 assert_eq!(json, json!({}));
7730
7731 let deserialized: ProvidersCapabilities = serde_json::from_value(json).unwrap();
7732 assert!(deserialized.meta.is_none());
7733 }
7734
7735 #[cfg(feature = "unstable_llm_providers")]
7736 #[test]
7737 fn test_agent_capabilities_with_providers() {
7738 let caps = AgentCapabilities::new().providers(ProvidersCapabilities::new());
7739
7740 let json = serde_json::to_value(&caps).unwrap();
7741 assert_eq!(json["providers"], json!({}));
7742
7743 let deserialized: AgentCapabilities = serde_json::from_value(json).unwrap();
7744 assert!(deserialized.providers.is_some());
7745 }
7746
7747 #[test]
7748 fn test_agent_capabilities_session_is_explicit() {
7749 let json = serde_json::to_value(AgentCapabilities::new()).unwrap();
7750 assert!(json.get("session").is_none());
7751
7752 let caps = AgentCapabilities::new().session(
7753 SessionCapabilities::new()
7754 .prompt(PromptCapabilities::new().image(PromptImageCapabilities::new()))
7755 .mcp(McpCapabilities::new().stdio(McpStdioCapabilities::new())),
7756 );
7757
7758 assert_eq!(
7759 serde_json::to_value(&caps).unwrap(),
7760 json!({
7761 "session": {
7762 "prompt": {
7763 "image": {}
7764 },
7765 "mcp": {
7766 "stdio": {}
7767 }
7768 }
7769 })
7770 );
7771
7772 let deserialized: AgentCapabilities = serde_json::from_value(json!({
7773 "session": false
7774 }))
7775 .unwrap();
7776 assert!(deserialized.session.is_none());
7777 }
7778
7779 #[test]
7780 fn test_prompt_capabilities_serialize_supported_content_as_objects() {
7781 let caps = PromptCapabilities::new()
7782 .image(PromptImageCapabilities::new())
7783 .audio(PromptAudioCapabilities::new())
7784 .embedded_context(PromptEmbeddedContextCapabilities::new());
7785
7786 assert_eq!(
7787 serde_json::to_value(&caps).unwrap(),
7788 json!({
7789 "image": {},
7790 "audio": {},
7791 "embeddedContext": {}
7792 })
7793 );
7794
7795 let deserialized: PromptCapabilities = serde_json::from_value(json!({
7796 "image": null,
7797 "audio": false,
7798 "embeddedContext": {}
7799 }))
7800 .unwrap();
7801 assert!(deserialized.image.is_none());
7802 assert!(deserialized.audio.is_none());
7803 assert!(deserialized.embedded_context.is_some());
7804 }
7805
7806 #[test]
7807 fn test_mcp_capabilities_serialize_supported_transports_as_objects() {
7808 let caps = McpCapabilities::new()
7809 .stdio(McpStdioCapabilities::new())
7810 .http(McpHttpCapabilities::new());
7811
7812 assert_eq!(
7813 serde_json::to_value(&caps).unwrap(),
7814 json!({
7815 "stdio": {},
7816 "http": {}
7817 })
7818 );
7819
7820 let deserialized: McpCapabilities = serde_json::from_value(json!({
7821 "stdio": null,
7822 "http": false
7823 }))
7824 .unwrap();
7825 assert!(deserialized.stdio.is_none());
7826 assert!(deserialized.http.is_none());
7827 }
7828
7829 #[cfg(feature = "unstable_mcp_over_acp")]
7830 #[test]
7831 fn test_mcp_capabilities_serialize_acp_support_as_object() {
7832 let caps = McpCapabilities::new().acp(McpAcpCapabilities::new());
7833
7834 assert_eq!(
7835 serde_json::to_value(&caps).unwrap(),
7836 json!({
7837 "acp": {}
7838 })
7839 );
7840 }
7841
7842 #[test]
7843 fn prompt_request_rejects_malformed_content_block() {
7844 use serde_json::json;
7845
7846 assert!(
7847 serde_json::from_value::<PromptRequest>(json!({
7848 "sessionId": "sess-1",
7849 "prompt": [{"type": "text"}]
7850 }))
7851 .is_err()
7852 );
7853 }
7854
7855 #[test]
7856 fn prompt_request_rejects_non_array_prompt() {
7857 use serde_json::json;
7858
7859 assert!(
7860 serde_json::from_value::<PromptRequest>(json!({
7861 "sessionId": "sess-1",
7862 "prompt": "hello"
7863 }))
7864 .is_err()
7865 );
7866 }
7867
7868 fn assert_session_setup_lists_are_strict<T>(base: &serde_json::Value)
7869 where
7870 T: serde::de::DeserializeOwned + PartialEq + std::fmt::Debug,
7871 {
7872 let name = std::any::type_name::<T>();
7873 let with = |field: &str, value: serde_json::Value| {
7874 let mut request = base.clone();
7875 request[field] = value;
7876 serde_json::from_value::<T>(request)
7877 };
7878
7879 for (field, value) in [
7880 (
7882 "mcpServers",
7883 json!([
7884 {"type": "stdio", "name": "github", "command": "/usr/bin/gh-mcp", "env": [{"name": "GITHUB_TOKEN", "value": null}]},
7885 {"type": "stdio", "name": "fs", "command": "/usr/bin/fs-mcp"}
7886 ]),
7887 ),
7888 ("mcpServers", json!({"type": "stdio", "name": "fs"})),
7889 ("additionalDirectories", json!(["/repo/lib", 42])),
7890 ("additionalDirectories", json!("/repo/lib")),
7891 ] {
7892 assert!(
7893 with(field, value.clone()).is_err(),
7894 "{name}.{field}: {value}"
7895 );
7896 }
7897
7898 let empty = serde_json::from_value::<T>(base.clone()).unwrap();
7899 for field in ["mcpServers", "additionalDirectories"] {
7900 assert_eq!(
7901 with(field, serde_json::Value::Null).unwrap(),
7902 empty,
7903 "{name}.{field}"
7904 );
7905 }
7906 }
7907
7908 #[test]
7909 fn session_setup_requests_reject_malformed_mcp_servers_and_directories() {
7910 assert_session_setup_lists_are_strict::<NewSessionRequest>(&json!({"cwd": "/repo"}));
7911 assert_session_setup_lists_are_strict::<ResumeSessionRequest>(&json!({
7912 "sessionId": "sess-1",
7913 "cwd": "/repo"
7914 }));
7915 #[cfg(feature = "unstable_session_fork")]
7916 assert_session_setup_lists_are_strict::<ForkSessionRequest>(&json!({
7917 "sessionId": "sess-1",
7918 "cwd": "/repo"
7919 }));
7920 }
7921
7922 #[test]
7923 fn resume_session_request_rejects_malformed_replay_from() {
7924 let request = |replay_from: serde_json::Value| {
7925 serde_json::from_value::<ResumeSessionRequest>(json!({
7926 "sessionId": "sess-1",
7927 "cwd": "/repo",
7928 "replayFrom": replay_from
7929 }))
7930 };
7931
7932 assert!(request(json!("start")).is_err());
7934 assert_eq!(request(serde_json::Value::Null).unwrap().replay_from, None);
7935 assert!(matches!(
7936 request(json!({"type": "start"})).unwrap().replay_from,
7937 Some(ReplayFrom::Start(_))
7938 ));
7939 }
7940
7941 #[test]
7942 fn mcp_server_lists_treat_null_as_empty() {
7943 let servers: Vec<McpServer> = serde_json::from_value(json!([
7944 {"type": "stdio", "name": "fs", "command": "/usr/bin/fs-mcp", "args": null, "env": null},
7945 {"type": "http", "name": "api", "url": "https://example.com/mcp", "headers": null}
7946 ]))
7947 .unwrap();
7948
7949 assert_eq!(
7950 servers,
7951 vec![
7952 McpServer::Stdio(McpServerStdio::new("fs", "/usr/bin/fs-mcp")),
7953 McpServer::Http(McpServerHttp::new("api", "https://example.com/mcp")),
7954 ]
7955 );
7956 }
7957
7958 #[test]
7959 fn terminal_auth_methods_reject_malformed_launch_config() {
7960 let response: InitializeResponse = serde_json::from_value(json!({
7964 "protocolVersion": 2,
7965 "info": {"name": "agent", "version": "1.0.0"},
7966 "authMethods": [
7967 {"type": "terminal", "methodId": "bad-args", "name": "Log in", "args": ["auth", "--port", 8123]},
7968 {"type": "terminal", "methodId": "bad-env", "name": "Log in", "env": [{"name": "DEBUG", "value": 1}]},
7969 {"type": "terminal", "methodId": "null-config", "name": "Log in", "args": null, "env": null},
7970 {"type": "agent", "methodId": "agent", "name": "Agent login"}
7971 ]
7972 }))
7973 .unwrap();
7974
7975 assert_eq!(
7976 response.auth_methods,
7977 vec![
7978 AuthMethod::Terminal(AuthMethodTerminal::new("null-config", "Log in")),
7979 AuthMethod::Agent(AuthMethodAgent::new("agent", "Agent login")),
7980 ]
7981 );
7982 }
7983}