1extern crate proc_macro;
2use proc_macro::TokenStream;
3use quote::{format_ident, quote};
4use syn::{parse_macro_input, DeriveInput, ItemFn};
5
6#[proc_macro_derive(VexJob, attributes(job))]
14pub fn derive_vex_job(input: TokenStream) -> TokenStream {
15 let input = parse_macro_input!(input as DeriveInput);
16 let name = input.ident;
17
18 let job_name = name.to_string().to_lowercase();
20 let max_retries = 3u32;
21
22 let expanded = quote! {
23 #[async_trait::async_trait]
24 impl vex_queue::job::Job for #name {
25 fn name(&self) -> &str {
26 #job_name
27 }
28
29 async fn execute(&mut self) -> vex_queue::job::JobResult {
30 self.run().await
31 }
32
33 fn max_retries(&self) -> u32 {
34 #max_retries
35 }
36
37 fn backoff_strategy(&self) -> vex_queue::job::BackoffStrategy {
38 vex_queue::job::BackoffStrategy::Exponential {
39 initial_secs: 1,
40 multiplier: 2.0
41 }
42 }
43 }
44 };
45
46 TokenStream::from(expanded)
47}
48
49#[proc_macro_attribute]
59pub fn vex_tool(_args: TokenStream, item: TokenStream) -> TokenStream {
60 let input = parse_macro_input!(item as ItemFn);
61 let fn_name = &input.sig.ident;
62 let tool_name = fn_name.to_string();
63 let tool_desc = "Auto-generated tool"; let mut props_map = std::collections::HashMap::new();
66 let mut req_list = Vec::new();
67
68 for arg in &input.sig.inputs {
69 if let syn::FnArg::Typed(pat_type) = arg {
70 if let syn::Pat::Ident(pat_ident) = &*pat_type.pat {
71 let arg_name = pat_ident.ident.to_string();
72 let arg_type = &*pat_type.ty;
73
74 let (json_type, is_optional) = match get_json_type_static(arg_type) {
75 Some((t, opt)) => (t, opt),
76 None => ("string", false),
77 };
78
79 props_map.insert(arg_name.clone(), json_type);
80 if !is_optional {
81 req_list.push(arg_name);
82 }
83 }
84 }
85 }
86
87 let parameters = if props_map.is_empty() {
88 "{}".to_string()
89 } else {
90 let mut props_vec: Vec<_> = props_map.iter().collect();
91 props_vec.sort_by_key(|a| a.0); let props_str = props_vec
94 .iter()
95 .map(|(k, v)| format!("\"{}\":{{\"type\":\"{}\"}}", k, v))
96 .collect::<Vec<_>>()
97 .join(",");
98
99 let mut req_vec = req_list.clone();
100 req_vec.sort();
101
102 let req_str = req_vec
103 .iter()
104 .map(|k| format!("\"{}\"", k))
105 .collect::<Vec<_>>()
106 .join(",");
107 format!(
108 "{{\"type\":\"object\",\"properties\":{{{}}},\"required\":[{}]}}",
109 props_str, req_str
110 )
111 };
112
113 let const_name = format_ident!("{}_TOOL", tool_name.to_uppercase());
114
115 let expanded = quote! {
116 #input
117
118 pub const #const_name: vex_llm::ToolDefinition = vex_llm::ToolDefinition {
119 name: #tool_name,
120 description: #tool_desc,
121 parameters: #parameters,
122 };
123 };
124
125 TokenStream::from(expanded)
126}
127
128fn get_json_type_static(ty: &syn::Type) -> Option<(&'static str, bool)> {
129 match ty {
130 syn::Type::Path(tp) => {
131 let last = tp.path.segments.last()?;
132 let ident = last.ident.to_string();
133 match ident.as_str() {
134 "String" | "str" => Some(("string", false)),
135 "i32" | "i64" | "u32" | "u64" | "isize" | "usize" => Some(("integer", false)),
136 "f32" | "f64" => Some(("number", false)),
137 "bool" => Some(("boolean", false)),
138 "Option" => {
139 if let syn::PathArguments::AngleBracketed(args) = &last.arguments {
140 if let Some(syn::GenericArgument::Type(inner)) = args.args.first() {
141 let (inner_type, _) = get_json_type_static(inner)?;
142 return Some((inner_type, true));
143 }
144 }
145 None
146 }
147 _ => None,
148 }
149 }
150 _ => None,
151 }
152}
153
154#[proc_macro_attribute]
162pub fn instrument_agent(_args: TokenStream, item: TokenStream) -> TokenStream {
163 let input = parse_macro_input!(item as ItemFn);
164 let fn_name = &input.sig.ident;
165 let block = &input.block;
166 let sig = &input.sig;
167 let vis = &input.vis;
168 let attrs = &input.attrs;
169
170 let expanded = quote! {
171 #(#attrs)*
172 #vis #sig {
173 let span = tracing::info_span!(
174 stringify!(#fn_name),
175 agent_id = %self.id,
176 generation = %self.generation
177 );
178 let _enter = span.enter();
179 #block
180 }
181 };
182
183 TokenStream::from(expanded)
184}