git_iris/agents/tools/
common.rs

1//! Common utilities for agent tools
2//!
3//! This module provides shared functionality used across multiple tools:
4//! - Schema generation for OpenAI-compatible tool definitions
5//! - Error type macros
6//! - Repository initialization helpers
7
8use serde_json::{Map, Value};
9
10use crate::git::GitRepo;
11
12/// Generate a JSON schema for tool parameters that's `OpenAI`-compatible.
13/// `OpenAI` tool schemas require the `required` array to list every property.
14pub fn parameters_schema<T: schemars::JsonSchema>() -> Value {
15    use schemars::schema_for;
16
17    let schema = schema_for!(T);
18    let mut value = serde_json::to_value(schema).expect("tool schema should serialize");
19    enforce_required_properties(&mut value);
20    value
21}
22
23/// Ensure all properties are listed in the `required` array.
24/// This is needed for `OpenAI` tool compatibility.
25fn enforce_required_properties(value: &mut Value) {
26    let Some(obj) = value.as_object_mut() else {
27        return;
28    };
29
30    let props_entry = obj
31        .entry("properties")
32        .or_insert_with(|| Value::Object(Map::new()));
33    let props_obj = props_entry.as_object().expect("properties must be object");
34    let required_keys: Vec<Value> = props_obj.keys().cloned().map(Value::String).collect();
35
36    obj.insert("required".to_string(), Value::Array(required_keys));
37}
38
39/// Get the current repository from the working directory.
40/// This is a common operation used by most tools.
41pub fn get_current_repo() -> anyhow::Result<GitRepo> {
42    let current_dir = std::env::current_dir()?;
43    GitRepo::new(&current_dir)
44}
45
46/// Macro to define a tool error type with standard From implementations.
47///
48/// This creates a newtype wrapper around String that implements:
49/// - `Debug`, `thiserror::Error`
50/// - `From<anyhow::Error>`
51/// - `From<std::io::Error>`
52///
53/// # Example
54/// ```ignore
55/// define_tool_error!(GitError);
56/// // Creates: pub struct GitError(String);
57/// // With Display showing: "GitError: {message}"
58/// ```
59#[macro_export]
60macro_rules! define_tool_error {
61    ($name:ident) => {
62        #[derive(Debug)]
63        pub struct $name(pub String);
64
65        impl std::fmt::Display for $name {
66            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67                write!(f, "{}", self.0)
68            }
69        }
70
71        impl std::error::Error for $name {}
72
73        impl From<anyhow::Error> for $name {
74            fn from(err: anyhow::Error) -> Self {
75                $name(err.to_string())
76            }
77        }
78
79        impl From<std::io::Error> for $name {
80            fn from(err: std::io::Error) -> Self {
81                $name(err.to_string())
82            }
83        }
84    };
85}
86
87// Re-export the macro at the module level
88pub use define_tool_error;