1#![deny(rustdoc::broken_intra_doc_links, rustdoc::private_intra_doc_links)]
5
6use std::ffi::{c_char, c_void};
13use std::marker::{PhantomData, PhantomPinned};
14use std::panic::{AssertUnwindSafe, catch_unwind};
15use std::ptr;
16use std::sync::Mutex;
17
18pub use nemo_relay_types::Json;
19pub use nemo_relay_types::api::event::{
20 CategoryProfile, DataSchema, Event, EventCategory, EventSanitizeFields, PendingMarkSpec,
21 ScopeCategory,
22};
23pub use nemo_relay_types::api::llm::{LlmAttributes, LlmRequest, LlmRequestInterceptOutcome};
24pub use nemo_relay_types::api::scope::{HandleAttributes, ScopeAttributes, ScopeType};
25pub use nemo_relay_types::api::tool::{ToolAttributes, ToolExecutionInterceptOutcome};
26pub use nemo_relay_types::codec::optimization::{
27 LlmOptimizationContribution, LlmOptimizationEvidenceQuality, LlmOptimizationKind,
28 LlmOptimizationModel, LlmOptimizationModelTransition, LlmOptimizationPayload,
29 LlmOptimizationSummary, LlmOptimizationSummaryStatus, LlmOptimizationTokenImpact,
30 LlmOptimizationTokens,
31};
32pub use nemo_relay_types::codec::request::AnnotatedLlmRequest;
33pub use nemo_relay_types::codec::response::AnnotatedLlmResponse;
34pub use nemo_relay_types::plugin::{ConfigDiagnostic, DiagnosticLevel};
35use serde::{Serialize, de::DeserializeOwned};
36use serde_json::Map;
37
38pub const NEMO_RELAY_NATIVE_ABI_VERSION: u32 = 1;
40
41#[repr(i32)]
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub enum NemoRelayStatus {
45 Ok = 0,
47 AlreadyExists = 1,
49 NotFound = 2,
51 ScopeStackEmpty = 3,
53 GuardrailRejected = 4,
55 Internal = 5,
57 NullPointer = 6,
59 InvalidJson = 7,
61 InvalidUtf8 = 8,
63 InvalidArg = 9,
65 StreamEnd = 10,
67}
68
69#[repr(C)]
71pub struct NemoRelayNativeString {
72 _private: [u8; 0],
73 _marker: PhantomData<(*mut u8, PhantomPinned)>,
74}
75
76#[repr(C)]
78pub struct NemoRelayNativePluginContext {
79 _private: [u8; 0],
80 _marker: PhantomData<(*mut u8, PhantomPinned)>,
81}
82
83#[repr(C)]
85pub struct NemoRelayNativeScopeHandle {
86 _private: [u8; 0],
87 _marker: PhantomData<(*mut u8, PhantomPinned)>,
88}
89
90#[repr(C)]
92pub struct NemoRelayNativeScopeStack {
93 _private: [u8; 0],
94 _marker: PhantomData<(*mut u8, PhantomPinned)>,
95}
96
97#[repr(C)]
99pub struct NemoRelayNativeScopeStackBinding {
100 _private: [u8; 0],
101 _marker: PhantomData<(*mut u8, PhantomPinned)>,
102}
103
104#[repr(i32)]
106#[derive(Debug, Clone, Copy, PartialEq, Eq)]
107pub enum NemoRelayNativeScopeType {
108 Agent = 0,
110 Function = 1,
112 Tool = 2,
114 Llm = 3,
116 Retriever = 4,
118 Embedder = 5,
120 Reranker = 6,
122 Guardrail = 7,
124 Evaluator = 8,
126 Custom = 9,
128 Unknown = 10,
130}
131
132pub type NemoRelayNativeFreeFn = Option<unsafe extern "C" fn(user_data: *mut c_void)>;
134
135pub type NemoRelayNativeWithScopeStackCb =
137 unsafe extern "C" fn(user_data: *mut c_void) -> NemoRelayStatus;
138
139pub type NemoRelayNativeToolNextFn = unsafe extern "C" fn(
141 args_json: *const NemoRelayNativeString,
142 next_ctx: *mut c_void,
143 out_json: *mut *mut NemoRelayNativeString,
144) -> NemoRelayStatus;
145
146pub type NemoRelayNativeLlmNextFn = unsafe extern "C" fn(
148 request_json: *const NemoRelayNativeString,
149 next_ctx: *mut c_void,
150 out_json: *mut *mut NemoRelayNativeString,
151) -> NemoRelayStatus;
152
153pub type NemoRelayNativeLlmStreamPollFn = unsafe extern "C" fn(
159 user_data: *mut c_void,
160 out_json: *mut *mut NemoRelayNativeString,
161) -> NemoRelayStatus;
162
163pub type NemoRelayNativeLlmStreamCancelFn =
165 Option<unsafe extern "C" fn(user_data: *mut c_void) -> NemoRelayStatus>;
166
167pub type NemoRelayNativeLlmStreamDropFn = Option<unsafe extern "C" fn(user_data: *mut c_void)>;
169
170#[repr(C)]
172pub struct NemoRelayNativeLlmStreamV1 {
173 pub struct_size: usize,
175 pub user_data: *mut c_void,
177 pub next: Option<NemoRelayNativeLlmStreamPollFn>,
179 pub cancel: NemoRelayNativeLlmStreamCancelFn,
181 pub drop: NemoRelayNativeLlmStreamDropFn,
183}
184
185impl Default for NemoRelayNativeLlmStreamV1 {
186 fn default() -> Self {
187 Self {
188 struct_size: std::mem::size_of::<Self>(),
189 user_data: ptr::null_mut(),
190 next: None,
191 cancel: None,
192 drop: None,
193 }
194 }
195}
196
197pub type NemoRelayNativeLlmStreamNextFn = unsafe extern "C" fn(
199 request_json: *const NemoRelayNativeString,
200 next_ctx: *mut c_void,
201 out_stream: *mut NemoRelayNativeLlmStreamV1,
202) -> NemoRelayStatus;
203
204pub type NemoRelayNativeEventSubscriberCb = unsafe extern "C" fn(
206 user_data: *mut c_void,
207 event_json: *const NemoRelayNativeString,
208) -> NemoRelayStatus;
209
210pub type NemoRelayNativeEventSanitizeCb = unsafe extern "C" fn(
212 user_data: *mut c_void,
213 event_json: *const NemoRelayNativeString,
214 fields_json: *const NemoRelayNativeString,
215 out_fields_json: *mut *mut NemoRelayNativeString,
216) -> NemoRelayStatus;
217
218pub type NemoRelayNativeToolJsonCb = unsafe extern "C" fn(
220 user_data: *mut c_void,
221 name: *const NemoRelayNativeString,
222 payload_json: *const NemoRelayNativeString,
223 out_json: *mut *mut NemoRelayNativeString,
224) -> NemoRelayStatus;
225
226pub type NemoRelayNativeToolConditionalCb = unsafe extern "C" fn(
228 user_data: *mut c_void,
229 name: *const NemoRelayNativeString,
230 args_json: *const NemoRelayNativeString,
231 out_reason: *mut *mut NemoRelayNativeString,
232) -> NemoRelayStatus;
233
234pub type NemoRelayNativeToolExecutionCb = unsafe extern "C" fn(
236 user_data: *mut c_void,
237 name: *const NemoRelayNativeString,
238 args_json: *const NemoRelayNativeString,
239 next_fn: NemoRelayNativeToolNextFn,
240 next_ctx: *mut c_void,
241 out_outcome_json: *mut *mut NemoRelayNativeString,
242) -> NemoRelayStatus;
243
244pub type NemoRelayNativeLlmRequestCb = unsafe extern "C" fn(
246 user_data: *mut c_void,
247 request_json: *const NemoRelayNativeString,
248 out_request_json: *mut *mut NemoRelayNativeString,
249) -> NemoRelayStatus;
250
251pub type NemoRelayNativeJsonCb = unsafe extern "C" fn(
253 user_data: *mut c_void,
254 payload_json: *const NemoRelayNativeString,
255 out_json: *mut *mut NemoRelayNativeString,
256) -> NemoRelayStatus;
257
258pub type NemoRelayNativeLlmConditionalCb = unsafe extern "C" fn(
260 user_data: *mut c_void,
261 request_json: *const NemoRelayNativeString,
262 out_reason: *mut *mut NemoRelayNativeString,
263) -> NemoRelayStatus;
264
265pub type NemoRelayNativeLlmRequestInterceptCb = unsafe extern "C" fn(
267 user_data: *mut c_void,
268 name: *const NemoRelayNativeString,
269 request_json: *const NemoRelayNativeString,
270 annotated_json: *const NemoRelayNativeString,
271 out_outcome_json: *mut *mut NemoRelayNativeString,
272) -> NemoRelayStatus;
273
274pub type NemoRelayNativeLlmExecutionCb = unsafe extern "C" fn(
276 user_data: *mut c_void,
277 name: *const NemoRelayNativeString,
278 request_json: *const NemoRelayNativeString,
279 next_fn: NemoRelayNativeLlmNextFn,
280 next_ctx: *mut c_void,
281 out_json: *mut *mut NemoRelayNativeString,
282) -> NemoRelayStatus;
283
284pub type NemoRelayNativeLlmStreamExecutionCb = unsafe extern "C" fn(
286 user_data: *mut c_void,
287 name: *const NemoRelayNativeString,
288 request_json: *const NemoRelayNativeString,
289 next_fn: NemoRelayNativeLlmStreamNextFn,
290 next_ctx: *mut c_void,
291 out_stream: *mut NemoRelayNativeLlmStreamV1,
292) -> NemoRelayStatus;
293
294pub type NemoRelayNativePluginValidateFn = unsafe extern "C" fn(
296 user_data: *mut c_void,
297 plugin_config_json: *const NemoRelayNativeString,
298 out_diagnostics_json: *mut *mut NemoRelayNativeString,
299) -> NemoRelayStatus;
300
301pub type NemoRelayNativePluginRegisterFn = unsafe extern "C" fn(
303 user_data: *mut c_void,
304 plugin_config_json: *const NemoRelayNativeString,
305 ctx: *mut NemoRelayNativePluginContext,
306) -> NemoRelayStatus;
307
308pub type NemoRelayNativePluginDropFn = Option<unsafe extern "C" fn(user_data: *mut c_void)>;
310
311#[repr(C)]
313#[derive(Clone, Copy)]
314pub struct NemoRelayNativeHostApiV1 {
315 pub abi_version: u32,
317 pub struct_size: usize,
319 pub relay_version: *const c_char,
321 pub string_new: unsafe extern "C" fn(
323 data: *const u8,
324 len: usize,
325 out: *mut *mut NemoRelayNativeString,
326 ) -> NemoRelayStatus,
327 pub string_data: unsafe extern "C" fn(value: *const NemoRelayNativeString) -> *const u8,
329 pub string_len: unsafe extern "C" fn(value: *const NemoRelayNativeString) -> usize,
331 pub string_free: unsafe extern "C" fn(value: *mut NemoRelayNativeString),
333 pub last_error_clear: unsafe extern "C" fn(),
335 pub last_error_set: unsafe extern "C" fn(message: *const NemoRelayNativeString),
337 pub plugin_context_register_subscriber: unsafe extern "C" fn(
339 ctx: *mut NemoRelayNativePluginContext,
340 name: *const NemoRelayNativeString,
341 cb: NemoRelayNativeEventSubscriberCb,
342 user_data: *mut c_void,
343 free_fn: NemoRelayNativeFreeFn,
344 ) -> NemoRelayStatus,
345 pub plugin_context_register_tool_sanitize_request_guardrail:
347 unsafe extern "C" fn(
348 ctx: *mut NemoRelayNativePluginContext,
349 name: *const NemoRelayNativeString,
350 priority: i32,
351 cb: NemoRelayNativeToolJsonCb,
352 user_data: *mut c_void,
353 free_fn: NemoRelayNativeFreeFn,
354 ) -> NemoRelayStatus,
355 pub plugin_context_register_tool_sanitize_response_guardrail:
357 unsafe extern "C" fn(
358 ctx: *mut NemoRelayNativePluginContext,
359 name: *const NemoRelayNativeString,
360 priority: i32,
361 cb: NemoRelayNativeToolJsonCb,
362 user_data: *mut c_void,
363 free_fn: NemoRelayNativeFreeFn,
364 ) -> NemoRelayStatus,
365 pub plugin_context_register_tool_conditional_execution_guardrail:
367 unsafe extern "C" fn(
368 ctx: *mut NemoRelayNativePluginContext,
369 name: *const NemoRelayNativeString,
370 priority: i32,
371 cb: NemoRelayNativeToolConditionalCb,
372 user_data: *mut c_void,
373 free_fn: NemoRelayNativeFreeFn,
374 ) -> NemoRelayStatus,
375 pub plugin_context_register_tool_request_intercept: unsafe extern "C" fn(
377 ctx: *mut NemoRelayNativePluginContext,
378 name: *const NemoRelayNativeString,
379 priority: i32,
380 break_chain: bool,
381 cb: NemoRelayNativeToolJsonCb,
382 user_data: *mut c_void,
383 free_fn: NemoRelayNativeFreeFn,
384 )
385 -> NemoRelayStatus,
386 pub plugin_context_register_tool_execution_intercept: unsafe extern "C" fn(
388 ctx: *mut NemoRelayNativePluginContext,
389 name: *const NemoRelayNativeString,
390 priority: i32,
391 cb: NemoRelayNativeToolExecutionCb,
392 user_data: *mut c_void,
393 free_fn: NemoRelayNativeFreeFn,
394 )
395 -> NemoRelayStatus,
396 pub plugin_context_register_llm_sanitize_request_guardrail:
398 unsafe extern "C" fn(
399 ctx: *mut NemoRelayNativePluginContext,
400 name: *const NemoRelayNativeString,
401 priority: i32,
402 cb: NemoRelayNativeLlmRequestCb,
403 user_data: *mut c_void,
404 free_fn: NemoRelayNativeFreeFn,
405 ) -> NemoRelayStatus,
406 pub plugin_context_register_llm_sanitize_response_guardrail:
408 unsafe extern "C" fn(
409 ctx: *mut NemoRelayNativePluginContext,
410 name: *const NemoRelayNativeString,
411 priority: i32,
412 cb: NemoRelayNativeJsonCb,
413 user_data: *mut c_void,
414 free_fn: NemoRelayNativeFreeFn,
415 ) -> NemoRelayStatus,
416 pub plugin_context_register_llm_conditional_execution_guardrail:
418 unsafe extern "C" fn(
419 ctx: *mut NemoRelayNativePluginContext,
420 name: *const NemoRelayNativeString,
421 priority: i32,
422 cb: NemoRelayNativeLlmConditionalCb,
423 user_data: *mut c_void,
424 free_fn: NemoRelayNativeFreeFn,
425 ) -> NemoRelayStatus,
426 pub plugin_context_register_llm_request_intercept: unsafe extern "C" fn(
428 ctx: *mut NemoRelayNativePluginContext,
429 name: *const NemoRelayNativeString,
430 priority: i32,
431 break_chain: bool,
432 cb: NemoRelayNativeLlmRequestInterceptCb,
433 user_data: *mut c_void,
434 free_fn: NemoRelayNativeFreeFn,
435 ) -> NemoRelayStatus,
436 pub plugin_context_register_llm_execution_intercept: unsafe extern "C" fn(
438 ctx: *mut NemoRelayNativePluginContext,
439 name: *const NemoRelayNativeString,
440 priority: i32,
441 cb: NemoRelayNativeLlmExecutionCb,
442 user_data: *mut c_void,
443 free_fn: NemoRelayNativeFreeFn,
444 )
445 -> NemoRelayStatus,
446 pub plugin_context_register_llm_stream_execution_intercept:
448 unsafe extern "C" fn(
449 ctx: *mut NemoRelayNativePluginContext,
450 name: *const NemoRelayNativeString,
451 priority: i32,
452 cb: NemoRelayNativeLlmStreamExecutionCb,
453 user_data: *mut c_void,
454 free_fn: NemoRelayNativeFreeFn,
455 ) -> NemoRelayStatus,
456 pub scope_handle_free: unsafe extern "C" fn(handle: *mut NemoRelayNativeScopeHandle),
458 pub scope_get_current:
460 unsafe extern "C" fn(out: *mut *mut NemoRelayNativeScopeHandle) -> NemoRelayStatus,
461 pub scope_push: unsafe extern "C" fn(
463 name: *const NemoRelayNativeString,
464 scope_type: NemoRelayNativeScopeType,
465 parent: *const NemoRelayNativeScopeHandle,
466 attributes: u32,
467 data_json: *const NemoRelayNativeString,
468 metadata_json: *const NemoRelayNativeString,
469 input_json: *const NemoRelayNativeString,
470 timestamp_unix_micros: *const i64,
471 out: *mut *mut NemoRelayNativeScopeHandle,
472 ) -> NemoRelayStatus,
473 pub scope_pop: unsafe extern "C" fn(
475 handle: *const NemoRelayNativeScopeHandle,
476 output_json: *const NemoRelayNativeString,
477 metadata_json: *const NemoRelayNativeString,
478 timestamp_unix_micros: *const i64,
479 ) -> NemoRelayStatus,
480 pub emit_mark: unsafe extern "C" fn(
482 name: *const NemoRelayNativeString,
483 parent: *const NemoRelayNativeScopeHandle,
484 data_json: *const NemoRelayNativeString,
485 metadata_json: *const NemoRelayNativeString,
486 timestamp_unix_micros: *const i64,
487 ) -> NemoRelayStatus,
488 pub scope_stack_create:
490 unsafe extern "C" fn(out: *mut *mut NemoRelayNativeScopeStack) -> NemoRelayStatus,
491 pub scope_stack_free: unsafe extern "C" fn(stack: *mut NemoRelayNativeScopeStack),
493 pub scope_stack_set_thread:
495 unsafe extern "C" fn(stack: *const NemoRelayNativeScopeStack) -> NemoRelayStatus,
496 pub scope_stack_capture_thread:
498 unsafe extern "C" fn(out: *mut *mut NemoRelayNativeScopeStackBinding) -> NemoRelayStatus,
499 pub scope_stack_restore_thread:
501 unsafe extern "C" fn(binding: *mut NemoRelayNativeScopeStackBinding) -> NemoRelayStatus,
502 pub scope_stack_binding_free:
504 unsafe extern "C" fn(binding: *mut NemoRelayNativeScopeStackBinding),
505 pub scope_stack_active: unsafe extern "C" fn() -> bool,
507 pub scope_stack_with_current: unsafe extern "C" fn(
509 stack: *const NemoRelayNativeScopeStack,
510 cb: NemoRelayNativeWithScopeStackCb,
511 user_data: *mut c_void,
512 ) -> NemoRelayStatus,
513 pub plugin_context_register_mark_sanitize_guardrail: unsafe extern "C" fn(
515 ctx: *mut NemoRelayNativePluginContext,
516 name: *const NemoRelayNativeString,
517 priority: i32,
518 cb: NemoRelayNativeEventSanitizeCb,
519 user_data: *mut c_void,
520 free_fn: NemoRelayNativeFreeFn,
521 )
522 -> NemoRelayStatus,
523 pub plugin_context_register_scope_sanitize_start_guardrail:
525 unsafe extern "C" fn(
526 ctx: *mut NemoRelayNativePluginContext,
527 name: *const NemoRelayNativeString,
528 priority: i32,
529 cb: NemoRelayNativeEventSanitizeCb,
530 user_data: *mut c_void,
531 free_fn: NemoRelayNativeFreeFn,
532 ) -> NemoRelayStatus,
533 pub plugin_context_register_scope_sanitize_end_guardrail:
535 unsafe extern "C" fn(
536 ctx: *mut NemoRelayNativePluginContext,
537 name: *const NemoRelayNativeString,
538 priority: i32,
539 cb: NemoRelayNativeEventSanitizeCb,
540 user_data: *mut c_void,
541 free_fn: NemoRelayNativeFreeFn,
542 ) -> NemoRelayStatus,
543}
544
545unsafe impl Send for NemoRelayNativeHostApiV1 {}
548unsafe impl Sync for NemoRelayNativeHostApiV1 {}
549
550#[repr(C)]
552pub struct NemoRelayNativePluginV1 {
553 pub struct_size: usize,
555 pub plugin_kind: *mut NemoRelayNativeString,
557 pub allows_multiple_components: bool,
559 pub user_data: *mut c_void,
561 pub validate: Option<NemoRelayNativePluginValidateFn>,
563 pub register: Option<NemoRelayNativePluginRegisterFn>,
565 pub drop: NemoRelayNativePluginDropFn,
567}
568
569impl Default for NemoRelayNativePluginV1 {
570 fn default() -> Self {
571 Self {
572 struct_size: std::mem::size_of::<Self>(),
573 plugin_kind: ptr::null_mut(),
574 allows_multiple_components: true,
575 user_data: ptr::null_mut(),
576 validate: None,
577 register: None,
578 drop: None,
579 }
580 }
581}
582
583pub type NemoRelayNativePluginEntry = unsafe extern "C" fn(
585 host: *const NemoRelayNativeHostApiV1,
586 out: *mut NemoRelayNativePluginV1,
587) -> NemoRelayStatus;
588
589pub type Result<T> = std::result::Result<T, String>;
591
592pub type LlmJsonStream = Box<dyn Iterator<Item = Result<Json>> + Send>;
594
595#[derive(Clone)]
597pub struct PluginRuntime {
598 host: NemoRelayNativeHostApiV1,
599}
600
601impl PluginRuntime {
602 pub fn new(host: &NemoRelayNativeHostApiV1) -> Self {
604 Self { host: *host }
605 }
606
607 pub fn host_api(&self) -> &NemoRelayNativeHostApiV1 {
609 &self.host
610 }
611
612 pub fn current_scope(&self) -> Result<ScopeHandle<'_>> {
614 current_scope(&self.host)
615 }
616
617 pub fn push_scope(
619 &self,
620 name: &str,
621 scope_type: ScopeType,
622 data: Option<&Json>,
623 metadata: Option<&Json>,
624 input: Option<&Json>,
625 ) -> Result<ScopeHandle<'_>> {
626 push_scope(&self.host, name, scope_type.into(), data, metadata, input)
627 }
628
629 pub fn pop_scope(
631 &self,
632 handle: &ScopeHandle<'_>,
633 output: Option<&Json>,
634 metadata: Option<&Json>,
635 ) -> Result<()> {
636 pop_scope(&self.host, handle, output, metadata)
637 }
638
639 pub fn scope(
641 &self,
642 name: &str,
643 scope_type: ScopeType,
644 data: Option<&Json>,
645 metadata: Option<&Json>,
646 input: Option<&Json>,
647 ) -> Result<ScopeGuard<'_>> {
648 let handle = self.push_scope(name, scope_type, data, metadata, input)?;
649 Ok(ScopeGuard {
650 runtime: self,
651 handle: Some(handle),
652 })
653 }
654
655 pub fn emit_mark(
657 &self,
658 name: &str,
659 data: Option<&Json>,
660 metadata: Option<&Json>,
661 ) -> Result<()> {
662 emit_mark(&self.host, name, data, metadata)
663 }
664
665 pub fn create_scope_stack(&self) -> Result<ScopeStack<'_>> {
667 create_scope_stack(&self.host)
668 }
669
670 pub fn capture_scope_stack_thread(&self) -> Result<ScopeStackBinding<'_>> {
672 capture_scope_stack_thread(&self.host)
673 }
674
675 pub fn scope_stack_active(&self) -> bool {
677 unsafe { (self.host.scope_stack_active)() }
678 }
679
680 pub fn bind_scope_stack_thread<'a>(
682 &'a self,
683 stack: &'a ScopeStack<'a>,
684 ) -> Result<ThreadScopeStackGuard<'a>> {
685 let previous = self.capture_scope_stack_thread()?;
686 let status = stack.set_thread();
687 if status == NemoRelayStatus::Ok {
688 Ok(ThreadScopeStackGuard {
689 previous: Some(previous),
690 })
691 } else {
692 let _ = previous.restore();
693 Err(format!("scope_stack_set_thread failed: {status:?}"))
694 }
695 }
696}
697
698impl From<ScopeType> for NemoRelayNativeScopeType {
699 fn from(value: ScopeType) -> Self {
700 match value {
701 ScopeType::Agent => Self::Agent,
702 ScopeType::Function => Self::Function,
703 ScopeType::Tool => Self::Tool,
704 ScopeType::Llm => Self::Llm,
705 ScopeType::Retriever => Self::Retriever,
706 ScopeType::Embedder => Self::Embedder,
707 ScopeType::Reranker => Self::Reranker,
708 ScopeType::Guardrail => Self::Guardrail,
709 ScopeType::Evaluator => Self::Evaluator,
710 ScopeType::Custom => Self::Custom,
711 ScopeType::Unknown => Self::Unknown,
712 }
713 }
714}
715
716pub struct ScopeGuard<'a> {
718 runtime: &'a PluginRuntime,
719 handle: Option<ScopeHandle<'a>>,
720}
721
722impl<'a> ScopeGuard<'a> {
723 pub fn handle(&self) -> Option<&ScopeHandle<'a>> {
725 self.handle.as_ref()
726 }
727
728 pub fn close(&mut self, output: Option<&Json>, metadata: Option<&Json>) -> Result<()> {
730 let Some(handle) = self.handle.as_ref() else {
731 return Ok(());
732 };
733 self.runtime.pop_scope(handle, output, metadata)?;
734 self.handle.take();
735 Ok(())
736 }
737}
738
739impl Drop for ScopeGuard<'_> {
740 fn drop(&mut self) {
741 if let Some(handle) = self.handle.take() {
742 let _ = self.runtime.pop_scope(&handle, None, None);
743 }
744 }
745}
746
747pub struct ThreadScopeStackGuard<'a> {
749 previous: Option<ScopeStackBinding<'a>>,
750}
751
752impl ThreadScopeStackGuard<'_> {
753 pub fn restore(mut self) -> Result<()> {
755 let Some(previous) = self.previous.take() else {
756 return Ok(());
757 };
758 let status = previous.restore();
759 if status == NemoRelayStatus::Ok {
760 Ok(())
761 } else {
762 Err(format!("scope_stack_restore_thread failed: {status:?}"))
763 }
764 }
765}
766
767impl Drop for ThreadScopeStackGuard<'_> {
768 fn drop(&mut self) {
769 if let Some(previous) = self.previous.take() {
770 let _ = previous.restore();
771 }
772 }
773}
774
775pub struct ToolNext<'a> {
777 host: &'a NemoRelayNativeHostApiV1,
778 next_fn: NemoRelayNativeToolNextFn,
779 next_ctx: *mut c_void,
780}
781
782impl ToolNext<'_> {
783 pub fn call(&self, args: Json) -> Result<Json> {
785 let args = HostString::from_json(self.host, &args)
786 .ok_or_else(|| "failed to allocate tool next args".to_string())?;
787 let mut out = ptr::null_mut();
788 let status = unsafe { (self.next_fn)(args.as_ptr(), self.next_ctx, &mut out) };
789 if status != NemoRelayStatus::Ok {
790 return Err(format!("tool next failed: {status:?}"));
791 }
792 if out.is_null() {
793 return Err("tool next returned null output".into());
794 }
795 let result = read_json_value(self.host, out, "tool next result");
796 unsafe { (self.host.string_free)(out) };
797 result.map_err(|status| format!("tool next returned invalid JSON: {status:?}"))
798 }
799}
800
801pub struct LlmNext<'a> {
803 host: &'a NemoRelayNativeHostApiV1,
804 next_fn: NemoRelayNativeLlmNextFn,
805 next_ctx: *mut c_void,
806}
807
808impl LlmNext<'_> {
809 pub fn call(&self, request: LlmRequest) -> Result<Json> {
811 let request = HostString::from_json(self.host, &request)
812 .ok_or_else(|| "failed to allocate LLM next request".to_string())?;
813 let mut out = ptr::null_mut();
814 let status = unsafe { (self.next_fn)(request.as_ptr(), self.next_ctx, &mut out) };
815 if status != NemoRelayStatus::Ok {
816 return Err(format!("llm next failed: {status:?}"));
817 }
818 if out.is_null() {
819 return Err("llm next returned null output".into());
820 }
821 let result = read_json_value(self.host, out, "llm next result");
822 unsafe { (self.host.string_free)(out) };
823 result.map_err(|status| format!("llm next returned invalid JSON: {status:?}"))
824 }
825}
826
827pub struct LlmStreamNext<'a> {
829 host: &'a NemoRelayNativeHostApiV1,
830 next_fn: NemoRelayNativeLlmStreamNextFn,
831 next_ctx: *mut c_void,
832}
833
834impl LlmStreamNext<'_> {
835 pub fn call(&self, request: LlmRequest) -> Result<LlmStream> {
837 let request = HostString::from_json(self.host, &request)
838 .ok_or_else(|| "failed to allocate LLM stream next request".to_string())?;
839 let mut raw = NemoRelayNativeLlmStreamV1::default();
840 let status = unsafe { (self.next_fn)(request.as_ptr(), self.next_ctx, &mut raw) };
841 if status != NemoRelayStatus::Ok {
842 return Err(format!("llm stream next failed: {status:?}"));
843 }
844 unsafe { LlmStream::from_raw(self.host, raw) }
845 }
846}
847
848pub struct LlmStream {
850 host: NemoRelayNativeHostApiV1,
851 raw: NemoRelayNativeLlmStreamV1,
852 finished: bool,
853}
854
855unsafe impl Send for LlmStream {}
857
858impl LlmStream {
859 pub unsafe fn from_raw(
865 host: &NemoRelayNativeHostApiV1,
866 mut raw: NemoRelayNativeLlmStreamV1,
867 ) -> Result<Self> {
868 let expected_size = std::mem::size_of::<NemoRelayNativeLlmStreamV1>();
869 if raw.struct_size != expected_size {
870 if raw.struct_size >= expected_size {
871 unsafe { drop_raw_llm_stream(&mut raw) };
872 }
873 return Err(format!(
874 "unsupported LLM stream struct size: {}",
875 raw.struct_size
876 ));
877 }
878 if raw.next.is_none() {
879 unsafe { drop_raw_llm_stream(&mut raw) };
880 return Err("LLM stream next callback was null".into());
881 }
882 Ok(Self {
883 host: *host,
884 raw,
885 finished: false,
886 })
887 }
888
889 pub fn next_chunk(&mut self) -> Result<Option<Json>> {
891 if self.finished {
892 return Ok(None);
893 }
894 let next = self
895 .raw
896 .next
897 .expect("LLM stream next callback is validated on construction");
898 let mut out = ptr::null_mut();
899 let status = unsafe { next(self.raw.user_data, &mut out) };
900 match status {
901 NemoRelayStatus::Ok => {
902 if out.is_null() {
903 self.finished = true;
904 return Err("LLM stream returned null chunk".into());
905 }
906 let result = read_json_value(&self.host, out, "LLM stream chunk");
907 unsafe { (self.host.string_free)(out) };
908 match result {
909 Ok(chunk) => Ok(Some(chunk)),
910 Err(status) => {
911 self.finished = true;
912 Err(format!("LLM stream returned invalid JSON: {status:?}"))
913 }
914 }
915 }
916 NemoRelayStatus::StreamEnd => {
917 if !out.is_null() {
918 unsafe { (self.host.string_free)(out) };
919 }
920 self.finished = true;
921 Ok(None)
922 }
923 other => {
924 if !out.is_null() {
925 unsafe { (self.host.string_free)(out) };
926 }
927 self.finished = true;
928 Err(format!("LLM stream failed: {other:?}"))
929 }
930 }
931 }
932
933 pub fn cancel(&mut self) -> Result<()> {
935 if self.finished {
936 return Ok(());
937 }
938 if let Some(cancel) = self.raw.cancel {
939 let status = unsafe { cancel(self.raw.user_data) };
940 if status != NemoRelayStatus::Ok {
941 return Err(format!("LLM stream cancel failed: {status:?}"));
942 }
943 }
944 self.finished = true;
945 Ok(())
946 }
947}
948
949impl Iterator for LlmStream {
950 type Item = Result<Json>;
951
952 fn next(&mut self) -> Option<Self::Item> {
953 match self.next_chunk() {
954 Ok(Some(chunk)) => Some(Ok(chunk)),
955 Ok(None) => None,
956 Err(message) => Some(Err(message)),
957 }
958 }
959}
960
961unsafe fn drop_raw_llm_stream(raw: &mut NemoRelayNativeLlmStreamV1) {
962 if let Some(drop_fn) = raw.drop.take() {
963 unsafe { drop_fn(raw.user_data) };
964 }
965 raw.user_data = ptr::null_mut();
966}
967
968impl Drop for LlmStream {
969 fn drop(&mut self) {
970 if !self.finished {
971 if let Some(cancel) = self.raw.cancel {
972 let _ = unsafe { cancel(self.raw.user_data) };
973 }
974 self.finished = true;
975 }
976 unsafe { drop_raw_llm_stream(&mut self.raw) };
977 }
978}
979
980pub struct ScopeHandle<'a> {
982 host: &'a NemoRelayNativeHostApiV1,
983 ptr: *mut NemoRelayNativeScopeHandle,
984}
985
986impl<'a> ScopeHandle<'a> {
987 pub fn as_ptr(&self) -> *const NemoRelayNativeScopeHandle {
989 self.ptr
990 }
991}
992
993impl Drop for ScopeHandle<'_> {
994 fn drop(&mut self) {
995 unsafe { (self.host.scope_handle_free)(self.ptr) };
996 }
997}
998
999pub struct ScopeStack<'a> {
1001 host: &'a NemoRelayNativeHostApiV1,
1002 ptr: *mut NemoRelayNativeScopeStack,
1003}
1004
1005impl<'a> ScopeStack<'a> {
1006 pub fn as_ptr(&self) -> *const NemoRelayNativeScopeStack {
1008 self.ptr
1009 }
1010
1011 fn set_thread(&self) -> NemoRelayStatus {
1012 unsafe { (self.host.scope_stack_set_thread)(self.ptr) }
1013 }
1014
1015 pub fn with_current<F>(&self, f: F) -> Result<()>
1017 where
1018 F: FnOnce() -> Result<()>,
1019 {
1020 struct State<F> {
1021 f: Option<F>,
1022 error: Option<String>,
1023 }
1024
1025 unsafe extern "C" fn trampoline<F>(user_data: *mut c_void) -> NemoRelayStatus
1026 where
1027 F: FnOnce() -> Result<()>,
1028 {
1029 if user_data.is_null() {
1030 return NemoRelayStatus::NullPointer;
1031 }
1032 let state = unsafe { &mut *(user_data as *mut State<F>) };
1033 let result = catch_unwind(AssertUnwindSafe(|| {
1034 let Some(f) = state.f.take() else {
1035 return Err("scope-stack callback was already consumed".to_string());
1036 };
1037 f()
1038 }));
1039 match result {
1040 Ok(Ok(())) => NemoRelayStatus::Ok,
1041 Ok(Err(message)) => {
1042 state.error = Some(message);
1043 NemoRelayStatus::Internal
1044 }
1045 Err(_) => {
1046 state.error = Some("scope-stack callback panicked".into());
1047 NemoRelayStatus::Internal
1048 }
1049 }
1050 }
1051
1052 let mut state = State {
1053 f: Some(f),
1054 error: None,
1055 };
1056 let status = unsafe {
1057 (self.host.scope_stack_with_current)(
1058 self.ptr,
1059 trampoline::<F>,
1060 (&mut state as *mut State<_>).cast(),
1061 )
1062 };
1063 if status == NemoRelayStatus::Ok {
1064 Ok(())
1065 } else {
1066 Err(state
1067 .error
1068 .unwrap_or_else(|| format!("scope_stack_with_current failed: {status:?}")))
1069 }
1070 }
1071}
1072
1073impl Drop for ScopeStack<'_> {
1074 fn drop(&mut self) {
1075 unsafe { (self.host.scope_stack_free)(self.ptr) };
1076 }
1077}
1078
1079pub struct ScopeStackBinding<'a> {
1081 host: &'a NemoRelayNativeHostApiV1,
1082 ptr: *mut NemoRelayNativeScopeStackBinding,
1083}
1084
1085impl<'a> ScopeStackBinding<'a> {
1086 pub fn restore(mut self) -> NemoRelayStatus {
1088 let ptr = std::mem::replace(&mut self.ptr, ptr::null_mut());
1089 unsafe { (self.host.scope_stack_restore_thread)(ptr) }
1090 }
1091}
1092
1093impl Drop for ScopeStackBinding<'_> {
1094 fn drop(&mut self) {
1095 if !self.ptr.is_null() {
1096 unsafe { (self.host.scope_stack_binding_free)(self.ptr) };
1097 }
1098 }
1099}
1100
1101pub fn current_scope(host: &NemoRelayNativeHostApiV1) -> Result<ScopeHandle<'_>> {
1103 let mut out = ptr::null_mut();
1104 let status = unsafe { (host.scope_get_current)(&mut out) };
1105 if status == NemoRelayStatus::Ok && !out.is_null() {
1106 Ok(ScopeHandle { host, ptr: out })
1107 } else {
1108 Err(format!("scope_get_current failed: {status:?}"))
1109 }
1110}
1111
1112pub fn push_scope<'a>(
1114 host: &'a NemoRelayNativeHostApiV1,
1115 name: &str,
1116 scope_type: NemoRelayNativeScopeType,
1117 data: Option<&Json>,
1118 metadata: Option<&Json>,
1119 input: Option<&Json>,
1120) -> Result<ScopeHandle<'a>> {
1121 let name =
1122 HostString::new(host, name).ok_or_else(|| "failed to allocate scope name".to_string())?;
1123 let data = OptionalHostJson::new(host, data)?;
1124 let metadata = OptionalHostJson::new(host, metadata)?;
1125 let input = OptionalHostJson::new(host, input)?;
1126 let mut out = ptr::null_mut();
1127 let status = unsafe {
1128 (host.scope_push)(
1129 name.as_ptr(),
1130 scope_type,
1131 ptr::null(),
1132 0,
1133 data.as_ptr(),
1134 metadata.as_ptr(),
1135 input.as_ptr(),
1136 ptr::null(),
1137 &mut out,
1138 )
1139 };
1140 if status == NemoRelayStatus::Ok && !out.is_null() {
1141 Ok(ScopeHandle { host, ptr: out })
1142 } else {
1143 Err(format!("scope_push failed: {status:?}"))
1144 }
1145}
1146
1147pub fn pop_scope(
1149 host: &NemoRelayNativeHostApiV1,
1150 handle: &ScopeHandle<'_>,
1151 output: Option<&Json>,
1152 metadata: Option<&Json>,
1153) -> Result<()> {
1154 let output = OptionalHostJson::new(host, output)?;
1155 let metadata = OptionalHostJson::new(host, metadata)?;
1156 let status = unsafe {
1157 (host.scope_pop)(
1158 handle.as_ptr(),
1159 output.as_ptr(),
1160 metadata.as_ptr(),
1161 ptr::null(),
1162 )
1163 };
1164 if status == NemoRelayStatus::Ok {
1165 Ok(())
1166 } else {
1167 Err(format!("scope_pop failed: {status:?}"))
1168 }
1169}
1170
1171pub fn emit_mark(
1173 host: &NemoRelayNativeHostApiV1,
1174 name: &str,
1175 data: Option<&Json>,
1176 metadata: Option<&Json>,
1177) -> Result<()> {
1178 let name =
1179 HostString::new(host, name).ok_or_else(|| "failed to allocate mark name".to_string())?;
1180 let data = OptionalHostJson::new(host, data)?;
1181 let metadata = OptionalHostJson::new(host, metadata)?;
1182 let status = unsafe {
1183 (host.emit_mark)(
1184 name.as_ptr(),
1185 ptr::null(),
1186 data.as_ptr(),
1187 metadata.as_ptr(),
1188 ptr::null(),
1189 )
1190 };
1191 if status == NemoRelayStatus::Ok {
1192 Ok(())
1193 } else {
1194 Err(format!("emit_mark failed: {status:?}"))
1195 }
1196}
1197
1198pub fn create_scope_stack(host: &NemoRelayNativeHostApiV1) -> Result<ScopeStack<'_>> {
1200 let mut out = ptr::null_mut();
1201 let status = unsafe { (host.scope_stack_create)(&mut out) };
1202 if status == NemoRelayStatus::Ok && !out.is_null() {
1203 Ok(ScopeStack { host, ptr: out })
1204 } else {
1205 Err(format!("scope_stack_create failed: {status:?}"))
1206 }
1207}
1208
1209pub fn capture_scope_stack_thread(
1211 host: &NemoRelayNativeHostApiV1,
1212) -> Result<ScopeStackBinding<'_>> {
1213 let mut out = ptr::null_mut();
1214 let status = unsafe { (host.scope_stack_capture_thread)(&mut out) };
1215 if status == NemoRelayStatus::Ok && !out.is_null() {
1216 Ok(ScopeStackBinding { host, ptr: out })
1217 } else {
1218 Err(format!("scope_stack_capture_thread failed: {status:?}"))
1219 }
1220}
1221
1222pub trait NativePlugin: Send + 'static {
1224 fn plugin_kind(&self) -> &str;
1226
1227 fn allows_multiple_components(&self) -> bool {
1229 true
1230 }
1231
1232 fn validate(&self, _plugin_config: &Map<String, Json>) -> Vec<ConfigDiagnostic> {
1234 vec![]
1235 }
1236
1237 fn register(
1239 &mut self,
1240 plugin_config: &Map<String, Json>,
1241 ctx: &mut PluginContext<'_>,
1242 ) -> Result<()>;
1243}
1244
1245pub struct PluginContext<'a> {
1247 host: &'a NemoRelayNativeHostApiV1,
1248 raw: *mut NemoRelayNativePluginContext,
1249}
1250
1251#[allow(clippy::not_unsafe_ptr_arg_deref)]
1252impl<'a> PluginContext<'a> {
1253 pub unsafe fn from_raw(
1258 host: &'a NemoRelayNativeHostApiV1,
1259 raw: *mut NemoRelayNativePluginContext,
1260 ) -> Self {
1261 Self { host, raw }
1262 }
1263
1264 pub fn host_api(&self) -> &'a NemoRelayNativeHostApiV1 {
1266 self.host
1267 }
1268
1269 pub fn runtime(&self) -> PluginRuntime {
1271 PluginRuntime::new(self.host)
1272 }
1273
1274 pub fn register_subscriber<F>(&mut self, name: &str, callback: F) -> Result<()>
1276 where
1277 F: Fn(&Event) + Send + Sync + 'static,
1278 {
1279 let user_data = typed_callback_user_data(self.host, callback);
1280 let status = unsafe {
1281 self.register_subscriber_raw(
1282 name,
1283 typed_subscriber_trampoline::<F>,
1284 user_data,
1285 Some(drop_typed_callback::<F>),
1286 )
1287 };
1288 finish_typed_registration::<F>(self.host, status, user_data, "subscriber")
1289 }
1290
1291 fn register_event_sanitizer<F>(
1292 &mut self,
1293 name: &str,
1294 priority: i32,
1295 callback: F,
1296 register: unsafe extern "C" fn(
1297 *mut NemoRelayNativePluginContext,
1298 *const NemoRelayNativeString,
1299 i32,
1300 NemoRelayNativeEventSanitizeCb,
1301 *mut c_void,
1302 NemoRelayNativeFreeFn,
1303 ) -> NemoRelayStatus,
1304 label: &str,
1305 ) -> Result<()>
1306 where
1307 F: Fn(&Event, EventSanitizeFields) -> EventSanitizeFields + Send + Sync + 'static,
1308 {
1309 let user_data = typed_callback_user_data(self.host, callback);
1310 let status = self.with_name(name, |_, name| unsafe {
1311 register(
1312 self.raw,
1313 name,
1314 priority,
1315 typed_event_sanitize_trampoline::<F>,
1316 user_data,
1317 Some(drop_typed_callback::<F>),
1318 )
1319 });
1320 finish_typed_registration::<F>(self.host, status, user_data, label)
1321 }
1322
1323 pub fn register_mark_sanitize_guardrail<F>(
1325 &mut self,
1326 name: &str,
1327 priority: i32,
1328 callback: F,
1329 ) -> Result<()>
1330 where
1331 F: Fn(&Event, EventSanitizeFields) -> EventSanitizeFields + Send + Sync + 'static,
1332 {
1333 self.register_event_sanitizer(
1334 name,
1335 priority,
1336 callback,
1337 self.host.plugin_context_register_mark_sanitize_guardrail,
1338 "mark sanitize guardrail",
1339 )
1340 }
1341
1342 pub fn register_scope_sanitize_start_guardrail<F>(
1344 &mut self,
1345 name: &str,
1346 priority: i32,
1347 callback: F,
1348 ) -> Result<()>
1349 where
1350 F: Fn(&Event, EventSanitizeFields) -> EventSanitizeFields + Send + Sync + 'static,
1351 {
1352 self.register_event_sanitizer(
1353 name,
1354 priority,
1355 callback,
1356 self.host
1357 .plugin_context_register_scope_sanitize_start_guardrail,
1358 "scope-start sanitize guardrail",
1359 )
1360 }
1361
1362 pub fn register_scope_sanitize_end_guardrail<F>(
1364 &mut self,
1365 name: &str,
1366 priority: i32,
1367 callback: F,
1368 ) -> Result<()>
1369 where
1370 F: Fn(&Event, EventSanitizeFields) -> EventSanitizeFields + Send + Sync + 'static,
1371 {
1372 self.register_event_sanitizer(
1373 name,
1374 priority,
1375 callback,
1376 self.host
1377 .plugin_context_register_scope_sanitize_end_guardrail,
1378 "scope-end sanitize guardrail",
1379 )
1380 }
1381
1382 pub fn register_tool_sanitize_request_guardrail<F>(
1384 &mut self,
1385 name: &str,
1386 priority: i32,
1387 callback: F,
1388 ) -> Result<()>
1389 where
1390 F: Fn(&str, Json) -> Json + Send + Sync + 'static,
1391 {
1392 let user_data = typed_callback_user_data(self.host, callback);
1393 let status = unsafe {
1394 self.register_tool_sanitize_request_guardrail_raw(
1395 name,
1396 priority,
1397 typed_tool_sanitize_trampoline::<F>,
1398 user_data,
1399 Some(drop_typed_callback::<F>),
1400 )
1401 };
1402 finish_typed_registration::<F>(
1403 self.host,
1404 status,
1405 user_data,
1406 "tool sanitize request guardrail",
1407 )
1408 }
1409
1410 pub fn register_tool_sanitize_response_guardrail<F>(
1412 &mut self,
1413 name: &str,
1414 priority: i32,
1415 callback: F,
1416 ) -> Result<()>
1417 where
1418 F: Fn(&str, Json) -> Json + Send + Sync + 'static,
1419 {
1420 let user_data = typed_callback_user_data(self.host, callback);
1421 let status = unsafe {
1422 self.register_tool_sanitize_response_guardrail_raw(
1423 name,
1424 priority,
1425 typed_tool_sanitize_trampoline::<F>,
1426 user_data,
1427 Some(drop_typed_callback::<F>),
1428 )
1429 };
1430 finish_typed_registration::<F>(
1431 self.host,
1432 status,
1433 user_data,
1434 "tool sanitize response guardrail",
1435 )
1436 }
1437
1438 pub fn register_tool_conditional_execution_guardrail<F>(
1440 &mut self,
1441 name: &str,
1442 priority: i32,
1443 callback: F,
1444 ) -> Result<()>
1445 where
1446 F: Fn(&str, &Json) -> Result<Option<String>> + Send + Sync + 'static,
1447 {
1448 let user_data = typed_callback_user_data(self.host, callback);
1449 let status = unsafe {
1450 self.register_tool_conditional_execution_guardrail_raw(
1451 name,
1452 priority,
1453 typed_tool_conditional_trampoline::<F>,
1454 user_data,
1455 Some(drop_typed_callback::<F>),
1456 )
1457 };
1458 finish_typed_registration::<F>(
1459 self.host,
1460 status,
1461 user_data,
1462 "tool conditional execution guardrail",
1463 )
1464 }
1465
1466 pub fn register_tool_request_intercept<F>(
1468 &mut self,
1469 name: &str,
1470 priority: i32,
1471 break_chain: bool,
1472 callback: F,
1473 ) -> Result<()>
1474 where
1475 F: Fn(&str, Json) -> Result<Json> + Send + Sync + 'static,
1476 {
1477 let user_data = typed_callback_user_data(self.host, callback);
1478 let status = unsafe {
1479 self.register_tool_request_intercept_raw(
1480 name,
1481 priority,
1482 break_chain,
1483 typed_tool_intercept_trampoline::<F>,
1484 user_data,
1485 Some(drop_typed_callback::<F>),
1486 )
1487 };
1488 finish_typed_registration::<F>(self.host, status, user_data, "tool request intercept")
1489 }
1490
1491 pub fn register_tool_execution_intercept<F>(
1497 &mut self,
1498 name: &str,
1499 priority: i32,
1500 callback: F,
1501 ) -> Result<()>
1502 where
1503 F: for<'next> Fn(&str, Json, ToolNext<'next>) -> Result<ToolExecutionInterceptOutcome>
1504 + Send
1505 + Sync
1506 + 'static,
1507 {
1508 let user_data = typed_callback_user_data(self.host, callback);
1509 let status = unsafe {
1510 self.register_tool_execution_intercept_raw(
1511 name,
1512 priority,
1513 typed_tool_execution_trampoline::<F>,
1514 user_data,
1515 Some(drop_typed_callback::<F>),
1516 )
1517 };
1518 finish_typed_registration::<F>(self.host, status, user_data, "tool execution intercept")
1519 }
1520
1521 pub fn register_llm_sanitize_request_guardrail<F>(
1523 &mut self,
1524 name: &str,
1525 priority: i32,
1526 callback: F,
1527 ) -> Result<()>
1528 where
1529 F: Fn(LlmRequest) -> LlmRequest + Send + Sync + 'static,
1530 {
1531 let user_data = typed_callback_user_data(self.host, callback);
1532 let status = unsafe {
1533 self.register_llm_sanitize_request_guardrail_raw(
1534 name,
1535 priority,
1536 typed_llm_sanitize_request_trampoline::<F>,
1537 user_data,
1538 Some(drop_typed_callback::<F>),
1539 )
1540 };
1541 finish_typed_registration::<F>(
1542 self.host,
1543 status,
1544 user_data,
1545 "llm sanitize request guardrail",
1546 )
1547 }
1548
1549 pub fn register_llm_sanitize_response_guardrail<F>(
1551 &mut self,
1552 name: &str,
1553 priority: i32,
1554 callback: F,
1555 ) -> Result<()>
1556 where
1557 F: Fn(Json) -> Json + Send + Sync + 'static,
1558 {
1559 let user_data = typed_callback_user_data(self.host, callback);
1560 let status = unsafe {
1561 self.register_llm_sanitize_response_guardrail_raw(
1562 name,
1563 priority,
1564 typed_llm_sanitize_response_trampoline::<F>,
1565 user_data,
1566 Some(drop_typed_callback::<F>),
1567 )
1568 };
1569 finish_typed_registration::<F>(
1570 self.host,
1571 status,
1572 user_data,
1573 "llm sanitize response guardrail",
1574 )
1575 }
1576
1577 pub fn register_llm_conditional_execution_guardrail<F>(
1579 &mut self,
1580 name: &str,
1581 priority: i32,
1582 callback: F,
1583 ) -> Result<()>
1584 where
1585 F: Fn(&LlmRequest) -> Result<Option<String>> + Send + Sync + 'static,
1586 {
1587 let user_data = typed_callback_user_data(self.host, callback);
1588 let status = unsafe {
1589 self.register_llm_conditional_execution_guardrail_raw(
1590 name,
1591 priority,
1592 typed_llm_conditional_trampoline::<F>,
1593 user_data,
1594 Some(drop_typed_callback::<F>),
1595 )
1596 };
1597 finish_typed_registration::<F>(
1598 self.host,
1599 status,
1600 user_data,
1601 "llm conditional execution guardrail",
1602 )
1603 }
1604
1605 pub fn register_llm_request_intercept<F>(
1607 &mut self,
1608 name: &str,
1609 priority: i32,
1610 break_chain: bool,
1611 callback: F,
1612 ) -> Result<()>
1613 where
1614 F: Fn(&str, LlmRequest, Option<AnnotatedLlmRequest>) -> Result<LlmRequestInterceptOutcome>
1615 + Send
1616 + Sync
1617 + 'static,
1618 {
1619 let user_data = typed_callback_user_data(self.host, callback);
1620 let status = unsafe {
1621 self.register_llm_request_intercept_raw(
1622 name,
1623 priority,
1624 break_chain,
1625 typed_llm_request_intercept_trampoline::<F>,
1626 user_data,
1627 Some(drop_typed_callback::<F>),
1628 )
1629 };
1630 finish_typed_registration::<F>(self.host, status, user_data, "llm request intercept")
1631 }
1632
1633 pub fn register_llm_execution_intercept<F>(
1635 &mut self,
1636 name: &str,
1637 priority: i32,
1638 callback: F,
1639 ) -> Result<()>
1640 where
1641 F: for<'next> Fn(&str, LlmRequest, LlmNext<'next>) -> Result<Json> + Send + Sync + 'static,
1642 {
1643 let user_data = typed_callback_user_data(self.host, callback);
1644 let status = unsafe {
1645 self.register_llm_execution_intercept_raw(
1646 name,
1647 priority,
1648 typed_llm_execution_trampoline::<F>,
1649 user_data,
1650 Some(drop_typed_callback::<F>),
1651 )
1652 };
1653 finish_typed_registration::<F>(self.host, status, user_data, "llm execution intercept")
1654 }
1655
1656 pub fn register_llm_stream_execution_intercept<F>(
1661 &mut self,
1662 name: &str,
1663 priority: i32,
1664 callback: F,
1665 ) -> Result<()>
1666 where
1667 F: for<'next> Fn(&str, LlmRequest, LlmStreamNext<'next>) -> Result<LlmJsonStream>
1668 + Send
1669 + Sync
1670 + 'static,
1671 {
1672 let user_data = typed_callback_user_data(self.host, callback);
1673 let status = unsafe {
1674 self.register_llm_stream_execution_intercept_raw(
1675 name,
1676 priority,
1677 typed_llm_stream_execution_trampoline::<F>,
1678 user_data,
1679 Some(drop_typed_callback::<F>),
1680 )
1681 };
1682 finish_typed_registration::<F>(
1683 self.host,
1684 status,
1685 user_data,
1686 "llm stream execution intercept",
1687 )
1688 }
1689
1690 pub unsafe fn register_subscriber_raw(
1697 &mut self,
1698 name: &str,
1699 cb: NemoRelayNativeEventSubscriberCb,
1700 user_data: *mut c_void,
1701 free_fn: NemoRelayNativeFreeFn,
1702 ) -> NemoRelayStatus {
1703 self.with_name(name, |host, name| unsafe {
1704 (host.plugin_context_register_subscriber)(self.raw, name, cb, user_data, free_fn)
1705 })
1706 }
1707
1708 pub unsafe fn register_mark_sanitize_guardrail_raw(
1715 &mut self,
1716 name: &str,
1717 priority: i32,
1718 cb: NemoRelayNativeEventSanitizeCb,
1719 user_data: *mut c_void,
1720 free_fn: NemoRelayNativeFreeFn,
1721 ) -> NemoRelayStatus {
1722 self.with_name(name, |host, name| unsafe {
1723 (host.plugin_context_register_mark_sanitize_guardrail)(
1724 self.raw, name, priority, cb, user_data, free_fn,
1725 )
1726 })
1727 }
1728
1729 pub unsafe fn register_scope_sanitize_start_guardrail_raw(
1736 &mut self,
1737 name: &str,
1738 priority: i32,
1739 cb: NemoRelayNativeEventSanitizeCb,
1740 user_data: *mut c_void,
1741 free_fn: NemoRelayNativeFreeFn,
1742 ) -> NemoRelayStatus {
1743 self.with_name(name, |host, name| unsafe {
1744 (host.plugin_context_register_scope_sanitize_start_guardrail)(
1745 self.raw, name, priority, cb, user_data, free_fn,
1746 )
1747 })
1748 }
1749
1750 pub unsafe fn register_scope_sanitize_end_guardrail_raw(
1757 &mut self,
1758 name: &str,
1759 priority: i32,
1760 cb: NemoRelayNativeEventSanitizeCb,
1761 user_data: *mut c_void,
1762 free_fn: NemoRelayNativeFreeFn,
1763 ) -> NemoRelayStatus {
1764 self.with_name(name, |host, name| unsafe {
1765 (host.plugin_context_register_scope_sanitize_end_guardrail)(
1766 self.raw, name, priority, cb, user_data, free_fn,
1767 )
1768 })
1769 }
1770
1771 pub unsafe fn register_tool_sanitize_request_guardrail_raw(
1778 &mut self,
1779 name: &str,
1780 priority: i32,
1781 cb: NemoRelayNativeToolJsonCb,
1782 user_data: *mut c_void,
1783 free_fn: NemoRelayNativeFreeFn,
1784 ) -> NemoRelayStatus {
1785 self.with_name(name, |host, name| unsafe {
1786 (host.plugin_context_register_tool_sanitize_request_guardrail)(
1787 self.raw, name, priority, cb, user_data, free_fn,
1788 )
1789 })
1790 }
1791
1792 pub unsafe fn register_tool_sanitize_response_guardrail_raw(
1799 &mut self,
1800 name: &str,
1801 priority: i32,
1802 cb: NemoRelayNativeToolJsonCb,
1803 user_data: *mut c_void,
1804 free_fn: NemoRelayNativeFreeFn,
1805 ) -> NemoRelayStatus {
1806 self.with_name(name, |host, name| unsafe {
1807 (host.plugin_context_register_tool_sanitize_response_guardrail)(
1808 self.raw, name, priority, cb, user_data, free_fn,
1809 )
1810 })
1811 }
1812
1813 pub unsafe fn register_tool_conditional_execution_guardrail_raw(
1820 &mut self,
1821 name: &str,
1822 priority: i32,
1823 cb: NemoRelayNativeToolConditionalCb,
1824 user_data: *mut c_void,
1825 free_fn: NemoRelayNativeFreeFn,
1826 ) -> NemoRelayStatus {
1827 self.with_name(name, |host, name| unsafe {
1828 (host.plugin_context_register_tool_conditional_execution_guardrail)(
1829 self.raw, name, priority, cb, user_data, free_fn,
1830 )
1831 })
1832 }
1833
1834 pub unsafe fn register_tool_request_intercept_raw(
1841 &mut self,
1842 name: &str,
1843 priority: i32,
1844 break_chain: bool,
1845 cb: NemoRelayNativeToolJsonCb,
1846 user_data: *mut c_void,
1847 free_fn: NemoRelayNativeFreeFn,
1848 ) -> NemoRelayStatus {
1849 self.with_name(name, |host, name| unsafe {
1850 (host.plugin_context_register_tool_request_intercept)(
1851 self.raw,
1852 name,
1853 priority,
1854 break_chain,
1855 cb,
1856 user_data,
1857 free_fn,
1858 )
1859 })
1860 }
1861
1862 pub unsafe fn register_tool_execution_intercept_raw(
1869 &mut self,
1870 name: &str,
1871 priority: i32,
1872 cb: NemoRelayNativeToolExecutionCb,
1873 user_data: *mut c_void,
1874 free_fn: NemoRelayNativeFreeFn,
1875 ) -> NemoRelayStatus {
1876 self.with_name(name, |host, name| unsafe {
1877 (host.plugin_context_register_tool_execution_intercept)(
1878 self.raw, name, priority, cb, user_data, free_fn,
1879 )
1880 })
1881 }
1882
1883 pub unsafe fn register_llm_sanitize_request_guardrail_raw(
1890 &mut self,
1891 name: &str,
1892 priority: i32,
1893 cb: NemoRelayNativeLlmRequestCb,
1894 user_data: *mut c_void,
1895 free_fn: NemoRelayNativeFreeFn,
1896 ) -> NemoRelayStatus {
1897 self.with_name(name, |host, name| unsafe {
1898 (host.plugin_context_register_llm_sanitize_request_guardrail)(
1899 self.raw, name, priority, cb, user_data, free_fn,
1900 )
1901 })
1902 }
1903
1904 pub unsafe fn register_llm_sanitize_response_guardrail_raw(
1911 &mut self,
1912 name: &str,
1913 priority: i32,
1914 cb: NemoRelayNativeJsonCb,
1915 user_data: *mut c_void,
1916 free_fn: NemoRelayNativeFreeFn,
1917 ) -> NemoRelayStatus {
1918 self.with_name(name, |host, name| unsafe {
1919 (host.plugin_context_register_llm_sanitize_response_guardrail)(
1920 self.raw, name, priority, cb, user_data, free_fn,
1921 )
1922 })
1923 }
1924
1925 pub unsafe fn register_llm_conditional_execution_guardrail_raw(
1932 &mut self,
1933 name: &str,
1934 priority: i32,
1935 cb: NemoRelayNativeLlmConditionalCb,
1936 user_data: *mut c_void,
1937 free_fn: NemoRelayNativeFreeFn,
1938 ) -> NemoRelayStatus {
1939 self.with_name(name, |host, name| unsafe {
1940 (host.plugin_context_register_llm_conditional_execution_guardrail)(
1941 self.raw, name, priority, cb, user_data, free_fn,
1942 )
1943 })
1944 }
1945
1946 pub unsafe fn register_llm_request_intercept_raw(
1953 &mut self,
1954 name: &str,
1955 priority: i32,
1956 break_chain: bool,
1957 cb: NemoRelayNativeLlmRequestInterceptCb,
1958 user_data: *mut c_void,
1959 free_fn: NemoRelayNativeFreeFn,
1960 ) -> NemoRelayStatus {
1961 self.with_name(name, |host, name| unsafe {
1962 (host.plugin_context_register_llm_request_intercept)(
1963 self.raw,
1964 name,
1965 priority,
1966 break_chain,
1967 cb,
1968 user_data,
1969 free_fn,
1970 )
1971 })
1972 }
1973
1974 pub unsafe fn register_llm_execution_intercept_raw(
1981 &mut self,
1982 name: &str,
1983 priority: i32,
1984 cb: NemoRelayNativeLlmExecutionCb,
1985 user_data: *mut c_void,
1986 free_fn: NemoRelayNativeFreeFn,
1987 ) -> NemoRelayStatus {
1988 self.with_name(name, |host, name| unsafe {
1989 (host.plugin_context_register_llm_execution_intercept)(
1990 self.raw, name, priority, cb, user_data, free_fn,
1991 )
1992 })
1993 }
1994
1995 pub unsafe fn register_llm_stream_execution_intercept_raw(
2002 &mut self,
2003 name: &str,
2004 priority: i32,
2005 cb: NemoRelayNativeLlmStreamExecutionCb,
2006 user_data: *mut c_void,
2007 free_fn: NemoRelayNativeFreeFn,
2008 ) -> NemoRelayStatus {
2009 self.with_name(name, |host, name| unsafe {
2010 (host.plugin_context_register_llm_stream_execution_intercept)(
2011 self.raw, name, priority, cb, user_data, free_fn,
2012 )
2013 })
2014 }
2015
2016 fn with_name(
2017 &self,
2018 name: &str,
2019 f: impl FnOnce(&NemoRelayNativeHostApiV1, *const NemoRelayNativeString) -> NemoRelayStatus,
2020 ) -> NemoRelayStatus {
2021 let name = match HostString::try_new(self.host, name) {
2022 Ok(name) => name,
2023 Err(status) => return status,
2024 };
2025 f(self.host, name.as_ptr())
2026 }
2027}
2028
2029struct TypedCallback<F> {
2030 host: NemoRelayNativeHostApiV1,
2031 callback: F,
2032}
2033
2034fn typed_callback_user_data<F>(host: &NemoRelayNativeHostApiV1, callback: F) -> *mut c_void {
2035 Box::into_raw(Box::new(TypedCallback {
2036 host: *host,
2037 callback,
2038 })) as *mut c_void
2039}
2040
2041unsafe extern "C" fn drop_typed_callback<F>(user_data: *mut c_void) {
2042 if !user_data.is_null() {
2043 let callback = unsafe { Box::from_raw(user_data as *mut TypedCallback<F>) };
2044 let host = callback.host;
2045 if catch_unwind(AssertUnwindSafe(|| drop(callback))).is_err() {
2046 set_last_error(&host, "native plugin typed callback state drop panicked");
2047 }
2048 }
2049}
2050
2051fn finish_typed_registration<F>(
2052 host: &NemoRelayNativeHostApiV1,
2053 status: NemoRelayStatus,
2054 user_data: *mut c_void,
2055 label: &str,
2056) -> Result<()> {
2057 if status == NemoRelayStatus::Ok {
2058 Ok(())
2059 } else {
2060 unsafe { drop_typed_callback::<F>(user_data) };
2061 Err(status_error(host, status, label))
2062 }
2063}
2064
2065fn status_error(host: &NemoRelayNativeHostApiV1, status: NemoRelayStatus, label: &str) -> String {
2066 debug_assert_ne!(status, NemoRelayStatus::Ok);
2067 set_last_error(host, &format!("{label} failed: {status:?}"));
2068 format!("{label} failed: {status:?}")
2069}
2070
2071fn callback_error(host: &NemoRelayNativeHostApiV1, message: String) -> NemoRelayStatus {
2072 set_last_error(host, &message);
2073 NemoRelayStatus::Internal
2074}
2075
2076fn callback_panic(host: &NemoRelayNativeHostApiV1, label: &str) -> NemoRelayStatus {
2077 set_last_error(host, &format!("{label} panicked"));
2078 NemoRelayStatus::Internal
2079}
2080
2081unsafe extern "C" fn typed_subscriber_trampoline<F>(
2082 user_data: *mut c_void,
2083 event_json: *const NemoRelayNativeString,
2084) -> NemoRelayStatus
2085where
2086 F: Fn(&Event) + Send + Sync + 'static,
2087{
2088 if user_data.is_null() {
2089 return NemoRelayStatus::NullPointer;
2090 }
2091 let state = unsafe { &*(user_data as *const TypedCallback<F>) };
2092 let result = catch_unwind(AssertUnwindSafe(|| {
2093 let event: Event = read_json_value(&state.host, event_json, "event")?;
2094 (state.callback)(&event);
2095 Ok::<_, NemoRelayStatus>(())
2096 }));
2097 match result {
2098 Ok(Ok(())) => NemoRelayStatus::Ok,
2099 Ok(Err(status)) => status,
2100 Err(_) => callback_panic(&state.host, "subscriber callback"),
2101 }
2102}
2103
2104unsafe extern "C" fn typed_event_sanitize_trampoline<F>(
2105 user_data: *mut c_void,
2106 event_json: *const NemoRelayNativeString,
2107 fields_json: *const NemoRelayNativeString,
2108 out_fields_json: *mut *mut NemoRelayNativeString,
2109) -> NemoRelayStatus
2110where
2111 F: Fn(&Event, EventSanitizeFields) -> EventSanitizeFields + Send + Sync + 'static,
2112{
2113 if user_data.is_null() || out_fields_json.is_null() {
2114 return NemoRelayStatus::NullPointer;
2115 }
2116 unsafe { *out_fields_json = ptr::null_mut() };
2117 let state = unsafe { &*(user_data as *const TypedCallback<F>) };
2118 let result = catch_unwind(AssertUnwindSafe(|| {
2119 let event: Event = read_json_value(&state.host, event_json, "event")?;
2120 let fields: EventSanitizeFields =
2121 read_json_value(&state.host, fields_json, "event sanitize fields")?;
2122 let output = (state.callback)(&event, fields);
2123 Ok::<_, NemoRelayStatus>(write_json(&state.host, &output, out_fields_json))
2124 }));
2125 match result {
2126 Ok(Ok(status)) => status,
2127 Ok(Err(status)) => status,
2128 Err(_) => callback_panic(&state.host, "event sanitize callback"),
2129 }
2130}
2131
2132unsafe extern "C" fn typed_tool_sanitize_trampoline<F>(
2133 user_data: *mut c_void,
2134 name: *const NemoRelayNativeString,
2135 payload_json: *const NemoRelayNativeString,
2136 out_json: *mut *mut NemoRelayNativeString,
2137) -> NemoRelayStatus
2138where
2139 F: Fn(&str, Json) -> Json + Send + Sync + 'static,
2140{
2141 if user_data.is_null() || out_json.is_null() {
2142 return NemoRelayStatus::NullPointer;
2143 }
2144 unsafe { *out_json = ptr::null_mut() };
2145 let state = unsafe { &*(user_data as *const TypedCallback<F>) };
2146 let result = catch_unwind(AssertUnwindSafe(|| {
2147 let name = read_required_host_string(&state.host, name, "tool name")?;
2148 let payload: Json = read_json_value(&state.host, payload_json, "tool payload")?;
2149 let output = (state.callback)(&name, payload);
2150 Ok::<_, NemoRelayStatus>(write_json(&state.host, &output, out_json))
2151 }));
2152 match result {
2153 Ok(Ok(status)) => status,
2154 Ok(Err(status)) => status,
2155 Err(_) => callback_panic(&state.host, "tool sanitize callback"),
2156 }
2157}
2158
2159unsafe extern "C" fn typed_tool_intercept_trampoline<F>(
2160 user_data: *mut c_void,
2161 name: *const NemoRelayNativeString,
2162 payload_json: *const NemoRelayNativeString,
2163 out_json: *mut *mut NemoRelayNativeString,
2164) -> NemoRelayStatus
2165where
2166 F: Fn(&str, Json) -> Result<Json> + Send + Sync + 'static,
2167{
2168 if user_data.is_null() || out_json.is_null() {
2169 return NemoRelayStatus::NullPointer;
2170 }
2171 unsafe { *out_json = ptr::null_mut() };
2172 let state = unsafe { &*(user_data as *const TypedCallback<F>) };
2173 let result = catch_unwind(AssertUnwindSafe(|| {
2174 let name = read_required_host_string(&state.host, name, "tool name")?;
2175 let payload: Json = read_json_value(&state.host, payload_json, "tool payload")?;
2176 match (state.callback)(&name, payload) {
2177 Ok(output) => Ok::<_, NemoRelayStatus>(write_json(&state.host, &output, out_json)),
2178 Err(message) => Ok(callback_error(&state.host, message)),
2179 }
2180 }));
2181 match result {
2182 Ok(Ok(status)) => status,
2183 Ok(Err(status)) => status,
2184 Err(_) => callback_panic(&state.host, "tool intercept callback"),
2185 }
2186}
2187
2188unsafe extern "C" fn typed_tool_conditional_trampoline<F>(
2189 user_data: *mut c_void,
2190 name: *const NemoRelayNativeString,
2191 args_json: *const NemoRelayNativeString,
2192 out_reason: *mut *mut NemoRelayNativeString,
2193) -> NemoRelayStatus
2194where
2195 F: Fn(&str, &Json) -> Result<Option<String>> + Send + Sync + 'static,
2196{
2197 if user_data.is_null() || out_reason.is_null() {
2198 return NemoRelayStatus::NullPointer;
2199 }
2200 unsafe { *out_reason = ptr::null_mut() };
2201 let state = unsafe { &*(user_data as *const TypedCallback<F>) };
2202 let result = catch_unwind(AssertUnwindSafe(|| {
2203 let name = read_required_host_string(&state.host, name, "tool name")?;
2204 let args: Json = read_json_value(&state.host, args_json, "tool args")?;
2205 match (state.callback)(&name, &args) {
2206 Ok(Some(reason)) => {
2207 let reason =
2208 HostString::new(&state.host, &reason).ok_or(NemoRelayStatus::Internal)?;
2209 unsafe { *out_reason = reason.ptr };
2210 std::mem::forget(reason);
2211 Ok(NemoRelayStatus::Ok)
2212 }
2213 Ok(None) => {
2214 unsafe { *out_reason = ptr::null_mut() };
2215 Ok(NemoRelayStatus::Ok)
2216 }
2217 Err(message) => Ok(callback_error(&state.host, message)),
2218 }
2219 }));
2220 match result {
2221 Ok(Ok(status)) => status,
2222 Ok(Err(status)) => status,
2223 Err(_) => callback_panic(&state.host, "tool conditional callback"),
2224 }
2225}
2226
2227unsafe extern "C" fn typed_tool_execution_trampoline<F>(
2228 user_data: *mut c_void,
2229 name: *const NemoRelayNativeString,
2230 args_json: *const NemoRelayNativeString,
2231 next_fn: NemoRelayNativeToolNextFn,
2232 next_ctx: *mut c_void,
2233 out_outcome_json: *mut *mut NemoRelayNativeString,
2234) -> NemoRelayStatus
2235where
2236 F: for<'next> Fn(&str, Json, ToolNext<'next>) -> Result<ToolExecutionInterceptOutcome>
2237 + Send
2238 + Sync
2239 + 'static,
2240{
2241 if user_data.is_null() || out_outcome_json.is_null() {
2242 return NemoRelayStatus::NullPointer;
2243 }
2244 unsafe { *out_outcome_json = ptr::null_mut() };
2245 let state = unsafe { &*(user_data as *const TypedCallback<F>) };
2246 let result = catch_unwind(AssertUnwindSafe(|| {
2247 let name = read_required_host_string(&state.host, name, "tool name")?;
2248 let args: Json = read_json_value(&state.host, args_json, "tool args")?;
2249 let next = ToolNext {
2250 host: &state.host,
2251 next_fn,
2252 next_ctx,
2253 };
2254 match (state.callback)(&name, args, next) {
2255 Ok(outcome) => {
2256 let Some(outcome) = HostString::from_json(&state.host, &outcome) else {
2257 set_last_error(&state.host, "failed to allocate tool execution outcome");
2258 return Ok(NemoRelayStatus::Internal);
2259 };
2260 unsafe { *out_outcome_json = outcome.ptr };
2261 std::mem::forget(outcome);
2262 Ok(NemoRelayStatus::Ok)
2263 }
2264 Err(message) => Ok(callback_error(&state.host, message)),
2265 }
2266 }));
2267 match result {
2268 Ok(Ok(status)) => status,
2269 Ok(Err(status)) => status,
2270 Err(_) => callback_panic(&state.host, "tool execution callback"),
2271 }
2272}
2273
2274unsafe extern "C" fn typed_llm_sanitize_request_trampoline<F>(
2275 user_data: *mut c_void,
2276 request_json: *const NemoRelayNativeString,
2277 out_request_json: *mut *mut NemoRelayNativeString,
2278) -> NemoRelayStatus
2279where
2280 F: Fn(LlmRequest) -> LlmRequest + Send + Sync + 'static,
2281{
2282 if user_data.is_null() || out_request_json.is_null() {
2283 return NemoRelayStatus::NullPointer;
2284 }
2285 unsafe { *out_request_json = ptr::null_mut() };
2286 let state = unsafe { &*(user_data as *const TypedCallback<F>) };
2287 let result = catch_unwind(AssertUnwindSafe(|| {
2288 let request: LlmRequest = read_json_value(&state.host, request_json, "LLM request")?;
2289 let output = (state.callback)(request);
2290 Ok::<_, NemoRelayStatus>(write_json(&state.host, &output, out_request_json))
2291 }));
2292 match result {
2293 Ok(Ok(status)) => status,
2294 Ok(Err(status)) => status,
2295 Err(_) => callback_panic(&state.host, "LLM sanitize request callback"),
2296 }
2297}
2298
2299unsafe extern "C" fn typed_llm_sanitize_response_trampoline<F>(
2300 user_data: *mut c_void,
2301 payload_json: *const NemoRelayNativeString,
2302 out_json: *mut *mut NemoRelayNativeString,
2303) -> NemoRelayStatus
2304where
2305 F: Fn(Json) -> Json + Send + Sync + 'static,
2306{
2307 if user_data.is_null() || out_json.is_null() {
2308 return NemoRelayStatus::NullPointer;
2309 }
2310 unsafe { *out_json = ptr::null_mut() };
2311 let state = unsafe { &*(user_data as *const TypedCallback<F>) };
2312 let result = catch_unwind(AssertUnwindSafe(|| {
2313 let payload: Json = read_json_value(&state.host, payload_json, "LLM response")?;
2314 let output = (state.callback)(payload);
2315 Ok::<_, NemoRelayStatus>(write_json(&state.host, &output, out_json))
2316 }));
2317 match result {
2318 Ok(Ok(status)) => status,
2319 Ok(Err(status)) => status,
2320 Err(_) => callback_panic(&state.host, "LLM sanitize response callback"),
2321 }
2322}
2323
2324unsafe extern "C" fn typed_llm_conditional_trampoline<F>(
2325 user_data: *mut c_void,
2326 request_json: *const NemoRelayNativeString,
2327 out_reason: *mut *mut NemoRelayNativeString,
2328) -> NemoRelayStatus
2329where
2330 F: Fn(&LlmRequest) -> Result<Option<String>> + Send + Sync + 'static,
2331{
2332 if user_data.is_null() || out_reason.is_null() {
2333 return NemoRelayStatus::NullPointer;
2334 }
2335 unsafe { *out_reason = ptr::null_mut() };
2336 let state = unsafe { &*(user_data as *const TypedCallback<F>) };
2337 let result = catch_unwind(AssertUnwindSafe(|| {
2338 let request: LlmRequest = read_json_value(&state.host, request_json, "LLM request")?;
2339 match (state.callback)(&request) {
2340 Ok(Some(reason)) => {
2341 let reason =
2342 HostString::new(&state.host, &reason).ok_or(NemoRelayStatus::Internal)?;
2343 unsafe { *out_reason = reason.ptr };
2344 std::mem::forget(reason);
2345 Ok(NemoRelayStatus::Ok)
2346 }
2347 Ok(None) => {
2348 unsafe { *out_reason = ptr::null_mut() };
2349 Ok(NemoRelayStatus::Ok)
2350 }
2351 Err(message) => Ok(callback_error(&state.host, message)),
2352 }
2353 }));
2354 match result {
2355 Ok(Ok(status)) => status,
2356 Ok(Err(status)) => status,
2357 Err(_) => callback_panic(&state.host, "LLM conditional callback"),
2358 }
2359}
2360
2361unsafe extern "C" fn typed_llm_request_intercept_trampoline<F>(
2362 user_data: *mut c_void,
2363 name: *const NemoRelayNativeString,
2364 request_json: *const NemoRelayNativeString,
2365 annotated_json: *const NemoRelayNativeString,
2366 out_outcome_json: *mut *mut NemoRelayNativeString,
2367) -> NemoRelayStatus
2368where
2369 F: Fn(&str, LlmRequest, Option<AnnotatedLlmRequest>) -> Result<LlmRequestInterceptOutcome>
2370 + Send
2371 + Sync
2372 + 'static,
2373{
2374 if user_data.is_null() || out_outcome_json.is_null() {
2375 return NemoRelayStatus::NullPointer;
2376 }
2377 unsafe {
2378 *out_outcome_json = ptr::null_mut();
2379 }
2380 let state = unsafe { &*(user_data as *const TypedCallback<F>) };
2381 let result = catch_unwind(AssertUnwindSafe(|| {
2382 let name = read_required_host_string(&state.host, name, "LLM name")?;
2383 let request: LlmRequest = read_json_value(&state.host, request_json, "LLM request")?;
2384 let annotated: Option<AnnotatedLlmRequest> =
2385 read_optional_json_value(&state.host, annotated_json, "annotated LLM request")?;
2386 match (state.callback)(&name, request, annotated) {
2387 Ok(outcome) => {
2388 let Some(outcome) = HostString::from_json(&state.host, &outcome) else {
2389 set_last_error(&state.host, "failed to allocate LLM request outcome");
2390 return Ok(NemoRelayStatus::Internal);
2391 };
2392 unsafe {
2393 *out_outcome_json = outcome.ptr;
2394 }
2395 std::mem::forget(outcome);
2396 Ok(NemoRelayStatus::Ok)
2397 }
2398 Err(message) => Ok(callback_error(&state.host, message)),
2399 }
2400 }));
2401 match result {
2402 Ok(Ok(status)) => status,
2403 Ok(Err(status)) => status,
2404 Err(_) => callback_panic(&state.host, "LLM request intercept callback"),
2405 }
2406}
2407
2408unsafe extern "C" fn typed_llm_execution_trampoline<F>(
2409 user_data: *mut c_void,
2410 name: *const NemoRelayNativeString,
2411 request_json: *const NemoRelayNativeString,
2412 next_fn: NemoRelayNativeLlmNextFn,
2413 next_ctx: *mut c_void,
2414 out_json: *mut *mut NemoRelayNativeString,
2415) -> NemoRelayStatus
2416where
2417 F: for<'next> Fn(&str, LlmRequest, LlmNext<'next>) -> Result<Json> + Send + Sync + 'static,
2418{
2419 if user_data.is_null() || out_json.is_null() {
2420 return NemoRelayStatus::NullPointer;
2421 }
2422 unsafe { *out_json = ptr::null_mut() };
2423 let state = unsafe { &*(user_data as *const TypedCallback<F>) };
2424 let result = catch_unwind(AssertUnwindSafe(|| {
2425 let name = read_required_host_string(&state.host, name, "LLM name")?;
2426 let request: LlmRequest = read_json_value(&state.host, request_json, "LLM request")?;
2427 let next = LlmNext {
2428 host: &state.host,
2429 next_fn,
2430 next_ctx,
2431 };
2432 match (state.callback)(&name, request, next) {
2433 Ok(output) => Ok::<_, NemoRelayStatus>(write_json(&state.host, &output, out_json)),
2434 Err(message) => Ok(callback_error(&state.host, message)),
2435 }
2436 }));
2437 match result {
2438 Ok(Ok(status)) => status,
2439 Ok(Err(status)) => status,
2440 Err(_) => callback_panic(&state.host, "LLM execution callback"),
2441 }
2442}
2443
2444struct TypedLlmJsonStream {
2445 host: NemoRelayNativeHostApiV1,
2446 state: Mutex<TypedLlmJsonStreamState>,
2447}
2448
2449struct TypedLlmJsonStreamState {
2450 iter: LlmJsonStream,
2451 finished: bool,
2452}
2453
2454fn native_stream_from_iter(
2455 host: &NemoRelayNativeHostApiV1,
2456 iter: LlmJsonStream,
2457) -> NemoRelayNativeLlmStreamV1 {
2458 let state = Box::new(TypedLlmJsonStream {
2459 host: *host,
2460 state: Mutex::new(TypedLlmJsonStreamState {
2461 iter,
2462 finished: false,
2463 }),
2464 });
2465 NemoRelayNativeLlmStreamV1 {
2466 struct_size: std::mem::size_of::<NemoRelayNativeLlmStreamV1>(),
2467 user_data: Box::into_raw(state).cast(),
2468 next: Some(poll_typed_llm_json_stream),
2469 cancel: Some(cancel_typed_llm_json_stream),
2470 drop: Some(drop_typed_llm_json_stream),
2471 }
2472}
2473
2474unsafe extern "C" fn poll_typed_llm_json_stream(
2475 user_data: *mut c_void,
2476 out_json: *mut *mut NemoRelayNativeString,
2477) -> NemoRelayStatus {
2478 if user_data.is_null() || out_json.is_null() {
2479 return NemoRelayStatus::NullPointer;
2480 }
2481 unsafe { *out_json = ptr::null_mut() };
2482 let stream = unsafe { &*(user_data as *const TypedLlmJsonStream) };
2483 let result = catch_unwind(AssertUnwindSafe(|| {
2484 let mut state = match stream.state.lock() {
2485 Ok(state) => state,
2486 Err(_) => {
2487 set_last_error(&stream.host, "native plugin stream state lock poisoned");
2488 return NemoRelayStatus::Internal;
2489 }
2490 };
2491 if state.finished {
2492 return NemoRelayStatus::StreamEnd;
2493 }
2494 match state.iter.next() {
2495 Some(Ok(chunk)) => {
2496 let status = write_json(&stream.host, &chunk, out_json);
2497 if status != NemoRelayStatus::Ok {
2498 state.finished = true;
2499 }
2500 status
2501 }
2502 Some(Err(message)) => {
2503 state.finished = true;
2504 callback_error(&stream.host, message)
2505 }
2506 None => {
2507 state.finished = true;
2508 NemoRelayStatus::StreamEnd
2509 }
2510 }
2511 }));
2512 result.unwrap_or_else(|_| callback_panic(&stream.host, "LLM stream callback"))
2513}
2514
2515unsafe extern "C" fn cancel_typed_llm_json_stream(user_data: *mut c_void) -> NemoRelayStatus {
2516 if user_data.is_null() {
2517 return NemoRelayStatus::NullPointer;
2518 }
2519 let stream = unsafe { &*(user_data as *const TypedLlmJsonStream) };
2520 let result = catch_unwind(AssertUnwindSafe(|| {
2521 let mut state = match stream.state.lock() {
2522 Ok(state) => state,
2523 Err(_) => {
2524 set_last_error(&stream.host, "native plugin stream state lock poisoned");
2525 return NemoRelayStatus::Internal;
2526 }
2527 };
2528 state.finished = true;
2529 NemoRelayStatus::Ok
2530 }));
2531 result.unwrap_or_else(|_| callback_panic(&stream.host, "LLM stream cancel callback"))
2532}
2533
2534unsafe extern "C" fn drop_typed_llm_json_stream(user_data: *mut c_void) {
2535 if !user_data.is_null() {
2536 let stream = unsafe { Box::from_raw(user_data as *mut TypedLlmJsonStream) };
2537 let host = stream.host;
2538 if catch_unwind(AssertUnwindSafe(|| drop(stream))).is_err() {
2539 set_last_error(&host, "native plugin LLM stream state drop panicked");
2540 }
2541 }
2542}
2543
2544unsafe extern "C" fn typed_llm_stream_execution_trampoline<F>(
2545 user_data: *mut c_void,
2546 name: *const NemoRelayNativeString,
2547 request_json: *const NemoRelayNativeString,
2548 next_fn: NemoRelayNativeLlmStreamNextFn,
2549 next_ctx: *mut c_void,
2550 out_stream: *mut NemoRelayNativeLlmStreamV1,
2551) -> NemoRelayStatus
2552where
2553 F: for<'next> Fn(&str, LlmRequest, LlmStreamNext<'next>) -> Result<LlmJsonStream>
2554 + Send
2555 + Sync
2556 + 'static,
2557{
2558 if user_data.is_null() || out_stream.is_null() {
2559 return NemoRelayStatus::NullPointer;
2560 }
2561 unsafe { *out_stream = NemoRelayNativeLlmStreamV1::default() };
2562 let state = unsafe { &*(user_data as *const TypedCallback<F>) };
2563 let result = catch_unwind(AssertUnwindSafe(|| {
2564 let name = read_required_host_string(&state.host, name, "LLM name")?;
2565 let request: LlmRequest = read_json_value(&state.host, request_json, "LLM request")?;
2566 let next = LlmStreamNext {
2567 host: &state.host,
2568 next_fn,
2569 next_ctx,
2570 };
2571 match (state.callback)(&name, request, next) {
2572 Ok(stream) => {
2573 unsafe { *out_stream = native_stream_from_iter(&state.host, stream) };
2574 Ok::<_, NemoRelayStatus>(NemoRelayStatus::Ok)
2575 }
2576 Err(message) => Ok(callback_error(&state.host, message)),
2577 }
2578 }));
2579 match result {
2580 Ok(Ok(status)) => status,
2581 Ok(Err(status)) => status,
2582 Err(_) => callback_panic(&state.host, "LLM stream execution callback"),
2583 }
2584}
2585
2586struct HostString<'a> {
2587 host: &'a NemoRelayNativeHostApiV1,
2588 ptr: *mut NemoRelayNativeString,
2589}
2590
2591impl<'a> HostString<'a> {
2592 fn try_new(
2593 host: &'a NemoRelayNativeHostApiV1,
2594 value: &str,
2595 ) -> std::result::Result<Self, NemoRelayStatus> {
2596 let mut out = ptr::null_mut();
2597 let status = unsafe { (host.string_new)(value.as_ptr(), value.len(), &mut out) };
2598 if status != NemoRelayStatus::Ok {
2599 return Err(status);
2600 }
2601 if out.is_null() {
2602 return Err(NemoRelayStatus::Internal);
2603 }
2604 Ok(Self { host, ptr: out })
2605 }
2606
2607 fn new(host: &'a NemoRelayNativeHostApiV1, value: &str) -> Option<Self> {
2608 Self::try_new(host, value).ok()
2609 }
2610
2611 fn from_json<T: Serialize>(host: &'a NemoRelayNativeHostApiV1, value: &T) -> Option<Self> {
2612 serde_json::to_string(value)
2613 .ok()
2614 .and_then(|json| Self::new(host, &json))
2615 }
2616
2617 fn as_ptr(&self) -> *const NemoRelayNativeString {
2618 self.ptr
2619 }
2620}
2621
2622impl Drop for HostString<'_> {
2623 fn drop(&mut self) {
2624 unsafe { (self.host.string_free)(self.ptr) };
2625 }
2626}
2627
2628struct OptionalHostJson<'a>(Option<HostString<'a>>);
2629
2630impl<'a> OptionalHostJson<'a> {
2631 fn new(host: &'a NemoRelayNativeHostApiV1, value: Option<&Json>) -> Result<Self> {
2632 match value {
2633 Some(value) => HostString::from_json(host, value)
2634 .map(|value| Self(Some(value)))
2635 .ok_or_else(|| "failed to allocate JSON host string".into()),
2636 None => Ok(Self(None)),
2637 }
2638 }
2639
2640 fn as_ptr(&self) -> *const NemoRelayNativeString {
2641 self.0
2642 .as_ref()
2643 .map(HostString::as_ptr)
2644 .unwrap_or(ptr::null())
2645 }
2646}
2647
2648struct PluginState<P> {
2649 host: NemoRelayNativeHostApiV1,
2650 plugin: Mutex<P>,
2651}
2652
2653unsafe extern "C" fn drop_plugin_state<P: NativePlugin>(user_data: *mut c_void) {
2654 if !user_data.is_null() {
2655 let state = unsafe { Box::from_raw(user_data as *mut PluginState<P>) };
2656 let host = state.host;
2657 if catch_unwind(AssertUnwindSafe(|| drop(state))).is_err() {
2658 set_last_error(&host, "native plugin state drop panicked");
2659 }
2660 }
2661}
2662
2663unsafe extern "C" fn validate_trampoline<P: NativePlugin>(
2664 user_data: *mut c_void,
2665 plugin_config_json: *const NemoRelayNativeString,
2666 out_diagnostics_json: *mut *mut NemoRelayNativeString,
2667) -> NemoRelayStatus {
2668 if user_data.is_null() || out_diagnostics_json.is_null() {
2669 return NemoRelayStatus::NullPointer;
2670 }
2671 unsafe { *out_diagnostics_json = ptr::null_mut() };
2672 let state = unsafe { &*(user_data as *const PluginState<P>) };
2673 let result = catch_unwind(AssertUnwindSafe(|| {
2674 let config = match read_json_object(&state.host, plugin_config_json) {
2675 Ok(config) => config,
2676 Err(status) => return status,
2677 };
2678 let plugin = match state.plugin.lock() {
2679 Ok(plugin) => plugin,
2680 Err(_) => {
2681 set_last_error(&state.host, "native plugin state lock poisoned");
2682 return NemoRelayStatus::Internal;
2683 }
2684 };
2685 let diagnostics = plugin.validate(&config);
2686 write_json(&state.host, &diagnostics, out_diagnostics_json)
2687 }));
2688 result.unwrap_or_else(|_| {
2689 set_last_error(&state.host, "native plugin validate callback panicked");
2690 NemoRelayStatus::Internal
2691 })
2692}
2693
2694unsafe extern "C" fn register_trampoline<P: NativePlugin>(
2695 user_data: *mut c_void,
2696 plugin_config_json: *const NemoRelayNativeString,
2697 ctx: *mut NemoRelayNativePluginContext,
2698) -> NemoRelayStatus {
2699 if user_data.is_null() || ctx.is_null() {
2700 return NemoRelayStatus::NullPointer;
2701 }
2702 let state = unsafe { &*(user_data as *const PluginState<P>) };
2703 let result = catch_unwind(AssertUnwindSafe(|| {
2704 let config = match read_json_object(&state.host, plugin_config_json) {
2705 Ok(config) => config,
2706 Err(status) => return status,
2707 };
2708 let mut ctx = unsafe { PluginContext::from_raw(&state.host, ctx) };
2709 let mut plugin = match state.plugin.lock() {
2710 Ok(plugin) => plugin,
2711 Err(_) => {
2712 set_last_error(&state.host, "native plugin state lock poisoned");
2713 return NemoRelayStatus::Internal;
2714 }
2715 };
2716 match plugin.register(&config, &mut ctx) {
2717 Ok(()) => NemoRelayStatus::Ok,
2718 Err(message) => {
2719 set_last_error(&state.host, &message);
2720 NemoRelayStatus::Internal
2721 }
2722 }
2723 }));
2724 result.unwrap_or_else(|_| {
2725 set_last_error(&state.host, "native plugin register callback panicked");
2726 NemoRelayStatus::Internal
2727 })
2728}
2729
2730fn read_json_object(
2731 host: &NemoRelayNativeHostApiV1,
2732 value: *const NemoRelayNativeString,
2733) -> std::result::Result<Map<String, Json>, NemoRelayStatus> {
2734 let value: Json = read_json_value(host, value, "plugin config")?;
2735 match value {
2736 Json::Object(map) => Ok(map),
2737 _ => {
2738 set_last_error(host, "plugin config must be a JSON object");
2739 Err(NemoRelayStatus::InvalidJson)
2740 }
2741 }
2742}
2743
2744fn read_json_value<T: DeserializeOwned>(
2745 host: &NemoRelayNativeHostApiV1,
2746 value: *const NemoRelayNativeString,
2747 label: &str,
2748) -> std::result::Result<T, NemoRelayStatus> {
2749 let text = read_required_host_string(host, value, label)?;
2750 serde_json::from_str::<T>(&text).map_err(|error| {
2751 set_last_error(host, &format!("{label} was invalid JSON: {error}"));
2752 NemoRelayStatus::InvalidJson
2753 })
2754}
2755
2756fn read_optional_json_value<T: DeserializeOwned>(
2757 host: &NemoRelayNativeHostApiV1,
2758 value: *const NemoRelayNativeString,
2759 label: &str,
2760) -> std::result::Result<Option<T>, NemoRelayStatus> {
2761 if value.is_null() {
2762 Ok(None)
2763 } else {
2764 read_json_value(host, value, label).map(Some)
2765 }
2766}
2767
2768enum HostStringReadError {
2769 Null,
2770 InvalidUtf8,
2771}
2772
2773fn read_required_host_string(
2774 host: &NemoRelayNativeHostApiV1,
2775 value: *const NemoRelayNativeString,
2776 label: &str,
2777) -> std::result::Result<String, NemoRelayStatus> {
2778 match read_host_string(host, value) {
2779 Ok(value) => Ok(value),
2780 Err(HostStringReadError::Null) => {
2781 set_last_error(host, &format!("{label} was null"));
2782 Err(NemoRelayStatus::NullPointer)
2783 }
2784 Err(HostStringReadError::InvalidUtf8) => {
2785 set_last_error(host, &format!("{label} contained invalid UTF-8"));
2786 Err(NemoRelayStatus::InvalidUtf8)
2787 }
2788 }
2789}
2790
2791fn read_host_string(
2792 host: &NemoRelayNativeHostApiV1,
2793 value: *const NemoRelayNativeString,
2794) -> std::result::Result<String, HostStringReadError> {
2795 if value.is_null() {
2796 return Err(HostStringReadError::Null);
2797 }
2798 let len = unsafe { (host.string_len)(value) };
2799 let data = unsafe { (host.string_data)(value) };
2800 if data.is_null() && len > 0 {
2801 return Err(HostStringReadError::InvalidUtf8);
2802 }
2803 let bytes = if len == 0 {
2804 &[][..]
2805 } else {
2806 unsafe { std::slice::from_raw_parts(data, len) }
2807 };
2808 std::str::from_utf8(bytes)
2809 .map(str::to_owned)
2810 .map_err(|_| HostStringReadError::InvalidUtf8)
2811}
2812
2813fn write_json<T: Serialize>(
2814 host: &NemoRelayNativeHostApiV1,
2815 value: &T,
2816 out: *mut *mut NemoRelayNativeString,
2817) -> NemoRelayStatus {
2818 if out.is_null() {
2819 return NemoRelayStatus::NullPointer;
2820 }
2821 unsafe { *out = ptr::null_mut() };
2822 let json = serde_json::to_value(value).expect("Relay DTOs and serde_json::Value serialize");
2823 let Some(handle) = HostString::from_json(host, &json) else {
2824 set_last_error(host, "failed to allocate host string");
2825 return NemoRelayStatus::Internal;
2826 };
2827 unsafe { *out = handle.ptr };
2828 std::mem::forget(handle);
2829 NemoRelayStatus::Ok
2830}
2831
2832fn set_last_error(host: &NemoRelayNativeHostApiV1, message: &str) {
2833 if let Some(message) = HostString::new(host, message) {
2834 unsafe { (host.last_error_set)(message.as_ptr()) };
2835 }
2836}
2837
2838#[doc(hidden)]
2843pub unsafe fn __set_last_error_from_entry(host: *const NemoRelayNativeHostApiV1, message: &str) {
2844 if !host.is_null() {
2845 set_last_error(unsafe { &*host }, message);
2846 }
2847}
2848
2849pub unsafe fn export_plugin<P: NativePlugin>(
2856 host: *const NemoRelayNativeHostApiV1,
2857 out: *mut NemoRelayNativePluginV1,
2858 plugin: P,
2859) -> NemoRelayStatus {
2860 if host.is_null() || out.is_null() {
2861 return NemoRelayStatus::NullPointer;
2862 }
2863 unsafe { *out = NemoRelayNativePluginV1::default() };
2864 let host_ref = unsafe { &*host };
2865 export_plugin_checked(host_ref, out, || plugin)
2866}
2867
2868#[doc(hidden)]
2875pub unsafe fn __export_plugin_from_constructor<P, F>(
2876 host: *const NemoRelayNativeHostApiV1,
2877 out: *mut NemoRelayNativePluginV1,
2878 constructor: F,
2879) -> NemoRelayStatus
2880where
2881 P: NativePlugin,
2882 F: FnOnce() -> P,
2883{
2884 if host.is_null() || out.is_null() {
2885 return NemoRelayStatus::NullPointer;
2886 }
2887 unsafe { *out = NemoRelayNativePluginV1::default() };
2888 let host_ref = unsafe { &*host };
2889 export_plugin_checked(host_ref, out, constructor)
2890}
2891
2892fn export_plugin_checked<P, F>(
2893 host_ref: &NemoRelayNativeHostApiV1,
2894 out: *mut NemoRelayNativePluginV1,
2895 constructor: F,
2896) -> NemoRelayStatus
2897where
2898 P: NativePlugin,
2899 F: FnOnce() -> P,
2900{
2901 if host_ref.abi_version != NEMO_RELAY_NATIVE_ABI_VERSION {
2902 return NemoRelayStatus::InvalidArg;
2903 }
2904 if host_ref.struct_size < std::mem::size_of::<NemoRelayNativeHostApiV1>() {
2905 return NemoRelayStatus::InvalidArg;
2906 }
2907
2908 let plugin = constructor();
2909 let kind = plugin.plugin_kind().to_owned();
2910 let allows_multiple_components = plugin.allows_multiple_components();
2911 let Some(kind_handle) = HostString::new(host_ref, &kind) else {
2912 return NemoRelayStatus::Internal;
2913 };
2914 let state = Box::new(PluginState {
2915 host: *host_ref,
2916 plugin: Mutex::new(plugin),
2917 });
2918 unsafe {
2919 *out = NemoRelayNativePluginV1 {
2920 struct_size: std::mem::size_of::<NemoRelayNativePluginV1>(),
2921 plugin_kind: kind_handle.ptr,
2922 allows_multiple_components,
2923 user_data: Box::into_raw(state) as *mut c_void,
2924 validate: Some(validate_trampoline::<P>),
2925 register: Some(register_trampoline::<P>),
2926 drop: Some(drop_plugin_state::<P>),
2927 };
2928 }
2929 std::mem::forget(kind_handle);
2930 NemoRelayStatus::Ok
2931}
2932
2933#[macro_export]
2935macro_rules! nemo_relay_plugin {
2936 ($symbol:ident, $constructor:expr) => {
2937 #[doc = "Native plugin entry symbol generated by `nemo_relay_plugin!`."]
2938 #[unsafe(no_mangle)]
2939 pub unsafe extern "C" fn $symbol(
2940 host: *const $crate::NemoRelayNativeHostApiV1,
2941 out: *mut $crate::NemoRelayNativePluginV1,
2942 ) -> $crate::NemoRelayStatus {
2943 match ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| unsafe {
2944 $crate::__export_plugin_from_constructor(host, out, $constructor)
2945 })) {
2946 Ok(status) => status,
2947 Err(_) => {
2948 unsafe {
2949 $crate::__set_last_error_from_entry(
2950 host,
2951 "native plugin entry callback panicked",
2952 )
2953 };
2954 $crate::NemoRelayStatus::Internal
2955 }
2956 }
2957 }
2958 };
2959}