use serde_json::{Map, Value};
use std::fs;
use std::path::{Path, PathBuf};
use crate::preset::Preset;
pub fn generate_runtime_config(
preset: &Preset,
port: u16,
mount_point: &Path,
) -> Result<PathBuf, color_eyre::Report> {
let home = std::env::var("HOME")?;
let template_path = PathBuf::from(&home).join(".config/muthr/opencode-config.json");
let content = fs::read_to_string(&template_path)?;
let mut config: Value = serde_json::from_str(&content)?;
let primary_slot = preset
.slots
.first()
.ok_or_else(|| color_eyre::eyre::eyre!("No slots found in preset"))?;
let model_id = format!("01-{}", primary_slot.name);
let ctx_window = primary_slot.ctx_size.unwrap_or(200000);
if let Some(obj) = config.as_object_mut() {
obj.insert(
"model".to_string(),
Value::String(format!("llama-cpp/{}", model_id)),
);
obj.insert(
"small_model".to_string(),
Value::String(format!("llama-cpp/{}", model_id)),
);
if let Some(provider) = obj.get_mut("provider").and_then(|p| p.get_mut("llama-cpp")) {
if let Some(p_obj) = provider.as_object_mut() {
let mut options = Map::new();
options.insert(
"baseURL".to_string(),
Value::String(format!("http://host.lima.internal:{}/v1", port)),
);
p_obj.insert("options".to_string(), Value::Object(options));
let mut models_map = Map::new();
let mut inner_model = Map::new();
inner_model.insert("name".to_string(), Value::String(model_id.clone()));
inner_model.insert("tools".to_string(), Value::Bool(true));
inner_model.insert(
"context_window".to_string(),
Value::Number(ctx_window.into()),
);
let mut limit_map = Map::new();
limit_map.insert("context".to_string(), Value::Number(ctx_window.into()));
limit_map.insert("output".to_string(), Value::Number(8192.into()));
inner_model.insert("limit".to_string(), Value::Object(limit_map));
models_map.insert(model_id, Value::Object(inner_model));
p_obj.insert("models".to_string(), Value::Object(models_map));
}
}
if let Some(mcp) = obj.get_mut("mcp").and_then(|m| m.get_mut("filesystem")) {
if let Some(m_obj) = mcp.as_object_mut() {
m_obj.insert(
"command".to_string(),
serde_json::json!(["mcp-server-filesystem", mount_point.to_string_lossy()]),
);
}
}
}
let runtime_dir = PathBuf::from(&home).join(".cache/muthr/opencode_runtimes");
fs::create_dir_all(&runtime_dir)?;
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_secs();
let runtime_path = runtime_dir.join(format!("opencode-runtime-{}.json", timestamp));
fs::write(&runtime_path, serde_json::to_string_pretty(&config)?)?;
Ok(runtime_path)
}
pub fn generate_host_config(
preset: &Preset,
port: u16,
) -> Result<serde_json::Value, color_eyre::Report> {
let primary_slot = preset
.slots
.first()
.ok_or_else(|| color_eyre::eyre::eyre!("No slots found in preset"))?;
let model_id = format!("01-{}", primary_slot.name);
let mut models_config = serde_json::Map::new();
let mut model_entry = serde_json::Map::new();
model_entry.insert(
"name".to_string(),
serde_json::json!(primary_slot.name.clone()),
);
model_entry.insert("tools".to_string(), serde_json::json!(true));
let ctx_window = primary_slot.ctx_size.unwrap_or(200000);
model_entry.insert("context_window".to_string(), serde_json::json!(ctx_window));
let mut limit = serde_json::Map::new();
limit.insert("context".to_string(), serde_json::json!(ctx_window));
limit.insert("output".to_string(), serde_json::json!(8192));
model_entry.insert("limit".to_string(), serde_json::Value::Object(limit));
models_config.insert(model_id.clone(), serde_json::Value::Object(model_entry));
for slot in &preset.slots {
if slot.index > 1 {
let mut slot_entry = serde_json::Map::new();
slot_entry.insert("name".to_string(), serde_json::json!(slot.name.clone()));
slot_entry.insert("tools".to_string(), serde_json::json!(true));
slot_entry.insert(
"context_window".to_string(),
serde_json::json!(slot.ctx_size.unwrap_or(16384)),
);
let mut limit = serde_json::Map::new();
limit.insert(
"context".to_string(),
serde_json::json!(slot.ctx_size.unwrap_or(16384)),
);
limit.insert("output".to_string(), serde_json::json!(8192));
slot_entry.insert("limit".to_string(), serde_json::Value::Object(limit));
let slot_id = format!("0{}-{}", slot.index, slot.name);
models_config.insert(slot_id, serde_json::Value::Object(slot_entry));
}
}
let mut provider_map = serde_json::Map::new();
provider_map.insert(
"npm".to_string(),
serde_json::json!("@ai-sdk/openai-compatible"),
);
provider_map.insert(
"name".to_string(),
serde_json::json!("llama-cpp (localhost)"),
);
let mut options = serde_json::Map::new();
let config_port = preset.global.port.unwrap_or(port as u32) as u16;
options.insert(
"baseURL".to_string(),
serde_json::json!(format!("http://127.0.0.1:{}/v1", config_port)),
);
provider_map.insert("options".to_string(), serde_json::Value::Object(options));
provider_map.insert(
"models".to_string(),
serde_json::Value::Object(models_config),
);
let mut config = serde_json::Map::new();
config.insert(
"model".to_string(),
serde_json::json!(format!("llama-cpp/{}", model_id)),
);
config.insert(
"provider".to_string(),
serde_json::Value::Object(provider_map),
);
Ok(serde_json::Value::Object(config))
}