1use serde::{Deserialize, Serialize};
2
3use crate::{AgentError, McpConfigMode, Session, SessionConfig};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
7#[serde(rename_all = "snake_case")]
8#[non_exhaustive]
9pub enum CapabilitySupport {
10 Unsupported,
12 ConfigDependent,
14 Supported,
16}
17
18impl CapabilitySupport {
19 pub fn is_supported(self) -> bool {
20 !matches!(self, Self::Unsupported)
21 }
22}
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
26#[serde(rename_all = "snake_case")]
27#[non_exhaustive]
28pub enum ResumeSemantics {
29 Unsupported,
31 Stateless,
33 Stateful,
35}
36
37impl ResumeSemantics {
38 pub fn accepts_resume_id(self) -> bool {
39 !matches!(self, Self::Unsupported)
40 }
41
42 pub fn preserves_history(self) -> bool {
43 matches!(self, Self::Stateful)
44 }
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
49#[serde(rename_all = "snake_case")]
50#[non_exhaustive]
51pub enum McpSupport {
52 Unsupported,
54 ExplicitServersOnly,
57 MergeOnly,
59 MergeAndExplicitOnly,
61}
62
63impl McpSupport {
64 pub fn supports_mcp(self) -> bool {
65 !matches!(self, Self::Unsupported)
66 }
67
68 pub fn supports_mode(self, mode: McpConfigMode) -> bool {
69 match (self, mode) {
70 (Self::Unsupported, _) => false,
71 (Self::ExplicitServersOnly, McpConfigMode::Merge) => false,
72 (Self::ExplicitServersOnly, McpConfigMode::ExplicitOnly) => true,
73 (Self::MergeOnly, McpConfigMode::Merge) => true,
74 (Self::MergeOnly, McpConfigMode::ExplicitOnly) => false,
75 (Self::MergeAndExplicitOnly, _) => true,
76 }
77 }
78}
79
80#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
82pub struct BackendCapabilities {
83 pub input_images: CapabilitySupport,
85 pub output_schema: CapabilitySupport,
87 pub interrupt: CapabilitySupport,
89 pub resume: ResumeSemantics,
91 pub mcp: McpSupport,
93 pub tool_call_events: CapabilitySupport,
95}
96
97pub trait Backend: Send + Sync {
102 type Config: Send + Sync + 'static;
104
105 type Session: Session + 'static;
107
108 fn name(&self) -> &'static str;
110
111 fn capabilities(&self) -> BackendCapabilities;
117
118 fn session(
120 &self,
121 config: SessionConfig<Self::Config>,
122 ) -> impl std::future::Future<Output = Result<Self::Session, AgentError>> + Send;
123
124 fn resume(
126 &self,
127 session_id: &str,
128 config: SessionConfig<Self::Config>,
129 ) -> impl std::future::Future<Output = Result<Self::Session, AgentError>> + Send;
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135
136 #[test]
137 fn capability_support_reports_if_backend_can_handle_feature() {
138 assert!(!CapabilitySupport::Unsupported.is_supported());
139 assert!(CapabilitySupport::ConfigDependent.is_supported());
140 assert!(CapabilitySupport::Supported.is_supported());
141 }
142
143 #[test]
144 fn resume_semantics_distinguish_stateful_and_stateless() {
145 assert!(!ResumeSemantics::Unsupported.accepts_resume_id());
146 assert!(ResumeSemantics::Stateless.accepts_resume_id());
147 assert!(ResumeSemantics::Stateful.accepts_resume_id());
148 assert!(!ResumeSemantics::Stateless.preserves_history());
149 assert!(ResumeSemantics::Stateful.preserves_history());
150 }
151
152 #[test]
153 fn mcp_support_tracks_supported_modes() {
154 assert!(!McpSupport::Unsupported.supports_mcp());
155 assert!(McpSupport::MergeOnly.supports_mode(McpConfigMode::Merge));
156 assert!(!McpSupport::MergeOnly.supports_mode(McpConfigMode::ExplicitOnly));
157 assert!(McpSupport::ExplicitServersOnly.supports_mode(McpConfigMode::ExplicitOnly));
158 assert!(!McpSupport::ExplicitServersOnly.supports_mode(McpConfigMode::Merge));
159 assert!(McpSupport::MergeAndExplicitOnly.supports_mode(McpConfigMode::ExplicitOnly));
160 }
161}