use std::collections::HashMap;
use std::sync::Arc;
use crate::error::{ToolError, codes};
use crate::metadata::CapabilityFlag;
use crate::tool::{DynTool, SharedTool, ToolDefinition};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum CapabilityMode {
#[default]
Full,
ReadOnly,
Plan,
}
impl CapabilityMode {
#[must_use]
pub fn parse(s: &str) -> Option<Self> {
match s.trim().to_ascii_lowercase().as_str() {
"full" => Some(Self::Full),
"read_only" | "read-only" | "readonly" => Some(Self::ReadOnly),
"plan" => Some(Self::Plan),
_ => None,
}
}
#[must_use]
pub const fn intersect(self, other: Self) -> Self {
use CapabilityMode::{Full, Plan, ReadOnly};
match (self, other) {
(ReadOnly, _) | (_, ReadOnly) => ReadOnly,
(Plan, _) | (_, Plan) => Plan,
(Full, Full) => Full,
}
}
}
#[derive(Clone, Default)]
pub struct ToolRegistry {
tools: Arc<HashMap<String, SharedTool>>,
}
impl std::fmt::Debug for ToolRegistry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut names: Vec<_> = self.tools.keys().map(String::as_str).collect();
names.sort_unstable();
f.debug_struct("ToolRegistry")
.field("tools", &names)
.finish()
}
}
impl ToolRegistry {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn from_tools(tools: Vec<SharedTool>) -> Self {
let mut map = HashMap::new();
for tool in tools {
map.insert(tool.name().to_owned(), tool);
}
Self {
tools: Arc::new(map),
}
}
#[must_use]
pub fn get(&self, name: &str) -> Option<SharedTool> {
self.tools.get(name).cloned()
}
pub fn require(&self, name: &str) -> Result<SharedTool, ToolError> {
self.get(name).ok_or_else(|| codes::not_found(name))
}
#[must_use]
pub fn definitions(&self, mode: CapabilityMode) -> Vec<ToolDefinition> {
let mut defs: Vec<_> = self
.tools
.values()
.filter(|t| self.allows(t.as_ref(), mode))
.map(|t| t.definition())
.collect();
defs.sort_by(|a, b| a.name.cmp(&b.name));
defs
}
#[must_use]
pub fn allows(&self, tool: &dyn DynTool, mode: CapabilityMode) -> bool {
let _ = self;
let meta = tool.metadata();
match mode {
CapabilityMode::Full => true,
CapabilityMode::ReadOnly => meta.allowed_in_read_only(),
CapabilityMode::Plan => !meta
.capabilities
.iter()
.any(|c| matches!(c, CapabilityFlag::Execute | CapabilityFlag::Spawn)),
}
}
#[must_use]
pub fn len(&self) -> usize {
self.tools.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.tools.is_empty()
}
#[must_use]
pub fn merge(&self, other: &Self) -> Self {
let mut map = (*self.tools).clone();
for (k, v) in other.tools.iter() {
map.insert(k.clone(), Arc::clone(v));
}
Self {
tools: Arc::new(map),
}
}
#[must_use]
pub fn names(&self) -> Vec<String> {
let mut n: Vec<_> = self.tools.keys().cloned().collect();
n.sort_unstable();
n
}
}