use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Capability {
pub id: String,
pub name: String,
pub description: String,
pub capability_path: CapabilityPath,
pub input_schema: Value,
pub output_schema: Value,
pub protocol: String, pub endpoint: Option<String>, }
#[derive(Default)]
pub struct CapabilityBuilder {
id: Option<String>,
name: Option<String>,
description: Option<String>,
capability_path: Option<CapabilityPath>,
input_schema: Option<Value>,
output_schema: Option<Value>,
protocol: Option<String>,
endpoint: Option<String>,
}
impl CapabilityBuilder {
pub fn id(mut self, id: impl Into<String>) -> Self {
self.id = Some(id.into());
self
}
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
pub fn description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
pub fn capability_path(mut self, capability_path: CapabilityPath) -> Self {
self.capability_path = Some(capability_path);
self
}
pub fn input_schema(mut self, input_schema: Value) -> Self {
self.input_schema = Some(input_schema);
self
}
pub fn output_schema(mut self, output_schema: Value) -> Self {
self.output_schema = Some(output_schema);
self
}
pub fn protocol(mut self, protocol: impl Into<String>) -> Self {
self.protocol = Some(protocol.into());
self
}
pub fn endpoint(mut self, endpoint: Option<String>) -> Self {
self.endpoint = endpoint;
self
}
pub fn build(self) -> Capability {
Capability {
id: self.id.unwrap_or_default(),
name: self.name.unwrap_or_default(),
description: self.description.unwrap_or_default(),
capability_path: self.capability_path.unwrap_or_default(),
input_schema: self.input_schema.unwrap_or_default(),
output_schema: self.output_schema.unwrap_or_default(),
protocol: self.protocol.unwrap_or_default(),
endpoint: self.endpoint,
}
}
}
impl Capability {
#[allow(clippy::too_many_arguments)]
pub fn new(
id: impl Into<String>,
name: impl Into<String>,
description: impl Into<String>,
capability_path: CapabilityPath,
input_schema: Value,
output_schema: Value,
protocol: impl Into<String>,
endpoint: Option<String>,
) -> Self {
Self {
id: id.into(),
name: name.into(),
description: description.into(),
capability_path,
input_schema,
output_schema,
protocol: protocol.into(),
endpoint,
}
}
pub fn builder() -> CapabilityBuilder {
CapabilityBuilder::default()
}
pub fn required_inputs(&self) -> Vec<String> {
self.input_schema
.get("required")
.and_then(|v| v.as_array())
.map(|arr| {
arr.iter()
.filter_map(|v| v.as_str().map(|s| s.to_string()))
.collect()
})
.unwrap_or_default()
}
pub fn input_properties(&self) -> Option<&Value> {
self.input_schema.get("properties")
}
pub fn output_properties(&self) -> Option<&Value> {
self.output_schema.get("properties")
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
pub struct CapabilityPath(Vec<String>);
impl CapabilityPath {
pub fn new(path: Vec<String>) -> Self {
Self(path)
}
pub fn from_string(path: &str) -> Self {
let parts: Vec<String> = path
.split('/')
.filter(|s| !s.is_empty())
.map(|s| s.to_string())
.collect();
Self(parts)
}
pub fn to_path_string(&self) -> String {
self.0.join("/")
}
pub fn segments(&self) -> &[String] {
&self.0
}
pub fn parent(&self) -> Option<Self> {
if self.0.len() <= 1 {
None
} else {
Some(Self(self.0[..self.0.len() - 1].to_vec()))
}
}
pub fn depth(&self) -> usize {
self.0.len()
}
pub fn is_child_of(&self, other: &CapabilityPath) -> bool {
if self.0.len() <= other.0.len() {
return false;
}
self.0.starts_with(&other.0)
}
pub fn contains(&self, other: &CapabilityPath) -> bool {
if self.0.len() > other.0.len() {
return false;
}
other.0.starts_with(&self.0)
}
pub fn name(&self) -> Option<&str> {
self.0.last().map(|s| s.as_str())
}
}
pub struct InputSchema;
impl InputSchema {
pub fn object() -> ObjectSchemaBuilder {
ObjectSchemaBuilder::new()
}
pub fn string() -> TypeBuilder {
TypeBuilder::new("string")
}
pub fn integer() -> TypeBuilder {
TypeBuilder::new("integer")
}
pub fn number() -> TypeBuilder {
TypeBuilder::new("number")
}
pub fn boolean() -> TypeBuilder {
TypeBuilder::new("boolean")
}
pub fn array(item_schema: Value) -> TypeBuilder {
let mut builder = TypeBuilder::new("array");
builder.schema["items"] = item_schema;
builder
}
pub fn string_array() -> TypeBuilder {
Self::array(serde_json::json!({"type": "string"}))
}
pub fn enum_values(values: Vec<&str>) -> TypeBuilder {
let mut builder = TypeBuilder::new("string");
builder.schema["enum"] = serde_json::json!(values);
builder
}
}
pub struct OutputSchema;
impl OutputSchema {
pub fn object() -> ObjectSchemaBuilder {
ObjectSchemaBuilder::new()
}
pub fn string() -> TypeBuilder {
TypeBuilder::new("string")
}
pub fn integer() -> TypeBuilder {
TypeBuilder::new("integer")
}
pub fn number() -> TypeBuilder {
TypeBuilder::new("number")
}
pub fn boolean() -> TypeBuilder {
TypeBuilder::new("boolean")
}
pub fn array(item_schema: Value) -> TypeBuilder {
let mut builder = TypeBuilder::new("array");
builder.schema["items"] = item_schema;
builder
}
}
use serde_json::json;
pub struct ObjectSchemaBuilder {
schema: Value,
}
impl ObjectSchemaBuilder {
fn new() -> Self {
Self {
schema: json!({
"type": "object",
"properties": {},
"required": []
}),
}
}
pub fn property(mut self, name: &str, builder: TypeBuilder) -> Self {
let is_required = builder.required;
let properties = self.schema["properties"]
.as_object_mut()
.expect("Expected 'properties' to be an object in schema");
properties.insert(name.to_string(), builder.build());
if is_required {
let required = self.schema["required"]
.as_array_mut()
.expect("Expected 'required' to be an array in schema");
required.push(json!(name));
}
self
}
pub fn raw_property(mut self, name: &str, schema: Value, is_required: bool) -> Self {
let properties = self.schema["properties"]
.as_object_mut()
.expect("Expected 'properties' to be an object in schema");
properties.insert(name.to_string(), schema);
if is_required {
let required = self.schema["required"]
.as_array_mut()
.expect("Expected 'required' to be an array in schema");
required.push(json!(name));
}
self
}
pub fn build(self) -> Value {
self.schema
}
}
pub struct TypeBuilder {
schema: Value,
required: bool,
}
impl TypeBuilder {
fn new(type_name: &str) -> Self {
Self {
schema: json!({"type": type_name}),
required: true,
}
}
pub fn description(mut self, desc: &str) -> Self {
self.schema["description"] = json!(desc);
self
}
pub fn optional(mut self) -> Self {
self.required = false;
self
}
pub fn enum_values(mut self, values: Vec<&str>) -> Self {
self.schema["enum"] = json!(values);
self
}
pub fn build(self) -> Value {
self.schema
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum MatchType {
Exact,
#[default]
Fuzzy,
}
pub trait CapabilityProvider: Send + Sync {
fn list_capabilities(&self) -> Vec<Capability>;
fn search_capabilities(&self, query: &str, match_type: MatchType) -> Vec<Capability>;
fn list_capabilities_by_path(&self, path: &str) -> Vec<Capability>;
fn get_capability_tree(&self) -> CapabilityNodeInfo;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CapabilityNodeInfo {
pub name: String,
pub path: String,
pub capability_count: usize,
pub children: Vec<CapabilityNodeInfo>,
}
impl CapabilityNodeInfo {
pub fn new(name: impl Into<String>, path: impl Into<String>) -> Self {
Self {
name: name.into(),
path: path.into(),
capability_count: 0,
children: Vec::new(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum CapabilityAccessType {
Public,
Private,
}
#[derive(Debug, Clone)]
pub struct CapabilityCallContext {
pub caller_id: String,
pub timestamp: i64,
pub session_id: Option<String>,
pub parameters: Value,
}
impl CapabilityCallContext {
pub fn new(caller_id: impl Into<String>, parameters: Value) -> Self {
Self {
caller_id: caller_id.into(),
timestamp: chrono::Utc::now().timestamp(),
session_id: None,
parameters,
}
}
pub fn with_session_id(mut self, session_id: impl Into<String>) -> Self {
self.session_id = Some(session_id.into());
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SkillCapabilityBinding {
pub skill_id: String,
pub capability_id: String,
pub binding_type: BindingType,
pub metadata: std::collections::HashMap<String, serde_json::Value>,
}
impl SkillCapabilityBinding {
pub fn new(
skill_id: impl Into<String>,
capability_id: impl Into<String>,
binding_type: BindingType,
) -> Self {
Self {
skill_id: skill_id.into(),
capability_id: capability_id.into(),
binding_type,
metadata: std::collections::HashMap::new(),
}
}
pub fn with_metadata(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
self.metadata.insert(key.into(), value);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum BindingType {
Required,
Optional,
}