Skip to main content

systemprompt_provider_contracts/
tool.rs

1use async_trait::async_trait;
2use serde::{Deserialize, Serialize};
3use serde_json::Value as JsonValue;
4use std::collections::HashMap;
5
6#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
7pub struct ToolDefinition {
8    pub name: String,
9    pub description: Option<String>,
10    pub input_schema: Option<JsonValue>,
11    pub output_schema: Option<JsonValue>,
12    pub service_id: String,
13    #[serde(default)]
14    pub terminal_on_success: bool,
15    #[serde(skip_serializing_if = "Option::is_none")]
16    pub model_config: Option<JsonValue>,
17}
18
19impl ToolDefinition {
20    pub fn new(name: impl Into<String>, service_id: impl Into<String>) -> Self {
21        Self {
22            name: name.into(),
23            description: None,
24            input_schema: None,
25            output_schema: None,
26            service_id: service_id.into(),
27            terminal_on_success: false,
28            model_config: None,
29        }
30    }
31
32    pub fn with_description(mut self, description: impl Into<String>) -> Self {
33        self.description = Some(description.into());
34        self
35    }
36
37    pub fn with_input_schema(mut self, schema: JsonValue) -> Self {
38        self.input_schema = Some(schema);
39        self
40    }
41
42    pub fn with_output_schema(mut self, schema: JsonValue) -> Self {
43        self.output_schema = Some(schema);
44        self
45    }
46
47    pub const fn with_terminal_on_success(mut self, terminal: bool) -> Self {
48        self.terminal_on_success = terminal;
49        self
50    }
51
52    pub fn with_model_config(mut self, config: JsonValue) -> Self {
53        self.model_config = Some(config);
54        self
55    }
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct ToolCallRequest {
60    pub tool_call_id: String,
61    pub name: String,
62    pub arguments: JsonValue,
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct ToolCallResult {
67    pub content: Vec<ToolContent>,
68    pub structured_content: Option<JsonValue>,
69    pub is_error: Option<bool>,
70    pub meta: Option<JsonValue>,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
74#[serde(tag = "type", rename_all = "lowercase")]
75pub enum ToolContent {
76    Text {
77        text: String,
78    },
79    Image {
80        data: String,
81        mime_type: String,
82    },
83    Resource {
84        uri: String,
85        mime_type: Option<String>,
86    },
87}
88
89impl ToolContent {
90    pub fn text(text: impl Into<String>) -> Self {
91        Self::Text { text: text.into() }
92    }
93}
94
95impl ToolCallResult {
96    pub fn success(text: impl Into<String>) -> Self {
97        Self {
98            content: vec![ToolContent::text(text)],
99            structured_content: None,
100            is_error: Some(false),
101            meta: None,
102        }
103    }
104
105    pub fn error(message: impl Into<String>) -> Self {
106        Self {
107            content: vec![ToolContent::text(message)],
108            structured_content: None,
109            is_error: Some(true),
110            meta: None,
111        }
112    }
113
114    pub fn with_structured_content(mut self, content: JsonValue) -> Self {
115        self.structured_content = Some(content);
116        self
117    }
118}
119
120#[derive(Debug, thiserror::Error)]
121pub enum ToolProviderError {
122    #[error("Tool '{0}' not found")]
123    ToolNotFound(String),
124
125    #[error("Service '{0}' not found")]
126    ServiceNotFound(String),
127
128    #[error("Failed to connect to service '{service}': {message}")]
129    ConnectionFailed { service: String, message: String },
130
131    #[error("Tool execution failed: {0}")]
132    ExecutionFailed(String),
133
134    #[error("Authorization failed: {0}")]
135    AuthorizationFailed(String),
136
137    #[error("Configuration error: {0}")]
138    ConfigurationError(String),
139
140    #[error("Internal error: {0}")]
141    Internal(String),
142}
143
144impl From<anyhow::Error> for ToolProviderError {
145    fn from(err: anyhow::Error) -> Self {
146        Self::Internal(err.to_string())
147    }
148}
149
150#[derive(Debug, Clone)]
151pub struct ToolContext {
152    pub auth_token: String,
153    pub session_id: Option<String>,
154    pub trace_id: Option<String>,
155    pub ai_tool_call_id: Option<String>,
156    pub headers: HashMap<String, String>,
157}
158
159impl ToolContext {
160    pub fn new(auth_token: impl Into<String>) -> Self {
161        Self {
162            auth_token: auth_token.into(),
163            session_id: None,
164            trace_id: None,
165            ai_tool_call_id: None,
166            headers: HashMap::new(),
167        }
168    }
169
170    pub fn with_session_id(mut self, session_id: impl Into<String>) -> Self {
171        self.session_id = Some(session_id.into());
172        self
173    }
174
175    pub fn with_trace_id(mut self, trace_id: impl Into<String>) -> Self {
176        self.trace_id = Some(trace_id.into());
177        self
178    }
179
180    pub fn with_ai_tool_call_id(mut self, id: impl Into<String>) -> Self {
181        self.ai_tool_call_id = Some(id.into());
182        self
183    }
184
185    pub fn with_header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
186        self.headers.insert(key.into(), value.into());
187        self
188    }
189}
190
191pub type ToolProviderResult<T> = Result<T, ToolProviderError>;
192
193#[async_trait]
194pub trait ToolProvider: Send + Sync {
195    async fn list_tools(
196        &self,
197        agent_name: &str,
198        context: &ToolContext,
199    ) -> ToolProviderResult<Vec<ToolDefinition>>;
200
201    async fn call_tool(
202        &self,
203        request: &ToolCallRequest,
204        service_id: &str,
205        context: &ToolContext,
206    ) -> ToolProviderResult<ToolCallResult>;
207
208    async fn refresh_connections(&self, agent_name: &str) -> ToolProviderResult<()>;
209
210    async fn health_check(&self) -> ToolProviderResult<HashMap<String, bool>>;
211
212    async fn find_tool(
213        &self,
214        agent_name: &str,
215        tool_name: &str,
216        context: &ToolContext,
217    ) -> ToolProviderResult<Option<ToolDefinition>> {
218        let tools = self.list_tools(agent_name, context).await?;
219        Ok(tools.into_iter().find(|t| t.name == tool_name))
220    }
221}