Skip to main content

ai_agents_tools/
provider.rs

1use 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    /// Policy bindings supplied by provider descriptors when available.
20    #[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    /// Attach policy bindings advertised by the provider.
53    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            && let Some(ref aliases) = self.aliases
61            && let Some(name) = aliases.get_name(lang)
62        {
63            return name;
64        }
65        &self.name
66    }
67
68    pub fn get_description(&self, lang: Option<&str>) -> &str {
69        if let Some(lang) = lang
70            && let Some(ref aliases) = self.aliases
71            && let Some(desc) = aliases.get_description(lang)
72        {
73            return desc;
74        }
75        &self.description
76    }
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize, Default)]
80#[serde(tag = "status", rename_all = "snake_case")]
81pub enum ProviderHealth {
82    #[default]
83    Healthy,
84    Degraded {
85        message: String,
86    },
87    Unavailable {
88        message: String,
89    },
90}
91
92impl ProviderHealth {
93    pub fn is_healthy(&self) -> bool {
94        matches!(self, ProviderHealth::Healthy)
95    }
96
97    pub fn is_available(&self) -> bool {
98        !matches!(self, ProviderHealth::Unavailable { .. })
99    }
100
101    pub fn degraded(message: impl Into<String>) -> Self {
102        ProviderHealth::Degraded {
103            message: message.into(),
104        }
105    }
106
107    pub fn unavailable(message: impl Into<String>) -> Self {
108        ProviderHealth::Unavailable {
109            message: message.into(),
110        }
111    }
112}
113
114#[derive(Debug, thiserror::Error)]
115pub enum ToolProviderError {
116    #[error("Tool not found: {0}")]
117    ToolNotFound(String),
118
119    #[error("Execution failed: {0}")]
120    ExecutionFailed(String),
121
122    #[error("Provider unavailable: {0}")]
123    Unavailable(String),
124
125    #[error("Connection error: {0}")]
126    ConnectionError(String),
127
128    #[error("Configuration error: {0}")]
129    ConfigError(String),
130
131    #[error("Timeout after {0}ms")]
132    Timeout(u64),
133
134    #[error("{0}")]
135    Other(String),
136}
137
138#[async_trait]
139pub trait ToolProvider: Send + Sync {
140    fn id(&self) -> &str;
141
142    fn name(&self) -> &str;
143
144    fn provider_type(&self) -> ToolProviderType;
145
146    async fn list_tools(&self) -> Vec<ToolDescriptor>;
147
148    async fn get_tool(&self, tool_id: &str) -> Option<Arc<dyn Tool>>;
149
150    async fn execute(
151        &self,
152        tool_id: &str,
153        args: Value,
154        ctx: ToolExecutionContext,
155    ) -> Result<ToolResult, ToolProviderError> {
156        if let Some(tool) = self.get_tool(tool_id).await {
157            Ok(tool.execute(args, ctx).await)
158        } else {
159            Err(ToolProviderError::ToolNotFound(tool_id.to_string()))
160        }
161    }
162
163    fn supports_refresh(&self) -> bool {
164        false
165    }
166
167    async fn refresh(&self) -> Result<(), ToolProviderError> {
168        Ok(())
169    }
170
171    async fn health_check(&self) -> ProviderHealth {
172        ProviderHealth::Healthy
173    }
174}
175
176#[cfg(test)]
177mod tests {
178    use super::*;
179
180    #[test]
181    fn test_tool_descriptor() {
182        let desc = ToolDescriptor::new(
183            "search",
184            "Web Search",
185            "Search the web",
186            serde_json::json!({"type": "object"}),
187        );
188
189        assert_eq!(desc.id, "search");
190        assert_eq!(desc.get_name(None), "Web Search");
191        assert_eq!(desc.get_description(None), "Search the web");
192    }
193
194    #[test]
195    fn test_tool_descriptor_with_aliases() {
196        let aliases = ToolAliases::new()
197            .with_name("ko", "검색")
198            .with_description("ko", "웹 검색");
199
200        let desc = ToolDescriptor::new(
201            "search",
202            "Web Search",
203            "Search the web",
204            serde_json::json!({}),
205        )
206        .with_aliases(aliases);
207
208        assert_eq!(desc.get_name(Some("ko")), "검색");
209        assert_eq!(desc.get_name(Some("en")), "Web Search");
210        assert_eq!(desc.get_description(Some("ko")), "웹 검색");
211    }
212
213    #[test]
214    fn test_provider_health() {
215        let healthy = ProviderHealth::Healthy;
216        assert!(healthy.is_healthy());
217        assert!(healthy.is_available());
218
219        let degraded = ProviderHealth::degraded("Some tools failing");
220        assert!(!degraded.is_healthy());
221        assert!(degraded.is_available());
222
223        let unavailable = ProviderHealth::unavailable("Connection lost");
224        assert!(!unavailable.is_healthy());
225        assert!(!unavailable.is_available());
226    }
227}