use std::ffi::{CStr, CString, c_char, c_void};
use std::path::PathBuf;
use std::ptr;
use std::sync::atomic::Ordering;
use serde_json::Value;
use crate::ffi::{FFI_ENGINE_COUNTER, ffi_engine_registry, with_engine, with_engine_mut};
use crate::host::callbacks::{
RuntimeHostToolCallback, RuntimeHostToolRequest, RuntimeModelEmbedCallback,
RuntimeModelEmbedRequest, RuntimeModelEmbedResponse, RuntimeModelError, RuntimeModelErrorCode,
RuntimeModelLlmCallback, RuntimeModelLlmRequest, RuntimeModelLlmResponse,
set_host_tool_callback, set_model_embed_callback, set_model_llm_callback,
};
use crate::host::database::{
LuaRuntimeDatabaseCallbackMode, LuaRuntimeDatabaseProviderMode, RuntimeDatabaseBindingContext,
RuntimeDatabaseKind, RuntimeLanceDbProviderAction, RuntimeLanceDbProviderCallback,
RuntimeLanceDbProviderRequest, RuntimeLanceDbProviderResult, RuntimeSqliteProviderAction,
RuntimeSqliteProviderCallback, RuntimeSqliteProviderRequest, set_lancedb_provider_callback,
set_lancedb_provider_json_callback, set_sqlite_provider_callback,
set_sqlite_provider_json_callback,
};
use crate::runtime_context::RuntimeRequestContext;
use crate::runtime_help::{
RuntimeHelpDetail, RuntimeHelpNodeDescriptor, RuntimeSkillHelpDescriptor,
};
use crate::runtime_options::{
LuaInvocationContext, LuaRuntimeCapabilityOptions, LuaRuntimeHostOptions,
LuaRuntimeRunLuaPoolConfig, LuaRuntimeSpaceControllerOptions,
LuaRuntimeSpaceControllerProcessMode, RuntimeSkillRoot,
};
use crate::runtime_result::RuntimeHostResult;
use crate::skill::manager::{SkillInstallRequest, SkillManagementAuthority, SkillUninstallOptions};
use crate::skill::source::SkillInstallSourceType;
use crate::tool_cache::ToolCacheConfig;
use crate::{
LuaEngine, LuaEngineOptions, LuaVmPoolConfig, RuntimeEntryDescriptor,
RuntimeEntryParameterDescriptor, RuntimeInvocationResult, SkillApplyResult,
SkillUninstallResult,
};
const FFI_STATUS_OK: i32 = 0;
const FFI_STATUS_ERROR: i32 = 1;
const FFI_SOURCE_TYPE_ABSENT: i32 = -1;
const FFI_SOURCE_TYPE_GITHUB: i32 = 0;
const FFI_SOURCE_TYPE_URL: i32 = 1;
const FFI_PROVIDER_MODE_DYNAMIC_LIBRARY: i32 = 0;
const FFI_PROVIDER_MODE_HOST_CALLBACK: i32 = 1;
const FFI_PROVIDER_MODE_SPACE_CONTROLLER: i32 = 2;
const FFI_CALLBACK_MODE_STANDARD: i32 = 0;
const FFI_CALLBACK_MODE_JSON: i32 = 1;
const FFI_SPACE_CONTROLLER_PROCESS_MODE_SERVICE: i32 = 0;
const FFI_SPACE_CONTROLLER_PROCESS_MODE_MANAGED: i32 = 1;
const FFI_DATABASE_KIND_SQLITE: i32 = 0;
const FFI_DATABASE_KIND_LANCEDB: i32 = 1;
const FFI_SQLITE_PROVIDER_ACTION_EXECUTE_SCRIPT: i32 = 0;
const FFI_SQLITE_PROVIDER_ACTION_EXECUTE_BATCH: i32 = 1;
const FFI_SQLITE_PROVIDER_ACTION_QUERY_JSON: i32 = 2;
const FFI_SQLITE_PROVIDER_ACTION_QUERY_STREAM: i32 = 3;
const FFI_SQLITE_PROVIDER_ACTION_QUERY_STREAM_WAIT_METRICS: i32 = 4;
const FFI_SQLITE_PROVIDER_ACTION_QUERY_STREAM_CHUNK: i32 = 5;
const FFI_SQLITE_PROVIDER_ACTION_QUERY_STREAM_CLOSE: i32 = 6;
const FFI_SQLITE_PROVIDER_ACTION_TOKENIZE_TEXT: i32 = 7;
const FFI_SQLITE_PROVIDER_ACTION_UPSERT_CUSTOM_WORD: i32 = 8;
const FFI_SQLITE_PROVIDER_ACTION_REMOVE_CUSTOM_WORD: i32 = 9;
const FFI_SQLITE_PROVIDER_ACTION_LIST_CUSTOM_WORDS: i32 = 10;
const FFI_SQLITE_PROVIDER_ACTION_ENSURE_FTS_INDEX: i32 = 11;
const FFI_SQLITE_PROVIDER_ACTION_REBUILD_FTS_INDEX: i32 = 12;
const FFI_SQLITE_PROVIDER_ACTION_UPSERT_FTS_DOCUMENT: i32 = 13;
const FFI_SQLITE_PROVIDER_ACTION_DELETE_FTS_DOCUMENT: i32 = 14;
const FFI_SQLITE_PROVIDER_ACTION_SEARCH_FTS: i32 = 15;
const FFI_LANCEDB_PROVIDER_ACTION_CREATE_TABLE: i32 = 0;
const FFI_LANCEDB_PROVIDER_ACTION_VECTOR_UPSERT: i32 = 1;
const FFI_LANCEDB_PROVIDER_ACTION_VECTOR_SEARCH: i32 = 2;
const FFI_LANCEDB_PROVIDER_ACTION_DELETE: i32 = 3;
const FFI_LANCEDB_PROVIDER_ACTION_DROP_TABLE: i32 = 4;
const FFI_SKILL_AUTHORITY_SYSTEM: i32 = 0;
const FFI_SKILL_AUTHORITY_DELEGATED_TOOL: i32 = 1;
mod types;
pub use self::types::*;
fn set_error_out(error_out: *mut FfiOwnedBuffer, message: impl Into<String>) {
if error_out.is_null() {
return;
}
let text = message.into();
unsafe {
*error_out = alloc_owned_buffer_from_bytes(text.as_bytes());
}
}
fn clear_error_out(error_out: *mut FfiOwnedBuffer) {
clear_out_buffer(error_out);
}
fn clear_out_ptr<T>(value_out: *mut *mut T) {
if !value_out.is_null() {
unsafe { *value_out = std::ptr::null_mut() };
}
}
fn clear_out_buffer(value_out: *mut FfiOwnedBuffer) {
if !value_out.is_null() {
unsafe {
*value_out = FfiOwnedBuffer {
ptr: ptr::null_mut(),
len: 0,
}
};
}
}
fn clear_out_u64(value_out: *mut u64) {
if !value_out.is_null() {
unsafe { *value_out = 0 };
}
}
fn clear_out_u8(value_out: *mut u8) {
if !value_out.is_null() {
unsafe { *value_out = 0 };
}
}
fn alloc_c_string(value: impl AsRef<str>) -> *mut c_char {
CString::new(value.as_ref())
.unwrap_or_else(|_| CString::new("FFI string contains NUL byte").expect("static text"))
.into_raw()
}
fn alloc_owned_buffer_from_bytes(value: &[u8]) -> FfiOwnedBuffer {
if value.is_empty() {
return FfiOwnedBuffer {
ptr: ptr::null_mut(),
len: 0,
};
}
let mut bytes = value.to_vec();
let pointer = bytes.as_mut_ptr();
let len = bytes.len();
std::mem::forget(bytes);
FfiOwnedBuffer { ptr: pointer, len }
}
fn alloc_owned_buffer_from_string(value: impl AsRef<str>) -> FfiOwnedBuffer {
alloc_owned_buffer_from_bytes(value.as_ref().as_bytes())
}
fn to_cstring(value: impl AsRef<str>, field_name: &str) -> Result<CString, String> {
CString::new(value.as_ref()).map_err(|_| format!("{} contains interior NUL bytes", field_name))
}
fn alloc_optional_owned_buffer_from_string(value: Option<&str>) -> FfiOwnedBuffer {
value.map_or(
FfiOwnedBuffer {
ptr: ptr::null_mut(),
len: 0,
},
alloc_owned_buffer_from_string,
)
}
fn parse_required_string(value: *const c_char, field_name: &str) -> Result<String, String> {
if value.is_null() {
return Err(format!("{} must not be null", field_name));
}
let text = unsafe { CStr::from_ptr(value) }
.to_str()
.map_err(|error| format!("{} contains invalid UTF-8: {}", field_name, error))?;
if text.is_empty() {
return Err(format!("{} must not be empty", field_name));
}
Ok(text.to_string())
}
fn parse_required_string_allow_empty(
value: *const c_char,
field_name: &str,
) -> Result<String, String> {
if value.is_null() {
return Err(format!("{} must not be null", field_name));
}
unsafe { CStr::from_ptr(value) }
.to_str()
.map(|text| text.to_string())
.map_err(|error| format!("{} contains invalid UTF-8: {}", field_name, error))
}
fn parse_optional_string(value: *const c_char, field_name: &str) -> Result<Option<String>, String> {
if value.is_null() {
return Ok(None);
}
let text = unsafe { CStr::from_ptr(value) }
.to_str()
.map_err(|error| format!("{} contains invalid UTF-8: {}", field_name, error))?;
if text.is_empty() {
return Ok(None);
}
Ok(Some(text.to_string()))
}
fn parse_string_array(
items: *const *const c_char,
len: usize,
field_name: &str,
) -> Result<Vec<String>, String> {
if len == 0 {
return Ok(Vec::new());
}
if items.is_null() {
return Err(format!(
"{} items pointer must not be null when len > 0",
field_name
));
}
let slice = unsafe { std::slice::from_raw_parts(items, len) };
slice
.iter()
.enumerate()
.map(|(index, item)| parse_required_string(*item, &format!("{}[{}]", field_name, index)))
.collect()
}
fn parse_optional_borrowed_text(
value: &FfiBorrowedBuffer,
field_name: &str,
) -> Result<Option<String>, String> {
if value.len == 0 {
return Ok(None);
}
if value.ptr.is_null() {
return Err(format!(
"{} pointer must not be null when len > 0",
field_name
));
}
let bytes = unsafe { std::slice::from_raw_parts(value.ptr, value.len) };
let text = std::str::from_utf8(bytes)
.map_err(|error| format!("{} contains invalid UTF-8: {}", field_name, error))?;
if text.is_empty() {
return Ok(None);
}
Ok(Some(text.to_string()))
}
fn parse_json_value_or_empty_object_buffer(
value: &FfiBorrowedBuffer,
field_name: &str,
) -> Result<Value, String> {
match parse_optional_borrowed_text(value, field_name)? {
Some(text) => serde_json::from_str(&text)
.map_err(|error| format!("{} contains invalid JSON: {}", field_name, error)),
None => Ok(Value::Object(serde_json::Map::new())),
}
}
fn parse_request_context_buffer(
value: &FfiBorrowedBuffer,
field_name: &str,
) -> Result<Option<RuntimeRequestContext>, String> {
match parse_optional_borrowed_text(value, field_name)? {
Some(text) => serde_json::from_str(&text)
.map(Some)
.map_err(|error| format!("{} contains invalid JSON: {}", field_name, error)),
None => Ok(None),
}
}
fn run_engine_json_text_call<F>(
engine_id: u64,
request_json: &FfiBorrowedBuffer,
result_json_out: *mut FfiOwnedBuffer,
error_out: *mut FfiOwnedBuffer,
field_name: &str,
callback: F,
) -> i32
where
F: FnOnce(&LuaEngine, &str) -> Result<String, String>,
{
clear_error_out(error_out);
clear_out_buffer(result_json_out);
if result_json_out.is_null() {
return ffi_error_status(error_out, "result_json_out must not be null");
}
let request_json = match parse_optional_borrowed_text(request_json, field_name) {
Ok(Some(text)) => text,
Ok(None) => return ffi_error_status(error_out, format!("{field_name} must not be empty")),
Err(error) => return ffi_error_status(error_out, error),
};
match with_engine(engine_id, |engine| callback(engine, &request_json)) {
Ok(result_json) => {
unsafe { *result_json_out = alloc_owned_buffer_from_string(result_json) };
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
fn parse_cache_config(value: *const FfiToolCacheConfig) -> Option<ToolCacheConfig> {
if value.is_null() {
None
} else {
let config = unsafe { &*value };
Some(ToolCacheConfig {
max_entries: config.max_entries,
default_ttl_secs: config.default_ttl_secs,
max_ttl_secs: config.max_ttl_secs,
})
}
}
fn parse_runlua_pool_config(
value: *const FfiLuaVmPoolConfig,
) -> Option<LuaRuntimeRunLuaPoolConfig> {
if value.is_null() {
None
} else {
let config = unsafe { &*value };
Some(LuaRuntimeRunLuaPoolConfig {
min_size: config.min_size,
max_size: config.max_size,
idle_ttl_secs: config.idle_ttl_secs,
})
}
}
fn parse_host_options(value: &FfiLuaRuntimeHostOptions) -> Result<LuaRuntimeHostOptions, String> {
Ok(LuaRuntimeHostOptions {
temp_dir: parse_optional_string(value.temp_dir, "temp_dir")?.map(PathBuf::from),
resources_dir: parse_optional_string(value.resources_dir, "resources_dir")?
.map(PathBuf::from),
lua_packages_dir: parse_optional_string(value.lua_packages_dir, "lua_packages_dir")?
.map(PathBuf::from),
host_provided_tool_root: parse_optional_string(
value.host_provided_tool_root,
"host_provided_tool_root",
)?
.map(PathBuf::from),
host_provided_lua_root: parse_optional_string(
value.host_provided_lua_root,
"host_provided_lua_root",
)?
.map(PathBuf::from),
host_provided_ffi_root: parse_optional_string(
value.host_provided_ffi_root,
"host_provided_ffi_root",
)?
.map(PathBuf::from),
system_lua_lib_dir: parse_optional_string(value.system_lua_lib_dir, "system_lua_lib_dir")?
.map(PathBuf::from),
download_cache_root: parse_optional_string(
value.download_cache_root,
"download_cache_root",
)?
.map(PathBuf::from),
dependency_dir_name: parse_required_string(
value.dependency_dir_name,
"dependency_dir_name",
)?,
state_dir_name: parse_required_string(value.state_dir_name, "state_dir_name")?,
database_dir_name: parse_required_string(value.database_dir_name, "database_dir_name")?,
skill_config_file_path: parse_optional_string(
value.skill_config_file_path,
"skill_config_file_path",
)?
.map(PathBuf::from),
allow_network_download: value.allow_network_download != 0,
github_base_url: parse_optional_string(value.github_base_url, "github_base_url")?,
github_api_base_url: parse_optional_string(
value.github_api_base_url,
"github_api_base_url",
)?,
default_text_encoding: parse_optional_string(
value.default_text_encoding,
"default_text_encoding",
)?,
sqlite_library_path: parse_optional_string(
value.sqlite_library_path,
"sqlite_library_path",
)?
.map(PathBuf::from),
sqlite_provider_mode: parse_provider_mode(
value.sqlite_provider_mode,
"sqlite_provider_mode",
)?,
sqlite_callback_mode: parse_callback_mode(
value.sqlite_callback_mode,
"sqlite_callback_mode",
)?,
lancedb_library_path: parse_optional_string(
value.lancedb_library_path,
"lancedb_library_path",
)?
.map(PathBuf::from),
lancedb_provider_mode: parse_provider_mode(
value.lancedb_provider_mode,
"lancedb_provider_mode",
)?,
lancedb_callback_mode: parse_callback_mode(
value.lancedb_callback_mode,
"lancedb_callback_mode",
)?,
space_controller: LuaRuntimeSpaceControllerOptions {
endpoint: parse_optional_string(
value.space_controller_endpoint,
"space_controller_endpoint",
)?,
auto_spawn: value.space_controller_auto_spawn != 0,
executable_path: parse_optional_string(
value.space_controller_executable_path,
"space_controller_executable_path",
)?
.map(PathBuf::from),
process_mode: parse_space_controller_process_mode(
value.space_controller_process_mode,
"space_controller_process_mode",
)?,
..LuaRuntimeSpaceControllerOptions::default()
},
cache_config: parse_cache_config(value.cache_config),
runlua_pool_config: parse_runlua_pool_config(value.runlua_pool_config),
reserved_entry_names: parse_string_array(
value.reserved_entry_names,
value.reserved_entry_names_len,
"reserved_entry_names",
)?,
ignored_skill_ids: parse_string_array(
value.ignored_skill_ids,
value.ignored_skill_ids_len,
"ignored_skill_ids",
)?,
capabilities: LuaRuntimeCapabilityOptions {
enable_skill_management_bridge: value.enable_skill_management_bridge != 0,
enable_managed_io_compat: value.disable_managed_io_compat == 0,
},
})
}
fn parse_provider_mode(
value: i32,
field_name: &str,
) -> Result<LuaRuntimeDatabaseProviderMode, String> {
match value {
FFI_PROVIDER_MODE_DYNAMIC_LIBRARY => Ok(LuaRuntimeDatabaseProviderMode::DynamicLibrary),
FFI_PROVIDER_MODE_HOST_CALLBACK => Ok(LuaRuntimeDatabaseProviderMode::HostCallback),
FFI_PROVIDER_MODE_SPACE_CONTROLLER => Ok(LuaRuntimeDatabaseProviderMode::SpaceController),
_ => Err(format!("Unsupported {} value '{}'", field_name, value)),
}
}
fn parse_skill_management_authority(
value: i32,
field_name: &str,
) -> Result<SkillManagementAuthority, String> {
match value {
FFI_SKILL_AUTHORITY_SYSTEM => Ok(SkillManagementAuthority::System),
FFI_SKILL_AUTHORITY_DELEGATED_TOOL => Ok(SkillManagementAuthority::DelegatedTool),
other => Err(format!(
"{} must be 0 (system) or 1 (delegated_tool); got {}",
field_name, other
)),
}
}
fn parse_callback_mode(
value: i32,
field_name: &str,
) -> Result<LuaRuntimeDatabaseCallbackMode, String> {
match value {
FFI_CALLBACK_MODE_STANDARD => Ok(LuaRuntimeDatabaseCallbackMode::Standard),
FFI_CALLBACK_MODE_JSON => Ok(LuaRuntimeDatabaseCallbackMode::Json),
_ => Err(format!("Unsupported {} value '{}'", field_name, value)),
}
}
fn parse_space_controller_process_mode(
value: i32,
field_name: &str,
) -> Result<LuaRuntimeSpaceControllerProcessMode, String> {
match value {
FFI_SPACE_CONTROLLER_PROCESS_MODE_SERVICE => {
Ok(LuaRuntimeSpaceControllerProcessMode::Service)
}
FFI_SPACE_CONTROLLER_PROCESS_MODE_MANAGED => {
Ok(LuaRuntimeSpaceControllerProcessMode::Managed)
}
_ => Err(format!("Unsupported {} value '{}'", field_name, value)),
}
}
fn parse_engine_options(value: &FfiLuaEngineOptions) -> Result<LuaEngineOptions, String> {
Ok(LuaEngineOptions::new(
LuaVmPoolConfig {
min_size: value.pool.min_size,
max_size: value.pool.max_size,
idle_ttl_secs: value.pool.idle_ttl_secs,
},
parse_host_options(&value.host)?,
))
}
fn parse_skill_roots(
skill_roots: *const FfiRuntimeSkillRoot,
skill_roots_len: usize,
) -> Result<Vec<RuntimeSkillRoot>, String> {
if skill_roots_len == 0 {
return Ok(Vec::new());
}
if skill_roots.is_null() {
return Err("skill_roots pointer must not be null when len > 0".to_string());
}
let roots = unsafe { std::slice::from_raw_parts(skill_roots, skill_roots_len) };
roots
.iter()
.enumerate()
.map(|(index, root)| {
Ok(RuntimeSkillRoot {
name: parse_required_string(root.name, &format!("skill_roots[{}].name", index))?,
skills_dir: PathBuf::from(parse_required_string(
root.skills_dir,
&format!("skill_roots[{}].skills_dir", index),
)?),
})
})
.collect()
}
fn parse_invocation_context(
value: *const FfiLuaInvocationContext,
) -> Result<Option<LuaInvocationContext>, String> {
if value.is_null() {
return Ok(None);
}
let context = unsafe { &*value };
Ok(Some(LuaInvocationContext::new(
parse_request_context_buffer(&context.request_context_json, "request_context_json")?,
parse_json_value_or_empty_object_buffer(&context.client_budget_json, "client_budget_json")?,
parse_json_value_or_empty_object_buffer(&context.tool_config_json, "tool_config_json")?,
)))
}
fn parse_source_type(value: i32) -> Result<SkillInstallSourceType, String> {
match value {
FFI_SOURCE_TYPE_GITHUB => Ok(SkillInstallSourceType::Github),
FFI_SOURCE_TYPE_URL => Ok(SkillInstallSourceType::Url),
_ => Err(format!("Unsupported source_type '{}'", value)),
}
}
fn parse_install_request(value: &FfiSkillInstallRequest) -> Result<SkillInstallRequest, String> {
Ok(SkillInstallRequest {
skill_id: parse_optional_string(value.skill_id, "skill_id")?,
source: parse_optional_string(value.source, "source")?,
source_type: parse_source_type(value.source_type)?,
})
}
fn parse_uninstall_options(value: Option<&FfiSkillUninstallOptions>) -> SkillUninstallOptions {
match value {
Some(value) => SkillUninstallOptions {
remove_sqlite: value.remove_sqlite != 0,
remove_lancedb: value.remove_lancedb != 0,
},
None => SkillUninstallOptions::default(),
}
}
fn alloc_string_array(values: &[String]) -> FfiStringArray {
let mut items: Vec<FfiOwnedBuffer> =
values.iter().map(alloc_owned_buffer_from_string).collect();
let result = FfiStringArray {
items: items.as_mut_ptr(),
len: items.len(),
};
std::mem::forget(items);
result
}
fn alloc_entry_parameter_descriptor(
value: &RuntimeEntryParameterDescriptor,
) -> FfiRuntimeEntryParameterDescriptor {
FfiRuntimeEntryParameterDescriptor {
name: alloc_owned_buffer_from_string(&value.name),
param_type: alloc_owned_buffer_from_string(&value.param_type),
description: alloc_owned_buffer_from_string(&value.description),
required: u8::from(value.required),
}
}
fn alloc_entry_descriptor(value: &RuntimeEntryDescriptor) -> FfiRuntimeEntryDescriptor {
let mut parameters: Vec<FfiRuntimeEntryParameterDescriptor> = value
.parameters
.iter()
.map(alloc_entry_parameter_descriptor)
.collect();
let parameters_ptr = parameters.as_mut_ptr();
let parameters_len = parameters.len();
std::mem::forget(parameters);
FfiRuntimeEntryDescriptor {
canonical_name: alloc_owned_buffer_from_string(&value.canonical_name),
skill_id: alloc_owned_buffer_from_string(&value.skill_id),
local_name: alloc_owned_buffer_from_string(&value.local_name),
root_name: alloc_owned_buffer_from_string(&value.root_name),
skill_dir: alloc_owned_buffer_from_string(&value.skill_dir),
description: alloc_owned_buffer_from_string(&value.description),
parameters: parameters_ptr,
parameters_len,
}
}
fn alloc_help_node_descriptor(value: &RuntimeHelpNodeDescriptor) -> FfiRuntimeHelpNodeDescriptor {
let related_entries = alloc_string_array(&value.related_entries);
FfiRuntimeHelpNodeDescriptor {
flow_name: alloc_owned_buffer_from_string(&value.flow_name),
description: alloc_owned_buffer_from_string(&value.description),
related_entries: related_entries.items,
related_entries_len: related_entries.len,
is_main: u8::from(value.is_main),
}
}
fn alloc_help_descriptor(value: &RuntimeSkillHelpDescriptor) -> FfiRuntimeSkillHelpDescriptor {
let mut flows: Vec<FfiRuntimeHelpNodeDescriptor> =
value.flows.iter().map(alloc_help_node_descriptor).collect();
let flows_ptr = flows.as_mut_ptr();
let flows_len = flows.len();
std::mem::forget(flows);
FfiRuntimeSkillHelpDescriptor {
skill_id: alloc_owned_buffer_from_string(&value.skill_id),
skill_name: alloc_owned_buffer_from_string(&value.skill_name),
skill_version: alloc_owned_buffer_from_string(&value.skill_version),
root_name: alloc_owned_buffer_from_string(&value.root_name),
skill_dir: alloc_owned_buffer_from_string(&value.skill_dir),
main: alloc_help_node_descriptor(&value.main),
flows: flows_ptr,
flows_len,
}
}
fn alloc_help_detail(value: &RuntimeHelpDetail) -> FfiRuntimeHelpDetail {
let related_entries = alloc_string_array(&value.related_entries);
FfiRuntimeHelpDetail {
skill_id: alloc_owned_buffer_from_string(&value.skill_id),
skill_name: alloc_owned_buffer_from_string(&value.skill_name),
skill_version: alloc_owned_buffer_from_string(&value.skill_version),
root_name: alloc_owned_buffer_from_string(&value.root_name),
skill_dir: alloc_owned_buffer_from_string(&value.skill_dir),
flow_name: alloc_owned_buffer_from_string(&value.flow_name),
description: alloc_owned_buffer_from_string(&value.description),
related_entries: related_entries.items,
related_entries_len: related_entries.len,
is_main: u8::from(value.is_main),
content_type: alloc_owned_buffer_from_string(&value.content_type),
content: alloc_owned_buffer_from_string(&value.content),
}
}
fn alloc_host_result(value: &RuntimeHostResult) -> Result<FfiRuntimeHostResult, String> {
let payload_json = serde_json::to_string(&value.payload)
.map_err(|error| format!("Failed to serialize host_result payload: {}", error))?;
Ok(FfiRuntimeHostResult {
kind: alloc_owned_buffer_from_string(&value.kind),
payload_json: alloc_owned_buffer_from_string(&payload_json),
payload_bytes: payload_json.len(),
})
}
fn alloc_invocation_result(value: &RuntimeInvocationResult) -> FfiRuntimeInvocationResult {
let overflow_mode = match value.overflow_mode {
None => 0,
Some(crate::ToolOverflowMode::Truncate) => 1,
Some(crate::ToolOverflowMode::Page) => 2,
};
let host_result = value
.host_result
.as_ref()
.and_then(|host_result| alloc_host_result(host_result).ok())
.map(|host_result| Box::into_raw(Box::new(host_result)))
.unwrap_or(ptr::null_mut());
FfiRuntimeInvocationResult {
content: alloc_owned_buffer_from_string(&value.content),
overflow_mode,
template_hint: alloc_optional_owned_buffer_from_string(value.template_hint.as_deref()),
content_bytes: value.content_bytes,
content_lines: value.content_lines,
host_result,
}
}
fn alloc_skill_apply_result(value: &SkillApplyResult) -> FfiSkillApplyResult {
let source_type = match value.source_type {
None => FFI_SOURCE_TYPE_ABSENT,
Some(SkillInstallSourceType::Github) => FFI_SOURCE_TYPE_GITHUB,
Some(SkillInstallSourceType::Url) => FFI_SOURCE_TYPE_URL,
};
FfiSkillApplyResult {
skill_id: alloc_owned_buffer_from_string(&value.skill_id),
status: alloc_owned_buffer_from_string(&value.status),
message: alloc_owned_buffer_from_string(&value.message),
version: alloc_optional_owned_buffer_from_string(value.version.as_deref()),
source_type,
source_locator: alloc_optional_owned_buffer_from_string(value.source_locator.as_deref()),
}
}
fn alloc_skill_uninstall_result(value: &SkillUninstallResult) -> FfiSkillUninstallResult {
FfiSkillUninstallResult {
skill_id: alloc_owned_buffer_from_string(&value.skill_id),
skill_removed: u8::from(value.skill_removed),
sqlite_removed: u8::from(value.sqlite_removed),
lancedb_removed: u8::from(value.lancedb_removed),
sqlite_retained: u8::from(value.sqlite_retained),
lancedb_retained: u8::from(value.lancedb_retained),
message: alloc_owned_buffer_from_string(&value.message),
}
}
struct OwnedFfiRuntimeDatabaseBindingContext {
space_label: CString,
skill_id: CString,
binding_tag: CString,
root_name: CString,
space_root: CString,
skill_dir: CString,
skill_dir_name: CString,
default_database_path: CString,
ffi: FfiRuntimeDatabaseBindingContext,
}
impl OwnedFfiRuntimeDatabaseBindingContext {
fn from_runtime(value: &RuntimeDatabaseBindingContext) -> Result<Self, String> {
let space_label = to_cstring(&value.space_label, "space_label")?;
let skill_id = to_cstring(&value.skill_id, "skill_id")?;
let binding_tag = to_cstring(&value.binding_tag, "binding_tag")?;
let root_name = to_cstring(&value.root_name, "root_name")?;
let space_root = to_cstring(&value.space_root, "space_root")?;
let skill_dir = to_cstring(&value.skill_dir, "skill_dir")?;
let skill_dir_name = to_cstring(&value.skill_dir_name, "skill_dir_name")?;
let default_database_path =
to_cstring(&value.default_database_path, "default_database_path")?;
let database_kind = ffi_database_kind_code(value.database_kind);
let ffi = FfiRuntimeDatabaseBindingContext {
space_label: space_label.as_ptr(),
skill_id: skill_id.as_ptr(),
binding_tag: binding_tag.as_ptr(),
root_name: root_name.as_ptr(),
space_root: space_root.as_ptr(),
skill_dir: skill_dir.as_ptr(),
skill_dir_name: skill_dir_name.as_ptr(),
database_kind,
default_database_path: default_database_path.as_ptr(),
};
Ok(Self {
space_label,
skill_id,
binding_tag,
root_name,
space_root,
skill_dir,
skill_dir_name,
default_database_path,
ffi,
})
}
fn as_ffi(&self) -> FfiRuntimeDatabaseBindingContext {
FfiRuntimeDatabaseBindingContext {
space_label: self.space_label.as_ptr(),
skill_id: self.skill_id.as_ptr(),
binding_tag: self.binding_tag.as_ptr(),
root_name: self.root_name.as_ptr(),
space_root: self.space_root.as_ptr(),
skill_dir: self.skill_dir.as_ptr(),
skill_dir_name: self.skill_dir_name.as_ptr(),
database_kind: self.ffi.database_kind,
default_database_path: self.default_database_path.as_ptr(),
}
}
}
fn borrowed_buffer_from_bytes(bytes: &[u8]) -> FfiBorrowedBuffer {
if bytes.is_empty() {
return FfiBorrowedBuffer {
ptr: ptr::null(),
len: 0,
};
}
FfiBorrowedBuffer {
ptr: bytes.as_ptr(),
len: bytes.len(),
}
}
struct OwnedFfiSqliteProviderRequest {
_binding: OwnedFfiRuntimeDatabaseBindingContext,
_input_json: Vec<u8>,
ffi: FfiSqliteProviderRequest,
}
impl OwnedFfiSqliteProviderRequest {
fn from_runtime(value: &RuntimeSqliteProviderRequest) -> Result<Self, String> {
let binding = OwnedFfiRuntimeDatabaseBindingContext::from_runtime(&value.binding)?;
let input_json = serde_json::to_vec(&value.input)
.map_err(|error| format!("failed to encode sqlite input json: {}", error))?;
let ffi = FfiSqliteProviderRequest {
action: ffi_sqlite_provider_action_code(&value.action),
binding: binding.as_ffi(),
input_json: borrowed_buffer_from_bytes(&input_json),
};
Ok(Self {
_binding: binding,
_input_json: input_json,
ffi,
})
}
fn as_ptr(&self) -> *const FfiSqliteProviderRequest {
&self.ffi
}
}
struct OwnedFfiLanceDbProviderRequest {
_binding: OwnedFfiRuntimeDatabaseBindingContext,
_input_json: Vec<u8>,
ffi: FfiLanceDbProviderRequest,
}
impl OwnedFfiLanceDbProviderRequest {
fn from_runtime(value: &RuntimeLanceDbProviderRequest) -> Result<Self, String> {
let binding = OwnedFfiRuntimeDatabaseBindingContext::from_runtime(&value.binding)?;
let input_json = serde_json::to_vec(&value.input)
.map_err(|error| format!("failed to encode lancedb input json: {}", error))?;
let ffi = FfiLanceDbProviderRequest {
action: ffi_lancedb_provider_action_code(&value.action),
binding: binding.as_ffi(),
input_json: borrowed_buffer_from_bytes(&input_json),
};
Ok(Self {
_binding: binding,
_input_json: input_json,
ffi,
})
}
fn as_ptr(&self) -> *const FfiLanceDbProviderRequest {
&self.ffi
}
}
fn ffi_database_kind_code(value: RuntimeDatabaseKind) -> i32 {
match value {
RuntimeDatabaseKind::Sqlite => FFI_DATABASE_KIND_SQLITE,
RuntimeDatabaseKind::LanceDb => FFI_DATABASE_KIND_LANCEDB,
}
}
fn ffi_sqlite_provider_action_code(value: &RuntimeSqliteProviderAction) -> i32 {
match value {
RuntimeSqliteProviderAction::ExecuteScript => FFI_SQLITE_PROVIDER_ACTION_EXECUTE_SCRIPT,
RuntimeSqliteProviderAction::ExecuteBatch => FFI_SQLITE_PROVIDER_ACTION_EXECUTE_BATCH,
RuntimeSqliteProviderAction::QueryJson => FFI_SQLITE_PROVIDER_ACTION_QUERY_JSON,
RuntimeSqliteProviderAction::QueryStream => FFI_SQLITE_PROVIDER_ACTION_QUERY_STREAM,
RuntimeSqliteProviderAction::QueryStreamWaitMetrics => {
FFI_SQLITE_PROVIDER_ACTION_QUERY_STREAM_WAIT_METRICS
}
RuntimeSqliteProviderAction::QueryStreamChunk => {
FFI_SQLITE_PROVIDER_ACTION_QUERY_STREAM_CHUNK
}
RuntimeSqliteProviderAction::QueryStreamClose => {
FFI_SQLITE_PROVIDER_ACTION_QUERY_STREAM_CLOSE
}
RuntimeSqliteProviderAction::TokenizeText => FFI_SQLITE_PROVIDER_ACTION_TOKENIZE_TEXT,
RuntimeSqliteProviderAction::UpsertCustomWord => {
FFI_SQLITE_PROVIDER_ACTION_UPSERT_CUSTOM_WORD
}
RuntimeSqliteProviderAction::RemoveCustomWord => {
FFI_SQLITE_PROVIDER_ACTION_REMOVE_CUSTOM_WORD
}
RuntimeSqliteProviderAction::ListCustomWords => {
FFI_SQLITE_PROVIDER_ACTION_LIST_CUSTOM_WORDS
}
RuntimeSqliteProviderAction::EnsureFtsIndex => FFI_SQLITE_PROVIDER_ACTION_ENSURE_FTS_INDEX,
RuntimeSqliteProviderAction::RebuildFtsIndex => {
FFI_SQLITE_PROVIDER_ACTION_REBUILD_FTS_INDEX
}
RuntimeSqliteProviderAction::UpsertFtsDocument => {
FFI_SQLITE_PROVIDER_ACTION_UPSERT_FTS_DOCUMENT
}
RuntimeSqliteProviderAction::DeleteFtsDocument => {
FFI_SQLITE_PROVIDER_ACTION_DELETE_FTS_DOCUMENT
}
RuntimeSqliteProviderAction::SearchFts => FFI_SQLITE_PROVIDER_ACTION_SEARCH_FTS,
}
}
fn ffi_lancedb_provider_action_code(value: &RuntimeLanceDbProviderAction) -> i32 {
match value {
RuntimeLanceDbProviderAction::CreateTable => FFI_LANCEDB_PROVIDER_ACTION_CREATE_TABLE,
RuntimeLanceDbProviderAction::VectorUpsert => FFI_LANCEDB_PROVIDER_ACTION_VECTOR_UPSERT,
RuntimeLanceDbProviderAction::VectorSearch => FFI_LANCEDB_PROVIDER_ACTION_VECTOR_SEARCH,
RuntimeLanceDbProviderAction::Delete => FFI_LANCEDB_PROVIDER_ACTION_DELETE,
RuntimeLanceDbProviderAction::DropTable => FFI_LANCEDB_PROVIDER_ACTION_DROP_TABLE,
}
}
fn invoke_json_provider_callback(
callback: FfiJsonProviderCallback,
user_data: usize,
request_json: &str,
) -> Result<String, String> {
let request_bytes = request_json.as_bytes();
let mut response_out = FfiOwnedBuffer {
ptr: ptr::null_mut(),
len: 0,
};
let mut error_out = FfiOwnedBuffer {
ptr: ptr::null_mut(),
len: 0,
};
let status = unsafe {
callback(
FfiBorrowedBuffer {
ptr: request_bytes.as_ptr(),
len: request_bytes.len(),
},
user_data as *mut c_void,
&mut response_out,
&mut error_out,
)
};
let callback_error =
take_optional_owned_ffi_string_buffer(error_out, "json host provider callback error_out")?;
if status != FFI_STATUS_OK {
unsafe { free_ffi_bytes(response_out.ptr, response_out.len) };
return Err(callback_error.unwrap_or_else(|| {
"json host provider callback returned failure without error message".to_string()
}));
}
let response = take_optional_owned_ffi_string_buffer(
response_out,
"json host provider callback response_out",
)?
.ok_or_else(|| "json host provider callback returned empty response_out".to_string())?;
if let Some(message) = callback_error {
if !message.is_empty() {
return Err(format!(
"json host provider callback returned unexpected error text on success: {}",
message
));
}
}
Ok(response)
}
fn runtime_model_callback_internal_error(message: impl Into<String>) -> RuntimeModelError {
RuntimeModelError {
code: RuntimeModelErrorCode::InternalError,
message: message.into(),
provider_message: None,
provider_code: None,
provider_status: None,
}
}
fn runtime_model_error_string_field(
object: &serde_json::Map<String, Value>,
field_name: &str,
) -> Option<String> {
object
.get(field_name)
.and_then(Value::as_str)
.map(str::to_string)
}
fn runtime_model_error_status_field(
object: &serde_json::Map<String, Value>,
field_name: &str,
) -> Option<u16> {
object
.get(field_name)
.and_then(Value::as_u64)
.and_then(|value| u16::try_from(value).ok())
}
fn runtime_model_error_object(value: &Value) -> Option<&serde_json::Map<String, Value>> {
value.get("error").and_then(Value::as_object).or_else(|| {
value.as_object().and_then(|object| {
if object.contains_key("code") || object.contains_key("message") {
Some(object)
} else {
None
}
})
})
}
fn runtime_model_error_from_json_value(value: &Value) -> Option<RuntimeModelError> {
let object = runtime_model_error_object(value)?;
let code = runtime_model_error_string_field(object, "code")
.map(|value| RuntimeModelErrorCode::from_code_str(&value))
.unwrap_or(RuntimeModelErrorCode::InternalError);
let message = runtime_model_error_string_field(object, "message")
.unwrap_or_else(|| "model callback returned an error".to_string());
Some(RuntimeModelError {
code,
message,
provider_message: runtime_model_error_string_field(object, "provider_message"),
provider_code: runtime_model_error_string_field(object, "provider_code"),
provider_status: runtime_model_error_status_field(object, "provider_status"),
})
}
fn runtime_model_error_from_callback_failure(message: String) -> RuntimeModelError {
if let Ok(value) = serde_json::from_str::<Value>(&message) {
if let Some(error) = runtime_model_error_from_json_value(&value) {
return error;
}
}
runtime_model_callback_internal_error(message)
}
fn runtime_model_callback_response_value(
response_json: &str,
capability: &str,
) -> Result<Value, RuntimeModelError> {
let value = serde_json::from_str::<Value>(response_json).map_err(|error| {
runtime_model_callback_internal_error(format!(
"model {} response JSON decode failed: {}",
capability, error
))
})?;
if value.get("ok").and_then(Value::as_bool) == Some(false) {
return Err(
runtime_model_error_from_json_value(&value).unwrap_or_else(|| {
runtime_model_callback_internal_error(format!(
"model {} callback returned ok=false without a valid error object",
capability
))
}),
);
}
if value.get("ok").and_then(Value::as_bool) == Some(true) {
if let Some(inner) = value.get("value").or_else(|| value.get("result")) {
return Ok(inner.clone());
}
}
Ok(value)
}
fn runtime_model_embed_response_from_json(
response_json: &str,
) -> Result<RuntimeModelEmbedResponse, RuntimeModelError> {
let value = runtime_model_callback_response_value(response_json, "embed")?;
serde_json::from_value::<RuntimeModelEmbedResponse>(value).map_err(|error| {
runtime_model_callback_internal_error(format!(
"model embed response JSON decode failed: {}",
error
))
})
}
fn runtime_model_llm_response_from_json(
response_json: &str,
) -> Result<RuntimeModelLlmResponse, RuntimeModelError> {
let value = runtime_model_callback_response_value(response_json, "llm")?;
serde_json::from_value::<RuntimeModelLlmResponse>(value).map_err(|error| {
runtime_model_callback_internal_error(format!(
"model llm response JSON decode failed: {}",
error
))
})
}
fn invoke_standard_sqlite_provider_callback(
callback: FfiSqliteProviderCallback,
user_data: usize,
request: &RuntimeSqliteProviderRequest,
) -> Result<Value, String> {
let request = OwnedFfiSqliteProviderRequest::from_runtime(request)?;
let mut response_json_out = FfiOwnedBuffer {
ptr: ptr::null_mut(),
len: 0,
};
let mut error_out = FfiOwnedBuffer {
ptr: ptr::null_mut(),
len: 0,
};
let status = unsafe {
callback(
request.as_ptr(),
user_data as *mut c_void,
&mut response_json_out,
&mut error_out,
)
};
let callback_error = take_optional_owned_ffi_string_buffer(
error_out,
"sqlite host provider callback error_out",
)?;
if status != FFI_STATUS_OK {
unsafe { free_ffi_bytes(response_json_out.ptr, response_json_out.len) };
return Err(callback_error.unwrap_or_else(|| {
"sqlite host provider callback returned failure without error message".to_string()
}));
}
let response_json = take_optional_owned_ffi_string_buffer(
response_json_out,
"sqlite host provider callback response_json_out",
)?
.ok_or_else(|| "sqlite host provider callback returned empty response_json_out".to_string())?;
if let Some(message) = callback_error {
if !message.is_empty() {
return Err(format!(
"sqlite host provider callback returned unexpected error text on success: {}",
message
));
}
}
serde_json::from_str(&response_json).map_err(|error| {
format!(
"failed to parse sqlite provider callback response json: {}",
error
)
})
}
fn invoke_standard_lancedb_provider_callback(
callback: FfiLanceDbProviderCallback,
user_data: usize,
request: &RuntimeLanceDbProviderRequest,
) -> Result<RuntimeLanceDbProviderResult, String> {
let request = OwnedFfiLanceDbProviderRequest::from_runtime(request)?;
let mut meta_json_out = FfiOwnedBuffer {
ptr: ptr::null_mut(),
len: 0,
};
let mut data_out = FfiOwnedBuffer {
ptr: ptr::null_mut(),
len: 0,
};
let mut error_out = FfiOwnedBuffer {
ptr: ptr::null_mut(),
len: 0,
};
let status = unsafe {
callback(
request.as_ptr(),
user_data as *mut c_void,
&mut meta_json_out,
&mut data_out,
&mut error_out,
)
};
let callback_error = take_optional_owned_ffi_string_buffer(
error_out,
"lancedb host provider callback error_out",
)?;
if status != FFI_STATUS_OK {
unsafe {
free_ffi_bytes(meta_json_out.ptr, meta_json_out.len);
free_ffi_bytes(data_out.ptr, data_out.len);
}
return Err(callback_error.unwrap_or_else(|| {
"lancedb host provider callback returned failure without error message".to_string()
}));
}
let meta_json = take_optional_owned_ffi_string_buffer(
meta_json_out,
"lancedb host provider callback meta_json_out",
)?
.unwrap_or_else(|| "{}".to_string());
let meta = serde_json::from_str::<Value>(&meta_json).map_err(|error| {
format!(
"failed to parse lancedb provider callback meta json: {}",
error
)
})?;
let bytes =
take_optional_owned_ffi_buffer(data_out, "lancedb host provider callback data_out")?
.unwrap_or_default();
if let Some(message) = callback_error {
if !message.is_empty() {
return Err(format!(
"lancedb host provider callback returned unexpected error text on success: {}",
message
));
}
}
Ok(RuntimeLanceDbProviderResult::binary(meta, bytes))
}
fn take_optional_owned_ffi_buffer(
value: FfiOwnedBuffer,
field_name: &str,
) -> Result<Option<Vec<u8>>, String> {
if value.ptr.is_null() {
if value.len == 0 {
return Ok(None);
}
return Err(format!(
"{} returned null ptr with non-zero len",
field_name
));
}
let bytes = unsafe { std::slice::from_raw_parts(value.ptr, value.len) }.to_vec();
unsafe { free_ffi_bytes(value.ptr, value.len) };
Ok(Some(bytes))
}
fn take_optional_owned_ffi_string_buffer(
value: FfiOwnedBuffer,
field_name: &str,
) -> Result<Option<String>, String> {
let bytes = match take_optional_owned_ffi_buffer(value, field_name)? {
Some(bytes) => bytes,
None => return Ok(None),
};
String::from_utf8(bytes)
.map(Some)
.map_err(|error| format!("{} returned non-utf8 bytes: {}", field_name, error))
}
unsafe fn free_ffi_bytes(value: *mut u8, len: usize) {
if value.is_null() || len == 0 {
return;
}
let _ = unsafe { Vec::from_raw_parts(value, len, len) };
}
unsafe fn free_string_array_parts(items: *mut FfiOwnedBuffer, len: usize) {
if items.is_null() || len == 0 {
return;
}
let values = unsafe { Vec::from_raw_parts(items, len, len) };
for value in values {
unsafe { luaskills_ffi_buffer_free(value) };
}
}
unsafe fn free_entry_parameter_descriptor(value: FfiRuntimeEntryParameterDescriptor) {
unsafe { luaskills_ffi_buffer_free(value.name) };
unsafe { luaskills_ffi_buffer_free(value.param_type) };
unsafe { luaskills_ffi_buffer_free(value.description) };
}
unsafe fn free_entry_descriptor(value: FfiRuntimeEntryDescriptor) {
unsafe { luaskills_ffi_buffer_free(value.canonical_name) };
unsafe { luaskills_ffi_buffer_free(value.skill_id) };
unsafe { luaskills_ffi_buffer_free(value.local_name) };
unsafe { luaskills_ffi_buffer_free(value.root_name) };
unsafe { luaskills_ffi_buffer_free(value.skill_dir) };
unsafe { luaskills_ffi_buffer_free(value.description) };
if !value.parameters.is_null() && value.parameters_len > 0 {
let parameters = unsafe {
Vec::from_raw_parts(value.parameters, value.parameters_len, value.parameters_len)
};
for parameter in parameters {
unsafe { free_entry_parameter_descriptor(parameter) };
}
}
}
unsafe fn free_help_node_descriptor(value: FfiRuntimeHelpNodeDescriptor) {
unsafe { luaskills_ffi_buffer_free(value.flow_name) };
unsafe { luaskills_ffi_buffer_free(value.description) };
unsafe { free_string_array_parts(value.related_entries, value.related_entries_len) };
}
fn ffi_ok_status(error_out: *mut FfiOwnedBuffer) -> i32 {
clear_error_out(error_out);
FFI_STATUS_OK
}
fn ffi_error_status(error_out: *mut FfiOwnedBuffer, message: impl Into<String>) -> i32 {
set_error_out(error_out, message);
FFI_STATUS_ERROR
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_string_clone(value: *const c_char) -> *mut c_char {
if value.is_null() {
return alloc_c_string("");
}
let text = unsafe { CStr::from_ptr(value) }
.to_string_lossy()
.to_string();
alloc_c_string(&text)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_buffer_clone(
value: *const u8,
len: usize,
buffer_out: *mut FfiOwnedBuffer,
error_out: *mut FfiOwnedBuffer,
) -> i32 {
clear_error_out(error_out);
clear_out_buffer(buffer_out);
if buffer_out.is_null() {
return ffi_error_status(error_out, "buffer_out must not be null");
}
if value.is_null() && len != 0 {
return ffi_error_status(error_out, "value must not be null when len > 0");
}
let slice = if len == 0 {
&[][..]
} else {
unsafe { std::slice::from_raw_parts(value, len) }
};
unsafe {
*buffer_out = alloc_owned_buffer_from_bytes(slice);
}
ffi_ok_status(error_out)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_bytes_clone(value: *const u8, len: usize) -> *mut u8 {
if value.is_null() || len == 0 {
return ptr::null_mut();
}
let slice = unsafe { std::slice::from_raw_parts(value, len) };
let mut bytes = slice.to_vec();
let pointer = bytes.as_mut_ptr();
std::mem::forget(bytes);
pointer
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_bytes_free(value: *mut u8, len: usize) {
unsafe { free_ffi_bytes(value, len) };
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_buffer_free(value: FfiOwnedBuffer) {
unsafe { free_ffi_bytes(value.ptr, value.len) };
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_set_sqlite_provider_callback(
callback: Option<FfiSqliteProviderCallback>,
user_data: *mut c_void,
error_out: *mut FfiOwnedBuffer,
) -> i32 {
clear_error_out(error_out);
let wrapped = callback.map(|callback_fn| {
let user_data = user_data as usize;
std::sync::Arc::new(move |request: &RuntimeSqliteProviderRequest| {
invoke_standard_sqlite_provider_callback(callback_fn, user_data, request)
}) as RuntimeSqliteProviderCallback
});
set_sqlite_provider_callback(wrapped);
ffi_ok_status(error_out)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_set_lancedb_provider_callback(
callback: Option<FfiLanceDbProviderCallback>,
user_data: *mut c_void,
error_out: *mut FfiOwnedBuffer,
) -> i32 {
clear_error_out(error_out);
let wrapped = callback.map(|callback_fn| {
let user_data = user_data as usize;
std::sync::Arc::new(move |request: &RuntimeLanceDbProviderRequest| {
invoke_standard_lancedb_provider_callback(callback_fn, user_data, request)
}) as RuntimeLanceDbProviderCallback
});
set_lancedb_provider_callback(wrapped);
ffi_ok_status(error_out)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_set_sqlite_provider_json_callback(
callback: Option<FfiJsonProviderCallback>,
user_data: *mut c_void,
error_out: *mut FfiOwnedBuffer,
) -> i32 {
clear_error_out(error_out);
let wrapped = callback.map(|callback_fn| {
let user_data = user_data as usize;
std::sync::Arc::new(move |request_json: &str| {
invoke_json_provider_callback(callback_fn, user_data, request_json)
}) as crate::host::database::RuntimeSqliteProviderJsonCallback
});
set_sqlite_provider_json_callback(wrapped);
ffi_ok_status(error_out)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_set_lancedb_provider_json_callback(
callback: Option<FfiJsonProviderCallback>,
user_data: *mut c_void,
error_out: *mut FfiOwnedBuffer,
) -> i32 {
clear_error_out(error_out);
let wrapped = callback.map(|callback_fn| {
let user_data = user_data as usize;
std::sync::Arc::new(move |request_json: &str| {
invoke_json_provider_callback(callback_fn, user_data, request_json)
}) as crate::host::database::RuntimeLanceDbProviderJsonCallback
});
set_lancedb_provider_json_callback(wrapped);
ffi_ok_status(error_out)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_set_host_tool_json_callback(
callback: Option<FfiJsonProviderCallback>,
user_data: *mut c_void,
error_out: *mut FfiOwnedBuffer,
) -> i32 {
clear_error_out(error_out);
let wrapped = callback.map(|callback_fn| {
let user_data = user_data as usize;
std::sync::Arc::new(move |request: &RuntimeHostToolRequest| {
let request_json = serde_json::to_string(request)
.map_err(|error| format!("host tool request JSON encode failed: {}", error))?;
let response_json =
invoke_json_provider_callback(callback_fn, user_data, &request_json)?;
serde_json::from_str::<Value>(&response_json)
.map_err(|error| format!("host tool response JSON decode failed: {}", error))
}) as RuntimeHostToolCallback
});
set_host_tool_callback(wrapped);
ffi_ok_status(error_out)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_set_model_embed_json_callback(
callback: Option<FfiJsonProviderCallback>,
user_data: *mut c_void,
error_out: *mut FfiOwnedBuffer,
) -> i32 {
clear_error_out(error_out);
let wrapped = callback.map(|callback_fn| {
let user_data = user_data as usize;
std::sync::Arc::new(move |request: &RuntimeModelEmbedRequest| {
let request_json = serde_json::to_string(request).map_err(|error| {
runtime_model_callback_internal_error(format!(
"model embed request JSON encode failed: {}",
error
))
})?;
let response_json =
invoke_json_provider_callback(callback_fn, user_data, &request_json)
.map_err(runtime_model_error_from_callback_failure)?;
runtime_model_embed_response_from_json(&response_json)
}) as RuntimeModelEmbedCallback
});
set_model_embed_callback(wrapped);
ffi_ok_status(error_out)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_set_model_llm_json_callback(
callback: Option<FfiJsonProviderCallback>,
user_data: *mut c_void,
error_out: *mut FfiOwnedBuffer,
) -> i32 {
clear_error_out(error_out);
let wrapped = callback.map(|callback_fn| {
let user_data = user_data as usize;
std::sync::Arc::new(move |request: &RuntimeModelLlmRequest| {
let request_json = serde_json::to_string(request).map_err(|error| {
runtime_model_callback_internal_error(format!(
"model llm request JSON encode failed: {}",
error
))
})?;
let response_json =
invoke_json_provider_callback(callback_fn, user_data, &request_json)
.map_err(runtime_model_error_from_callback_failure)?;
runtime_model_llm_response_from_json(&response_json)
}) as RuntimeModelLlmCallback
});
set_model_llm_callback(wrapped);
ffi_ok_status(error_out)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_string_array_free(value: *mut FfiStringArray) {
if value.is_null() {
return;
}
let value = unsafe { Box::from_raw(value) };
unsafe { free_string_array_parts(value.items, value.len) };
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_entry_list_free(value: *mut FfiRuntimeEntryDescriptorList) {
if value.is_null() {
return;
}
let value = unsafe { Box::from_raw(value) };
if !value.items.is_null() && value.len > 0 {
let items = unsafe { Vec::from_raw_parts(value.items, value.len, value.len) };
for item in items {
unsafe { free_entry_descriptor(item) };
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_help_list_free(
value: *mut FfiRuntimeSkillHelpDescriptorList,
) {
if value.is_null() {
return;
}
let value = unsafe { Box::from_raw(value) };
if !value.items.is_null() && value.len > 0 {
let items = unsafe { Vec::from_raw_parts(value.items, value.len, value.len) };
for item in items {
unsafe { luaskills_ffi_buffer_free(item.skill_id) };
unsafe { luaskills_ffi_buffer_free(item.skill_name) };
unsafe { luaskills_ffi_buffer_free(item.skill_version) };
unsafe { luaskills_ffi_buffer_free(item.root_name) };
unsafe { luaskills_ffi_buffer_free(item.skill_dir) };
unsafe { free_help_node_descriptor(item.main) };
if !item.flows.is_null() && item.flows_len > 0 {
let flows =
unsafe { Vec::from_raw_parts(item.flows, item.flows_len, item.flows_len) };
for flow in flows {
unsafe { free_help_node_descriptor(flow) };
}
}
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_help_detail_free(value: *mut FfiRuntimeHelpDetail) {
if value.is_null() {
return;
}
let value = unsafe { *Box::from_raw(value) };
unsafe { luaskills_ffi_buffer_free(value.skill_id) };
unsafe { luaskills_ffi_buffer_free(value.skill_name) };
unsafe { luaskills_ffi_buffer_free(value.skill_version) };
unsafe { luaskills_ffi_buffer_free(value.root_name) };
unsafe { luaskills_ffi_buffer_free(value.skill_dir) };
unsafe { luaskills_ffi_buffer_free(value.flow_name) };
unsafe { luaskills_ffi_buffer_free(value.description) };
unsafe { free_string_array_parts(value.related_entries, value.related_entries_len) };
unsafe { luaskills_ffi_buffer_free(value.content_type) };
unsafe { luaskills_ffi_buffer_free(value.content) };
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_invocation_result_free(
value: *mut FfiRuntimeInvocationResult,
) {
if value.is_null() {
return;
}
let value = unsafe { *Box::from_raw(value) };
unsafe { luaskills_ffi_buffer_free(value.content) };
unsafe { luaskills_ffi_buffer_free(value.template_hint) };
if !value.host_result.is_null() {
let host_result = unsafe { *Box::from_raw(value.host_result) };
unsafe { luaskills_ffi_buffer_free(host_result.kind) };
unsafe { luaskills_ffi_buffer_free(host_result.payload_json) };
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_skill_apply_result_free(value: *mut FfiSkillApplyResult) {
if value.is_null() {
return;
}
let value = unsafe { *Box::from_raw(value) };
unsafe { luaskills_ffi_buffer_free(value.skill_id) };
unsafe { luaskills_ffi_buffer_free(value.status) };
unsafe { luaskills_ffi_buffer_free(value.message) };
unsafe { luaskills_ffi_buffer_free(value.version) };
unsafe { luaskills_ffi_buffer_free(value.source_locator) };
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_skill_uninstall_result_free(
value: *mut FfiSkillUninstallResult,
) {
if value.is_null() {
return;
}
let value = unsafe { *Box::from_raw(value) };
unsafe { luaskills_ffi_buffer_free(value.skill_id) };
unsafe { luaskills_ffi_buffer_free(value.message) };
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_version(
version_out: *mut FfiOwnedBuffer,
error_out: *mut FfiOwnedBuffer,
) -> i32 {
clear_error_out(error_out);
clear_out_buffer(version_out);
if version_out.is_null() {
return ffi_error_status(error_out, "version_out must not be null");
}
unsafe { *version_out = alloc_owned_buffer_from_string(crate::ffi::FFI_VERSION) };
ffi_ok_status(error_out)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_describe(
functions_out: *mut *mut FfiStringArray,
error_out: *mut FfiOwnedBuffer,
) -> i32 {
clear_error_out(error_out);
clear_out_ptr(functions_out);
if functions_out.is_null() {
return ffi_error_status(error_out, "functions_out must not be null");
}
let values = crate::ffi::exported_ffi_function_names();
unsafe { *functions_out = Box::into_raw(Box::new(alloc_string_array(&values))) };
ffi_ok_status(error_out)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_engine_new(
options: *const FfiLuaEngineOptions,
engine_id_out: *mut u64,
error_out: *mut FfiOwnedBuffer,
) -> i32 {
clear_error_out(error_out);
clear_out_u64(engine_id_out);
if options.is_null() {
return ffi_error_status(error_out, "options must not be null");
}
if engine_id_out.is_null() {
return ffi_error_status(error_out, "engine_id_out must not be null");
}
let options = match parse_engine_options(unsafe { &*options }) {
Ok(options) => options,
Err(error) => return ffi_error_status(error_out, error),
};
match LuaEngine::new(options) {
Ok(engine) => {
let engine_id = FFI_ENGINE_COUNTER.fetch_add(1, Ordering::Relaxed);
match ffi_engine_registry().lock() {
Ok(mut registry) => {
registry.insert(engine_id, crate::ffi::FfiEngineSlot::new(engine));
unsafe { *engine_id_out = engine_id };
ffi_ok_status(error_out)
}
Err(_) => ffi_error_status(error_out, "FFI engine registry lock poisoned"),
}
}
Err(error) => ffi_error_status(error_out, error.to_string()),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_engine_free(
engine_id: u64,
error_out: *mut FfiOwnedBuffer,
) -> i32 {
clear_error_out(error_out);
match ffi_engine_registry().lock() {
Ok(mut registry) => {
if registry.remove(&engine_id).is_none() {
ffi_error_status(error_out, format!("FFI engine {} not found", engine_id))
} else {
ffi_ok_status(error_out)
}
}
Err(_) => ffi_error_status(error_out, "FFI engine registry lock poisoned"),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_load_from_roots(
engine_id: u64,
skill_roots: *const FfiRuntimeSkillRoot,
skill_roots_len: usize,
error_out: *mut FfiOwnedBuffer,
) -> i32 {
clear_error_out(error_out);
let skill_roots = match parse_skill_roots(skill_roots, skill_roots_len) {
Ok(skill_roots) => skill_roots,
Err(error) => return ffi_error_status(error_out, error),
};
match with_engine_mut(engine_id, |engine| {
engine
.load_from_roots(&skill_roots)
.map_err(|error| error.to_string())
}) {
Ok(()) => ffi_ok_status(error_out),
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_reload_from_roots(
engine_id: u64,
skill_roots: *const FfiRuntimeSkillRoot,
skill_roots_len: usize,
error_out: *mut FfiOwnedBuffer,
) -> i32 {
clear_error_out(error_out);
let skill_roots = match parse_skill_roots(skill_roots, skill_roots_len) {
Ok(skill_roots) => skill_roots,
Err(error) => return ffi_error_status(error_out, error),
};
match with_engine_mut(engine_id, |engine| {
engine
.reload_from_roots(&skill_roots)
.map_err(|error| error.to_string())
}) {
Ok(()) => ffi_ok_status(error_out),
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_list_entries(
engine_id: u64,
authority: i32,
entries_out: *mut *mut FfiRuntimeEntryDescriptorList,
error_out: *mut FfiOwnedBuffer,
) -> i32 {
clear_error_out(error_out);
clear_out_ptr(entries_out);
if entries_out.is_null() {
return ffi_error_status(error_out, "entries_out must not be null");
}
let authority = match parse_skill_management_authority(authority, "authority") {
Ok(authority) => authority,
Err(error) => return ffi_error_status(error_out, error),
};
match with_engine(engine_id, |engine| {
Ok(engine.list_entries_for_authority(authority))
}) {
Ok(entries) => {
let mut items: Vec<FfiRuntimeEntryDescriptor> =
entries.iter().map(alloc_entry_descriptor).collect();
let list = FfiRuntimeEntryDescriptorList {
items: items.as_mut_ptr(),
len: items.len(),
};
std::mem::forget(items);
unsafe { *entries_out = Box::into_raw(Box::new(list)) };
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_list_skill_help(
engine_id: u64,
authority: i32,
help_out: *mut *mut FfiRuntimeSkillHelpDescriptorList,
error_out: *mut FfiOwnedBuffer,
) -> i32 {
clear_error_out(error_out);
clear_out_ptr(help_out);
if help_out.is_null() {
return ffi_error_status(error_out, "help_out must not be null");
}
let authority = match parse_skill_management_authority(authority, "authority") {
Ok(authority) => authority,
Err(error) => return ffi_error_status(error_out, error),
};
match with_engine(engine_id, |engine| {
Ok(engine.list_skill_help_for_authority(authority))
}) {
Ok(help_descriptors) => {
let mut items: Vec<FfiRuntimeSkillHelpDescriptor> =
help_descriptors.iter().map(alloc_help_descriptor).collect();
let list = FfiRuntimeSkillHelpDescriptorList {
items: items.as_mut_ptr(),
len: items.len(),
};
std::mem::forget(items);
unsafe { *help_out = Box::into_raw(Box::new(list)) };
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_render_skill_help_detail(
engine_id: u64,
authority: i32,
skill_id: *const c_char,
flow_name: *const c_char,
request_context_json: FfiBorrowedBuffer,
detail_out: *mut *mut FfiRuntimeHelpDetail,
error_out: *mut FfiOwnedBuffer,
) -> i32 {
clear_error_out(error_out);
clear_out_ptr(detail_out);
if detail_out.is_null() {
return ffi_error_status(error_out, "detail_out must not be null");
}
let skill_id = match parse_required_string(skill_id, "skill_id") {
Ok(skill_id) => skill_id,
Err(error) => return ffi_error_status(error_out, error),
};
let flow_name = match parse_required_string(flow_name, "flow_name") {
Ok(flow_name) => flow_name,
Err(error) => return ffi_error_status(error_out, error),
};
let authority = match parse_skill_management_authority(authority, "authority") {
Ok(authority) => authority,
Err(error) => return ffi_error_status(error_out, error),
};
let request_context =
match parse_request_context_buffer(&request_context_json, "request_context_json") {
Ok(request_context) => request_context,
Err(error) => return ffi_error_status(error_out, error),
};
match with_engine(engine_id, |engine| {
engine.render_skill_help_detail_for_authority(
authority,
&skill_id,
&flow_name,
request_context.as_ref(),
)
}) {
Ok(Some(detail)) => {
unsafe { *detail_out = Box::into_raw(Box::new(alloc_help_detail(&detail))) };
ffi_ok_status(error_out)
}
Ok(None) => ffi_error_status(error_out, "Requested help detail was not found"),
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_prompt_argument_completions(
engine_id: u64,
authority: i32,
prompt_name: *const c_char,
argument_name: *const c_char,
values_out: *mut *mut FfiStringArray,
error_out: *mut FfiOwnedBuffer,
) -> i32 {
clear_error_out(error_out);
clear_out_ptr(values_out);
if values_out.is_null() {
return ffi_error_status(error_out, "values_out must not be null");
}
let prompt_name = match parse_required_string(prompt_name, "prompt_name") {
Ok(prompt_name) => prompt_name,
Err(error) => return ffi_error_status(error_out, error),
};
let argument_name = match parse_required_string(argument_name, "argument_name") {
Ok(argument_name) => argument_name,
Err(error) => return ffi_error_status(error_out, error),
};
let authority = match parse_skill_management_authority(authority, "authority") {
Ok(authority) => authority,
Err(error) => return ffi_error_status(error_out, error),
};
match with_engine(engine_id, |engine| {
Ok(engine.prompt_argument_completions_for_authority(
authority,
&prompt_name,
&argument_name,
))
}) {
Ok(Some(values)) => {
unsafe { *values_out = Box::into_raw(Box::new(alloc_string_array(&values))) };
ffi_ok_status(error_out)
}
Ok(None) => {
unsafe { *values_out = Box::into_raw(Box::new(alloc_string_array(&[]))) };
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_is_skill(
engine_id: u64,
authority: i32,
tool_name: *const c_char,
value_out: *mut u8,
error_out: *mut FfiOwnedBuffer,
) -> i32 {
clear_error_out(error_out);
clear_out_u8(value_out);
if value_out.is_null() {
return ffi_error_status(error_out, "value_out must not be null");
}
let tool_name = match parse_required_string(tool_name, "tool_name") {
Ok(tool_name) => tool_name,
Err(error) => return ffi_error_status(error_out, error),
};
let authority = match parse_skill_management_authority(authority, "authority") {
Ok(authority) => authority,
Err(error) => return ffi_error_status(error_out, error),
};
match with_engine(engine_id, |engine| {
Ok(engine.is_skill_for_authority(authority, &tool_name))
}) {
Ok(value) => {
unsafe { *value_out = u8::from(value) };
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_skill_name_for_tool(
engine_id: u64,
authority: i32,
tool_name: *const c_char,
skill_id_out: *mut FfiOwnedBuffer,
error_out: *mut FfiOwnedBuffer,
) -> i32 {
clear_error_out(error_out);
clear_out_buffer(skill_id_out);
if skill_id_out.is_null() {
return ffi_error_status(error_out, "skill_id_out must not be null");
}
let tool_name = match parse_required_string(tool_name, "tool_name") {
Ok(tool_name) => tool_name,
Err(error) => return ffi_error_status(error_out, error),
};
let authority = match parse_skill_management_authority(authority, "authority") {
Ok(authority) => authority,
Err(error) => return ffi_error_status(error_out, error),
};
match with_engine(engine_id, |engine| {
Ok(engine.skill_name_for_tool_for_authority(authority, &tool_name))
}) {
Ok(skill_id) => {
unsafe { *skill_id_out = alloc_optional_owned_buffer_from_string(skill_id.as_deref()) };
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_skill_config_list(
engine_id: u64,
skill_id: *const c_char,
result_json_out: *mut FfiOwnedBuffer,
error_out: *mut FfiOwnedBuffer,
) -> i32 {
clear_error_out(error_out);
clear_out_buffer(result_json_out);
if result_json_out.is_null() {
return ffi_error_status(error_out, "result_json_out must not be null");
}
let skill_id = match parse_optional_string(skill_id, "skill_id") {
Ok(skill_id) => skill_id,
Err(error) => return ffi_error_status(error_out, error),
};
match with_engine(engine_id, |engine| {
engine.list_skill_config_entries(skill_id.as_deref())
}) {
Ok(entries) => match serde_json::to_string(&entries) {
Ok(result_json) => {
unsafe { *result_json_out = alloc_owned_buffer_from_string(result_json) };
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(
error_out,
format!("failed to serialize skill config entries: {}", error),
),
},
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_skill_config_get(
engine_id: u64,
skill_id: *const c_char,
key: *const c_char,
value_out: *mut FfiOwnedBuffer,
found_out: *mut u8,
error_out: *mut FfiOwnedBuffer,
) -> i32 {
clear_error_out(error_out);
clear_out_buffer(value_out);
clear_out_u8(found_out);
if value_out.is_null() {
return ffi_error_status(error_out, "value_out must not be null");
}
if found_out.is_null() {
return ffi_error_status(error_out, "found_out must not be null");
}
let skill_id = match parse_required_string(skill_id, "skill_id") {
Ok(skill_id) => skill_id,
Err(error) => return ffi_error_status(error_out, error),
};
let key = match parse_required_string(key, "key") {
Ok(key) => key,
Err(error) => return ffi_error_status(error_out, error),
};
match with_engine(engine_id, |engine| {
engine.get_skill_config_value(&skill_id, &key)
}) {
Ok(value) => {
unsafe {
*found_out = u8::from(value.is_some());
*value_out = alloc_optional_owned_buffer_from_string(value.as_deref());
}
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_skill_config_set(
engine_id: u64,
skill_id: *const c_char,
key: *const c_char,
value: *const c_char,
error_out: *mut FfiOwnedBuffer,
) -> i32 {
clear_error_out(error_out);
let skill_id = match parse_required_string(skill_id, "skill_id") {
Ok(skill_id) => skill_id,
Err(error) => return ffi_error_status(error_out, error),
};
let key = match parse_required_string(key, "key") {
Ok(key) => key,
Err(error) => return ffi_error_status(error_out, error),
};
let value = match parse_required_string_allow_empty(value, "value") {
Ok(value) => value,
Err(error) => return ffi_error_status(error_out, error),
};
match with_engine_mut(engine_id, |engine| {
engine.set_skill_config_value(&skill_id, &key, &value)
}) {
Ok(()) => ffi_ok_status(error_out),
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_skill_config_delete(
engine_id: u64,
skill_id: *const c_char,
key: *const c_char,
deleted_out: *mut u8,
error_out: *mut FfiOwnedBuffer,
) -> i32 {
clear_error_out(error_out);
clear_out_u8(deleted_out);
if deleted_out.is_null() {
return ffi_error_status(error_out, "deleted_out must not be null");
}
let skill_id = match parse_required_string(skill_id, "skill_id") {
Ok(skill_id) => skill_id,
Err(error) => return ffi_error_status(error_out, error),
};
let key = match parse_required_string(key, "key") {
Ok(key) => key,
Err(error) => return ffi_error_status(error_out, error),
};
match with_engine_mut(engine_id, |engine| {
engine.delete_skill_config_value(&skill_id, &key)
}) {
Ok(deleted) => {
unsafe { *deleted_out = u8::from(deleted) };
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_call_skill(
engine_id: u64,
tool_name: *const c_char,
args_json: FfiBorrowedBuffer,
invocation_context: *const FfiLuaInvocationContext,
result_out: *mut *mut FfiRuntimeInvocationResult,
error_out: *mut FfiOwnedBuffer,
) -> i32 {
clear_error_out(error_out);
clear_out_ptr(result_out);
if result_out.is_null() {
return ffi_error_status(error_out, "result_out must not be null");
}
let tool_name = match parse_required_string(tool_name, "tool_name") {
Ok(tool_name) => tool_name,
Err(error) => return ffi_error_status(error_out, error),
};
let args = match parse_json_value_or_empty_object_buffer(&args_json, "args_json") {
Ok(args) => args,
Err(error) => return ffi_error_status(error_out, error),
};
let invocation_context = match parse_invocation_context(invocation_context) {
Ok(invocation_context) => invocation_context,
Err(error) => return ffi_error_status(error_out, error),
};
match with_engine(engine_id, |engine| {
engine.call_skill(&tool_name, &args, invocation_context.as_ref())
}) {
Ok(result) => {
unsafe { *result_out = Box::into_raw(Box::new(alloc_invocation_result(&result))) };
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_run_lua(
engine_id: u64,
code: *const c_char,
args_json: FfiBorrowedBuffer,
invocation_context: *const FfiLuaInvocationContext,
result_json_out: *mut FfiOwnedBuffer,
error_out: *mut FfiOwnedBuffer,
) -> i32 {
clear_error_out(error_out);
clear_out_buffer(result_json_out);
if result_json_out.is_null() {
return ffi_error_status(error_out, "result_json_out must not be null");
}
let code = match parse_required_string(code, "code") {
Ok(code) => code,
Err(error) => return ffi_error_status(error_out, error),
};
let args = match parse_json_value_or_empty_object_buffer(&args_json, "args_json") {
Ok(args) => args,
Err(error) => return ffi_error_status(error_out, error),
};
let invocation_context = match parse_invocation_context(invocation_context) {
Ok(invocation_context) => invocation_context,
Err(error) => return ffi_error_status(error_out, error),
};
match with_engine(engine_id, |engine| {
engine.run_lua(&code, &args, invocation_context.as_ref())
}) {
Ok(result) => match serde_json::to_string(&result) {
Ok(result_json) => {
unsafe { *result_json_out = alloc_owned_buffer_from_string(result_json) };
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(
error_out,
format!("Failed to serialize Lua result: {}", error),
),
},
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_runtime_lease_create(
engine_id: u64,
request_json: FfiBorrowedBuffer,
result_json_out: *mut FfiOwnedBuffer,
error_out: *mut FfiOwnedBuffer,
) -> i32 {
run_engine_json_text_call(
engine_id,
&request_json,
result_json_out,
error_out,
"request_json",
|engine, request_json| engine.create_runtime_lease_json(request_json),
)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_runtime_lease_eval(
engine_id: u64,
request_json: FfiBorrowedBuffer,
result_json_out: *mut FfiOwnedBuffer,
error_out: *mut FfiOwnedBuffer,
) -> i32 {
run_engine_json_text_call(
engine_id,
&request_json,
result_json_out,
error_out,
"request_json",
|engine, request_json| engine.eval_runtime_lease_json(request_json),
)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_runtime_lease_status(
engine_id: u64,
request_json: FfiBorrowedBuffer,
result_json_out: *mut FfiOwnedBuffer,
error_out: *mut FfiOwnedBuffer,
) -> i32 {
run_engine_json_text_call(
engine_id,
&request_json,
result_json_out,
error_out,
"request_json",
|engine, request_json| engine.runtime_lease_status_json(request_json),
)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_runtime_lease_list(
engine_id: u64,
request_json: FfiBorrowedBuffer,
result_json_out: *mut FfiOwnedBuffer,
error_out: *mut FfiOwnedBuffer,
) -> i32 {
run_engine_json_text_call(
engine_id,
&request_json,
result_json_out,
error_out,
"request_json",
|engine, request_json| engine.list_runtime_leases_json(request_json),
)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_runtime_lease_close(
engine_id: u64,
request_json: FfiBorrowedBuffer,
result_json_out: *mut FfiOwnedBuffer,
error_out: *mut FfiOwnedBuffer,
) -> i32 {
run_engine_json_text_call(
engine_id,
&request_json,
result_json_out,
error_out,
"request_json",
|engine, request_json| engine.close_runtime_lease_json(request_json),
)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_system_runtime_lease_create(
engine_id: u64,
request_json: FfiBorrowedBuffer,
result_json_out: *mut FfiOwnedBuffer,
error_out: *mut FfiOwnedBuffer,
) -> i32 {
run_engine_json_text_call(
engine_id,
&request_json,
result_json_out,
error_out,
"request_json",
|engine, request_json| engine.create_system_runtime_lease_json(request_json),
)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_system_runtime_lease_eval(
engine_id: u64,
request_json: FfiBorrowedBuffer,
result_json_out: *mut FfiOwnedBuffer,
error_out: *mut FfiOwnedBuffer,
) -> i32 {
run_engine_json_text_call(
engine_id,
&request_json,
result_json_out,
error_out,
"request_json",
|engine, request_json| engine.eval_system_runtime_lease_json(request_json),
)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_system_runtime_lease_status(
engine_id: u64,
request_json: FfiBorrowedBuffer,
result_json_out: *mut FfiOwnedBuffer,
error_out: *mut FfiOwnedBuffer,
) -> i32 {
run_engine_json_text_call(
engine_id,
&request_json,
result_json_out,
error_out,
"request_json",
|engine, request_json| engine.system_runtime_lease_status_json(request_json),
)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_system_runtime_lease_list(
engine_id: u64,
request_json: FfiBorrowedBuffer,
result_json_out: *mut FfiOwnedBuffer,
error_out: *mut FfiOwnedBuffer,
) -> i32 {
run_engine_json_text_call(
engine_id,
&request_json,
result_json_out,
error_out,
"request_json",
|engine, request_json| engine.list_system_runtime_leases_json(request_json),
)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_system_runtime_lease_close(
engine_id: u64,
request_json: FfiBorrowedBuffer,
result_json_out: *mut FfiOwnedBuffer,
error_out: *mut FfiOwnedBuffer,
) -> i32 {
run_engine_json_text_call(
engine_id,
&request_json,
result_json_out,
error_out,
"request_json",
|engine, request_json| engine.close_system_runtime_lease_json(request_json),
)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_disable_skill(
engine_id: u64,
skill_roots: *const FfiRuntimeSkillRoot,
skill_roots_len: usize,
skill_id: *const c_char,
reason: *const c_char,
error_out: *mut FfiOwnedBuffer,
) -> i32 {
clear_error_out(error_out);
let skill_roots = match parse_skill_roots(skill_roots, skill_roots_len) {
Ok(skill_roots) => skill_roots,
Err(error) => return ffi_error_status(error_out, error),
};
let skill_id = match parse_required_string(skill_id, "skill_id") {
Ok(skill_id) => skill_id,
Err(error) => return ffi_error_status(error_out, error),
};
let reason = match parse_optional_string(reason, "reason") {
Ok(reason) => reason,
Err(error) => return ffi_error_status(error_out, error),
};
match with_engine_mut(engine_id, |engine| {
engine
.disable_skill_in_roots(&skill_roots, &skill_id, reason.as_deref())
.map_err(|error| error.to_string())
}) {
Ok(()) => ffi_ok_status(error_out),
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_system_disable_skill(
engine_id: u64,
skill_roots: *const FfiRuntimeSkillRoot,
skill_roots_len: usize,
authority: i32,
skill_id: *const c_char,
reason: *const c_char,
error_out: *mut FfiOwnedBuffer,
) -> i32 {
clear_error_out(error_out);
let skill_roots = match parse_skill_roots(skill_roots, skill_roots_len) {
Ok(skill_roots) => skill_roots,
Err(error) => return ffi_error_status(error_out, error),
};
let skill_id = match parse_required_string(skill_id, "skill_id") {
Ok(skill_id) => skill_id,
Err(error) => return ffi_error_status(error_out, error),
};
let reason = match parse_optional_string(reason, "reason") {
Ok(reason) => reason,
Err(error) => return ffi_error_status(error_out, error),
};
let authority = match parse_skill_management_authority(authority, "authority") {
Ok(authority) => authority,
Err(error) => return ffi_error_status(error_out, error),
};
match with_engine_mut(engine_id, |engine| {
engine
.system_disable_skill_in_roots(&skill_roots, authority, &skill_id, reason.as_deref())
.map_err(|error| error.to_string())
}) {
Ok(()) => ffi_ok_status(error_out),
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_enable_skill(
engine_id: u64,
skill_roots: *const FfiRuntimeSkillRoot,
skill_roots_len: usize,
skill_id: *const c_char,
error_out: *mut FfiOwnedBuffer,
) -> i32 {
clear_error_out(error_out);
let skill_roots = match parse_skill_roots(skill_roots, skill_roots_len) {
Ok(skill_roots) => skill_roots,
Err(error) => return ffi_error_status(error_out, error),
};
let skill_id = match parse_required_string(skill_id, "skill_id") {
Ok(skill_id) => skill_id,
Err(error) => return ffi_error_status(error_out, error),
};
match with_engine_mut(engine_id, |engine| {
engine
.enable_skill(&skill_roots, &skill_id)
.map_err(|error| error.to_string())
}) {
Ok(()) => ffi_ok_status(error_out),
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_system_enable_skill(
engine_id: u64,
skill_roots: *const FfiRuntimeSkillRoot,
skill_roots_len: usize,
authority: i32,
skill_id: *const c_char,
error_out: *mut FfiOwnedBuffer,
) -> i32 {
clear_error_out(error_out);
let skill_roots = match parse_skill_roots(skill_roots, skill_roots_len) {
Ok(skill_roots) => skill_roots,
Err(error) => return ffi_error_status(error_out, error),
};
let skill_id = match parse_required_string(skill_id, "skill_id") {
Ok(skill_id) => skill_id,
Err(error) => return ffi_error_status(error_out, error),
};
let authority = match parse_skill_management_authority(authority, "authority") {
Ok(authority) => authority,
Err(error) => return ffi_error_status(error_out, error),
};
match with_engine_mut(engine_id, |engine| {
engine
.system_enable_skill(&skill_roots, authority, &skill_id)
.map_err(|error| error.to_string())
}) {
Ok(()) => ffi_ok_status(error_out),
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_uninstall_skill(
engine_id: u64,
skill_roots: *const FfiRuntimeSkillRoot,
skill_roots_len: usize,
skill_id: *const c_char,
options: *const FfiSkillUninstallOptions,
result_out: *mut *mut FfiSkillUninstallResult,
error_out: *mut FfiOwnedBuffer,
) -> i32 {
clear_error_out(error_out);
clear_out_ptr(result_out);
if result_out.is_null() {
return ffi_error_status(error_out, "result_out must not be null");
}
let skill_roots = match parse_skill_roots(skill_roots, skill_roots_len) {
Ok(skill_roots) => skill_roots,
Err(error) => return ffi_error_status(error_out, error),
};
let skill_id = match parse_required_string(skill_id, "skill_id") {
Ok(skill_id) => skill_id,
Err(error) => return ffi_error_status(error_out, error),
};
let options = parse_uninstall_options(unsafe { options.as_ref() });
match with_engine_mut(engine_id, |engine| {
engine
.uninstall_skill(&skill_roots, &skill_id, &options)
.map_err(|error| error.to_string())
}) {
Ok(result) => {
unsafe { *result_out = Box::into_raw(Box::new(alloc_skill_uninstall_result(&result))) };
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_system_uninstall_skill(
engine_id: u64,
skill_roots: *const FfiRuntimeSkillRoot,
skill_roots_len: usize,
authority: i32,
skill_id: *const c_char,
options: *const FfiSkillUninstallOptions,
result_out: *mut *mut FfiSkillUninstallResult,
error_out: *mut FfiOwnedBuffer,
) -> i32 {
clear_error_out(error_out);
clear_out_ptr(result_out);
if result_out.is_null() {
return ffi_error_status(error_out, "result_out must not be null");
}
let skill_roots = match parse_skill_roots(skill_roots, skill_roots_len) {
Ok(skill_roots) => skill_roots,
Err(error) => return ffi_error_status(error_out, error),
};
let skill_id = match parse_required_string(skill_id, "skill_id") {
Ok(skill_id) => skill_id,
Err(error) => return ffi_error_status(error_out, error),
};
let authority = match parse_skill_management_authority(authority, "authority") {
Ok(authority) => authority,
Err(error) => return ffi_error_status(error_out, error),
};
let options = parse_uninstall_options(unsafe { options.as_ref() });
match with_engine_mut(engine_id, |engine| {
engine
.system_uninstall_skill(&skill_roots, authority, &skill_id, &options)
.map_err(|error| error.to_string())
}) {
Ok(result) => {
unsafe { *result_out = Box::into_raw(Box::new(alloc_skill_uninstall_result(&result))) };
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_install_skill(
engine_id: u64,
skill_roots: *const FfiRuntimeSkillRoot,
skill_roots_len: usize,
request: *const FfiSkillInstallRequest,
result_out: *mut *mut FfiSkillApplyResult,
error_out: *mut FfiOwnedBuffer,
) -> i32 {
clear_error_out(error_out);
clear_out_ptr(result_out);
if result_out.is_null() {
return ffi_error_status(error_out, "result_out must not be null");
}
if request.is_null() {
return ffi_error_status(error_out, "request must not be null");
}
let skill_roots = match parse_skill_roots(skill_roots, skill_roots_len) {
Ok(skill_roots) => skill_roots,
Err(error) => return ffi_error_status(error_out, error),
};
let request = match parse_install_request(unsafe { &*request }) {
Ok(request) => request,
Err(error) => return ffi_error_status(error_out, error),
};
match with_engine_mut(engine_id, |engine| {
engine
.install_skill(&skill_roots, &request)
.map_err(|error| error.to_string())
}) {
Ok(result) => {
unsafe { *result_out = Box::into_raw(Box::new(alloc_skill_apply_result(&result))) };
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_system_install_skill(
engine_id: u64,
skill_roots: *const FfiRuntimeSkillRoot,
skill_roots_len: usize,
authority: i32,
request: *const FfiSkillInstallRequest,
result_out: *mut *mut FfiSkillApplyResult,
error_out: *mut FfiOwnedBuffer,
) -> i32 {
clear_error_out(error_out);
clear_out_ptr(result_out);
if result_out.is_null() {
return ffi_error_status(error_out, "result_out must not be null");
}
if request.is_null() {
return ffi_error_status(error_out, "request must not be null");
}
let skill_roots = match parse_skill_roots(skill_roots, skill_roots_len) {
Ok(skill_roots) => skill_roots,
Err(error) => return ffi_error_status(error_out, error),
};
let request = match parse_install_request(unsafe { &*request }) {
Ok(request) => request,
Err(error) => return ffi_error_status(error_out, error),
};
let authority = match parse_skill_management_authority(authority, "authority") {
Ok(authority) => authority,
Err(error) => return ffi_error_status(error_out, error),
};
match with_engine_mut(engine_id, |engine| {
engine
.system_install_skill(&skill_roots, authority, &request)
.map_err(|error| error.to_string())
}) {
Ok(result) => {
unsafe { *result_out = Box::into_raw(Box::new(alloc_skill_apply_result(&result))) };
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_update_skill(
engine_id: u64,
skill_roots: *const FfiRuntimeSkillRoot,
skill_roots_len: usize,
request: *const FfiSkillInstallRequest,
result_out: *mut *mut FfiSkillApplyResult,
error_out: *mut FfiOwnedBuffer,
) -> i32 {
clear_error_out(error_out);
clear_out_ptr(result_out);
if result_out.is_null() {
return ffi_error_status(error_out, "result_out must not be null");
}
if request.is_null() {
return ffi_error_status(error_out, "request must not be null");
}
let skill_roots = match parse_skill_roots(skill_roots, skill_roots_len) {
Ok(skill_roots) => skill_roots,
Err(error) => return ffi_error_status(error_out, error),
};
let request = match parse_install_request(unsafe { &*request }) {
Ok(request) => request,
Err(error) => return ffi_error_status(error_out, error),
};
match with_engine_mut(engine_id, |engine| {
engine
.update_skill(&skill_roots, &request)
.map_err(|error| error.to_string())
}) {
Ok(result) => {
unsafe { *result_out = Box::into_raw(Box::new(alloc_skill_apply_result(&result))) };
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_system_update_skill(
engine_id: u64,
skill_roots: *const FfiRuntimeSkillRoot,
skill_roots_len: usize,
authority: i32,
request: *const FfiSkillInstallRequest,
result_out: *mut *mut FfiSkillApplyResult,
error_out: *mut FfiOwnedBuffer,
) -> i32 {
clear_error_out(error_out);
clear_out_ptr(result_out);
if result_out.is_null() {
return ffi_error_status(error_out, "result_out must not be null");
}
if request.is_null() {
return ffi_error_status(error_out, "request must not be null");
}
let skill_roots = match parse_skill_roots(skill_roots, skill_roots_len) {
Ok(skill_roots) => skill_roots,
Err(error) => return ffi_error_status(error_out, error),
};
let request = match parse_install_request(unsafe { &*request }) {
Ok(request) => request,
Err(error) => return ffi_error_status(error_out, error),
};
let authority = match parse_skill_management_authority(authority, "authority") {
Ok(authority) => authority,
Err(error) => return ffi_error_status(error_out, error),
};
match with_engine_mut(engine_id, |engine| {
engine
.system_update_skill(&skill_roots, authority, &request)
.map_err(|error| error.to_string())
}) {
Ok(result) => {
unsafe { *result_out = Box::into_raw(Box::new(alloc_skill_apply_result(&result))) };
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[cfg(test)]
mod tests;