#![deny(missing_docs)]
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 return_type = &input_fn.sig.output;
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")
&& let Expr::Lit(lit) = &arg.value
&& let Lit::Str(str_lit) = &lit.lit
{
description = Some(str_lit.value());
} else if arg.path.is_ident("name")
&& let Expr::Lit(lit) = &arg.value
&& 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 = ::aisdk::__private::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! {
#vis fn #fn_name() #return_type {
use std::collections::HashMap;
use ::aisdk::__private::schemars::{schema_for, JsonSchema, Schema};
#[derive(::aisdk::__private::schemars::JsonSchema, Debug)]
#[schemars(crate = "::aisdk::__private::schemars")]
#[allow(dead_code)]
struct Function {
#(#struct_fields),*
}
let input_schema = schema_for!(Function);
let mut tool = ::aisdk::core::tools::Tool::builder()
.name(#name.to_string())
.description(#description.to_string())
.input_schema(input_schema)
.execute(::aisdk::core::tools::ToolExecute::new(Box::new(|inp| -> std::result::Result<String, String> {
#(#binding_tokens)*
#block
})));
tool.build().expect("Failed to build tool")
}
};
TokenStream::from(expanded)
}