use serde::{Deserialize, Serialize};
use crate::{AgentError, McpConfigMode, Session, SessionConfig};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum CapabilitySupport {
Unsupported,
ConfigDependent,
Supported,
}
impl CapabilitySupport {
pub fn is_supported(self) -> bool {
!matches!(self, Self::Unsupported)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum ResumeSemantics {
Unsupported,
Stateless,
Stateful,
}
impl ResumeSemantics {
pub fn accepts_resume_id(self) -> bool {
!matches!(self, Self::Unsupported)
}
pub fn preserves_history(self) -> bool {
matches!(self, Self::Stateful)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum McpSupport {
Unsupported,
ExplicitServersOnly,
MergeOnly,
MergeAndExplicitOnly,
}
impl McpSupport {
pub fn supports_mcp(self) -> bool {
!matches!(self, Self::Unsupported)
}
pub fn supports_mode(self, mode: McpConfigMode) -> bool {
match (self, mode) {
(Self::Unsupported, _) => false,
(Self::ExplicitServersOnly, McpConfigMode::Merge) => false,
(Self::ExplicitServersOnly, McpConfigMode::ExplicitOnly) => true,
(Self::MergeOnly, McpConfigMode::Merge) => true,
(Self::MergeOnly, McpConfigMode::ExplicitOnly) => false,
(Self::MergeAndExplicitOnly, _) => true,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct BackendCapabilities {
pub input_images: CapabilitySupport,
pub output_schema: CapabilitySupport,
pub interrupt: CapabilitySupport,
pub resume: ResumeSemantics,
pub mcp: McpSupport,
pub tool_call_events: CapabilitySupport,
}
pub trait Backend: Send + Sync {
type Config: Send + Sync + 'static;
type Session: Session + 'static;
fn name(&self) -> &'static str;
fn capabilities(&self) -> BackendCapabilities;
fn session(
&self,
config: SessionConfig<Self::Config>,
) -> impl std::future::Future<Output = Result<Self::Session, AgentError>> + Send;
fn resume(
&self,
session_id: &str,
config: SessionConfig<Self::Config>,
) -> impl std::future::Future<Output = Result<Self::Session, AgentError>> + Send;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn capability_support_reports_if_backend_can_handle_feature() {
assert!(!CapabilitySupport::Unsupported.is_supported());
assert!(CapabilitySupport::ConfigDependent.is_supported());
assert!(CapabilitySupport::Supported.is_supported());
}
#[test]
fn resume_semantics_distinguish_stateful_and_stateless() {
assert!(!ResumeSemantics::Unsupported.accepts_resume_id());
assert!(ResumeSemantics::Stateless.accepts_resume_id());
assert!(ResumeSemantics::Stateful.accepts_resume_id());
assert!(!ResumeSemantics::Stateless.preserves_history());
assert!(ResumeSemantics::Stateful.preserves_history());
}
#[test]
fn mcp_support_tracks_supported_modes() {
assert!(!McpSupport::Unsupported.supports_mcp());
assert!(McpSupport::MergeOnly.supports_mode(McpConfigMode::Merge));
assert!(!McpSupport::MergeOnly.supports_mode(McpConfigMode::ExplicitOnly));
assert!(McpSupport::ExplicitServersOnly.supports_mode(McpConfigMode::ExplicitOnly));
assert!(!McpSupport::ExplicitServersOnly.supports_mode(McpConfigMode::Merge));
assert!(McpSupport::MergeAndExplicitOnly.supports_mode(McpConfigMode::ExplicitOnly));
}
}