1use super::{
5 Arc, CStr, ConfigDiagnostic, DiagnosticLevel, FfiPluginContext, Future,
6 NemoFlowEventSubscriberCb, NemoFlowFreeFn, NemoFlowJsonCb, NemoFlowLlmConditionalCb,
7 NemoFlowLlmExecInterceptCb, NemoFlowLlmRequestCb, NemoFlowLlmRequestInterceptCb,
8 NemoFlowPluginRegisterCb, NemoFlowPluginValidateCb, NemoFlowStatus, NemoFlowToolConditionalCb,
9 NemoFlowToolExecInterceptCb, NemoFlowToolSanitizeCb, Pin, Plugin, PluginConfig, PluginError,
10 PluginRegistrationContext, active_plugin_report, c_char, c_str_to_json, c_str_to_string,
11 clear_last_error, clear_plugin_configuration, deregister_plugin, initialize_plugins,
12 json_to_c_string, last_error_message, list_plugin_kinds, nemo_flow_string_free,
13 register_adaptive_component, register_plugin, set_last_error, status_from_plugin_error,
14 tokio_runtime, validate_plugin_config, wrap_event_subscriber, wrap_llm_conditional_fn,
15 wrap_llm_exec_intercept_fn, wrap_llm_request_intercept_fn, wrap_llm_response_fn,
16 wrap_llm_sanitize_request_fn, wrap_llm_stream_exec_intercept_fn, wrap_tool_conditional_fn,
17 wrap_tool_exec_intercept_fn, wrap_tool_request_intercept_fn, wrap_tool_sanitize_fn,
18};
19
20struct FfiHostedPluginUserData {
21 ptr: *mut libc::c_void,
22 free_fn: NemoFlowFreeFn,
23}
24
25unsafe impl Send for FfiHostedPluginUserData {}
26unsafe impl Sync for FfiHostedPluginUserData {}
27
28impl Drop for FfiHostedPluginUserData {
29 fn drop(&mut self) {
30 if let Some(free_fn) = self.free_fn {
31 unsafe { free_fn(self.ptr) };
32 }
33 }
34}
35
36struct FfiHostedPluginAdapter {
37 plugin_kind: String,
38 validate_cb: Option<NemoFlowPluginValidateCb>,
39 register_cb: NemoFlowPluginRegisterCb,
40 user_data: Arc<FfiHostedPluginUserData>,
41}
42
43impl Plugin for FfiHostedPluginAdapter {
44 fn plugin_kind(&self) -> &str {
45 &self.plugin_kind
46 }
47
48 fn validate(
49 &self,
50 plugin_config: &serde_json::Map<String, serde_json::Value>,
51 ) -> Vec<ConfigDiagnostic> {
52 let Some(validate_cb) = self.validate_cb else {
53 return vec![];
54 };
55
56 clear_last_error();
57 let plugin_config_json =
58 json_to_c_string(&serde_json::Value::Object(plugin_config.clone()));
59 let result_ptr = unsafe { validate_cb(self.user_data.ptr, plugin_config_json) };
60 unsafe { nemo_flow_string_free(plugin_config_json) };
61
62 if result_ptr.is_null() {
63 let message = last_error_message().unwrap_or_else(|| {
64 format!(
65 "plugin '{}' validate callback returned null",
66 self.plugin_kind
67 )
68 });
69 return vec![ConfigDiagnostic {
70 level: DiagnosticLevel::Error,
71 code: "plugin.validate_failed".to_string(),
72 component: Some(self.plugin_kind.clone()),
73 field: None,
74 message,
75 }];
76 }
77
78 let diagnostics = unsafe { CStr::from_ptr(result_ptr) }
79 .to_str()
80 .ok()
81 .and_then(|text| serde_json::from_str::<Vec<ConfigDiagnostic>>(text).ok());
82 unsafe { nemo_flow_string_free(result_ptr) };
83 diagnostics.unwrap_or_else(|| {
84 vec![ConfigDiagnostic {
85 level: DiagnosticLevel::Error,
86 code: "plugin.validate_failed".to_string(),
87 component: Some(self.plugin_kind.clone()),
88 field: None,
89 message: format!(
90 "plugin '{}' validate callback returned invalid diagnostics JSON",
91 self.plugin_kind
92 ),
93 }]
94 })
95 }
96
97 fn register<'a>(
98 &'a self,
99 plugin_config: &serde_json::Map<String, serde_json::Value>,
100 ctx: &'a mut PluginRegistrationContext,
101 ) -> Pin<Box<dyn Future<Output = std::result::Result<(), PluginError>> + Send + 'a>> {
102 let plugin_config = plugin_config.clone();
103 Box::pin(async move {
104 clear_last_error();
105 let plugin_config_json = json_to_c_string(&serde_json::Value::Object(plugin_config));
106 let mut ffi_ctx = FfiPluginContext(ctx as *mut _);
107 let status =
108 unsafe { (self.register_cb)(self.user_data.ptr, plugin_config_json, &mut ffi_ctx) };
109 unsafe { nemo_flow_string_free(plugin_config_json) };
110 if status == NemoFlowStatus::Ok {
111 Ok(())
112 } else if let Some(message) = last_error_message() {
113 Err(PluginError::RegistrationFailed(message))
114 } else {
115 Err(PluginError::RegistrationFailed(format!(
116 "plugin '{}' register callback failed with status {:?}",
117 self.plugin_kind, status
118 )))
119 }
120 })
121 }
122}
123
124fn ensure_adaptive_component_registered() -> std::result::Result<(), NemoFlowStatus> {
125 register_adaptive_component().map_err(|err| status_from_plugin_error(&err))
126}
127
128#[unsafe(no_mangle)]
133pub unsafe extern "C" fn nemo_flow_validate_plugin_config(
134 config_json: *const c_char,
135 out_json: *mut *mut c_char,
136) -> NemoFlowStatus {
137 clear_last_error();
138 if out_json.is_null() {
139 set_last_error("out_json pointer is null");
140 return NemoFlowStatus::NullPointer;
141 }
142 if let Err(status) = ensure_adaptive_component_registered() {
143 return status;
144 }
145 let config_value = match c_str_to_json(config_json) {
146 Some(value) => value,
147 None => return NemoFlowStatus::InvalidJson,
148 };
149 let config: PluginConfig = match serde_json::from_value(config_value) {
150 Ok(config) => config,
151 Err(err) => {
152 set_last_error(&err.to_string());
153 return NemoFlowStatus::InvalidJson;
154 }
155 };
156 let report_json = match serde_json::to_value(validate_plugin_config(&config)) {
157 Ok(value) => value,
158 Err(err) => {
159 set_last_error(&err.to_string());
160 return NemoFlowStatus::Internal;
161 }
162 };
163 unsafe { *out_json = json_to_c_string(&report_json) };
164 NemoFlowStatus::Ok
165}
166
167#[unsafe(no_mangle)]
172pub unsafe extern "C" fn nemo_flow_initialize_plugins(
173 config_json: *const c_char,
174 out_json: *mut *mut c_char,
175) -> NemoFlowStatus {
176 clear_last_error();
177 if out_json.is_null() {
178 set_last_error("out_json pointer is null");
179 return NemoFlowStatus::NullPointer;
180 }
181 if let Err(status) = ensure_adaptive_component_registered() {
182 return status;
183 }
184 let config_value = match c_str_to_json(config_json) {
185 Some(value) => value,
186 None => return NemoFlowStatus::InvalidJson,
187 };
188 let config: PluginConfig = match serde_json::from_value(config_value) {
189 Ok(config) => config,
190 Err(err) => {
191 set_last_error(&err.to_string());
192 return NemoFlowStatus::InvalidJson;
193 }
194 };
195 let report = match tokio_runtime().block_on(initialize_plugins(config)) {
196 Ok(report) => report,
197 Err(err) => return status_from_plugin_error(&err),
198 };
199 let report_json = match serde_json::to_value(report) {
200 Ok(value) => value,
201 Err(err) => {
202 set_last_error(&err.to_string());
203 return NemoFlowStatus::Internal;
204 }
205 };
206 unsafe { *out_json = json_to_c_string(&report_json) };
207 NemoFlowStatus::Ok
208}
209
210#[unsafe(no_mangle)]
212pub extern "C" fn nemo_flow_clear_plugin_configuration() -> NemoFlowStatus {
213 clear_last_error();
214 match clear_plugin_configuration() {
215 Ok(()) => NemoFlowStatus::Ok,
216 Err(err) => status_from_plugin_error(&err),
217 }
218}
219
220#[unsafe(no_mangle)]
225pub unsafe extern "C" fn nemo_flow_active_plugin_report_json(
226 out_json: *mut *mut c_char,
227) -> NemoFlowStatus {
228 clear_last_error();
229 if out_json.is_null() {
230 set_last_error("out_json pointer is null");
231 return NemoFlowStatus::NullPointer;
232 }
233 let report_json = match serde_json::to_value(active_plugin_report()) {
234 Ok(value) => value,
235 Err(err) => {
236 set_last_error(&err.to_string());
237 return NemoFlowStatus::Internal;
238 }
239 };
240 unsafe { *out_json = json_to_c_string(&report_json) };
241 NemoFlowStatus::Ok
242}
243
244#[unsafe(no_mangle)]
249pub unsafe extern "C" fn nemo_flow_list_plugin_kinds_json(
250 out_json: *mut *mut c_char,
251) -> NemoFlowStatus {
252 clear_last_error();
253 if out_json.is_null() {
254 set_last_error("out_json pointer is null");
255 return NemoFlowStatus::NullPointer;
256 }
257 if let Err(status) = ensure_adaptive_component_registered() {
258 return status;
259 }
260 let kinds_json = match serde_json::to_value(list_plugin_kinds()) {
261 Ok(value) => value,
262 Err(err) => {
263 set_last_error(&err.to_string());
264 return NemoFlowStatus::Internal;
265 }
266 };
267 unsafe { *out_json = json_to_c_string(&kinds_json) };
268 NemoFlowStatus::Ok
269}
270
271#[unsafe(no_mangle)]
276pub unsafe extern "C" fn nemo_flow_register_plugin(
277 plugin_kind: *const c_char,
278 validate_cb: Option<NemoFlowPluginValidateCb>,
279 register_cb: NemoFlowPluginRegisterCb,
280 user_data: *mut libc::c_void,
281 free_fn: NemoFlowFreeFn,
282) -> NemoFlowStatus {
283 clear_last_error();
284 let plugin_kind = match c_str_to_string(plugin_kind) {
285 Ok(value) => value,
286 Err(status) => return status,
287 };
288
289 let plugin = Arc::new(FfiHostedPluginAdapter {
290 plugin_kind: plugin_kind.clone(),
291 validate_cb,
292 register_cb,
293 user_data: Arc::new(FfiHostedPluginUserData {
294 ptr: user_data,
295 free_fn,
296 }),
297 });
298 match register_plugin(plugin) {
299 Ok(()) => NemoFlowStatus::Ok,
300 Err(err) => status_from_plugin_error(&err),
301 }
302}
303
304#[unsafe(no_mangle)]
309pub unsafe extern "C" fn nemo_flow_deregister_plugin(plugin_kind: *const c_char) -> NemoFlowStatus {
310 clear_last_error();
311 let plugin_kind = match c_str_to_string(plugin_kind) {
312 Ok(value) => value,
313 Err(status) => return status,
314 };
315 if deregister_plugin(&plugin_kind) {
316 NemoFlowStatus::Ok
317 } else {
318 set_last_error(&format!("not found: plugin '{plugin_kind}'"));
319 NemoFlowStatus::NotFound
320 }
321}
322
323#[unsafe(no_mangle)]
329pub unsafe extern "C" fn nemo_flow_plugin_context_register_subscriber(
330 ctx: *mut FfiPluginContext,
331 name: *const c_char,
332 cb: NemoFlowEventSubscriberCb,
333 user_data: *mut libc::c_void,
334 free_fn: NemoFlowFreeFn,
335) -> NemoFlowStatus {
336 clear_last_error();
337 if ctx.is_null() {
338 set_last_error("plugin context is null");
339 return NemoFlowStatus::NullPointer;
340 }
341 let name = match c_str_to_string(name) {
342 Ok(value) => value,
343 Err(status) => return status,
344 };
345 let wrapped = wrap_event_subscriber(cb, user_data, free_fn);
346 match unsafe { &mut *((*ctx).0) }.register_subscriber(&name, wrapped) {
347 Ok(()) => NemoFlowStatus::Ok,
348 Err(err) => status_from_plugin_error(&err),
349 }
350}
351
352#[unsafe(no_mangle)]
358pub unsafe extern "C" fn nemo_flow_plugin_context_register_tool_sanitize_request_guardrail(
359 ctx: *mut FfiPluginContext,
360 name: *const c_char,
361 priority: i32,
362 cb: NemoFlowToolSanitizeCb,
363 user_data: *mut libc::c_void,
364 free_fn: NemoFlowFreeFn,
365) -> NemoFlowStatus {
366 clear_last_error();
367 if ctx.is_null() {
368 set_last_error("plugin context is null");
369 return NemoFlowStatus::NullPointer;
370 }
371 let name = match c_str_to_string(name) {
372 Ok(value) => value,
373 Err(status) => return status,
374 };
375 let wrapped = wrap_tool_sanitize_fn(cb, user_data, free_fn);
376 match unsafe { &mut *((*ctx).0) }
377 .register_tool_sanitize_request_guardrail(&name, priority, wrapped)
378 {
379 Ok(()) => NemoFlowStatus::Ok,
380 Err(err) => status_from_plugin_error(&err),
381 }
382}
383
384#[unsafe(no_mangle)]
390pub unsafe extern "C" fn nemo_flow_plugin_context_register_tool_sanitize_response_guardrail(
391 ctx: *mut FfiPluginContext,
392 name: *const c_char,
393 priority: i32,
394 cb: NemoFlowToolSanitizeCb,
395 user_data: *mut libc::c_void,
396 free_fn: NemoFlowFreeFn,
397) -> NemoFlowStatus {
398 clear_last_error();
399 if ctx.is_null() {
400 set_last_error("plugin context is null");
401 return NemoFlowStatus::NullPointer;
402 }
403 let name = match c_str_to_string(name) {
404 Ok(value) => value,
405 Err(status) => return status,
406 };
407 let wrapped = wrap_tool_sanitize_fn(cb, user_data, free_fn);
408 match unsafe { &mut *((*ctx).0) }
409 .register_tool_sanitize_response_guardrail(&name, priority, wrapped)
410 {
411 Ok(()) => NemoFlowStatus::Ok,
412 Err(err) => status_from_plugin_error(&err),
413 }
414}
415
416#[unsafe(no_mangle)]
422pub unsafe extern "C" fn nemo_flow_plugin_context_register_tool_conditional_execution_guardrail(
423 ctx: *mut FfiPluginContext,
424 name: *const c_char,
425 priority: i32,
426 cb: NemoFlowToolConditionalCb,
427 user_data: *mut libc::c_void,
428 free_fn: NemoFlowFreeFn,
429) -> NemoFlowStatus {
430 clear_last_error();
431 if ctx.is_null() {
432 set_last_error("plugin context is null");
433 return NemoFlowStatus::NullPointer;
434 }
435 let name = match c_str_to_string(name) {
436 Ok(value) => value,
437 Err(status) => return status,
438 };
439 let wrapped = wrap_tool_conditional_fn(cb, user_data, free_fn);
440 match unsafe { &mut *((*ctx).0) }
441 .register_tool_conditional_execution_guardrail(&name, priority, wrapped)
442 {
443 Ok(()) => NemoFlowStatus::Ok,
444 Err(err) => status_from_plugin_error(&err),
445 }
446}
447
448#[unsafe(no_mangle)]
454pub unsafe extern "C" fn nemo_flow_plugin_context_register_llm_sanitize_request_guardrail(
455 ctx: *mut FfiPluginContext,
456 name: *const c_char,
457 priority: i32,
458 cb: NemoFlowLlmRequestCb,
459 user_data: *mut libc::c_void,
460 free_fn: NemoFlowFreeFn,
461) -> NemoFlowStatus {
462 clear_last_error();
463 if ctx.is_null() {
464 set_last_error("plugin context is null");
465 return NemoFlowStatus::NullPointer;
466 }
467 let name = match c_str_to_string(name) {
468 Ok(value) => value,
469 Err(status) => return status,
470 };
471 let wrapped = wrap_llm_sanitize_request_fn(cb, user_data, free_fn);
472 match unsafe { &mut *((*ctx).0) }
473 .register_llm_sanitize_request_guardrail(&name, priority, wrapped)
474 {
475 Ok(()) => NemoFlowStatus::Ok,
476 Err(err) => status_from_plugin_error(&err),
477 }
478}
479
480#[unsafe(no_mangle)]
486pub unsafe extern "C" fn nemo_flow_plugin_context_register_llm_sanitize_response_guardrail(
487 ctx: *mut FfiPluginContext,
488 name: *const c_char,
489 priority: i32,
490 cb: NemoFlowJsonCb,
491 user_data: *mut libc::c_void,
492 free_fn: NemoFlowFreeFn,
493) -> NemoFlowStatus {
494 clear_last_error();
495 if ctx.is_null() {
496 set_last_error("plugin context is null");
497 return NemoFlowStatus::NullPointer;
498 }
499 let name = match c_str_to_string(name) {
500 Ok(value) => value,
501 Err(status) => return status,
502 };
503 let wrapped = wrap_llm_response_fn(cb, user_data, free_fn);
504 match unsafe { &mut *((*ctx).0) }
505 .register_llm_sanitize_response_guardrail(&name, priority, wrapped)
506 {
507 Ok(()) => NemoFlowStatus::Ok,
508 Err(err) => status_from_plugin_error(&err),
509 }
510}
511
512#[unsafe(no_mangle)]
518pub unsafe extern "C" fn nemo_flow_plugin_context_register_llm_conditional_execution_guardrail(
519 ctx: *mut FfiPluginContext,
520 name: *const c_char,
521 priority: i32,
522 cb: NemoFlowLlmConditionalCb,
523 user_data: *mut libc::c_void,
524 free_fn: NemoFlowFreeFn,
525) -> NemoFlowStatus {
526 clear_last_error();
527 if ctx.is_null() {
528 set_last_error("plugin context is null");
529 return NemoFlowStatus::NullPointer;
530 }
531 let name = match c_str_to_string(name) {
532 Ok(value) => value,
533 Err(status) => return status,
534 };
535 let wrapped = wrap_llm_conditional_fn(cb, user_data, free_fn);
536 match unsafe { &mut *((*ctx).0) }
537 .register_llm_conditional_execution_guardrail(&name, priority, wrapped)
538 {
539 Ok(()) => NemoFlowStatus::Ok,
540 Err(err) => status_from_plugin_error(&err),
541 }
542}
543
544#[unsafe(no_mangle)]
550pub unsafe extern "C" fn nemo_flow_plugin_context_register_llm_request_intercept(
551 ctx: *mut FfiPluginContext,
552 name: *const c_char,
553 priority: i32,
554 break_chain: bool,
555 cb: NemoFlowLlmRequestInterceptCb,
556 user_data: *mut libc::c_void,
557 free_fn: NemoFlowFreeFn,
558) -> NemoFlowStatus {
559 clear_last_error();
560 if ctx.is_null() {
561 set_last_error("plugin context is null");
562 return NemoFlowStatus::NullPointer;
563 }
564 let name = match c_str_to_string(name) {
565 Ok(value) => value,
566 Err(status) => return status,
567 };
568 let wrapped = wrap_llm_request_intercept_fn(cb, user_data, free_fn);
569 match unsafe { &mut *((*ctx).0) }.register_llm_request_intercept(
570 &name,
571 priority,
572 break_chain,
573 wrapped,
574 ) {
575 Ok(()) => NemoFlowStatus::Ok,
576 Err(err) => status_from_plugin_error(&err),
577 }
578}
579
580#[unsafe(no_mangle)]
586pub unsafe extern "C" fn nemo_flow_plugin_context_register_tool_request_intercept(
587 ctx: *mut FfiPluginContext,
588 name: *const c_char,
589 priority: i32,
590 break_chain: bool,
591 cb: NemoFlowToolSanitizeCb,
592 user_data: *mut libc::c_void,
593 free_fn: NemoFlowFreeFn,
594) -> NemoFlowStatus {
595 clear_last_error();
596 if ctx.is_null() {
597 set_last_error("plugin context is null");
598 return NemoFlowStatus::NullPointer;
599 }
600 let name = match c_str_to_string(name) {
601 Ok(value) => value,
602 Err(status) => return status,
603 };
604 let wrapped = wrap_tool_request_intercept_fn(cb, user_data, free_fn);
605 match unsafe { &mut *((*ctx).0) }.register_tool_request_intercept(
606 &name,
607 priority,
608 break_chain,
609 wrapped,
610 ) {
611 Ok(()) => NemoFlowStatus::Ok,
612 Err(err) => status_from_plugin_error(&err),
613 }
614}
615
616#[unsafe(no_mangle)]
622pub unsafe extern "C" fn nemo_flow_plugin_context_register_llm_execution_intercept(
623 ctx: *mut FfiPluginContext,
624 name: *const c_char,
625 priority: i32,
626 cb: NemoFlowLlmExecInterceptCb,
627 user_data: *mut libc::c_void,
628 free_fn: NemoFlowFreeFn,
629) -> NemoFlowStatus {
630 clear_last_error();
631 if ctx.is_null() {
632 set_last_error("plugin context is null");
633 return NemoFlowStatus::NullPointer;
634 }
635 let name = match c_str_to_string(name) {
636 Ok(value) => value,
637 Err(status) => return status,
638 };
639 let wrapped = wrap_llm_exec_intercept_fn(cb, user_data, free_fn);
640 match unsafe { &mut *((*ctx).0) }.register_llm_execution_intercept(&name, priority, wrapped) {
641 Ok(()) => NemoFlowStatus::Ok,
642 Err(err) => status_from_plugin_error(&err),
643 }
644}
645
646#[unsafe(no_mangle)]
652pub unsafe extern "C" fn nemo_flow_plugin_context_register_llm_stream_execution_intercept(
653 ctx: *mut FfiPluginContext,
654 name: *const c_char,
655 priority: i32,
656 cb: NemoFlowLlmExecInterceptCb,
657 user_data: *mut libc::c_void,
658 free_fn: NemoFlowFreeFn,
659) -> NemoFlowStatus {
660 clear_last_error();
661 if ctx.is_null() {
662 set_last_error("plugin context is null");
663 return NemoFlowStatus::NullPointer;
664 }
665 let name = match c_str_to_string(name) {
666 Ok(value) => value,
667 Err(status) => return status,
668 };
669 let wrapped = wrap_llm_stream_exec_intercept_fn(cb, user_data, free_fn);
670 match unsafe { &mut *((*ctx).0) }
671 .register_llm_stream_execution_intercept(&name, priority, wrapped)
672 {
673 Ok(()) => NemoFlowStatus::Ok,
674 Err(err) => status_from_plugin_error(&err),
675 }
676}
677
678#[unsafe(no_mangle)]
684pub unsafe extern "C" fn nemo_flow_plugin_context_register_tool_execution_intercept(
685 ctx: *mut FfiPluginContext,
686 name: *const c_char,
687 priority: i32,
688 cb: NemoFlowToolExecInterceptCb,
689 user_data: *mut libc::c_void,
690 free_fn: NemoFlowFreeFn,
691) -> NemoFlowStatus {
692 clear_last_error();
693 if ctx.is_null() {
694 set_last_error("plugin context is null");
695 return NemoFlowStatus::NullPointer;
696 }
697 let name = match c_str_to_string(name) {
698 Ok(value) => value,
699 Err(status) => return status,
700 };
701 let wrapped = wrap_tool_exec_intercept_fn(cb, user_data, free_fn);
702 match unsafe { &mut *((*ctx).0) }.register_tool_execution_intercept(&name, priority, wrapped) {
703 Ok(()) => NemoFlowStatus::Ok,
704 Err(err) => status_from_plugin_error(&err),
705 }
706}