mono-ai-macros 2.0.0

procedural macros for tools in mono-ai
Documentation
extern crate proc_macro;

use proc_macro::TokenStream;
use quote::{format_ident, quote};
use serde_json::json;
use syn::{ItemFn, Pat, PatType, Type, parse_macro_input};

fn rust_type_to_json_type(ty: &Type) -> &'static str {
    if let Type::Path(type_path) = ty {
        if type_path.path.is_ident("String") {
            return "string";
        } else if type_path.path.is_ident("i32")
            || type_path.path.is_ident("i64")
            || type_path.path.is_ident("usize")
        {
            return "integer";
        } else if type_path.path.is_ident("f32") || type_path.path.is_ident("f64") {
            return "number";
        } else if type_path.path.is_ident("bool") {
            return "boolean";
        }
    }
    "string"
}

#[proc_macro_attribute]
pub fn tool(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let func = parse_macro_input!(item as ItemFn);
    let func_name = &func.sig.ident;
    let func_name_str = func_name.to_string();
    let tool_func_name = format_ident!("{}_tool", func_name);

    let mut description = String::new();
    for attr in &func.attrs {
        if attr.path().is_ident("doc") {
            if let Ok(name_value) = attr.meta.require_name_value() {
                if let syn::Expr::Lit(expr_lit) = &name_value.value {
                    if let syn::Lit::Str(lit_str) = &expr_lit.lit {
                        description.push_str(&lit_str.value().trim());
                        description.push(' ');
                    }
                }
            }
        }
    }
    let description = description.trim().to_string();

    let mut params_properties = serde_json::Map::new();
    let mut required_params = Vec::new();
    let mut arg_names = Vec::new();
    let mut arg_names_str = Vec::new();
    let mut arg_types = Vec::new();

    for input in &func.sig.inputs {
        if let syn::FnArg::Typed(PatType { pat, ty, .. }) = input {
            if let Pat::Ident(pat_ident) = &**pat {
                let arg_name = pat_ident.ident.to_string();
                let json_type = rust_type_to_json_type(ty);

                params_properties.insert(
                    arg_name.clone(),
                    json!({
                        "type": json_type,
                        "description": ""
                    }),
                );
                required_params.push(arg_name.clone());
                arg_names.push(pat_ident.ident.clone());
                arg_names_str.push(arg_name.clone());
                arg_types.push(ty.clone());
            }
        }
    }

    let parameters_json = json!({
        "type": "object",
        "properties": params_properties,
        "required": required_params
    })
    .to_string();

    let expanded = quote! {
        pub fn #tool_func_name() -> mono_ai::Tool {
            #func

            mono_ai::Tool {
                name: #func_name_str.to_string(),
                description: #description.to_string(),
                parameters: serde_json::from_str(#parameters_json).unwrap(),
                function: Box::new(|args| {
                    #(let #arg_names: #arg_types = serde_json::from_value(args[#arg_names_str].clone()).unwrap();)*
                    #func_name(#(#arg_names),*).to_string()
                }),
            }
        }
    };

    TokenStream::from(expanded)
}