Skip to main content

neva_macros/
lib.rs

1//! A proc macro implementation for configuring tool
2
3use proc_macro::TokenStream;
4use syn::{Token, parse_macro_input, punctuated::Punctuated};
5
6#[cfg(feature = "client")]
7mod client;
8#[cfg(feature = "server")]
9mod server;
10mod shared;
11
12/// Maps the function to a tool
13///
14/// # Parameters
15/// * `title` - Tool title.
16/// * `descr` - Tool description.
17/// * `input_schema` - Schema for the tool input.
18/// * `output_schema` - Schema for the tool output.
19/// * `annotations` - Arbitrary [metadata](https://docs.rs/neva/latest/neva/types/tool/struct.ToolAnnotations.html).
20/// * `roles` & `permissions` - Define which users can run the tool when using Streamable HTTP transport with OAuth.
21/// * `middleware` - Middleware list to apply to the tool.
22/// * `task_support` - Specifies task augmentation support for this tool.
23/// * `no_schema` - Explicitly disables input schema generation if it's not set in `input_schema`.
24///
25/// # Simple Example
26/// ```ignore
27/// use neva::prelude::*;
28///
29/// #[tool(descr = "Hello world tool")]
30/// async fn say_hello() -> &'static str {
31///     "Hello, world!"
32/// }
33/// ```
34///
35/// # Full Example
36/// ```ignore
37/// use neva::prelude::*;
38///
39/// #[derive(serde::Deserialize)]
40/// struct Payload {
41///     say: String,
42///     name: String,
43/// }
44///
45/// #[json_schema(ser)]
46/// struct Results {
47///     message: String,
48/// }
49///
50/// #[tool(
51///     title = "JSON Hello",
52///     descr = "Say from JSON",
53///     roles = ["user"],
54///     permissions = ["read"],
55///     annotations = r#"{
56///         "destructiveHint": false,
57///         "idempotentHint": true,
58///         "openWorldHint": false,
59///         "readOnlyHint": false
60///     }"#,
61///     input_schema = r#"{
62///         "properties": {
63///             "arg": {
64///                 "type": "object",
65///                 "description": "A message in JSON format",
66///                 "properties": {
67///                     "say": { "type": "string", "description": "A message to say" },
68///                     "name": { "type": "string", "description": "A name to whom say Hello" }
69///                 },
70///                 "required": ["say", "name"]
71///             }
72///         },
73///         "required": ["arg"]
74///     }"#,
75///     output_schema = r#"{
76///         "properties": {
77///             "message": { "type": "string", "description": "A message to say" }
78///         },
79///         "required": ["message"]
80///     }"#
81/// )]
82/// async fn say_json(arg: Json<Payload>) -> Json<Results> {
83///     let result = Results { message: format!("{}, {}!", arg.say, arg.name) };
84///     result.into()
85/// }
86/// ```
87#[proc_macro_attribute]
88#[cfg(feature = "server")]
89pub fn tool(attr: TokenStream, item: TokenStream) -> TokenStream {
90    let function = parse_macro_input!(item as syn::ItemFn);
91    let attr = parse_macro_input!(
92        attr with Punctuated::<syn::Meta, Token![,]>::parse_terminated
93    );
94    server::tool::expand(&attr, &function)
95        .unwrap_or_else(syn::Error::into_compile_error)
96        .into()
97}
98
99/// Maps the function to a resource template
100///
101/// # Parameters
102/// * `uri` - Resource URI.
103/// * `title` - Resource title.
104/// * `descr` - Resource description.
105/// * `mime` - Resource MIME type.
106/// * `annotations` - Resource content arbitrary [metadata](https://docs.rs/neva/latest/neva/types/struct.Annotations.html).
107/// * `roles` & `permissions` - Define which users can read the resource when using Streamable HTTP transport with OAuth.
108///
109/// # Simple Example
110/// ```ignore
111/// use neva::prelude::*;
112///
113/// #[resource(uri = "res://{name}"]
114/// async fn get_res(name: String) -> TextResourceContents {
115///     TextResourceContents::new(
116///         format!("res://{name}"),
117///         format!("Some details about resource: {name}"))
118/// }
119/// ```
120///
121/// # Full Example
122/// ```ignore
123/// use neva::prelude::*;
124///
125/// #[resource(
126///     uri = "res://{name}",
127///     title = "Read resource",
128///     descr = "Some details about resource",
129///     mime = "text/plain",
130///     roles = ["user"],
131///     permissions = ["read"],
132///     annotations = r#"{
133///         "audience": ["user"],
134///         "priority": 1.0
135///     }"#
136/// )]
137/// async fn get_res(name: String) -> TextResourceContents {
138///     TextResourceContents::new(
139///         format!("res://{name}"),
140///         format!("Some details about resource: {name}"))
141/// }
142/// ```
143#[proc_macro_attribute]
144#[cfg(feature = "server")]
145pub fn resource(attr: TokenStream, item: TokenStream) -> TokenStream {
146    let function = parse_macro_input!(item as syn::ItemFn);
147    let attr = parse_macro_input!(
148        attr with Punctuated::<syn::Meta, Token![,]>::parse_terminated
149    );
150    server::resource::expand_resource(&attr, &function)
151        .unwrap_or_else(syn::Error::into_compile_error)
152        .into()
153}
154
155/// Maps the list of resources function
156#[proc_macro_attribute]
157#[cfg(feature = "server")]
158pub fn resources(attr: TokenStream, item: TokenStream) -> TokenStream {
159    let function = parse_macro_input!(item as syn::ItemFn);
160    let attr = parse_macro_input!(
161        attr with Punctuated::<syn::Meta, Token![,]>::parse_terminated
162    );
163    server::resource::expand_resources(&attr, &function)
164        .unwrap_or_else(syn::Error::into_compile_error)
165        .into()
166}
167
168/// Maps the function to a prompt
169///
170/// # Parameters
171/// * `title` - Prompt title.
172/// * `descr` - Prompt description.
173/// * `args` - Prompt arguments.
174/// * `no_args` - Explicitly disables argument generation if it's not set in `args`.
175/// * `middleware` - Middleware list to apply to the prompt.
176/// * `roles` & `permissions` - Define which users can read the resource when using Streamable HTTP transport with OAuth.
177///
178/// # Simple Example
179/// ```ignore
180/// use neva::prelude::*;
181///
182/// #[prompt(descr = "Analyze code for potential improvements"]
183/// async fn analyze_code(lang: String) -> PromptMessage {
184///     PromptMessage::user()
185///         .with(format!("Language: {lang}"))
186/// }
187/// ```
188///
189/// # Full Example
190/// ```ignore
191/// use neva::prelude::*;
192///
193/// #[prompt(
194///     title = "Code Analyzer",
195///     descr = "Analyze code for potential improvements",
196///     roles = ["user"],
197///     permissions = ["read"],
198///     args = r#"[
199///         {
200///             "name": "lang",
201///             "description": "A language to use",
202///             "required": true
203///         }    
204///     ]"#
205/// )]
206/// async fn analyze_code(lang: String) -> PromptMessage {
207///     PromptMessage::user()
208///         .with(format!("Language: {lang}"))
209/// }
210/// ```
211#[proc_macro_attribute]
212#[cfg(feature = "server")]
213pub fn prompt(attr: TokenStream, item: TokenStream) -> TokenStream {
214    let function = parse_macro_input!(item as syn::ItemFn);
215    let attr = parse_macro_input!(
216        attr with Punctuated::<syn::Meta, Token![,]>::parse_terminated
217    );
218    server::prompt::expand(&attr, &function)
219        .unwrap_or_else(syn::Error::into_compile_error)
220        .into()
221}
222
223/// Maps the function to a command handler
224///
225/// # Parameters
226/// * `command` - Command name.
227/// * `middleware` - Middleware list to apply to the command.
228///
229/// # Example
230/// ```ignore
231/// use neva::prelude::*;
232///
233/// #[handler(command = "ping")]
234/// async fn ping_handler() {
235///     println!("pong");
236/// }
237/// ```
238#[proc_macro_attribute]
239#[cfg(feature = "server")]
240pub fn handler(attr: TokenStream, item: TokenStream) -> TokenStream {
241    let function = parse_macro_input!(item as syn::ItemFn);
242    let attr = parse_macro_input!(
243        attr with Punctuated::<syn::Meta, Token![,]>::parse_terminated
244    );
245    server::expand_handler(&attr, &function)
246        .unwrap_or_else(syn::Error::into_compile_error)
247        .into()
248}
249
250/// Maps the completion function
251#[proc_macro_attribute]
252#[cfg(feature = "server")]
253pub fn completion(attr: TokenStream, item: TokenStream) -> TokenStream {
254    let function = parse_macro_input!(item as syn::ItemFn);
255    let attr = parse_macro_input!(
256        attr with Punctuated::<syn::Meta, Token![,]>::parse_terminated
257    );
258    server::expand_completion(&attr, &function)
259        .unwrap_or_else(syn::Error::into_compile_error)
260        .into()
261}
262
263/// Maps the elicitation handler function
264///
265/// # Example
266/// ```ignore
267/// use neva::prelude::*;
268///
269/// #[json_schema(ser)]
270/// struct Contact {
271///     name: String,
272///     email: String,
273///     age: u32,
274/// }
275///
276/// #[elicitation]
277/// async fn elicitation_handler(params: ElicitRequestParams) -> impl Into<ElicitResult> {
278///     let contact = Contact {
279///         name: "John".to_string(),
280///         email: "john@email.com".to_string(),
281///         age: 30,
282///     };
283///     elicitation::Validator::new(params)
284///         .validate(contact)
285/// }
286/// ```
287#[proc_macro_attribute]
288#[cfg(feature = "client")]
289pub fn elicitation(_: TokenStream, item: TokenStream) -> TokenStream {
290    let function = parse_macro_input!(item as syn::ItemFn);
291    client::expand_elicitation(&function)
292        .unwrap_or_else(syn::Error::into_compile_error)
293        .into()
294}
295
296/// Maps the sampling handler function
297///
298/// # Example
299/// ```ignore
300/// use neva::prelude::*;
301///
302/// #[sampling]
303/// async fn sampling_handler(params: CreateMessageRequestParams) -> CreateMessageResult {
304///     CreateMessageResult::assistant()
305///         .with_model("o3-mini")
306///         .with_content("Some response")
307/// }
308/// ```
309#[proc_macro_attribute]
310#[cfg(feature = "client")]
311pub fn sampling(_: TokenStream, item: TokenStream) -> TokenStream {
312    let function = parse_macro_input!(item as syn::ItemFn);
313    client::expand_sampling(&function)
314        .unwrap_or_else(syn::Error::into_compile_error)
315        .into()
316}
317
318/// Provides a utility to extract a JSON schema of this type
319///
320/// # Optional parameters
321/// * `all` - Applies also `derive(Debug, serde::Serialize, serde::Deserialize)`.
322/// * `serde` - Applies also `derive(serde::Serialize, serde::Deserialize)`.
323/// * `ser` - Applies also `derive(serde::Serialize)`.
324/// * `de` - Applies also `derive(serde::Deserialize)`.
325///
326/// # Example
327/// ```ignore
328/// use neva::prelude::*;
329///
330/// #[json_schema(ser)]
331/// struct Results {
332///     message: String,
333/// }
334/// ```
335#[proc_macro_attribute]
336pub fn json_schema(attr: TokenStream, item: TokenStream) -> TokenStream {
337    let input = parse_macro_input!(item as syn::DeriveInput);
338    let attr = parse_macro_input!(
339        attr with Punctuated::<syn::Path, Token![,]>::parse_terminated
340    );
341    shared::expand_json_schema(&attr, &input)
342        .unwrap_or_else(syn::Error::into_compile_error)
343        .into()
344}