use crate::{
definition::{Definition, MemoryProfile, ToolControl, ToolDescriptorView},
format::{DaslCid, ModelValue},
input::ActivationInput,
limits::Limits,
memory::Memory,
};
use core::fmt;
#[derive(Clone, Copy)]
pub struct PlanningContext<'a> {
pub definition: &'a Definition,
pub input: &'a ActivationInput,
pub memory: Option<&'a Memory>,
pub limits: &'a Limits,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ToolFailure {
InvalidArguments,
PlanningFailed,
}
impl fmt::Display for ToolFailure {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::InvalidArguments => "tool arguments failed validation",
Self::PlanningFailed => "pure tool planning failed",
})
}
}
impl core::error::Error for ToolFailure {}
pub type ArgumentValidator = fn(&[u8]) -> Result<(), ToolFailure>;
pub type Planner = for<'context, 'arguments> fn(
PlanningContext<'context>,
&'arguments [u8],
) -> Result<ModelValue, ToolFailure>;
#[derive(Clone, Copy)]
pub enum ToolImplementation {
MemoryPatch,
Pure {
validate: ArgumentValidator,
plan: Planner,
},
Yield {
validate: ArgumentValidator,
plan: Planner,
},
}
impl ToolImplementation {
pub fn control(self) -> ToolControl {
match self {
Self::MemoryPatch | Self::Pure { .. } => ToolControl::Continue,
Self::Yield { .. } => ToolControl::Yield,
}
}
}
#[derive(Clone, Copy)]
pub struct ToolRegistration<'a> {
pub descriptor: ToolDescriptorView<'a>,
pub implementation: ToolImplementation,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ToolSetError {
CountLimit,
DuplicateRegistration,
UnlistedRegistration,
MissingRegistration,
VersionMismatch,
ControlMismatch,
DescriptionMismatch,
SchemaMismatch,
AuthorityMismatch,
ImplementationControlMismatch,
MemoryDisabled,
DefinitionMismatch,
}
impl fmt::Display for ToolSetError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::CountLimit => "tool registrations exceed caller/Definition count limit",
Self::DuplicateRegistration => "tool registrations repeat an ID",
Self::UnlistedRegistration => "registration is outside the Definition whitelist",
Self::MissingRegistration => "Definition tool has no implementation registration",
Self::VersionMismatch => "registered tool version differs from Definition",
Self::ControlMismatch => "registered tool control differs from Definition",
Self::DescriptionMismatch => "registered tool description differs from Definition",
Self::SchemaMismatch => "registered tool schema bytes differ from Definition",
Self::AuthorityMismatch => "registered tool authority differs from Definition",
Self::ImplementationControlMismatch => "implementation does not match declared control",
Self::MemoryDisabled => {
"memory-disabled profile cannot bind a memory-patch implementation"
}
Self::DefinitionMismatch => "ToolSet was bound to another Definition",
})
}
}
impl core::error::Error for ToolSetError {}
#[derive(Clone, Copy)]
pub struct ToolSet<'a> {
definition: DaslCid,
registrations: &'a [ToolRegistration<'a>],
}
impl<'a> ToolSet<'a> {
pub fn bind(
definition: &Definition,
registrations: &'a [ToolRegistration<'a>],
caller: &Limits,
) -> Result<Self, ToolSetError> {
let ceiling = caller
.max_collection_entries
.min(definition.limits().max_collection_entries);
if registrations.len() as u64 > ceiling || definition.tools().len() as u64 > ceiling {
return Err(ToolSetError::CountLimit);
}
for (index, registration) in registrations.iter().enumerate() {
let actual = registration.descriptor;
if registrations[..index]
.iter()
.any(|tool| tool.descriptor.id == actual.id)
{
return Err(ToolSetError::DuplicateRegistration);
}
let expected = definition
.tools()
.find(|tool| tool.id == actual.id)
.ok_or(ToolSetError::UnlistedRegistration)?;
if actual.version != expected.version {
return Err(ToolSetError::VersionMismatch);
}
if actual.control != expected.control {
return Err(ToolSetError::ControlMismatch);
}
if actual.description != expected.description {
return Err(ToolSetError::DescriptionMismatch);
}
if actual.arguments_schema != expected.arguments_schema {
return Err(ToolSetError::SchemaMismatch);
}
if actual.authority_binding != expected.authority_binding {
return Err(ToolSetError::AuthorityMismatch);
}
if registration.implementation.control() != expected.control {
return Err(ToolSetError::ImplementationControlMismatch);
}
if definition.memory_profile() == MemoryProfile::None
&& matches!(registration.implementation, ToolImplementation::MemoryPatch)
{
return Err(ToolSetError::MemoryDisabled);
}
}
if definition.tools().len() != registrations.len() {
return Err(ToolSetError::MissingRegistration);
}
Ok(Self {
definition: definition.cid(),
registrations,
})
}
pub fn definition_cid(&self) -> DaslCid {
self.definition
}
pub fn lookup(&self, id: &str) -> Option<BoundTool<'a>> {
let registered = self
.registrations
.iter()
.find(|tool| tool.descriptor.id == id)?;
Some(BoundTool {
descriptor: registered.descriptor,
implementation: registered.implementation,
})
}
pub fn tools<'view>(
&'view self,
definition: &'view Definition,
) -> Result<impl Iterator<Item = BoundTool<'view>> + 'view, ToolSetError> {
if definition.cid() != self.definition {
return Err(ToolSetError::DefinitionMismatch);
}
Ok(definition.tools().map(|descriptor| BoundTool {
descriptor,
implementation: self
.lookup(descriptor.id)
.expect("bound Definition tool")
.implementation,
}))
}
}
#[derive(Clone, Copy)]
pub struct BoundTool<'a> {
descriptor: ToolDescriptorView<'a>,
implementation: ToolImplementation,
}
impl<'a> BoundTool<'a> {
pub fn descriptor(self) -> ToolDescriptorView<'a> {
self.descriptor
}
pub fn implementation(self) -> ToolImplementation {
self.implementation
}
}
#[cfg(test)]
mod tests;