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}