Skip to main content

agy_bridge/tools/
mod.rs

1//! Custom tool registration for the Antigravity SDK bridge.
2//!
3//! This module re-exports types from the `llm_tool` crate, which provides
4//! framework-agnostic tool definitions. The explicit re-exports ensure backward
5//! compatibility — existing `use agy_bridge::tools::ToolRegistry` imports
6//! continue to work, while giving this crate control over its public API surface.
7
8// Re-export proc-macro helpers used by `#[llm_tool]` generated code.
9// These are `#[doc(hidden)]` in the `llm_tool` crate and should not
10// appear in user-facing documentation.
11#[doc(hidden)]
12pub use llm_tool::__private;
13pub use llm_tool::{
14    EmptyParams, Json, JsonSchema, RustTool, ToolContext, ToolDefinition, ToolError, ToolOutput,
15    ToolRegistry, definition_of,
16};
17
18// ── Available tool discovery types ──────────────────────────────────
19
20/// Where a tool originates from.
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
22#[serde(rename_all = "snake_case")]
23pub enum ToolSource {
24    /// SDK builtin tool (e.g. `view_file`, `run_command`) — implemented by
25    /// the Antigravity SDK backend, not by user code.
26    Builtin,
27    /// Custom Rust tool registered via [`ToolRegistry`].
28    Custom,
29    /// Tool discovered from a connected MCP server.
30    Mcp,
31}
32
33impl std::fmt::Display for ToolSource {
34    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35        match self {
36            Self::Builtin => f.write_str("builtin"),
37            Self::Custom => f.write_str("custom"),
38            Self::Mcp => f.write_str("mcp"),
39        }
40    }
41}
42
43/// A tool available to an agent, with metadata about its origin.
44///
45/// Returned by [`AgentHandle::available_tools()`](crate::agent::AgentHandle::available_tools).
46/// Includes tools from all sources: SDK builtins, custom Rust tools, and MCP
47/// server tools.
48#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
49pub struct AvailableTool {
50    /// Tool name as seen by the model (e.g. `"view_file"`, `"get_weather"`).
51    pub name: String,
52    /// Human-readable description. Empty if the tool source didn't provide one.
53    pub description: String,
54    /// JSON Schema for the tool's parameters. `Value::Null` if unavailable.
55    pub parameter_schema: serde_json::Value,
56    /// Where this tool originates from.
57    pub source: ToolSource,
58}
59
60impl std::fmt::Display for AvailableTool {
61    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62        write!(f, "{} [{}]", self.name, self.source)
63    }
64}
65
66// ── Unit tests ─────────────────────────────────────────────────────
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71
72    // ── ToolSource tests ──────────────────────────────────────────────
73
74    #[test]
75    fn tool_source_display() {
76        assert_eq!(ToolSource::Builtin.to_string(), "builtin");
77        assert_eq!(ToolSource::Custom.to_string(), "custom");
78        assert_eq!(ToolSource::Mcp.to_string(), "mcp");
79    }
80
81    #[test]
82    fn tool_source_serde_roundtrip() {
83        for source in [ToolSource::Builtin, ToolSource::Custom, ToolSource::Mcp] {
84            let json = serde_json::to_string(&source).unwrap();
85            let parsed: ToolSource = serde_json::from_str(&json).unwrap();
86            assert_eq!(parsed, source);
87        }
88    }
89
90    #[test]
91    fn tool_source_serializes_as_snake_case() {
92        assert_eq!(
93            serde_json::to_string(&ToolSource::Builtin).unwrap(),
94            "\"builtin\""
95        );
96        assert_eq!(
97            serde_json::to_string(&ToolSource::Custom).unwrap(),
98            "\"custom\""
99        );
100        assert_eq!(serde_json::to_string(&ToolSource::Mcp).unwrap(), "\"mcp\"");
101    }
102
103    // ── AvailableTool tests ───────────────────────────────────────────
104
105    #[test]
106    fn available_tool_display() {
107        let tool = AvailableTool {
108            name: "get_weather".to_owned(),
109            description: "Gets weather.".to_owned(),
110            parameter_schema: serde_json::Value::Null,
111            source: ToolSource::Mcp,
112        };
113        assert_eq!(tool.to_string(), "get_weather [mcp]");
114    }
115
116    #[test]
117    fn available_tool_serde_roundtrip() {
118        let tool = AvailableTool {
119            name: "view_file".to_owned(),
120            description: "Read file contents.".to_owned(),
121            parameter_schema: serde_json::json!({"type": "object"}),
122            source: ToolSource::Builtin,
123        };
124        let json = serde_json::to_string(&tool).unwrap();
125        let parsed: AvailableTool = serde_json::from_str(&json).unwrap();
126        assert_eq!(parsed.name, "view_file");
127        assert_eq!(parsed.source, ToolSource::Builtin);
128        assert!(!parsed.description.is_empty());
129    }
130}