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 = 3;
43pub const NEMO_RELAY_NATIVE_ABI_VERSION_ASYNC_MIDDLEWARE: u32 = 3;
45
46pub const NEMO_RELAY_NATIVE_ABI_VERSION_LEGACY: u32 = 2;
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, serde::Deserialize)]
51#[serde(rename_all = "snake_case")]
52pub enum BuiltinLlmCodec {
53 #[serde(rename = "openai_chat")]
55 OpenAiChat,
56 #[serde(rename = "openai_responses")]
58 OpenAiResponses,
59 #[serde(rename = "anthropic_messages")]
61 AnthropicMessages,
62}
63
64#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, serde::Deserialize)]
66#[serde(tag = "kind", content = "id", rename_all = "snake_case")]
67pub enum LlmCodecIdentity {
68 #[default]
70 None,
71 #[serde(rename = "builtin")]
73 BuiltIn(BuiltinLlmCodec),
74 Runtime(String),
76 Opaque,
78}
79
80pub struct LlmSanitizeRequestContext<'a> {
82 pub codec: LlmCodecIdentity,
84 resolved: Option<LlmSanitizeRequestCodec<'a>>,
85}
86
87pub struct LlmSanitizeResponseContext<'a> {
89 pub codec: LlmCodecIdentity,
91 resolved: Option<LlmSanitizeResponseCodec<'a>>,
92}
93
94#[repr(i32)]
96#[derive(Debug, Clone, Copy, PartialEq, Eq)]
97pub enum NemoRelayStatus {
98 Ok = 0,
100 AlreadyExists = 1,
102 NotFound = 2,
104 ScopeStackEmpty = 3,
106 GuardrailRejected = 4,
108 Internal = 5,
110 NullPointer = 6,
112 InvalidJson = 7,
114 InvalidUtf8 = 8,
116 InvalidArg = 9,
118 StreamEnd = 10,
120}
121
122#[repr(C)]
124pub struct NemoRelayNativeString {
125 _private: [u8; 0],
126 _marker: PhantomData<(*mut u8, PhantomPinned)>,
127}
128
129#[repr(C)]
131pub struct NemoRelayNativeLlmRequestCodec {
132 _private: [u8; 0],
133 _marker: PhantomData<(*mut u8, PhantomPinned)>,
134}
135
136#[repr(C)]
138pub struct NemoRelayNativeLlmResponseCodec {
139 _private: [u8; 0],
140 _marker: PhantomData<(*mut u8, PhantomPinned)>,
141}
142
143#[repr(u32)]
145#[derive(Debug, Clone, Copy, PartialEq, Eq)]
146pub enum NemoRelayNativeLlmCodecKind {
147 None = 0,
149 BuiltIn = 1,
151 Runtime = 2,
153 Opaque = 3,
155}
156
157#[repr(C)]
164#[derive(Debug, Clone, Copy)]
165pub struct NemoRelayNativeLlmSanitizeRequestContext {
166 pub codec_kind: NemoRelayNativeLlmCodecKind,
168 pub codec_id: *const NemoRelayNativeString,
170 pub codec: *const NemoRelayNativeLlmRequestCodec,
172}
173
174#[repr(C)]
176#[derive(Debug, Clone, Copy)]
177pub struct NemoRelayNativeLlmSanitizeResponseContext {
178 pub codec_kind: NemoRelayNativeLlmCodecKind,
180 pub codec_id: *const NemoRelayNativeString,
182 pub codec: *const NemoRelayNativeLlmResponseCodec,
184}
185
186pub struct LlmSanitizeRequestCodec<'a> {
188 host: NemoRelayNativeHostApiV1,
189 handle: *const NemoRelayNativeLlmRequestCodec,
190 _lifetime: PhantomData<&'a NemoRelayNativeLlmRequestCodec>,
191}
192
193impl LlmSanitizeRequestCodec<'_> {
194 pub fn decode(&self, request: &LlmRequest) -> Result<AnnotatedLlmRequest> {
196 native_codec_call(&self.host, |out| unsafe {
197 let request = HostString::from_json(&self.host, request)
198 .ok_or_else(|| "failed to serialize LLM request".to_string())?;
199 codec_status(
200 &self.host,
201 (self.host.llm_request_codec_decode)(self.handle, request.as_ptr(), out),
202 )
203 })
204 }
205
206 pub fn encode(
208 &self,
209 annotated: &AnnotatedLlmRequest,
210 original: &LlmRequest,
211 ) -> Result<LlmRequest> {
212 native_codec_call(&self.host, |out| unsafe {
213 let annotated = HostString::from_json(&self.host, annotated)
214 .ok_or_else(|| "failed to serialize annotated request".to_string())?;
215 let original = HostString::from_json(&self.host, original)
216 .ok_or_else(|| "failed to serialize original request".to_string())?;
217 codec_status(
218 &self.host,
219 (self.host.llm_request_codec_encode)(
220 self.handle,
221 annotated.as_ptr(),
222 original.as_ptr(),
223 out,
224 ),
225 )
226 })
227 }
228}
229
230pub struct LlmSanitizeResponseCodec<'a> {
232 host: NemoRelayNativeHostApiV1,
233 handle: *const NemoRelayNativeLlmResponseCodec,
234 _lifetime: PhantomData<&'a NemoRelayNativeLlmResponseCodec>,
235}
236
237impl LlmSanitizeResponseCodec<'_> {
238 pub fn decode(&self, response: &Json) -> Result<AnnotatedLlmResponse> {
240 native_codec_call(&self.host, |out| unsafe {
241 let response = HostString::from_json(&self.host, response)
242 .ok_or_else(|| "failed to serialize LLM response".to_string())?;
243 codec_status(
244 &self.host,
245 (self.host.llm_response_codec_decode)(self.handle, response.as_ptr(), out),
246 )
247 })
248 }
249}
250
251impl<'a> LlmSanitizeRequestContext<'a> {
252 #[must_use]
254 pub fn resolve_codec(&self) -> Option<&LlmSanitizeRequestCodec<'a>> {
255 self.resolved.as_ref()
256 }
257}
258
259impl<'a> LlmSanitizeResponseContext<'a> {
260 #[must_use]
262 pub fn resolve_codec(&self) -> Option<&LlmSanitizeResponseCodec<'a>> {
263 self.resolved.as_ref()
264 }
265}
266
267#[repr(C)]
269pub struct NemoRelayNativePluginContext {
270 _private: [u8; 0],
271 _marker: PhantomData<(*mut u8, PhantomPinned)>,
272}
273
274#[repr(C)]
276pub struct NemoRelayNativeScopeHandle {
277 _private: [u8; 0],
278 _marker: PhantomData<(*mut u8, PhantomPinned)>,
279}
280
281#[repr(C)]
283pub struct NemoRelayNativeScopeStack {
284 _private: [u8; 0],
285 _marker: PhantomData<(*mut u8, PhantomPinned)>,
286}
287
288#[repr(C)]
290pub struct NemoRelayNativeScopeStackBinding {
291 _private: [u8; 0],
292 _marker: PhantomData<(*mut u8, PhantomPinned)>,
293}
294
295#[repr(i32)]
297#[derive(Debug, Clone, Copy, PartialEq, Eq)]
298pub enum NemoRelayNativeScopeType {
299 Agent = 0,
301 Function = 1,
303 Tool = 2,
305 Llm = 3,
307 Retriever = 4,
309 Embedder = 5,
311 Reranker = 6,
313 Guardrail = 7,
315 Evaluator = 8,
317 Custom = 9,
319 Unknown = 10,
321}
322
323pub type NemoRelayNativeFreeFn = Option<unsafe extern "C" fn(user_data: *mut c_void)>;
325
326pub type NemoRelayNativeWithScopeStackCb =
328 unsafe extern "C" fn(user_data: *mut c_void) -> NemoRelayStatus;
329
330pub type NemoRelayNativeToolNextFn = unsafe extern "C" fn(
332 args_json: *const NemoRelayNativeString,
333 next_ctx: *mut c_void,
334 out_json: *mut *mut NemoRelayNativeString,
335) -> NemoRelayStatus;
336
337pub type NemoRelayNativeLlmNextFn = unsafe extern "C" fn(
339 request_json: *const NemoRelayNativeString,
340 next_ctx: *mut c_void,
341 out_json: *mut *mut NemoRelayNativeString,
342) -> NemoRelayStatus;
343
344pub type NemoRelayNativeLlmStreamPollFn = unsafe extern "C" fn(
350 user_data: *mut c_void,
351 out_json: *mut *mut NemoRelayNativeString,
352) -> NemoRelayStatus;
353
354pub type NemoRelayNativeLlmStreamCancelFn =
356 Option<unsafe extern "C" fn(user_data: *mut c_void) -> NemoRelayStatus>;
357
358pub type NemoRelayNativeLlmStreamDropFn = Option<unsafe extern "C" fn(user_data: *mut c_void)>;
360
361#[repr(C)]
363pub struct NemoRelayNativeLlmStreamV1 {
364 pub struct_size: usize,
366 pub user_data: *mut c_void,
368 pub next: Option<NemoRelayNativeLlmStreamPollFn>,
370 pub cancel: NemoRelayNativeLlmStreamCancelFn,
372 pub drop: NemoRelayNativeLlmStreamDropFn,
374}
375
376impl Default for NemoRelayNativeLlmStreamV1 {
377 fn default() -> Self {
378 Self {
379 struct_size: std::mem::size_of::<Self>(),
380 user_data: ptr::null_mut(),
381 next: None,
382 cancel: None,
383 drop: None,
384 }
385 }
386}
387
388pub type NemoRelayNativeLlmStreamNextFn = unsafe extern "C" fn(
390 request_json: *const NemoRelayNativeString,
391 next_ctx: *mut c_void,
392 out_stream: *mut NemoRelayNativeLlmStreamV1,
393) -> NemoRelayStatus;
394
395pub type NemoRelayNativeEventSubscriberCb = unsafe extern "C" fn(
397 user_data: *mut c_void,
398 event_json: *const NemoRelayNativeString,
399) -> NemoRelayStatus;
400
401pub type NemoRelayNativeEventSanitizeCb = unsafe extern "C" fn(
403 user_data: *mut c_void,
404 event_json: *const NemoRelayNativeString,
405 fields_json: *const NemoRelayNativeString,
406 out_fields_json: *mut *mut NemoRelayNativeString,
407) -> NemoRelayStatus;
408
409pub type NemoRelayNativeToolJsonCb = unsafe extern "C" fn(
411 user_data: *mut c_void,
412 name: *const NemoRelayNativeString,
413 payload_json: *const NemoRelayNativeString,
414 out_json: *mut *mut NemoRelayNativeString,
415) -> NemoRelayStatus;
416
417pub type NemoRelayNativeToolConditionalCb = unsafe extern "C" fn(
419 user_data: *mut c_void,
420 name: *const NemoRelayNativeString,
421 args_json: *const NemoRelayNativeString,
422 out_reason: *mut *mut NemoRelayNativeString,
423) -> NemoRelayStatus;
424
425pub type NemoRelayNativeToolExecutionCb = unsafe extern "C" fn(
427 user_data: *mut c_void,
428 name: *const NemoRelayNativeString,
429 args_json: *const NemoRelayNativeString,
430 next_fn: NemoRelayNativeToolNextFn,
431 next_ctx: *mut c_void,
432 out_outcome_json: *mut *mut NemoRelayNativeString,
433) -> NemoRelayStatus;
434
435pub type NemoRelayNativeLlmSanitizeRequestCb = unsafe extern "C" fn(
441 user_data: *mut c_void,
442 request_json: *const NemoRelayNativeString,
443 context: NemoRelayNativeLlmSanitizeRequestContext,
444 out_request_json: *mut *mut NemoRelayNativeString,
445) -> NemoRelayStatus;
446
447pub type NemoRelayNativeLlmSanitizeResponseCb = unsafe extern "C" fn(
453 user_data: *mut c_void,
454 payload_json: *const NemoRelayNativeString,
455 context: NemoRelayNativeLlmSanitizeResponseContext,
456 out_json: *mut *mut NemoRelayNativeString,
457) -> NemoRelayStatus;
458
459pub type NemoRelayNativeLlmConditionalCb = unsafe extern "C" fn(
461 user_data: *mut c_void,
462 request_json: *const NemoRelayNativeString,
463 out_reason: *mut *mut NemoRelayNativeString,
464) -> NemoRelayStatus;
465
466pub type NemoRelayNativeLlmRequestInterceptCb = unsafe extern "C" fn(
468 user_data: *mut c_void,
469 name: *const NemoRelayNativeString,
470 request_json: *const NemoRelayNativeString,
471 annotated_json: *const NemoRelayNativeString,
472 out_outcome_json: *mut *mut NemoRelayNativeString,
473) -> NemoRelayStatus;
474
475pub type NemoRelayNativeLlmExecutionCb = unsafe extern "C" fn(
477 user_data: *mut c_void,
478 name: *const NemoRelayNativeString,
479 request_json: *const NemoRelayNativeString,
480 next_fn: NemoRelayNativeLlmNextFn,
481 next_ctx: *mut c_void,
482 out_json: *mut *mut NemoRelayNativeString,
483) -> NemoRelayStatus;
484
485pub type NemoRelayNativeLlmStreamExecutionCb = unsafe extern "C" fn(
487 user_data: *mut c_void,
488 name: *const NemoRelayNativeString,
489 request_json: *const NemoRelayNativeString,
490 next_fn: NemoRelayNativeLlmStreamNextFn,
491 next_ctx: *mut c_void,
492 out_stream: *mut NemoRelayNativeLlmStreamV1,
493) -> NemoRelayStatus;
494
495pub type NemoRelayNativePluginValidateFn = unsafe extern "C" fn(
497 user_data: *mut c_void,
498 plugin_config_json: *const NemoRelayNativeString,
499 out_diagnostics_json: *mut *mut NemoRelayNativeString,
500) -> NemoRelayStatus;
501
502pub type NemoRelayNativePluginRegisterFn = unsafe extern "C" fn(
504 user_data: *mut c_void,
505 plugin_config_json: *const NemoRelayNativeString,
506 ctx: *mut NemoRelayNativePluginContext,
507) -> NemoRelayStatus;
508
509pub type NemoRelayNativePluginDropFn = Option<unsafe extern "C" fn(user_data: *mut c_void)>;
511
512#[repr(C)]
514#[derive(Clone, Copy)]
515pub struct NemoRelayNativeHostApiV1 {
516 pub abi_version: u32,
518 pub struct_size: usize,
520 pub relay_version: *const c_char,
522 pub string_new: unsafe extern "C" fn(
524 data: *const u8,
525 len: usize,
526 out: *mut *mut NemoRelayNativeString,
527 ) -> NemoRelayStatus,
528 pub string_data: unsafe extern "C" fn(value: *const NemoRelayNativeString) -> *const u8,
530 pub string_len: unsafe extern "C" fn(value: *const NemoRelayNativeString) -> usize,
532 pub string_free: unsafe extern "C" fn(value: *mut NemoRelayNativeString),
534 pub last_error_clear: unsafe extern "C" fn(),
536 pub last_error_set: unsafe extern "C" fn(message: *const NemoRelayNativeString),
538 pub llm_request_codec_decode: unsafe extern "C" fn(
540 codec: *const NemoRelayNativeLlmRequestCodec,
541 request_json: *const NemoRelayNativeString,
542 out: *mut *mut NemoRelayNativeString,
543 ) -> NemoRelayStatus,
544 pub llm_request_codec_encode: unsafe extern "C" fn(
546 codec: *const NemoRelayNativeLlmRequestCodec,
547 annotated_json: *const NemoRelayNativeString,
548 original_json: *const NemoRelayNativeString,
549 out: *mut *mut NemoRelayNativeString,
550 ) -> NemoRelayStatus,
551 pub llm_response_codec_decode: unsafe extern "C" fn(
553 codec: *const NemoRelayNativeLlmResponseCodec,
554 response_json: *const NemoRelayNativeString,
555 out: *mut *mut NemoRelayNativeString,
556 ) -> NemoRelayStatus,
557 pub plugin_context_register_subscriber: unsafe extern "C" fn(
559 ctx: *mut NemoRelayNativePluginContext,
560 name: *const NemoRelayNativeString,
561 cb: NemoRelayNativeEventSubscriberCb,
562 user_data: *mut c_void,
563 free_fn: NemoRelayNativeFreeFn,
564 ) -> NemoRelayStatus,
565 pub plugin_context_register_tool_sanitize_request_guardrail:
567 unsafe extern "C" fn(
568 ctx: *mut NemoRelayNativePluginContext,
569 name: *const NemoRelayNativeString,
570 priority: i32,
571 cb: NemoRelayNativeToolJsonCb,
572 user_data: *mut c_void,
573 free_fn: NemoRelayNativeFreeFn,
574 ) -> NemoRelayStatus,
575 pub plugin_context_register_tool_sanitize_response_guardrail:
577 unsafe extern "C" fn(
578 ctx: *mut NemoRelayNativePluginContext,
579 name: *const NemoRelayNativeString,
580 priority: i32,
581 cb: NemoRelayNativeToolJsonCb,
582 user_data: *mut c_void,
583 free_fn: NemoRelayNativeFreeFn,
584 ) -> NemoRelayStatus,
585 pub plugin_context_register_tool_conditional_execution_guardrail:
587 unsafe extern "C" fn(
588 ctx: *mut NemoRelayNativePluginContext,
589 name: *const NemoRelayNativeString,
590 priority: i32,
591 cb: NemoRelayNativeToolConditionalCb,
592 user_data: *mut c_void,
593 free_fn: NemoRelayNativeFreeFn,
594 ) -> NemoRelayStatus,
595 pub plugin_context_register_tool_request_intercept: unsafe extern "C" fn(
597 ctx: *mut NemoRelayNativePluginContext,
598 name: *const NemoRelayNativeString,
599 priority: i32,
600 break_chain: bool,
601 cb: NemoRelayNativeToolJsonCb,
602 user_data: *mut c_void,
603 free_fn: NemoRelayNativeFreeFn,
604 )
605 -> NemoRelayStatus,
606 pub plugin_context_register_tool_execution_intercept: unsafe extern "C" fn(
608 ctx: *mut NemoRelayNativePluginContext,
609 name: *const NemoRelayNativeString,
610 priority: i32,
611 cb: NemoRelayNativeToolExecutionCb,
612 user_data: *mut c_void,
613 free_fn: NemoRelayNativeFreeFn,
614 )
615 -> NemoRelayStatus,
616 pub plugin_context_register_llm_sanitize_request_guardrail:
618 unsafe extern "C" fn(
619 ctx: *mut NemoRelayNativePluginContext,
620 name: *const NemoRelayNativeString,
621 priority: i32,
622 cb: NemoRelayNativeLlmSanitizeRequestCb,
623 user_data: *mut c_void,
624 free_fn: NemoRelayNativeFreeFn,
625 ) -> NemoRelayStatus,
626 pub plugin_context_register_llm_sanitize_response_guardrail:
628 unsafe extern "C" fn(
629 ctx: *mut NemoRelayNativePluginContext,
630 name: *const NemoRelayNativeString,
631 priority: i32,
632 cb: NemoRelayNativeLlmSanitizeResponseCb,
633 user_data: *mut c_void,
634 free_fn: NemoRelayNativeFreeFn,
635 ) -> NemoRelayStatus,
636 pub plugin_context_register_llm_conditional_execution_guardrail:
638 unsafe extern "C" fn(
639 ctx: *mut NemoRelayNativePluginContext,
640 name: *const NemoRelayNativeString,
641 priority: i32,
642 cb: NemoRelayNativeLlmConditionalCb,
643 user_data: *mut c_void,
644 free_fn: NemoRelayNativeFreeFn,
645 ) -> NemoRelayStatus,
646 pub plugin_context_register_llm_request_intercept: unsafe extern "C" fn(
648 ctx: *mut NemoRelayNativePluginContext,
649 name: *const NemoRelayNativeString,
650 priority: i32,
651 break_chain: bool,
652 cb: NemoRelayNativeLlmRequestInterceptCb,
653 user_data: *mut c_void,
654 free_fn: NemoRelayNativeFreeFn,
655 ) -> NemoRelayStatus,
656 pub plugin_context_register_llm_execution_intercept: unsafe extern "C" fn(
658 ctx: *mut NemoRelayNativePluginContext,
659 name: *const NemoRelayNativeString,
660 priority: i32,
661 cb: NemoRelayNativeLlmExecutionCb,
662 user_data: *mut c_void,
663 free_fn: NemoRelayNativeFreeFn,
664 )
665 -> NemoRelayStatus,
666 pub plugin_context_register_llm_stream_execution_intercept:
668 unsafe extern "C" fn(
669 ctx: *mut NemoRelayNativePluginContext,
670 name: *const NemoRelayNativeString,
671 priority: i32,
672 cb: NemoRelayNativeLlmStreamExecutionCb,
673 user_data: *mut c_void,
674 free_fn: NemoRelayNativeFreeFn,
675 ) -> NemoRelayStatus,
676 pub scope_handle_free: unsafe extern "C" fn(handle: *mut NemoRelayNativeScopeHandle),
678 pub scope_get_current:
680 unsafe extern "C" fn(out: *mut *mut NemoRelayNativeScopeHandle) -> NemoRelayStatus,
681 pub scope_push: unsafe extern "C" fn(
683 name: *const NemoRelayNativeString,
684 scope_type: NemoRelayNativeScopeType,
685 parent: *const NemoRelayNativeScopeHandle,
686 attributes: u32,
687 data_json: *const NemoRelayNativeString,
688 metadata_json: *const NemoRelayNativeString,
689 input_json: *const NemoRelayNativeString,
690 timestamp_unix_micros: *const i64,
691 out: *mut *mut NemoRelayNativeScopeHandle,
692 ) -> NemoRelayStatus,
693 pub scope_pop: unsafe extern "C" fn(
695 handle: *const NemoRelayNativeScopeHandle,
696 output_json: *const NemoRelayNativeString,
697 metadata_json: *const NemoRelayNativeString,
698 timestamp_unix_micros: *const i64,
699 ) -> NemoRelayStatus,
700 pub emit_mark: unsafe extern "C" fn(
702 name: *const NemoRelayNativeString,
703 parent: *const NemoRelayNativeScopeHandle,
704 data_json: *const NemoRelayNativeString,
705 metadata_json: *const NemoRelayNativeString,
706 timestamp_unix_micros: *const i64,
707 ) -> NemoRelayStatus,
708 pub scope_stack_create:
710 unsafe extern "C" fn(out: *mut *mut NemoRelayNativeScopeStack) -> NemoRelayStatus,
711 pub scope_stack_free: unsafe extern "C" fn(stack: *mut NemoRelayNativeScopeStack),
713 pub scope_stack_set_thread:
715 unsafe extern "C" fn(stack: *const NemoRelayNativeScopeStack) -> NemoRelayStatus,
716 pub scope_stack_capture_thread:
718 unsafe extern "C" fn(out: *mut *mut NemoRelayNativeScopeStackBinding) -> NemoRelayStatus,
719 pub scope_stack_restore_thread:
721 unsafe extern "C" fn(binding: *mut NemoRelayNativeScopeStackBinding) -> NemoRelayStatus,
722 pub scope_stack_binding_free:
724 unsafe extern "C" fn(binding: *mut NemoRelayNativeScopeStackBinding),
725 pub scope_stack_active: unsafe extern "C" fn() -> bool,
727 pub scope_stack_with_current: unsafe extern "C" fn(
729 stack: *const NemoRelayNativeScopeStack,
730 cb: NemoRelayNativeWithScopeStackCb,
731 user_data: *mut c_void,
732 ) -> NemoRelayStatus,
733 pub plugin_context_register_mark_sanitize_guardrail: unsafe extern "C" fn(
735 ctx: *mut NemoRelayNativePluginContext,
736 name: *const NemoRelayNativeString,
737 priority: i32,
738 cb: NemoRelayNativeEventSanitizeCb,
739 user_data: *mut c_void,
740 free_fn: NemoRelayNativeFreeFn,
741 )
742 -> NemoRelayStatus,
743 pub plugin_context_register_scope_sanitize_start_guardrail:
745 unsafe extern "C" fn(
746 ctx: *mut NemoRelayNativePluginContext,
747 name: *const NemoRelayNativeString,
748 priority: i32,
749 cb: NemoRelayNativeEventSanitizeCb,
750 user_data: *mut c_void,
751 free_fn: NemoRelayNativeFreeFn,
752 ) -> NemoRelayStatus,
753 pub plugin_context_register_scope_sanitize_end_guardrail:
755 unsafe extern "C" fn(
756 ctx: *mut NemoRelayNativePluginContext,
757 name: *const NemoRelayNativeString,
758 priority: i32,
759 cb: NemoRelayNativeEventSanitizeCb,
760 user_data: *mut c_void,
761 free_fn: NemoRelayNativeFreeFn,
762 ) -> NemoRelayStatus,
763}
764
765#[repr(u32)]
771#[derive(Debug, Clone, Copy, PartialEq, Eq)]
772pub enum NemoRelayNativeAsyncMiddlewareKind {
773 ToolSanitizeRequest = 0,
775 ToolSanitizeResponse = 1,
777 ToolConditionalExecution = 2,
779 ToolRequestIntercept = 3,
781 ToolExecutionIntercept = 4,
783 LlmSanitizeRequest = 5,
785 LlmSanitizeResponse = 6,
787 LlmConditionalExecution = 7,
789 LlmRequestIntercept = 8,
791 LlmExecutionIntercept = 9,
793 LlmStreamExecutionIntercept = 10,
799 MarkSanitize = 11,
801 ScopeSanitizeStart = 12,
803 ScopeSanitizeEnd = 13,
805}
806
807impl TryFrom<u32> for NemoRelayNativeAsyncMiddlewareKind {
808 type Error = ();
809
810 fn try_from(value: u32) -> std::result::Result<Self, Self::Error> {
811 match value {
812 0 => Ok(Self::ToolSanitizeRequest),
813 1 => Ok(Self::ToolSanitizeResponse),
814 2 => Ok(Self::ToolConditionalExecution),
815 3 => Ok(Self::ToolRequestIntercept),
816 4 => Ok(Self::ToolExecutionIntercept),
817 5 => Ok(Self::LlmSanitizeRequest),
818 6 => Ok(Self::LlmSanitizeResponse),
819 7 => Ok(Self::LlmConditionalExecution),
820 8 => Ok(Self::LlmRequestIntercept),
821 9 => Ok(Self::LlmExecutionIntercept),
822 10 => Ok(Self::LlmStreamExecutionIntercept),
823 11 => Ok(Self::MarkSanitize),
824 12 => Ok(Self::ScopeSanitizeStart),
825 13 => Ok(Self::ScopeSanitizeEnd),
826 _ => Err(()),
827 }
828 }
829}
830
831#[repr(u32)]
833#[derive(Debug, Clone, Copy, PartialEq, Eq)]
834pub enum NemoRelayNativeAsyncCallbackState {
835 Complete = 0,
837 Pending = 1,
839}
840
841impl TryFrom<u32> for NemoRelayNativeAsyncCallbackState {
842 type Error = ();
843
844 fn try_from(value: u32) -> std::result::Result<Self, Self::Error> {
845 match value {
846 0 => Ok(Self::Complete),
847 1 => Ok(Self::Pending),
848 _ => Err(()),
849 }
850 }
851}
852
853#[repr(C)]
855pub struct NemoRelayNativeAsyncCompletion {
856 _private: [u8; 0],
857 _marker: PhantomData<(*mut u8, PhantomPinned)>,
858}
859
860#[repr(C)]
862pub struct NemoRelayNativeAsyncNext {
863 _private: [u8; 0],
864 _marker: PhantomData<(*mut u8, PhantomPinned)>,
865}
866
867#[repr(C)]
869pub struct NemoRelayNativeAsyncStream {
870 _private: [u8; 0],
871 _marker: PhantomData<(*mut u8, PhantomPinned)>,
872}
873
874pub type NemoRelayNativeAsyncNextStreamCb = unsafe extern "C" fn(
882 user_data: *mut c_void,
883 chunk_json: *const NemoRelayNativeString,
884 error: *const NemoRelayNativeString,
885 done: bool,
886) -> bool;
887
888pub type NemoRelayNativeAsyncNextResultCb = unsafe extern "C" fn(
895 user_data: *mut c_void,
896 value_json: *const NemoRelayNativeString,
897 error: *const NemoRelayNativeString,
898);
899
900pub type NemoRelayNativeAsyncStreamMiddlewareCb = unsafe extern "C" fn(
915 user_data: *mut c_void,
916 invocation_json: *const NemoRelayNativeString,
917 next: *const NemoRelayNativeAsyncNext,
918 stream: *const NemoRelayNativeAsyncStream,
919) -> u32;
920
921pub type NemoRelayNativeAsyncMiddlewareCb = unsafe extern "C" fn(
941 user_data: *mut c_void,
942 invocation_json: *const NemoRelayNativeString,
943 next: *const NemoRelayNativeAsyncNext,
944 completion: *const NemoRelayNativeAsyncCompletion,
945) -> u32;
946
947#[repr(C)]
952#[derive(Clone, Copy)]
953pub struct NemoRelayNativeHostApiV3 {
954 pub v1: NemoRelayNativeHostApiV1,
956 pub async_completion_resolve_json: unsafe extern "C" fn(
958 completion: *const NemoRelayNativeAsyncCompletion,
959 value_json: *const NemoRelayNativeString,
960 ) -> NemoRelayStatus,
961 pub async_completion_reject: unsafe extern "C" fn(
963 completion: *const NemoRelayNativeAsyncCompletion,
964 message: *const NemoRelayNativeString,
965 ) -> NemoRelayStatus,
966 pub async_completion_is_cancelled:
968 unsafe extern "C" fn(completion: *const NemoRelayNativeAsyncCompletion) -> bool,
969 pub async_completion_release:
971 unsafe extern "C" fn(completion: *const NemoRelayNativeAsyncCompletion),
972 pub async_next_invoke: unsafe extern "C" fn(
979 next: *const NemoRelayNativeAsyncNext,
980 invocation_json: *const NemoRelayNativeString,
981 completion: *const NemoRelayNativeAsyncCompletion,
982 ) -> NemoRelayStatus,
983 pub async_next_release: unsafe extern "C" fn(next: *const NemoRelayNativeAsyncNext),
988 pub plugin_context_register_async_middleware: unsafe extern "C" fn(
995 ctx: *mut NemoRelayNativePluginContext,
996 kind: u32,
997 name: *const NemoRelayNativeString,
998 priority: i32,
999 break_chain: bool,
1000 cb: NemoRelayNativeAsyncMiddlewareCb,
1001 user_data: *mut c_void,
1002 free_fn: NemoRelayNativeFreeFn,
1003 ) -> NemoRelayStatus,
1004 pub async_stream_push_json: unsafe extern "C" fn(
1010 stream: *const NemoRelayNativeAsyncStream,
1011 chunk_json: *const NemoRelayNativeString,
1012 ) -> NemoRelayStatus,
1013 pub async_stream_finish:
1015 unsafe extern "C" fn(stream: *const NemoRelayNativeAsyncStream) -> NemoRelayStatus,
1016 pub async_stream_reject: unsafe extern "C" fn(
1021 stream: *const NemoRelayNativeAsyncStream,
1022 message: *const NemoRelayNativeString,
1023 ) -> NemoRelayStatus,
1024 pub async_stream_is_cancelled:
1026 unsafe extern "C" fn(stream: *const NemoRelayNativeAsyncStream) -> bool,
1027 pub async_stream_release: unsafe extern "C" fn(stream: *const NemoRelayNativeAsyncStream),
1029 pub async_next_invoke_stream: unsafe extern "C" fn(
1037 next: *const NemoRelayNativeAsyncNext,
1038 invocation_json: *const NemoRelayNativeString,
1039 stream: *const NemoRelayNativeAsyncStream,
1040 cb: NemoRelayNativeAsyncNextStreamCb,
1041 user_data: *mut c_void,
1042 ) -> NemoRelayStatus,
1043 pub plugin_context_register_async_stream_middleware: unsafe extern "C" fn(
1045 ctx: *mut NemoRelayNativePluginContext,
1046 name: *const NemoRelayNativeString,
1047 priority: i32,
1048 cb: NemoRelayNativeAsyncStreamMiddlewareCb,
1049 user_data: *mut c_void,
1050 free_fn: NemoRelayNativeFreeFn,
1051 )
1052 -> NemoRelayStatus,
1053 pub async_next_invoke_result: unsafe extern "C" fn(
1058 next: *const NemoRelayNativeAsyncNext,
1059 invocation_json: *const NemoRelayNativeString,
1060 cb: NemoRelayNativeAsyncNextResultCb,
1061 user_data: *mut c_void,
1062 ) -> NemoRelayStatus,
1063}
1064
1065unsafe impl Send for NemoRelayNativeHostApiV3 {}
1066unsafe impl Sync for NemoRelayNativeHostApiV3 {}
1067
1068unsafe impl Send for NemoRelayNativeHostApiV1 {}
1071unsafe impl Sync for NemoRelayNativeHostApiV1 {}
1072
1073#[repr(C)]
1075pub struct NemoRelayNativePluginV1 {
1076 pub struct_size: usize,
1078 pub plugin_kind: *mut NemoRelayNativeString,
1080 pub allows_multiple_components: bool,
1082 pub user_data: *mut c_void,
1084 pub validate: Option<NemoRelayNativePluginValidateFn>,
1086 pub register: Option<NemoRelayNativePluginRegisterFn>,
1088 pub drop: NemoRelayNativePluginDropFn,
1090}
1091
1092impl Default for NemoRelayNativePluginV1 {
1093 fn default() -> Self {
1094 Self {
1095 struct_size: std::mem::size_of::<Self>(),
1096 plugin_kind: ptr::null_mut(),
1097 allows_multiple_components: true,
1098 user_data: ptr::null_mut(),
1099 validate: None,
1100 register: None,
1101 drop: None,
1102 }
1103 }
1104}
1105
1106pub type NemoRelayNativePluginEntry = unsafe extern "C" fn(
1108 host: *const NemoRelayNativeHostApiV1,
1109 out: *mut NemoRelayNativePluginV1,
1110) -> NemoRelayStatus;
1111
1112pub type Result<T> = std::result::Result<T, String>;
1114
1115pub type LlmJsonStream = Box<dyn Iterator<Item = Result<Json>> + Send>;
1117
1118#[derive(Clone)]
1120pub struct PluginRuntime {
1121 host: NemoRelayNativeHostApiV1,
1122}
1123
1124impl PluginRuntime {
1125 pub fn new(host: &NemoRelayNativeHostApiV1) -> Self {
1127 Self { host: *host }
1128 }
1129
1130 pub fn host_api(&self) -> &NemoRelayNativeHostApiV1 {
1132 &self.host
1133 }
1134
1135 pub fn current_scope(&self) -> Result<ScopeHandle<'_>> {
1137 current_scope(&self.host)
1138 }
1139
1140 pub fn push_scope(
1142 &self,
1143 name: &str,
1144 scope_type: ScopeType,
1145 data: Option<&Json>,
1146 metadata: Option<&Json>,
1147 input: Option<&Json>,
1148 ) -> Result<ScopeHandle<'_>> {
1149 push_scope(&self.host, name, scope_type.into(), data, metadata, input)
1150 }
1151
1152 pub fn pop_scope(
1154 &self,
1155 handle: &ScopeHandle<'_>,
1156 output: Option<&Json>,
1157 metadata: Option<&Json>,
1158 ) -> Result<()> {
1159 pop_scope(&self.host, handle, output, metadata)
1160 }
1161
1162 pub fn scope(
1164 &self,
1165 name: &str,
1166 scope_type: ScopeType,
1167 data: Option<&Json>,
1168 metadata: Option<&Json>,
1169 input: Option<&Json>,
1170 ) -> Result<ScopeGuard<'_>> {
1171 let handle = self.push_scope(name, scope_type, data, metadata, input)?;
1172 Ok(ScopeGuard {
1173 runtime: self,
1174 handle: Some(handle),
1175 })
1176 }
1177
1178 pub fn emit_mark(
1180 &self,
1181 name: &str,
1182 data: Option<&Json>,
1183 metadata: Option<&Json>,
1184 ) -> Result<()> {
1185 emit_mark(&self.host, name, data, metadata)
1186 }
1187
1188 pub fn create_scope_stack(&self) -> Result<ScopeStack<'_>> {
1190 create_scope_stack(&self.host)
1191 }
1192
1193 pub fn capture_scope_stack_thread(&self) -> Result<ScopeStackBinding<'_>> {
1195 capture_scope_stack_thread(&self.host)
1196 }
1197
1198 pub fn scope_stack_active(&self) -> bool {
1200 unsafe { (self.host.scope_stack_active)() }
1201 }
1202
1203 pub fn bind_scope_stack_thread<'a>(
1205 &'a self,
1206 stack: &'a ScopeStack<'a>,
1207 ) -> Result<ThreadScopeStackGuard<'a>> {
1208 let previous = self.capture_scope_stack_thread()?;
1209 let status = stack.set_thread();
1210 if status == NemoRelayStatus::Ok {
1211 Ok(ThreadScopeStackGuard {
1212 previous: Some(previous),
1213 })
1214 } else {
1215 let _ = previous.restore();
1216 Err(format!("scope_stack_set_thread failed: {status:?}"))
1217 }
1218 }
1219}
1220
1221impl From<ScopeType> for NemoRelayNativeScopeType {
1222 fn from(value: ScopeType) -> Self {
1223 match value {
1224 ScopeType::Agent => Self::Agent,
1225 ScopeType::Function => Self::Function,
1226 ScopeType::Tool => Self::Tool,
1227 ScopeType::Llm => Self::Llm,
1228 ScopeType::Retriever => Self::Retriever,
1229 ScopeType::Embedder => Self::Embedder,
1230 ScopeType::Reranker => Self::Reranker,
1231 ScopeType::Guardrail => Self::Guardrail,
1232 ScopeType::Evaluator => Self::Evaluator,
1233 ScopeType::Custom => Self::Custom,
1234 ScopeType::Unknown => Self::Unknown,
1235 }
1236 }
1237}
1238
1239pub struct ScopeGuard<'a> {
1241 runtime: &'a PluginRuntime,
1242 handle: Option<ScopeHandle<'a>>,
1243}
1244
1245impl<'a> ScopeGuard<'a> {
1246 pub fn handle(&self) -> Option<&ScopeHandle<'a>> {
1248 self.handle.as_ref()
1249 }
1250
1251 pub fn close(&mut self, output: Option<&Json>, metadata: Option<&Json>) -> Result<()> {
1253 let Some(handle) = self.handle.as_ref() else {
1254 return Ok(());
1255 };
1256 self.runtime.pop_scope(handle, output, metadata)?;
1257 self.handle.take();
1258 Ok(())
1259 }
1260}
1261
1262impl Drop for ScopeGuard<'_> {
1263 fn drop(&mut self) {
1264 if let Some(handle) = self.handle.take() {
1265 let _ = self.runtime.pop_scope(&handle, None, None);
1266 }
1267 }
1268}
1269
1270pub struct ThreadScopeStackGuard<'a> {
1272 previous: Option<ScopeStackBinding<'a>>,
1273}
1274
1275impl ThreadScopeStackGuard<'_> {
1276 pub fn restore(mut self) -> Result<()> {
1278 let Some(previous) = self.previous.take() else {
1279 return Ok(());
1280 };
1281 let status = previous.restore();
1282 if status == NemoRelayStatus::Ok {
1283 Ok(())
1284 } else {
1285 Err(format!("scope_stack_restore_thread failed: {status:?}"))
1286 }
1287 }
1288}
1289
1290impl Drop for ThreadScopeStackGuard<'_> {
1291 fn drop(&mut self) {
1292 if let Some(previous) = self.previous.take() {
1293 let _ = previous.restore();
1294 }
1295 }
1296}
1297
1298pub struct ToolNext<'a> {
1300 host: &'a NemoRelayNativeHostApiV1,
1301 next_fn: NemoRelayNativeToolNextFn,
1302 next_ctx: *mut c_void,
1303}
1304
1305impl ToolNext<'_> {
1306 pub fn call(&self, args: Json) -> Result<Json> {
1308 let args = HostString::from_json(self.host, &args)
1309 .ok_or_else(|| "failed to allocate tool next args".to_string())?;
1310 let mut out = ptr::null_mut();
1311 let status = unsafe { (self.next_fn)(args.as_ptr(), self.next_ctx, &mut out) };
1312 if status != NemoRelayStatus::Ok {
1313 return Err(format!("tool next failed: {status:?}"));
1314 }
1315 if out.is_null() {
1316 return Err("tool next returned null output".into());
1317 }
1318 let result = read_json_value(self.host, out, "tool next result");
1319 unsafe { (self.host.string_free)(out) };
1320 result.map_err(|status| format!("tool next returned invalid JSON: {status:?}"))
1321 }
1322}
1323
1324pub struct LlmNext<'a> {
1326 host: &'a NemoRelayNativeHostApiV1,
1327 next_fn: NemoRelayNativeLlmNextFn,
1328 next_ctx: *mut c_void,
1329}
1330
1331impl LlmNext<'_> {
1332 pub fn call(&self, request: LlmRequest) -> Result<Json> {
1334 let request = HostString::from_json(self.host, &request)
1335 .ok_or_else(|| "failed to allocate LLM next request".to_string())?;
1336 let mut out = ptr::null_mut();
1337 let status = unsafe { (self.next_fn)(request.as_ptr(), self.next_ctx, &mut out) };
1338 if status != NemoRelayStatus::Ok {
1339 return Err(format!("llm next failed: {status:?}"));
1340 }
1341 if out.is_null() {
1342 return Err("llm next returned null output".into());
1343 }
1344 let result = read_json_value(self.host, out, "llm next result");
1345 unsafe { (self.host.string_free)(out) };
1346 result.map_err(|status| format!("llm next returned invalid JSON: {status:?}"))
1347 }
1348}
1349
1350pub struct LlmStreamNext<'a> {
1352 host: &'a NemoRelayNativeHostApiV1,
1353 next_fn: NemoRelayNativeLlmStreamNextFn,
1354 next_ctx: *mut c_void,
1355}
1356
1357impl LlmStreamNext<'_> {
1358 pub fn call(&self, request: LlmRequest) -> Result<LlmStream> {
1360 let request = HostString::from_json(self.host, &request)
1361 .ok_or_else(|| "failed to allocate LLM stream next request".to_string())?;
1362 let mut raw = NemoRelayNativeLlmStreamV1::default();
1363 let status = unsafe { (self.next_fn)(request.as_ptr(), self.next_ctx, &mut raw) };
1364 if status != NemoRelayStatus::Ok {
1365 return Err(format!("llm stream next failed: {status:?}"));
1366 }
1367 unsafe { LlmStream::from_raw(self.host, raw) }
1368 }
1369}
1370
1371pub struct LlmStream {
1373 host: NemoRelayNativeHostApiV1,
1374 raw: NemoRelayNativeLlmStreamV1,
1375 finished: bool,
1376}
1377
1378unsafe impl Send for LlmStream {}
1380
1381impl LlmStream {
1382 pub unsafe fn from_raw(
1388 host: &NemoRelayNativeHostApiV1,
1389 mut raw: NemoRelayNativeLlmStreamV1,
1390 ) -> Result<Self> {
1391 let expected_size = std::mem::size_of::<NemoRelayNativeLlmStreamV1>();
1392 if raw.struct_size != expected_size {
1393 if raw.struct_size >= expected_size {
1394 unsafe { drop_raw_llm_stream(&mut raw) };
1395 }
1396 return Err(format!(
1397 "unsupported LLM stream struct size: {}",
1398 raw.struct_size
1399 ));
1400 }
1401 if raw.next.is_none() {
1402 unsafe { drop_raw_llm_stream(&mut raw) };
1403 return Err("LLM stream next callback was null".into());
1404 }
1405 Ok(Self {
1406 host: *host,
1407 raw,
1408 finished: false,
1409 })
1410 }
1411
1412 pub fn next_chunk(&mut self) -> Result<Option<Json>> {
1414 if self.finished {
1415 return Ok(None);
1416 }
1417 let next = self
1418 .raw
1419 .next
1420 .expect("LLM stream next callback is validated on construction");
1421 let mut out = ptr::null_mut();
1422 let status = unsafe { next(self.raw.user_data, &mut out) };
1423 match status {
1424 NemoRelayStatus::Ok => {
1425 if out.is_null() {
1426 self.finished = true;
1427 return Err("LLM stream returned null chunk".into());
1428 }
1429 let result = read_json_value(&self.host, out, "LLM stream chunk");
1430 unsafe { (self.host.string_free)(out) };
1431 match result {
1432 Ok(chunk) => Ok(Some(chunk)),
1433 Err(status) => {
1434 self.finished = true;
1435 Err(format!("LLM stream returned invalid JSON: {status:?}"))
1436 }
1437 }
1438 }
1439 NemoRelayStatus::StreamEnd => {
1440 if !out.is_null() {
1441 unsafe { (self.host.string_free)(out) };
1442 }
1443 self.finished = true;
1444 Ok(None)
1445 }
1446 other => {
1447 if !out.is_null() {
1448 unsafe { (self.host.string_free)(out) };
1449 }
1450 self.finished = true;
1451 Err(format!("LLM stream failed: {other:?}"))
1452 }
1453 }
1454 }
1455
1456 pub fn cancel(&mut self) -> Result<()> {
1458 if self.finished {
1459 return Ok(());
1460 }
1461 if let Some(cancel) = self.raw.cancel {
1462 let status = unsafe { cancel(self.raw.user_data) };
1463 if status != NemoRelayStatus::Ok {
1464 return Err(format!("LLM stream cancel failed: {status:?}"));
1465 }
1466 }
1467 self.finished = true;
1468 Ok(())
1469 }
1470}
1471
1472impl Iterator for LlmStream {
1473 type Item = Result<Json>;
1474
1475 fn next(&mut self) -> Option<Self::Item> {
1476 match self.next_chunk() {
1477 Ok(Some(chunk)) => Some(Ok(chunk)),
1478 Ok(None) => None,
1479 Err(message) => Some(Err(message)),
1480 }
1481 }
1482}
1483
1484unsafe fn drop_raw_llm_stream(raw: &mut NemoRelayNativeLlmStreamV1) {
1485 if let Some(drop_fn) = raw.drop.take() {
1486 unsafe { drop_fn(raw.user_data) };
1487 }
1488 raw.user_data = ptr::null_mut();
1489}
1490
1491impl Drop for LlmStream {
1492 fn drop(&mut self) {
1493 if !self.finished {
1494 if let Some(cancel) = self.raw.cancel {
1495 let _ = unsafe { cancel(self.raw.user_data) };
1496 }
1497 self.finished = true;
1498 }
1499 unsafe { drop_raw_llm_stream(&mut self.raw) };
1500 }
1501}
1502
1503pub struct ScopeHandle<'a> {
1505 host: &'a NemoRelayNativeHostApiV1,
1506 ptr: *mut NemoRelayNativeScopeHandle,
1507}
1508
1509impl<'a> ScopeHandle<'a> {
1510 pub fn as_ptr(&self) -> *const NemoRelayNativeScopeHandle {
1512 self.ptr
1513 }
1514}
1515
1516impl Drop for ScopeHandle<'_> {
1517 fn drop(&mut self) {
1518 unsafe { (self.host.scope_handle_free)(self.ptr) };
1519 }
1520}
1521
1522pub struct ScopeStack<'a> {
1524 host: &'a NemoRelayNativeHostApiV1,
1525 ptr: *mut NemoRelayNativeScopeStack,
1526}
1527
1528impl<'a> ScopeStack<'a> {
1529 pub fn as_ptr(&self) -> *const NemoRelayNativeScopeStack {
1531 self.ptr
1532 }
1533
1534 fn set_thread(&self) -> NemoRelayStatus {
1535 unsafe { (self.host.scope_stack_set_thread)(self.ptr) }
1536 }
1537
1538 pub fn with_current<F>(&self, f: F) -> Result<()>
1540 where
1541 F: FnOnce() -> Result<()>,
1542 {
1543 struct State<F> {
1544 f: Option<F>,
1545 error: Option<String>,
1546 }
1547
1548 unsafe extern "C" fn trampoline<F>(user_data: *mut c_void) -> NemoRelayStatus
1549 where
1550 F: FnOnce() -> Result<()>,
1551 {
1552 if user_data.is_null() {
1553 return NemoRelayStatus::NullPointer;
1554 }
1555 let state = unsafe { &mut *(user_data as *mut State<F>) };
1556 let result = catch_unwind(AssertUnwindSafe(|| {
1557 let Some(f) = state.f.take() else {
1558 return Err("scope-stack callback was already consumed".to_string());
1559 };
1560 f()
1561 }));
1562 match result {
1563 Ok(Ok(())) => NemoRelayStatus::Ok,
1564 Ok(Err(message)) => {
1565 state.error = Some(message);
1566 NemoRelayStatus::Internal
1567 }
1568 Err(_) => {
1569 state.error = Some("scope-stack callback panicked".into());
1570 NemoRelayStatus::Internal
1571 }
1572 }
1573 }
1574
1575 let mut state = State {
1576 f: Some(f),
1577 error: None,
1578 };
1579 let status = unsafe {
1580 (self.host.scope_stack_with_current)(
1581 self.ptr,
1582 trampoline::<F>,
1583 (&mut state as *mut State<_>).cast(),
1584 )
1585 };
1586 if status == NemoRelayStatus::Ok {
1587 Ok(())
1588 } else {
1589 Err(state
1590 .error
1591 .unwrap_or_else(|| format!("scope_stack_with_current failed: {status:?}")))
1592 }
1593 }
1594}
1595
1596impl Drop for ScopeStack<'_> {
1597 fn drop(&mut self) {
1598 unsafe { (self.host.scope_stack_free)(self.ptr) };
1599 }
1600}
1601
1602pub struct ScopeStackBinding<'a> {
1604 host: &'a NemoRelayNativeHostApiV1,
1605 ptr: *mut NemoRelayNativeScopeStackBinding,
1606}
1607
1608impl<'a> ScopeStackBinding<'a> {
1609 pub fn restore(mut self) -> NemoRelayStatus {
1611 let ptr = std::mem::replace(&mut self.ptr, ptr::null_mut());
1612 unsafe { (self.host.scope_stack_restore_thread)(ptr) }
1613 }
1614}
1615
1616impl Drop for ScopeStackBinding<'_> {
1617 fn drop(&mut self) {
1618 if !self.ptr.is_null() {
1619 unsafe { (self.host.scope_stack_binding_free)(self.ptr) };
1620 }
1621 }
1622}
1623
1624pub fn current_scope(host: &NemoRelayNativeHostApiV1) -> Result<ScopeHandle<'_>> {
1626 let mut out = ptr::null_mut();
1627 let status = unsafe { (host.scope_get_current)(&mut out) };
1628 if status == NemoRelayStatus::Ok && !out.is_null() {
1629 Ok(ScopeHandle { host, ptr: out })
1630 } else {
1631 Err(format!("scope_get_current failed: {status:?}"))
1632 }
1633}
1634
1635pub fn push_scope<'a>(
1637 host: &'a NemoRelayNativeHostApiV1,
1638 name: &str,
1639 scope_type: NemoRelayNativeScopeType,
1640 data: Option<&Json>,
1641 metadata: Option<&Json>,
1642 input: Option<&Json>,
1643) -> Result<ScopeHandle<'a>> {
1644 let name =
1645 HostString::new(host, name).ok_or_else(|| "failed to allocate scope name".to_string())?;
1646 let data = OptionalHostJson::new(host, data)?;
1647 let metadata = OptionalHostJson::new(host, metadata)?;
1648 let input = OptionalHostJson::new(host, input)?;
1649 let mut out = ptr::null_mut();
1650 let status = unsafe {
1651 (host.scope_push)(
1652 name.as_ptr(),
1653 scope_type,
1654 ptr::null(),
1655 0,
1656 data.as_ptr(),
1657 metadata.as_ptr(),
1658 input.as_ptr(),
1659 ptr::null(),
1660 &mut out,
1661 )
1662 };
1663 if status == NemoRelayStatus::Ok && !out.is_null() {
1664 Ok(ScopeHandle { host, ptr: out })
1665 } else {
1666 Err(format!("scope_push failed: {status:?}"))
1667 }
1668}
1669
1670pub fn pop_scope(
1672 host: &NemoRelayNativeHostApiV1,
1673 handle: &ScopeHandle<'_>,
1674 output: Option<&Json>,
1675 metadata: Option<&Json>,
1676) -> Result<()> {
1677 let output = OptionalHostJson::new(host, output)?;
1678 let metadata = OptionalHostJson::new(host, metadata)?;
1679 let status = unsafe {
1680 (host.scope_pop)(
1681 handle.as_ptr(),
1682 output.as_ptr(),
1683 metadata.as_ptr(),
1684 ptr::null(),
1685 )
1686 };
1687 if status == NemoRelayStatus::Ok {
1688 Ok(())
1689 } else {
1690 Err(format!("scope_pop failed: {status:?}"))
1691 }
1692}
1693
1694pub fn emit_mark(
1696 host: &NemoRelayNativeHostApiV1,
1697 name: &str,
1698 data: Option<&Json>,
1699 metadata: Option<&Json>,
1700) -> Result<()> {
1701 let name =
1702 HostString::new(host, name).ok_or_else(|| "failed to allocate mark name".to_string())?;
1703 let data = OptionalHostJson::new(host, data)?;
1704 let metadata = OptionalHostJson::new(host, metadata)?;
1705 let status = unsafe {
1706 (host.emit_mark)(
1707 name.as_ptr(),
1708 ptr::null(),
1709 data.as_ptr(),
1710 metadata.as_ptr(),
1711 ptr::null(),
1712 )
1713 };
1714 if status == NemoRelayStatus::Ok {
1715 Ok(())
1716 } else {
1717 Err(format!("emit_mark failed: {status:?}"))
1718 }
1719}
1720
1721pub fn create_scope_stack(host: &NemoRelayNativeHostApiV1) -> Result<ScopeStack<'_>> {
1723 let mut out = ptr::null_mut();
1724 let status = unsafe { (host.scope_stack_create)(&mut out) };
1725 if status == NemoRelayStatus::Ok && !out.is_null() {
1726 Ok(ScopeStack { host, ptr: out })
1727 } else {
1728 Err(format!("scope_stack_create failed: {status:?}"))
1729 }
1730}
1731
1732pub fn capture_scope_stack_thread(
1734 host: &NemoRelayNativeHostApiV1,
1735) -> Result<ScopeStackBinding<'_>> {
1736 let mut out = ptr::null_mut();
1737 let status = unsafe { (host.scope_stack_capture_thread)(&mut out) };
1738 if status == NemoRelayStatus::Ok && !out.is_null() {
1739 Ok(ScopeStackBinding { host, ptr: out })
1740 } else {
1741 Err(format!("scope_stack_capture_thread failed: {status:?}"))
1742 }
1743}
1744
1745pub trait NativePlugin: Send + 'static {
1747 fn plugin_kind(&self) -> &str;
1749
1750 fn allows_multiple_components(&self) -> bool {
1752 true
1753 }
1754
1755 fn validate(&self, _plugin_config: &Map<String, Json>) -> Vec<ConfigDiagnostic> {
1757 vec![]
1758 }
1759
1760 fn register(
1762 &mut self,
1763 plugin_config: &Map<String, Json>,
1764 ctx: &mut PluginContext<'_>,
1765 ) -> Result<()>;
1766}
1767
1768pub struct PluginContext<'a> {
1770 host: &'a NemoRelayNativeHostApiV1,
1771 raw: *mut NemoRelayNativePluginContext,
1772}
1773
1774#[allow(clippy::not_unsafe_ptr_arg_deref)]
1775impl<'a> PluginContext<'a> {
1776 pub unsafe fn from_raw(
1781 host: &'a NemoRelayNativeHostApiV1,
1782 raw: *mut NemoRelayNativePluginContext,
1783 ) -> Self {
1784 Self { host, raw }
1785 }
1786
1787 pub fn host_api(&self) -> &'a NemoRelayNativeHostApiV1 {
1789 self.host
1790 }
1791
1792 pub fn runtime(&self) -> PluginRuntime {
1794 PluginRuntime::new(self.host)
1795 }
1796
1797 pub fn register_subscriber<F>(&mut self, name: &str, callback: F) -> Result<()>
1799 where
1800 F: Fn(&Event) + Send + Sync + 'static,
1801 {
1802 let user_data = typed_callback_user_data(self.host, callback);
1803 let status = unsafe {
1804 self.register_subscriber_raw(
1805 name,
1806 typed_subscriber_trampoline::<F>,
1807 user_data,
1808 Some(drop_typed_callback::<F>),
1809 )
1810 };
1811 finish_typed_registration::<F>(self.host, status, user_data, "subscriber")
1812 }
1813
1814 fn register_event_sanitizer<F>(
1815 &mut self,
1816 name: &str,
1817 priority: i32,
1818 callback: F,
1819 register: unsafe extern "C" fn(
1820 *mut NemoRelayNativePluginContext,
1821 *const NemoRelayNativeString,
1822 i32,
1823 NemoRelayNativeEventSanitizeCb,
1824 *mut c_void,
1825 NemoRelayNativeFreeFn,
1826 ) -> NemoRelayStatus,
1827 label: &str,
1828 ) -> Result<()>
1829 where
1830 F: Fn(&Event, EventSanitizeFields) -> EventSanitizeFields + Send + Sync + 'static,
1831 {
1832 let user_data = typed_callback_user_data(self.host, callback);
1833 let status = self.with_name(name, |_, name| unsafe {
1834 register(
1835 self.raw,
1836 name,
1837 priority,
1838 typed_event_sanitize_trampoline::<F>,
1839 user_data,
1840 Some(drop_typed_callback::<F>),
1841 )
1842 });
1843 finish_typed_registration::<F>(self.host, status, user_data, label)
1844 }
1845
1846 pub fn register_mark_sanitize_guardrail<F>(
1848 &mut self,
1849 name: &str,
1850 priority: i32,
1851 callback: F,
1852 ) -> Result<()>
1853 where
1854 F: Fn(&Event, EventSanitizeFields) -> EventSanitizeFields + Send + Sync + 'static,
1855 {
1856 self.register_event_sanitizer(
1857 name,
1858 priority,
1859 callback,
1860 self.host.plugin_context_register_mark_sanitize_guardrail,
1861 "mark sanitize guardrail",
1862 )
1863 }
1864
1865 pub fn register_scope_sanitize_start_guardrail<F>(
1867 &mut self,
1868 name: &str,
1869 priority: i32,
1870 callback: F,
1871 ) -> Result<()>
1872 where
1873 F: Fn(&Event, EventSanitizeFields) -> EventSanitizeFields + Send + Sync + 'static,
1874 {
1875 self.register_event_sanitizer(
1876 name,
1877 priority,
1878 callback,
1879 self.host
1880 .plugin_context_register_scope_sanitize_start_guardrail,
1881 "scope-start sanitize guardrail",
1882 )
1883 }
1884
1885 pub fn register_scope_sanitize_end_guardrail<F>(
1887 &mut self,
1888 name: &str,
1889 priority: i32,
1890 callback: F,
1891 ) -> Result<()>
1892 where
1893 F: Fn(&Event, EventSanitizeFields) -> EventSanitizeFields + Send + Sync + 'static,
1894 {
1895 self.register_event_sanitizer(
1896 name,
1897 priority,
1898 callback,
1899 self.host
1900 .plugin_context_register_scope_sanitize_end_guardrail,
1901 "scope-end sanitize guardrail",
1902 )
1903 }
1904
1905 pub fn register_tool_sanitize_request_guardrail<F>(
1907 &mut self,
1908 name: &str,
1909 priority: i32,
1910 callback: F,
1911 ) -> Result<()>
1912 where
1913 F: Fn(&str, Json) -> Json + Send + Sync + 'static,
1914 {
1915 let user_data = typed_callback_user_data(self.host, callback);
1916 let status = unsafe {
1917 self.register_tool_sanitize_request_guardrail_raw(
1918 name,
1919 priority,
1920 typed_tool_sanitize_trampoline::<F>,
1921 user_data,
1922 Some(drop_typed_callback::<F>),
1923 )
1924 };
1925 finish_typed_registration::<F>(
1926 self.host,
1927 status,
1928 user_data,
1929 "tool sanitize request guardrail",
1930 )
1931 }
1932
1933 pub fn register_tool_sanitize_response_guardrail<F>(
1935 &mut self,
1936 name: &str,
1937 priority: i32,
1938 callback: F,
1939 ) -> Result<()>
1940 where
1941 F: Fn(&str, Json) -> Json + Send + Sync + 'static,
1942 {
1943 let user_data = typed_callback_user_data(self.host, callback);
1944 let status = unsafe {
1945 self.register_tool_sanitize_response_guardrail_raw(
1946 name,
1947 priority,
1948 typed_tool_sanitize_trampoline::<F>,
1949 user_data,
1950 Some(drop_typed_callback::<F>),
1951 )
1952 };
1953 finish_typed_registration::<F>(
1954 self.host,
1955 status,
1956 user_data,
1957 "tool sanitize response guardrail",
1958 )
1959 }
1960
1961 pub fn register_tool_conditional_execution_guardrail<F>(
1963 &mut self,
1964 name: &str,
1965 priority: i32,
1966 callback: F,
1967 ) -> Result<()>
1968 where
1969 F: Fn(&str, &Json) -> Result<Option<String>> + Send + Sync + 'static,
1970 {
1971 let user_data = typed_callback_user_data(self.host, callback);
1972 let status = unsafe {
1973 self.register_tool_conditional_execution_guardrail_raw(
1974 name,
1975 priority,
1976 typed_tool_conditional_trampoline::<F>,
1977 user_data,
1978 Some(drop_typed_callback::<F>),
1979 )
1980 };
1981 finish_typed_registration::<F>(
1982 self.host,
1983 status,
1984 user_data,
1985 "tool conditional execution guardrail",
1986 )
1987 }
1988
1989 pub fn register_tool_request_intercept<F>(
1991 &mut self,
1992 name: &str,
1993 priority: i32,
1994 break_chain: bool,
1995 callback: F,
1996 ) -> Result<()>
1997 where
1998 F: Fn(&str, Json) -> Result<Json> + Send + Sync + 'static,
1999 {
2000 let user_data = typed_callback_user_data(self.host, callback);
2001 let status = unsafe {
2002 self.register_tool_request_intercept_raw(
2003 name,
2004 priority,
2005 break_chain,
2006 typed_tool_intercept_trampoline::<F>,
2007 user_data,
2008 Some(drop_typed_callback::<F>),
2009 )
2010 };
2011 finish_typed_registration::<F>(self.host, status, user_data, "tool request intercept")
2012 }
2013
2014 pub fn register_tool_execution_intercept<F>(
2020 &mut self,
2021 name: &str,
2022 priority: i32,
2023 callback: F,
2024 ) -> Result<()>
2025 where
2026 F: for<'next> Fn(&str, Json, ToolNext<'next>) -> Result<ToolExecutionInterceptOutcome>
2027 + Send
2028 + Sync
2029 + 'static,
2030 {
2031 let user_data = typed_callback_user_data(self.host, callback);
2032 let status = unsafe {
2033 self.register_tool_execution_intercept_raw(
2034 name,
2035 priority,
2036 typed_tool_execution_trampoline::<F>,
2037 user_data,
2038 Some(drop_typed_callback::<F>),
2039 )
2040 };
2041 finish_typed_registration::<F>(self.host, status, user_data, "tool execution intercept")
2042 }
2043
2044 pub fn register_llm_sanitize_request_guardrail<F>(
2046 &mut self,
2047 name: &str,
2048 priority: i32,
2049 callback: F,
2050 ) -> Result<()>
2051 where
2052 F: for<'ctx> Fn(LlmRequest, LlmSanitizeRequestContext<'ctx>) -> Option<LlmRequest>
2053 + Send
2054 + Sync
2055 + 'static,
2056 {
2057 let user_data = typed_callback_user_data(self.host, callback);
2058 let status = unsafe {
2059 self.register_llm_sanitize_request_guardrail_raw(
2060 name,
2061 priority,
2062 typed_llm_sanitize_request_trampoline::<F>,
2063 user_data,
2064 Some(drop_typed_callback::<F>),
2065 )
2066 };
2067 finish_typed_registration::<F>(
2068 self.host,
2069 status,
2070 user_data,
2071 "llm sanitize request guardrail",
2072 )
2073 }
2074
2075 pub fn register_llm_sanitize_response_guardrail<F>(
2077 &mut self,
2078 name: &str,
2079 priority: i32,
2080 callback: F,
2081 ) -> Result<()>
2082 where
2083 F: for<'ctx> Fn(Json, LlmSanitizeResponseContext<'ctx>) -> Option<Json>
2084 + Send
2085 + Sync
2086 + 'static,
2087 {
2088 let user_data = typed_callback_user_data(self.host, callback);
2089 let status = unsafe {
2090 self.register_llm_sanitize_response_guardrail_raw(
2091 name,
2092 priority,
2093 typed_llm_sanitize_response_trampoline::<F>,
2094 user_data,
2095 Some(drop_typed_callback::<F>),
2096 )
2097 };
2098 finish_typed_registration::<F>(
2099 self.host,
2100 status,
2101 user_data,
2102 "llm sanitize response guardrail",
2103 )
2104 }
2105
2106 pub fn register_llm_conditional_execution_guardrail<F>(
2108 &mut self,
2109 name: &str,
2110 priority: i32,
2111 callback: F,
2112 ) -> Result<()>
2113 where
2114 F: Fn(&LlmRequest) -> Result<Option<String>> + Send + Sync + 'static,
2115 {
2116 let user_data = typed_callback_user_data(self.host, callback);
2117 let status = unsafe {
2118 self.register_llm_conditional_execution_guardrail_raw(
2119 name,
2120 priority,
2121 typed_llm_conditional_trampoline::<F>,
2122 user_data,
2123 Some(drop_typed_callback::<F>),
2124 )
2125 };
2126 finish_typed_registration::<F>(
2127 self.host,
2128 status,
2129 user_data,
2130 "llm conditional execution guardrail",
2131 )
2132 }
2133
2134 pub fn register_llm_request_intercept<F>(
2136 &mut self,
2137 name: &str,
2138 priority: i32,
2139 break_chain: bool,
2140 callback: F,
2141 ) -> Result<()>
2142 where
2143 F: Fn(&str, LlmRequest, Option<AnnotatedLlmRequest>) -> Result<LlmRequestInterceptOutcome>
2144 + Send
2145 + Sync
2146 + 'static,
2147 {
2148 let user_data = typed_callback_user_data(self.host, callback);
2149 let status = unsafe {
2150 self.register_llm_request_intercept_raw(
2151 name,
2152 priority,
2153 break_chain,
2154 typed_llm_request_intercept_trampoline::<F>,
2155 user_data,
2156 Some(drop_typed_callback::<F>),
2157 )
2158 };
2159 finish_typed_registration::<F>(self.host, status, user_data, "llm request intercept")
2160 }
2161
2162 pub fn register_llm_execution_intercept<F>(
2164 &mut self,
2165 name: &str,
2166 priority: i32,
2167 callback: F,
2168 ) -> Result<()>
2169 where
2170 F: for<'next> Fn(&str, LlmRequest, LlmNext<'next>) -> Result<Json> + Send + Sync + 'static,
2171 {
2172 let user_data = typed_callback_user_data(self.host, callback);
2173 let status = unsafe {
2174 self.register_llm_execution_intercept_raw(
2175 name,
2176 priority,
2177 typed_llm_execution_trampoline::<F>,
2178 user_data,
2179 Some(drop_typed_callback::<F>),
2180 )
2181 };
2182 finish_typed_registration::<F>(self.host, status, user_data, "llm execution intercept")
2183 }
2184
2185 pub fn register_llm_stream_execution_intercept<F>(
2190 &mut self,
2191 name: &str,
2192 priority: i32,
2193 callback: F,
2194 ) -> Result<()>
2195 where
2196 F: for<'next> Fn(&str, LlmRequest, LlmStreamNext<'next>) -> Result<LlmJsonStream>
2197 + Send
2198 + Sync
2199 + 'static,
2200 {
2201 let user_data = typed_callback_user_data(self.host, callback);
2202 let status = unsafe {
2203 self.register_llm_stream_execution_intercept_raw(
2204 name,
2205 priority,
2206 typed_llm_stream_execution_trampoline::<F>,
2207 user_data,
2208 Some(drop_typed_callback::<F>),
2209 )
2210 };
2211 finish_typed_registration::<F>(
2212 self.host,
2213 status,
2214 user_data,
2215 "llm stream execution intercept",
2216 )
2217 }
2218
2219 pub unsafe fn register_subscriber_raw(
2226 &mut self,
2227 name: &str,
2228 cb: NemoRelayNativeEventSubscriberCb,
2229 user_data: *mut c_void,
2230 free_fn: NemoRelayNativeFreeFn,
2231 ) -> NemoRelayStatus {
2232 self.with_name(name, |host, name| unsafe {
2233 (host.plugin_context_register_subscriber)(self.raw, name, cb, user_data, free_fn)
2234 })
2235 }
2236
2237 pub unsafe fn register_mark_sanitize_guardrail_raw(
2244 &mut self,
2245 name: &str,
2246 priority: i32,
2247 cb: NemoRelayNativeEventSanitizeCb,
2248 user_data: *mut c_void,
2249 free_fn: NemoRelayNativeFreeFn,
2250 ) -> NemoRelayStatus {
2251 self.with_name(name, |host, name| unsafe {
2252 (host.plugin_context_register_mark_sanitize_guardrail)(
2253 self.raw, name, priority, cb, user_data, free_fn,
2254 )
2255 })
2256 }
2257
2258 pub unsafe fn register_scope_sanitize_start_guardrail_raw(
2265 &mut self,
2266 name: &str,
2267 priority: i32,
2268 cb: NemoRelayNativeEventSanitizeCb,
2269 user_data: *mut c_void,
2270 free_fn: NemoRelayNativeFreeFn,
2271 ) -> NemoRelayStatus {
2272 self.with_name(name, |host, name| unsafe {
2273 (host.plugin_context_register_scope_sanitize_start_guardrail)(
2274 self.raw, name, priority, cb, user_data, free_fn,
2275 )
2276 })
2277 }
2278
2279 pub unsafe fn register_scope_sanitize_end_guardrail_raw(
2286 &mut self,
2287 name: &str,
2288 priority: i32,
2289 cb: NemoRelayNativeEventSanitizeCb,
2290 user_data: *mut c_void,
2291 free_fn: NemoRelayNativeFreeFn,
2292 ) -> NemoRelayStatus {
2293 self.with_name(name, |host, name| unsafe {
2294 (host.plugin_context_register_scope_sanitize_end_guardrail)(
2295 self.raw, name, priority, cb, user_data, free_fn,
2296 )
2297 })
2298 }
2299
2300 pub unsafe fn register_tool_sanitize_request_guardrail_raw(
2307 &mut self,
2308 name: &str,
2309 priority: i32,
2310 cb: NemoRelayNativeToolJsonCb,
2311 user_data: *mut c_void,
2312 free_fn: NemoRelayNativeFreeFn,
2313 ) -> NemoRelayStatus {
2314 self.with_name(name, |host, name| unsafe {
2315 (host.plugin_context_register_tool_sanitize_request_guardrail)(
2316 self.raw, name, priority, cb, user_data, free_fn,
2317 )
2318 })
2319 }
2320
2321 pub unsafe fn register_tool_sanitize_response_guardrail_raw(
2328 &mut self,
2329 name: &str,
2330 priority: i32,
2331 cb: NemoRelayNativeToolJsonCb,
2332 user_data: *mut c_void,
2333 free_fn: NemoRelayNativeFreeFn,
2334 ) -> NemoRelayStatus {
2335 self.with_name(name, |host, name| unsafe {
2336 (host.plugin_context_register_tool_sanitize_response_guardrail)(
2337 self.raw, name, priority, cb, user_data, free_fn,
2338 )
2339 })
2340 }
2341
2342 pub unsafe fn register_tool_conditional_execution_guardrail_raw(
2349 &mut self,
2350 name: &str,
2351 priority: i32,
2352 cb: NemoRelayNativeToolConditionalCb,
2353 user_data: *mut c_void,
2354 free_fn: NemoRelayNativeFreeFn,
2355 ) -> NemoRelayStatus {
2356 self.with_name(name, |host, name| unsafe {
2357 (host.plugin_context_register_tool_conditional_execution_guardrail)(
2358 self.raw, name, priority, cb, user_data, free_fn,
2359 )
2360 })
2361 }
2362
2363 pub unsafe fn register_tool_request_intercept_raw(
2370 &mut self,
2371 name: &str,
2372 priority: i32,
2373 break_chain: bool,
2374 cb: NemoRelayNativeToolJsonCb,
2375 user_data: *mut c_void,
2376 free_fn: NemoRelayNativeFreeFn,
2377 ) -> NemoRelayStatus {
2378 self.with_name(name, |host, name| unsafe {
2379 (host.plugin_context_register_tool_request_intercept)(
2380 self.raw,
2381 name,
2382 priority,
2383 break_chain,
2384 cb,
2385 user_data,
2386 free_fn,
2387 )
2388 })
2389 }
2390
2391 pub unsafe fn register_tool_execution_intercept_raw(
2398 &mut self,
2399 name: &str,
2400 priority: i32,
2401 cb: NemoRelayNativeToolExecutionCb,
2402 user_data: *mut c_void,
2403 free_fn: NemoRelayNativeFreeFn,
2404 ) -> NemoRelayStatus {
2405 self.with_name(name, |host, name| unsafe {
2406 (host.plugin_context_register_tool_execution_intercept)(
2407 self.raw, name, priority, cb, user_data, free_fn,
2408 )
2409 })
2410 }
2411
2412 pub unsafe fn register_llm_sanitize_request_guardrail_raw(
2419 &mut self,
2420 name: &str,
2421 priority: i32,
2422 cb: NemoRelayNativeLlmSanitizeRequestCb,
2423 user_data: *mut c_void,
2424 free_fn: NemoRelayNativeFreeFn,
2425 ) -> NemoRelayStatus {
2426 self.with_name(name, |host, name| unsafe {
2427 (host.plugin_context_register_llm_sanitize_request_guardrail)(
2428 self.raw, name, priority, cb, user_data, free_fn,
2429 )
2430 })
2431 }
2432
2433 pub unsafe fn register_llm_sanitize_response_guardrail_raw(
2440 &mut self,
2441 name: &str,
2442 priority: i32,
2443 cb: NemoRelayNativeLlmSanitizeResponseCb,
2444 user_data: *mut c_void,
2445 free_fn: NemoRelayNativeFreeFn,
2446 ) -> NemoRelayStatus {
2447 self.with_name(name, |host, name| unsafe {
2448 (host.plugin_context_register_llm_sanitize_response_guardrail)(
2449 self.raw, name, priority, cb, user_data, free_fn,
2450 )
2451 })
2452 }
2453
2454 pub unsafe fn register_llm_conditional_execution_guardrail_raw(
2461 &mut self,
2462 name: &str,
2463 priority: i32,
2464 cb: NemoRelayNativeLlmConditionalCb,
2465 user_data: *mut c_void,
2466 free_fn: NemoRelayNativeFreeFn,
2467 ) -> NemoRelayStatus {
2468 self.with_name(name, |host, name| unsafe {
2469 (host.plugin_context_register_llm_conditional_execution_guardrail)(
2470 self.raw, name, priority, cb, user_data, free_fn,
2471 )
2472 })
2473 }
2474
2475 pub unsafe fn register_llm_request_intercept_raw(
2482 &mut self,
2483 name: &str,
2484 priority: i32,
2485 break_chain: bool,
2486 cb: NemoRelayNativeLlmRequestInterceptCb,
2487 user_data: *mut c_void,
2488 free_fn: NemoRelayNativeFreeFn,
2489 ) -> NemoRelayStatus {
2490 self.with_name(name, |host, name| unsafe {
2491 (host.plugin_context_register_llm_request_intercept)(
2492 self.raw,
2493 name,
2494 priority,
2495 break_chain,
2496 cb,
2497 user_data,
2498 free_fn,
2499 )
2500 })
2501 }
2502
2503 pub unsafe fn register_llm_execution_intercept_raw(
2510 &mut self,
2511 name: &str,
2512 priority: i32,
2513 cb: NemoRelayNativeLlmExecutionCb,
2514 user_data: *mut c_void,
2515 free_fn: NemoRelayNativeFreeFn,
2516 ) -> NemoRelayStatus {
2517 self.with_name(name, |host, name| unsafe {
2518 (host.plugin_context_register_llm_execution_intercept)(
2519 self.raw, name, priority, cb, user_data, free_fn,
2520 )
2521 })
2522 }
2523
2524 pub unsafe fn register_llm_stream_execution_intercept_raw(
2531 &mut self,
2532 name: &str,
2533 priority: i32,
2534 cb: NemoRelayNativeLlmStreamExecutionCb,
2535 user_data: *mut c_void,
2536 free_fn: NemoRelayNativeFreeFn,
2537 ) -> NemoRelayStatus {
2538 self.with_name(name, |host, name| unsafe {
2539 (host.plugin_context_register_llm_stream_execution_intercept)(
2540 self.raw, name, priority, cb, user_data, free_fn,
2541 )
2542 })
2543 }
2544
2545 #[allow(clippy::too_many_arguments)] pub unsafe fn register_async_middleware_raw(
2559 &mut self,
2560 kind: NemoRelayNativeAsyncMiddlewareKind,
2561 name: &str,
2562 priority: i32,
2563 break_chain: bool,
2564 cb: NemoRelayNativeAsyncMiddlewareCb,
2565 user_data: *mut c_void,
2566 free_fn: NemoRelayNativeFreeFn,
2567 ) -> NemoRelayStatus {
2568 if self.host.abi_version < NEMO_RELAY_NATIVE_ABI_VERSION_ASYNC_MIDDLEWARE
2569 || self.host.struct_size < std::mem::size_of::<NemoRelayNativeHostApiV3>()
2570 {
2571 return NemoRelayStatus::InvalidArg;
2572 }
2573 let host = unsafe { &*(self.host as *const _ as *const NemoRelayNativeHostApiV3) };
2574 self.with_name(name, |_, name| unsafe {
2575 (host.plugin_context_register_async_middleware)(
2576 self.raw,
2577 kind as u32,
2578 name,
2579 priority,
2580 break_chain,
2581 cb,
2582 user_data,
2583 free_fn,
2584 )
2585 })
2586 }
2587
2588 pub unsafe fn register_async_stream_middleware_raw(
2600 &mut self,
2601 name: &str,
2602 priority: i32,
2603 cb: NemoRelayNativeAsyncStreamMiddlewareCb,
2604 user_data: *mut c_void,
2605 free_fn: NemoRelayNativeFreeFn,
2606 ) -> NemoRelayStatus {
2607 if self.host.abi_version < NEMO_RELAY_NATIVE_ABI_VERSION_ASYNC_MIDDLEWARE
2608 || self.host.struct_size < std::mem::size_of::<NemoRelayNativeHostApiV3>()
2609 {
2610 return NemoRelayStatus::InvalidArg;
2611 }
2612 let host = unsafe { &*(self.host as *const _ as *const NemoRelayNativeHostApiV3) };
2613 self.with_name(name, |_, name| unsafe {
2614 (host.plugin_context_register_async_stream_middleware)(
2615 self.raw, name, priority, cb, user_data, free_fn,
2616 )
2617 })
2618 }
2619
2620 fn with_name(
2621 &self,
2622 name: &str,
2623 f: impl FnOnce(&NemoRelayNativeHostApiV1, *const NemoRelayNativeString) -> NemoRelayStatus,
2624 ) -> NemoRelayStatus {
2625 let name = match HostString::try_new(self.host, name) {
2626 Ok(name) => name,
2627 Err(status) => return status,
2628 };
2629 f(self.host, name.as_ptr())
2630 }
2631}
2632
2633struct TypedCallback<F> {
2634 host: NemoRelayNativeHostApiV1,
2635 callback: F,
2636}
2637
2638fn typed_callback_user_data<F>(host: &NemoRelayNativeHostApiV1, callback: F) -> *mut c_void {
2639 Box::into_raw(Box::new(TypedCallback {
2640 host: *host,
2641 callback,
2642 })) as *mut c_void
2643}
2644
2645unsafe extern "C" fn drop_typed_callback<F>(user_data: *mut c_void) {
2646 if !user_data.is_null() {
2647 let callback = unsafe { Box::from_raw(user_data as *mut TypedCallback<F>) };
2648 let host = callback.host;
2649 if catch_unwind(AssertUnwindSafe(|| drop(callback))).is_err() {
2650 set_last_error(&host, "native plugin typed callback state drop panicked");
2651 }
2652 }
2653}
2654
2655fn finish_typed_registration<F>(
2656 host: &NemoRelayNativeHostApiV1,
2657 status: NemoRelayStatus,
2658 user_data: *mut c_void,
2659 label: &str,
2660) -> Result<()> {
2661 if status == NemoRelayStatus::Ok {
2662 Ok(())
2663 } else {
2664 unsafe { drop_typed_callback::<F>(user_data) };
2665 Err(status_error(host, status, label))
2666 }
2667}
2668
2669fn status_error(host: &NemoRelayNativeHostApiV1, status: NemoRelayStatus, label: &str) -> String {
2670 debug_assert_ne!(status, NemoRelayStatus::Ok);
2671 set_last_error(host, &format!("{label} failed: {status:?}"));
2672 format!("{label} failed: {status:?}")
2673}
2674
2675fn callback_error(host: &NemoRelayNativeHostApiV1, message: String) -> NemoRelayStatus {
2676 set_last_error(host, &message);
2677 NemoRelayStatus::Internal
2678}
2679
2680fn callback_panic(host: &NemoRelayNativeHostApiV1, label: &str) -> NemoRelayStatus {
2681 set_last_error(host, &format!("{label} panicked"));
2682 NemoRelayStatus::Internal
2683}
2684
2685unsafe extern "C" fn typed_subscriber_trampoline<F>(
2686 user_data: *mut c_void,
2687 event_json: *const NemoRelayNativeString,
2688) -> NemoRelayStatus
2689where
2690 F: Fn(&Event) + Send + Sync + 'static,
2691{
2692 if user_data.is_null() {
2693 return NemoRelayStatus::NullPointer;
2694 }
2695 let state = unsafe { &*(user_data as *const TypedCallback<F>) };
2696 let result = catch_unwind(AssertUnwindSafe(|| {
2697 let event: Event = read_json_value(&state.host, event_json, "event")?;
2698 (state.callback)(&event);
2699 Ok::<_, NemoRelayStatus>(())
2700 }));
2701 match result {
2702 Ok(Ok(())) => NemoRelayStatus::Ok,
2703 Ok(Err(status)) => status,
2704 Err(_) => callback_panic(&state.host, "subscriber callback"),
2705 }
2706}
2707
2708unsafe extern "C" fn typed_event_sanitize_trampoline<F>(
2709 user_data: *mut c_void,
2710 event_json: *const NemoRelayNativeString,
2711 fields_json: *const NemoRelayNativeString,
2712 out_fields_json: *mut *mut NemoRelayNativeString,
2713) -> NemoRelayStatus
2714where
2715 F: Fn(&Event, EventSanitizeFields) -> EventSanitizeFields + Send + Sync + 'static,
2716{
2717 if user_data.is_null() || out_fields_json.is_null() {
2718 return NemoRelayStatus::NullPointer;
2719 }
2720 unsafe { *out_fields_json = ptr::null_mut() };
2721 let state = unsafe { &*(user_data as *const TypedCallback<F>) };
2722 let result = catch_unwind(AssertUnwindSafe(|| {
2723 let event: Event = read_json_value(&state.host, event_json, "event")?;
2724 let fields: EventSanitizeFields =
2725 read_json_value(&state.host, fields_json, "event sanitize fields")?;
2726 let output = (state.callback)(&event, fields);
2727 Ok::<_, NemoRelayStatus>(write_json(&state.host, &output, out_fields_json))
2728 }));
2729 match result {
2730 Ok(Ok(status)) => status,
2731 Ok(Err(status)) => status,
2732 Err(_) => callback_panic(&state.host, "event sanitize callback"),
2733 }
2734}
2735
2736unsafe extern "C" fn typed_tool_sanitize_trampoline<F>(
2737 user_data: *mut c_void,
2738 name: *const NemoRelayNativeString,
2739 payload_json: *const NemoRelayNativeString,
2740 out_json: *mut *mut NemoRelayNativeString,
2741) -> NemoRelayStatus
2742where
2743 F: Fn(&str, Json) -> Json + Send + Sync + 'static,
2744{
2745 if user_data.is_null() || out_json.is_null() {
2746 return NemoRelayStatus::NullPointer;
2747 }
2748 unsafe { *out_json = ptr::null_mut() };
2749 let state = unsafe { &*(user_data as *const TypedCallback<F>) };
2750 let result = catch_unwind(AssertUnwindSafe(|| {
2751 let name = read_required_host_string(&state.host, name, "tool name")?;
2752 let payload: Json = read_json_value(&state.host, payload_json, "tool payload")?;
2753 let output = (state.callback)(&name, payload);
2754 Ok::<_, NemoRelayStatus>(write_json(&state.host, &output, out_json))
2755 }));
2756 match result {
2757 Ok(Ok(status)) => status,
2758 Ok(Err(status)) => status,
2759 Err(_) => callback_panic(&state.host, "tool sanitize callback"),
2760 }
2761}
2762
2763unsafe extern "C" fn typed_tool_intercept_trampoline<F>(
2764 user_data: *mut c_void,
2765 name: *const NemoRelayNativeString,
2766 payload_json: *const NemoRelayNativeString,
2767 out_json: *mut *mut NemoRelayNativeString,
2768) -> NemoRelayStatus
2769where
2770 F: Fn(&str, Json) -> Result<Json> + Send + Sync + 'static,
2771{
2772 if user_data.is_null() || out_json.is_null() {
2773 return NemoRelayStatus::NullPointer;
2774 }
2775 unsafe { *out_json = ptr::null_mut() };
2776 let state = unsafe { &*(user_data as *const TypedCallback<F>) };
2777 let result = catch_unwind(AssertUnwindSafe(|| {
2778 let name = read_required_host_string(&state.host, name, "tool name")?;
2779 let payload: Json = read_json_value(&state.host, payload_json, "tool payload")?;
2780 match (state.callback)(&name, payload) {
2781 Ok(output) => Ok::<_, NemoRelayStatus>(write_json(&state.host, &output, out_json)),
2782 Err(message) => Ok(callback_error(&state.host, message)),
2783 }
2784 }));
2785 match result {
2786 Ok(Ok(status)) => status,
2787 Ok(Err(status)) => status,
2788 Err(_) => callback_panic(&state.host, "tool intercept callback"),
2789 }
2790}
2791
2792unsafe extern "C" fn typed_tool_conditional_trampoline<F>(
2793 user_data: *mut c_void,
2794 name: *const NemoRelayNativeString,
2795 args_json: *const NemoRelayNativeString,
2796 out_reason: *mut *mut NemoRelayNativeString,
2797) -> NemoRelayStatus
2798where
2799 F: Fn(&str, &Json) -> Result<Option<String>> + Send + Sync + 'static,
2800{
2801 if user_data.is_null() || out_reason.is_null() {
2802 return NemoRelayStatus::NullPointer;
2803 }
2804 unsafe { *out_reason = ptr::null_mut() };
2805 let state = unsafe { &*(user_data as *const TypedCallback<F>) };
2806 let result = catch_unwind(AssertUnwindSafe(|| {
2807 let name = read_required_host_string(&state.host, name, "tool name")?;
2808 let args: Json = read_json_value(&state.host, args_json, "tool args")?;
2809 match (state.callback)(&name, &args) {
2810 Ok(Some(reason)) => {
2811 let reason =
2812 HostString::new(&state.host, &reason).ok_or(NemoRelayStatus::Internal)?;
2813 unsafe { *out_reason = reason.ptr };
2814 std::mem::forget(reason);
2815 Ok(NemoRelayStatus::Ok)
2816 }
2817 Ok(None) => {
2818 unsafe { *out_reason = ptr::null_mut() };
2819 Ok(NemoRelayStatus::Ok)
2820 }
2821 Err(message) => Ok(callback_error(&state.host, message)),
2822 }
2823 }));
2824 match result {
2825 Ok(Ok(status)) => status,
2826 Ok(Err(status)) => status,
2827 Err(_) => callback_panic(&state.host, "tool conditional callback"),
2828 }
2829}
2830
2831unsafe extern "C" fn typed_tool_execution_trampoline<F>(
2832 user_data: *mut c_void,
2833 name: *const NemoRelayNativeString,
2834 args_json: *const NemoRelayNativeString,
2835 next_fn: NemoRelayNativeToolNextFn,
2836 next_ctx: *mut c_void,
2837 out_outcome_json: *mut *mut NemoRelayNativeString,
2838) -> NemoRelayStatus
2839where
2840 F: for<'next> Fn(&str, Json, ToolNext<'next>) -> Result<ToolExecutionInterceptOutcome>
2841 + Send
2842 + Sync
2843 + 'static,
2844{
2845 if user_data.is_null() || out_outcome_json.is_null() {
2846 return NemoRelayStatus::NullPointer;
2847 }
2848 unsafe { *out_outcome_json = ptr::null_mut() };
2849 let state = unsafe { &*(user_data as *const TypedCallback<F>) };
2850 let result = catch_unwind(AssertUnwindSafe(|| {
2851 let name = read_required_host_string(&state.host, name, "tool name")?;
2852 let args: Json = read_json_value(&state.host, args_json, "tool args")?;
2853 let next = ToolNext {
2854 host: &state.host,
2855 next_fn,
2856 next_ctx,
2857 };
2858 match (state.callback)(&name, args, next) {
2859 Ok(outcome) => {
2860 let Some(outcome) = HostString::from_json(&state.host, &outcome) else {
2861 set_last_error(&state.host, "failed to allocate tool execution outcome");
2862 return Ok(NemoRelayStatus::Internal);
2863 };
2864 unsafe { *out_outcome_json = outcome.ptr };
2865 std::mem::forget(outcome);
2866 Ok(NemoRelayStatus::Ok)
2867 }
2868 Err(message) => Ok(callback_error(&state.host, message)),
2869 }
2870 }));
2871 match result {
2872 Ok(Ok(status)) => status,
2873 Ok(Err(status)) => status,
2874 Err(_) => callback_panic(&state.host, "tool execution callback"),
2875 }
2876}
2877
2878unsafe extern "C" fn typed_llm_sanitize_request_trampoline<F>(
2879 user_data: *mut c_void,
2880 request_json: *const NemoRelayNativeString,
2881 context: NemoRelayNativeLlmSanitizeRequestContext,
2882 out_request_json: *mut *mut NemoRelayNativeString,
2883) -> NemoRelayStatus
2884where
2885 F: for<'a> Fn(LlmRequest, LlmSanitizeRequestContext<'a>) -> Option<LlmRequest>
2886 + Send
2887 + Sync
2888 + 'static,
2889{
2890 if user_data.is_null() || out_request_json.is_null() {
2891 return NemoRelayStatus::NullPointer;
2892 }
2893 unsafe { *out_request_json = ptr::null_mut() };
2894 let state = unsafe { &*(user_data as *const TypedCallback<F>) };
2895 let result = catch_unwind(AssertUnwindSafe(|| {
2896 let context = llm_sanitize_request_context_from_native(&state.host, context)?;
2897 let request: LlmRequest = read_json_value(&state.host, request_json, "LLM request")?;
2898 match (state.callback)(request, context) {
2899 Some(output) => {
2900 Ok::<_, NemoRelayStatus>(write_json(&state.host, &output, out_request_json))
2901 }
2902 None => Ok(NemoRelayStatus::Ok),
2903 }
2904 }));
2905 match result {
2906 Ok(Ok(status)) => status,
2907 Ok(Err(status)) => status,
2908 Err(_) => callback_panic(&state.host, "LLM sanitize request callback"),
2909 }
2910}
2911
2912unsafe extern "C" fn typed_llm_sanitize_response_trampoline<F>(
2913 user_data: *mut c_void,
2914 payload_json: *const NemoRelayNativeString,
2915 context: NemoRelayNativeLlmSanitizeResponseContext,
2916 out_json: *mut *mut NemoRelayNativeString,
2917) -> NemoRelayStatus
2918where
2919 F: for<'a> Fn(Json, LlmSanitizeResponseContext<'a>) -> Option<Json> + Send + Sync + 'static,
2920{
2921 if user_data.is_null() || out_json.is_null() {
2922 return NemoRelayStatus::NullPointer;
2923 }
2924 unsafe { *out_json = ptr::null_mut() };
2925 let state = unsafe { &*(user_data as *const TypedCallback<F>) };
2926 let result = catch_unwind(AssertUnwindSafe(|| {
2927 let context = llm_sanitize_response_context_from_native(&state.host, context)?;
2928 let payload: Json = read_json_value(&state.host, payload_json, "LLM response")?;
2929 match (state.callback)(payload, context) {
2930 Some(output) => Ok::<_, NemoRelayStatus>(write_json(&state.host, &output, out_json)),
2931 None => Ok(NemoRelayStatus::Ok),
2932 }
2933 }));
2934 match result {
2935 Ok(Ok(status)) => status,
2936 Ok(Err(status)) => status,
2937 Err(_) => callback_panic(&state.host, "LLM sanitize response callback"),
2938 }
2939}
2940
2941unsafe extern "C" fn typed_llm_conditional_trampoline<F>(
2942 user_data: *mut c_void,
2943 request_json: *const NemoRelayNativeString,
2944 out_reason: *mut *mut NemoRelayNativeString,
2945) -> NemoRelayStatus
2946where
2947 F: Fn(&LlmRequest) -> Result<Option<String>> + Send + Sync + 'static,
2948{
2949 if user_data.is_null() || out_reason.is_null() {
2950 return NemoRelayStatus::NullPointer;
2951 }
2952 unsafe { *out_reason = ptr::null_mut() };
2953 let state = unsafe { &*(user_data as *const TypedCallback<F>) };
2954 let result = catch_unwind(AssertUnwindSafe(|| {
2955 let request: LlmRequest = read_json_value(&state.host, request_json, "LLM request")?;
2956 match (state.callback)(&request) {
2957 Ok(Some(reason)) => {
2958 let reason =
2959 HostString::new(&state.host, &reason).ok_or(NemoRelayStatus::Internal)?;
2960 unsafe { *out_reason = reason.ptr };
2961 std::mem::forget(reason);
2962 Ok(NemoRelayStatus::Ok)
2963 }
2964 Ok(None) => {
2965 unsafe { *out_reason = ptr::null_mut() };
2966 Ok(NemoRelayStatus::Ok)
2967 }
2968 Err(message) => Ok(callback_error(&state.host, message)),
2969 }
2970 }));
2971 match result {
2972 Ok(Ok(status)) => status,
2973 Ok(Err(status)) => status,
2974 Err(_) => callback_panic(&state.host, "LLM conditional callback"),
2975 }
2976}
2977
2978unsafe extern "C" fn typed_llm_request_intercept_trampoline<F>(
2979 user_data: *mut c_void,
2980 name: *const NemoRelayNativeString,
2981 request_json: *const NemoRelayNativeString,
2982 annotated_json: *const NemoRelayNativeString,
2983 out_outcome_json: *mut *mut NemoRelayNativeString,
2984) -> NemoRelayStatus
2985where
2986 F: Fn(&str, LlmRequest, Option<AnnotatedLlmRequest>) -> Result<LlmRequestInterceptOutcome>
2987 + Send
2988 + Sync
2989 + 'static,
2990{
2991 if user_data.is_null() || out_outcome_json.is_null() {
2992 return NemoRelayStatus::NullPointer;
2993 }
2994 unsafe {
2995 *out_outcome_json = ptr::null_mut();
2996 }
2997 let state = unsafe { &*(user_data as *const TypedCallback<F>) };
2998 let result = catch_unwind(AssertUnwindSafe(|| {
2999 let name = read_required_host_string(&state.host, name, "LLM name")?;
3000 let request: LlmRequest = read_json_value(&state.host, request_json, "LLM request")?;
3001 let annotated: Option<AnnotatedLlmRequest> =
3002 read_optional_json_value(&state.host, annotated_json, "annotated LLM request")?;
3003 match (state.callback)(&name, request, annotated) {
3004 Ok(outcome) => {
3005 let Some(outcome) = HostString::from_json(&state.host, &outcome) else {
3006 set_last_error(&state.host, "failed to allocate LLM request outcome");
3007 return Ok(NemoRelayStatus::Internal);
3008 };
3009 unsafe {
3010 *out_outcome_json = outcome.ptr;
3011 }
3012 std::mem::forget(outcome);
3013 Ok(NemoRelayStatus::Ok)
3014 }
3015 Err(message) => Ok(callback_error(&state.host, message)),
3016 }
3017 }));
3018 match result {
3019 Ok(Ok(status)) => status,
3020 Ok(Err(status)) => status,
3021 Err(_) => callback_panic(&state.host, "LLM request intercept callback"),
3022 }
3023}
3024
3025unsafe extern "C" fn typed_llm_execution_trampoline<F>(
3026 user_data: *mut c_void,
3027 name: *const NemoRelayNativeString,
3028 request_json: *const NemoRelayNativeString,
3029 next_fn: NemoRelayNativeLlmNextFn,
3030 next_ctx: *mut c_void,
3031 out_json: *mut *mut NemoRelayNativeString,
3032) -> NemoRelayStatus
3033where
3034 F: for<'next> Fn(&str, LlmRequest, LlmNext<'next>) -> Result<Json> + Send + Sync + 'static,
3035{
3036 if user_data.is_null() || out_json.is_null() {
3037 return NemoRelayStatus::NullPointer;
3038 }
3039 unsafe { *out_json = ptr::null_mut() };
3040 let state = unsafe { &*(user_data as *const TypedCallback<F>) };
3041 let result = catch_unwind(AssertUnwindSafe(|| {
3042 let name = read_required_host_string(&state.host, name, "LLM name")?;
3043 let request: LlmRequest = read_json_value(&state.host, request_json, "LLM request")?;
3044 let next = LlmNext {
3045 host: &state.host,
3046 next_fn,
3047 next_ctx,
3048 };
3049 match (state.callback)(&name, request, next) {
3050 Ok(output) => Ok::<_, NemoRelayStatus>(write_json(&state.host, &output, out_json)),
3051 Err(message) => Ok(callback_error(&state.host, message)),
3052 }
3053 }));
3054 match result {
3055 Ok(Ok(status)) => status,
3056 Ok(Err(status)) => status,
3057 Err(_) => callback_panic(&state.host, "LLM execution callback"),
3058 }
3059}
3060
3061struct TypedLlmJsonStream {
3062 host: NemoRelayNativeHostApiV1,
3063 state: Mutex<TypedLlmJsonStreamState>,
3064}
3065
3066struct TypedLlmJsonStreamState {
3067 iter: LlmJsonStream,
3068 finished: bool,
3069}
3070
3071fn native_stream_from_iter(
3072 host: &NemoRelayNativeHostApiV1,
3073 iter: LlmJsonStream,
3074) -> NemoRelayNativeLlmStreamV1 {
3075 let state = Box::new(TypedLlmJsonStream {
3076 host: *host,
3077 state: Mutex::new(TypedLlmJsonStreamState {
3078 iter,
3079 finished: false,
3080 }),
3081 });
3082 NemoRelayNativeLlmStreamV1 {
3083 struct_size: std::mem::size_of::<NemoRelayNativeLlmStreamV1>(),
3084 user_data: Box::into_raw(state).cast(),
3085 next: Some(poll_typed_llm_json_stream),
3086 cancel: Some(cancel_typed_llm_json_stream),
3087 drop: Some(drop_typed_llm_json_stream),
3088 }
3089}
3090
3091unsafe extern "C" fn poll_typed_llm_json_stream(
3092 user_data: *mut c_void,
3093 out_json: *mut *mut NemoRelayNativeString,
3094) -> NemoRelayStatus {
3095 if user_data.is_null() || out_json.is_null() {
3096 return NemoRelayStatus::NullPointer;
3097 }
3098 unsafe { *out_json = ptr::null_mut() };
3099 let stream = unsafe { &*(user_data as *const TypedLlmJsonStream) };
3100 let result = catch_unwind(AssertUnwindSafe(|| {
3101 let mut state = match stream.state.lock() {
3102 Ok(state) => state,
3103 Err(_) => {
3104 set_last_error(&stream.host, "native plugin stream state lock poisoned");
3105 return NemoRelayStatus::Internal;
3106 }
3107 };
3108 if state.finished {
3109 return NemoRelayStatus::StreamEnd;
3110 }
3111 match state.iter.next() {
3112 Some(Ok(chunk)) => {
3113 let status = write_json(&stream.host, &chunk, out_json);
3114 if status != NemoRelayStatus::Ok {
3115 state.finished = true;
3116 }
3117 status
3118 }
3119 Some(Err(message)) => {
3120 state.finished = true;
3121 callback_error(&stream.host, message)
3122 }
3123 None => {
3124 state.finished = true;
3125 NemoRelayStatus::StreamEnd
3126 }
3127 }
3128 }));
3129 result.unwrap_or_else(|_| callback_panic(&stream.host, "LLM stream callback"))
3130}
3131
3132unsafe extern "C" fn cancel_typed_llm_json_stream(user_data: *mut c_void) -> NemoRelayStatus {
3133 if user_data.is_null() {
3134 return NemoRelayStatus::NullPointer;
3135 }
3136 let stream = unsafe { &*(user_data as *const TypedLlmJsonStream) };
3137 let result = catch_unwind(AssertUnwindSafe(|| {
3138 let mut state = match stream.state.lock() {
3139 Ok(state) => state,
3140 Err(_) => {
3141 set_last_error(&stream.host, "native plugin stream state lock poisoned");
3142 return NemoRelayStatus::Internal;
3143 }
3144 };
3145 state.finished = true;
3146 NemoRelayStatus::Ok
3147 }));
3148 result.unwrap_or_else(|_| callback_panic(&stream.host, "LLM stream cancel callback"))
3149}
3150
3151unsafe extern "C" fn drop_typed_llm_json_stream(user_data: *mut c_void) {
3152 if !user_data.is_null() {
3153 let stream = unsafe { Box::from_raw(user_data as *mut TypedLlmJsonStream) };
3154 let host = stream.host;
3155 if catch_unwind(AssertUnwindSafe(|| drop(stream))).is_err() {
3156 set_last_error(&host, "native plugin LLM stream state drop panicked");
3157 }
3158 }
3159}
3160
3161unsafe extern "C" fn typed_llm_stream_execution_trampoline<F>(
3162 user_data: *mut c_void,
3163 name: *const NemoRelayNativeString,
3164 request_json: *const NemoRelayNativeString,
3165 next_fn: NemoRelayNativeLlmStreamNextFn,
3166 next_ctx: *mut c_void,
3167 out_stream: *mut NemoRelayNativeLlmStreamV1,
3168) -> NemoRelayStatus
3169where
3170 F: for<'next> Fn(&str, LlmRequest, LlmStreamNext<'next>) -> Result<LlmJsonStream>
3171 + Send
3172 + Sync
3173 + 'static,
3174{
3175 if user_data.is_null() || out_stream.is_null() {
3176 return NemoRelayStatus::NullPointer;
3177 }
3178 unsafe { *out_stream = NemoRelayNativeLlmStreamV1::default() };
3179 let state = unsafe { &*(user_data as *const TypedCallback<F>) };
3180 let result = catch_unwind(AssertUnwindSafe(|| {
3181 let name = read_required_host_string(&state.host, name, "LLM name")?;
3182 let request: LlmRequest = read_json_value(&state.host, request_json, "LLM request")?;
3183 let next = LlmStreamNext {
3184 host: &state.host,
3185 next_fn,
3186 next_ctx,
3187 };
3188 match (state.callback)(&name, request, next) {
3189 Ok(stream) => {
3190 unsafe { *out_stream = native_stream_from_iter(&state.host, stream) };
3191 Ok::<_, NemoRelayStatus>(NemoRelayStatus::Ok)
3192 }
3193 Err(message) => Ok(callback_error(&state.host, message)),
3194 }
3195 }));
3196 match result {
3197 Ok(Ok(status)) => status,
3198 Ok(Err(status)) => status,
3199 Err(_) => callback_panic(&state.host, "LLM stream execution callback"),
3200 }
3201}
3202
3203struct HostString<'a> {
3204 host: &'a NemoRelayNativeHostApiV1,
3205 ptr: *mut NemoRelayNativeString,
3206}
3207
3208impl<'a> HostString<'a> {
3209 fn try_new(
3210 host: &'a NemoRelayNativeHostApiV1,
3211 value: &str,
3212 ) -> std::result::Result<Self, NemoRelayStatus> {
3213 let mut out = ptr::null_mut();
3214 let status = unsafe { (host.string_new)(value.as_ptr(), value.len(), &mut out) };
3215 if status != NemoRelayStatus::Ok {
3216 return Err(status);
3217 }
3218 if out.is_null() {
3219 return Err(NemoRelayStatus::Internal);
3220 }
3221 Ok(Self { host, ptr: out })
3222 }
3223
3224 fn new(host: &'a NemoRelayNativeHostApiV1, value: &str) -> Option<Self> {
3225 Self::try_new(host, value).ok()
3226 }
3227
3228 fn from_json<T: Serialize>(host: &'a NemoRelayNativeHostApiV1, value: &T) -> Option<Self> {
3229 serde_json::to_string(value)
3230 .ok()
3231 .and_then(|json| Self::new(host, &json))
3232 }
3233
3234 fn as_ptr(&self) -> *const NemoRelayNativeString {
3235 self.ptr
3236 }
3237}
3238
3239impl Drop for HostString<'_> {
3240 fn drop(&mut self) {
3241 unsafe { (self.host.string_free)(self.ptr) };
3242 }
3243}
3244
3245fn codec_status(host: &NemoRelayNativeHostApiV1, status: NemoRelayStatus) -> Result<()> {
3246 if status == NemoRelayStatus::Ok {
3247 Ok(())
3248 } else {
3249 Err(status_error(host, status, "LLM codec operation"))
3250 }
3251}
3252
3253fn native_codec_call<T: DeserializeOwned>(
3254 host: &NemoRelayNativeHostApiV1,
3255 call: impl FnOnce(*mut *mut NemoRelayNativeString) -> Result<()>,
3256) -> Result<T> {
3257 let mut out = ptr::null_mut();
3258 call(&mut out)?;
3259 if out.is_null() {
3260 return Err("LLM codec operation returned null".into());
3261 }
3262 let out = HostString { host, ptr: out };
3263 let text = read_host_string(host, out.as_ptr())
3264 .map_err(|_| "LLM codec operation returned invalid UTF-8".to_string())?;
3265 serde_json::from_str(&text).map_err(|error| format!("invalid LLM codec result: {error}"))
3266}
3267
3268struct OptionalHostJson<'a>(Option<HostString<'a>>);
3269
3270impl<'a> OptionalHostJson<'a> {
3271 fn new(host: &'a NemoRelayNativeHostApiV1, value: Option<&Json>) -> Result<Self> {
3272 match value {
3273 Some(value) => HostString::from_json(host, value)
3274 .map(|value| Self(Some(value)))
3275 .ok_or_else(|| "failed to allocate JSON host string".into()),
3276 None => Ok(Self(None)),
3277 }
3278 }
3279
3280 fn as_ptr(&self) -> *const NemoRelayNativeString {
3281 self.0
3282 .as_ref()
3283 .map(HostString::as_ptr)
3284 .unwrap_or(ptr::null())
3285 }
3286}
3287
3288enum OwnedHostApi {
3289 V1(NemoRelayNativeHostApiV1),
3290 V3(NemoRelayNativeHostApiV3),
3291}
3292
3293impl OwnedHostApi {
3294 unsafe fn copy_from(host: &NemoRelayNativeHostApiV1) -> Self {
3295 if host.abi_version >= NEMO_RELAY_NATIVE_ABI_VERSION_ASYNC_MIDDLEWARE
3296 && host.struct_size >= std::mem::size_of::<NemoRelayNativeHostApiV3>()
3297 {
3298 Self::V3(unsafe { *(host as *const _ as *const NemoRelayNativeHostApiV3) })
3299 } else {
3300 Self::V1(*host)
3301 }
3302 }
3303
3304 fn v1(&self) -> &NemoRelayNativeHostApiV1 {
3305 match self {
3306 Self::V1(host) => host,
3307 Self::V3(host) => &host.v1,
3308 }
3309 }
3310}
3311
3312struct PluginState<P> {
3313 host: OwnedHostApi,
3314 plugin: Mutex<P>,
3315}
3316
3317unsafe extern "C" fn drop_plugin_state<P: NativePlugin>(user_data: *mut c_void) {
3318 if !user_data.is_null() {
3319 let state = unsafe { Box::from_raw(user_data as *mut PluginState<P>) };
3320 let host = *state.host.v1();
3321 if catch_unwind(AssertUnwindSafe(|| drop(state))).is_err() {
3322 set_last_error(&host, "native plugin state drop panicked");
3323 }
3324 }
3325}
3326
3327unsafe extern "C" fn validate_trampoline<P: NativePlugin>(
3328 user_data: *mut c_void,
3329 plugin_config_json: *const NemoRelayNativeString,
3330 out_diagnostics_json: *mut *mut NemoRelayNativeString,
3331) -> NemoRelayStatus {
3332 if user_data.is_null() || out_diagnostics_json.is_null() {
3333 return NemoRelayStatus::NullPointer;
3334 }
3335 unsafe { *out_diagnostics_json = ptr::null_mut() };
3336 let state = unsafe { &*(user_data as *const PluginState<P>) };
3337 let result = catch_unwind(AssertUnwindSafe(|| {
3338 let host = state.host.v1();
3339 let config = match read_json_object(host, plugin_config_json) {
3340 Ok(config) => config,
3341 Err(status) => return status,
3342 };
3343 let plugin = match state.plugin.lock() {
3344 Ok(plugin) => plugin,
3345 Err(_) => {
3346 set_last_error(host, "native plugin state lock poisoned");
3347 return NemoRelayStatus::Internal;
3348 }
3349 };
3350 let diagnostics = plugin.validate(&config);
3351 write_json(host, &diagnostics, out_diagnostics_json)
3352 }));
3353 result.unwrap_or_else(|_| {
3354 set_last_error(state.host.v1(), "native plugin validate callback panicked");
3355 NemoRelayStatus::Internal
3356 })
3357}
3358
3359unsafe extern "C" fn register_trampoline<P: NativePlugin>(
3360 user_data: *mut c_void,
3361 plugin_config_json: *const NemoRelayNativeString,
3362 ctx: *mut NemoRelayNativePluginContext,
3363) -> NemoRelayStatus {
3364 if user_data.is_null() || ctx.is_null() {
3365 return NemoRelayStatus::NullPointer;
3366 }
3367 let state = unsafe { &*(user_data as *const PluginState<P>) };
3368 let result = catch_unwind(AssertUnwindSafe(|| {
3369 let host = state.host.v1();
3370 let config = match read_json_object(host, plugin_config_json) {
3371 Ok(config) => config,
3372 Err(status) => return status,
3373 };
3374 let mut ctx = unsafe { PluginContext::from_raw(host, ctx) };
3375 let mut plugin = match state.plugin.lock() {
3376 Ok(plugin) => plugin,
3377 Err(_) => {
3378 set_last_error(host, "native plugin state lock poisoned");
3379 return NemoRelayStatus::Internal;
3380 }
3381 };
3382 match plugin.register(&config, &mut ctx) {
3383 Ok(()) => NemoRelayStatus::Ok,
3384 Err(message) => {
3385 set_last_error(host, &message);
3386 NemoRelayStatus::Internal
3387 }
3388 }
3389 }));
3390 result.unwrap_or_else(|_| {
3391 set_last_error(state.host.v1(), "native plugin register callback panicked");
3392 NemoRelayStatus::Internal
3393 })
3394}
3395
3396fn read_json_object(
3397 host: &NemoRelayNativeHostApiV1,
3398 value: *const NemoRelayNativeString,
3399) -> std::result::Result<Map<String, Json>, NemoRelayStatus> {
3400 let value: Json = read_json_value(host, value, "plugin config")?;
3401 match value {
3402 Json::Object(map) => Ok(map),
3403 _ => {
3404 set_last_error(host, "plugin config must be a JSON object");
3405 Err(NemoRelayStatus::InvalidJson)
3406 }
3407 }
3408}
3409
3410fn read_json_value<T: DeserializeOwned>(
3411 host: &NemoRelayNativeHostApiV1,
3412 value: *const NemoRelayNativeString,
3413 label: &str,
3414) -> std::result::Result<T, NemoRelayStatus> {
3415 let text = read_required_host_string(host, value, label)?;
3416 serde_json::from_str::<T>(&text).map_err(|error| {
3417 set_last_error(host, &format!("{label} was invalid JSON: {error}"));
3418 NemoRelayStatus::InvalidJson
3419 })
3420}
3421
3422fn read_optional_json_value<T: DeserializeOwned>(
3423 host: &NemoRelayNativeHostApiV1,
3424 value: *const NemoRelayNativeString,
3425 label: &str,
3426) -> std::result::Result<Option<T>, NemoRelayStatus> {
3427 if value.is_null() {
3428 Ok(None)
3429 } else {
3430 read_json_value(host, value, label).map(Some)
3431 }
3432}
3433
3434fn llm_codec_identity_from_native(
3435 host: &NemoRelayNativeHostApiV1,
3436 codec_kind: NemoRelayNativeLlmCodecKind,
3437 codec_id: *const NemoRelayNativeString,
3438) -> std::result::Result<LlmCodecIdentity, NemoRelayStatus> {
3439 let codec = match codec_kind {
3440 NemoRelayNativeLlmCodecKind::None => LlmCodecIdentity::None,
3441 NemoRelayNativeLlmCodecKind::Opaque => LlmCodecIdentity::Opaque,
3442 NemoRelayNativeLlmCodecKind::BuiltIn => {
3443 let id = read_required_host_string(host, codec_id, "LLM built-in codec ID")?;
3444 let builtin = match id.as_str() {
3445 "openai_chat" => BuiltinLlmCodec::OpenAiChat,
3446 "openai_responses" => BuiltinLlmCodec::OpenAiResponses,
3447 "anthropic_messages" => BuiltinLlmCodec::AnthropicMessages,
3448 _ => {
3449 set_last_error(host, &format!("unknown built-in LLM codec ID: {id}"));
3450 return Err(NemoRelayStatus::InvalidArg);
3451 }
3452 };
3453 LlmCodecIdentity::BuiltIn(builtin)
3454 }
3455 NemoRelayNativeLlmCodecKind::Runtime => LlmCodecIdentity::Runtime(
3456 read_required_host_string(host, codec_id, "LLM runtime codec ID")?,
3457 ),
3458 };
3459 Ok(codec)
3460}
3461
3462fn llm_sanitize_request_context_from_native<'a>(
3463 host: &NemoRelayNativeHostApiV1,
3464 context: NemoRelayNativeLlmSanitizeRequestContext,
3465) -> std::result::Result<LlmSanitizeRequestContext<'a>, NemoRelayStatus> {
3466 let codec = llm_codec_identity_from_native(host, context.codec_kind, context.codec_id)?;
3467 let resolved = (!context.codec.is_null()).then_some(LlmSanitizeRequestCodec {
3468 host: *host,
3469 handle: context.codec,
3470 _lifetime: PhantomData,
3471 });
3472 Ok(LlmSanitizeRequestContext { codec, resolved })
3473}
3474
3475fn llm_sanitize_response_context_from_native<'a>(
3476 host: &NemoRelayNativeHostApiV1,
3477 context: NemoRelayNativeLlmSanitizeResponseContext,
3478) -> std::result::Result<LlmSanitizeResponseContext<'a>, NemoRelayStatus> {
3479 let codec = llm_codec_identity_from_native(host, context.codec_kind, context.codec_id)?;
3480 let resolved = (!context.codec.is_null()).then_some(LlmSanitizeResponseCodec {
3481 host: *host,
3482 handle: context.codec,
3483 _lifetime: PhantomData,
3484 });
3485 Ok(LlmSanitizeResponseContext { codec, resolved })
3486}
3487
3488enum HostStringReadError {
3489 Null,
3490 InvalidUtf8,
3491}
3492
3493fn read_required_host_string(
3494 host: &NemoRelayNativeHostApiV1,
3495 value: *const NemoRelayNativeString,
3496 label: &str,
3497) -> std::result::Result<String, NemoRelayStatus> {
3498 match read_host_string(host, value) {
3499 Ok(value) => Ok(value),
3500 Err(HostStringReadError::Null) => {
3501 set_last_error(host, &format!("{label} was null"));
3502 Err(NemoRelayStatus::NullPointer)
3503 }
3504 Err(HostStringReadError::InvalidUtf8) => {
3505 set_last_error(host, &format!("{label} contained invalid UTF-8"));
3506 Err(NemoRelayStatus::InvalidUtf8)
3507 }
3508 }
3509}
3510
3511fn read_host_string(
3512 host: &NemoRelayNativeHostApiV1,
3513 value: *const NemoRelayNativeString,
3514) -> std::result::Result<String, HostStringReadError> {
3515 if value.is_null() {
3516 return Err(HostStringReadError::Null);
3517 }
3518 let len = unsafe { (host.string_len)(value) };
3519 let data = unsafe { (host.string_data)(value) };
3520 if data.is_null() && len > 0 {
3521 return Err(HostStringReadError::InvalidUtf8);
3522 }
3523 let bytes = if len == 0 {
3524 &[][..]
3525 } else {
3526 unsafe { std::slice::from_raw_parts(data, len) }
3527 };
3528 std::str::from_utf8(bytes)
3529 .map(str::to_owned)
3530 .map_err(|_| HostStringReadError::InvalidUtf8)
3531}
3532
3533fn write_json<T: Serialize>(
3534 host: &NemoRelayNativeHostApiV1,
3535 value: &T,
3536 out: *mut *mut NemoRelayNativeString,
3537) -> NemoRelayStatus {
3538 if out.is_null() {
3539 return NemoRelayStatus::NullPointer;
3540 }
3541 unsafe { *out = ptr::null_mut() };
3542 let json = serde_json::to_value(value).expect("Relay DTOs and serde_json::Value serialize");
3543 let Some(handle) = HostString::from_json(host, &json) else {
3544 set_last_error(host, "failed to allocate host string");
3545 return NemoRelayStatus::Internal;
3546 };
3547 unsafe { *out = handle.ptr };
3548 std::mem::forget(handle);
3549 NemoRelayStatus::Ok
3550}
3551
3552fn set_last_error(host: &NemoRelayNativeHostApiV1, message: &str) {
3553 if let Some(message) = HostString::new(host, message) {
3554 unsafe { (host.last_error_set)(message.as_ptr()) };
3555 }
3556}
3557
3558#[doc(hidden)]
3563pub unsafe fn __set_last_error_from_entry(host: *const NemoRelayNativeHostApiV1, message: &str) {
3564 if !host.is_null() {
3565 set_last_error(unsafe { &*host }, message);
3566 }
3567}
3568
3569pub unsafe fn export_plugin<P: NativePlugin>(
3576 host: *const NemoRelayNativeHostApiV1,
3577 out: *mut NemoRelayNativePluginV1,
3578 plugin: P,
3579) -> NemoRelayStatus {
3580 if host.is_null() || out.is_null() {
3581 return NemoRelayStatus::NullPointer;
3582 }
3583 unsafe { *out = NemoRelayNativePluginV1::default() };
3584 let host_ref = unsafe { &*host };
3585 export_plugin_checked(host_ref, out, || plugin)
3586}
3587
3588#[doc(hidden)]
3595pub unsafe fn __export_plugin_from_constructor<P, F>(
3596 host: *const NemoRelayNativeHostApiV1,
3597 out: *mut NemoRelayNativePluginV1,
3598 constructor: F,
3599) -> NemoRelayStatus
3600where
3601 P: NativePlugin,
3602 F: FnOnce() -> P,
3603{
3604 if host.is_null() || out.is_null() {
3605 return NemoRelayStatus::NullPointer;
3606 }
3607 unsafe { *out = NemoRelayNativePluginV1::default() };
3608 let host_ref = unsafe { &*host };
3609 export_plugin_checked(host_ref, out, constructor)
3610}
3611
3612fn export_plugin_checked<P, F>(
3613 host_ref: &NemoRelayNativeHostApiV1,
3614 out: *mut NemoRelayNativePluginV1,
3615 constructor: F,
3616) -> NemoRelayStatus
3617where
3618 P: NativePlugin,
3619 F: FnOnce() -> P,
3620{
3621 if host_ref.abi_version != NEMO_RELAY_NATIVE_ABI_VERSION {
3622 return NemoRelayStatus::InvalidArg;
3623 }
3624 if host_ref.struct_size < std::mem::size_of::<NemoRelayNativeHostApiV1>() {
3625 return NemoRelayStatus::InvalidArg;
3626 }
3627
3628 let plugin = constructor();
3629 let kind = plugin.plugin_kind().to_owned();
3630 let allows_multiple_components = plugin.allows_multiple_components();
3631 let Some(kind_handle) = HostString::new(host_ref, &kind) else {
3632 return NemoRelayStatus::Internal;
3633 };
3634 let state = Box::new(PluginState {
3635 host: unsafe { OwnedHostApi::copy_from(host_ref) },
3636 plugin: Mutex::new(plugin),
3637 });
3638 unsafe {
3639 *out = NemoRelayNativePluginV1 {
3640 struct_size: std::mem::size_of::<NemoRelayNativePluginV1>(),
3641 plugin_kind: kind_handle.ptr,
3642 allows_multiple_components,
3643 user_data: Box::into_raw(state) as *mut c_void,
3644 validate: Some(validate_trampoline::<P>),
3645 register: Some(register_trampoline::<P>),
3646 drop: Some(drop_plugin_state::<P>),
3647 };
3648 }
3649 std::mem::forget(kind_handle);
3650 NemoRelayStatus::Ok
3651}
3652
3653#[macro_export]
3655macro_rules! nemo_relay_plugin {
3656 ($symbol:ident, $constructor:expr) => {
3657 #[doc = "Native plugin entry symbol generated by `nemo_relay_plugin!`."]
3658 #[unsafe(no_mangle)]
3659 pub unsafe extern "C" fn $symbol(
3660 host: *const $crate::NemoRelayNativeHostApiV1,
3661 out: *mut $crate::NemoRelayNativePluginV1,
3662 ) -> $crate::NemoRelayStatus {
3663 match ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| unsafe {
3664 $crate::__export_plugin_from_constructor(host, out, $constructor)
3665 })) {
3666 Ok(status) => status,
3667 Err(_) => {
3668 unsafe {
3669 $crate::__set_last_error_from_entry(
3670 host,
3671 "native plugin entry callback panicked",
3672 )
3673 };
3674 $crate::NemoRelayStatus::Internal
3675 }
3676 }
3677 }
3678 };
3679}