use std::collections::BTreeMap;
use runifold_core::{CapabilityDescriptor, CapabilityId, CapabilityKind, EffectClass, RiskLevel};
use runifold_model::ToolSpec;
use serde::{Deserialize, Serialize};
use serde_json::{Value, json};
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct AgentDescriptor {
pub id: CapabilityId,
pub name: String,
pub version: String,
pub description: String,
pub risk: RiskLevel,
pub metadata: BTreeMap<String, Value>,
}
impl AgentDescriptor {
pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
Self {
id: CapabilityId::new(),
name: name.into(),
version: "1".into(),
description: description.into(),
risk: RiskLevel::Medium,
metadata: BTreeMap::new(),
}
}
#[must_use]
pub const fn with_id(mut self, id: CapabilityId) -> Self {
self.id = id;
self
}
pub fn capability(&self) -> CapabilityDescriptor {
CapabilityDescriptor {
id: self.id,
name: self.name.clone(),
version: self.version.clone(),
kind: CapabilityKind::Agent,
input_schema: input_schema(),
output_schema: output_schema(),
effect: EffectClass::Unknown,
risk: self.risk,
metadata: self.metadata.clone(),
}
}
pub fn model_spec(&self) -> ToolSpec {
ToolSpec {
name: self.name.clone(),
description: self.description.clone(),
input_schema: input_schema(),
output_schema: Some(output_schema()),
metadata: self.metadata.clone(),
}
}
}
fn input_schema() -> Value {
json!({
"type": "object",
"properties": {
"input": {
"type": "string",
"description": "Task or question delegated to the agent"
}
},
"required": ["input"],
"additionalProperties": false
})
}
fn output_schema() -> Value {
json!({
"type": "object",
"properties": {
"agent": {"type": "string"},
"content": {"type": "array"},
"turns": {"type": "integer", "minimum": 0},
"tool_calls": {"type": "integer", "minimum": 0},
"delegations": {"type": "integer", "minimum": 0}
},
"required": ["agent", "content", "turns", "tool_calls", "delegations"],
"additionalProperties": false
})
}
#[cfg(test)]
mod tests {
use runifold_core::CapabilityId;
use super::AgentDescriptor;
#[test]
fn configured_identity_survives_descriptor_reconstruction() {
let id: CapabilityId = "018f6f7e-6f1d-7f2a-9c40-7f4f8f0a3d21"
.parse()
.expect("configured UUID is valid");
let first = AgentDescriptor::new("researcher", "delegate research").with_id(id);
let second = AgentDescriptor::new("researcher", "delegate research").with_id(id);
assert_eq!(first.id, second.id);
assert_eq!(first.capability().id, second.capability().id);
}
#[test]
fn default_construction_remains_ephemeral() {
let first = AgentDescriptor::new("researcher", "delegate research");
let second = AgentDescriptor::new("researcher", "delegate research");
assert_ne!(first.id, second.id);
}
}