chimera-core 0.2.0

Shared traits, types, and transport for the chimera AI agent SDK
Documentation
use serde::{Deserialize, Serialize};

use crate::{AgentError, McpConfigMode, Session, SessionConfig};

/// Whether a backend implementation can handle a feature.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum CapabilitySupport {
    /// The Chimera backend implementation does not support this feature.
    Unsupported,
    /// Support depends on the selected backend config or session mode.
    ConfigDependent,
    /// The feature is supported unconditionally by this backend implementation.
    Supported,
}

impl CapabilitySupport {
    pub fn is_supported(self) -> bool {
        !matches!(self, Self::Unsupported)
    }
}

/// How `resume()` behaves for a backend implementation.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum ResumeSemantics {
    /// `resume()` is not supported.
    Unsupported,
    /// `resume()` accepts an ID but does not restore prior conversation state.
    Stateless,
    /// `resume()` restores prior conversation state.
    Stateful,
}

impl ResumeSemantics {
    pub fn accepts_resume_id(self) -> bool {
        !matches!(self, Self::Unsupported)
    }

    pub fn preserves_history(self) -> bool {
        matches!(self, Self::Stateful)
    }
}

/// How a backend implementation handles MCP wiring.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum McpSupport {
    /// MCP is not supported.
    Unsupported,
    /// Backend can use Chimera-provided MCP servers, but does not merge ambient
    /// backend MCP config.
    ExplicitServersOnly,
    /// Backend supports ambient MCP merge only.
    MergeOnly,
    /// Backend supports both merged and explicit-only MCP modes.
    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,
        }
    }
}

/// Public feature matrix for a Chimera backend implementation.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct BackendCapabilities {
    /// Whether structured image inputs are supported.
    pub input_images: CapabilitySupport,
    /// Whether structured output schemas are supported.
    pub output_schema: CapabilitySupport,
    /// Whether an in-flight turn can be interrupted.
    pub interrupt: CapabilitySupport,
    /// How `resume()` behaves.
    pub resume: ResumeSemantics,
    /// How MCP is supported.
    pub mcp: McpSupport,
    /// Whether tool/function call events are emitted.
    pub tool_call_events: CapabilitySupport,
}

/// Factory for creating agent sessions.
///
/// Not object-safe (has associated types and RPITIT). Use `AgentBackend`
/// from the facade crate for runtime dispatch.
pub trait Backend: Send + Sync {
    /// Backend-specific session configuration type.
    type Config: Send + Sync + 'static;

    /// Concrete session type returned by this backend.
    type Session: Session + 'static;

    /// Backend identifier (e.g. "codex", "claude-code").
    fn name(&self) -> &'static str;

    /// Backend feature support exposed by this implementation.
    ///
    /// This reports backend-level support. For config-dependent behavior,
    /// callers should consult the backend config's own `capabilities()`
    /// helper when available.
    fn capabilities(&self) -> BackendCapabilities;

    /// Start a new agent session.
    fn session(
        &self,
        config: SessionConfig<Self::Config>,
    ) -> impl std::future::Future<Output = Result<Self::Session, AgentError>> + Send;

    /// Resume a previously saved session by ID.
    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));
    }
}