use anyhow::Result;
use mlua::prelude::*;
use std::collections::HashMap;
use std::path::Path;
#[derive(Debug, Clone)]
pub enum CommandDef {
String(String),
Function(Vec<u8>),
}
#[derive(Debug, Clone)]
pub struct KotaConfig {
pub model: String,
pub api_key: String,
pub api_base: String,
pub temperature: Option<f64>,
pub enabled_tools: Vec<String>,
pub disabled_tools: Vec<String>,
pub commands: HashMap<String, CommandDef>,
}
impl Default for KotaConfig {
fn default() -> Self {
Self {
model: "gpt-4o".to_string(),
api_key: String::new(),
api_base: "https://api.openai.com/v1".to_string(),
temperature: Some(0.7),
enabled_tools: vec![],
disabled_tools: vec![],
commands: HashMap::new(),
}
}
}
impl KotaConfig {
pub fn from_lua_file<P: AsRef<Path>>(config_path: P) -> Result<Self> {
let lua = Lua::new();
let mut config = Self::default();
let config_content = std::fs::read_to_string(config_path.as_ref())
.map_err(|e| anyhow::anyhow!("Failed to read config file: {}", e))?;
lua.load(
r#"
_kota_config = nil
kota = {
setup = function(args)
_kota_config = args
end
}
-- Provide os.getenv functionality
if not os then os = {} end
os.getenv = function(name)
return _rust_getenv(name)
end
"#,
)
.exec()?;
let globals = lua.globals();
globals.set(
"_rust_getenv",
lua.create_function(|_, name: String| Ok(std::env::var(&name).ok()))?,
)?;
lua.load(&config_content)
.exec()
.map_err(|e| anyhow::anyhow!("Failed to execute Lua config: {}", e))?;
Self::parse_from_lua(&lua, &mut config)?;
Ok(config)
}
fn parse_from_lua(lua: &Lua, config: &mut KotaConfig) -> Result<()> {
let captured: LuaTable = lua
.globals()
.get("_kota_config")
.map_err(|e| anyhow::anyhow!("Config not properly initialized: {}", e))?;
if let Ok(model) = captured.get::<_, String>("model") {
config.model = model;
}
if let Ok(api_key) = captured.get::<_, String>("api_key") {
config.api_key = api_key;
}
if let Ok(api_base) = captured.get::<_, String>("api_base") {
config.api_base = api_base;
}
if let Ok(temp) = captured.get::<_, f64>("temperature") {
config.temperature = Some(temp);
}
if let Ok(tools) = captured.get::<_, LuaTable>("tools") {
if let Ok(enabled) = tools.get::<&str, LuaTable>("enabled") {
for pair in enabled.pairs::<LuaValue, String>() {
if let Ok((_, tool)) = pair {
config.enabled_tools.push(tool);
}
}
}
if let Ok(disabled) = tools.get::<&str, LuaTable>("disabled") {
for pair in disabled.pairs::<LuaValue, String>() {
if let Ok((_, tool)) = pair {
config.disabled_tools.push(tool);
}
}
}
}
if let Ok(commands) = captured.get::<_, LuaTable>("commands") {
for pair in commands.pairs::<String, LuaValue>() {
if let Ok((name, value)) = pair {
match value {
LuaValue::String(s) => {
config
.commands
.insert(name, CommandDef::String(s.to_str()?.to_string()));
}
LuaValue::Function(func) => {
let bytecode = func.dump(false);
config.commands.insert(name, CommandDef::Function(bytecode));
}
_ => {
}
}
}
}
}
Ok(())
}
pub fn load() -> Result<Self> {
let config_path = ".kota/config.lua";
if !Path::new(config_path).exists() {
return Err(anyhow::anyhow!(
"Configuration file not found: {}\n\
Please create a .kota/config.lua file. ",
config_path
));
}
Self::from_lua_file(config_path)
}
}