use super::{
Arc, CStr, ConfigDiagnostic, DiagnosticLevel, FfiPluginContext, Future,
NemoFlowEventSubscriberCb, NemoFlowFreeFn, NemoFlowJsonCb, NemoFlowLlmConditionalCb,
NemoFlowLlmExecInterceptCb, NemoFlowLlmRequestCb, NemoFlowLlmRequestInterceptCb,
NemoFlowPluginRegisterCb, NemoFlowPluginValidateCb, NemoFlowStatus, NemoFlowToolConditionalCb,
NemoFlowToolExecInterceptCb, NemoFlowToolSanitizeCb, Pin, Plugin, PluginConfig, PluginError,
PluginRegistrationContext, active_plugin_report, c_char, c_str_to_json, c_str_to_string,
clear_last_error, clear_plugin_configuration, deregister_plugin, initialize_plugins,
json_to_c_string, last_error_message, list_plugin_kinds, nemo_flow_string_free,
register_adaptive_component, register_plugin, set_last_error, status_from_plugin_error,
tokio_runtime, validate_plugin_config, wrap_event_subscriber, wrap_llm_conditional_fn,
wrap_llm_exec_intercept_fn, wrap_llm_request_intercept_fn, wrap_llm_response_fn,
wrap_llm_sanitize_request_fn, wrap_llm_stream_exec_intercept_fn, wrap_tool_conditional_fn,
wrap_tool_exec_intercept_fn, wrap_tool_request_intercept_fn, wrap_tool_sanitize_fn,
};
struct FfiHostedPluginUserData {
ptr: *mut libc::c_void,
free_fn: NemoFlowFreeFn,
}
unsafe impl Send for FfiHostedPluginUserData {}
unsafe impl Sync for FfiHostedPluginUserData {}
impl Drop for FfiHostedPluginUserData {
fn drop(&mut self) {
if let Some(free_fn) = self.free_fn {
unsafe { free_fn(self.ptr) };
}
}
}
struct FfiHostedPluginAdapter {
plugin_kind: String,
validate_cb: Option<NemoFlowPluginValidateCb>,
register_cb: NemoFlowPluginRegisterCb,
user_data: Arc<FfiHostedPluginUserData>,
}
impl Plugin for FfiHostedPluginAdapter {
fn plugin_kind(&self) -> &str {
&self.plugin_kind
}
fn validate(
&self,
plugin_config: &serde_json::Map<String, serde_json::Value>,
) -> Vec<ConfigDiagnostic> {
let Some(validate_cb) = self.validate_cb else {
return vec![];
};
clear_last_error();
let plugin_config_json =
json_to_c_string(&serde_json::Value::Object(plugin_config.clone()));
let result_ptr = unsafe { validate_cb(self.user_data.ptr, plugin_config_json) };
unsafe { nemo_flow_string_free(plugin_config_json) };
if result_ptr.is_null() {
let message = last_error_message().unwrap_or_else(|| {
format!(
"plugin '{}' validate callback returned null",
self.plugin_kind
)
});
return vec![ConfigDiagnostic {
level: DiagnosticLevel::Error,
code: "plugin.validate_failed".to_string(),
component: Some(self.plugin_kind.clone()),
field: None,
message,
}];
}
let diagnostics = unsafe { CStr::from_ptr(result_ptr) }
.to_str()
.ok()
.and_then(|text| serde_json::from_str::<Vec<ConfigDiagnostic>>(text).ok());
unsafe { nemo_flow_string_free(result_ptr) };
diagnostics.unwrap_or_else(|| {
vec![ConfigDiagnostic {
level: DiagnosticLevel::Error,
code: "plugin.validate_failed".to_string(),
component: Some(self.plugin_kind.clone()),
field: None,
message: format!(
"plugin '{}' validate callback returned invalid diagnostics JSON",
self.plugin_kind
),
}]
})
}
fn register<'a>(
&'a self,
plugin_config: &serde_json::Map<String, serde_json::Value>,
ctx: &'a mut PluginRegistrationContext,
) -> Pin<Box<dyn Future<Output = std::result::Result<(), PluginError>> + Send + 'a>> {
let plugin_config = plugin_config.clone();
Box::pin(async move {
clear_last_error();
let plugin_config_json = json_to_c_string(&serde_json::Value::Object(plugin_config));
let mut ffi_ctx = FfiPluginContext(ctx as *mut _);
let status =
unsafe { (self.register_cb)(self.user_data.ptr, plugin_config_json, &mut ffi_ctx) };
unsafe { nemo_flow_string_free(plugin_config_json) };
if status == NemoFlowStatus::Ok {
Ok(())
} else if let Some(message) = last_error_message() {
Err(PluginError::RegistrationFailed(message))
} else {
Err(PluginError::RegistrationFailed(format!(
"plugin '{}' register callback failed with status {:?}",
self.plugin_kind, status
)))
}
})
}
}
fn ensure_adaptive_component_registered() -> std::result::Result<(), NemoFlowStatus> {
register_adaptive_component().map_err(|err| status_from_plugin_error(&err))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn nemo_flow_validate_plugin_config(
config_json: *const c_char,
out_json: *mut *mut c_char,
) -> NemoFlowStatus {
clear_last_error();
if out_json.is_null() {
set_last_error("out_json pointer is null");
return NemoFlowStatus::NullPointer;
}
if let Err(status) = ensure_adaptive_component_registered() {
return status;
}
let config_value = match c_str_to_json(config_json) {
Some(value) => value,
None => return NemoFlowStatus::InvalidJson,
};
let config: PluginConfig = match serde_json::from_value(config_value) {
Ok(config) => config,
Err(err) => {
set_last_error(&err.to_string());
return NemoFlowStatus::InvalidJson;
}
};
let report_json = match serde_json::to_value(validate_plugin_config(&config)) {
Ok(value) => value,
Err(err) => {
set_last_error(&err.to_string());
return NemoFlowStatus::Internal;
}
};
unsafe { *out_json = json_to_c_string(&report_json) };
NemoFlowStatus::Ok
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn nemo_flow_initialize_plugins(
config_json: *const c_char,
out_json: *mut *mut c_char,
) -> NemoFlowStatus {
clear_last_error();
if out_json.is_null() {
set_last_error("out_json pointer is null");
return NemoFlowStatus::NullPointer;
}
if let Err(status) = ensure_adaptive_component_registered() {
return status;
}
let config_value = match c_str_to_json(config_json) {
Some(value) => value,
None => return NemoFlowStatus::InvalidJson,
};
let config: PluginConfig = match serde_json::from_value(config_value) {
Ok(config) => config,
Err(err) => {
set_last_error(&err.to_string());
return NemoFlowStatus::InvalidJson;
}
};
let report = match tokio_runtime().block_on(initialize_plugins(config)) {
Ok(report) => report,
Err(err) => return status_from_plugin_error(&err),
};
let report_json = match serde_json::to_value(report) {
Ok(value) => value,
Err(err) => {
set_last_error(&err.to_string());
return NemoFlowStatus::Internal;
}
};
unsafe { *out_json = json_to_c_string(&report_json) };
NemoFlowStatus::Ok
}
#[unsafe(no_mangle)]
pub extern "C" fn nemo_flow_clear_plugin_configuration() -> NemoFlowStatus {
clear_last_error();
match clear_plugin_configuration() {
Ok(()) => NemoFlowStatus::Ok,
Err(err) => status_from_plugin_error(&err),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn nemo_flow_active_plugin_report_json(
out_json: *mut *mut c_char,
) -> NemoFlowStatus {
clear_last_error();
if out_json.is_null() {
set_last_error("out_json pointer is null");
return NemoFlowStatus::NullPointer;
}
let report_json = match serde_json::to_value(active_plugin_report()) {
Ok(value) => value,
Err(err) => {
set_last_error(&err.to_string());
return NemoFlowStatus::Internal;
}
};
unsafe { *out_json = json_to_c_string(&report_json) };
NemoFlowStatus::Ok
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn nemo_flow_list_plugin_kinds_json(
out_json: *mut *mut c_char,
) -> NemoFlowStatus {
clear_last_error();
if out_json.is_null() {
set_last_error("out_json pointer is null");
return NemoFlowStatus::NullPointer;
}
if let Err(status) = ensure_adaptive_component_registered() {
return status;
}
let kinds_json = match serde_json::to_value(list_plugin_kinds()) {
Ok(value) => value,
Err(err) => {
set_last_error(&err.to_string());
return NemoFlowStatus::Internal;
}
};
unsafe { *out_json = json_to_c_string(&kinds_json) };
NemoFlowStatus::Ok
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn nemo_flow_register_plugin(
plugin_kind: *const c_char,
validate_cb: Option<NemoFlowPluginValidateCb>,
register_cb: NemoFlowPluginRegisterCb,
user_data: *mut libc::c_void,
free_fn: NemoFlowFreeFn,
) -> NemoFlowStatus {
clear_last_error();
let plugin_kind = match c_str_to_string(plugin_kind) {
Ok(value) => value,
Err(status) => return status,
};
let plugin = Arc::new(FfiHostedPluginAdapter {
plugin_kind: plugin_kind.clone(),
validate_cb,
register_cb,
user_data: Arc::new(FfiHostedPluginUserData {
ptr: user_data,
free_fn,
}),
});
match register_plugin(plugin) {
Ok(()) => NemoFlowStatus::Ok,
Err(err) => status_from_plugin_error(&err),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn nemo_flow_deregister_plugin(plugin_kind: *const c_char) -> NemoFlowStatus {
clear_last_error();
let plugin_kind = match c_str_to_string(plugin_kind) {
Ok(value) => value,
Err(status) => return status,
};
if deregister_plugin(&plugin_kind) {
NemoFlowStatus::Ok
} else {
set_last_error(&format!("not found: plugin '{plugin_kind}'"));
NemoFlowStatus::NotFound
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn nemo_flow_plugin_context_register_subscriber(
ctx: *mut FfiPluginContext,
name: *const c_char,
cb: NemoFlowEventSubscriberCb,
user_data: *mut libc::c_void,
free_fn: NemoFlowFreeFn,
) -> NemoFlowStatus {
clear_last_error();
if ctx.is_null() {
set_last_error("plugin context is null");
return NemoFlowStatus::NullPointer;
}
let name = match c_str_to_string(name) {
Ok(value) => value,
Err(status) => return status,
};
let wrapped = wrap_event_subscriber(cb, user_data, free_fn);
match unsafe { &mut *((*ctx).0) }.register_subscriber(&name, wrapped) {
Ok(()) => NemoFlowStatus::Ok,
Err(err) => status_from_plugin_error(&err),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn nemo_flow_plugin_context_register_tool_sanitize_request_guardrail(
ctx: *mut FfiPluginContext,
name: *const c_char,
priority: i32,
cb: NemoFlowToolSanitizeCb,
user_data: *mut libc::c_void,
free_fn: NemoFlowFreeFn,
) -> NemoFlowStatus {
clear_last_error();
if ctx.is_null() {
set_last_error("plugin context is null");
return NemoFlowStatus::NullPointer;
}
let name = match c_str_to_string(name) {
Ok(value) => value,
Err(status) => return status,
};
let wrapped = wrap_tool_sanitize_fn(cb, user_data, free_fn);
match unsafe { &mut *((*ctx).0) }
.register_tool_sanitize_request_guardrail(&name, priority, wrapped)
{
Ok(()) => NemoFlowStatus::Ok,
Err(err) => status_from_plugin_error(&err),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn nemo_flow_plugin_context_register_tool_sanitize_response_guardrail(
ctx: *mut FfiPluginContext,
name: *const c_char,
priority: i32,
cb: NemoFlowToolSanitizeCb,
user_data: *mut libc::c_void,
free_fn: NemoFlowFreeFn,
) -> NemoFlowStatus {
clear_last_error();
if ctx.is_null() {
set_last_error("plugin context is null");
return NemoFlowStatus::NullPointer;
}
let name = match c_str_to_string(name) {
Ok(value) => value,
Err(status) => return status,
};
let wrapped = wrap_tool_sanitize_fn(cb, user_data, free_fn);
match unsafe { &mut *((*ctx).0) }
.register_tool_sanitize_response_guardrail(&name, priority, wrapped)
{
Ok(()) => NemoFlowStatus::Ok,
Err(err) => status_from_plugin_error(&err),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn nemo_flow_plugin_context_register_tool_conditional_execution_guardrail(
ctx: *mut FfiPluginContext,
name: *const c_char,
priority: i32,
cb: NemoFlowToolConditionalCb,
user_data: *mut libc::c_void,
free_fn: NemoFlowFreeFn,
) -> NemoFlowStatus {
clear_last_error();
if ctx.is_null() {
set_last_error("plugin context is null");
return NemoFlowStatus::NullPointer;
}
let name = match c_str_to_string(name) {
Ok(value) => value,
Err(status) => return status,
};
let wrapped = wrap_tool_conditional_fn(cb, user_data, free_fn);
match unsafe { &mut *((*ctx).0) }
.register_tool_conditional_execution_guardrail(&name, priority, wrapped)
{
Ok(()) => NemoFlowStatus::Ok,
Err(err) => status_from_plugin_error(&err),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn nemo_flow_plugin_context_register_llm_sanitize_request_guardrail(
ctx: *mut FfiPluginContext,
name: *const c_char,
priority: i32,
cb: NemoFlowLlmRequestCb,
user_data: *mut libc::c_void,
free_fn: NemoFlowFreeFn,
) -> NemoFlowStatus {
clear_last_error();
if ctx.is_null() {
set_last_error("plugin context is null");
return NemoFlowStatus::NullPointer;
}
let name = match c_str_to_string(name) {
Ok(value) => value,
Err(status) => return status,
};
let wrapped = wrap_llm_sanitize_request_fn(cb, user_data, free_fn);
match unsafe { &mut *((*ctx).0) }
.register_llm_sanitize_request_guardrail(&name, priority, wrapped)
{
Ok(()) => NemoFlowStatus::Ok,
Err(err) => status_from_plugin_error(&err),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn nemo_flow_plugin_context_register_llm_sanitize_response_guardrail(
ctx: *mut FfiPluginContext,
name: *const c_char,
priority: i32,
cb: NemoFlowJsonCb,
user_data: *mut libc::c_void,
free_fn: NemoFlowFreeFn,
) -> NemoFlowStatus {
clear_last_error();
if ctx.is_null() {
set_last_error("plugin context is null");
return NemoFlowStatus::NullPointer;
}
let name = match c_str_to_string(name) {
Ok(value) => value,
Err(status) => return status,
};
let wrapped = wrap_llm_response_fn(cb, user_data, free_fn);
match unsafe { &mut *((*ctx).0) }
.register_llm_sanitize_response_guardrail(&name, priority, wrapped)
{
Ok(()) => NemoFlowStatus::Ok,
Err(err) => status_from_plugin_error(&err),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn nemo_flow_plugin_context_register_llm_conditional_execution_guardrail(
ctx: *mut FfiPluginContext,
name: *const c_char,
priority: i32,
cb: NemoFlowLlmConditionalCb,
user_data: *mut libc::c_void,
free_fn: NemoFlowFreeFn,
) -> NemoFlowStatus {
clear_last_error();
if ctx.is_null() {
set_last_error("plugin context is null");
return NemoFlowStatus::NullPointer;
}
let name = match c_str_to_string(name) {
Ok(value) => value,
Err(status) => return status,
};
let wrapped = wrap_llm_conditional_fn(cb, user_data, free_fn);
match unsafe { &mut *((*ctx).0) }
.register_llm_conditional_execution_guardrail(&name, priority, wrapped)
{
Ok(()) => NemoFlowStatus::Ok,
Err(err) => status_from_plugin_error(&err),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn nemo_flow_plugin_context_register_llm_request_intercept(
ctx: *mut FfiPluginContext,
name: *const c_char,
priority: i32,
break_chain: bool,
cb: NemoFlowLlmRequestInterceptCb,
user_data: *mut libc::c_void,
free_fn: NemoFlowFreeFn,
) -> NemoFlowStatus {
clear_last_error();
if ctx.is_null() {
set_last_error("plugin context is null");
return NemoFlowStatus::NullPointer;
}
let name = match c_str_to_string(name) {
Ok(value) => value,
Err(status) => return status,
};
let wrapped = wrap_llm_request_intercept_fn(cb, user_data, free_fn);
match unsafe { &mut *((*ctx).0) }.register_llm_request_intercept(
&name,
priority,
break_chain,
wrapped,
) {
Ok(()) => NemoFlowStatus::Ok,
Err(err) => status_from_plugin_error(&err),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn nemo_flow_plugin_context_register_tool_request_intercept(
ctx: *mut FfiPluginContext,
name: *const c_char,
priority: i32,
break_chain: bool,
cb: NemoFlowToolSanitizeCb,
user_data: *mut libc::c_void,
free_fn: NemoFlowFreeFn,
) -> NemoFlowStatus {
clear_last_error();
if ctx.is_null() {
set_last_error("plugin context is null");
return NemoFlowStatus::NullPointer;
}
let name = match c_str_to_string(name) {
Ok(value) => value,
Err(status) => return status,
};
let wrapped = wrap_tool_request_intercept_fn(cb, user_data, free_fn);
match unsafe { &mut *((*ctx).0) }.register_tool_request_intercept(
&name,
priority,
break_chain,
wrapped,
) {
Ok(()) => NemoFlowStatus::Ok,
Err(err) => status_from_plugin_error(&err),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn nemo_flow_plugin_context_register_llm_execution_intercept(
ctx: *mut FfiPluginContext,
name: *const c_char,
priority: i32,
cb: NemoFlowLlmExecInterceptCb,
user_data: *mut libc::c_void,
free_fn: NemoFlowFreeFn,
) -> NemoFlowStatus {
clear_last_error();
if ctx.is_null() {
set_last_error("plugin context is null");
return NemoFlowStatus::NullPointer;
}
let name = match c_str_to_string(name) {
Ok(value) => value,
Err(status) => return status,
};
let wrapped = wrap_llm_exec_intercept_fn(cb, user_data, free_fn);
match unsafe { &mut *((*ctx).0) }.register_llm_execution_intercept(&name, priority, wrapped) {
Ok(()) => NemoFlowStatus::Ok,
Err(err) => status_from_plugin_error(&err),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn nemo_flow_plugin_context_register_llm_stream_execution_intercept(
ctx: *mut FfiPluginContext,
name: *const c_char,
priority: i32,
cb: NemoFlowLlmExecInterceptCb,
user_data: *mut libc::c_void,
free_fn: NemoFlowFreeFn,
) -> NemoFlowStatus {
clear_last_error();
if ctx.is_null() {
set_last_error("plugin context is null");
return NemoFlowStatus::NullPointer;
}
let name = match c_str_to_string(name) {
Ok(value) => value,
Err(status) => return status,
};
let wrapped = wrap_llm_stream_exec_intercept_fn(cb, user_data, free_fn);
match unsafe { &mut *((*ctx).0) }
.register_llm_stream_execution_intercept(&name, priority, wrapped)
{
Ok(()) => NemoFlowStatus::Ok,
Err(err) => status_from_plugin_error(&err),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn nemo_flow_plugin_context_register_tool_execution_intercept(
ctx: *mut FfiPluginContext,
name: *const c_char,
priority: i32,
cb: NemoFlowToolExecInterceptCb,
user_data: *mut libc::c_void,
free_fn: NemoFlowFreeFn,
) -> NemoFlowStatus {
clear_last_error();
if ctx.is_null() {
set_last_error("plugin context is null");
return NemoFlowStatus::NullPointer;
}
let name = match c_str_to_string(name) {
Ok(value) => value,
Err(status) => return status,
};
let wrapped = wrap_tool_exec_intercept_fn(cb, user_data, free_fn);
match unsafe { &mut *((*ctx).0) }.register_tool_execution_intercept(&name, priority, wrapped) {
Ok(()) => NemoFlowStatus::Ok,
Err(err) => status_from_plugin_error(&err),
}
}