openfunctions_rs/core/
tool.rs

1//! Tool management and representation.
2//!
3//! This module defines the `Tool` struct, which represents a single executable
4//! function, and related components like `ToolLanguage`.
5
6use crate::models::ToolDefinition;
7use crate::parser::ToolParser;
8use anyhow::Result;
9use serde::{Deserialize, Serialize};
10use std::path::{Path, PathBuf};
11
12/// Represents a single, executable tool that can be called by an LLM or user.
13///
14/// A `Tool` is loaded from a script file (e.g., Bash, Python, JavaScript) and
15/// contains its definition, which is parsed from comments in the source code.
16#[derive(Debug, Clone)]
17pub struct Tool {
18    /// The name of the tool, derived from its filename.
19    pub name: String,
20
21    /// The path to the tool's source file.
22    pub path: PathBuf,
23
24    /// The programming language of the tool's script.
25    pub language: ToolLanguage,
26
27    /// The detailed definition of the tool, parsed from its source code.
28    pub definition: ToolDefinition,
29
30    /// The function declaration for the LLM, generated from the `ToolDefinition`.
31    pub declaration: crate::core::function::FunctionDeclaration,
32}
33
34impl Tool {
35    /// Loads a tool from a source file.
36    ///
37    /// This function reads a file, determines its language, parses the tool
38    /// definition from its comments, and creates a `Tool` instance.
39    pub async fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
40        let path = path.as_ref().to_path_buf();
41        let name = path
42            .file_stem()
43            .ok_or_else(|| anyhow::anyhow!("Invalid tool filename: {}", path.display()))?
44            .to_string_lossy()
45            .to_string();
46
47        let language = ToolLanguage::from_extension(
48            path.extension().and_then(|ext| ext.to_str()).unwrap_or(""),
49        )?;
50
51        let content = tokio::fs::read_to_string(&path).await?;
52        let parser = ToolParser::new(language);
53        let definition = parser.parse(&content)?;
54
55        let declaration = definition.to_function_declaration(&name)?;
56
57        Ok(Self {
58            name,
59            path,
60            language,
61            definition,
62            declaration,
63        })
64    }
65
66    /// Returns the command and arguments needed to execute the tool.
67    pub fn get_command(&self) -> Vec<String> {
68        match self.language {
69            ToolLanguage::Bash => vec!["bash".to_string(), self.path.to_string_lossy().to_string()],
70            ToolLanguage::JavaScript => {
71                vec!["node".to_string(), self.path.to_string_lossy().to_string()]
72            }
73            ToolLanguage::Python => vec![
74                "python".to_string(),
75                self.path.to_string_lossy().to_string(),
76            ],
77        }
78    }
79}
80
81/// An enumeration of the programming languages supported for tools.
82#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
83#[serde(rename_all = "lowercase")]
84pub enum ToolLanguage {
85    /// Bash shell script
86    Bash,
87    /// JavaScript (Node.js)
88    JavaScript,
89    /// Python script
90    Python,
91}
92
93impl ToolLanguage {
94    /// Determines the `ToolLanguage` from a file extension.
95    pub fn from_extension(ext: &str) -> Result<Self> {
96        match ext {
97            "sh" => Ok(Self::Bash),
98            "js" => Ok(Self::JavaScript),
99            "py" => Ok(Self::Python),
100            _ => anyhow::bail!("Unsupported file extension for a tool: {}", ext),
101        }
102    }
103
104    /// Returns the canonical file extension for the language.
105    pub fn extension(&self) -> &'static str {
106        match self {
107            Self::Bash => "sh",
108            Self::JavaScript => "js",
109            Self::Python => "py",
110        }
111    }
112}
113
114impl std::fmt::Display for ToolLanguage {
115    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116        f.write_str(match self {
117            Self::Bash => "bash",
118            Self::JavaScript => "javascript",
119            Self::Python => "python",
120        })
121    }
122}