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