1#![deny(rustdoc::broken_intra_doc_links, rustdoc::private_intra_doc_links)]
5
6mod async_sdk;
13
14pub use async_sdk::{LlmJsonAsyncStream, LlmNext, LlmStreamNext, NativeExecutorConfig, ToolNext};
15
16use std::ffi::{c_char, c_void};
17use std::marker::{PhantomData, PhantomPinned};
18use std::panic::{AssertUnwindSafe, catch_unwind};
19use std::ptr;
20use std::sync::{Arc, Mutex};
21
22pub use nemo_relay_types::Json;
23pub use nemo_relay_types::api::event::{
24 CategoryProfile, DataSchema, Event, EventCategory, EventSanitizeFields, LogSeverity,
25 METRIC_DATA_SCHEMA_NAME, METRIC_DATA_SCHEMA_VERSION, MetricEnvelope, MetricKind,
26 MetricMeasurement, MetricValueType, PendingMarkSpec, ScopeCategory,
27};
28pub use nemo_relay_types::api::llm::{LlmAttributes, LlmRequest, LlmRequestInterceptOutcome};
29pub use nemo_relay_types::api::registry::{
30 RuntimeRegistrationIdentity, RuntimeRegistrationKind, RuntimeRegistrationOwner,
31 RuntimeRegistrationOwnerKind,
32};
33pub use nemo_relay_types::api::scope::{HandleAttributes, ScopeAttributes, ScopeType};
34pub use nemo_relay_types::api::tool::{
35 TOOL_EXECUTION_INTERCEPT_OUTCOME_SCHEMA, TOOL_EXECUTION_RESULT_SCHEMA, ToolAttributes,
36 ToolExecutionInterceptOutcome, ToolExecutionResult,
37};
38pub use nemo_relay_types::codec::identity::{BuiltinLlmCodec, LlmCodecIdentity};
39pub use nemo_relay_types::codec::optimization::{
40 LlmOptimizationContribution, LlmOptimizationEvidenceQuality, LlmOptimizationKind,
41 LlmOptimizationModel, LlmOptimizationModelTransition, LlmOptimizationPayload,
42 LlmOptimizationSummary, LlmOptimizationSummaryStatus, LlmOptimizationTokenImpact,
43 LlmOptimizationTokens,
44};
45pub use nemo_relay_types::codec::request::AnnotatedLlmRequest;
46pub use nemo_relay_types::codec::response::AnnotatedLlmResponse;
47pub use nemo_relay_types::plugin::{ConfigDiagnostic, DiagnosticLevel};
48use serde::{Serialize, de::DeserializeOwned};
49use serde_json::Map;
50
51pub const NEMO_RELAY_NATIVE_ABI_VERSION: u32 = 4;
59pub const NEMO_RELAY_NATIVE_ABI_VERSION_ASYNC_MIDDLEWARE: u32 = 3;
61
62pub const NEMO_RELAY_NATIVE_ABI_VERSION_LEGACY: u32 = 2;
64
65pub struct LlmSanitizeRequestContext<'a> {
67 pub codec: LlmCodecIdentity,
69 resolved: Option<LlmSanitizeRequestCodec<'a>>,
70}
71unsafe impl Send for LlmSanitizeRequestContext<'_> {}
74
75pub struct LlmSanitizeResponseContext<'a> {
77 pub codec: LlmCodecIdentity,
79 resolved: Option<LlmSanitizeResponseCodec<'a>>,
80}
81unsafe impl Send for LlmSanitizeResponseContext<'_> {}
84
85#[repr(i32)]
87#[derive(Debug, Clone, Copy, PartialEq, Eq)]
88pub enum NemoRelayStatus {
89 Ok = 0,
91 AlreadyExists = 1,
93 NotFound = 2,
95 ScopeStackEmpty = 3,
97 GuardrailRejected = 4,
99 Internal = 5,
101 NullPointer = 6,
103 InvalidJson = 7,
105 InvalidUtf8 = 8,
107 InvalidArg = 9,
109 StreamEnd = 10,
111 Backpressured = 11,
113}
114
115#[repr(C)]
117pub struct NemoRelayNativeString {
118 _private: [u8; 0],
119 _marker: PhantomData<(*mut u8, PhantomPinned)>,
120}
121
122#[repr(C)]
124pub struct NemoRelayNativeLlmRequestCodec {
125 _private: [u8; 0],
126 _marker: PhantomData<(*mut u8, PhantomPinned)>,
127}
128
129#[repr(C)]
131pub struct NemoRelayNativeLlmResponseCodec {
132 _private: [u8; 0],
133 _marker: PhantomData<(*mut u8, PhantomPinned)>,
134}
135
136#[repr(u32)]
138#[derive(Debug, Clone, Copy, PartialEq, Eq)]
139pub enum NemoRelayNativeLlmCodecKind {
140 None = 0,
142 BuiltIn = 1,
144 Runtime = 2,
146 Opaque = 3,
148}
149
150#[repr(C)]
157#[derive(Debug, Clone, Copy)]
158pub struct NemoRelayNativeLlmSanitizeRequestContext {
159 pub codec_kind: NemoRelayNativeLlmCodecKind,
161 pub codec_id: *const NemoRelayNativeString,
163 pub codec: *const NemoRelayNativeLlmRequestCodec,
165}
166
167#[repr(C)]
169#[derive(Debug, Clone, Copy)]
170pub struct NemoRelayNativeLlmSanitizeResponseContext {
171 pub codec_kind: NemoRelayNativeLlmCodecKind,
173 pub codec_id: *const NemoRelayNativeString,
175 pub codec: *const NemoRelayNativeLlmResponseCodec,
177}
178
179pub struct LlmSanitizeRequestCodec<'a> {
181 async_host: NemoRelayNativeHostApiV4,
182 completion: *const NemoRelayNativeAsyncCompletion,
183 completion_release: unsafe extern "C" fn(*const NemoRelayNativeAsyncCompletion),
184 _lifetime: PhantomData<&'a NemoRelayNativeLlmRequestCodec>,
185}
186unsafe impl Send for LlmSanitizeRequestCodec<'_> {}
189unsafe impl Sync for LlmSanitizeRequestCodec<'_> {}
192
193impl Drop for LlmSanitizeRequestCodec<'_> {
194 fn drop(&mut self) {
195 unsafe { (self.completion_release)(self.completion) };
196 }
197}
198
199impl LlmSanitizeRequestCodec<'_> {
200 pub fn decode(&self, request: &LlmRequest) -> Result<AnnotatedLlmRequest> {
202 native_codec_call(&self.async_host.v3.v1, |out| unsafe {
203 let request = HostString::from_json(&self.async_host.v3.v1, request)
204 .ok_or_else(|| "failed to serialize LLM request".to_string())?;
205 let status = (self.async_host.async_completion_llm_request_codec_decode)(
206 self.completion,
207 request.as_ptr(),
208 out,
209 );
210 codec_status(&self.async_host.v3.v1, status)
211 })
212 }
213
214 pub fn encode(
216 &self,
217 annotated: &AnnotatedLlmRequest,
218 original: &LlmRequest,
219 ) -> Result<LlmRequest> {
220 native_codec_call(&self.async_host.v3.v1, |out| unsafe {
221 let annotated = HostString::from_json(&self.async_host.v3.v1, annotated)
222 .ok_or_else(|| "failed to serialize annotated request".to_string())?;
223 let original = HostString::from_json(&self.async_host.v3.v1, original)
224 .ok_or_else(|| "failed to serialize original request".to_string())?;
225 let status = (self.async_host.async_completion_llm_request_codec_encode)(
226 self.completion,
227 annotated.as_ptr(),
228 original.as_ptr(),
229 out,
230 );
231 codec_status(&self.async_host.v3.v1, status)
232 })
233 }
234}
235
236pub struct LlmSanitizeResponseCodec<'a> {
238 async_host: NemoRelayNativeHostApiV4,
239 completion: *const NemoRelayNativeAsyncCompletion,
240 completion_release: unsafe extern "C" fn(*const NemoRelayNativeAsyncCompletion),
241 _lifetime: PhantomData<&'a NemoRelayNativeLlmResponseCodec>,
242}
243unsafe impl Send for LlmSanitizeResponseCodec<'_> {}
246unsafe impl Sync for LlmSanitizeResponseCodec<'_> {}
249
250impl Drop for LlmSanitizeResponseCodec<'_> {
251 fn drop(&mut self) {
252 unsafe { (self.completion_release)(self.completion) };
253 }
254}
255
256impl LlmSanitizeResponseCodec<'_> {
257 pub fn decode(&self, response: &Json) -> Result<AnnotatedLlmResponse> {
259 native_codec_call(&self.async_host.v3.v1, |out| unsafe {
260 let response = HostString::from_json(&self.async_host.v3.v1, response)
261 .ok_or_else(|| "failed to serialize LLM response".to_string())?;
262 let status = (self.async_host.async_completion_llm_response_codec_decode)(
263 self.completion,
264 response.as_ptr(),
265 out,
266 );
267 codec_status(&self.async_host.v3.v1, status)
268 })
269 }
270}
271
272impl<'a> LlmSanitizeRequestContext<'a> {
273 #[must_use]
275 pub fn resolve_codec(&self) -> Option<&LlmSanitizeRequestCodec<'a>> {
276 self.resolved.as_ref()
277 }
278}
279
280impl<'a> LlmSanitizeResponseContext<'a> {
281 #[must_use]
283 pub fn resolve_codec(&self) -> Option<&LlmSanitizeResponseCodec<'a>> {
284 self.resolved.as_ref()
285 }
286}
287
288#[repr(C)]
290pub struct NemoRelayNativePluginContext {
291 _private: [u8; 0],
292 _marker: PhantomData<(*mut u8, PhantomPinned)>,
293}
294
295#[repr(C)]
297pub struct NemoRelayNativePluginRuntime {
298 _private: [u8; 0],
299 _marker: PhantomData<(*mut u8, PhantomPinned)>,
300}
301
302#[repr(C)]
304pub struct NemoRelayNativeScopeHandle {
305 _private: [u8; 0],
306 _marker: PhantomData<(*mut u8, PhantomPinned)>,
307}
308
309#[repr(C)]
311pub struct NemoRelayNativeScopeStack {
312 _private: [u8; 0],
313 _marker: PhantomData<(*mut u8, PhantomPinned)>,
314}
315
316#[repr(C)]
318pub struct NemoRelayNativeScopeStackBinding {
319 _private: [u8; 0],
320 _marker: PhantomData<(*mut u8, PhantomPinned)>,
321}
322
323#[repr(i32)]
325#[derive(Debug, Clone, Copy, PartialEq, Eq)]
326pub enum NemoRelayNativeScopeType {
327 Agent = 0,
329 Function = 1,
331 Tool = 2,
333 Llm = 3,
335 Retriever = 4,
337 Embedder = 5,
339 Reranker = 6,
341 Guardrail = 7,
343 Evaluator = 8,
345 Custom = 9,
347 Unknown = 10,
349}
350
351pub type NemoRelayNativeFreeFn = Option<unsafe extern "C" fn(user_data: *mut c_void)>;
353
354pub type NemoRelayNativeWithScopeStackCb =
356 unsafe extern "C" fn(user_data: *mut c_void) -> NemoRelayStatus;
357
358pub type NemoRelayNativeToolNextFn = unsafe extern "C" fn(
364 args_json: *const NemoRelayNativeString,
365 next_ctx: *mut c_void,
366 out_json: *mut *mut NemoRelayNativeString,
367) -> NemoRelayStatus;
368
369pub type NemoRelayNativeLlmNextFn = unsafe extern "C" fn(
371 request_json: *const NemoRelayNativeString,
372 next_ctx: *mut c_void,
373 out_json: *mut *mut NemoRelayNativeString,
374) -> NemoRelayStatus;
375
376pub type NemoRelayNativeLlmStreamPollFn = unsafe extern "C" fn(
382 user_data: *mut c_void,
383 out_json: *mut *mut NemoRelayNativeString,
384) -> NemoRelayStatus;
385
386pub type NemoRelayNativeLlmStreamCancelFn =
388 Option<unsafe extern "C" fn(user_data: *mut c_void) -> NemoRelayStatus>;
389
390pub type NemoRelayNativeLlmStreamDropFn = Option<unsafe extern "C" fn(user_data: *mut c_void)>;
392
393#[repr(C)]
395pub struct NemoRelayNativeLlmStreamV1 {
396 pub struct_size: usize,
398 pub user_data: *mut c_void,
400 pub next: Option<NemoRelayNativeLlmStreamPollFn>,
402 pub cancel: NemoRelayNativeLlmStreamCancelFn,
404 pub drop: NemoRelayNativeLlmStreamDropFn,
406}
407
408impl Default for NemoRelayNativeLlmStreamV1 {
409 fn default() -> Self {
410 Self {
411 struct_size: std::mem::size_of::<Self>(),
412 user_data: ptr::null_mut(),
413 next: None,
414 cancel: None,
415 drop: None,
416 }
417 }
418}
419
420pub type NemoRelayNativeLlmStreamNextFn = unsafe extern "C" fn(
422 request_json: *const NemoRelayNativeString,
423 next_ctx: *mut c_void,
424 out_stream: *mut NemoRelayNativeLlmStreamV1,
425) -> NemoRelayStatus;
426
427pub type NemoRelayNativeEventSubscriberCb = unsafe extern "C" fn(
429 user_data: *mut c_void,
430 event_json: *const NemoRelayNativeString,
431) -> NemoRelayStatus;
432
433pub type NemoRelayNativeEventSanitizeCb = unsafe extern "C" fn(
435 user_data: *mut c_void,
436 event_json: *const NemoRelayNativeString,
437 fields_json: *const NemoRelayNativeString,
438 out_fields_json: *mut *mut NemoRelayNativeString,
439) -> NemoRelayStatus;
440
441pub type NemoRelayNativeToolJsonCb = unsafe extern "C" fn(
443 user_data: *mut c_void,
444 name: *const NemoRelayNativeString,
445 payload_json: *const NemoRelayNativeString,
446 out_json: *mut *mut NemoRelayNativeString,
447) -> NemoRelayStatus;
448
449pub type NemoRelayNativeToolConditionalCb = unsafe extern "C" fn(
451 user_data: *mut c_void,
452 name: *const NemoRelayNativeString,
453 args_json: *const NemoRelayNativeString,
454 out_reason: *mut *mut NemoRelayNativeString,
455) -> NemoRelayStatus;
456
457pub type NemoRelayNativeToolExecutionCb = unsafe extern "C" fn(
464 user_data: *mut c_void,
465 name: *const NemoRelayNativeString,
466 args_json: *const NemoRelayNativeString,
467 next_fn: NemoRelayNativeToolNextFn,
468 next_ctx: *mut c_void,
469 out_outcome_json: *mut *mut NemoRelayNativeString,
470) -> NemoRelayStatus;
471
472pub type NemoRelayNativeLlmSanitizeRequestCb = unsafe extern "C" fn(
478 user_data: *mut c_void,
479 request_json: *const NemoRelayNativeString,
480 context: NemoRelayNativeLlmSanitizeRequestContext,
481 out_request_json: *mut *mut NemoRelayNativeString,
482) -> NemoRelayStatus;
483
484pub type NemoRelayNativeLlmSanitizeResponseCb = unsafe extern "C" fn(
490 user_data: *mut c_void,
491 payload_json: *const NemoRelayNativeString,
492 context: NemoRelayNativeLlmSanitizeResponseContext,
493 out_json: *mut *mut NemoRelayNativeString,
494) -> NemoRelayStatus;
495
496pub type NemoRelayNativeLlmConditionalCb = unsafe extern "C" fn(
498 user_data: *mut c_void,
499 request_json: *const NemoRelayNativeString,
500 out_reason: *mut *mut NemoRelayNativeString,
501) -> NemoRelayStatus;
502
503pub type NemoRelayNativeLlmRequestInterceptCb = unsafe extern "C" fn(
505 user_data: *mut c_void,
506 name: *const NemoRelayNativeString,
507 request_json: *const NemoRelayNativeString,
508 annotated_json: *const NemoRelayNativeString,
509 out_outcome_json: *mut *mut NemoRelayNativeString,
510) -> NemoRelayStatus;
511
512pub type NemoRelayNativeLlmExecutionCb = unsafe extern "C" fn(
514 user_data: *mut c_void,
515 name: *const NemoRelayNativeString,
516 request_json: *const NemoRelayNativeString,
517 next_fn: NemoRelayNativeLlmNextFn,
518 next_ctx: *mut c_void,
519 out_json: *mut *mut NemoRelayNativeString,
520) -> NemoRelayStatus;
521
522pub type NemoRelayNativeLlmStreamExecutionCb = unsafe extern "C" fn(
524 user_data: *mut c_void,
525 name: *const NemoRelayNativeString,
526 request_json: *const NemoRelayNativeString,
527 next_fn: NemoRelayNativeLlmStreamNextFn,
528 next_ctx: *mut c_void,
529 out_stream: *mut NemoRelayNativeLlmStreamV1,
530) -> NemoRelayStatus;
531
532pub type NemoRelayNativePluginValidateFn = unsafe extern "C" fn(
534 user_data: *mut c_void,
535 plugin_config_json: *const NemoRelayNativeString,
536 out_diagnostics_json: *mut *mut NemoRelayNativeString,
537) -> NemoRelayStatus;
538
539pub type NemoRelayNativePluginRegisterFn = unsafe extern "C" fn(
541 user_data: *mut c_void,
542 plugin_config_json: *const NemoRelayNativeString,
543 ctx: *mut NemoRelayNativePluginContext,
544) -> NemoRelayStatus;
545
546pub type NemoRelayNativePluginDropFn = Option<unsafe extern "C" fn(user_data: *mut c_void)>;
548
549#[repr(C)]
551#[derive(Clone, Copy)]
552pub struct NemoRelayNativeHostApiV1 {
553 pub abi_version: u32,
555 pub struct_size: usize,
557 pub relay_version: *const c_char,
559 pub string_new: unsafe extern "C" fn(
561 data: *const u8,
562 len: usize,
563 out: *mut *mut NemoRelayNativeString,
564 ) -> NemoRelayStatus,
565 pub string_data: unsafe extern "C" fn(value: *const NemoRelayNativeString) -> *const u8,
567 pub string_len: unsafe extern "C" fn(value: *const NemoRelayNativeString) -> usize,
569 pub string_free: unsafe extern "C" fn(value: *mut NemoRelayNativeString),
571 pub last_error_clear: unsafe extern "C" fn(),
573 pub last_error_set: unsafe extern "C" fn(message: *const NemoRelayNativeString),
575 pub llm_request_codec_decode: unsafe extern "C" fn(
577 codec: *const NemoRelayNativeLlmRequestCodec,
578 request_json: *const NemoRelayNativeString,
579 out: *mut *mut NemoRelayNativeString,
580 ) -> NemoRelayStatus,
581 pub llm_request_codec_encode: unsafe extern "C" fn(
583 codec: *const NemoRelayNativeLlmRequestCodec,
584 annotated_json: *const NemoRelayNativeString,
585 original_json: *const NemoRelayNativeString,
586 out: *mut *mut NemoRelayNativeString,
587 ) -> NemoRelayStatus,
588 pub llm_response_codec_decode: unsafe extern "C" fn(
590 codec: *const NemoRelayNativeLlmResponseCodec,
591 response_json: *const NemoRelayNativeString,
592 out: *mut *mut NemoRelayNativeString,
593 ) -> NemoRelayStatus,
594 pub plugin_context_register_subscriber: unsafe extern "C" fn(
596 ctx: *mut NemoRelayNativePluginContext,
597 name: *const NemoRelayNativeString,
598 cb: NemoRelayNativeEventSubscriberCb,
599 user_data: *mut c_void,
600 free_fn: NemoRelayNativeFreeFn,
601 ) -> NemoRelayStatus,
602 pub plugin_context_register_tool_sanitize_request_guardrail:
604 unsafe extern "C" fn(
605 ctx: *mut NemoRelayNativePluginContext,
606 name: *const NemoRelayNativeString,
607 priority: i32,
608 cb: NemoRelayNativeToolJsonCb,
609 user_data: *mut c_void,
610 free_fn: NemoRelayNativeFreeFn,
611 ) -> NemoRelayStatus,
612 pub plugin_context_register_tool_sanitize_response_guardrail:
614 unsafe extern "C" fn(
615 ctx: *mut NemoRelayNativePluginContext,
616 name: *const NemoRelayNativeString,
617 priority: i32,
618 cb: NemoRelayNativeToolJsonCb,
619 user_data: *mut c_void,
620 free_fn: NemoRelayNativeFreeFn,
621 ) -> NemoRelayStatus,
622 pub plugin_context_register_tool_conditional_execution_guardrail:
624 unsafe extern "C" fn(
625 ctx: *mut NemoRelayNativePluginContext,
626 name: *const NemoRelayNativeString,
627 priority: i32,
628 cb: NemoRelayNativeToolConditionalCb,
629 user_data: *mut c_void,
630 free_fn: NemoRelayNativeFreeFn,
631 ) -> NemoRelayStatus,
632 pub plugin_context_register_tool_request_intercept: unsafe extern "C" fn(
634 ctx: *mut NemoRelayNativePluginContext,
635 name: *const NemoRelayNativeString,
636 priority: i32,
637 break_chain: bool,
638 cb: NemoRelayNativeToolJsonCb,
639 user_data: *mut c_void,
640 free_fn: NemoRelayNativeFreeFn,
641 )
642 -> NemoRelayStatus,
643 pub plugin_context_register_tool_execution_intercept: unsafe extern "C" fn(
645 ctx: *mut NemoRelayNativePluginContext,
646 name: *const NemoRelayNativeString,
647 priority: i32,
648 cb: NemoRelayNativeToolExecutionCb,
649 user_data: *mut c_void,
650 free_fn: NemoRelayNativeFreeFn,
651 )
652 -> NemoRelayStatus,
653 pub plugin_context_register_llm_sanitize_request_guardrail:
655 unsafe extern "C" fn(
656 ctx: *mut NemoRelayNativePluginContext,
657 name: *const NemoRelayNativeString,
658 priority: i32,
659 cb: NemoRelayNativeLlmSanitizeRequestCb,
660 user_data: *mut c_void,
661 free_fn: NemoRelayNativeFreeFn,
662 ) -> NemoRelayStatus,
663 pub plugin_context_register_llm_sanitize_response_guardrail:
665 unsafe extern "C" fn(
666 ctx: *mut NemoRelayNativePluginContext,
667 name: *const NemoRelayNativeString,
668 priority: i32,
669 cb: NemoRelayNativeLlmSanitizeResponseCb,
670 user_data: *mut c_void,
671 free_fn: NemoRelayNativeFreeFn,
672 ) -> NemoRelayStatus,
673 pub plugin_context_register_llm_conditional_execution_guardrail:
675 unsafe extern "C" fn(
676 ctx: *mut NemoRelayNativePluginContext,
677 name: *const NemoRelayNativeString,
678 priority: i32,
679 cb: NemoRelayNativeLlmConditionalCb,
680 user_data: *mut c_void,
681 free_fn: NemoRelayNativeFreeFn,
682 ) -> NemoRelayStatus,
683 pub plugin_context_register_llm_request_intercept: unsafe extern "C" fn(
685 ctx: *mut NemoRelayNativePluginContext,
686 name: *const NemoRelayNativeString,
687 priority: i32,
688 break_chain: bool,
689 cb: NemoRelayNativeLlmRequestInterceptCb,
690 user_data: *mut c_void,
691 free_fn: NemoRelayNativeFreeFn,
692 ) -> NemoRelayStatus,
693 pub plugin_context_register_llm_execution_intercept: unsafe extern "C" fn(
695 ctx: *mut NemoRelayNativePluginContext,
696 name: *const NemoRelayNativeString,
697 priority: i32,
698 cb: NemoRelayNativeLlmExecutionCb,
699 user_data: *mut c_void,
700 free_fn: NemoRelayNativeFreeFn,
701 )
702 -> NemoRelayStatus,
703 pub plugin_context_register_llm_stream_execution_intercept:
705 unsafe extern "C" fn(
706 ctx: *mut NemoRelayNativePluginContext,
707 name: *const NemoRelayNativeString,
708 priority: i32,
709 cb: NemoRelayNativeLlmStreamExecutionCb,
710 user_data: *mut c_void,
711 free_fn: NemoRelayNativeFreeFn,
712 ) -> NemoRelayStatus,
713 pub scope_handle_free: unsafe extern "C" fn(handle: *mut NemoRelayNativeScopeHandle),
715 pub scope_get_current:
717 unsafe extern "C" fn(out: *mut *mut NemoRelayNativeScopeHandle) -> NemoRelayStatus,
718 pub scope_push: unsafe extern "C" fn(
720 name: *const NemoRelayNativeString,
721 scope_type: NemoRelayNativeScopeType,
722 parent: *const NemoRelayNativeScopeHandle,
723 attributes: u32,
724 data_json: *const NemoRelayNativeString,
725 metadata_json: *const NemoRelayNativeString,
726 input_json: *const NemoRelayNativeString,
727 timestamp_unix_micros: *const i64,
728 out: *mut *mut NemoRelayNativeScopeHandle,
729 ) -> NemoRelayStatus,
730 pub scope_pop: unsafe extern "C" fn(
732 handle: *const NemoRelayNativeScopeHandle,
733 output_json: *const NemoRelayNativeString,
734 metadata_json: *const NemoRelayNativeString,
735 timestamp_unix_micros: *const i64,
736 ) -> NemoRelayStatus,
737 pub emit_mark: unsafe extern "C" fn(
739 name: *const NemoRelayNativeString,
740 parent: *const NemoRelayNativeScopeHandle,
741 data_json: *const NemoRelayNativeString,
742 metadata_json: *const NemoRelayNativeString,
743 timestamp_unix_micros: *const i64,
744 ) -> NemoRelayStatus,
745 pub scope_stack_create:
747 unsafe extern "C" fn(out: *mut *mut NemoRelayNativeScopeStack) -> NemoRelayStatus,
748 pub scope_stack_free: unsafe extern "C" fn(stack: *mut NemoRelayNativeScopeStack),
750 pub scope_stack_set_thread:
752 unsafe extern "C" fn(stack: *const NemoRelayNativeScopeStack) -> NemoRelayStatus,
753 pub scope_stack_capture_thread:
755 unsafe extern "C" fn(out: *mut *mut NemoRelayNativeScopeStackBinding) -> NemoRelayStatus,
756 pub scope_stack_restore_thread:
758 unsafe extern "C" fn(binding: *mut NemoRelayNativeScopeStackBinding) -> NemoRelayStatus,
759 pub scope_stack_binding_free:
761 unsafe extern "C" fn(binding: *mut NemoRelayNativeScopeStackBinding),
762 pub scope_stack_active: unsafe extern "C" fn() -> bool,
764 pub scope_stack_with_current: unsafe extern "C" fn(
766 stack: *const NemoRelayNativeScopeStack,
767 cb: NemoRelayNativeWithScopeStackCb,
768 user_data: *mut c_void,
769 ) -> NemoRelayStatus,
770 pub plugin_context_register_mark_sanitize_guardrail: unsafe extern "C" fn(
772 ctx: *mut NemoRelayNativePluginContext,
773 name: *const NemoRelayNativeString,
774 priority: i32,
775 cb: NemoRelayNativeEventSanitizeCb,
776 user_data: *mut c_void,
777 free_fn: NemoRelayNativeFreeFn,
778 )
779 -> NemoRelayStatus,
780 pub plugin_context_register_scope_sanitize_start_guardrail:
782 unsafe extern "C" fn(
783 ctx: *mut NemoRelayNativePluginContext,
784 name: *const NemoRelayNativeString,
785 priority: i32,
786 cb: NemoRelayNativeEventSanitizeCb,
787 user_data: *mut c_void,
788 free_fn: NemoRelayNativeFreeFn,
789 ) -> NemoRelayStatus,
790 pub plugin_context_register_scope_sanitize_end_guardrail:
792 unsafe extern "C" fn(
793 ctx: *mut NemoRelayNativePluginContext,
794 name: *const NemoRelayNativeString,
795 priority: i32,
796 cb: NemoRelayNativeEventSanitizeCb,
797 user_data: *mut c_void,
798 free_fn: NemoRelayNativeFreeFn,
799 ) -> NemoRelayStatus,
800}
801
802#[repr(u32)]
808#[derive(Debug, Clone, Copy, PartialEq, Eq)]
809pub enum NemoRelayNativeAsyncMiddlewareKind {
810 ToolSanitizeRequest = 0,
812 ToolSanitizeResponse = 1,
814 ToolConditionalExecution = 2,
816 ToolRequestIntercept = 3,
818 ToolExecutionIntercept = 4,
820 LlmSanitizeRequest = 5,
822 LlmSanitizeResponse = 6,
824 LlmConditionalExecution = 7,
826 LlmRequestIntercept = 8,
828 LlmExecutionIntercept = 9,
830 LlmStreamExecutionIntercept = 10,
836 MarkSanitize = 11,
838 ScopeSanitizeStart = 12,
840 ScopeSanitizeEnd = 13,
842 EventMetadataInjector = 14,
844}
845
846impl TryFrom<u32> for NemoRelayNativeAsyncMiddlewareKind {
847 type Error = ();
848
849 fn try_from(value: u32) -> std::result::Result<Self, Self::Error> {
850 match value {
851 0 => Ok(Self::ToolSanitizeRequest),
852 1 => Ok(Self::ToolSanitizeResponse),
853 2 => Ok(Self::ToolConditionalExecution),
854 3 => Ok(Self::ToolRequestIntercept),
855 4 => Ok(Self::ToolExecutionIntercept),
856 5 => Ok(Self::LlmSanitizeRequest),
857 6 => Ok(Self::LlmSanitizeResponse),
858 7 => Ok(Self::LlmConditionalExecution),
859 8 => Ok(Self::LlmRequestIntercept),
860 9 => Ok(Self::LlmExecutionIntercept),
861 10 => Ok(Self::LlmStreamExecutionIntercept),
862 11 => Ok(Self::MarkSanitize),
863 12 => Ok(Self::ScopeSanitizeStart),
864 13 => Ok(Self::ScopeSanitizeEnd),
865 14 => Ok(Self::EventMetadataInjector),
866 _ => Err(()),
867 }
868 }
869}
870
871#[repr(u32)]
873#[derive(Debug, Clone, Copy, PartialEq, Eq)]
874pub enum NemoRelayNativeAsyncCallbackState {
875 Complete = 0,
877 Pending = 1,
879}
880
881impl TryFrom<u32> for NemoRelayNativeAsyncCallbackState {
882 type Error = ();
883
884 fn try_from(value: u32) -> std::result::Result<Self, Self::Error> {
885 match value {
886 0 => Ok(Self::Complete),
887 1 => Ok(Self::Pending),
888 _ => Err(()),
889 }
890 }
891}
892
893#[repr(C)]
895pub struct NemoRelayNativeAsyncCompletion {
896 _private: [u8; 0],
897 _marker: PhantomData<(*mut u8, PhantomPinned)>,
898}
899
900#[repr(C)]
902pub struct NemoRelayNativeAsyncNext {
903 _private: [u8; 0],
904 _marker: PhantomData<(*mut u8, PhantomPinned)>,
905}
906
907#[repr(C)]
909pub struct NemoRelayNativeAsyncStream {
910 _private: [u8; 0],
911 _marker: PhantomData<(*mut u8, PhantomPinned)>,
912}
913
914#[repr(C)]
916pub struct NemoRelayNativeLlmAsyncStream {
917 _private: [u8; 0],
918 _marker: PhantomData<(*mut u8, PhantomPinned)>,
919}
920
921pub type NemoRelayNativeAsyncLlmStreamOpenCb = unsafe extern "C" fn(
926 user_data: *mut c_void,
927 stream: *const NemoRelayNativeLlmAsyncStream,
928 error: *const NemoRelayNativeString,
929);
930
931pub type NemoRelayNativeAsyncLlmStreamPullCb = unsafe extern "C" fn(
937 user_data: *mut c_void,
938 chunk_json: *const NemoRelayNativeString,
939 error: *const NemoRelayNativeString,
940 done: bool,
941);
942
943pub type NemoRelayNativeAsyncNextStreamCb = unsafe extern "C" fn(
951 user_data: *mut c_void,
952 chunk_json: *const NemoRelayNativeString,
953 error: *const NemoRelayNativeString,
954 done: bool,
955) -> bool;
956
957pub type NemoRelayNativeAsyncNextResultCb = unsafe extern "C" fn(
965 user_data: *mut c_void,
966 value_json: *const NemoRelayNativeString,
967 error: *const NemoRelayNativeString,
968);
969
970pub type NemoRelayNativeAsyncStreamMiddlewareCb = unsafe extern "C" fn(
985 user_data: *mut c_void,
986 invocation_json: *const NemoRelayNativeString,
987 next: *const NemoRelayNativeAsyncNext,
988 stream: *const NemoRelayNativeAsyncStream,
989) -> u32;
990
991pub type NemoRelayNativeAsyncMiddlewareCb = unsafe extern "C" fn(
1013 user_data: *mut c_void,
1014 invocation_json: *const NemoRelayNativeString,
1015 next: *const NemoRelayNativeAsyncNext,
1016 completion: *const NemoRelayNativeAsyncCompletion,
1017) -> u32;
1018
1019#[repr(C)]
1024#[derive(Clone, Copy)]
1025pub struct NemoRelayNativeHostApiV3 {
1026 pub v1: NemoRelayNativeHostApiV1,
1028 pub async_completion_resolve_json: unsafe extern "C" fn(
1033 completion: *const NemoRelayNativeAsyncCompletion,
1034 value_json: *const NemoRelayNativeString,
1035 ) -> NemoRelayStatus,
1036 pub async_completion_reject: unsafe extern "C" fn(
1038 completion: *const NemoRelayNativeAsyncCompletion,
1039 message: *const NemoRelayNativeString,
1040 ) -> NemoRelayStatus,
1041 pub async_completion_is_cancelled:
1043 unsafe extern "C" fn(completion: *const NemoRelayNativeAsyncCompletion) -> bool,
1044 pub async_completion_release:
1046 unsafe extern "C" fn(completion: *const NemoRelayNativeAsyncCompletion),
1047 pub async_next_invoke: unsafe extern "C" fn(
1055 next: *const NemoRelayNativeAsyncNext,
1056 invocation_json: *const NemoRelayNativeString,
1057 completion: *const NemoRelayNativeAsyncCompletion,
1058 ) -> NemoRelayStatus,
1059 pub async_next_release: unsafe extern "C" fn(next: *const NemoRelayNativeAsyncNext),
1064 pub plugin_context_register_async_middleware: unsafe extern "C" fn(
1071 ctx: *mut NemoRelayNativePluginContext,
1072 kind: u32,
1073 name: *const NemoRelayNativeString,
1074 priority: i32,
1075 break_chain: bool,
1076 cb: NemoRelayNativeAsyncMiddlewareCb,
1077 user_data: *mut c_void,
1078 free_fn: NemoRelayNativeFreeFn,
1079 ) -> NemoRelayStatus,
1080 pub async_stream_push_json: unsafe extern "C" fn(
1085 stream: *const NemoRelayNativeAsyncStream,
1086 chunk_json: *const NemoRelayNativeString,
1087 ) -> NemoRelayStatus,
1088 pub async_stream_finish:
1090 unsafe extern "C" fn(stream: *const NemoRelayNativeAsyncStream) -> NemoRelayStatus,
1091 pub async_stream_reject: unsafe extern "C" fn(
1096 stream: *const NemoRelayNativeAsyncStream,
1097 message: *const NemoRelayNativeString,
1098 ) -> NemoRelayStatus,
1099 pub async_stream_is_cancelled:
1101 unsafe extern "C" fn(stream: *const NemoRelayNativeAsyncStream) -> bool,
1102 pub async_stream_release: unsafe extern "C" fn(stream: *const NemoRelayNativeAsyncStream),
1104 pub async_next_invoke_stream: unsafe extern "C" fn(
1112 next: *const NemoRelayNativeAsyncNext,
1113 invocation_json: *const NemoRelayNativeString,
1114 stream: *const NemoRelayNativeAsyncStream,
1115 cb: NemoRelayNativeAsyncNextStreamCb,
1116 user_data: *mut c_void,
1117 ) -> NemoRelayStatus,
1118 pub plugin_context_register_async_stream_middleware: unsafe extern "C" fn(
1120 ctx: *mut NemoRelayNativePluginContext,
1121 name: *const NemoRelayNativeString,
1122 priority: i32,
1123 cb: NemoRelayNativeAsyncStreamMiddlewareCb,
1124 user_data: *mut c_void,
1125 free_fn: NemoRelayNativeFreeFn,
1126 )
1127 -> NemoRelayStatus,
1128 pub async_next_invoke_result: unsafe extern "C" fn(
1135 next: *const NemoRelayNativeAsyncNext,
1136 invocation_json: *const NemoRelayNativeString,
1137 cb: NemoRelayNativeAsyncNextResultCb,
1138 user_data: *mut c_void,
1139 ) -> NemoRelayStatus,
1140}
1141
1142pub type NemoRelayNativeEmitMarkV2Fn = unsafe extern "C" fn(
1144 name: *const NemoRelayNativeString,
1145 parent: *const NemoRelayNativeScopeHandle,
1146 data_json: *const NemoRelayNativeString,
1147 metadata_json: *const NemoRelayNativeString,
1148 data_schema_json: *const NemoRelayNativeString,
1149 severity: *const NemoRelayNativeString,
1150 timestamp_unix_micros: *const i64,
1151) -> NemoRelayStatus;
1152
1153pub type NemoRelayNativeGetRuntimeDiagnosticsFn =
1155 unsafe extern "C" fn(out_json: *mut *mut NemoRelayNativeString) -> NemoRelayStatus;
1156
1157#[repr(C)]
1162#[derive(Clone, Copy)]
1163pub struct NemoRelayNativeHostApiV4 {
1164 pub v3: NemoRelayNativeHostApiV3,
1166 pub async_completion_llm_request_codec_decode: unsafe extern "C" fn(
1168 completion: *const NemoRelayNativeAsyncCompletion,
1169 request_json: *const NemoRelayNativeString,
1170 out: *mut *mut NemoRelayNativeString,
1171 ) -> NemoRelayStatus,
1172 pub async_completion_llm_request_codec_encode: unsafe extern "C" fn(
1174 completion: *const NemoRelayNativeAsyncCompletion,
1175 annotated_json: *const NemoRelayNativeString,
1176 original_json: *const NemoRelayNativeString,
1177 out: *mut *mut NemoRelayNativeString,
1178 ) -> NemoRelayStatus,
1179 pub async_completion_llm_response_codec_decode: unsafe extern "C" fn(
1181 completion: *const NemoRelayNativeAsyncCompletion,
1182 response_json: *const NemoRelayNativeString,
1183 out: *mut *mut NemoRelayNativeString,
1184 ) -> NemoRelayStatus,
1185 pub async_next_open_llm_stream: unsafe extern "C" fn(
1187 next: *const NemoRelayNativeAsyncNext,
1188 request_json: *const NemoRelayNativeString,
1189 cb: NemoRelayNativeAsyncLlmStreamOpenCb,
1190 user_data: *mut c_void,
1191 ) -> NemoRelayStatus,
1192 pub async_llm_stream_pull: unsafe extern "C" fn(
1194 stream: *const NemoRelayNativeLlmAsyncStream,
1195 cb: NemoRelayNativeAsyncLlmStreamPullCb,
1196 user_data: *mut c_void,
1197 ) -> NemoRelayStatus,
1198 pub async_llm_stream_cancel:
1200 unsafe extern "C" fn(stream: *const NemoRelayNativeLlmAsyncStream) -> NemoRelayStatus,
1201 pub async_llm_stream_release:
1203 unsafe extern "C" fn(stream: *const NemoRelayNativeLlmAsyncStream),
1204 pub async_completion_retain:
1207 unsafe extern "C" fn(completion: *const NemoRelayNativeAsyncCompletion) -> NemoRelayStatus,
1208 pub async_stream_is_backpressured:
1213 unsafe extern "C" fn(stream: *const NemoRelayNativeAsyncStream) -> bool,
1214 pub emit_mark_v2: NemoRelayNativeEmitMarkV2Fn,
1216 pub get_runtime_diagnostics: NemoRelayNativeGetRuntimeDiagnosticsFn,
1218 pub plugin_context_runtime: unsafe extern "C" fn(
1223 ctx: *mut NemoRelayNativePluginContext,
1224 out: *mut *const NemoRelayNativePluginRuntime,
1225 ) -> NemoRelayStatus,
1226 pub plugin_runtime_retain:
1230 unsafe extern "C" fn(runtime: *const NemoRelayNativePluginRuntime) -> NemoRelayStatus,
1231 pub plugin_runtime_release: unsafe extern "C" fn(runtime: *const NemoRelayNativePluginRuntime),
1233 pub plugin_runtime_list_registrations: unsafe extern "C" fn(
1235 runtime: *const NemoRelayNativePluginRuntime,
1236 kinds_json: *const NemoRelayNativeString,
1237 out_json: *mut *mut NemoRelayNativeString,
1238 ) -> NemoRelayStatus,
1239 pub plugin_runtime_register_conditional_middleware_guardrail:
1241 unsafe extern "C" fn(
1242 runtime: *const NemoRelayNativePluginRuntime,
1243 name: *const NemoRelayNativeString,
1244 kinds_json: *const NemoRelayNativeString,
1245 registration_name: *const NemoRelayNativeString,
1246 reason: *const NemoRelayNativeString,
1247 out_handle: *mut *mut NemoRelayNativeString,
1248 ) -> NemoRelayStatus,
1249 pub plugin_runtime_deregister_conditional_middleware_guardrail:
1251 unsafe extern "C" fn(
1252 runtime: *const NemoRelayNativePluginRuntime,
1253 handle: *const NemoRelayNativeString,
1254 out_removed: *mut bool,
1255 ) -> NemoRelayStatus,
1256 pub plugin_context_register_conditional_middleware_guardrail:
1258 unsafe extern "C" fn(
1259 ctx: *mut NemoRelayNativePluginContext,
1260 name: *const NemoRelayNativeString,
1261 kinds_json: *const NemoRelayNativeString,
1262 registration_name: *const NemoRelayNativeString,
1263 reason: *const NemoRelayNativeString,
1264 ) -> NemoRelayStatus,
1265}
1266
1267unsafe impl Send for NemoRelayNativeHostApiV3 {}
1268unsafe impl Sync for NemoRelayNativeHostApiV3 {}
1269unsafe impl Send for NemoRelayNativeHostApiV4 {}
1272unsafe impl Sync for NemoRelayNativeHostApiV4 {}
1273
1274unsafe impl Send for NemoRelayNativeHostApiV1 {}
1277unsafe impl Sync for NemoRelayNativeHostApiV1 {}
1278
1279#[repr(C)]
1281pub struct NemoRelayNativePluginV1 {
1282 pub struct_size: usize,
1284 pub plugin_kind: *mut NemoRelayNativeString,
1286 pub allows_multiple_components: bool,
1288 pub user_data: *mut c_void,
1290 pub validate: Option<NemoRelayNativePluginValidateFn>,
1292 pub register: Option<NemoRelayNativePluginRegisterFn>,
1294 pub drop: NemoRelayNativePluginDropFn,
1296}
1297
1298impl Default for NemoRelayNativePluginV1 {
1299 fn default() -> Self {
1300 Self {
1301 struct_size: std::mem::size_of::<Self>(),
1302 plugin_kind: ptr::null_mut(),
1303 allows_multiple_components: true,
1304 user_data: ptr::null_mut(),
1305 validate: None,
1306 register: None,
1307 drop: None,
1308 }
1309 }
1310}
1311
1312pub type NemoRelayNativePluginEntry = unsafe extern "C" fn(
1314 host: *const NemoRelayNativeHostApiV1,
1315 out: *mut NemoRelayNativePluginV1,
1316) -> NemoRelayStatus;
1317
1318pub type Result<T> = std::result::Result<T, String>;
1320
1321#[derive(Debug, Clone, PartialEq, Eq, Serialize, serde::Deserialize)]
1323pub struct RuntimeDiagnostic {
1324 pub code: String,
1326 pub message: String,
1328 pub count: u64,
1330}
1331
1332#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, serde::Deserialize)]
1334pub struct RuntimeDiagnostics {
1335 entries: Vec<RuntimeDiagnostic>,
1336}
1337
1338#[derive(Debug, Clone, PartialEq, Eq)]
1340pub struct ConditionalMiddlewareGuardrailHandle(String);
1341
1342impl RuntimeDiagnostics {
1343 pub fn entries(&self) -> &[RuntimeDiagnostic] {
1345 &self.entries
1346 }
1347
1348 pub fn get(&self, code: &str) -> Option<&RuntimeDiagnostic> {
1350 self.entries
1351 .iter()
1352 .find(|diagnostic| diagnostic.code == code)
1353 }
1354}
1355
1356pub type LlmJsonStream = Box<dyn Iterator<Item = Result<Json>> + Send>;
1358
1359pub struct PluginRuntime {
1361 host: NemoRelayNativeHostApiV1,
1362 emit_mark_v2: Option<NemoRelayNativeEmitMarkV2Fn>,
1363 get_runtime_diagnostics: Option<NemoRelayNativeGetRuntimeDiagnosticsFn>,
1364 v4: Option<NemoRelayNativeHostApiV4>,
1365 capability: *const NemoRelayNativePluginRuntime,
1366}
1367
1368unsafe impl Send for PluginRuntime {}
1372unsafe impl Sync for PluginRuntime {}
1373
1374impl Clone for PluginRuntime {
1375 fn clone(&self) -> Self {
1376 let mut capability = self.capability;
1377 if let (Some(v4), false) = (self.v4, self.capability.is_null())
1378 && unsafe { (v4.plugin_runtime_retain)(self.capability) } != NemoRelayStatus::Ok
1379 {
1380 capability = ptr::null();
1381 }
1382 Self {
1383 host: self.host,
1384 emit_mark_v2: self.emit_mark_v2,
1385 get_runtime_diagnostics: self.get_runtime_diagnostics,
1386 v4: self.v4,
1387 capability,
1388 }
1389 }
1390}
1391
1392impl Drop for PluginRuntime {
1393 fn drop(&mut self) {
1394 if let (Some(v4), false) = (self.v4, self.capability.is_null()) {
1395 unsafe { (v4.plugin_runtime_release)(self.capability) };
1396 }
1397 }
1398}
1399
1400impl PluginRuntime {
1401 pub fn new(host: &NemoRelayNativeHostApiV1) -> Self {
1403 let v4 = (host.abi_version >= NEMO_RELAY_NATIVE_ABI_VERSION
1404 && host.struct_size >= std::mem::size_of::<NemoRelayNativeHostApiV4>())
1405 .then(|| unsafe { *(host as *const _ as *const NemoRelayNativeHostApiV4) });
1406 Self {
1407 host: *host,
1408 emit_mark_v2: v4.map(|host| host.emit_mark_v2),
1409 get_runtime_diagnostics: v4.map(|host| host.get_runtime_diagnostics),
1410 v4,
1411 capability: ptr::null(),
1412 }
1413 }
1414
1415 fn from_context(
1416 host: &NemoRelayNativeHostApiV1,
1417 ctx: *mut NemoRelayNativePluginContext,
1418 ) -> Self {
1419 let mut runtime = Self::new(host);
1420 let Some(v4) = runtime.v4 else {
1421 return runtime;
1422 };
1423 let mut capability = ptr::null();
1424 if unsafe { (v4.plugin_context_runtime)(ctx, &mut capability) } == NemoRelayStatus::Ok {
1425 runtime.capability = capability;
1426 }
1427 runtime
1428 }
1429
1430 pub fn list_runtime_registrations(
1432 &self,
1433 kinds: Option<&std::collections::BTreeSet<RuntimeRegistrationKind>>,
1434 ) -> Result<Vec<RuntimeRegistrationIdentity>> {
1435 let v4 = self.runtime_v4()?;
1436 let kinds = match kinds {
1437 Some(kinds) => Some(
1438 HostString::from_json(&self.host, kinds)
1439 .ok_or_else(|| "failed to serialize runtime registration kinds".to_string())?,
1440 ),
1441 None => None,
1442 };
1443 let mut out = ptr::null_mut();
1444 let status = unsafe {
1445 (v4.plugin_runtime_list_registrations)(
1446 self.capability,
1447 kinds.as_ref().map_or(ptr::null(), HostString::as_ptr),
1448 &mut out,
1449 )
1450 };
1451 status_result(&self.host, status, "list runtime registrations")?;
1452 take_host_json(&self.host, out)
1453 }
1454
1455 pub fn register_conditional_middleware_guardrail(
1457 &self,
1458 name: &str,
1459 kinds: &std::collections::BTreeSet<RuntimeRegistrationKind>,
1460 registration_name: &str,
1461 reason: &str,
1462 ) -> Result<ConditionalMiddlewareGuardrailHandle> {
1463 let v4 = self.runtime_v4()?;
1464 let name = HostString::new(&self.host, name)
1465 .ok_or_else(|| "failed to allocate gate name".to_string())?;
1466 let kinds = HostString::from_json(&self.host, kinds)
1467 .ok_or_else(|| "failed to serialize runtime registration kinds".to_string())?;
1468 let registration_name = HostString::new(&self.host, registration_name)
1469 .ok_or_else(|| "failed to allocate target name".to_string())?;
1470 let reason = HostString::new(&self.host, reason)
1471 .ok_or_else(|| "failed to allocate gate reason".to_string())?;
1472 let mut out = ptr::null_mut();
1473 let status = unsafe {
1474 (v4.plugin_runtime_register_conditional_middleware_guardrail)(
1475 self.capability,
1476 name.as_ptr(),
1477 kinds.as_ptr(),
1478 registration_name.as_ptr(),
1479 reason.as_ptr(),
1480 &mut out,
1481 )
1482 };
1483 status_result(
1484 &self.host,
1485 status,
1486 "register conditional middleware guardrail",
1487 )?;
1488 take_host_string(&self.host, out).map(ConditionalMiddlewareGuardrailHandle)
1489 }
1490
1491 pub fn deregister_conditional_middleware_guardrail(
1493 &self,
1494 handle: &ConditionalMiddlewareGuardrailHandle,
1495 ) -> Result<bool> {
1496 let v4 = self.runtime_v4()?;
1497 let handle = HostString::new(&self.host, &handle.0)
1498 .ok_or_else(|| "failed to allocate gate handle".to_string())?;
1499 let mut removed = false;
1500 let status = unsafe {
1501 (v4.plugin_runtime_deregister_conditional_middleware_guardrail)(
1502 self.capability,
1503 handle.as_ptr(),
1504 &mut removed,
1505 )
1506 };
1507 status_result(
1508 &self.host,
1509 status,
1510 "deregister conditional middleware guardrail",
1511 )?;
1512 Ok(removed)
1513 }
1514
1515 fn runtime_v4(&self) -> Result<NemoRelayNativeHostApiV4> {
1516 self.v4
1517 .filter(|_| !self.capability.is_null())
1518 .ok_or_else(|| "host does not support activation-owned runtime gate control".into())
1519 }
1520
1521 pub fn host_api(&self) -> &NemoRelayNativeHostApiV1 {
1523 &self.host
1524 }
1525
1526 pub fn current_scope(&self) -> Result<ScopeHandle<'_>> {
1528 current_scope(&self.host)
1529 }
1530
1531 pub fn push_scope(
1533 &self,
1534 name: &str,
1535 scope_type: ScopeType,
1536 data: Option<&Json>,
1537 metadata: Option<&Json>,
1538 input: Option<&Json>,
1539 ) -> Result<ScopeHandle<'_>> {
1540 push_scope(&self.host, name, scope_type.into(), data, metadata, input)
1541 }
1542
1543 pub fn pop_scope(
1545 &self,
1546 handle: &ScopeHandle<'_>,
1547 output: Option<&Json>,
1548 metadata: Option<&Json>,
1549 ) -> Result<()> {
1550 pop_scope(&self.host, handle, output, metadata)
1551 }
1552
1553 pub fn scope(
1555 &self,
1556 name: &str,
1557 scope_type: ScopeType,
1558 data: Option<&Json>,
1559 metadata: Option<&Json>,
1560 input: Option<&Json>,
1561 ) -> Result<ScopeGuard<'_>> {
1562 let handle = self.push_scope(name, scope_type, data, metadata, input)?;
1563 Ok(ScopeGuard {
1564 runtime: self,
1565 handle: Some(handle),
1566 })
1567 }
1568
1569 pub fn emit_mark(
1571 &self,
1572 name: &str,
1573 data: Option<&Json>,
1574 metadata: Option<&Json>,
1575 ) -> Result<()> {
1576 emit_mark(&self.host, name, data, metadata)
1577 }
1578
1579 pub fn emit_mark_with_options(
1584 &self,
1585 name: &str,
1586 data: Option<&Json>,
1587 metadata: Option<&Json>,
1588 data_schema: Option<&DataSchema>,
1589 severity: Option<LogSeverity>,
1590 ) -> Result<()> {
1591 match self.emit_mark_v2 {
1592 Some(emit_mark_v2) => emit_mark_v2_call(
1593 &self.host,
1594 emit_mark_v2,
1595 name,
1596 data,
1597 metadata,
1598 data_schema,
1599 severity,
1600 ),
1601 None if data_schema.is_none() && severity.is_none() => {
1602 emit_mark(&self.host, name, data, metadata)
1603 }
1604 None => Err("mark data_schema and severity require native host ABI v4".into()),
1605 }
1606 }
1607
1608 pub fn emit_metric(
1610 &self,
1611 name: &str,
1612 measurements: Vec<MetricMeasurement>,
1613 metadata: Option<&Json>,
1614 ) -> Result<()> {
1615 let envelope = MetricEnvelope { measurements };
1616 envelope.validate().map_err(|err| err.to_string())?;
1617 let data = serde_json::to_value(envelope)
1618 .map_err(|err| format!("failed to serialize metric mark: {err}"))?;
1619 let data_schema = DataSchema::builder()
1620 .name(METRIC_DATA_SCHEMA_NAME)
1621 .version(METRIC_DATA_SCHEMA_VERSION)
1622 .build();
1623 self.emit_mark_with_options(name, Some(&data), metadata, Some(&data_schema), None)
1624 }
1625
1626 pub fn runtime_diagnostics(&self) -> Result<RuntimeDiagnostics> {
1628 let Some(get_runtime_diagnostics) = self.get_runtime_diagnostics else {
1629 return Err(
1630 "runtime diagnostics require the native host ABI v4 diagnostics extension".into(),
1631 );
1632 };
1633 native_json_call(&self.host, "runtime diagnostics", |out| {
1634 let status = unsafe { get_runtime_diagnostics(out) };
1635 codec_status(&self.host, status)
1636 })
1637 }
1638
1639 pub fn create_scope_stack(&self) -> Result<ScopeStack<'_>> {
1641 create_scope_stack(&self.host)
1642 }
1643
1644 pub fn capture_scope_stack_thread(&self) -> Result<ScopeStackBinding<'_>> {
1646 capture_scope_stack_thread(&self.host)
1647 }
1648
1649 pub fn scope_stack_active(&self) -> bool {
1651 unsafe { (self.host.scope_stack_active)() }
1652 }
1653
1654 pub fn bind_scope_stack_thread<'a>(
1656 &'a self,
1657 stack: &'a ScopeStack<'a>,
1658 ) -> Result<ThreadScopeStackGuard<'a>> {
1659 let previous = self.capture_scope_stack_thread()?;
1660 let status = stack.set_thread();
1661 if status == NemoRelayStatus::Ok {
1662 Ok(ThreadScopeStackGuard {
1663 previous: Some(previous),
1664 })
1665 } else {
1666 let _ = previous.restore();
1667 Err(format!("scope_stack_set_thread failed: {status:?}"))
1668 }
1669 }
1670}
1671
1672impl From<ScopeType> for NemoRelayNativeScopeType {
1673 fn from(value: ScopeType) -> Self {
1674 match value {
1675 ScopeType::Agent => Self::Agent,
1676 ScopeType::Function => Self::Function,
1677 ScopeType::Tool => Self::Tool,
1678 ScopeType::Llm => Self::Llm,
1679 ScopeType::Retriever => Self::Retriever,
1680 ScopeType::Embedder => Self::Embedder,
1681 ScopeType::Reranker => Self::Reranker,
1682 ScopeType::Guardrail => Self::Guardrail,
1683 ScopeType::Evaluator => Self::Evaluator,
1684 ScopeType::Custom => Self::Custom,
1685 ScopeType::Unknown => Self::Unknown,
1686 }
1687 }
1688}
1689
1690pub struct ScopeGuard<'a> {
1696 runtime: &'a PluginRuntime,
1697 handle: Option<ScopeHandle<'a>>,
1698}
1699unsafe impl Send for ScopeGuard<'_> {}
1700
1701impl<'a> ScopeGuard<'a> {
1702 pub fn handle(&self) -> Option<&ScopeHandle<'a>> {
1704 self.handle.as_ref()
1705 }
1706
1707 pub fn close(&mut self, output: Option<&Json>, metadata: Option<&Json>) -> Result<()> {
1709 let Some(handle) = self.handle.as_ref() else {
1710 return Ok(());
1711 };
1712 self.runtime.pop_scope(handle, output, metadata)?;
1713 self.handle.take();
1714 Ok(())
1715 }
1716}
1717
1718impl Drop for ScopeGuard<'_> {
1719 fn drop(&mut self) {
1720 if let Some(handle) = self.handle.take() {
1721 let _ = self.runtime.pop_scope(&handle, None, None);
1722 }
1723 }
1724}
1725
1726pub struct ThreadScopeStackGuard<'a> {
1728 previous: Option<ScopeStackBinding<'a>>,
1729}
1730
1731impl ThreadScopeStackGuard<'_> {
1732 pub fn restore(mut self) -> Result<()> {
1734 let Some(previous) = self.previous.take() else {
1735 return Ok(());
1736 };
1737 let status = previous.restore();
1738 if status == NemoRelayStatus::Ok {
1739 Ok(())
1740 } else {
1741 Err(format!("scope_stack_restore_thread failed: {status:?}"))
1742 }
1743 }
1744}
1745
1746impl Drop for ThreadScopeStackGuard<'_> {
1747 fn drop(&mut self) {
1748 if let Some(previous) = self.previous.take() {
1749 let _ = previous.restore();
1750 }
1751 }
1752}
1753
1754pub struct LlmStream {
1756 host: NemoRelayNativeHostApiV1,
1757 raw: NemoRelayNativeLlmStreamV1,
1758 finished: bool,
1759}
1760
1761unsafe impl Send for LlmStream {}
1763
1764impl LlmStream {
1765 pub unsafe fn from_raw(
1771 host: &NemoRelayNativeHostApiV1,
1772 mut raw: NemoRelayNativeLlmStreamV1,
1773 ) -> Result<Self> {
1774 let expected_size = std::mem::size_of::<NemoRelayNativeLlmStreamV1>();
1775 if raw.struct_size != expected_size {
1776 if raw.struct_size >= expected_size {
1777 unsafe { drop_raw_llm_stream(&mut raw) };
1778 }
1779 return Err(format!(
1780 "unsupported LLM stream struct size: {}",
1781 raw.struct_size
1782 ));
1783 }
1784 if raw.next.is_none() {
1785 unsafe { drop_raw_llm_stream(&mut raw) };
1786 return Err("LLM stream next callback was null".into());
1787 }
1788 Ok(Self {
1789 host: *host,
1790 raw,
1791 finished: false,
1792 })
1793 }
1794
1795 pub fn next_chunk(&mut self) -> Result<Option<Json>> {
1797 if self.finished {
1798 return Ok(None);
1799 }
1800 let next = self
1801 .raw
1802 .next
1803 .expect("LLM stream next callback is validated on construction");
1804 let mut out = ptr::null_mut();
1805 let status = unsafe { next(self.raw.user_data, &mut out) };
1806 match status {
1807 NemoRelayStatus::Ok => {
1808 if out.is_null() {
1809 self.finished = true;
1810 return Err("LLM stream returned null chunk".into());
1811 }
1812 let result = read_json_value(&self.host, out, "LLM stream chunk");
1813 unsafe { (self.host.string_free)(out) };
1814 match result {
1815 Ok(chunk) => Ok(Some(chunk)),
1816 Err(status) => {
1817 self.finished = true;
1818 Err(format!("LLM stream returned invalid JSON: {status:?}"))
1819 }
1820 }
1821 }
1822 NemoRelayStatus::StreamEnd => {
1823 if !out.is_null() {
1824 unsafe { (self.host.string_free)(out) };
1825 }
1826 self.finished = true;
1827 Ok(None)
1828 }
1829 other => {
1830 if !out.is_null() {
1831 unsafe { (self.host.string_free)(out) };
1832 }
1833 self.finished = true;
1834 Err(format!("LLM stream failed: {other:?}"))
1835 }
1836 }
1837 }
1838
1839 pub fn cancel(&mut self) -> Result<()> {
1841 if self.finished {
1842 return Ok(());
1843 }
1844 if let Some(cancel) = self.raw.cancel {
1845 let status = unsafe { cancel(self.raw.user_data) };
1846 if status != NemoRelayStatus::Ok {
1847 return Err(format!("LLM stream cancel failed: {status:?}"));
1848 }
1849 }
1850 self.finished = true;
1851 Ok(())
1852 }
1853}
1854
1855impl Iterator for LlmStream {
1856 type Item = Result<Json>;
1857
1858 fn next(&mut self) -> Option<Self::Item> {
1859 match self.next_chunk() {
1860 Ok(Some(chunk)) => Some(Ok(chunk)),
1861 Ok(None) => None,
1862 Err(message) => Some(Err(message)),
1863 }
1864 }
1865}
1866
1867unsafe fn drop_raw_llm_stream(raw: &mut NemoRelayNativeLlmStreamV1) {
1868 if let Some(drop_fn) = raw.drop.take() {
1869 unsafe { drop_fn(raw.user_data) };
1870 }
1871 raw.user_data = ptr::null_mut();
1872}
1873
1874impl Drop for LlmStream {
1875 fn drop(&mut self) {
1876 if !self.finished {
1877 if let Some(cancel) = self.raw.cancel {
1878 let _ = unsafe { cancel(self.raw.user_data) };
1879 }
1880 self.finished = true;
1881 }
1882 unsafe { drop_raw_llm_stream(&mut self.raw) };
1883 }
1884}
1885
1886pub struct ScopeHandle<'a> {
1888 host: &'a NemoRelayNativeHostApiV1,
1889 ptr: *mut NemoRelayNativeScopeHandle,
1890}
1891unsafe impl Send for ScopeHandle<'_> {}
1892
1893impl<'a> ScopeHandle<'a> {
1894 pub fn as_ptr(&self) -> *const NemoRelayNativeScopeHandle {
1896 self.ptr
1897 }
1898}
1899
1900impl Drop for ScopeHandle<'_> {
1901 fn drop(&mut self) {
1902 unsafe { (self.host.scope_handle_free)(self.ptr) };
1903 }
1904}
1905
1906pub struct ScopeStack<'a> {
1908 host: &'a NemoRelayNativeHostApiV1,
1909 ptr: *mut NemoRelayNativeScopeStack,
1910}
1911unsafe impl Send for ScopeStack<'_> {}
1912
1913impl<'a> ScopeStack<'a> {
1914 pub fn as_ptr(&self) -> *const NemoRelayNativeScopeStack {
1916 self.ptr
1917 }
1918
1919 pub fn set_thread(&self) -> NemoRelayStatus {
1925 unsafe { (self.host.scope_stack_set_thread)(self.ptr) }
1926 }
1927
1928 pub fn with_current<F>(&self, f: F) -> Result<()>
1930 where
1931 F: FnOnce() -> Result<()>,
1932 {
1933 struct State<F> {
1934 f: Option<F>,
1935 error: Option<String>,
1936 }
1937
1938 unsafe extern "C" fn trampoline<F>(user_data: *mut c_void) -> NemoRelayStatus
1939 where
1940 F: FnOnce() -> Result<()>,
1941 {
1942 if user_data.is_null() {
1943 return NemoRelayStatus::NullPointer;
1944 }
1945 let state = unsafe { &mut *(user_data as *mut State<F>) };
1946 let result = catch_unwind(AssertUnwindSafe(|| {
1947 let Some(f) = state.f.take() else {
1948 return Err("scope-stack callback was already consumed".to_string());
1949 };
1950 f()
1951 }));
1952 match result {
1953 Ok(Ok(())) => NemoRelayStatus::Ok,
1954 Ok(Err(message)) => {
1955 state.error = Some(message);
1956 NemoRelayStatus::Internal
1957 }
1958 Err(_) => {
1959 state.error = Some("scope-stack callback panicked".into());
1960 NemoRelayStatus::Internal
1961 }
1962 }
1963 }
1964
1965 let mut state = State {
1966 f: Some(f),
1967 error: None,
1968 };
1969 let status = unsafe {
1970 (self.host.scope_stack_with_current)(
1971 self.ptr,
1972 trampoline::<F>,
1973 (&mut state as *mut State<_>).cast(),
1974 )
1975 };
1976 if status == NemoRelayStatus::Ok {
1977 Ok(())
1978 } else {
1979 Err(state
1980 .error
1981 .unwrap_or_else(|| format!("scope_stack_with_current failed: {status:?}")))
1982 }
1983 }
1984}
1985
1986impl Drop for ScopeStack<'_> {
1987 fn drop(&mut self) {
1988 unsafe { (self.host.scope_stack_free)(self.ptr) };
1989 }
1990}
1991
1992pub struct ScopeStackBinding<'a> {
1994 host: &'a NemoRelayNativeHostApiV1,
1995 ptr: *mut NemoRelayNativeScopeStackBinding,
1996}
1997unsafe impl Send for ScopeStackBinding<'_> {}
1998
1999impl<'a> ScopeStackBinding<'a> {
2000 pub fn restore(mut self) -> NemoRelayStatus {
2002 let ptr = std::mem::replace(&mut self.ptr, ptr::null_mut());
2003 unsafe { (self.host.scope_stack_restore_thread)(ptr) }
2004 }
2005}
2006
2007impl Drop for ScopeStackBinding<'_> {
2008 fn drop(&mut self) {
2009 if !self.ptr.is_null() {
2010 unsafe { (self.host.scope_stack_binding_free)(self.ptr) };
2011 }
2012 }
2013}
2014
2015pub fn current_scope(host: &NemoRelayNativeHostApiV1) -> Result<ScopeHandle<'_>> {
2017 let mut out = ptr::null_mut();
2018 let status = unsafe { (host.scope_get_current)(&mut out) };
2019 if status == NemoRelayStatus::Ok && !out.is_null() {
2020 Ok(ScopeHandle { host, ptr: out })
2021 } else {
2022 Err(format!("scope_get_current failed: {status:?}"))
2023 }
2024}
2025
2026pub fn push_scope<'a>(
2028 host: &'a NemoRelayNativeHostApiV1,
2029 name: &str,
2030 scope_type: NemoRelayNativeScopeType,
2031 data: Option<&Json>,
2032 metadata: Option<&Json>,
2033 input: Option<&Json>,
2034) -> Result<ScopeHandle<'a>> {
2035 let name =
2036 HostString::new(host, name).ok_or_else(|| "failed to allocate scope name".to_string())?;
2037 let data = OptionalHostJson::new(host, data)?;
2038 let metadata = OptionalHostJson::new(host, metadata)?;
2039 let input = OptionalHostJson::new(host, input)?;
2040 let mut out = ptr::null_mut();
2041 let status = unsafe {
2042 (host.scope_push)(
2043 name.as_ptr(),
2044 scope_type,
2045 ptr::null(),
2046 0,
2047 data.as_ptr(),
2048 metadata.as_ptr(),
2049 input.as_ptr(),
2050 ptr::null(),
2051 &mut out,
2052 )
2053 };
2054 if status == NemoRelayStatus::Ok && !out.is_null() {
2055 Ok(ScopeHandle { host, ptr: out })
2056 } else {
2057 Err(format!("scope_push failed: {status:?}"))
2058 }
2059}
2060
2061pub fn pop_scope(
2063 host: &NemoRelayNativeHostApiV1,
2064 handle: &ScopeHandle<'_>,
2065 output: Option<&Json>,
2066 metadata: Option<&Json>,
2067) -> Result<()> {
2068 let output = OptionalHostJson::new(host, output)?;
2069 let metadata = OptionalHostJson::new(host, metadata)?;
2070 let status = unsafe {
2071 (host.scope_pop)(
2072 handle.as_ptr(),
2073 output.as_ptr(),
2074 metadata.as_ptr(),
2075 ptr::null(),
2076 )
2077 };
2078 if status == NemoRelayStatus::Ok {
2079 Ok(())
2080 } else {
2081 Err(format!("scope_pop failed: {status:?}"))
2082 }
2083}
2084
2085pub fn emit_mark(
2087 host: &NemoRelayNativeHostApiV1,
2088 name: &str,
2089 data: Option<&Json>,
2090 metadata: Option<&Json>,
2091) -> Result<()> {
2092 let name =
2093 HostString::new(host, name).ok_or_else(|| "failed to allocate mark name".to_string())?;
2094 let data = OptionalHostJson::new(host, data)?;
2095 let metadata = OptionalHostJson::new(host, metadata)?;
2096 let status = unsafe {
2097 (host.emit_mark)(
2098 name.as_ptr(),
2099 ptr::null(),
2100 data.as_ptr(),
2101 metadata.as_ptr(),
2102 ptr::null(),
2103 )
2104 };
2105 if status == NemoRelayStatus::Ok {
2106 Ok(())
2107 } else {
2108 Err(format!("emit_mark failed: {status:?}"))
2109 }
2110}
2111
2112#[allow(clippy::too_many_arguments)] fn emit_mark_v2_call(
2114 host: &NemoRelayNativeHostApiV1,
2115 emit_mark_v2: NemoRelayNativeEmitMarkV2Fn,
2116 name: &str,
2117 data: Option<&Json>,
2118 metadata: Option<&Json>,
2119 data_schema: Option<&DataSchema>,
2120 severity: Option<LogSeverity>,
2121) -> Result<()> {
2122 let name =
2123 HostString::new(host, name).ok_or_else(|| "failed to allocate mark name".to_string())?;
2124 let data = OptionalHostJson::new(host, data)?;
2125 let metadata = OptionalHostJson::new(host, metadata)?;
2126 let data_schema = data_schema
2127 .map(|value| {
2128 HostString::from_json(host, value)
2129 .ok_or_else(|| "failed to serialize mark data schema".to_string())
2130 })
2131 .transpose()?;
2132 let severity = severity
2133 .map(|value| {
2134 serde_json::to_value(value)
2135 .map_err(|err| format!("failed to serialize mark severity: {err}"))
2136 .and_then(|value| {
2137 value
2138 .as_str()
2139 .ok_or_else(|| "mark severity did not serialize as a string".to_string())
2140 .and_then(|value| {
2141 HostString::new(host, value)
2142 .ok_or_else(|| "failed to allocate mark severity".to_string())
2143 })
2144 })
2145 })
2146 .transpose()?;
2147 let status = unsafe {
2148 emit_mark_v2(
2149 name.as_ptr(),
2150 ptr::null(),
2151 data.as_ptr(),
2152 metadata.as_ptr(),
2153 data_schema
2154 .as_ref()
2155 .map(HostString::as_ptr)
2156 .unwrap_or(ptr::null()),
2157 severity
2158 .as_ref()
2159 .map(HostString::as_ptr)
2160 .unwrap_or(ptr::null()),
2161 ptr::null(),
2162 )
2163 };
2164 if status == NemoRelayStatus::Ok {
2165 Ok(())
2166 } else {
2167 Err(format!("emit_mark_v2 failed: {status:?}"))
2168 }
2169}
2170
2171pub fn create_scope_stack(host: &NemoRelayNativeHostApiV1) -> Result<ScopeStack<'_>> {
2173 let mut out = ptr::null_mut();
2174 let status = unsafe { (host.scope_stack_create)(&mut out) };
2175 if status == NemoRelayStatus::Ok && !out.is_null() {
2176 Ok(ScopeStack { host, ptr: out })
2177 } else {
2178 Err(format!("scope_stack_create failed: {status:?}"))
2179 }
2180}
2181
2182pub fn capture_scope_stack_thread(
2184 host: &NemoRelayNativeHostApiV1,
2185) -> Result<ScopeStackBinding<'_>> {
2186 let mut out = ptr::null_mut();
2187 let status = unsafe { (host.scope_stack_capture_thread)(&mut out) };
2188 if status == NemoRelayStatus::Ok && !out.is_null() {
2189 Ok(ScopeStackBinding { host, ptr: out })
2190 } else {
2191 Err(format!("scope_stack_capture_thread failed: {status:?}"))
2192 }
2193}
2194
2195pub trait NativePlugin: Send + 'static {
2197 fn plugin_kind(&self) -> &str;
2199
2200 fn allows_multiple_components(&self) -> bool {
2202 true
2203 }
2204
2205 fn executor_config(&self) -> NativeExecutorConfig {
2211 NativeExecutorConfig::default()
2212 }
2213
2214 fn executor_config_for_component(
2220 &self,
2221 plugin_config: &Map<String, Json>,
2222 ) -> Result<NativeExecutorConfig> {
2223 self.executor_config().with_component_config(plugin_config)
2224 }
2225
2226 fn validate(&self, plugin_config: &Map<String, Json>) -> Vec<ConfigDiagnostic> {
2228 self.executor_config_for_component(plugin_config)
2229 .err()
2230 .map(|message| ConfigDiagnostic {
2231 level: DiagnosticLevel::Error,
2232 code: "native_executor_config.invalid".into(),
2233 component: None,
2234 field: Some("executor.worker_threads".into()),
2235 message,
2236 })
2237 .into_iter()
2238 .collect()
2239 }
2240
2241 fn register(
2243 &mut self,
2244 plugin_config: &Map<String, Json>,
2245 ctx: &mut PluginContext<'_>,
2246 ) -> Result<()>;
2247}
2248
2249pub struct PluginContext<'a> {
2251 host: &'a NemoRelayNativeHostApiV1,
2252 raw: *mut NemoRelayNativePluginContext,
2253 executor: Arc<async_sdk::NativeExecutor>,
2254}
2255
2256#[allow(clippy::not_unsafe_ptr_arg_deref)]
2257impl<'a> PluginContext<'a> {
2258 pub unsafe fn from_raw(
2263 host: &'a NemoRelayNativeHostApiV1,
2264 raw: *mut NemoRelayNativePluginContext,
2265 ) -> Self {
2266 Self {
2267 host,
2268 raw,
2269 executor: async_sdk::NativeExecutor::new(NativeExecutorConfig::default(), "standalone"),
2270 }
2271 }
2272
2273 unsafe fn from_raw_with_executor(
2274 host: &'a NemoRelayNativeHostApiV1,
2275 raw: *mut NemoRelayNativePluginContext,
2276 executor: Arc<async_sdk::NativeExecutor>,
2277 ) -> Self {
2278 Self {
2279 host,
2280 raw,
2281 executor,
2282 }
2283 }
2284
2285 pub fn host_api(&self) -> &'a NemoRelayNativeHostApiV1 {
2287 self.host
2288 }
2289
2290 pub fn runtime(&self) -> PluginRuntime {
2292 PluginRuntime::from_context(self.host, self.raw)
2293 }
2294
2295 pub fn register_conditional_middleware_guardrail(
2297 &mut self,
2298 name: &str,
2299 kinds: &std::collections::BTreeSet<RuntimeRegistrationKind>,
2300 registration_name: &str,
2301 reason: &str,
2302 ) -> Result<()> {
2303 if self.host.abi_version < NEMO_RELAY_NATIVE_ABI_VERSION
2304 || self.host.struct_size < std::mem::size_of::<NemoRelayNativeHostApiV4>()
2305 {
2306 return Err("host does not support conditional middleware guardrails".into());
2307 }
2308 let v4 = unsafe { &*(self.host as *const _ as *const NemoRelayNativeHostApiV4) };
2309 let name = HostString::new(self.host, name)
2310 .ok_or_else(|| "failed to allocate gate name".to_string())?;
2311 let kinds = HostString::from_json(self.host, kinds)
2312 .ok_or_else(|| "failed to serialize runtime registration kinds".to_string())?;
2313 let registration_name = HostString::new(self.host, registration_name)
2314 .ok_or_else(|| "failed to allocate target name".to_string())?;
2315 let reason = HostString::new(self.host, reason)
2316 .ok_or_else(|| "failed to allocate gate reason".to_string())?;
2317 let status = unsafe {
2318 (v4.plugin_context_register_conditional_middleware_guardrail)(
2319 self.raw,
2320 name.as_ptr(),
2321 kinds.as_ptr(),
2322 registration_name.as_ptr(),
2323 reason.as_ptr(),
2324 )
2325 };
2326 status_result(
2327 self.host,
2328 status,
2329 "register conditional middleware guardrail",
2330 )
2331 }
2332
2333 pub fn register_subscriber<F>(&mut self, name: &str, callback: F) -> Result<()>
2335 where
2336 F: Fn(&Event) + Send + Sync + 'static,
2337 {
2338 let user_data = typed_callback_user_data(self.host, callback);
2339 let status = unsafe {
2340 self.register_subscriber_raw(
2341 name,
2342 typed_subscriber_trampoline::<F>,
2343 user_data,
2344 Some(drop_typed_callback::<F>),
2345 )
2346 };
2347 finish_typed_registration(self.host, status, user_data, "subscriber")
2348 }
2349
2350 pub unsafe fn register_subscriber_raw(
2357 &mut self,
2358 name: &str,
2359 cb: NemoRelayNativeEventSubscriberCb,
2360 user_data: *mut c_void,
2361 free_fn: NemoRelayNativeFreeFn,
2362 ) -> NemoRelayStatus {
2363 self.with_name_and_callback(name, user_data, free_fn, |host, name| unsafe {
2364 (host.plugin_context_register_subscriber)(self.raw, name, cb, user_data, free_fn)
2365 })
2366 }
2367
2368 pub unsafe fn register_mark_sanitize_guardrail_raw(
2375 &mut self,
2376 name: &str,
2377 priority: i32,
2378 cb: NemoRelayNativeEventSanitizeCb,
2379 user_data: *mut c_void,
2380 free_fn: NemoRelayNativeFreeFn,
2381 ) -> NemoRelayStatus {
2382 self.with_name_and_callback(name, user_data, free_fn, |host, name| unsafe {
2383 (host.plugin_context_register_mark_sanitize_guardrail)(
2384 self.raw, name, priority, cb, user_data, free_fn,
2385 )
2386 })
2387 }
2388
2389 pub unsafe fn register_scope_sanitize_start_guardrail_raw(
2396 &mut self,
2397 name: &str,
2398 priority: i32,
2399 cb: NemoRelayNativeEventSanitizeCb,
2400 user_data: *mut c_void,
2401 free_fn: NemoRelayNativeFreeFn,
2402 ) -> NemoRelayStatus {
2403 self.with_name_and_callback(name, user_data, free_fn, |host, name| unsafe {
2404 (host.plugin_context_register_scope_sanitize_start_guardrail)(
2405 self.raw, name, priority, cb, user_data, free_fn,
2406 )
2407 })
2408 }
2409
2410 pub unsafe fn register_scope_sanitize_end_guardrail_raw(
2417 &mut self,
2418 name: &str,
2419 priority: i32,
2420 cb: NemoRelayNativeEventSanitizeCb,
2421 user_data: *mut c_void,
2422 free_fn: NemoRelayNativeFreeFn,
2423 ) -> NemoRelayStatus {
2424 self.with_name_and_callback(name, user_data, free_fn, |host, name| unsafe {
2425 (host.plugin_context_register_scope_sanitize_end_guardrail)(
2426 self.raw, name, priority, cb, user_data, free_fn,
2427 )
2428 })
2429 }
2430
2431 pub unsafe fn register_tool_sanitize_request_guardrail_raw(
2438 &mut self,
2439 name: &str,
2440 priority: i32,
2441 cb: NemoRelayNativeToolJsonCb,
2442 user_data: *mut c_void,
2443 free_fn: NemoRelayNativeFreeFn,
2444 ) -> NemoRelayStatus {
2445 self.with_name_and_callback(name, user_data, free_fn, |host, name| unsafe {
2446 (host.plugin_context_register_tool_sanitize_request_guardrail)(
2447 self.raw, name, priority, cb, user_data, free_fn,
2448 )
2449 })
2450 }
2451
2452 pub unsafe fn register_tool_sanitize_response_guardrail_raw(
2459 &mut self,
2460 name: &str,
2461 priority: i32,
2462 cb: NemoRelayNativeToolJsonCb,
2463 user_data: *mut c_void,
2464 free_fn: NemoRelayNativeFreeFn,
2465 ) -> NemoRelayStatus {
2466 self.with_name_and_callback(name, user_data, free_fn, |host, name| unsafe {
2467 (host.plugin_context_register_tool_sanitize_response_guardrail)(
2468 self.raw, name, priority, cb, user_data, free_fn,
2469 )
2470 })
2471 }
2472
2473 pub unsafe fn register_tool_conditional_execution_guardrail_raw(
2480 &mut self,
2481 name: &str,
2482 priority: i32,
2483 cb: NemoRelayNativeToolConditionalCb,
2484 user_data: *mut c_void,
2485 free_fn: NemoRelayNativeFreeFn,
2486 ) -> NemoRelayStatus {
2487 self.with_name_and_callback(name, user_data, free_fn, |host, name| unsafe {
2488 (host.plugin_context_register_tool_conditional_execution_guardrail)(
2489 self.raw, name, priority, cb, user_data, free_fn,
2490 )
2491 })
2492 }
2493
2494 pub unsafe fn register_tool_request_intercept_raw(
2501 &mut self,
2502 name: &str,
2503 priority: i32,
2504 break_chain: bool,
2505 cb: NemoRelayNativeToolJsonCb,
2506 user_data: *mut c_void,
2507 free_fn: NemoRelayNativeFreeFn,
2508 ) -> NemoRelayStatus {
2509 self.with_name_and_callback(name, user_data, free_fn, |host, name| unsafe {
2510 (host.plugin_context_register_tool_request_intercept)(
2511 self.raw,
2512 name,
2513 priority,
2514 break_chain,
2515 cb,
2516 user_data,
2517 free_fn,
2518 )
2519 })
2520 }
2521
2522 pub unsafe fn register_tool_execution_intercept_raw(
2529 &mut self,
2530 name: &str,
2531 priority: i32,
2532 cb: NemoRelayNativeToolExecutionCb,
2533 user_data: *mut c_void,
2534 free_fn: NemoRelayNativeFreeFn,
2535 ) -> NemoRelayStatus {
2536 self.with_name_and_callback(name, user_data, free_fn, |host, name| unsafe {
2537 (host.plugin_context_register_tool_execution_intercept)(
2538 self.raw, name, priority, cb, user_data, free_fn,
2539 )
2540 })
2541 }
2542
2543 pub unsafe fn register_llm_sanitize_request_guardrail_raw(
2550 &mut self,
2551 name: &str,
2552 priority: i32,
2553 cb: NemoRelayNativeLlmSanitizeRequestCb,
2554 user_data: *mut c_void,
2555 free_fn: NemoRelayNativeFreeFn,
2556 ) -> NemoRelayStatus {
2557 self.with_name_and_callback(name, user_data, free_fn, |host, name| unsafe {
2558 (host.plugin_context_register_llm_sanitize_request_guardrail)(
2559 self.raw, name, priority, cb, user_data, free_fn,
2560 )
2561 })
2562 }
2563
2564 pub unsafe fn register_llm_sanitize_response_guardrail_raw(
2571 &mut self,
2572 name: &str,
2573 priority: i32,
2574 cb: NemoRelayNativeLlmSanitizeResponseCb,
2575 user_data: *mut c_void,
2576 free_fn: NemoRelayNativeFreeFn,
2577 ) -> NemoRelayStatus {
2578 self.with_name_and_callback(name, user_data, free_fn, |host, name| unsafe {
2579 (host.plugin_context_register_llm_sanitize_response_guardrail)(
2580 self.raw, name, priority, cb, user_data, free_fn,
2581 )
2582 })
2583 }
2584
2585 pub unsafe fn register_llm_conditional_execution_guardrail_raw(
2592 &mut self,
2593 name: &str,
2594 priority: i32,
2595 cb: NemoRelayNativeLlmConditionalCb,
2596 user_data: *mut c_void,
2597 free_fn: NemoRelayNativeFreeFn,
2598 ) -> NemoRelayStatus {
2599 self.with_name_and_callback(name, user_data, free_fn, |host, name| unsafe {
2600 (host.plugin_context_register_llm_conditional_execution_guardrail)(
2601 self.raw, name, priority, cb, user_data, free_fn,
2602 )
2603 })
2604 }
2605
2606 pub unsafe fn register_llm_request_intercept_raw(
2613 &mut self,
2614 name: &str,
2615 priority: i32,
2616 break_chain: bool,
2617 cb: NemoRelayNativeLlmRequestInterceptCb,
2618 user_data: *mut c_void,
2619 free_fn: NemoRelayNativeFreeFn,
2620 ) -> NemoRelayStatus {
2621 self.with_name_and_callback(name, user_data, free_fn, |host, name| unsafe {
2622 (host.plugin_context_register_llm_request_intercept)(
2623 self.raw,
2624 name,
2625 priority,
2626 break_chain,
2627 cb,
2628 user_data,
2629 free_fn,
2630 )
2631 })
2632 }
2633
2634 pub unsafe fn register_llm_execution_intercept_raw(
2641 &mut self,
2642 name: &str,
2643 priority: i32,
2644 cb: NemoRelayNativeLlmExecutionCb,
2645 user_data: *mut c_void,
2646 free_fn: NemoRelayNativeFreeFn,
2647 ) -> NemoRelayStatus {
2648 self.with_name_and_callback(name, user_data, free_fn, |host, name| unsafe {
2649 (host.plugin_context_register_llm_execution_intercept)(
2650 self.raw, name, priority, cb, user_data, free_fn,
2651 )
2652 })
2653 }
2654
2655 pub unsafe fn register_llm_stream_execution_intercept_raw(
2662 &mut self,
2663 name: &str,
2664 priority: i32,
2665 cb: NemoRelayNativeLlmStreamExecutionCb,
2666 user_data: *mut c_void,
2667 free_fn: NemoRelayNativeFreeFn,
2668 ) -> NemoRelayStatus {
2669 self.with_name_and_callback(name, user_data, free_fn, |host, name| unsafe {
2670 (host.plugin_context_register_llm_stream_execution_intercept)(
2671 self.raw, name, priority, cb, user_data, free_fn,
2672 )
2673 })
2674 }
2675
2676 #[allow(clippy::too_many_arguments)] pub unsafe fn register_async_middleware_raw(
2691 &mut self,
2692 kind: NemoRelayNativeAsyncMiddlewareKind,
2693 name: &str,
2694 priority: i32,
2695 break_chain: bool,
2696 cb: NemoRelayNativeAsyncMiddlewareCb,
2697 user_data: *mut c_void,
2698 free_fn: NemoRelayNativeFreeFn,
2699 ) -> NemoRelayStatus {
2700 if self.host.abi_version < NEMO_RELAY_NATIVE_ABI_VERSION_ASYNC_MIDDLEWARE
2701 || self.host.struct_size < std::mem::size_of::<NemoRelayNativeHostApiV3>()
2702 {
2703 if let Some(free_fn) = free_fn {
2704 unsafe { free_fn(user_data) };
2705 }
2706 return NemoRelayStatus::InvalidArg;
2707 }
2708 let host = unsafe { &*(self.host as *const _ as *const NemoRelayNativeHostApiV3) };
2709 self.with_name_and_callback(name, user_data, free_fn, |_, name| unsafe {
2710 (host.plugin_context_register_async_middleware)(
2711 self.raw,
2712 kind as u32,
2713 name,
2714 priority,
2715 break_chain,
2716 cb,
2717 user_data,
2718 free_fn,
2719 )
2720 })
2721 }
2722
2723 pub unsafe fn register_async_stream_middleware_raw(
2735 &mut self,
2736 name: &str,
2737 priority: i32,
2738 cb: NemoRelayNativeAsyncStreamMiddlewareCb,
2739 user_data: *mut c_void,
2740 free_fn: NemoRelayNativeFreeFn,
2741 ) -> NemoRelayStatus {
2742 if self.host.abi_version < NEMO_RELAY_NATIVE_ABI_VERSION_ASYNC_MIDDLEWARE
2743 || self.host.struct_size < std::mem::size_of::<NemoRelayNativeHostApiV3>()
2744 {
2745 if let Some(free_fn) = free_fn {
2746 unsafe { free_fn(user_data) };
2747 }
2748 return NemoRelayStatus::InvalidArg;
2749 }
2750 let host = unsafe { &*(self.host as *const _ as *const NemoRelayNativeHostApiV3) };
2751 self.with_name_and_callback(name, user_data, free_fn, |_, name| unsafe {
2752 (host.plugin_context_register_async_stream_middleware)(
2753 self.raw, name, priority, cb, user_data, free_fn,
2754 )
2755 })
2756 }
2757
2758 fn with_name_and_callback(
2759 &self,
2760 name: &str,
2761 user_data: *mut c_void,
2762 free_fn: NemoRelayNativeFreeFn,
2763 f: impl FnOnce(&NemoRelayNativeHostApiV1, *const NemoRelayNativeString) -> NemoRelayStatus,
2764 ) -> NemoRelayStatus {
2765 let name = match HostString::try_new(self.host, name) {
2766 Ok(name) => name,
2767 Err(status) => {
2768 if let Some(free_fn) = free_fn {
2769 unsafe { free_fn(user_data) };
2770 }
2771 return status;
2772 }
2773 };
2774 f(self.host, name.as_ptr())
2775 }
2776}
2777
2778struct TypedCallback<F> {
2779 host: NemoRelayNativeHostApiV1,
2780 callback: F,
2781}
2782
2783fn typed_callback_user_data<F>(host: &NemoRelayNativeHostApiV1, callback: F) -> *mut c_void {
2784 Box::into_raw(Box::new(TypedCallback {
2785 host: *host,
2786 callback,
2787 })) as *mut c_void
2788}
2789
2790unsafe extern "C" fn drop_typed_callback<F>(user_data: *mut c_void) {
2791 if !user_data.is_null() {
2792 let callback = unsafe { Box::from_raw(user_data as *mut TypedCallback<F>) };
2793 let host = callback.host;
2794 if catch_unwind(AssertUnwindSafe(|| drop(callback))).is_err() {
2795 set_last_error(&host, "native plugin typed callback state drop panicked");
2796 }
2797 }
2798}
2799
2800fn finish_typed_registration(
2801 host: &NemoRelayNativeHostApiV1,
2802 status: NemoRelayStatus,
2803 user_data: *mut c_void,
2804 label: &str,
2805) -> Result<()> {
2806 let _ = user_data;
2807 if status == NemoRelayStatus::Ok {
2808 Ok(())
2809 } else {
2810 Err(status_error(host, status, label))
2811 }
2812}
2813
2814fn status_error(host: &NemoRelayNativeHostApiV1, status: NemoRelayStatus, label: &str) -> String {
2815 debug_assert_ne!(status, NemoRelayStatus::Ok);
2816 set_last_error(host, &format!("{label} failed: {status:?}"));
2817 format!("{label} failed: {status:?}")
2818}
2819
2820fn status_result(
2821 host: &NemoRelayNativeHostApiV1,
2822 status: NemoRelayStatus,
2823 label: &str,
2824) -> Result<()> {
2825 if status == NemoRelayStatus::Ok {
2826 Ok(())
2827 } else {
2828 Err(status_error(host, status, label))
2829 }
2830}
2831
2832fn callback_panic(host: &NemoRelayNativeHostApiV1, label: &str) -> NemoRelayStatus {
2833 set_last_error(host, &format!("{label} panicked"));
2834 NemoRelayStatus::Internal
2835}
2836
2837unsafe extern "C" fn typed_subscriber_trampoline<F>(
2838 user_data: *mut c_void,
2839 event_json: *const NemoRelayNativeString,
2840) -> NemoRelayStatus
2841where
2842 F: Fn(&Event) + Send + Sync + 'static,
2843{
2844 if user_data.is_null() {
2845 return NemoRelayStatus::NullPointer;
2846 }
2847 let state = unsafe { &*(user_data as *const TypedCallback<F>) };
2848 let result = catch_unwind(AssertUnwindSafe(|| {
2849 let event: Event = read_json_value(&state.host, event_json, "event")?;
2850 (state.callback)(&event);
2851 Ok::<_, NemoRelayStatus>(())
2852 }));
2853 match result {
2854 Ok(Ok(())) => NemoRelayStatus::Ok,
2855 Ok(Err(status)) => status,
2856 Err(_) => callback_panic(&state.host, "subscriber callback"),
2857 }
2858}
2859
2860struct HostString<'a> {
2861 host: &'a NemoRelayNativeHostApiV1,
2862 ptr: *mut NemoRelayNativeString,
2863}
2864unsafe impl Send for HostString<'_> {}
2865
2866impl<'a> HostString<'a> {
2867 fn try_new(
2868 host: &'a NemoRelayNativeHostApiV1,
2869 value: &str,
2870 ) -> std::result::Result<Self, NemoRelayStatus> {
2871 let mut out = ptr::null_mut();
2872 let status = unsafe { (host.string_new)(value.as_ptr(), value.len(), &mut out) };
2873 if status != NemoRelayStatus::Ok {
2874 return Err(status);
2875 }
2876 if out.is_null() {
2877 return Err(NemoRelayStatus::Internal);
2878 }
2879 Ok(Self { host, ptr: out })
2880 }
2881
2882 fn new(host: &'a NemoRelayNativeHostApiV1, value: &str) -> Option<Self> {
2883 Self::try_new(host, value).ok()
2884 }
2885
2886 fn from_json<T: Serialize>(host: &'a NemoRelayNativeHostApiV1, value: &T) -> Option<Self> {
2887 serde_json::to_string(value)
2888 .ok()
2889 .and_then(|json| Self::new(host, &json))
2890 }
2891
2892 fn as_ptr(&self) -> *const NemoRelayNativeString {
2893 self.ptr
2894 }
2895}
2896
2897impl Drop for HostString<'_> {
2898 fn drop(&mut self) {
2899 unsafe { (self.host.string_free)(self.ptr) };
2900 }
2901}
2902
2903fn codec_status(host: &NemoRelayNativeHostApiV1, status: NemoRelayStatus) -> Result<()> {
2904 if status == NemoRelayStatus::Ok {
2905 Ok(())
2906 } else {
2907 Err(status_error(host, status, "LLM codec operation"))
2908 }
2909}
2910
2911fn native_codec_call<T: DeserializeOwned>(
2912 host: &NemoRelayNativeHostApiV1,
2913 call: impl FnOnce(*mut *mut NemoRelayNativeString) -> Result<()>,
2914) -> Result<T> {
2915 native_json_call(host, "LLM codec operation", call)
2916}
2917
2918fn native_json_call<T: DeserializeOwned>(
2919 host: &NemoRelayNativeHostApiV1,
2920 operation: &str,
2921 call: impl FnOnce(*mut *mut NemoRelayNativeString) -> Result<()>,
2922) -> Result<T> {
2923 let mut out = ptr::null_mut();
2924 call(&mut out)?;
2925 if out.is_null() {
2926 return Err(format!("{operation} returned null"));
2927 }
2928 let out = HostString { host, ptr: out };
2929 let text = read_host_string(host, out.as_ptr())
2930 .map_err(|_| format!("{operation} returned invalid UTF-8"))?;
2931 serde_json::from_str(&text).map_err(|error| format!("invalid {operation} result: {error}"))
2932}
2933
2934struct OptionalHostJson<'a>(Option<HostString<'a>>);
2935
2936impl<'a> OptionalHostJson<'a> {
2937 fn new(host: &'a NemoRelayNativeHostApiV1, value: Option<&Json>) -> Result<Self> {
2938 match value {
2939 Some(value) => HostString::from_json(host, value)
2940 .map(|value| Self(Some(value)))
2941 .ok_or_else(|| "failed to allocate JSON host string".into()),
2942 None => Ok(Self(None)),
2943 }
2944 }
2945
2946 fn as_ptr(&self) -> *const NemoRelayNativeString {
2947 self.0
2948 .as_ref()
2949 .map(HostString::as_ptr)
2950 .unwrap_or(ptr::null())
2951 }
2952}
2953
2954enum OwnedHostApi {
2955 V1(NemoRelayNativeHostApiV1),
2956 V3(NemoRelayNativeHostApiV3),
2957 V4(NemoRelayNativeHostApiV4),
2958}
2959
2960impl OwnedHostApi {
2961 unsafe fn copy_from(host: &NemoRelayNativeHostApiV1) -> Self {
2962 if host.abi_version >= NEMO_RELAY_NATIVE_ABI_VERSION
2963 && host.struct_size >= std::mem::size_of::<NemoRelayNativeHostApiV4>()
2964 {
2965 Self::V4(unsafe { *(host as *const _ as *const NemoRelayNativeHostApiV4) })
2966 } else if host.abi_version >= NEMO_RELAY_NATIVE_ABI_VERSION_ASYNC_MIDDLEWARE
2967 && host.struct_size >= std::mem::size_of::<NemoRelayNativeHostApiV3>()
2968 {
2969 Self::V3(unsafe { *(host as *const _ as *const NemoRelayNativeHostApiV3) })
2970 } else {
2971 Self::V1(*host)
2972 }
2973 }
2974
2975 fn v1(&self) -> &NemoRelayNativeHostApiV1 {
2976 match self {
2977 Self::V1(host) => host,
2978 Self::V3(host) => &host.v1,
2979 Self::V4(host) => &host.v3.v1,
2980 }
2981 }
2982}
2983
2984struct PluginState<P> {
2985 host: OwnedHostApi,
2986 plugin: Mutex<P>,
2987}
2988
2989unsafe extern "C" fn drop_plugin_state<P: NativePlugin>(user_data: *mut c_void) {
2990 if !user_data.is_null() {
2991 let state = unsafe { Box::from_raw(user_data as *mut PluginState<P>) };
2992 let host = *state.host.v1();
2993 if catch_unwind(AssertUnwindSafe(|| drop(state))).is_err() {
2994 set_last_error(&host, "native plugin state drop panicked");
2995 }
2996 }
2997}
2998
2999unsafe extern "C" fn validate_trampoline<P: NativePlugin>(
3000 user_data: *mut c_void,
3001 plugin_config_json: *const NemoRelayNativeString,
3002 out_diagnostics_json: *mut *mut NemoRelayNativeString,
3003) -> NemoRelayStatus {
3004 if user_data.is_null() || out_diagnostics_json.is_null() {
3005 return NemoRelayStatus::NullPointer;
3006 }
3007 unsafe { *out_diagnostics_json = ptr::null_mut() };
3008 let state = unsafe { &*(user_data as *const PluginState<P>) };
3009 let result = catch_unwind(AssertUnwindSafe(|| {
3010 let host = state.host.v1();
3011 let config = match read_json_object(host, plugin_config_json) {
3012 Ok(config) => config,
3013 Err(status) => return status,
3014 };
3015 let plugin = match state.plugin.lock() {
3016 Ok(plugin) => plugin,
3017 Err(_) => {
3018 set_last_error(host, "native plugin state lock poisoned");
3019 return NemoRelayStatus::Internal;
3020 }
3021 };
3022 let diagnostics = plugin.validate(&config);
3023 write_json(host, &diagnostics, out_diagnostics_json)
3024 }));
3025 result.unwrap_or_else(|_| {
3026 set_last_error(state.host.v1(), "native plugin validate callback panicked");
3027 NemoRelayStatus::Internal
3028 })
3029}
3030
3031unsafe extern "C" fn register_trampoline<P: NativePlugin>(
3032 user_data: *mut c_void,
3033 plugin_config_json: *const NemoRelayNativeString,
3034 ctx: *mut NemoRelayNativePluginContext,
3035) -> NemoRelayStatus {
3036 if user_data.is_null() || ctx.is_null() {
3037 return NemoRelayStatus::NullPointer;
3038 }
3039 let state = unsafe { &*(user_data as *const PluginState<P>) };
3040 let result = catch_unwind(AssertUnwindSafe(|| {
3041 let host = state.host.v1();
3042 let config = match read_json_object(host, plugin_config_json) {
3043 Ok(config) => config,
3044 Err(status) => return status,
3045 };
3046 let mut plugin = match state.plugin.lock() {
3047 Ok(plugin) => plugin,
3048 Err(_) => {
3049 set_last_error(host, "native plugin state lock poisoned");
3050 return NemoRelayStatus::Internal;
3051 }
3052 };
3053 let executor_config = match plugin.executor_config_for_component(&config) {
3054 Ok(config) => config,
3055 Err(error) => {
3056 set_last_error(host, &error);
3057 return NemoRelayStatus::InvalidArg;
3058 }
3059 };
3060 let mut ctx = unsafe {
3061 PluginContext::from_raw_with_executor(
3062 host,
3063 ctx,
3064 async_sdk::NativeExecutor::new(executor_config, plugin.plugin_kind()),
3065 )
3066 };
3067 match plugin.register(&config, &mut ctx) {
3068 Ok(()) => NemoRelayStatus::Ok,
3069 Err(message) => {
3070 set_last_error(host, &message);
3071 NemoRelayStatus::Internal
3072 }
3073 }
3074 }));
3075 result.unwrap_or_else(|_| {
3076 set_last_error(state.host.v1(), "native plugin register callback panicked");
3077 NemoRelayStatus::Internal
3078 })
3079}
3080
3081fn read_json_object(
3082 host: &NemoRelayNativeHostApiV1,
3083 value: *const NemoRelayNativeString,
3084) -> std::result::Result<Map<String, Json>, NemoRelayStatus> {
3085 let value: Json = read_json_value(host, value, "plugin config")?;
3086 match value {
3087 Json::Object(map) => Ok(map),
3088 _ => {
3089 set_last_error(host, "plugin config must be a JSON object");
3090 Err(NemoRelayStatus::InvalidJson)
3091 }
3092 }
3093}
3094
3095fn read_json_value<T: DeserializeOwned>(
3096 host: &NemoRelayNativeHostApiV1,
3097 value: *const NemoRelayNativeString,
3098 label: &str,
3099) -> std::result::Result<T, NemoRelayStatus> {
3100 let text = read_required_host_string(host, value, label)?;
3101 serde_json::from_str::<T>(&text).map_err(|error| {
3102 set_last_error(host, &format!("{label} was invalid JSON: {error}"));
3103 NemoRelayStatus::InvalidJson
3104 })
3105}
3106
3107#[derive(Debug)]
3108enum HostStringReadError {
3109 Null,
3110 InvalidUtf8,
3111}
3112
3113fn read_required_host_string(
3114 host: &NemoRelayNativeHostApiV1,
3115 value: *const NemoRelayNativeString,
3116 label: &str,
3117) -> std::result::Result<String, NemoRelayStatus> {
3118 match read_host_string(host, value) {
3119 Ok(value) => Ok(value),
3120 Err(HostStringReadError::Null) => {
3121 set_last_error(host, &format!("{label} was null"));
3122 Err(NemoRelayStatus::NullPointer)
3123 }
3124 Err(HostStringReadError::InvalidUtf8) => {
3125 set_last_error(host, &format!("{label} contained invalid UTF-8"));
3126 Err(NemoRelayStatus::InvalidUtf8)
3127 }
3128 }
3129}
3130
3131fn read_host_string(
3132 host: &NemoRelayNativeHostApiV1,
3133 value: *const NemoRelayNativeString,
3134) -> std::result::Result<String, HostStringReadError> {
3135 if value.is_null() {
3136 return Err(HostStringReadError::Null);
3137 }
3138 let len = unsafe { (host.string_len)(value) };
3139 let data = unsafe { (host.string_data)(value) };
3140 if data.is_null() && len > 0 {
3141 return Err(HostStringReadError::InvalidUtf8);
3142 }
3143 let bytes = if len == 0 {
3144 &[][..]
3145 } else {
3146 unsafe { std::slice::from_raw_parts(data, len) }
3147 };
3148 std::str::from_utf8(bytes)
3149 .map(str::to_owned)
3150 .map_err(|_| HostStringReadError::InvalidUtf8)
3151}
3152
3153fn take_host_string(
3154 host: &NemoRelayNativeHostApiV1,
3155 value: *mut NemoRelayNativeString,
3156) -> Result<String> {
3157 let result = read_host_string(host, value)
3158 .map_err(|error| format!("host returned an invalid string: {error:?}"));
3159 if !value.is_null() {
3160 unsafe { (host.string_free)(value) };
3161 }
3162 result
3163}
3164
3165fn take_host_json<T: DeserializeOwned>(
3166 host: &NemoRelayNativeHostApiV1,
3167 value: *mut NemoRelayNativeString,
3168) -> Result<T> {
3169 let text = take_host_string(host, value)?;
3170 serde_json::from_str(&text).map_err(|error| format!("host returned invalid JSON: {error}"))
3171}
3172
3173fn write_json<T: Serialize>(
3174 host: &NemoRelayNativeHostApiV1,
3175 value: &T,
3176 out: *mut *mut NemoRelayNativeString,
3177) -> NemoRelayStatus {
3178 if out.is_null() {
3179 return NemoRelayStatus::NullPointer;
3180 }
3181 unsafe { *out = ptr::null_mut() };
3182 let json = serde_json::to_value(value).expect("Relay DTOs and serde_json::Value serialize");
3183 let Some(handle) = HostString::from_json(host, &json) else {
3184 set_last_error(host, "failed to allocate host string");
3185 return NemoRelayStatus::Internal;
3186 };
3187 unsafe { *out = handle.ptr };
3188 std::mem::forget(handle);
3189 NemoRelayStatus::Ok
3190}
3191
3192fn set_last_error(host: &NemoRelayNativeHostApiV1, message: &str) {
3193 if let Some(message) = HostString::new(host, message) {
3194 unsafe { (host.last_error_set)(message.as_ptr()) };
3195 }
3196}
3197
3198#[doc(hidden)]
3203pub unsafe fn __set_last_error_from_entry(host: *const NemoRelayNativeHostApiV1, message: &str) {
3204 if !host.is_null() {
3205 set_last_error(unsafe { &*host }, message);
3206 }
3207}
3208
3209pub unsafe fn export_plugin<P: NativePlugin>(
3216 host: *const NemoRelayNativeHostApiV1,
3217 out: *mut NemoRelayNativePluginV1,
3218 plugin: P,
3219) -> NemoRelayStatus {
3220 if host.is_null() || out.is_null() {
3221 return NemoRelayStatus::NullPointer;
3222 }
3223 unsafe { *out = NemoRelayNativePluginV1::default() };
3224 let host_ref = unsafe { &*host };
3225 export_plugin_checked(host_ref, out, || plugin)
3226}
3227
3228#[doc(hidden)]
3235pub unsafe fn __export_plugin_from_constructor<P, F>(
3236 host: *const NemoRelayNativeHostApiV1,
3237 out: *mut NemoRelayNativePluginV1,
3238 constructor: F,
3239) -> NemoRelayStatus
3240where
3241 P: NativePlugin,
3242 F: FnOnce() -> P,
3243{
3244 if host.is_null() || out.is_null() {
3245 return NemoRelayStatus::NullPointer;
3246 }
3247 unsafe { *out = NemoRelayNativePluginV1::default() };
3248 let host_ref = unsafe { &*host };
3249 export_plugin_checked(host_ref, out, constructor)
3250}
3251
3252fn export_plugin_checked<P, F>(
3253 host_ref: &NemoRelayNativeHostApiV1,
3254 out: *mut NemoRelayNativePluginV1,
3255 constructor: F,
3256) -> NemoRelayStatus
3257where
3258 P: NativePlugin,
3259 F: FnOnce() -> P,
3260{
3261 let supported_abi = (NEMO_RELAY_NATIVE_ABI_VERSION_LEGACY..=NEMO_RELAY_NATIVE_ABI_VERSION)
3262 .contains(&host_ref.abi_version);
3263 if !supported_abi {
3264 return NemoRelayStatus::InvalidArg;
3265 }
3266 if host_ref.struct_size < std::mem::size_of::<NemoRelayNativeHostApiV1>() {
3267 return NemoRelayStatus::InvalidArg;
3268 }
3269
3270 let plugin = constructor();
3271 let kind = plugin.plugin_kind().to_owned();
3272 let allows_multiple_components = plugin.allows_multiple_components();
3273 let Some(kind_handle) = HostString::new(host_ref, &kind) else {
3274 return NemoRelayStatus::Internal;
3275 };
3276 let state = Box::new(PluginState {
3277 host: unsafe { OwnedHostApi::copy_from(host_ref) },
3278 plugin: Mutex::new(plugin),
3279 });
3280 unsafe {
3281 *out = NemoRelayNativePluginV1 {
3282 struct_size: std::mem::size_of::<NemoRelayNativePluginV1>(),
3283 plugin_kind: kind_handle.ptr,
3284 allows_multiple_components,
3285 user_data: Box::into_raw(state) as *mut c_void,
3286 validate: Some(validate_trampoline::<P>),
3287 register: Some(register_trampoline::<P>),
3288 drop: Some(drop_plugin_state::<P>),
3289 };
3290 }
3291 std::mem::forget(kind_handle);
3292 NemoRelayStatus::Ok
3293}
3294
3295#[macro_export]
3297macro_rules! nemo_relay_plugin {
3298 ($symbol:ident, $constructor:expr) => {
3299 #[doc = "Native plugin entry symbol generated by `nemo_relay_plugin!`."]
3300 #[unsafe(no_mangle)]
3301 pub unsafe extern "C" fn $symbol(
3302 host: *const $crate::NemoRelayNativeHostApiV1,
3303 out: *mut $crate::NemoRelayNativePluginV1,
3304 ) -> $crate::NemoRelayStatus {
3305 match ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| unsafe {
3306 $crate::__export_plugin_from_constructor(host, out, $constructor)
3307 })) {
3308 Ok(status) => status,
3309 Err(_) => {
3310 unsafe {
3311 $crate::__set_last_error_from_entry(
3312 host,
3313 "native plugin entry callback panicked",
3314 )
3315 };
3316 $crate::NemoRelayStatus::Internal
3317 }
3318 }
3319 }
3320 };
3321}