use std::collections::BTreeMap;
use crate::error::AgentError;
use crate::provider::ToolProfile;
use super::ToolRegistry;
#[derive(Default)]
pub(crate) struct ToolProfiles {
default: Option<ToolRegistry>,
named: BTreeMap<ToolProfile, ToolRegistry>,
}
pub(crate) enum ToolSelection<'a> {
Named(&'a ToolProfile, &'a ToolRegistry),
Default(&'a ToolRegistry),
Empty,
}
impl ToolProfiles {
pub(crate) fn set_default(&mut self, registry: ToolRegistry) {
self.default = Some(registry);
}
pub(crate) fn insert(&mut self, profile: ToolProfile, registry: ToolRegistry) {
assert!(
!self.named.contains_key(&profile),
"tool profile '{profile}' already registered"
);
self.named.insert(profile, registry);
}
pub(crate) fn is_empty(&self) -> bool {
self.default.is_none() && self.named.is_empty()
}
pub(crate) fn select(
&self,
profile: Option<&ToolProfile>,
) -> Result<ToolSelection<'_>, AgentError> {
match profile {
Some(name) => self
.named
.get_key_value(name)
.map(|(key, registry)| ToolSelection::Named(key, registry))
.ok_or_else(|| AgentError::UnknownToolProfile {
profile: name.to_string(),
available: self.named.keys().map(ToolProfile::to_string).collect(),
}),
None => Ok(self
.default
.as_ref()
.map_or(ToolSelection::Empty, ToolSelection::Default)),
}
}
}
impl<'a> ToolSelection<'a> {
pub(crate) fn registry(&self) -> Option<&'a ToolRegistry> {
match self {
ToolSelection::Named(_, registry) | ToolSelection::Default(registry) => Some(registry),
ToolSelection::Empty => None,
}
}
pub(crate) fn label(&self) -> String {
match self {
ToolSelection::Named(name, _) => format!("tool profile '{name}'"),
ToolSelection::Default(_) => "default tool profile".to_string(),
ToolSelection::Empty => "no tool profile".to_string(),
}
}
pub(crate) fn describe(&self) -> String {
let names = self
.registry()
.map(ToolRegistry::tool_names)
.unwrap_or_default();
match names.len() {
0 => format!("{}: no tools exposed", self.label()),
1 => format!("{}: 1 tool exposed ({})", self.label(), names[0]),
n => format!("{}: {n} tools exposed ({})", self.label(), names.join(", ")),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn select_without_profile_or_default_is_empty() {
let profiles = ToolProfiles::default();
assert!(profiles.is_empty());
let selection = profiles.select(None).unwrap();
assert!(selection.registry().is_none());
assert_eq!(selection.describe(), "no tool profile: no tools exposed");
}
#[test]
fn select_named_profile_with_no_tools() {
let mut profiles = ToolProfiles::default();
let empty = ToolProfile::new("empty");
profiles.insert(empty.clone(), ToolRegistry::new());
assert!(!profiles.is_empty());
let selection = profiles.select(Some(&empty)).unwrap();
assert_eq!(
selection.describe(),
"tool profile 'empty': no tools exposed"
);
}
#[test]
fn unknown_profile_lists_sorted_names() {
let mut profiles = ToolProfiles::default();
profiles.insert(ToolProfile::new("zeta"), ToolRegistry::new());
profiles.insert(ToolProfile::new("alpha"), ToolRegistry::new());
let err = profiles
.select(Some(&ToolProfile::new("beta")))
.err()
.unwrap();
assert_eq!(
err.to_string(),
"unknown tool profile 'beta' (registered profiles: alpha, zeta)"
);
}
}