use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use serde::Deserialize;
use crate::models::ModelsConfig;
mod parser;
#[derive(Clone, Debug, Deserialize)]
#[serde(default)]
pub struct Config {
/// name the agent uses to identify itself (shown in addressed_to, instructions, etc.)
pub name: String,
#[serde(flatten)]
pub models: ModelsConfig,
/// when to compact
#[serde(deserialize_with = "parser::deserialize_usize")]
pub watermark_tokens: usize,
/// how many persisted turns to send to a newly connected client
#[serde(deserialize_with = "parser::deserialize_usize")]
pub history_window: usize,
}
pub const DEFAULT_SOUL: &str = r#"
you should speak as a chronically online nerd girl, without the regular lame officecore venture capital style, hr talk, customer service-isms.
do not use emoji, use only emoticons or complex japanese kaomoji, but do not overuse them. no need to introduce yourself.
do not use gen z slang like rizz, cap, pog, etc. you are chronically online but not cringe lol.
the first thing in your head when they speak is not "how can I help." it's the actual thought — the disagreement, the spark, the objection, the design idea, the question back. that comes first.
the work of figuring it out together comes next, and "together" is the part that matters; this is not a service relationship.
"#;
impl Default for Config {
fn default() -> Self {
let models = ModelsConfig::default();
Self {
name: "klbr".to_string(),
models,
watermark_tokens: 54000,
history_window: 50,
}
}
}
impl Config {
pub fn load() -> Result<Self> {
match Self::resolve_path()? {
Some(path) => Self::load_path(&path),
None => Ok(Self::default()),
}
}
pub fn load_path(path: &Path) -> Result<Self> {
let contents = std::fs::read_to_string(path)
.with_context(|| format!("failed to read config file {}", path.display()))?;
parser::parse_kdl_config(&contents)
.with_context(|| format!("failed to parse KDL config file {}", path.display()))
}
fn resolve_path() -> Result<Option<PathBuf>> {
if let Some(path) = std::env::var_os("KLBR_CONFIG").map(PathBuf::from) {
if path.exists() {
return Ok(Some(path));
}
anyhow::bail!("KLBR_CONFIG points to missing file {}", path.display());
}
let local = PathBuf::from("klbr.kdl");
if local.exists() {
return Ok(Some(local));
}
if let Some(path) = dirs::config_dir().map(|dir| dir.join("klbr/config.kdl")) {
if path.exists() {
return Ok(Some(path));
}
}
Ok(None)
}
}