use std::collections::BTreeSet;
use crate::KitError;
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "snake_case", tag = "mode", content = "names")]
pub enum ToolSelection {
Auto,
None,
Required,
Allowed(Vec<String>),
RequiredOneOf(Vec<String>),
}
impl ToolSelection {
pub(crate) fn validate(&self) -> Result<(), KitError> {
match self {
Self::Auto | Self::None | Self::Required => Ok(()),
Self::Allowed(names) | Self::RequiredOneOf(names) => Self::validate_names(names),
}
}
fn validate_names(names: &[String]) -> Result<(), KitError> {
if names.is_empty() {
return Err(KitError::invalid_input(
"named tool selection must not be empty",
));
}
let mut unique = BTreeSet::new();
for name in names {
if name.is_empty()
|| name.len() > 128
|| name.trim() != name
|| name.split('.').any(str::is_empty)
|| !name
.bytes()
.all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'_' | b'-' | b'.'))
{
return Err(KitError::invalid_input(
"tool selection names must be canonical portable names",
));
}
if !unique.insert(name) {
return Err(KitError::invalid_input(
"tool selection names must not contain duplicates",
));
}
}
Ok(())
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct ToolInfo {
name: String,
description: Option<String>,
input_schema: Option<serde_json::Value>,
}
impl ToolInfo {
pub fn new(
name: impl Into<String>,
description: Option<String>,
input_schema: Option<serde_json::Value>,
) -> Result<Self, KitError> {
let name = name.into();
if name.is_empty() || name.trim() != name {
return Err(KitError::invalid_input(
"tool name must be nonempty and unpadded",
));
}
if description
.as_deref()
.is_some_and(|value| value.is_empty() || value.trim() != value)
{
return Err(KitError::invalid_input(
"tool description must be nonempty and unpadded when present",
));
}
Ok(Self {
name,
description,
input_schema,
})
}
pub fn name(&self) -> &str {
&self.name
}
pub fn description(&self) -> Option<&str> {
self.description.as_deref()
}
pub fn input_schema(&self) -> Option<&serde_json::Value> {
self.input_schema.as_ref()
}
}