1use serde::{Deserialize, Serialize};
2
3use crate::enums::{BindingProtocol, SafetyLevel, ToolVisibility, TrustLevel};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
6#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
7pub struct ToolDefinition {
8 pub id: String,
9 pub name: String,
10 pub description: String,
11 pub version: String,
12
13 pub capability: ToolCapability,
14 pub input_schema: serde_json::Value,
15 pub output_schema: serde_json::Value,
16
17 pub bindings: Vec<ToolBinding>,
18 pub safety: ToolSafety,
19 pub resources: ToolResources,
20 pub trust: ToolTrust,
21
22 #[serde(default)]
23 pub visibility: ToolVisibility,
24
25 #[serde(default)]
31 pub required_capabilities: Vec<String>,
32
33 #[serde(default, skip_serializing_if = "Option::is_none")]
38 pub tier: Option<crate::enums::ToolTier>,
39
40 #[serde(default)]
44 pub errors: Vec<ToolErrorDef>,
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
48#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
49pub struct ToolErrorDef {
50 pub code: String,
52 pub description: String,
53 pub retryable: bool,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
57#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
58pub struct ToolCapability {
59 pub domain: String,
60 pub actions: Vec<String>,
61 pub tags: Vec<String>,
62 pub intent_examples: Vec<String>,
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
66#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
67pub struct ToolBinding {
68 pub protocol: BindingProtocol,
69 pub config: serde_json::Value,
70}
71
72#[derive(Debug, Clone, Serialize, Deserialize)]
73#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
74pub struct ToolSafety {
75 pub level: SafetyLevel,
76 pub dry_run: bool,
77 pub side_effects: Vec<String>,
78 pub data_sensitivity: Option<String>,
79}
80
81#[derive(Debug, Clone, Serialize, Deserialize)]
82#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
83pub struct ToolResources {
84 pub timeout_ms: u64,
85 pub max_concurrent: u32,
86 pub rate_limit_per_min: Option<u32>,
87 pub estimated_tokens: Option<u32>,
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
91#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
92pub struct ToolTrust {
93 pub publisher: String,
94 pub trust_level: TrustLevel,
95 pub signature: Option<Vec<u8>>,
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101
102 fn sample() -> ToolDefinition {
103 ToolDefinition {
104 id: "anos:fs.read".into(),
105 name: "Read File".into(),
106 description: "Read a file from disk.".into(),
107 version: "0.1.0".into(),
108 capability: ToolCapability {
109 domain: "fs".into(),
110 actions: vec!["read".into()],
111 tags: vec!["filesystem".into()],
112 intent_examples: vec!["read config.toml".into()],
113 },
114 input_schema: serde_json::json!({
115 "type": "object",
116 "properties": {"path": {"type": "string"}},
117 "required": ["path"]
118 }),
119 output_schema: serde_json::json!({"type": "string"}),
120 bindings: vec![ToolBinding {
121 protocol: BindingProtocol::Cli,
122 config: serde_json::json!({"cmd": "cat"}),
123 }],
124 safety: ToolSafety {
125 level: SafetyLevel::Read,
126 dry_run: false,
127 side_effects: vec![],
128 data_sensitivity: None,
129 },
130 resources: ToolResources {
131 timeout_ms: 5_000,
132 max_concurrent: 8,
133 rate_limit_per_min: None,
134 estimated_tokens: Some(100),
135 },
136 trust: ToolTrust {
137 publisher: "anos".into(),
138 trust_level: TrustLevel::L3Verified,
139 signature: None,
140 },
141 visibility: ToolVisibility::Read,
142 required_capabilities: vec![],
143 tier: None,
144 errors: vec![],
145 }
146 }
147
148 #[test]
149 fn tool_definition_roundtrip() {
150 let t = sample();
151 let json = serde_json::to_string(&t).unwrap();
152 let back: ToolDefinition = serde_json::from_str(&json).unwrap();
153 assert_eq!(back.id, t.id);
154 assert_eq!(back.capability.domain, "fs");
155 assert_eq!(back.safety.level, SafetyLevel::Read);
156 }
157
158 #[test]
159 fn visibility_defaults_when_missing_in_json() {
160 let mut v = serde_json::to_value(sample()).unwrap();
161 v.as_object_mut().unwrap().remove("visibility");
162 let back: ToolDefinition = serde_json::from_value(v).unwrap();
163 assert_eq!(back.visibility, ToolVisibility::Read);
164 }
165
166 #[test]
167 fn required_capabilities_defaults_to_empty_when_missing() {
168 let mut v = serde_json::to_value(sample()).unwrap();
170 v.as_object_mut().unwrap().remove("required_capabilities");
171 let back: ToolDefinition = serde_json::from_value(v).unwrap();
172 assert!(back.required_capabilities.is_empty());
173 }
174
175 #[test]
176 fn required_capabilities_roundtrip_when_set() {
177 let mut t = sample();
178 t.required_capabilities = vec!["exec".into(), "read".into()];
179 let j = serde_json::to_string(&t).unwrap();
180 let back: ToolDefinition = serde_json::from_str(&j).unwrap();
181 assert_eq!(back.required_capabilities, vec!["exec", "read"]);
182 }
183
184 #[test]
185 fn tier_omitted_when_none() {
186 let j = serde_json::to_string(&sample()).unwrap();
187 assert!(
188 !j.contains("\"tier\""),
189 "tier should be skipped when None: {j}"
190 );
191 }
192}