use proc_macro::TokenStream;
use quote::quote;
use syn::parse::Parser;
use syn::{
Expr, ExprLit, FnArg, ItemFn, Lit, Meta, MetaNameValue, Pat, Token, parse_macro_input,
punctuated::Punctuated,
};
#[proc_macro_attribute]
pub fn tool(_attr: TokenStream, item: TokenStream) -> TokenStream {
let input_fn = parse_macro_input!(item as ItemFn);
let fn_name = &input_fn.sig.ident;
let vis = &input_fn.vis;
let block = &input_fn.block;
let inputs = &input_fn.sig.inputs;
let attrs = &input_fn.attrs;
let args_parser = Punctuated::<MetaNameValue, Token![,]>::parse_terminated;
let args = args_parser.parse(_attr);
let (name_arg, description_arg) = if let Ok(args) = args {
let mut name: Option<String> = None;
let mut description: Option<String> = None;
for arg in args {
if arg.path.is_ident("desc") {
if let Expr::Lit(lit) = &arg.value {
if let Lit::Str(str_lit) = &lit.lit {
description = Some(str_lit.value());
}
}
} else if arg.path.is_ident("name") {
if let Expr::Lit(lit) = &arg.value {
if let Lit::Str(str_lit) = &lit.lit {
name = Some(str_lit.value());
}
}
}
}
(name, description)
} else {
(None, None)
};
let description = if let Some(desc) = description_arg {
desc
} else {
let doc_comments: Vec<String> = attrs
.iter()
.filter_map(|attr| {
if attr.path().is_ident("doc") {
if let Meta::NameValue(meta_name_value) = &attr.meta {
if let Expr::Lit(ExprLit {
lit: Lit::Str(lit_str),
..
}) = &meta_name_value.value
{
let doc = lit_str.value();
Some(doc)
} else {
None
}
} else {
None
}
} else {
None
}
})
.collect();
doc_comments.join("\n")
};
let name = if let Some(name) = name_arg {
name
} else {
fn_name.to_string()
};
let binding_tokens: Vec<_> = inputs
.iter()
.filter_map(|arg| {
if let FnArg::Typed(pat_type) = arg {
if let Pat::Ident(pat_ident) = &*pat_type.pat {
let ident = &pat_ident.ident;
let ty = &*pat_type.ty;
let ident_str = ident.to_string();
Some(quote! {
let #ident: #ty = serde_json::from_value(
inp.as_object()
.unwrap()
.get(#ident_str)
.unwrap()
.clone()
).unwrap_or_default(); })
} else {
None
}
} else {
None
}
})
.collect();
let struct_fields = inputs.iter().filter_map(|arg| {
if let FnArg::Typed(pat_type) = arg {
if let Pat::Ident(pat_ident) = &*pat_type.pat {
let ident = &pat_ident.ident;
let ty = &*pat_type.ty;
Some(quote! { #ident: #ty })
} else {
None
}
} else {
None
}
});
let expanded = quote! {
#[allow(unused_variables)]
#vis fn #fn_name() -> Tool {
use schemars::{schema_for, JsonSchema, Schema};
use std::collections::HashMap;
#[derive(JsonSchema, Debug)]
struct Function {
#(#struct_fields),*
}
let input_schema = schema_for!(Function);
let mut tool = Tool::new();
tool.name = #name.to_string();
tool.description = #description.to_string();
tool.input_schema = input_schema;
tool.execute = ToolExecute::new(Box::new(|inp| -> std::result::Result<String, String> {
#(#binding_tokens)*
#block
}));
tool
}
};
TokenStream::from(expanded)
}