1use std::collections::HashMap;
50
51use derive_builder::Builder;
52use serde::{Deserialize, Serialize};
53use serde_json::Value;
54
55use crate::error::OpenRouterError;
56
57#[derive(Serialize, Deserialize, Debug, Clone)]
83#[non_exhaustive]
84pub struct Tool {
85 #[serde(rename = "type")]
87 pub tool_type: String,
88
89 pub function: FunctionDefinition,
91
92 #[serde(skip_serializing_if = "Option::is_none")]
94 pub cache_control: Option<Value>,
95}
96
97impl Tool {
98 pub fn builder() -> ToolBuilder {
100 ToolBuilder::default()
101 }
102
103 pub fn new(name: &str, description: &str, parameters: Value) -> Self {
105 Self {
106 tool_type: "function".to_string(),
107 function: FunctionDefinition {
108 name: name.to_string(),
109 description: description.to_string(),
110 parameters,
111 strict: None,
112 },
113 cache_control: None,
114 }
115 }
116}
117
118#[derive(Debug, Default, Clone)]
119pub struct ToolBuilder {
120 tool_type: Option<String>,
121 name: Option<String>,
122 description: Option<String>,
123 parameters: Option<Value>,
124 strict: Option<bool>,
125 cache_control: Option<Value>,
126}
127
128impl ToolBuilder {
129 pub fn tool_type(&mut self, tool_type: impl Into<String>) -> &mut Self {
131 self.tool_type = Some(tool_type.into());
132 self
133 }
134
135 pub fn function(&mut self, function: FunctionDefinition) -> &mut Self {
137 self.name = Some(function.name);
138 self.description = Some(function.description);
139 self.parameters = Some(function.parameters);
140 self.strict = function.strict;
141 self
142 }
143
144 pub fn build(&self) -> Result<Tool, OpenRouterError> {
146 let name = self
147 .name
148 .clone()
149 .ok_or_else(|| OpenRouterError::ConfigError("Tool name is required".to_string()))?;
150
151 Ok(Tool {
152 tool_type: self
153 .tool_type
154 .clone()
155 .unwrap_or_else(|| "function".to_string()),
156 function: FunctionDefinition {
157 name,
158 description: self.description.clone().unwrap_or_default(),
159 parameters: self.parameters.clone().unwrap_or(Value::Null),
160 strict: self.strict,
161 },
162 cache_control: self.cache_control.clone(),
163 })
164 }
165}
166
167#[derive(Serialize, Deserialize, Debug, Clone, Builder)]
172#[builder(build_fn(error = "OpenRouterError"))]
173#[non_exhaustive]
174pub struct FunctionDefinition {
175 #[builder(setter(into))]
177 pub name: String,
178
179 #[builder(setter(into))]
181 pub description: String,
182
183 #[builder(setter(custom))]
185 pub parameters: Value,
186
187 #[builder(setter(strip_option), default)]
189 #[serde(skip_serializing_if = "Option::is_none")]
190 pub strict: Option<bool>,
191}
192
193impl FunctionDefinition {
194 pub fn builder() -> FunctionDefinitionBuilder {
196 FunctionDefinitionBuilder::default()
197 }
198}
199
200impl ToolBuilder {
201 pub fn name(&mut self, name: &str) -> &mut Self {
203 self.name = Some(name.to_string());
204 self
205 }
206
207 pub fn description(&mut self, description: &str) -> &mut Self {
209 self.description = Some(description.to_string());
210 self
211 }
212
213 pub fn parameters(&mut self, parameters: Value) -> &mut Self {
215 self.parameters = Some(parameters);
216 self
217 }
218
219 pub fn parameters_from<T: Serialize>(
221 &mut self,
222 params: &T,
223 ) -> Result<&mut Self, OpenRouterError> {
224 let value = serde_json::to_value(params).map_err(OpenRouterError::Serialization)?;
225 Ok(self.parameters(value))
226 }
227
228 pub fn parameters_json(&mut self, json: &str) -> Result<&mut Self, OpenRouterError> {
230 let value: Value = serde_json::from_str(json).map_err(OpenRouterError::Serialization)?;
231 Ok(self.parameters(value))
232 }
233
234 pub fn strict(&mut self, strict: bool) -> &mut Self {
236 self.strict = Some(strict);
237 self
238 }
239
240 pub fn cache_control(&mut self, cache_control: impl Into<Value>) -> &mut Self {
242 self.cache_control = Some(cache_control.into());
243 self
244 }
245}
246
247impl FunctionDefinitionBuilder {
248 pub fn parameters(&mut self, parameters: Value) -> &mut Self {
250 self.parameters = Some(parameters);
251 self
252 }
253
254 pub fn parameters_from<T: Serialize>(
256 &mut self,
257 params: &T,
258 ) -> Result<&mut Self, OpenRouterError> {
259 let value = serde_json::to_value(params).map_err(OpenRouterError::Serialization)?;
260 self.parameters = Some(value);
261 Ok(self)
262 }
263
264 pub fn parameters_json(&mut self, json: &str) -> Result<&mut Self, OpenRouterError> {
266 let value: Value = serde_json::from_str(json).map_err(OpenRouterError::Serialization)?;
267 self.parameters = Some(value);
268 Ok(self)
269 }
270}
271
272#[derive(Serialize, Deserialize, Debug, Clone)]
279#[non_exhaustive]
280pub struct ServerTool {
281 #[serde(rename = "type")]
282 pub tool_type: String,
283 #[serde(skip_serializing_if = "Option::is_none")]
284 pub parameters: Option<Value>,
285 #[serde(flatten)]
286 pub extra: HashMap<String, Value>,
287}
288
289impl ServerTool {
290 pub fn new(tool_type: impl Into<String>) -> Self {
291 Self {
292 tool_type: tool_type.into(),
293 parameters: None,
294 extra: HashMap::new(),
295 }
296 }
297
298 pub fn with_parameters(tool_type: impl Into<String>, parameters: impl Into<Value>) -> Self {
299 Self::new(tool_type).parameters(parameters)
300 }
301
302 pub fn parameters(mut self, parameters: impl Into<Value>) -> Self {
303 self.parameters = Some(parameters.into());
304 self
305 }
306
307 pub fn parameters_from<T: Serialize>(mut self, params: &T) -> Result<Self, OpenRouterError> {
308 self.parameters =
309 Some(serde_json::to_value(params).map_err(OpenRouterError::Serialization)?);
310 Ok(self)
311 }
312
313 pub fn option(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
314 self.extra.insert(key.into(), value.into());
315 self
316 }
317
318 pub fn web_search() -> Self {
319 Self::new("openrouter:web_search")
320 }
321
322 pub fn web_search_with_parameters(parameters: impl Into<Value>) -> Self {
323 Self::with_parameters("openrouter:web_search", parameters)
324 }
325
326 pub fn web_search_preview() -> Self {
327 Self::new("web_search_preview")
328 }
329
330 pub fn datetime() -> Self {
331 Self::new("openrouter:datetime")
332 }
333
334 pub fn datetime_with_timezone(timezone: impl Into<String>) -> Self {
335 Self::with_parameters(
336 "openrouter:datetime",
337 serde_json::json!({ "timezone": timezone.into() }),
338 )
339 }
340
341 pub fn files() -> Self {
342 Self::new("openrouter:files")
343 }
344
345 pub fn bash() -> Self {
346 Self::new("openrouter:bash")
347 }
348
349 pub fn web_fetch() -> Self {
350 Self::new("openrouter:web_fetch")
351 }
352
353 pub fn advisor() -> Self {
354 Self::new("openrouter:advisor")
355 }
356
357 pub fn subagent() -> Self {
358 Self::new("openrouter:subagent")
359 }
360
361 pub fn image_generation() -> Self {
362 Self::new("openrouter:image_generation")
363 }
364
365 pub fn search_models() -> Self {
366 Self::new("openrouter:experimental__search_models")
367 }
368
369 pub fn apply_patch() -> Self {
370 Self::new("openrouter:apply_patch")
371 }
372
373 pub(crate) fn is_server_tool_type(tool_type: &str) -> bool {
374 tool_type.starts_with("openrouter:")
375 || matches!(
376 tool_type,
377 "web_search"
378 | "web_search_2025_08_26"
379 | "web_search_preview"
380 | "web_search_preview_2025_03_11"
381 | "apply_patch"
382 | "shell"
383 | "namespace"
384 )
385 }
386
387 pub(crate) fn is_files_tool_type(tool_type: &str) -> bool {
388 matches!(tool_type, "openrouter:files" | "files")
389 }
390
391 pub(crate) fn is_files_tool(&self) -> bool {
392 Self::is_files_tool_type(&self.tool_type)
393 }
394
395 pub(crate) fn is_server_tool_value(value: &Value) -> bool {
396 value
397 .get("type")
398 .and_then(Value::as_str)
399 .is_some_and(Self::is_server_tool_type)
400 }
401
402 pub(crate) fn is_files_tool_value(value: &Value) -> bool {
403 value
404 .get("type")
405 .and_then(Value::as_str)
406 .is_some_and(Self::is_files_tool_type)
407 }
408}
409
410impl From<ServerTool> for Value {
411 fn from(tool: ServerTool) -> Self {
412 serde_json::to_value(tool).expect("server tool serialization should not fail")
413 }
414}
415
416#[derive(Serialize, Deserialize, Debug, Clone)]
439#[non_exhaustive]
440#[serde(untagged)]
441pub enum ToolChoice {
442 String(String),
444 Specific(SpecificToolChoice),
446 Server(ServerToolChoice),
448}
449
450impl ToolChoice {
451 pub fn none() -> Self {
453 Self::String("none".to_string())
454 }
455
456 pub fn auto() -> Self {
458 Self::String("auto".to_string())
459 }
460
461 pub fn required() -> Self {
463 Self::String("required".to_string())
464 }
465
466 pub fn force_tool(tool_name: &str) -> Self {
468 Self::Specific(SpecificToolChoice {
469 tool_type: "function".to_string(),
470 function: SpecificToolFunction {
471 name: tool_name.to_string(),
472 },
473 })
474 }
475
476 pub fn force_server_tool(tool_type: impl Into<String>) -> Self {
478 Self::Server(ServerToolChoice {
479 tool_type: tool_type.into(),
480 })
481 }
482}
483
484#[derive(Serialize, Deserialize, Debug, Clone)]
486#[non_exhaustive]
487pub struct SpecificToolChoice {
488 #[serde(rename = "type")]
489 pub tool_type: String,
490 pub function: SpecificToolFunction,
491}
492
493#[derive(Serialize, Deserialize, Debug, Clone)]
495#[non_exhaustive]
496pub struct SpecificToolFunction {
497 pub name: String,
498}
499
500#[derive(Serialize, Deserialize, Debug, Clone)]
502#[non_exhaustive]
503pub struct ServerToolChoice {
504 #[serde(rename = "type")]
505 pub tool_type: String,
506}
507
508pub fn create_tool(name: &str, description: &str, properties: Value, required: &[&str]) -> Tool {
530 let parameters = serde_json::json!({
531 "type": "object",
532 "properties": properties,
533 "required": required
534 });
535
536 Tool::new(name, description, parameters)
537}
538
539#[cfg(test)]
540mod tests {
541 use super::*;
542 use serde_json::json;
543
544 #[test]
545 fn test_tool_creation() {
546 let tool = Tool::builder()
547 .name("test_function")
548 .description("A test function")
549 .parameters(json!({"type": "object"}))
550 .build()
551 .unwrap();
552
553 assert_eq!(tool.tool_type, "function");
554 assert_eq!(tool.function.name, "test_function");
555 assert_eq!(tool.function.description, "A test function");
556 }
557
558 #[test]
559 fn test_tool_choice_variants() {
560 let auto = ToolChoice::auto();
561 let none = ToolChoice::none();
562 let required = ToolChoice::required();
563 let specific = ToolChoice::force_tool("my_function");
564
565 assert_eq!(serde_json::to_string(&auto).unwrap(), r#""auto""#);
567 assert_eq!(serde_json::to_string(&none).unwrap(), r#""none""#);
568 assert_eq!(serde_json::to_string(&required).unwrap(), r#""required""#);
569
570 if let ToolChoice::Specific(spec) = specific {
571 assert_eq!(spec.function.name, "my_function");
572 } else {
573 panic!("Expected specific tool choice");
574 }
575 }
576
577 #[test]
578 fn test_create_tool_helper() {
579 let tool = create_tool(
580 "weather",
581 "Get weather",
582 json!({"location": {"type": "string"}}),
583 &["location"],
584 );
585
586 assert_eq!(tool.function.name, "weather");
587 assert_eq!(tool.function.description, "Get weather");
588
589 let params = &tool.function.parameters;
590 assert_eq!(params["type"], "object");
591 assert_eq!(params["required"], json!(["location"]));
592 }
593}