use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::sync::Arc;
use async_trait::async_trait;
use foundation_db::traits::DocumentStore;
use foundation_db::StorageResult;
use crate::agentic::context::{ContextProvider, KnowledgeHit, SearchMode};
use crate::agentic::embedding::EmbeddingProvider;
use crate::agentic::errors::AgenticError;
use crate::agentic::memory::MemoryHierarchy;
use crate::agentic::memory_store::{MemoryStore, SessionMemory};
use crate::agentic::tool_impl::{ToolCallManager, ToolImpl};
use crate::agentic::tools::shed::{ToolDiscovery, SHED_TOOL_NAME};
use crate::types::{MemoryFact, SessionId};
#[async_trait]
pub trait ContextSearch: Send + Sync {
fn session_id(&self) -> &SessionId;
async fn search(&self, query: &str, mode: SearchMode, k: usize) -> Vec<KnowledgeHit>;
}
#[async_trait]
impl<D: DocumentStore + 'static, M: MemoryStore + 'static> ContextSearch for ContextProvider<D, M> {
fn session_id(&self) -> &SessionId {
ContextProvider::session_id(self)
}
async fn search(&self, query: &str, mode: SearchMode, k: usize) -> Vec<KnowledgeHit> {
ContextProvider::search(self, query, mode, k).await
}
}
#[async_trait]
pub trait MemoryAccess: Send + Sync {
fn session_id(&self) -> &SessionId;
async fn hydrate(&self) -> StorageResult<SessionMemory>;
async fn update_working_memory(
&self,
facts: Vec<MemoryFact>,
prev_version: u64,
) -> StorageResult<()>;
}
#[async_trait]
impl<M: MemoryStore + 'static, D: DocumentStore + 'static> MemoryAccess for MemoryHierarchy<M, D> {
fn session_id(&self) -> &SessionId {
MemoryHierarchy::session_id(self)
}
async fn hydrate(&self) -> StorageResult<SessionMemory> {
self.coordinator()
.hydrate_async(MemoryHierarchy::session_id(self))
.await
}
async fn update_working_memory(
&self,
facts: Vec<MemoryFact>,
prev_version: u64,
) -> StorageResult<()> {
MemoryHierarchy::update_working_memory(self, facts, prev_version).await
}
}
#[derive(Clone)]
pub struct SessionParts {
pub session_id: SessionId,
pub context: Arc<dyn ContextSearch>,
pub memory: Arc<dyn MemoryAccess>,
pub embedder: Option<(Arc<dyn EmbeddingProvider>, String)>,
}
pub trait ToolConstructor: Send + Sync {
fn name(&self) -> &str;
fn construct(&self, session: &SessionParts) -> Arc<dyn ToolImpl>;
fn prebuilt(&self) -> Option<Arc<dyn ToolImpl>> {
None
}
}
struct Prebuilt {
name: String,
tool: Arc<dyn ToolImpl>,
}
impl Prebuilt {
fn new(tool: Arc<dyn ToolImpl>) -> Self {
Self {
name: tool.definition().name().to_string(),
tool,
}
}
}
impl ToolConstructor for Prebuilt {
fn name(&self) -> &str {
&self.name
}
fn construct(&self, _session: &SessionParts) -> Arc<dyn ToolImpl> {
Arc::clone(&self.tool)
}
fn prebuilt(&self) -> Option<Arc<dyn ToolImpl>> {
Some(Arc::clone(&self.tool))
}
}
impl<T: ToolImpl + 'static> From<T> for Box<dyn ToolConstructor> {
fn from(tool: T) -> Self {
Box::new(Prebuilt::new(Arc::new(tool)))
}
}
impl<T: ToolImpl + 'static> From<Arc<T>> for Box<dyn ToolConstructor> {
fn from(tool: Arc<T>) -> Self {
Box::new(Prebuilt::new(tool))
}
}
impl From<Arc<dyn ToolImpl>> for Box<dyn ToolConstructor> {
fn from(tool: Arc<dyn ToolImpl>) -> Self {
Box::new(Prebuilt::new(tool))
}
}
struct FnConstructor<F> {
name: String,
make: F,
}
impl<F> ToolConstructor for FnConstructor<F>
where
F: Fn(&SessionParts) -> Arc<dyn ToolImpl> + Send + Sync,
{
fn name(&self) -> &str {
&self.name
}
fn construct(&self, session: &SessionParts) -> Arc<dyn ToolImpl> {
(self.make)(session)
}
}
pub fn tool_fn<F>(name: &str, f: F) -> Box<dyn ToolConstructor>
where
F: Fn(&SessionParts) -> Arc<dyn ToolImpl> + Send + Sync + 'static,
{
Box::new(FnConstructor {
name: name.to_string(),
make: f,
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum ToolShedError {
DuplicateTool(String),
ReservedName(String),
NameMismatch { expected: String, actual: String },
NeedsSession(String),
Discovery(String),
}
impl std::fmt::Display for ToolShedError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ToolShedError::DuplicateTool(name) => {
write!(f, "tool '{name}' was added to the toolshed twice")
}
ToolShedError::ReservedName(name) => {
write!(f, "'{name}' is reserved for the built-in discovery tool")
}
ToolShedError::NameMismatch { expected, actual } => write!(
f,
"constructor for '{expected}' built a tool named '{actual}'"
),
ToolShedError::NeedsSession(name) => write!(
f,
"tool '{name}' is built from a session; add it to a ToolShed instead"
),
ToolShedError::Discovery(reason) => {
write!(f, "indexing tools for discovery failed: {reason}")
}
}
}
}
impl std::error::Error for ToolShedError {}
impl From<ToolShedError> for AgenticError {
fn from(e: ToolShedError) -> Self {
AgenticError::Session(format!("toolshed: {e}"))
}
}
#[derive(Default)]
pub struct ToolShed {
tools: HashMap<String, Box<dyn ToolConstructor>>,
problems: Vec<ToolShedError>,
}
impl std::fmt::Debug for ToolShed {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ToolShed")
.field("tools", &self.names())
.field("problems", &self.problems)
.finish()
}
}
impl ToolShed {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn tool(mut self, tool: impl Into<Box<dyn ToolConstructor>>) -> Self {
self.insert(tool.into());
self
}
#[must_use]
pub fn tools(mut self, tools: impl IntoIterator<Item = Box<dyn ToolConstructor>>) -> Self {
for tool in tools {
self.insert(tool);
}
self
}
fn insert(&mut self, constructor: Box<dyn ToolConstructor>) {
let name = constructor.name().to_string();
if name == SHED_TOOL_NAME {
self.problems.push(ToolShedError::ReservedName(name));
return;
}
match self.tools.entry(name) {
Entry::Occupied(taken) => self
.problems
.push(ToolShedError::DuplicateTool(taken.key().clone())),
Entry::Vacant(slot) => {
slot.insert(constructor);
}
}
}
#[must_use]
pub fn get(&self, name: &str) -> Option<&dyn ToolConstructor> {
self.tools.get(name).map(AsRef::as_ref)
}
#[must_use]
pub fn contains(&self, name: &str) -> bool {
self.tools.contains_key(name)
}
#[must_use]
pub fn names(&self) -> Vec<&str> {
let mut names: Vec<&str> = self.tools.keys().map(String::as_str).collect();
names.sort_unstable();
names
}
#[must_use]
pub fn len(&self) -> usize {
self.tools.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.tools.is_empty()
}
pub fn build(self, session: &SessionParts) -> Result<ToolCallManager, ToolShedError> {
if let Some(problem) = self.problems.into_iter().next() {
return Err(problem);
}
let manager = ToolCallManager::new(session.session_id.clone());
let mut constructors: Vec<(String, Box<dyn ToolConstructor>)> =
self.tools.into_iter().collect();
constructors.sort_by(|(a, _), (b, _)| a.cmp(b));
for (name, constructor) in constructors {
let tool = constructor.construct(session);
let built = tool.definition().name().to_string();
if built != name {
return Err(ToolShedError::NameMismatch {
expected: name,
actual: built,
});
}
manager.register(tool);
}
if let Some((embedder, model)) = &session.embedder {
let discovery = ToolDiscovery::in_memory(Arc::clone(embedder), model.clone());
manager
.enable_discovery(Arc::new(discovery))
.map_err(|e| ToolShedError::Discovery(e.to_string()))?;
}
Ok(manager)
}
}