1use proc_macro::TokenStream;
4use proc_macro2::TokenStream as TokenStream2;
5use quote::quote;
6use syn::{
7 parse_macro_input, Data, DeriveInput, Expr, Field, Fields, Lit, Meta, Type,
8};
9use syn::punctuated::Punctuated;
10use syn::token::Comma;
11
12#[proc_macro_derive(Tool, attributes(tool))]
17pub fn derive_tool(input: TokenStream) -> TokenStream {
18 let input = parse_macro_input!(input as DeriveInput);
19
20 let fields = match &input.data {
21 Data::Struct(data) => match &data.fields {
22 Fields::Named(named) => &named.named,
23 _ => return err_compile("Tool 只支持具名字段的 struct"),
24 },
25 _ => return err_compile("Tool 只能 derive 在 struct 上"),
26 };
27
28 let struct_name = &input.ident;
29 let (tool_name, tool_desc) = parse_tool_attrs(&input);
30
31 let mut field_idents: Vec<TokenStream2> = Vec::new();
32 let mut schema_entries = Vec::new();
33 let mut deser_code = Vec::new();
34 let mut required_fields: Vec<String> = Vec::new();
35
36 for field in fields.iter() {
37 let fname = field.ident.as_ref().unwrap();
38 let fname_str = fname.to_string();
39 let desc = field_doc(field).unwrap_or_else(|| format!("字段 {}", fname_str));
40 let (schema_json, deser, required) = type_info(&field.ty, &fname_str, &desc);
41
42 if required {
43 required_fields.push(fname_str.clone());
44 }
45 field_idents.push(quote! { #fname });
46 schema_entries.push(quote! {
47 #fname_str: #schema_json
48 });
49 deser_code.push(deser);
50 }
51
52 let schema = if required_fields.is_empty() {
53 quote! {
54 ::serde_json::json!({
55 "type": "object",
56 "properties": { #(#schema_entries,)* }
57 })
58 }
59 } else {
60 quote! {
61 ::serde_json::json!({
62 "type": "object",
63 "properties": { #(#schema_entries,)* },
64 "required": [#(#required_fields,)*]
65 })
66 }
67 };
68
69 let expanded = quote! {
70 #[::async_trait::async_trait]
71 impl ::cortex_engine::tool::Tool for #struct_name {
72 fn name(&self) -> &str { #tool_name }
73 fn description(&self) -> String { #tool_desc.into() }
74 fn parameters(&self) -> ::serde_json::Value { #schema }
75 async fn execute(
76 &self,
77 __args: ::serde_json::Value,
78 ) -> Result<::serde_json::Value, ::cortex_engine::tool::ToolError> {
79 #(#deser_code)*
80 self.__tool_execute_impl(#(#field_idents,)*).await
81 }
82 }
83 };
84
85 expanded.into()
86}
87
88fn err_compile(msg: &str) -> TokenStream {
89 syn::Error::new(proc_macro2::Span::call_site(), msg)
90 .to_compile_error()
91 .into()
92}
93
94fn parse_tool_attrs(input: &DeriveInput) -> (String, String) {
96 let default_name = camel_to_snake(&input.ident.to_string());
97 let mut tool_name: Option<String> = None;
98 let mut tool_desc: Option<String> = None;
99
100 for attr in &input.attrs {
101 if !attr.path().is_ident("tool") { continue; }
102 if let Ok(metas) = attr.parse_args_with(
103 Punctuated::<Meta, Comma>::parse_terminated,
104 ) {
105 for meta in &metas {
106 if let Meta::NameValue(kv) = meta {
107 let key = kv.path.get_ident().map(|i| i.to_string());
108 let value = match &kv.value {
109 Expr::Lit(lit) => match &lit.lit {
110 Lit::Str(s) => s.value(),
111 _ => continue,
112 },
113 _ => continue,
114 };
115 match key.as_deref() {
116 Some("name") => tool_name = Some(value),
117 Some("desc") => tool_desc = Some(value),
118 _ => {}
119 }
120 }
121 }
122 }
123 }
124
125 let name = tool_name.unwrap_or(default_name);
126 let desc = tool_desc.unwrap_or_else(|| format!("工具: {}", input.ident));
127 (name, desc)
128}
129
130fn camel_to_snake(s: &str) -> String {
131 let mut r = String::with_capacity(s.len() + 4);
132 for (i, c) in s.chars().enumerate() {
133 if c.is_uppercase() {
134 if i > 0 { r.push('_'); }
135 for l in c.to_lowercase() { r.push(l); }
136 } else { r.push(c); }
137 }
138 r
139}
140
141fn field_doc(field: &Field) -> Option<String> {
143 let docs = get_doc_comments(&field.attrs);
144 if docs.is_empty() { None } else { Some(docs) }
145}
146
147fn get_doc_comments(attrs: &[syn::Attribute]) -> String {
149 let mut parts = Vec::new();
150 for attr in attrs {
151 if !attr.path().is_ident("doc") { continue; }
152 if let Ok(Meta::NameValue(kv)) = attr.parse_args::<Meta>() {
155 if let Expr::Lit(el) = &kv.value {
156 if let Lit::Str(s) = &el.lit {
157 let val = s.value().trim().to_string();
158 if !val.is_empty() {
159 parts.push(val);
160 }
161 }
162 }
163 }
164 }
165 parts.join(" ")
166}
167
168fn type_info(ty: &Type, field_name: &str, desc: &str) -> (TokenStream2, TokenStream2, bool) {
170 let ident = syn::Ident::new(field_name, proc_macro2::Span::call_site());
171 let key_str = field_name.to_string();
172 let type_str = quote!(#ty).to_string();
173 let desc = desc.to_string();
174
175 match type_str.as_str() {
176 "String" | "std :: string :: String" => (
177 quote! { ::serde_json::json!({"type": "string", "description": #desc}) },
178 quote! {
179 let #ident: String = __args.get(#key_str)
180 .and_then(|v| v.as_str()).unwrap_or("").to_string();
181 },
182 true,
183 ),
184 "i64" => (
185 quote! { ::serde_json::json!({"type": "integer", "description": #desc}) },
186 quote! {
187 let #ident: i64 = __args.get(#key_str)
188 .and_then(|v| v.as_i64()).unwrap_or(0);
189 },
190 true,
191 ),
192 "i32" => (
193 quote! { ::serde_json::json!({"type": "integer", "description": #desc}) },
194 quote! {
195 let #ident: i32 = __args.get(#key_str)
196 .and_then(|v| v.as_i64()).unwrap_or(0) as i32;
197 },
198 true,
199 ),
200 "f64" => (
201 quote! { ::serde_json::json!({"type": "number", "description": #desc}) },
202 quote! {
203 let #ident: f64 = __args.get(#key_str)
204 .and_then(|v| v.as_f64()).unwrap_or(0.0);
205 },
206 true,
207 ),
208 "f32" => (
209 quote! { ::serde_json::json!({"type": "number", "description": #desc}) },
210 quote! {
211 let #ident: f32 = __args.get(#key_str)
212 .and_then(|v| v.as_f64()).unwrap_or(0.0) as f32;
213 },
214 true,
215 ),
216 "bool" => (
217 quote! { ::serde_json::json!({"type": "boolean", "description": #desc}) },
218 quote! {
219 let #ident: bool = __args.get(#key_str)
220 .and_then(|v| v.as_bool()).unwrap_or(false);
221 },
222 true,
223 ),
224 "Option < String >" | "Option < std :: string :: String >" => (
226 quote! { ::serde_json::json!({"type": "string", "description": #desc}) },
227 quote! {
228 let #ident: Option<String> = __args.get(#key_str)
229 .and_then(|v| v.as_str()).map(String::from);
230 },
231 false,
232 ),
233 "Option < i64 >" => (
234 quote! { ::serde_json::json!({"type": "integer", "description": #desc}) },
235 quote! {
236 let #ident: Option<i64> = __args.get(#key_str)
237 .and_then(|v| v.as_i64());
238 },
239 false,
240 ),
241 "Option < f64 >" => (
242 quote! { ::serde_json::json!({"type": "number", "description": #desc}) },
243 quote! {
244 let #ident: Option<f64> = __args.get(#key_str)
245 .and_then(|v| v.as_f64());
246 },
247 false,
248 ),
249 "Option < bool >" => (
250 quote! { ::serde_json::json!({"type": "boolean", "description": #desc}) },
251 quote! {
252 let #ident: Option<bool> = __args.get(#key_str)
253 .and_then(|v| v.as_bool());
254 },
255 false,
256 ),
257 "Vec < String >" | "Vec < std :: string :: String >" => (
259 quote! { ::serde_json::json!({"type": "array", "items": {"type": "string"}, "description": #desc}) },
260 quote! {
261 let #ident: Vec<String> = __args.get(#key_str)
262 .and_then(|v| v.as_array())
263 .map(|a| a.iter().filter_map(|v| v.as_str().map(String::from)).collect())
264 .unwrap_or_default();
265 },
266 true,
267 ),
268 "Vec < i64 >" => (
269 quote! { ::serde_json::json!({"type": "array", "items": {"type": "integer"}, "description": #desc}) },
270 quote! {
271 let #ident: Vec<i64> = __args.get(#key_str)
272 .and_then(|v| v.as_array())
273 .map(|a| a.iter().filter_map(|v| v.as_i64()).collect())
274 .unwrap_or_default();
275 },
276 true,
277 ),
278 "Vec < f64 >" => (
279 quote! { ::serde_json::json!({"type": "array", "items": {"type": "number"}, "description": #desc}) },
280 quote! {
281 let #ident: Vec<f64> = __args.get(#key_str)
282 .and_then(|v| v.as_array())
283 .map(|a| a.iter().filter_map(|v| v.as_f64()).collect())
284 .unwrap_or_default();
285 },
286 true,
287 ),
288 _ => (
290 quote! { ::serde_json::json!({"type": "string", "description": #desc}) },
291 quote! {
292 let #ident = ::serde_json::from_value::<_>(
293 __args.get(#key_str).cloned()
294 .unwrap_or(::serde_json::Value::Null),
295 ).map_err(|e| ::cortex_engine::tool::ToolError::new(
296 format!("参数 '{}' 格式错误: {}", #key_str, e)
297 ))?;
298 },
299 true,
300 ),
301 }
302}