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