1use image::DynamicImage;
2use serde::{Deserialize, Serialize};
3use serde_json::{json, Value};
4use std::collections::HashMap;
5use std::fmt;
6use std::path::PathBuf;
7use std::sync::Arc;
8
9#[derive(Clone, Debug, Serialize, Deserialize)]
10#[serde(rename_all = "snake_case")]
11pub enum AgentToolSource {
12 BuiltIn,
13 User,
14 Mcp,
15 External,
16}
17
18#[derive(Clone, Debug, Serialize, Deserialize)]
19#[serde(rename_all = "snake_case")]
20pub enum AgentToolKind {
21 CodeExecution,
22 WebSearch,
23 File,
24 Custom,
25 External,
26}
27
28#[derive(Clone, Debug, Serialize, Deserialize)]
29pub struct AgentToolMetadata {
30 pub source: AgentToolSource,
31 pub kind: AgentToolKind,
32 pub label: String,
33}
34
35#[derive(Clone, Debug)]
36pub struct AgentToolApprovalRequest {
37 pub approval_id: String,
38 pub session_id: String,
39 pub round: usize,
40 pub tool: AgentToolMetadata,
41 pub arguments: Value,
42}
43
44pub type AgentToolApprovalNotifier = dyn Fn(AgentToolApprovalRequest) + Send + Sync + 'static;
45
46#[derive(Clone, Debug)]
47pub struct CodeExecutionApprovalRequest {
48 pub approval_id: String,
49 pub session_id: String,
50 pub round: usize,
51 pub tool_name: String,
52 pub code: String,
53 pub outputs: Vec<String>,
54 pub working_directory: Option<PathBuf>,
55}
56
57pub type CodeExecutionApprovalNotifier =
58 dyn Fn(CodeExecutionApprovalRequest) + Send + Sync + 'static;
59
60#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
61#[serde(rename_all = "kebab-case")]
62pub enum AgentPermission {
63 #[default]
64 Auto,
65 Ask,
66 Deny,
67}
68
69impl AgentPermission {
70 pub fn as_str(self) -> &'static str {
71 match self {
72 Self::Auto => "auto",
73 Self::Ask => "ask",
74 Self::Deny => "deny",
75 }
76 }
77
78 pub fn strictest(self, other: Self) -> Self {
79 match (self, other) {
80 (Self::Deny, _) | (_, Self::Deny) => Self::Deny,
81 (Self::Ask, _) | (_, Self::Ask) => Self::Ask,
82 (Self::Auto, Self::Auto) => Self::Auto,
83 }
84 }
85}
86
87#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
88#[serde(rename_all = "kebab-case")]
89pub enum CodeExecutionPermission {
90 #[default]
91 Auto,
92 Ask,
93 Deny,
94}
95
96impl CodeExecutionPermission {
97 pub fn as_str(self) -> &'static str {
98 match self {
99 Self::Auto => "auto",
100 Self::Ask => "ask",
101 Self::Deny => "deny",
102 }
103 }
104
105 pub fn strictest(self, other: Self) -> Self {
106 match (self, other) {
107 (Self::Deny, _) | (_, Self::Deny) => Self::Deny,
108 (Self::Ask, _) | (_, Self::Ask) => Self::Ask,
109 (Self::Auto, Self::Auto) => Self::Auto,
110 }
111 }
112}
113
114impl From<CodeExecutionPermission> for AgentPermission {
115 fn from(value: CodeExecutionPermission) -> Self {
116 match value {
117 CodeExecutionPermission::Auto => Self::Auto,
118 CodeExecutionPermission::Ask => Self::Ask,
119 CodeExecutionPermission::Deny => Self::Deny,
120 }
121 }
122}
123
124impl From<AgentPermission> for CodeExecutionPermission {
125 fn from(value: AgentPermission) -> Self {
126 match value {
127 AgentPermission::Auto => Self::Auto,
128 AgentPermission::Ask => Self::Ask,
129 AgentPermission::Deny => Self::Deny,
130 }
131 }
132}
133
134#[derive(Clone, Default)]
136pub struct ToolCallContext {
137 pub session_id: Option<String>,
139 pub round: Option<usize>,
140 pub tool_name: Option<String>,
141 pub agent_permission: Option<AgentPermission>,
142 pub agent_approval_notifier: Option<Arc<AgentToolApprovalNotifier>>,
143 pub code_execution_permission: Option<CodeExecutionPermission>,
144 pub code_execution_approval_notifier: Option<Arc<CodeExecutionApprovalNotifier>>,
145}
146
147impl fmt::Debug for ToolCallContext {
148 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149 f.debug_struct("ToolCallContext")
150 .field("session_id", &self.session_id)
151 .field("round", &self.round)
152 .field("tool_name", &self.tool_name)
153 .field("agent_permission", &self.agent_permission)
154 .field(
155 "agent_approval_notifier",
156 &self.agent_approval_notifier.is_some(),
157 )
158 .field("code_execution_permission", &self.code_execution_permission)
159 .field(
160 "code_execution_approval_notifier",
161 &self.code_execution_approval_notifier.is_some(),
162 )
163 .finish()
164 }
165}
166
167pub type ToolCallback =
169 dyn Fn(&CalledFunction, &ToolCallContext) -> anyhow::Result<String> + Send + Sync;
170
171pub type MultimodalToolCallback =
173 dyn Fn(&CalledFunction, &ToolCallContext) -> anyhow::Result<ToolOutput> + Send + Sync;
174
175#[derive(Debug, Clone)]
177pub struct ToolFile {
178 pub name: String,
179 pub format: String,
180 pub mime_type: Option<String>,
181 pub text: Option<String>,
183 pub data_base64: Option<String>,
185 pub size_bytes: u64,
186 pub error: Option<String>,
188}
189
190impl ToolFile {
191 pub fn is_text(&self) -> bool {
192 self.text.is_some()
193 }
194 pub fn is_error(&self) -> bool {
195 self.error.is_some()
196 }
197}
198
199pub enum ToolOutput {
201 Text(String),
202 Multimodal {
203 text: String,
204 images: Vec<DynamicImage>,
205 video_frames: Vec<DynamicImage>,
207 files: Vec<ToolFile>,
209 },
210}
211
212impl From<String> for ToolOutput {
213 fn from(s: String) -> Self {
214 ToolOutput::Text(s)
215 }
216}
217
218impl ToolOutput {
219 pub fn text(&self) -> &str {
220 match self {
221 ToolOutput::Text(s) => s,
222 ToolOutput::Multimodal { text, .. } => text,
223 }
224 }
225
226 pub fn images(&self) -> &[DynamicImage] {
227 match self {
228 ToolOutput::Text(_) => &[],
229 ToolOutput::Multimodal { images, .. } => images,
230 }
231 }
232
233 pub fn video_frames(&self) -> &[DynamicImage] {
234 match self {
235 ToolOutput::Text(_) => &[],
236 ToolOutput::Multimodal { video_frames, .. } => video_frames,
237 }
238 }
239
240 pub fn files(&self) -> &[ToolFile] {
241 match self {
242 ToolOutput::Text(_) => &[],
243 ToolOutput::Multimodal { files, .. } => files,
244 }
245 }
246
247 pub fn has_multimodal(&self) -> bool {
248 match self {
249 ToolOutput::Text(_) => false,
250 ToolOutput::Multimodal {
251 images,
252 video_frames,
253 ..
254 } => !images.is_empty() || !video_frames.is_empty(),
255 }
256 }
257}
258
259pub enum ToolCallbackKind {
261 Text(Arc<ToolCallback>),
263 Multimodal(Arc<MultimodalToolCallback>),
265}
266
267impl Clone for ToolCallbackKind {
268 fn clone(&self) -> Self {
269 match self {
270 ToolCallbackKind::Text(cb) => ToolCallbackKind::Text(Arc::clone(cb)),
271 ToolCallbackKind::Multimodal(cb) => ToolCallbackKind::Multimodal(Arc::clone(cb)),
272 }
273 }
274}
275
276#[derive(Clone)]
278pub struct ToolCallbackWithTool {
279 pub callback: ToolCallbackKind,
280 pub tool: Tool,
281}
282
283pub type ToolCallbacks = HashMap<String, Arc<ToolCallback>>;
285
286pub type ToolCallbacksWithTools = HashMap<String, ToolCallbackWithTool>;
288
289#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
291#[derive(Clone, Debug, Deserialize, Serialize)]
292pub enum ToolType {
293 #[serde(rename = "function")]
294 Function,
295}
296
297#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
299#[derive(Clone, Debug, Deserialize, Serialize)]
300pub struct Function {
301 pub description: Option<String>,
302 pub name: String,
303 #[serde(alias = "arguments")]
304 pub parameters: Option<HashMap<String, Value>>,
305 #[serde(default, skip_serializing_if = "Option::is_none")]
308 pub strict: Option<bool>,
309}
310
311impl Function {
312 pub fn strict_parameters_schema(&self) -> Option<Value> {
315 if self.strict != Some(true) {
316 return None;
317 }
318 match &self.parameters {
319 Some(p) => match serde_json::to_value(p) {
320 Ok(v) => Some(v),
321 Err(e) => {
322 tracing::warn!(
323 "Failed to serialize parameters for strict tool `{}`: {e}. \
324 Falling back to generic object schema.",
325 self.name,
326 );
327 Some(json!({"type": "object"}))
328 }
329 },
330 None => {
331 tracing::warn!(
332 "Tool `{}` has strict: true but no parameters schema defined. \
333 Cannot enforce strict mode; falling back to generic object schema.",
334 self.name,
335 );
336 Some(json!({"type": "object"}))
337 }
338 }
339 }
340}
341
342#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
344#[derive(Clone, Debug, Deserialize, Serialize)]
345pub struct Tool {
346 #[serde(rename = "type")]
347 pub tp: ToolType,
348 pub function: Function,
349}
350
351#[cfg_attr(feature = "pyo3_macros", pyo3::pyclass)]
353#[cfg_attr(feature = "pyo3_macros", pyo3(get_all))]
354#[derive(Clone, Debug, Serialize, Deserialize)]
355pub struct CalledFunction {
356 pub name: String,
357 pub arguments: String,
358}