Skip to main content

adk_tool/builtin/
openai.rs

1use adk_core::{Result, Tool, ToolContext};
2use async_trait::async_trait;
3use serde_json::{Map, Value, json};
4use std::sync::Arc;
5
6/// Approximate user location for OpenAI web search.
7#[derive(Debug, Clone, Default)]
8pub struct OpenAIApproximateLocation {
9    city: Option<String>,
10    country: Option<String>,
11    region: Option<String>,
12    timezone: Option<String>,
13}
14
15impl OpenAIApproximateLocation {
16    /// Create a new empty approximate location.
17    pub fn new() -> Self {
18        Self::default()
19    }
20
21    /// Set the city name.
22    pub fn with_city(mut self, city: impl Into<String>) -> Self {
23        self.city = Some(city.into());
24        self
25    }
26
27    /// Set the country name.
28    pub fn with_country(mut self, country: impl Into<String>) -> Self {
29        self.country = Some(country.into());
30        self
31    }
32
33    /// Set the region name.
34    pub fn with_region(mut self, region: impl Into<String>) -> Self {
35        self.region = Some(region.into());
36        self
37    }
38
39    /// Set the timezone identifier.
40    pub fn with_timezone(mut self, timezone: impl Into<String>) -> Self {
41        self.timezone = Some(timezone.into());
42        self
43    }
44
45    fn to_json(&self) -> Value {
46        json!({
47            "type": "approximate",
48            "city": self.city,
49            "country": self.country,
50            "region": self.region,
51            "timezone": self.timezone,
52        })
53    }
54}
55
56/// OpenAI web search tool flavor.
57#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
58pub enum OpenAIWebSearchVariant {
59    #[default]
60    Stable20250826,
61    Preview20250311,
62}
63
64impl OpenAIWebSearchVariant {
65    fn as_wire(self) -> &'static str {
66        match self {
67            Self::Stable20250826 => "web_search_2025_08_26",
68            Self::Preview20250311 => "web_search_preview_2025_03_11",
69        }
70    }
71}
72
73/// OpenAI hosted web search tool.
74#[derive(Debug, Clone, Default)]
75pub struct OpenAIWebSearchTool {
76    variant: OpenAIWebSearchVariant,
77    allowed_domains: Option<Vec<String>>,
78    user_location: Option<OpenAIApproximateLocation>,
79    search_context_size: Option<String>,
80}
81
82impl OpenAIWebSearchTool {
83    /// Create a new `OpenAIWebSearchTool` with default (stable) variant.
84    pub fn new() -> Self {
85        Self::default()
86    }
87
88    /// Use the preview variant of the web search tool.
89    pub fn preview(mut self) -> Self {
90        self.variant = OpenAIWebSearchVariant::Preview20250311;
91        self
92    }
93
94    /// Restrict search results to the specified domains.
95    pub fn with_allowed_domains(
96        mut self,
97        domains: impl IntoIterator<Item = impl Into<String>>,
98    ) -> Self {
99        self.allowed_domains = Some(domains.into_iter().map(Into::into).collect());
100        self
101    }
102
103    /// Set the approximate user location for localized results.
104    pub fn with_user_location(mut self, user_location: OpenAIApproximateLocation) -> Self {
105        self.user_location = Some(user_location);
106        self
107    }
108
109    /// Set the search context size (e.g., "low", "medium", "high").
110    pub fn with_search_context_size(mut self, size: impl Into<String>) -> Self {
111        self.search_context_size = Some(size.into());
112        self
113    }
114
115    fn tool_json(&self) -> Value {
116        json!({
117            "type": self.variant.as_wire(),
118            "filters": self.allowed_domains.as_ref().map(|domains| json!({ "allowed_domains": domains })),
119            "user_location": self.user_location.as_ref().map(OpenAIApproximateLocation::to_json),
120            "search_context_size": self.search_context_size,
121        })
122    }
123}
124
125#[async_trait]
126impl Tool for OpenAIWebSearchTool {
127    fn name(&self) -> &str {
128        "openai_web_search"
129    }
130
131    fn description(&self) -> &str {
132        "Uses OpenAI hosted web search to retrieve current web information."
133    }
134
135    fn is_builtin(&self) -> bool {
136        true
137    }
138
139    fn declaration(&self) -> Value {
140        json!({
141            "name": self.name(),
142            "description": self.description(),
143            "x-adk-openai-tool": self.tool_json(),
144        })
145    }
146
147    async fn execute(&self, _ctx: Arc<dyn ToolContext>, _args: Value) -> Result<Value> {
148        Err(adk_core::AdkError::tool("OpenAI web search is handled by the Responses API"))
149    }
150}
151
152/// OpenAI hosted file search tool.
153#[derive(Debug, Clone)]
154pub struct OpenAIFileSearchTool {
155    vector_store_ids: Vec<String>,
156    max_num_results: Option<u32>,
157    filters: Option<Value>,
158    ranking_options: Option<Value>,
159}
160
161impl OpenAIFileSearchTool {
162    /// Create a new `OpenAIFileSearchTool` with the given vector store IDs.
163    pub fn new(vector_store_ids: impl IntoIterator<Item = impl Into<String>>) -> Self {
164        Self {
165            vector_store_ids: vector_store_ids.into_iter().map(Into::into).collect(),
166            max_num_results: None,
167            filters: None,
168            ranking_options: None,
169        }
170    }
171
172    /// Set the maximum number of results to return.
173    pub fn with_max_num_results(mut self, max_num_results: u32) -> Self {
174        self.max_num_results = Some(max_num_results);
175        self
176    }
177
178    /// Set metadata filters for the search.
179    pub fn with_filters(mut self, filters: Value) -> Self {
180        self.filters = Some(filters);
181        self
182    }
183
184    /// Set ranking options for result ordering.
185    pub fn with_ranking_options(mut self, ranking_options: Value) -> Self {
186        self.ranking_options = Some(ranking_options);
187        self
188    }
189
190    fn tool_json(&self) -> Value {
191        json!({
192            "type": "file_search",
193            "vector_store_ids": self.vector_store_ids,
194            "max_num_results": self.max_num_results,
195            "filters": self.filters,
196            "ranking_options": self.ranking_options,
197        })
198    }
199}
200
201#[async_trait]
202impl Tool for OpenAIFileSearchTool {
203    fn name(&self) -> &str {
204        "openai_file_search"
205    }
206
207    fn description(&self) -> &str {
208        "Uses OpenAI hosted file search against one or more vector stores."
209    }
210
211    fn is_builtin(&self) -> bool {
212        true
213    }
214
215    fn declaration(&self) -> Value {
216        json!({
217            "name": self.name(),
218            "description": self.description(),
219            "x-adk-openai-tool": self.tool_json(),
220        })
221    }
222
223    async fn execute(&self, _ctx: Arc<dyn ToolContext>, _args: Value) -> Result<Value> {
224        Err(adk_core::AdkError::tool("OpenAI file search is handled by the Responses API"))
225    }
226}
227
228/// OpenAI hosted code interpreter tool.
229#[derive(Debug, Clone, Default)]
230pub struct OpenAICodeInterpreterTool {
231    file_ids: Vec<String>,
232    memory_limit: Option<u64>,
233    container_id: Option<String>,
234}
235
236impl OpenAICodeInterpreterTool {
237    /// Create a new `OpenAICodeInterpreterTool` with default settings.
238    pub fn new() -> Self {
239        Self::default()
240    }
241
242    /// Attach file IDs to the code interpreter container.
243    pub fn with_file_ids(mut self, file_ids: impl IntoIterator<Item = impl Into<String>>) -> Self {
244        self.file_ids = file_ids.into_iter().map(Into::into).collect();
245        self
246    }
247
248    /// Set the memory limit in bytes for the container.
249    pub fn with_memory_limit(mut self, memory_limit: u64) -> Self {
250        self.memory_limit = Some(memory_limit);
251        self
252    }
253
254    /// Use a specific container ID instead of auto-provisioning.
255    pub fn with_container_id(mut self, container_id: impl Into<String>) -> Self {
256        self.container_id = Some(container_id.into());
257        self
258    }
259
260    fn tool_json(&self) -> Value {
261        let container = if let Some(container_id) = &self.container_id {
262            Value::String(container_id.clone())
263        } else {
264            json!({
265                "type": "auto",
266                "file_ids": (!self.file_ids.is_empty()).then_some(self.file_ids.clone()),
267                "memory_limit": self.memory_limit,
268            })
269        };
270
271        json!({
272            "type": "code_interpreter",
273            "container": container,
274        })
275    }
276}
277
278#[async_trait]
279impl Tool for OpenAICodeInterpreterTool {
280    fn name(&self) -> &str {
281        "openai_code_interpreter"
282    }
283
284    fn description(&self) -> &str {
285        "Uses OpenAI hosted code interpreter to execute Python and return outputs."
286    }
287
288    fn is_builtin(&self) -> bool {
289        true
290    }
291
292    fn declaration(&self) -> Value {
293        json!({
294            "name": self.name(),
295            "description": self.description(),
296            "x-adk-openai-tool": self.tool_json(),
297        })
298    }
299
300    async fn execute(&self, _ctx: Arc<dyn ToolContext>, _args: Value) -> Result<Value> {
301        Err(adk_core::AdkError::tool("OpenAI code interpreter is handled by the Responses API"))
302    }
303}
304
305/// OpenAI hosted image generation tool.
306#[derive(Debug, Clone, Default)]
307pub struct OpenAIImageGenerationTool {
308    options: Map<String, Value>,
309}
310
311impl OpenAIImageGenerationTool {
312    /// Create a new `OpenAIImageGenerationTool` with default settings.
313    pub fn new() -> Self {
314        Self::default()
315    }
316
317    /// Set a custom option key-value pair for image generation.
318    pub fn with_option(mut self, key: impl Into<String>, value: Value) -> Self {
319        self.options.insert(key.into(), value);
320        self
321    }
322
323    fn tool_json(&self) -> Value {
324        let mut tool = self.options.clone();
325        tool.insert("type".to_string(), Value::String("image_generation".to_string()));
326        Value::Object(tool)
327    }
328}
329
330#[async_trait]
331impl Tool for OpenAIImageGenerationTool {
332    fn name(&self) -> &str {
333        "openai_image_generation"
334    }
335
336    fn description(&self) -> &str {
337        "Uses OpenAI hosted image generation."
338    }
339
340    fn is_builtin(&self) -> bool {
341        true
342    }
343
344    fn declaration(&self) -> Value {
345        json!({
346            "name": self.name(),
347            "description": self.description(),
348            "x-adk-openai-tool": self.tool_json(),
349        })
350    }
351
352    async fn execute(&self, _ctx: Arc<dyn ToolContext>, _args: Value) -> Result<Value> {
353        Err(adk_core::AdkError::tool("OpenAI image generation is handled by the Responses API"))
354    }
355}
356
357/// OpenAI computer use environment.
358#[derive(Debug, Clone, Copy, PartialEq, Eq)]
359pub enum OpenAIComputerEnvironment {
360    /// Browser-based environment.
361    Browser,
362    /// macOS environment.
363    Mac,
364    /// Windows environment.
365    Windows,
366    /// Generic Linux environment.
367    Linux,
368    /// Ubuntu environment.
369    Ubuntu,
370}
371
372impl OpenAIComputerEnvironment {
373    fn as_wire(self) -> &'static str {
374        match self {
375            Self::Browser => "browser",
376            Self::Mac => "mac",
377            Self::Windows => "windows",
378            Self::Linux => "linux",
379            Self::Ubuntu => "ubuntu",
380        }
381    }
382}
383
384/// OpenAI computer use tool declaration.
385#[derive(Debug, Clone)]
386pub struct OpenAIComputerUseTool {
387    environment: OpenAIComputerEnvironment,
388    display_width: u32,
389    display_height: u32,
390}
391
392impl OpenAIComputerUseTool {
393    /// Create a new `OpenAIComputerUseTool` with the given environment and display dimensions.
394    pub fn new(
395        environment: OpenAIComputerEnvironment,
396        display_width: u32,
397        display_height: u32,
398    ) -> Self {
399        Self { environment, display_width, display_height }
400    }
401
402    fn tool_json(&self) -> Value {
403        json!({
404            "type": "computer_use_preview",
405            "environment": self.environment.as_wire(),
406            "display_width": self.display_width,
407            "display_height": self.display_height,
408        })
409    }
410}
411
412#[async_trait]
413impl Tool for OpenAIComputerUseTool {
414    fn name(&self) -> &str {
415        "openai_computer_use"
416    }
417
418    fn description(&self) -> &str {
419        "Enables OpenAI computer use tool calls."
420    }
421
422    fn is_builtin(&self) -> bool {
423        true
424    }
425
426    fn declaration(&self) -> Value {
427        json!({
428            "name": self.name(),
429            "description": self.description(),
430            "x-adk-openai-tool": self.tool_json(),
431        })
432    }
433
434    async fn execute(&self, _ctx: Arc<dyn ToolContext>, _args: Value) -> Result<Value> {
435        Err(adk_core::AdkError::tool("OpenAI computer use requires client-side action handling"))
436    }
437}
438
439/// OpenAI remote MCP tool declaration.
440#[derive(Debug, Clone)]
441pub struct OpenAIMcpTool {
442    server_label: String,
443    definition: Map<String, Value>,
444}
445
446impl OpenAIMcpTool {
447    /// Create a new `OpenAIMcpTool` connecting to a server by URL.
448    pub fn new_with_url(server_label: impl Into<String>, server_url: impl Into<String>) -> Self {
449        let server_label = server_label.into();
450        let mut definition = Map::new();
451        definition.insert("type".to_string(), Value::String("mcp".to_string()));
452        definition.insert("server_label".to_string(), Value::String(server_label.clone()));
453        definition.insert("server_url".to_string(), Value::String(server_url.into()));
454        Self { server_label, definition }
455    }
456
457    /// Create a new `OpenAIMcpTool` connecting to a server by connector ID.
458    pub fn new_with_connector(
459        server_label: impl Into<String>,
460        connector_id: impl Into<String>,
461    ) -> Self {
462        let server_label = server_label.into();
463        let mut definition = Map::new();
464        definition.insert("type".to_string(), Value::String("mcp".to_string()));
465        definition.insert("server_label".to_string(), Value::String(server_label.clone()));
466        definition.insert("connector_id".to_string(), Value::String(connector_id.into()));
467        Self { server_label, definition }
468    }
469
470    /// Restrict which tools the model may invoke on this MCP server.
471    pub fn with_allowed_tools(mut self, allowed_tools: Value) -> Self {
472        self.definition.insert("allowed_tools".to_string(), allowed_tools);
473        self
474    }
475
476    /// Set the authorization header value for the MCP server.
477    pub fn with_authorization(mut self, authorization: impl Into<String>) -> Self {
478        self.definition.insert("authorization".to_string(), Value::String(authorization.into()));
479        self
480    }
481
482    /// Set custom headers for the MCP server connection.
483    pub fn with_headers(mut self, headers: Map<String, Value>) -> Self {
484        self.definition.insert("headers".to_string(), Value::Object(headers));
485        self
486    }
487
488    /// Configure tool approval requirements for this MCP server.
489    pub fn with_require_approval(mut self, require_approval: Value) -> Self {
490        self.definition.insert("require_approval".to_string(), require_approval);
491        self
492    }
493
494    fn tool_json(&self) -> Value {
495        Value::Object(self.definition.clone())
496    }
497}
498
499#[async_trait]
500impl Tool for OpenAIMcpTool {
501    fn name(&self) -> &str {
502        &self.server_label
503    }
504
505    fn description(&self) -> &str {
506        "Grants the model access to a remote MCP server."
507    }
508
509    fn is_builtin(&self) -> bool {
510        true
511    }
512
513    fn declaration(&self) -> Value {
514        json!({
515            "name": self.name(),
516            "description": self.description(),
517            "x-adk-openai-tool": self.tool_json(),
518        })
519    }
520
521    async fn execute(&self, _ctx: Arc<dyn ToolContext>, _args: Value) -> Result<Value> {
522        Err(adk_core::AdkError::tool("OpenAI MCP tool calls are handled by the Responses API"))
523    }
524}
525
526/// OpenAI local shell tool declaration.
527#[derive(Debug, Clone, Default)]
528pub struct OpenAILocalShellTool;
529
530impl OpenAILocalShellTool {
531    /// Create a new `OpenAILocalShellTool`.
532    pub fn new() -> Self {
533        Self
534    }
535}
536
537#[async_trait]
538impl Tool for OpenAILocalShellTool {
539    fn name(&self) -> &str {
540        "openai_local_shell"
541    }
542
543    fn description(&self) -> &str {
544        "Allows OpenAI to execute commands in the local shell tool protocol."
545    }
546
547    fn is_builtin(&self) -> bool {
548        true
549    }
550
551    fn declaration(&self) -> Value {
552        json!({
553            "name": self.name(),
554            "description": self.description(),
555            "x-adk-openai-tool": {
556                "type": "local_shell"
557            },
558        })
559    }
560
561    async fn execute(&self, _ctx: Arc<dyn ToolContext>, _args: Value) -> Result<Value> {
562        Err(adk_core::AdkError::tool(
563            "OpenAI local shell outputs must be handled through the Responses API item protocol",
564        ))
565    }
566}
567
568/// OpenAI managed shell tool declaration.
569#[derive(Debug, Clone, Default)]
570pub struct OpenAIShellTool {
571    environment: Option<Value>,
572}
573
574impl OpenAIShellTool {
575    /// Create a new `OpenAIShellTool` with default settings.
576    pub fn new() -> Self {
577        Self::default()
578    }
579
580    /// Set the shell environment configuration.
581    pub fn with_environment(mut self, environment: Value) -> Self {
582        self.environment = Some(environment);
583        self
584    }
585
586    fn tool_json(&self) -> Value {
587        json!({
588            "type": "shell",
589            "environment": self.environment,
590        })
591    }
592}
593
594#[async_trait]
595impl Tool for OpenAIShellTool {
596    fn name(&self) -> &str {
597        "openai_shell"
598    }
599
600    fn description(&self) -> &str {
601        "Allows OpenAI to execute commands in the managed shell tool protocol."
602    }
603
604    fn is_builtin(&self) -> bool {
605        true
606    }
607
608    fn declaration(&self) -> Value {
609        json!({
610            "name": self.name(),
611            "description": self.description(),
612            "x-adk-openai-tool": self.tool_json(),
613        })
614    }
615
616    async fn execute(&self, _ctx: Arc<dyn ToolContext>, _args: Value) -> Result<Value> {
617        Err(adk_core::AdkError::tool(
618            "OpenAI shell outputs must be handled through the Responses API item protocol",
619        ))
620    }
621}
622
623/// OpenAI apply_patch tool declaration.
624#[derive(Debug, Clone, Default)]
625pub struct OpenAIApplyPatchTool;
626
627impl OpenAIApplyPatchTool {
628    /// Create a new `OpenAIApplyPatchTool`.
629    pub fn new() -> Self {
630        Self
631    }
632}
633
634#[async_trait]
635impl Tool for OpenAIApplyPatchTool {
636    fn name(&self) -> &str {
637        "openai_apply_patch"
638    }
639
640    fn description(&self) -> &str {
641        "Allows OpenAI to propose file patches through the native apply_patch tool."
642    }
643
644    fn is_builtin(&self) -> bool {
645        true
646    }
647
648    fn declaration(&self) -> Value {
649        json!({
650            "name": self.name(),
651            "description": self.description(),
652            "x-adk-openai-tool": {
653                "type": "apply_patch"
654            },
655        })
656    }
657
658    async fn execute(&self, _ctx: Arc<dyn ToolContext>, _args: Value) -> Result<Value> {
659        Err(adk_core::AdkError::tool(
660            "OpenAI apply_patch outputs must be handled through the Responses API item protocol",
661        ))
662    }
663}