use machi_tools::CapabilityMode;
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
#[non_exhaustive]
pub enum Instructions {
Static(String),
}
impl Instructions {
#[must_use]
pub fn resolve(&self) -> String {
match self {
Self::Static(s) => s.clone(),
}
}
}
impl From<String> for Instructions {
fn from(value: String) -> Self {
Self::Static(value)
}
}
impl From<&str> for Instructions {
fn from(value: &str) -> Self {
Self::Static(value.to_owned())
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
#[non_exhaustive]
pub enum ToolPolicy {
#[default]
InheritAll,
Allowlist(Vec<String>),
Denylist(Vec<String>),
}
impl ToolPolicy {
#[must_use]
pub fn admits(&self, name: &str) -> bool {
match self {
Self::InheritAll => true,
Self::Allowlist(allow) => allow.iter().any(|n| n == name),
Self::Denylist(deny) => !deny.iter().any(|n| n == name),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompletionRequirement {
pub tool: String,
pub reminder: String,
pub max_retries: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum AgentSource {
Builtin,
User,
#[default]
Project,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentDefinition {
pub name: String,
pub description: String,
pub instructions: Instructions,
pub model: String,
#[serde(default)]
pub tools: ToolPolicy,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub output_schema: Option<Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub completion: Option<CompletionRequirement>,
#[serde(default = "default_max_steps")]
pub max_steps: usize,
#[serde(default = "default_enabled")]
pub enabled: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub capability: Option<CapabilityMode>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source: Option<AgentSource>,
}
fn default_max_steps() -> usize {
32
}
const fn default_enabled() -> bool {
true
}
impl AgentDefinition {
#[must_use]
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
description: String::new(),
instructions: Instructions::Static(String::new()),
model: "default".into(),
tools: ToolPolicy::InheritAll,
output_schema: None,
completion: None,
max_steps: default_max_steps(),
enabled: true,
capability: None,
source: None,
}
}
pub fn validate(&self) -> Result<(), machi_types::MachiError> {
use machi_types::{ErrorCode, MachiError};
if self.name.trim().is_empty() {
return Err(MachiError::new(
ErrorCode::AgentInvalidDefinition,
"agent name must be non-empty",
));
}
if self.model.trim().is_empty() {
return Err(MachiError::new(
ErrorCode::AgentInvalidDefinition,
"agent model must be non-empty",
));
}
if self.max_steps == 0 {
return Err(MachiError::new(
ErrorCode::AgentInvalidDefinition,
"max_steps must be >= 1",
));
}
Ok(())
}
}