cersei_tools_derive/
lib.rs1use proc_macro::TokenStream;
17use quote::quote;
18use syn::{parse_macro_input, DeriveInput};
19
20#[proc_macro_derive(Tool, attributes(tool))]
21pub fn derive_tool(input: TokenStream) -> TokenStream {
22 let input = parse_macro_input!(input as DeriveInput);
23 let name = &input.ident;
24
25 let mut tool_name = name.to_string().to_lowercase();
27 let mut tool_description = String::new();
28 let mut tool_permission = quote! { cersei_tools::PermissionLevel::None };
29 let mut tool_category = quote! { cersei_tools::ToolCategory::Custom };
30
31 for attr in &input.attrs {
32 if !attr.path().is_ident("tool") {
33 continue;
34 }
35 let _ = attr.parse_nested_meta(|meta| {
36 if let Some(ident) = meta.path.get_ident() {
37 let key = ident.to_string();
38 let value: syn::LitStr = meta.value()?.parse()?;
39 let val = value.value();
40 match key.as_str() {
41 "name" => tool_name = val,
42 "description" => tool_description = val,
43 "permission" => {
44 tool_permission = match val.as_str() {
45 "none" => quote! { cersei_tools::PermissionLevel::None },
46 "read_only" => quote! { cersei_tools::PermissionLevel::ReadOnly },
47 "write" => quote! { cersei_tools::PermissionLevel::Write },
48 "execute" => quote! { cersei_tools::PermissionLevel::Execute },
49 "dangerous" => quote! { cersei_tools::PermissionLevel::Dangerous },
50 _ => quote! { cersei_tools::PermissionLevel::None },
51 };
52 }
53 "category" => {
54 tool_category = match val.as_str() {
55 "filesystem" => quote! { cersei_tools::ToolCategory::FileSystem },
56 "shell" => quote! { cersei_tools::ToolCategory::Shell },
57 "web" => quote! { cersei_tools::ToolCategory::Web },
58 "memory" => quote! { cersei_tools::ToolCategory::Memory },
59 "orchestration" => quote! { cersei_tools::ToolCategory::Orchestration },
60 "mcp" => quote! { cersei_tools::ToolCategory::Mcp },
61 _ => quote! { cersei_tools::ToolCategory::Custom },
62 };
63 }
64 _ => {}
65 }
66 }
67 Ok(())
68 });
69 }
70
71 let expanded = quote! {
72 #[async_trait::async_trait]
73 impl cersei_tools::Tool for #name {
74 fn name(&self) -> &str {
75 #tool_name
76 }
77
78 fn description(&self) -> &str {
79 #tool_description
80 }
81
82 fn permission_level(&self) -> cersei_tools::PermissionLevel {
83 #tool_permission
84 }
85
86 fn category(&self) -> cersei_tools::ToolCategory {
87 #tool_category
88 }
89
90 fn input_schema(&self) -> serde_json::Value {
91 let schema = schemars::schema_for!(
92 <Self as cersei_tools::ToolExecute>::Input
93 );
94 serde_json::to_value(schema).unwrap_or(serde_json::json!({}))
95 }
96
97 async fn execute(
98 &self,
99 input: serde_json::Value,
100 ctx: &cersei_tools::ToolContext,
101 ) -> cersei_tools::ToolResult {
102 match serde_json::from_value::<<Self as cersei_tools::ToolExecute>::Input>(input) {
103 Ok(typed_input) => {
104 <Self as cersei_tools::ToolExecute>::run(self, typed_input, ctx).await
105 }
106 Err(e) => cersei_tools::ToolResult::error(
107 format!("Invalid input for '{}': {}", #tool_name, e)
108 ),
109 }
110 }
111 }
112 };
113
114 TokenStream::from(expanded)
115}