ai_agents_tools/
provider.rs1use async_trait::async_trait;
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4use std::sync::Arc;
5
6use super::types::{ToolAliases, ToolMetadata, ToolProviderType};
7use super::{Tool, ToolExecutionContext, ToolPolicyBindings, ToolResult};
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct ToolDescriptor {
11 pub id: String,
12 pub name: String,
13 pub description: String,
14 pub input_schema: Value,
15 #[serde(default, skip_serializing_if = "Option::is_none")]
16 pub aliases: Option<ToolAliases>,
17 #[serde(default, skip_serializing_if = "Option::is_none")]
18 pub metadata: Option<ToolMetadata>,
19 #[serde(default)]
21 pub policy_bindings: ToolPolicyBindings,
22}
23
24impl ToolDescriptor {
25 pub fn new(
26 id: impl Into<String>,
27 name: impl Into<String>,
28 description: impl Into<String>,
29 input_schema: Value,
30 ) -> Self {
31 Self {
32 id: id.into(),
33 name: name.into(),
34 description: description.into(),
35 input_schema,
36 aliases: None,
37 metadata: None,
38 policy_bindings: ToolPolicyBindings::default(),
39 }
40 }
41
42 pub fn with_aliases(mut self, aliases: ToolAliases) -> Self {
43 self.aliases = Some(aliases);
44 self
45 }
46
47 pub fn with_metadata(mut self, metadata: ToolMetadata) -> Self {
48 self.metadata = Some(metadata);
49 self
50 }
51
52 pub fn with_policy_bindings(mut self, bindings: ToolPolicyBindings) -> Self {
54 self.policy_bindings = bindings;
55 self
56 }
57
58 pub fn get_name(&self, lang: Option<&str>) -> &str {
59 if let Some(lang) = lang {
60 if let Some(ref aliases) = self.aliases {
61 if let Some(name) = aliases.get_name(lang) {
62 return name;
63 }
64 }
65 }
66 &self.name
67 }
68
69 pub fn get_description(&self, lang: Option<&str>) -> &str {
70 if let Some(lang) = lang {
71 if let Some(ref aliases) = self.aliases {
72 if let Some(desc) = aliases.get_description(lang) {
73 return desc;
74 }
75 }
76 }
77 &self.description
78 }
79}
80
81#[derive(Debug, Clone, Serialize, Deserialize, Default)]
82#[serde(tag = "status", rename_all = "snake_case")]
83pub enum ProviderHealth {
84 #[default]
85 Healthy,
86 Degraded {
87 message: String,
88 },
89 Unavailable {
90 message: String,
91 },
92}
93
94impl ProviderHealth {
95 pub fn is_healthy(&self) -> bool {
96 matches!(self, ProviderHealth::Healthy)
97 }
98
99 pub fn is_available(&self) -> bool {
100 !matches!(self, ProviderHealth::Unavailable { .. })
101 }
102
103 pub fn degraded(message: impl Into<String>) -> Self {
104 ProviderHealth::Degraded {
105 message: message.into(),
106 }
107 }
108
109 pub fn unavailable(message: impl Into<String>) -> Self {
110 ProviderHealth::Unavailable {
111 message: message.into(),
112 }
113 }
114}
115
116#[derive(Debug, thiserror::Error)]
117pub enum ToolProviderError {
118 #[error("Tool not found: {0}")]
119 ToolNotFound(String),
120
121 #[error("Execution failed: {0}")]
122 ExecutionFailed(String),
123
124 #[error("Provider unavailable: {0}")]
125 Unavailable(String),
126
127 #[error("Connection error: {0}")]
128 ConnectionError(String),
129
130 #[error("Configuration error: {0}")]
131 ConfigError(String),
132
133 #[error("Timeout after {0}ms")]
134 Timeout(u64),
135
136 #[error("{0}")]
137 Other(String),
138}
139
140#[async_trait]
141pub trait ToolProvider: Send + Sync {
142 fn id(&self) -> &str;
143
144 fn name(&self) -> &str;
145
146 fn provider_type(&self) -> ToolProviderType;
147
148 async fn list_tools(&self) -> Vec<ToolDescriptor>;
149
150 async fn get_tool(&self, tool_id: &str) -> Option<Arc<dyn Tool>>;
151
152 async fn execute(
153 &self,
154 tool_id: &str,
155 args: Value,
156 ctx: ToolExecutionContext,
157 ) -> Result<ToolResult, ToolProviderError> {
158 if let Some(tool) = self.get_tool(tool_id).await {
159 Ok(tool.execute(args, ctx).await)
160 } else {
161 Err(ToolProviderError::ToolNotFound(tool_id.to_string()))
162 }
163 }
164
165 fn supports_refresh(&self) -> bool {
166 false
167 }
168
169 async fn refresh(&self) -> Result<(), ToolProviderError> {
170 Ok(())
171 }
172
173 async fn health_check(&self) -> ProviderHealth {
174 ProviderHealth::Healthy
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181
182 #[test]
183 fn test_tool_descriptor() {
184 let desc = ToolDescriptor::new(
185 "search",
186 "Web Search",
187 "Search the web",
188 serde_json::json!({"type": "object"}),
189 );
190
191 assert_eq!(desc.id, "search");
192 assert_eq!(desc.get_name(None), "Web Search");
193 assert_eq!(desc.get_description(None), "Search the web");
194 }
195
196 #[test]
197 fn test_tool_descriptor_with_aliases() {
198 let aliases = ToolAliases::new()
199 .with_name("ko", "검색")
200 .with_description("ko", "웹 검색");
201
202 let desc = ToolDescriptor::new(
203 "search",
204 "Web Search",
205 "Search the web",
206 serde_json::json!({}),
207 )
208 .with_aliases(aliases);
209
210 assert_eq!(desc.get_name(Some("ko")), "검색");
211 assert_eq!(desc.get_name(Some("en")), "Web Search");
212 assert_eq!(desc.get_description(Some("ko")), "웹 검색");
213 }
214
215 #[test]
216 fn test_provider_health() {
217 let healthy = ProviderHealth::Healthy;
218 assert!(healthy.is_healthy());
219 assert!(healthy.is_available());
220
221 let degraded = ProviderHealth::degraded("Some tools failing");
222 assert!(!degraded.is_healthy());
223 assert!(degraded.is_available());
224
225 let unavailable = ProviderHealth::unavailable("Connection lost");
226 assert!(!unavailable.is_healthy());
227 assert!(!unavailable.is_available());
228 }
229}