mod chat;
mod delegate;
mod health;
#[cfg(test)]
pub mod mock;
pub use delegate::{DelegationError, DelegationOutcome, SmDecision, TaskSpec};
use std::path::PathBuf;
use std::sync::Arc;
use super::config::SessionManagerConfig;
use super::providers::TierResolver;
#[cfg(feature = "sm-memory")]
use super::memory::SmMemory;
pub use chat::{SmAgentError, SmChatOutcome};
pub use health::{SmHealth, SmModelTiers};
#[derive(Clone)]
pub struct SessionManagerAgent {
config: SessionManagerConfig,
runtime: Option<AgentRuntime>,
}
impl std::fmt::Debug for SessionManagerAgent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SessionManagerAgent")
.field("enabled", &self.config.enabled)
.field("has_runtime", &self.runtime.is_some())
.finish()
}
}
#[derive(Clone)]
struct AgentRuntime {
resolver: Arc<dyn TierResolver>,
data_root: PathBuf,
#[cfg(feature = "sm-memory")]
memory: Option<SmMemory>,
}
impl SessionManagerAgent {
pub fn new(config: SessionManagerConfig) -> Self {
Self {
config,
runtime: None,
}
}
#[cfg(feature = "sm-memory")]
pub fn with_runtime(
config: SessionManagerConfig,
resolver: Arc<dyn TierResolver>,
data_root: impl Into<PathBuf>,
memory: Option<SmMemory>,
) -> Self {
Self {
config,
runtime: Some(AgentRuntime {
resolver,
data_root: data_root.into(),
memory,
}),
}
}
#[cfg(not(feature = "sm-memory"))]
pub fn with_runtime(
config: SessionManagerConfig,
resolver: Arc<dyn TierResolver>,
data_root: impl Into<PathBuf>,
) -> Self {
Self {
config,
runtime: Some(AgentRuntime {
resolver,
data_root: data_root.into(),
}),
}
}
#[cfg(test)]
pub fn for_test(
config: SessionManagerConfig,
resolver: Arc<dyn TierResolver>,
data_root: impl Into<PathBuf>,
) -> Self {
#[cfg(feature = "sm-memory")]
{
Self::with_runtime(config, resolver, data_root, None)
}
#[cfg(not(feature = "sm-memory"))]
{
Self::with_runtime(config, resolver, data_root)
}
}
pub fn config(&self) -> &SessionManagerConfig {
&self.config
}
pub fn is_enabled(&self) -> bool {
self.config.enabled
}
pub fn has_runtime(&self) -> bool {
self.runtime.is_some()
}
fn runtime_ref(&self) -> Option<&AgentRuntime> {
self.runtime.as_ref()
}
#[cfg(feature = "sm-memory")]
async fn delegate_recall(&self, runtime: &AgentRuntime, message: &str) -> Option<String> {
let memory = runtime.memory.as_ref()?;
match memory.recall(message).await {
Ok(hits) if !hits.is_empty() => {
let joined = hits
.iter()
.map(|h| h.drawer.content.trim())
.filter(|c| !c.is_empty())
.collect::<Vec<_>>()
.join("\n");
(!joined.trim().is_empty()).then_some(joined)
}
_ => None,
}
}
#[cfg(not(feature = "sm-memory"))]
async fn delegate_recall(&self, _runtime: &AgentRuntime, _message: &str) -> Option<String> {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn agent_new_is_inert() {
let cfg = SessionManagerConfig::default();
let agent = SessionManagerAgent::new(cfg.clone());
assert_eq!(agent.config(), &cfg);
assert!(!agent.has_runtime(), "new() must not wire a runtime");
}
#[test]
fn agent_default_is_disabled() {
let agent = SessionManagerAgent::new(SessionManagerConfig::default());
assert!(!agent.is_enabled());
}
#[test]
fn agent_new_has_no_runtime() {
let agent = SessionManagerAgent::new(SessionManagerConfig::default());
assert!(!agent.has_runtime());
}
}