use std::cell::RefCell;
use std::collections::HashMap;
use std::ffi::{CString, c_char};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex, OnceLock};
use serde::Serialize;
use serde::de::DeserializeOwned;
use serde_json::{Value, json};
use crate::ffi_standard::{FfiBorrowedBuffer, FfiOwnedBuffer};
use crate::runtime_help::{RuntimeHelpDetail, RuntimeSkillHelpDescriptor};
use crate::{
LuaEngine, RuntimeEntryDescriptor, RuntimeInvocationResult, SkillApplyResult,
SkillManagementAuthority, SkillUninstallResult,
};
mod requests;
use self::requests::*;
pub(crate) const FFI_VERSION: &str = env!("CARGO_PKG_VERSION");
#[derive(Debug, Serialize)]
struct FfiJsonEnvelope<T: Serialize> {
ok: bool,
#[serde(skip_serializing_if = "Option::is_none")]
result: Option<T>,
#[serde(skip_serializing_if = "Option::is_none")]
error: Option<String>,
}
pub(crate) struct FfiEngineSlot {
pub(crate) engine: Arc<Mutex<LuaEngine>>,
}
impl FfiEngineSlot {
pub(crate) fn new(engine: LuaEngine) -> Self {
Self {
engine: Arc::new(Mutex::new(engine)),
}
}
}
pub(crate) static FFI_ENGINE_REGISTRY: OnceLock<Mutex<HashMap<u64, FfiEngineSlot>>> =
OnceLock::new();
pub(crate) static FFI_ENGINE_COUNTER: AtomicU64 = AtomicU64::new(1);
thread_local! {
static ACTIVE_FFI_ENGINE_IDS: RefCell<Vec<u64>> = const { RefCell::new(Vec::new()) };
}
struct ActiveFfiEngineGuard {
engine_id: u64,
}
impl ActiveFfiEngineGuard {
fn enter(engine_id: u64) -> Result<Self, String> {
ACTIVE_FFI_ENGINE_IDS.with(|active_ids| {
let mut active_ids = active_ids.borrow_mut();
if active_ids.contains(&engine_id) {
return Err(format!(
"FFI engine {} reentrant access is not allowed on the same thread",
engine_id
));
}
active_ids.push(engine_id);
Ok(Self { engine_id })
})
}
}
impl Drop for ActiveFfiEngineGuard {
fn drop(&mut self) {
ACTIVE_FFI_ENGINE_IDS.with(|active_ids| {
let mut active_ids = active_ids.borrow_mut();
if let Some(position) = active_ids
.iter()
.rposition(|active| *active == self.engine_id)
{
active_ids.remove(position);
}
});
}
}
fn default_json_object() -> Value {
Value::Object(serde_json::Map::new())
}
fn default_ffi_runtime_session_timeout_ms() -> u64 {
60_000
}
fn parse_runtime_session_engine_payload(
payload: String,
function_name: &str,
) -> Result<Value, String> {
serde_json::from_str(&payload)
.map_err(|error| format!("{function_name} received invalid engine JSON: {error}"))
}
fn require_json_authority(
authority: Option<SkillManagementAuthority>,
function_name: &str,
) -> Result<SkillManagementAuthority, String> {
authority.ok_or_else(|| {
format!(
"{} requires host-injected authority: use 'system' or 'delegated_tool'",
function_name
)
})
}
pub(crate) fn ffi_engine_registry() -> &'static Mutex<HashMap<u64, FfiEngineSlot>> {
FFI_ENGINE_REGISTRY.get_or_init(|| Mutex::new(HashMap::new()))
}
fn clone_engine_handle(engine_id: u64) -> Result<Arc<Mutex<LuaEngine>>, String> {
let registry = ffi_engine_registry()
.lock()
.map_err(|_| "FFI engine registry lock poisoned".to_string())?;
registry
.get(&engine_id)
.map(|slot| Arc::clone(&slot.engine))
.ok_or_else(|| format!("FFI engine {} not found", engine_id))
}
fn owned_buffer_from_bytes(bytes: &[u8]) -> FfiOwnedBuffer {
if bytes.is_empty() {
return FfiOwnedBuffer {
ptr: std::ptr::null_mut(),
len: 0,
};
}
let mut owned = bytes.to_vec();
let pointer = owned.as_mut_ptr();
let len = owned.len();
std::mem::forget(owned);
FfiOwnedBuffer { ptr: pointer, len }
}
fn encode_json_buffer<T: Serialize>(value: &T) -> FfiOwnedBuffer {
let json_text = serde_json::to_string(value).unwrap_or_else(|error| {
format!(
"{{\"ok\":false,\"error\":\"Failed to serialize FFI response: {}\"}}",
error
)
});
owned_buffer_from_bytes(json_text.as_bytes())
}
fn ffi_ok<T: Serialize>(result: T) -> FfiOwnedBuffer {
encode_json_buffer(&FfiJsonEnvelope {
ok: true,
result: Some(result),
error: None::<String>,
})
}
fn ffi_error(message: impl Into<String>) -> FfiOwnedBuffer {
encode_json_buffer(&FfiJsonEnvelope::<Value> {
ok: false,
result: None,
error: Some(message.into()),
})
}
fn decode_json_request<T: DeserializeOwned>(
input_json: FfiBorrowedBuffer,
function_name: &str,
) -> Result<T, String> {
if input_json.ptr.is_null() {
if input_json.len == 0 {
return Err(format!(
"{} requires one non-null JSON buffer",
function_name
));
}
return Err(format!(
"{} received null JSON buffer with non-zero len",
function_name
));
}
let bytes = unsafe { std::slice::from_raw_parts(input_json.ptr, input_json.len) };
let text = std::str::from_utf8(bytes)
.map_err(|error| format!("{} received invalid UTF-8 input: {}", function_name, error))?;
serde_json::from_str(text)
.map_err(|error| format!("{} received invalid JSON input: {}", function_name, error))
}
pub(crate) fn with_engine<T, F>(engine_id: u64, operation: F) -> Result<T, String>
where
F: FnOnce(&LuaEngine) -> Result<T, String>,
{
let engine_handle = clone_engine_handle(engine_id)?;
let _active_guard = ActiveFfiEngineGuard::enter(engine_id)?;
let engine = engine_handle
.lock()
.map_err(|_| format!("FFI engine {} lock poisoned", engine_id))?;
operation(&engine)
}
pub(crate) fn with_engine_mut<T, F>(engine_id: u64, operation: F) -> Result<T, String>
where
F: FnOnce(&mut LuaEngine) -> Result<T, String>,
{
let engine_handle = clone_engine_handle(engine_id)?;
let _active_guard = ActiveFfiEngineGuard::enter(engine_id)?;
let mut engine = engine_handle
.lock()
.map_err(|_| format!("FFI engine {} lock poisoned", engine_id))?;
operation(&mut engine)
}
pub(crate) fn exported_ffi_function_names() -> Vec<String> {
vec![
"luaskills_ffi_version",
"luaskills_ffi_describe",
"luaskills_ffi_engine_new",
"luaskills_ffi_engine_free",
"luaskills_ffi_load_from_roots",
"luaskills_ffi_reload_from_roots",
"luaskills_ffi_list_entries",
"luaskills_ffi_list_skill_help",
"luaskills_ffi_render_skill_help_detail",
"luaskills_ffi_prompt_argument_completions",
"luaskills_ffi_is_skill",
"luaskills_ffi_skill_name_for_tool",
"luaskills_ffi_skill_config_list",
"luaskills_ffi_skill_config_get",
"luaskills_ffi_skill_config_set",
"luaskills_ffi_skill_config_delete",
"luaskills_ffi_call_skill",
"luaskills_ffi_run_lua",
"luaskills_ffi_disable_skill",
"luaskills_ffi_system_disable_skill",
"luaskills_ffi_enable_skill",
"luaskills_ffi_system_enable_skill",
"luaskills_ffi_uninstall_skill",
"luaskills_ffi_system_uninstall_skill",
"luaskills_ffi_install_skill",
"luaskills_ffi_system_install_skill",
"luaskills_ffi_update_skill",
"luaskills_ffi_system_update_skill",
"luaskills_ffi_set_sqlite_provider_callback",
"luaskills_ffi_set_lancedb_provider_callback",
"luaskills_ffi_set_sqlite_provider_json_callback",
"luaskills_ffi_set_lancedb_provider_json_callback",
"luaskills_ffi_set_host_tool_json_callback",
"luaskills_ffi_set_model_embed_json_callback",
"luaskills_ffi_set_model_llm_json_callback",
"luaskills_ffi_string_clone",
"luaskills_ffi_version_json",
"luaskills_ffi_describe_json",
"luaskills_ffi_engine_new_json",
"luaskills_ffi_engine_free_json",
"luaskills_ffi_load_from_roots_json",
"luaskills_ffi_reload_from_roots_json",
"luaskills_ffi_list_entries_json",
"luaskills_ffi_list_skill_help_json",
"luaskills_ffi_render_skill_help_detail_json",
"luaskills_ffi_prompt_argument_completions_json",
"luaskills_ffi_is_skill_json",
"luaskills_ffi_skill_name_for_tool_json",
"luaskills_ffi_skill_config_list_json",
"luaskills_ffi_skill_config_get_json",
"luaskills_ffi_skill_config_set_json",
"luaskills_ffi_skill_config_delete_json",
"luaskills_ffi_call_skill_json",
"luaskills_ffi_run_lua_json",
"luaskills_ffi_runtime_lease_create_json",
"luaskills_ffi_runtime_lease_eval_json",
"luaskills_ffi_runtime_lease_status_json",
"luaskills_ffi_runtime_lease_list_json",
"luaskills_ffi_runtime_lease_close_json",
"luaskills_ffi_system_runtime_lease_create_json",
"luaskills_ffi_system_runtime_lease_eval_json",
"luaskills_ffi_system_runtime_lease_status_json",
"luaskills_ffi_system_runtime_lease_list_json",
"luaskills_ffi_system_runtime_lease_close_json",
"luaskills_ffi_disable_skill_json",
"luaskills_ffi_system_disable_skill_json",
"luaskills_ffi_enable_skill_json",
"luaskills_ffi_system_enable_skill_json",
"luaskills_ffi_uninstall_skill_json",
"luaskills_ffi_system_uninstall_skill_json",
"luaskills_ffi_install_skill_json",
"luaskills_ffi_system_install_skill_json",
"luaskills_ffi_update_skill_json",
"luaskills_ffi_system_update_skill_json",
"luaskills_ffi_string_free",
"luaskills_ffi_bytes_clone",
"luaskills_ffi_bytes_free",
"luaskills_ffi_buffer_clone",
"luaskills_ffi_buffer_free",
"luaskills_ffi_string_array_free",
"luaskills_ffi_entry_list_free",
"luaskills_ffi_help_list_free",
"luaskills_ffi_help_detail_free",
"luaskills_ffi_invocation_result_free",
"luaskills_ffi_skill_apply_result_free",
"luaskills_ffi_skill_uninstall_result_free",
]
.into_iter()
.map(str::to_string)
.collect()
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_string_free(value: *mut c_char) {
if !value.is_null() {
let _ = unsafe { CString::from_raw(value) };
}
}
#[unsafe(no_mangle)]
pub extern "C" fn luaskills_ffi_version_json() -> FfiOwnedBuffer {
ffi_ok(json!({
"ffi_version": FFI_VERSION,
"protocol": "json-cabi"
}))
}
#[unsafe(no_mangle)]
pub extern "C" fn luaskills_ffi_describe_json() -> FfiOwnedBuffer {
ffi_ok(FfiDescribeJsonResult {
ffi_version: FFI_VERSION.to_string(),
exported_functions: exported_ffi_function_names(),
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_engine_new_json(
input_json: FfiBorrowedBuffer,
) -> FfiOwnedBuffer {
let request = match decode_json_request::<EngineNewJsonRequest>(
input_json,
"luaskills_ffi_engine_new_json",
) {
Ok(request) => request,
Err(error) => return ffi_error(error),
};
match LuaEngine::new(request.options) {
Ok(engine) => {
let engine_id = FFI_ENGINE_COUNTER.fetch_add(1, Ordering::Relaxed);
let mut registry = match ffi_engine_registry().lock() {
Ok(registry) => registry,
Err(_) => return ffi_error("FFI engine registry lock poisoned"),
};
registry.insert(engine_id, FfiEngineSlot::new(engine));
ffi_ok(EngineHandleJsonResult { engine_id })
}
Err(error) => ffi_error(error.to_string()),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_engine_free_json(
input_json: FfiBorrowedBuffer,
) -> FfiOwnedBuffer {
let request = match decode_json_request::<EngineIdJsonRequest>(
input_json,
"luaskills_ffi_engine_free_json",
) {
Ok(request) => request,
Err(error) => return ffi_error(error),
};
let mut registry = match ffi_engine_registry().lock() {
Ok(registry) => registry,
Err(_) => return ffi_error("FFI engine registry lock poisoned"),
};
if registry.remove(&request.engine_id).is_none() {
return ffi_error(format!("FFI engine {} not found", request.engine_id));
}
ffi_ok(json!({ "freed": true }))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_load_from_roots_json(
input_json: FfiBorrowedBuffer,
) -> FfiOwnedBuffer {
let request = match decode_json_request::<EngineRootsJsonRequest>(
input_json,
"luaskills_ffi_load_from_roots_json",
) {
Ok(request) => request,
Err(error) => return ffi_error(error),
};
match with_engine_mut(request.engine_id, |engine| {
engine
.load_from_roots(&request.skill_roots)
.map_err(|error| error.to_string())
}) {
Ok(()) => ffi_ok(json!({ "loaded": true })),
Err(error) => ffi_error(error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_reload_from_roots_json(
input_json: FfiBorrowedBuffer,
) -> FfiOwnedBuffer {
let request = match decode_json_request::<EngineRootsJsonRequest>(
input_json,
"luaskills_ffi_reload_from_roots_json",
) {
Ok(request) => request,
Err(error) => return ffi_error(error),
};
match with_engine_mut(request.engine_id, |engine| {
engine
.reload_from_roots(&request.skill_roots)
.map_err(|error| error.to_string())
}) {
Ok(()) => ffi_ok(json!({ "reloaded": true })),
Err(error) => ffi_error(error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_list_entries_json(
input_json: FfiBorrowedBuffer,
) -> FfiOwnedBuffer {
let request = match decode_json_request::<EngineAuthorityJsonRequest>(
input_json,
"luaskills_ffi_list_entries_json",
) {
Ok(request) => request,
Err(error) => return ffi_error(error),
};
let authority =
match require_json_authority(request.authority, "luaskills_ffi_list_entries_json") {
Ok(authority) => authority,
Err(error) => return ffi_error(error),
};
match with_engine(request.engine_id, |engine| {
Ok(engine.list_entries_for_authority(authority))
}) {
Ok(result) => ffi_ok::<Vec<RuntimeEntryDescriptor>>(result),
Err(error) => ffi_error(error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_list_skill_help_json(
input_json: FfiBorrowedBuffer,
) -> FfiOwnedBuffer {
let request = match decode_json_request::<EngineAuthorityJsonRequest>(
input_json,
"luaskills_ffi_list_skill_help_json",
) {
Ok(request) => request,
Err(error) => return ffi_error(error),
};
let authority =
match require_json_authority(request.authority, "luaskills_ffi_list_skill_help_json") {
Ok(authority) => authority,
Err(error) => return ffi_error(error),
};
match with_engine(request.engine_id, |engine| {
Ok(engine.list_skill_help_for_authority(authority))
}) {
Ok(result) => ffi_ok::<Vec<RuntimeSkillHelpDescriptor>>(result),
Err(error) => ffi_error(error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_render_skill_help_detail_json(
input_json: FfiBorrowedBuffer,
) -> FfiOwnedBuffer {
let request = match decode_json_request::<RenderHelpJsonRequest>(
input_json,
"luaskills_ffi_render_skill_help_detail_json",
) {
Ok(request) => request,
Err(error) => return ffi_error(error),
};
let authority = match require_json_authority(
request.authority,
"luaskills_ffi_render_skill_help_detail_json",
) {
Ok(authority) => authority,
Err(error) => return ffi_error(error),
};
match with_engine(request.engine_id, |engine| {
engine.render_skill_help_detail_for_authority(
authority,
&request.skill_id,
&request.flow_name,
request.request_context.as_ref(),
)
}) {
Ok(result) => ffi_ok::<Option<RuntimeHelpDetail>>(result),
Err(error) => ffi_error(error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_prompt_argument_completions_json(
input_json: FfiBorrowedBuffer,
) -> FfiOwnedBuffer {
let request = match decode_json_request::<PromptCompletionJsonRequest>(
input_json,
"luaskills_ffi_prompt_argument_completions_json",
) {
Ok(request) => request,
Err(error) => return ffi_error(error),
};
let authority = match require_json_authority(
request.authority,
"luaskills_ffi_prompt_argument_completions_json",
) {
Ok(authority) => authority,
Err(error) => return ffi_error(error),
};
match with_engine(request.engine_id, |engine| {
Ok(engine.prompt_argument_completions_for_authority(
authority,
&request.prompt_name,
&request.argument_name,
))
}) {
Ok(result) => ffi_ok::<Option<Vec<String>>>(result),
Err(error) => ffi_error(error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_is_skill_json(
input_json: FfiBorrowedBuffer,
) -> FfiOwnedBuffer {
let request = match decode_json_request::<IsSkillJsonRequest>(
input_json,
"luaskills_ffi_is_skill_json",
) {
Ok(request) => request,
Err(error) => return ffi_error(error),
};
let authority = match require_json_authority(request.authority, "luaskills_ffi_is_skill_json") {
Ok(authority) => authority,
Err(error) => return ffi_error(error),
};
match with_engine(request.engine_id, |engine| {
Ok(engine.is_skill_for_authority(authority, &request.tool_name))
}) {
Ok(value) => ffi_ok(BoolJsonResult { value }),
Err(error) => ffi_error(error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_skill_name_for_tool_json(
input_json: FfiBorrowedBuffer,
) -> FfiOwnedBuffer {
let request = match decode_json_request::<SkillNameForToolJsonRequest>(
input_json,
"luaskills_ffi_skill_name_for_tool_json",
) {
Ok(request) => request,
Err(error) => return ffi_error(error),
};
let authority =
match require_json_authority(request.authority, "luaskills_ffi_skill_name_for_tool_json") {
Ok(authority) => authority,
Err(error) => return ffi_error(error),
};
match with_engine(request.engine_id, |engine| {
Ok(engine.skill_name_for_tool_for_authority(authority, &request.tool_name))
}) {
Ok(skill_id) => ffi_ok(OptionalSkillNameJsonResult { skill_id }),
Err(error) => ffi_error(error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_skill_config_list_json(
input_json: FfiBorrowedBuffer,
) -> FfiOwnedBuffer {
let request = match decode_json_request::<SkillConfigListJsonRequest>(
input_json,
"luaskills_ffi_skill_config_list_json",
) {
Ok(request) => request,
Err(error) => return ffi_error(error),
};
match with_engine(request.engine_id, |engine| {
engine.list_skill_config_entries(request.skill_id.as_deref())
}) {
Ok(entries) => ffi_ok(entries),
Err(error) => ffi_error(error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_skill_config_get_json(
input_json: FfiBorrowedBuffer,
) -> FfiOwnedBuffer {
let request = match decode_json_request::<SkillConfigGetJsonRequest>(
input_json,
"luaskills_ffi_skill_config_get_json",
) {
Ok(request) => request,
Err(error) => return ffi_error(error),
};
match with_engine(request.engine_id, |engine| {
engine.get_skill_config_value(&request.skill_id, &request.key)
}) {
Ok(value) => ffi_ok(SkillConfigGetJsonResult {
found: value.is_some(),
skill_id: request.skill_id,
key: request.key,
value,
}),
Err(error) => ffi_error(error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_skill_config_set_json(
input_json: FfiBorrowedBuffer,
) -> FfiOwnedBuffer {
let request = match decode_json_request::<SkillConfigSetJsonRequest>(
input_json,
"luaskills_ffi_skill_config_set_json",
) {
Ok(request) => request,
Err(error) => return ffi_error(error),
};
match with_engine_mut(request.engine_id, |engine| {
engine.set_skill_config_value(&request.skill_id, &request.key, &request.value)
}) {
Ok(()) => ffi_ok(SkillConfigMutationJsonResult {
action: "set".to_string(),
skill_id: request.skill_id,
key: request.key,
value: Some(request.value),
deleted: None,
}),
Err(error) => ffi_error(error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_skill_config_delete_json(
input_json: FfiBorrowedBuffer,
) -> FfiOwnedBuffer {
let request = match decode_json_request::<SkillConfigGetJsonRequest>(
input_json,
"luaskills_ffi_skill_config_delete_json",
) {
Ok(request) => request,
Err(error) => return ffi_error(error),
};
match with_engine_mut(request.engine_id, |engine| {
engine.delete_skill_config_value(&request.skill_id, &request.key)
}) {
Ok(deleted) => ffi_ok(SkillConfigMutationJsonResult {
action: "delete".to_string(),
skill_id: request.skill_id,
key: request.key,
value: None,
deleted: Some(deleted),
}),
Err(error) => ffi_error(error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_call_skill_json(
input_json: FfiBorrowedBuffer,
) -> FfiOwnedBuffer {
let request = match decode_json_request::<CallSkillJsonRequest>(
input_json,
"luaskills_ffi_call_skill_json",
) {
Ok(request) => request,
Err(error) => return ffi_error(error),
};
match with_engine(request.engine_id, |engine| {
engine.call_skill(
&request.tool_name,
&request.args,
request.invocation_context.as_ref(),
)
}) {
Ok(result) => ffi_ok::<RuntimeInvocationResult>(result),
Err(error) => ffi_error(error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_run_lua_json(
input_json: FfiBorrowedBuffer,
) -> FfiOwnedBuffer {
let request =
match decode_json_request::<RunLuaJsonRequest>(input_json, "luaskills_ffi_run_lua_json") {
Ok(request) => request,
Err(error) => return ffi_error(error),
};
match with_engine(request.engine_id, |engine| {
engine.run_lua(
&request.code,
&request.args,
request.invocation_context.as_ref(),
)
}) {
Ok(result) => ffi_ok::<Value>(result),
Err(error) => ffi_error(error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_runtime_lease_create_json(
input_json: FfiBorrowedBuffer,
) -> FfiOwnedBuffer {
let request = match decode_json_request::<RuntimeSessionCreateJsonRequest>(
input_json,
"luaskills_ffi_runtime_lease_create_json",
) {
Ok(request) => request,
Err(error) => return ffi_error(error),
};
match with_engine(request.engine_id, |engine| {
let payload = json!({
"sid": request.sid,
"ttl_sec": request.ttl_sec,
"replace": request.replace,
"cwd": request.cwd,
"workspace_root": request.workspace_root,
"lua_roots": request.lua_roots,
"c_roots": request.c_roots,
"mounts": request.mounts
});
let response = engine.create_runtime_lease_json(&payload.to_string())?;
parse_runtime_session_engine_payload(response, "luaskills_ffi_runtime_lease_create_json")
}) {
Ok(result) => ffi_ok::<Value>(result),
Err(error) => ffi_error(error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_runtime_lease_eval_json(
input_json: FfiBorrowedBuffer,
) -> FfiOwnedBuffer {
let request = match decode_json_request::<RuntimeSessionEvalJsonRequest>(
input_json,
"luaskills_ffi_runtime_lease_eval_json",
) {
Ok(request) => request,
Err(error) => return ffi_error(error),
};
match with_engine(request.engine_id, |engine| {
let request_context = request
.invocation_context
.as_ref()
.and_then(|context| context.request_context.clone());
let client_budget = request
.invocation_context
.as_ref()
.map(|context| context.client_budget.clone())
.unwrap_or_else(default_json_object);
let tool_config = request
.invocation_context
.as_ref()
.map(|context| context.tool_config.clone())
.unwrap_or_else(default_json_object);
let payload = json!({
"lease_id": request.lease_id,
"sid": request.sid,
"generation": request.generation,
"code": request.code,
"args": request.args,
"timeout_ms": request.timeout_ms,
"request_context": request_context,
"client_budget": client_budget,
"tool_config": tool_config
});
let response = engine.eval_runtime_lease_json(&payload.to_string())?;
parse_runtime_session_engine_payload(response, "luaskills_ffi_runtime_lease_eval_json")
}) {
Ok(result) => ffi_ok::<Value>(result),
Err(error) => ffi_error(error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_runtime_lease_status_json(
input_json: FfiBorrowedBuffer,
) -> FfiOwnedBuffer {
let request = match decode_json_request::<RuntimeSessionLeaseJsonRequest>(
input_json,
"luaskills_ffi_runtime_lease_status_json",
) {
Ok(request) => request,
Err(error) => return ffi_error(error),
};
match with_engine(request.engine_id, |engine| {
let payload = json!({
"lease_id": request.lease_id,
"sid": request.sid,
"generation": request.generation
});
let response = engine.runtime_lease_status_json(&payload.to_string())?;
parse_runtime_session_engine_payload(response, "luaskills_ffi_runtime_lease_status_json")
}) {
Ok(result) => ffi_ok::<Value>(result),
Err(error) => ffi_error(error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_runtime_lease_list_json(
input_json: FfiBorrowedBuffer,
) -> FfiOwnedBuffer {
let request = match decode_json_request::<RuntimeSessionListJsonRequest>(
input_json,
"luaskills_ffi_runtime_lease_list_json",
) {
Ok(request) => request,
Err(error) => return ffi_error(error),
};
match with_engine(request.engine_id, |engine| {
let payload = json!({ "sid": request.sid });
let response = engine.list_runtime_leases_json(&payload.to_string())?;
parse_runtime_session_engine_payload(response, "luaskills_ffi_runtime_lease_list_json")
}) {
Ok(result) => ffi_ok::<Value>(result),
Err(error) => ffi_error(error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_runtime_lease_close_json(
input_json: FfiBorrowedBuffer,
) -> FfiOwnedBuffer {
let request = match decode_json_request::<RuntimeSessionLeaseJsonRequest>(
input_json,
"luaskills_ffi_runtime_lease_close_json",
) {
Ok(request) => request,
Err(error) => return ffi_error(error),
};
match with_engine(request.engine_id, |engine| {
let payload = json!({
"lease_id": request.lease_id,
"sid": request.sid,
"generation": request.generation
});
let response = engine.close_runtime_lease_json(&payload.to_string())?;
parse_runtime_session_engine_payload(response, "luaskills_ffi_runtime_lease_close_json")
}) {
Ok(result) => ffi_ok::<Value>(result),
Err(error) => ffi_error(error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_system_runtime_lease_create_json(
input_json: FfiBorrowedBuffer,
) -> FfiOwnedBuffer {
let request = match decode_json_request::<RuntimeSessionCreateJsonRequest>(
input_json,
"luaskills_ffi_system_runtime_lease_create_json",
) {
Ok(request) => request,
Err(error) => return ffi_error(error),
};
let _authority = match require_json_authority(
request.authority,
"luaskills_ffi_system_runtime_lease_create_json",
) {
Ok(authority) => authority,
Err(error) => return ffi_error(error),
};
match with_engine(request.engine_id, |engine| {
let payload = json!({
"sid": request.sid,
"ttl_sec": request.ttl_sec,
"replace": request.replace,
"cwd": request.cwd,
"workspace_root": request.workspace_root,
"lua_roots": request.lua_roots,
"c_roots": request.c_roots,
"mounts": request.mounts
});
let response = engine.create_system_runtime_lease_json(&payload.to_string())?;
parse_runtime_session_engine_payload(
response,
"luaskills_ffi_system_runtime_lease_create_json",
)
}) {
Ok(result) => ffi_ok::<Value>(result),
Err(error) => ffi_error(error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_system_runtime_lease_eval_json(
input_json: FfiBorrowedBuffer,
) -> FfiOwnedBuffer {
let request = match decode_json_request::<RuntimeSessionEvalJsonRequest>(
input_json,
"luaskills_ffi_system_runtime_lease_eval_json",
) {
Ok(request) => request,
Err(error) => return ffi_error(error),
};
let _authority = match require_json_authority(
request.authority,
"luaskills_ffi_system_runtime_lease_eval_json",
) {
Ok(authority) => authority,
Err(error) => return ffi_error(error),
};
match with_engine(request.engine_id, |engine| {
let request_context = request
.invocation_context
.as_ref()
.and_then(|context| context.request_context.clone());
let client_budget = request
.invocation_context
.as_ref()
.map(|context| context.client_budget.clone())
.unwrap_or_else(default_json_object);
let tool_config = request
.invocation_context
.as_ref()
.map(|context| context.tool_config.clone())
.unwrap_or_else(default_json_object);
let payload = json!({
"lease_id": request.lease_id,
"sid": request.sid,
"generation": request.generation,
"code": request.code,
"args": request.args,
"timeout_ms": request.timeout_ms,
"request_context": request_context,
"client_budget": client_budget,
"tool_config": tool_config
});
let response = engine.eval_system_runtime_lease_json(&payload.to_string())?;
parse_runtime_session_engine_payload(
response,
"luaskills_ffi_system_runtime_lease_eval_json",
)
}) {
Ok(result) => ffi_ok::<Value>(result),
Err(error) => ffi_error(error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_system_runtime_lease_status_json(
input_json: FfiBorrowedBuffer,
) -> FfiOwnedBuffer {
let request = match decode_json_request::<RuntimeSessionLeaseJsonRequest>(
input_json,
"luaskills_ffi_system_runtime_lease_status_json",
) {
Ok(request) => request,
Err(error) => return ffi_error(error),
};
let _authority = match require_json_authority(
request.authority,
"luaskills_ffi_system_runtime_lease_status_json",
) {
Ok(authority) => authority,
Err(error) => return ffi_error(error),
};
match with_engine(request.engine_id, |engine| {
let payload = json!({
"lease_id": request.lease_id,
"sid": request.sid,
"generation": request.generation
});
let response = engine.system_runtime_lease_status_json(&payload.to_string())?;
parse_runtime_session_engine_payload(
response,
"luaskills_ffi_system_runtime_lease_status_json",
)
}) {
Ok(result) => ffi_ok::<Value>(result),
Err(error) => ffi_error(error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_system_runtime_lease_list_json(
input_json: FfiBorrowedBuffer,
) -> FfiOwnedBuffer {
let request = match decode_json_request::<RuntimeSessionListJsonRequest>(
input_json,
"luaskills_ffi_system_runtime_lease_list_json",
) {
Ok(request) => request,
Err(error) => return ffi_error(error),
};
let _authority = match require_json_authority(
request.authority,
"luaskills_ffi_system_runtime_lease_list_json",
) {
Ok(authority) => authority,
Err(error) => return ffi_error(error),
};
match with_engine(request.engine_id, |engine| {
let payload = json!({ "sid": request.sid });
let response = engine.list_system_runtime_leases_json(&payload.to_string())?;
parse_runtime_session_engine_payload(
response,
"luaskills_ffi_system_runtime_lease_list_json",
)
}) {
Ok(result) => ffi_ok::<Value>(result),
Err(error) => ffi_error(error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_system_runtime_lease_close_json(
input_json: FfiBorrowedBuffer,
) -> FfiOwnedBuffer {
let request = match decode_json_request::<RuntimeSessionLeaseJsonRequest>(
input_json,
"luaskills_ffi_system_runtime_lease_close_json",
) {
Ok(request) => request,
Err(error) => return ffi_error(error),
};
let _authority = match require_json_authority(
request.authority,
"luaskills_ffi_system_runtime_lease_close_json",
) {
Ok(authority) => authority,
Err(error) => return ffi_error(error),
};
match with_engine(request.engine_id, |engine| {
let payload = json!({
"lease_id": request.lease_id,
"sid": request.sid,
"generation": request.generation
});
let response = engine.close_system_runtime_lease_json(&payload.to_string())?;
parse_runtime_session_engine_payload(
response,
"luaskills_ffi_system_runtime_lease_close_json",
)
}) {
Ok(result) => ffi_ok::<Value>(result),
Err(error) => ffi_error(error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_disable_skill_json(
input_json: FfiBorrowedBuffer,
) -> FfiOwnedBuffer {
let request = match decode_json_request::<DisableSkillJsonRequest>(
input_json,
"luaskills_ffi_disable_skill_json",
) {
Ok(request) => request,
Err(error) => return ffi_error(error),
};
match with_engine_mut(request.engine_id, |engine| {
engine
.disable_skill_in_roots(
&request.skill_roots,
&request.skill_id,
request.reason.as_deref(),
)
.map_err(|error| error.to_string())
}) {
Ok(()) => ffi_ok(json!({ "disabled": true })),
Err(error) => ffi_error(error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_system_disable_skill_json(
input_json: FfiBorrowedBuffer,
) -> FfiOwnedBuffer {
let request = match decode_json_request::<DisableSkillJsonRequest>(
input_json,
"luaskills_ffi_system_disable_skill_json",
) {
Ok(request) => request,
Err(error) => return ffi_error(error),
};
let authority = match require_json_authority(
request.authority,
"luaskills_ffi_system_disable_skill_json",
) {
Ok(authority) => authority,
Err(error) => return ffi_error(error),
};
match with_engine_mut(request.engine_id, |engine| {
engine
.system_disable_skill_in_roots(
&request.skill_roots,
authority,
&request.skill_id,
request.reason.as_deref(),
)
.map_err(|error| error.to_string())
}) {
Ok(()) => ffi_ok(json!({ "disabled": true })),
Err(error) => ffi_error(error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_enable_skill_json(
input_json: FfiBorrowedBuffer,
) -> FfiOwnedBuffer {
let request = match decode_json_request::<EnableSkillJsonRequest>(
input_json,
"luaskills_ffi_enable_skill_json",
) {
Ok(request) => request,
Err(error) => return ffi_error(error),
};
match with_engine_mut(request.engine_id, |engine| {
engine
.enable_skill(&request.skill_roots, &request.skill_id)
.map_err(|error| error.to_string())
}) {
Ok(()) => ffi_ok(json!({ "enabled": true })),
Err(error) => ffi_error(error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_system_enable_skill_json(
input_json: FfiBorrowedBuffer,
) -> FfiOwnedBuffer {
let request = match decode_json_request::<EnableSkillJsonRequest>(
input_json,
"luaskills_ffi_system_enable_skill_json",
) {
Ok(request) => request,
Err(error) => return ffi_error(error),
};
let authority =
match require_json_authority(request.authority, "luaskills_ffi_system_enable_skill_json") {
Ok(authority) => authority,
Err(error) => return ffi_error(error),
};
match with_engine_mut(request.engine_id, |engine| {
engine
.system_enable_skill(&request.skill_roots, authority, &request.skill_id)
.map_err(|error| error.to_string())
}) {
Ok(()) => ffi_ok(json!({ "enabled": true })),
Err(error) => ffi_error(error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_uninstall_skill_json(
input_json: FfiBorrowedBuffer,
) -> FfiOwnedBuffer {
let request = match decode_json_request::<UninstallSkillJsonRequest>(
input_json,
"luaskills_ffi_uninstall_skill_json",
) {
Ok(request) => request,
Err(error) => return ffi_error(error),
};
match with_engine_mut(request.engine_id, |engine| {
if let Some(target_root) = request.target_root.as_ref() {
engine
.uninstall_skill_in_root(
&request.skill_roots,
target_root,
&request.skill_id,
&request.options,
)
.map_err(|error| error.to_string())
} else {
engine
.uninstall_skill(&request.skill_roots, &request.skill_id, &request.options)
.map_err(|error| error.to_string())
}
}) {
Ok(result) => ffi_ok::<SkillUninstallResult>(result),
Err(error) => ffi_error(error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_system_uninstall_skill_json(
input_json: FfiBorrowedBuffer,
) -> FfiOwnedBuffer {
let request = match decode_json_request::<UninstallSkillJsonRequest>(
input_json,
"luaskills_ffi_system_uninstall_skill_json",
) {
Ok(request) => request,
Err(error) => return ffi_error(error),
};
let authority = match require_json_authority(
request.authority,
"luaskills_ffi_system_uninstall_skill_json",
) {
Ok(authority) => authority,
Err(error) => return ffi_error(error),
};
match with_engine_mut(request.engine_id, |engine| {
if let Some(target_root) = request.target_root.as_ref() {
engine
.system_uninstall_skill_in_root(
&request.skill_roots,
target_root,
authority,
&request.skill_id,
&request.options,
)
.map_err(|error| error.to_string())
} else {
engine
.system_uninstall_skill(
&request.skill_roots,
authority,
&request.skill_id,
&request.options,
)
.map_err(|error| error.to_string())
}
}) {
Ok(result) => ffi_ok::<SkillUninstallResult>(result),
Err(error) => ffi_error(error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_install_skill_json(
input_json: FfiBorrowedBuffer,
) -> FfiOwnedBuffer {
let request = match decode_json_request::<ApplySkillJsonRequest>(
input_json,
"luaskills_ffi_install_skill_json",
) {
Ok(request) => request,
Err(error) => return ffi_error(error),
};
match with_engine_mut(request.engine_id, |engine| {
if let Some(target_root) = request.target_root.as_ref() {
engine
.install_skill_in_root(&request.skill_roots, target_root, &request.request)
.map_err(|error| error.to_string())
} else {
engine
.install_skill(&request.skill_roots, &request.request)
.map_err(|error| error.to_string())
}
}) {
Ok(result) => ffi_ok::<SkillApplyResult>(result),
Err(error) => ffi_error(error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_system_install_skill_json(
input_json: FfiBorrowedBuffer,
) -> FfiOwnedBuffer {
let request = match decode_json_request::<ApplySkillJsonRequest>(
input_json,
"luaskills_ffi_system_install_skill_json",
) {
Ok(request) => request,
Err(error) => return ffi_error(error),
};
let authority = match require_json_authority(
request.authority,
"luaskills_ffi_system_install_skill_json",
) {
Ok(authority) => authority,
Err(error) => return ffi_error(error),
};
match with_engine_mut(request.engine_id, |engine| {
if let Some(target_root) = request.target_root.as_ref() {
engine
.system_install_skill_in_root(
&request.skill_roots,
target_root,
authority,
&request.request,
)
.map_err(|error| error.to_string())
} else {
engine
.system_install_skill(&request.skill_roots, authority, &request.request)
.map_err(|error| error.to_string())
}
}) {
Ok(result) => ffi_ok::<SkillApplyResult>(result),
Err(error) => ffi_error(error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_update_skill_json(
input_json: FfiBorrowedBuffer,
) -> FfiOwnedBuffer {
let request = match decode_json_request::<ApplySkillJsonRequest>(
input_json,
"luaskills_ffi_update_skill_json",
) {
Ok(request) => request,
Err(error) => return ffi_error(error),
};
match with_engine_mut(request.engine_id, |engine| {
if let Some(target_root) = request.target_root.as_ref() {
engine
.update_skill_in_root(&request.skill_roots, target_root, &request.request)
.map_err(|error| error.to_string())
} else {
engine
.update_skill(&request.skill_roots, &request.request)
.map_err(|error| error.to_string())
}
}) {
Ok(result) => ffi_ok::<SkillApplyResult>(result),
Err(error) => ffi_error(error),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaskills_ffi_system_update_skill_json(
input_json: FfiBorrowedBuffer,
) -> FfiOwnedBuffer {
let request = match decode_json_request::<ApplySkillJsonRequest>(
input_json,
"luaskills_ffi_system_update_skill_json",
) {
Ok(request) => request,
Err(error) => return ffi_error(error),
};
let authority =
match require_json_authority(request.authority, "luaskills_ffi_system_update_skill_json") {
Ok(authority) => authority,
Err(error) => return ffi_error(error),
};
match with_engine_mut(request.engine_id, |engine| {
if let Some(target_root) = request.target_root.as_ref() {
engine
.system_update_skill_in_root(
&request.skill_roots,
target_root,
authority,
&request.request,
)
.map_err(|error| error.to_string())
} else {
engine
.system_update_skill(&request.skill_roots, authority, &request.request)
.map_err(|error| error.to_string())
}
}) {
Ok(result) => ffi_ok::<SkillApplyResult>(result),
Err(error) => ffi_error(error),
}
}
#[cfg(test)]
mod tests;