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>,
94 pub estimated_tokens: Option<u32>,
95}
96
97#[derive(Debug, Clone, Serialize, Deserialize)]
98#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
99pub struct ToolTrust {
100 pub publisher: String,
101 pub trust_level: TrustLevel,
107 pub signature: Option<Vec<u8>>,
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 fn sample() -> ToolDefinition {
115 ToolDefinition {
116 id: "anos:fs.read".into(),
117 name: "Read File".into(),
118 description: "Read a file from disk.".into(),
119 version: "0.1.0".into(),
120 capability: ToolCapability {
121 domain: "fs".into(),
122 actions: vec!["read".into()],
123 tags: vec!["filesystem".into()],
124 intent_examples: vec!["read config.toml".into()],
125 },
126 input_schema: serde_json::json!({
127 "type": "object",
128 "properties": {"path": {"type": "string"}},
129 "required": ["path"]
130 }),
131 output_schema: serde_json::json!({"type": "string"}),
132 bindings: vec![ToolBinding {
133 protocol: BindingProtocol::Cli,
134 config: serde_json::json!({"cmd": "cat"}),
135 }],
136 safety: ToolSafety {
137 level: SafetyLevel::Read,
138 dry_run: false,
139 side_effects: vec![],
140 data_sensitivity: None,
141 },
142 resources: ToolResources {
143 timeout_ms: 5_000,
144 max_concurrent: 8,
145 rate_limit_per_min: None,
146 estimated_tokens: Some(100),
147 },
148 trust: ToolTrust {
149 publisher: "anos".into(),
150 trust_level: TrustLevel::L3Verified,
151 signature: None,
152 },
153 visibility: ToolVisibility::Read,
154 required_capabilities: vec![],
155 tier: None,
156 errors: vec![],
157 }
158 }
159
160 #[test]
161 fn tool_definition_roundtrip() {
162 let t = sample();
163 let json = serde_json::to_string(&t).unwrap();
164 let back: ToolDefinition = serde_json::from_str(&json).unwrap();
165 assert_eq!(back.id, t.id);
166 assert_eq!(back.capability.domain, "fs");
167 assert_eq!(back.safety.level, SafetyLevel::Read);
168 }
169
170 #[test]
171 fn visibility_defaults_when_missing_in_json() {
172 let mut v = serde_json::to_value(sample()).unwrap();
173 v.as_object_mut().unwrap().remove("visibility");
174 let back: ToolDefinition = serde_json::from_value(v).unwrap();
175 assert_eq!(back.visibility, ToolVisibility::Read);
176 }
177
178 #[test]
179 fn required_capabilities_defaults_to_empty_when_missing() {
180 let mut v = serde_json::to_value(sample()).unwrap();
182 v.as_object_mut().unwrap().remove("required_capabilities");
183 let back: ToolDefinition = serde_json::from_value(v).unwrap();
184 assert!(back.required_capabilities.is_empty());
185 }
186
187 #[test]
188 fn required_capabilities_roundtrip_when_set() {
189 let mut t = sample();
190 t.required_capabilities = vec!["exec".into(), "read".into()];
191 let j = serde_json::to_string(&t).unwrap();
192 let back: ToolDefinition = serde_json::from_str(&j).unwrap();
193 assert_eq!(back.required_capabilities, vec!["exec", "read"]);
194 }
195
196 #[test]
197 fn tier_omitted_when_none() {
198 let j = serde_json::to_string(&sample()).unwrap();
199 assert!(
200 !j.contains("\"tier\""),
201 "tier should be skipped when None: {j}"
202 );
203 }
204}