pmcp_code_mode_derive/lib.rs
1// Allow needless_continue from darling's generated derive code
2#![allow(clippy::needless_continue)]
3
4//! Derive macro for Code Mode validation and execution in MCP servers.
5//!
6//! Provides `#[derive(CodeMode)]` which generates a `register_code_mode_tools`
7//! method that registers `validate_code` and `execute_code` tools on a
8//! [`pmcp::ServerBuilder`].
9//!
10//! # Field Name Convention (v0.1.0)
11//!
12//! The macro identifies required fields by **fixed well-known names**. This is
13//! the v0.1.0 contract -- the field names are the API:
14//!
15//! | Field Name | Required Type | Purpose |
16//! |------------|---------------|---------|
17//! | `code_mode_config` | `CodeModeConfig` | Validation pipeline configuration |
18//! | `token_secret` | `TokenSecret` | HMAC signing secret |
19//! | `policy_evaluator` | `Arc<dyn PolicyEvaluator>` or `Arc<P>` | Policy evaluation |
20//! | `code_executor` | `Arc<dyn CodeExecutor>` or `Arc<E>` | Code execution |
21//!
22//! If any required field is missing, the macro emits a **single** compile error
23//! listing all absent fields.
24//!
25//! # Struct-Level Attributes (v0.2.0)
26//!
27//! | Attribute | Type | Default | Purpose |
28//! |-----------|------|---------|---------|
29//! | `context_from` | `String` | (none) | Method name returning `ValidationContext` |
30//! | `language` | `String` | `"graphql"` | Selects validation path and tool metadata |
31//!
32//! Supported `language` values:
33//!
34//! | Value | Validation Method | Feature Required |
35//! |-------|-------------------|------------------|
36//! | `"graphql"` (default) | `validate_graphql_query_async` | *(none)* |
37//! | `"javascript"` / `"js"` | `validate_javascript_code` | `openapi-code-mode` |
38//! | `"sql"` | `validate_sql_query` | `sql-code-mode` |
39//! | `"mcp"` | `validate_mcp_composition` | `mcp-code-mode` |
40//!
41//! When `context_from` is specified, `register_code_mode_tools` requires
42//! `self: &Arc<Self>` and the generated handler calls `self.parent.{method}(&extra)`
43//! to obtain real `ValidationContext` bound to the current user/session.
44//!
45//! When `context_from` is omitted, `register_code_mode_tools` uses `&self` (no Arc
46//! required) with placeholder context values and a `#[deprecated]` warning guiding
47//! users toward the production path.
48//!
49//! # Generated Code
50//!
51//! The macro generates:
52//!
53//! 1. A `register_code_mode_tools` method on the struct that takes a
54//! `ServerBuilder` **by value** and returns it (by-value fluent pattern).
55//! 2. Two internal handler structs (`ValidateCodeHandler` and
56//! `ExecuteCodeHandler`) that implement `pmcp::ToolHandler`.
57//! 3. A `Send + Sync` compile-time assertion (per D-08).
58//!
59//! # Examples
60//!
61//! **Production (with `context_from`):**
62//! ```rust,ignore
63//! #[derive(CodeMode)]
64//! #[code_mode(context_from = "get_context", language = "graphql")]
65//! struct MyServer {
66//! code_mode_config: CodeModeConfig,
67//! token_secret: TokenSecret,
68//! policy_evaluator: Arc<NoopPolicyEvaluator>,
69//! code_executor: Arc<MyExecutor>,
70//! }
71//!
72//! impl MyServer {
73//! fn get_context(&self, extra: &RequestHandlerExtra) -> ValidationContext {
74//! ValidationContext::new("user-1", "session-1", "schema-v1", "perms-v1")
75//! }
76//! }
77//!
78//! // Generated: MyServer::register_code_mode_tools(self: &Arc<Self>, builder) -> ServerBuilder
79//! ```
80//!
81//! **Testing (without `context_from`, deprecated placeholder path):**
82//! ```rust,ignore
83//! #[derive(CodeMode)]
84//! struct MyServer {
85//! code_mode_config: CodeModeConfig,
86//! token_secret: TokenSecret,
87//! policy_evaluator: Arc<NoopPolicyEvaluator>,
88//! code_executor: Arc<MyExecutor>,
89//! }
90//!
91//! // Generated: #[deprecated] MyServer::register_code_mode_tools(&self, builder) -> ServerBuilder
92//! ```
93
94use darling::FromDeriveInput;
95use proc_macro::TokenStream;
96use quote::quote;
97use syn::{parse_macro_input, DeriveInput};
98
99/// Required field names for the Code Mode derive macro.
100const REQUIRED_FIELDS: &[&str] = &[
101 "code_mode_config",
102 "token_secret",
103 "policy_evaluator",
104 "code_executor",
105];
106
107/// Parsed attributes from `#[derive(CodeMode)]`.
108///
109/// Struct-level attributes (v0.2.0):
110/// - `context_from`: Optional method name for extracting `ValidationContext`.
111/// When specified, the generated `register_code_mode_tools` requires `self: &Arc<Self>`.
112/// - `language`: Code language for tool metadata. Defaults to `"graphql"`.
113#[derive(Debug, FromDeriveInput)]
114#[darling(attributes(code_mode))]
115struct CodeModeOpts {
116 ident: syn::Ident,
117 data: darling::ast::Data<(), CodeModeField>,
118 /// Optional method name for extracting `ValidationContext` from the struct.
119 /// When specified, the generated registration method requires `self: &Arc<Self>`.
120 #[darling(default)]
121 context_from: Option<String>,
122 /// Code language — selects both the validation method and tool metadata.
123 /// Supported: `"graphql"` (default), `"javascript"`/`"js"`, `"sql"`, `"mcp"`.
124 /// Non-default languages require their respective feature flag on `pmcp-code-mode`.
125 #[darling(default)]
126 language: Option<String>,
127}
128
129/// A single field parsed from the struct.
130#[derive(Debug, Clone, darling::FromField)]
131#[darling(attributes(code_mode))]
132struct CodeModeField {
133 ident: Option<syn::Ident>,
134}
135
136/// Derive macro that generates `register_code_mode_tools` for Code Mode servers.
137///
138/// Requires a named struct with four well-known fields:
139/// `code_mode_config`, `token_secret`, `policy_evaluator`, `code_executor`.
140///
141/// See [crate-level documentation](crate) for the full field name convention.
142#[proc_macro_derive(CodeMode, attributes(code_mode))]
143pub fn code_mode_derive(input: TokenStream) -> TokenStream {
144 let input = parse_macro_input!(input as DeriveInput);
145 expand_code_mode(&input)
146 .unwrap_or_else(|err| err.to_compile_error())
147 .into()
148}
149
150/// Core expansion logic for `#[derive(CodeMode)]`.
151fn expand_code_mode(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
152 let opts = CodeModeOpts::from_derive_input(input)
153 .map_err(|e| syn::Error::new_spanned(input, e.to_string()))?;
154
155 let struct_name = &opts.ident;
156
157 // Extract named fields
158 let fields = match &opts.data {
159 darling::ast::Data::Struct(ref fields) => &fields.fields,
160 darling::ast::Data::Enum(_) => {
161 return Err(syn::Error::new_spanned(
162 input,
163 "#[derive(CodeMode)] can only be applied to structs with named fields",
164 ));
165 },
166 };
167
168 let field_names: Vec<String> = fields
169 .iter()
170 .filter_map(|f| f.ident.as_ref().map(|i| i.to_string()))
171 .collect();
172
173 // Check for missing required fields
174 let missing: Vec<&str> = REQUIRED_FIELDS
175 .iter()
176 .filter(|&&name| !field_names.contains(&name.to_string()))
177 .copied()
178 .collect();
179
180 if !missing.is_empty() {
181 let all_required = REQUIRED_FIELDS.join(", ");
182 let missing_msgs: Vec<String> = missing
183 .iter()
184 .map(|&name| {
185 let type_hint = match name {
186 "code_mode_config" => "CodeModeConfig",
187 "token_secret" => "TokenSecret",
188 "policy_evaluator" => "Arc<dyn PolicyEvaluator>",
189 "code_executor" => "Arc<dyn CodeExecutor>",
190 _ => "unknown",
191 };
192 format!(
193 "#[derive(CodeMode)] requires field `{name}` (type: {type_hint}).\n\
194 Required fields: {all_required}"
195 )
196 })
197 .collect();
198 let msg = missing_msgs.join("\n\n");
199 return Err(syn::Error::new_spanned(&input.ident, msg));
200 }
201
202 // Generate the handler module name to avoid collision (snake_case to suppress warnings)
203 let mod_name = syn::Ident::new(
204 &format!(
205 "__code_mode_impl_{}",
206 struct_name.to_string().to_lowercase()
207 ),
208 struct_name.span(),
209 );
210
211 // Extract and validate language (defaults to "graphql").
212 // Known values mirror pmcp_code_mode::CodeLanguage — keep in sync.
213 let language = opts.language.as_deref().unwrap_or("graphql");
214 let language_lit = syn::LitStr::new(language, struct_name.span());
215
216 let validation_call = gen_validation_call(language, &input.ident)?;
217
218 // Branch code generation based on context_from presence
219 let expanded = if let Some(ref method_name) = opts.context_from {
220 // Validate that context_from is a valid Rust identifier
221 if method_name.is_empty() || syn::parse_str::<syn::Ident>(method_name).is_err() {
222 return Err(syn::Error::new_spanned(
223 &input.ident,
224 format!("`context_from = \"{method_name}\"` is not a valid Rust identifier"),
225 ));
226 }
227 let method_ident = syn::Ident::new(method_name, struct_name.span());
228 expand_with_context_from(
229 struct_name,
230 &mod_name,
231 &language_lit,
232 &method_ident,
233 &validation_call,
234 )
235 } else {
236 // --- default path: placeholder context with deprecation warning ---
237 expand_without_context_from(struct_name, &mod_name, &language_lit, &validation_call)
238 };
239
240 Ok(expanded)
241}
242
243/// Generate the validation call token stream for the given language.
244///
245/// Each language maps to a specific `ValidationPipeline` method. The method may be
246/// sync or async — the generated handler is always async, so sync methods work fine.
247///
248/// # Supported Languages
249///
250/// | Language | Method | Async | Feature |
251/// |----------|--------|-------|---------|
252/// | `graphql` | `validate_graphql_query_async` | yes | *(none)* |
253/// | `javascript`/`js` | `validate_javascript_code` | no | `openapi-code-mode` |
254/// | `sql` | `validate_sql_query` | no | `sql-code-mode` |
255/// | `mcp` | `validate_mcp_composition` | yes | `mcp-code-mode` |
256///
257/// To add a new language: add a match arm here and a variant to `CodeLanguage` in
258/// `pmcp-code-mode/src/types.rs`.
259fn gen_validation_call(
260 language: &str,
261 error_span: &syn::Ident,
262) -> Result<proc_macro2::TokenStream, syn::Error> {
263 let map_err = quote! {
264 .map_err(|e| pmcp::Error::Internal(format!("Validation error: {}", e)))?
265 };
266 match language {
267 "graphql" => Ok(quote! {
268 self.pipeline.validate_graphql_query_async(code, &context).await #map_err
269 }),
270 "javascript" | "js" => Ok(quote! {
271 self.pipeline.validate_javascript_code_async(code, &context).await #map_err
272 }),
273 "sql" => Ok(quote! {
274 self.pipeline.validate_sql_query(code, &context) #map_err
275 }),
276 "mcp" => Ok(quote! {
277 self.pipeline.validate_mcp_composition(code, &context).await #map_err
278 }),
279 other => Err(syn::Error::new_spanned(
280 error_span,
281 format!(
282 "`language = \"{other}\"` is not a supported language. \
283 Supported values: \"graphql\" (default), \"javascript\" (requires `openapi-code-mode`), \
284 \"sql\" (requires `sql-code-mode`), \"mcp\" (requires `mcp-code-mode`)"
285 ),
286 )),
287 }
288}
289
290/// Generate code when `context_from` is specified.
291///
292/// The registration method requires `self: &Arc<Self>` and the validate handler
293/// calls `self.parent.{method_ident}(&extra)` for real `ValidationContext`.
294fn expand_with_context_from(
295 struct_name: &syn::Ident,
296 mod_name: &syn::Ident,
297 language_lit: &syn::LitStr,
298 method_ident: &syn::Ident,
299 validation_call: &proc_macro2::TokenStream,
300) -> proc_macro2::TokenStream {
301 quote! {
302 // Send + Sync compile-time assertion (D-08)
303 const _: fn() = || {
304 fn assert_send_sync<T: Send + Sync>() {}
305 assert_send_sync::<#struct_name>();
306 };
307
308 #[doc(hidden)]
309 #[allow(non_snake_case)]
310 mod #mod_name {
311 use super::*;
312 use std::sync::Arc;
313 // Import TokenGenerator trait to bring verify/verify_code into scope
314 use pmcp_code_mode::TokenGenerator as _;
315
316 /// Internal state for the `validate_code` tool handler.
317 pub(super) struct ValidateCodeHandler {
318 pub(super) pipeline: Arc<pmcp_code_mode::ValidationPipeline>,
319 pub(super) config: pmcp_code_mode::CodeModeConfig,
320 pub(super) parent: Arc<#struct_name>,
321 }
322
323 #[pmcp_code_mode::async_trait]
324 impl pmcp::ToolHandler for ValidateCodeHandler {
325 async fn handle(
326 &self,
327 args: serde_json::Value,
328 extra: pmcp::RequestHandlerExtra,
329 ) -> pmcp::Result<serde_json::Value> {
330 let input: pmcp_code_mode::ValidateCodeInput =
331 serde_json::from_value(args).map_err(|e| {
332 pmcp::Error::Internal(format!("Invalid arguments: {}", e))
333 })?;
334
335 let code = input.code.trim();
336 let dry_run = input.dry_run.unwrap_or(false);
337
338 // Real ValidationContext from user-defined method
339 let context = self.parent.#method_ident(&extra);
340
341 let result = #validation_call;
342
343 let mut response = pmcp_code_mode::ValidationResponse::from_result(result);
344 if response.result.is_valid {
345 if dry_run {
346 response.result.approval_token = None;
347 }
348 let risk = response.result.risk_level;
349 response = response.with_auto_approved(self.config.should_auto_approve(risk));
350 }
351
352 let (json, _is_error) = response.to_json_response();
353 Ok(json)
354 }
355
356 fn metadata(&self) -> Option<pmcp::types::ToolInfo> {
357 Some(pmcp_code_mode::CodeModeToolBuilder::new(#language_lit).build_validate_tool())
358 }
359 }
360
361 /// Internal state for the `execute_code` tool handler.
362 pub(super) struct ExecuteCodeHandler<E: pmcp_code_mode::CodeExecutor + 'static> {
363 pub(super) pipeline: Arc<pmcp_code_mode::ValidationPipeline>,
364 pub(super) executor: Arc<E>,
365 }
366
367 #[pmcp_code_mode::async_trait]
368 impl<E: pmcp_code_mode::CodeExecutor + 'static> pmcp::ToolHandler for ExecuteCodeHandler<E> {
369 async fn handle(
370 &self,
371 args: serde_json::Value,
372 _extra: pmcp::RequestHandlerExtra,
373 ) -> pmcp::Result<serde_json::Value> {
374 let input: pmcp_code_mode::ExecuteCodeInput =
375 serde_json::from_value(args).map_err(|e| {
376 pmcp::Error::Internal(format!("Invalid arguments: {}", e))
377 })?;
378
379 let code = input.code.trim();
380
381 // Verify the approval token
382 let token_gen = self.pipeline.token_generator();
383 let token = pmcp_code_mode::ApprovalToken::decode(&input.approval_token)
384 .map_err(|e| pmcp::Error::Internal(
385 format!("Invalid approval token: {}", e),
386 ))?;
387
388 // Verify token signature and expiry
389 token_gen.verify(&token)
390 .map_err(|e| pmcp::Error::Internal(
391 format!("Token verification failed: {}", e),
392 ))?;
393
394 // Verify code matches the token's code hash
395 token_gen.verify_code(code, &token)
396 .map_err(|e| pmcp::Error::Internal(
397 format!("Code verification failed: {}", e),
398 ))?;
399
400 // Execute the validated code
401 let result = self.executor.execute(code, input.variables.as_ref()).await
402 .map_err(|e| pmcp::Error::Internal(
403 format!("Execution error: {}", e),
404 ))?;
405
406 Ok(result)
407 }
408
409 fn metadata(&self) -> Option<pmcp::types::ToolInfo> {
410 Some(pmcp_code_mode::CodeModeToolBuilder::new(#language_lit).build_execute_tool())
411 }
412 }
413 }
414
415 impl #struct_name {
416 /// Register Code Mode tools (`validate_code` + `execute_code`) on the builder.
417 ///
418 /// Uses the `context_from` method to extract real `ValidationContext` from
419 /// each request. Requires `self: &Arc<Self>` to share the server reference
420 /// with the generated handler.
421 ///
422 /// # Errors
423 ///
424 /// Returns [`pmcp_code_mode::TokenError`] if the `token_secret` is too short
425 /// for secure HMAC token generation.
426 ///
427 /// # Example
428 ///
429 /// ```rust,ignore
430 /// let server = Arc::new(my_server);
431 /// let builder = server.register_code_mode_tools(Server::builder())?;
432 /// ```
433 pub fn register_code_mode_tools(
434 self: &std::sync::Arc<Self>,
435 builder: pmcp::ServerBuilder,
436 ) -> Result<pmcp::ServerBuilder, pmcp_code_mode::TokenError> {
437 let pipeline = std::sync::Arc::new(
438 pmcp_code_mode::ValidationPipeline::from_token_secret_with_policy(
439 self.code_mode_config.clone(),
440 &self.token_secret,
441 std::sync::Arc::clone(&self.policy_evaluator) as std::sync::Arc<dyn pmcp_code_mode::PolicyEvaluator>,
442 )?
443 );
444
445 let validate_handler = #mod_name::ValidateCodeHandler {
446 pipeline: std::sync::Arc::clone(&pipeline),
447 config: self.code_mode_config.clone(),
448 parent: std::sync::Arc::clone(self),
449 };
450
451 let execute_handler = #mod_name::ExecuteCodeHandler {
452 pipeline,
453 executor: std::sync::Arc::clone(&self.code_executor),
454 };
455
456 Ok(builder
457 .tool("validate_code", validate_handler)
458 .tool("execute_code", execute_handler))
459 }
460 }
461 }
462}
463
464/// Generate code when `context_from` is NOT specified (backward-compatible path).
465///
466/// The registration method uses `&self` (no `Arc` needed) and the validate handler
467/// uses placeholder `ValidationContext` values with a `#[deprecated]` warning guiding
468/// users toward the `context_from` attribute for production use.
469fn expand_without_context_from(
470 struct_name: &syn::Ident,
471 mod_name: &syn::Ident,
472 language_lit: &syn::LitStr,
473 validation_call: &proc_macro2::TokenStream,
474) -> proc_macro2::TokenStream {
475 quote! {
476 // Send + Sync compile-time assertion (D-08)
477 const _: fn() = || {
478 fn assert_send_sync<T: Send + Sync>() {}
479 assert_send_sync::<#struct_name>();
480 };
481
482 #[doc(hidden)]
483 #[allow(non_snake_case)]
484 mod #mod_name {
485 use super::*;
486 use std::sync::Arc;
487 // Import TokenGenerator trait to bring verify/verify_code into scope
488 use pmcp_code_mode::TokenGenerator as _;
489
490 /// Internal state for the `validate_code` tool handler.
491 pub(super) struct ValidateCodeHandler {
492 pub(super) pipeline: Arc<pmcp_code_mode::ValidationPipeline>,
493 pub(super) config: pmcp_code_mode::CodeModeConfig,
494 }
495
496 #[pmcp_code_mode::async_trait]
497 impl pmcp::ToolHandler for ValidateCodeHandler {
498 async fn handle(
499 &self,
500 args: serde_json::Value,
501 _extra: pmcp::RequestHandlerExtra,
502 ) -> pmcp::Result<serde_json::Value> {
503 let input: pmcp_code_mode::ValidateCodeInput =
504 serde_json::from_value(args).map_err(|e| {
505 pmcp::Error::Internal(format!("Invalid arguments: {}", e))
506 })?;
507
508 let code = input.code.trim();
509 let dry_run = input.dry_run.unwrap_or(false);
510
511 // WARNING: These are PLACEHOLDER values. The validation context
512 // uses static strings, so approval tokens are NOT bound to a
513 // specific user, session, or schema version. An attacker who
514 // obtains a valid token can replay it across different users and
515 // sessions until it expires.
516 //
517 // Use `#[code_mode(context_from = "method_name")]` for production.
518 let context = pmcp_code_mode::ValidationContext::new(
519 "anonymous",
520 "session",
521 "schema",
522 "perms",
523 );
524
525 let result = #validation_call;
526
527 let mut response = pmcp_code_mode::ValidationResponse::from_result(result);
528 if response.result.is_valid {
529 if dry_run {
530 response.result.approval_token = None;
531 }
532 let risk = response.result.risk_level;
533 response = response.with_auto_approved(self.config.should_auto_approve(risk));
534 }
535
536 let (json, _is_error) = response.to_json_response();
537 Ok(json)
538 }
539
540 fn metadata(&self) -> Option<pmcp::types::ToolInfo> {
541 Some(pmcp_code_mode::CodeModeToolBuilder::new(#language_lit).build_validate_tool())
542 }
543 }
544
545 /// Internal state for the `execute_code` tool handler.
546 pub(super) struct ExecuteCodeHandler<E: pmcp_code_mode::CodeExecutor + 'static> {
547 pub(super) pipeline: Arc<pmcp_code_mode::ValidationPipeline>,
548 pub(super) executor: Arc<E>,
549 }
550
551 #[pmcp_code_mode::async_trait]
552 impl<E: pmcp_code_mode::CodeExecutor + 'static> pmcp::ToolHandler for ExecuteCodeHandler<E> {
553 async fn handle(
554 &self,
555 args: serde_json::Value,
556 _extra: pmcp::RequestHandlerExtra,
557 ) -> pmcp::Result<serde_json::Value> {
558 let input: pmcp_code_mode::ExecuteCodeInput =
559 serde_json::from_value(args).map_err(|e| {
560 pmcp::Error::Internal(format!("Invalid arguments: {}", e))
561 })?;
562
563 let code = input.code.trim();
564
565 // Verify the approval token
566 let token_gen = self.pipeline.token_generator();
567 let token = pmcp_code_mode::ApprovalToken::decode(&input.approval_token)
568 .map_err(|e| pmcp::Error::Internal(
569 format!("Invalid approval token: {}", e),
570 ))?;
571
572 // Verify token signature and expiry
573 token_gen.verify(&token)
574 .map_err(|e| pmcp::Error::Internal(
575 format!("Token verification failed: {}", e),
576 ))?;
577
578 // Verify code matches the token's code hash
579 token_gen.verify_code(code, &token)
580 .map_err(|e| pmcp::Error::Internal(
581 format!("Code verification failed: {}", e),
582 ))?;
583
584 // Execute the validated code
585 let result = self.executor.execute(code, input.variables.as_ref()).await
586 .map_err(|e| pmcp::Error::Internal(
587 format!("Execution error: {}", e),
588 ))?;
589
590 Ok(result)
591 }
592
593 fn metadata(&self) -> Option<pmcp::types::ToolInfo> {
594 Some(pmcp_code_mode::CodeModeToolBuilder::new(#language_lit).build_execute_tool())
595 }
596 }
597 }
598
599 impl #struct_name {
600 /// Register Code Mode tools (`validate_code` + `execute_code`) on the builder.
601 ///
602 /// **Deprecated:** Uses placeholder `ValidationContext` values. Use
603 /// `#[code_mode(context_from = "method_name")]` for production to bind
604 /// approval tokens to real user identity and session.
605 ///
606 /// # Errors
607 ///
608 /// Returns [`pmcp_code_mode::TokenError`] if the `token_secret` is too short
609 /// for secure HMAC token generation.
610 ///
611 /// # Example
612 ///
613 /// ```rust,ignore
614 /// #[allow(deprecated)]
615 /// let builder = server.register_code_mode_tools(Server::builder())?;
616 /// ```
617 #[deprecated(note = "Use #[code_mode(context_from = \"method_name\")] for production. This uses placeholder ValidationContext.")]
618 pub fn register_code_mode_tools(
619 &self,
620 builder: pmcp::ServerBuilder,
621 ) -> Result<pmcp::ServerBuilder, pmcp_code_mode::TokenError> {
622 let pipeline = std::sync::Arc::new(
623 pmcp_code_mode::ValidationPipeline::from_token_secret_with_policy(
624 self.code_mode_config.clone(),
625 &self.token_secret,
626 std::sync::Arc::clone(&self.policy_evaluator) as std::sync::Arc<dyn pmcp_code_mode::PolicyEvaluator>,
627 )?
628 );
629
630 let validate_handler = #mod_name::ValidateCodeHandler {
631 pipeline: std::sync::Arc::clone(&pipeline),
632 config: self.code_mode_config.clone(),
633 };
634
635 let execute_handler = #mod_name::ExecuteCodeHandler {
636 pipeline,
637 executor: std::sync::Arc::clone(&self.code_executor),
638 };
639
640 Ok(builder
641 .tool("validate_code", validate_handler)
642 .tool("execute_code", execute_handler))
643 }
644 }
645 }
646}