use crate::host::database::{LuaRuntimeDatabaseCallbackMode, LuaRuntimeDatabaseProviderMode};
use crate::runtime_context::RuntimeRequestContext;
use crate::tool_cache::ToolCacheConfig;
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use std::path::PathBuf;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct LuaRuntimeLayout {
pub runtime_root: PathBuf,
}
impl LuaRuntimeLayout {
pub fn new(runtime_root: impl Into<PathBuf>) -> Self {
Self {
runtime_root: runtime_root.into(),
}
}
pub fn bin_dir(&self) -> PathBuf {
self.runtime_root.join("bin")
}
pub fn libs_dir(&self) -> PathBuf {
self.runtime_root.join("libs")
}
pub fn lua_packages_dir(&self) -> PathBuf {
self.runtime_root.join("lua_packages")
}
pub fn resources_dir(&self) -> PathBuf {
self.runtime_root.join("resources")
}
pub fn skills_dir(&self) -> PathBuf {
self.runtime_root.join("skills")
}
pub fn temp_dir(&self) -> PathBuf {
self.runtime_root.join("temp")
}
pub fn downloads_dir(&self) -> PathBuf {
self.temp_dir().join("downloads")
}
pub fn dependencies_dir(&self) -> PathBuf {
self.runtime_root.join("dependencies")
}
pub fn state_dir(&self) -> PathBuf {
self.runtime_root.join("state")
}
pub fn databases_dir(&self) -> PathBuf {
self.runtime_root.join("databases")
}
pub fn config_dir(&self) -> PathBuf {
self.runtime_root.join("config")
}
pub fn skill_config_file_path(&self) -> PathBuf {
self.config_dir().join("skill_config.json")
}
pub fn system_lua_lib_dir(&self) -> PathBuf {
self.runtime_root.join("system_lua_lib")
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(rename_all = "snake_case")]
pub enum LuaRuntimeSpaceControllerProcessMode {
Service,
#[default]
Managed,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct LuaRuntimeSpaceControllerOptions {
pub endpoint: Option<String>,
pub auto_spawn: bool,
pub executable_path: Option<PathBuf>,
pub process_mode: LuaRuntimeSpaceControllerProcessMode,
pub minimum_uptime_secs: u64,
pub idle_timeout_secs: u64,
pub default_lease_ttl_secs: u64,
pub connect_timeout_secs: u64,
pub startup_timeout_secs: u64,
pub startup_retry_interval_ms: u64,
pub lease_renew_interval_secs: u64,
}
impl Default for LuaRuntimeSpaceControllerOptions {
fn default() -> Self {
Self {
endpoint: None,
auto_spawn: true,
executable_path: None,
process_mode: LuaRuntimeSpaceControllerProcessMode::Managed,
minimum_uptime_secs: 300,
idle_timeout_secs: 900,
default_lease_ttl_secs: 120,
connect_timeout_secs: 5,
startup_timeout_secs: 15,
startup_retry_interval_ms: 250,
lease_renew_interval_secs: 30,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct LuaRuntimeCapabilityOptions {
#[serde(default)]
pub enable_skill_management_bridge: bool,
pub enable_managed_io_compat: bool,
}
impl Default for LuaRuntimeCapabilityOptions {
fn default() -> Self {
Self {
enable_skill_management_bridge: false,
enable_managed_io_compat: true,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, serde::Deserialize)]
pub struct RuntimeSkillRoot {
pub name: String,
pub skills_dir: PathBuf,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub struct LuaRuntimeRunLuaPoolConfig {
pub min_size: usize,
pub max_size: usize,
pub idle_ttl_secs: u64,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct LuaRuntimeHostOptions {
#[serde(default)]
pub runtime_root: Option<PathBuf>,
pub temp_dir: Option<PathBuf>,
pub resources_dir: Option<PathBuf>,
pub lua_packages_dir: Option<PathBuf>,
pub host_provided_tool_root: Option<PathBuf>,
pub host_provided_lua_root: Option<PathBuf>,
pub host_provided_ffi_root: Option<PathBuf>,
pub system_lua_lib_dir: Option<PathBuf>,
pub download_cache_root: Option<PathBuf>,
#[serde(default)]
pub dependency_dir_name: String,
#[serde(default)]
pub state_dir_name: String,
#[serde(default)]
pub database_dir_name: String,
pub skill_config_file_path: Option<PathBuf>,
pub allow_network_download: bool,
pub github_base_url: Option<String>,
pub github_api_base_url: Option<String>,
#[serde(default)]
pub official_skill_hub_base_url: Option<String>,
#[serde(default)]
pub enable_private_url_skill_install: bool,
#[serde(default)]
pub private_skill_source_allowlist: Vec<String>,
#[serde(default)]
pub default_text_encoding: Option<String>,
pub sqlite_library_path: Option<PathBuf>,
#[serde(default)]
pub sqlite_provider_mode: LuaRuntimeDatabaseProviderMode,
#[serde(default)]
pub sqlite_callback_mode: LuaRuntimeDatabaseCallbackMode,
pub lancedb_library_path: Option<PathBuf>,
#[serde(default)]
pub lancedb_provider_mode: LuaRuntimeDatabaseProviderMode,
#[serde(default)]
pub lancedb_callback_mode: LuaRuntimeDatabaseCallbackMode,
#[serde(default)]
pub space_controller: LuaRuntimeSpaceControllerOptions,
pub cache_config: Option<ToolCacheConfig>,
#[serde(default)]
pub runlua_pool_config: Option<LuaRuntimeRunLuaPoolConfig>,
pub reserved_entry_names: Vec<String>,
#[serde(default)]
pub ignored_skill_ids: Vec<String>,
#[serde(default)]
pub capabilities: LuaRuntimeCapabilityOptions,
}
impl LuaRuntimeHostOptions {
pub fn with_runtime_root(runtime_root: impl Into<PathBuf>) -> Self {
let mut options = Self {
runtime_root: Some(runtime_root.into()),
..Self::default()
};
options.apply_runtime_root_layout();
options
}
pub fn runtime_layout(&self) -> Option<LuaRuntimeLayout> {
self.runtime_root.clone().map(LuaRuntimeLayout::new)
}
pub fn apply_runtime_root_layout(&mut self) {
let Some(layout) = self.runtime_layout() else {
return;
};
self.temp_dir = Some(layout.temp_dir());
self.resources_dir = Some(layout.resources_dir());
self.lua_packages_dir = Some(layout.lua_packages_dir());
self.host_provided_tool_root = Some(layout.bin_dir());
self.host_provided_lua_root = Some(layout.lua_packages_dir());
self.host_provided_ffi_root = Some(layout.libs_dir());
self.system_lua_lib_dir = Some(layout.system_lua_lib_dir());
self.download_cache_root = Some(layout.downloads_dir());
self.dependency_dir_name = "dependencies".to_string();
self.state_dir_name = "state".to_string();
self.database_dir_name = "databases".to_string();
self.skill_config_file_path = Some(layout.skill_config_file_path());
}
pub fn normalized(mut self) -> Self {
self.apply_runtime_root_layout();
self
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct LuaInvocationContext {
pub request_context: Option<RuntimeRequestContext>,
pub client_budget: Value,
pub tool_config: Value,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
pub struct EffectiveBudgetScope {
pub bytes: u64,
pub lines: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ClientBudgetSnapshot {
pub client_name: Option<String>,
pub tool_name: Option<String>,
pub skill_name: Option<String>,
pub matched_client_pattern: Option<String>,
pub tool_result: EffectiveBudgetScope,
pub file_read: EffectiveBudgetScope,
pub tool_config: Value,
}
#[cfg(test)]
mod tests {
use super::{LuaRuntimeCapabilityOptions, LuaRuntimeHostOptions};
use serde_json::json;
use std::path::PathBuf;
#[test]
fn capability_options_require_explicit_managed_io_compat_flag() {
let error = serde_json::from_value::<LuaRuntimeCapabilityOptions>(json!({
"enable_skill_management_bridge": true
}))
.expect_err("partial capability options should fail without managed io flag");
assert!(
error.to_string().contains("enable_managed_io_compat"),
"missing-field error should mention enable_managed_io_compat, got: {error}"
);
}
#[test]
fn runtime_root_expands_fixed_layout() {
let runtime_root = PathBuf::from("D:/runtime");
let options = LuaRuntimeHostOptions::with_runtime_root(runtime_root.clone());
assert_eq!(options.temp_dir, Some(runtime_root.join("temp")));
assert_eq!(options.resources_dir, Some(runtime_root.join("resources")));
assert_eq!(
options.lua_packages_dir,
Some(runtime_root.join("lua_packages"))
);
assert_eq!(
options.host_provided_tool_root,
Some(runtime_root.join("bin"))
);
assert_eq!(
options.host_provided_lua_root,
Some(runtime_root.join("lua_packages"))
);
assert_eq!(
options.host_provided_ffi_root,
Some(runtime_root.join("libs"))
);
assert_eq!(
options.system_lua_lib_dir,
Some(runtime_root.join("system_lua_lib"))
);
assert_eq!(
options.download_cache_root,
Some(runtime_root.join("temp").join("downloads"))
);
assert_eq!(
options.skill_config_file_path,
Some(runtime_root.join("config").join("skill_config.json"))
);
assert_eq!(options.dependency_dir_name, "dependencies");
assert_eq!(options.state_dir_name, "state");
assert_eq!(options.database_dir_name, "databases");
}
}
impl LuaInvocationContext {
pub fn new(
request_context: Option<RuntimeRequestContext>,
client_budget: Value,
tool_config: Value,
) -> Self {
Self {
request_context,
client_budget: normalize_context_object(client_budget),
tool_config: normalize_context_object(tool_config),
}
}
pub fn empty() -> Self {
Self::default()
}
}
fn normalize_context_object(value: Value) -> Value {
match value {
Value::Object(_) => value,
_ => Value::Object(Map::new()),
}
}