use serde::Deserialize;
use std::collections::HashMap;
#[derive(Debug, Default, Deserialize)]
pub struct CartogConfig {
pub database: Option<DatabaseConfig>,
pub embedding: Option<EmbeddingConfig>,
pub reranker: Option<RerankerConfig>,
pub rag: Option<RagConfig>,
pub remote: Option<RemoteConfig>,
pub security: Option<SecurityConfig>,
pub lsp: Option<LspConfig>,
pub index: Option<IndexConfig>,
}
#[derive(Debug, Default, Clone, Deserialize)]
pub struct LspConfig {
#[serde(default, flatten)]
pub langs: HashMap<String, LspLangConfig>,
pub max_concurrent_servers: Option<usize>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct LspLangConfig {
pub command: Vec<String>,
}
#[derive(Debug, Default, Clone, Deserialize)]
pub struct SecurityConfig {
pub redact_secrets: Option<bool>,
}
impl SecurityConfig {
#[must_use]
pub fn redact_secrets(&self) -> bool {
self.redact_secrets.unwrap_or(true)
}
}
#[derive(Debug, Default, Clone, Deserialize)]
pub struct IndexConfig {
pub exclude: Option<Vec<String>>,
pub respect_gitignore: Option<bool>,
pub jobs: Option<usize>,
}
#[derive(Debug, Default, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
#[cfg_attr(not(feature = "remote-s3"), allow(dead_code))]
pub struct RemoteConfig {
pub url: Option<String>,
pub region: Option<String>,
pub endpoint: Option<String>,
pub path_style: Option<bool>,
}
const CREDENTIAL_KEY_PREFIXES: &[&str] = &["aws_", "access_", "secret_"];
const CREDENTIAL_KEYS: &[&str] = &[
"access_key",
"secret_key",
"credentials",
"token",
"session_token",
"password",
];
pub(crate) fn validate_remote_no_credentials(table: &toml::value::Table) -> Result<(), String> {
fn walk(prefix: &str, val: &toml::Value) -> Result<(), String> {
match val {
toml::Value::Table(t) => {
for (k, v) in t {
let lower = k.to_lowercase();
if CREDENTIAL_KEYS.iter().any(|ck| lower == *ck)
|| CREDENTIAL_KEY_PREFIXES.iter().any(|p| lower.starts_with(p))
{
return Err(format!(
"{prefix}.{k} looks like a credential — cartog does not read \
credentials from .cartog.toml. Use the AWS environment chain \
instead (AWS_ACCESS_KEY_ID / AWS_PROFILE / IMDS)."
));
}
walk(&format!("{prefix}.{k}"), v)?;
}
}
toml::Value::Array(arr) => {
for (i, v) in arr.iter().enumerate() {
walk(&format!("{prefix}[{i}]"), v)?;
}
}
_ => {}
}
Ok(())
}
walk("[remote]", &toml::Value::Table(table.clone()))
}
#[derive(Debug, Default, Clone, Deserialize)]
pub struct RagConfig {
pub retrieval_multiplier: Option<u32>,
pub retrieval_floor: Option<u32>,
pub rerank_max: Option<u32>,
pub rerank_min: Option<u32>,
}
impl RagConfig {
pub fn to_search_tuning(&self) -> cartog_rag::search::SearchTuning {
let d = cartog_rag::search::SearchTuning::default();
let retrieval_multiplier = self
.retrieval_multiplier
.unwrap_or(d.retrieval_multiplier)
.max(1);
let retrieval_floor = self.retrieval_floor.unwrap_or(d.retrieval_floor).max(1);
let rerank_max = self.rerank_max.unwrap_or(d.rerank_max);
let rerank_min = self.rerank_min.unwrap_or(d.rerank_min);
let rerank_min = rerank_min.min(rerank_max);
cartog_rag::search::SearchTuning {
retrieval_multiplier,
retrieval_floor,
rerank_max,
rerank_min,
}
}
}
#[derive(Debug, Default, Deserialize)]
pub struct DatabaseConfig {
pub path: Option<String>,
}
#[derive(Debug, Default, Clone, Deserialize)]
pub struct EmbeddingConfig {
pub provider: Option<String>,
pub model: Option<String>,
pub dimension: Option<usize>,
pub local: Option<LocalEmbeddingConfig>,
pub ollama: Option<OllamaConfig>,
pub openai: Option<OpenAiConfig>,
pub max_concurrent_requests: Option<usize>,
pub auto_embed: Option<bool>,
}
pub const DEFAULT_EMBEDDING_PROVIDER: &str = "local";
impl EmbeddingConfig {
pub fn provider(&self) -> &str {
self.provider
.as_deref()
.unwrap_or(DEFAULT_EMBEDDING_PROVIDER)
}
}
#[derive(Debug, Default, Clone, Deserialize)]
pub struct LocalEmbeddingConfig {
pub query_prefix: Option<String>,
pub document_prefix: Option<String>,
pub intra_threads: Option<usize>,
}
#[derive(Debug, Default, Clone, Deserialize)]
pub struct OllamaConfig {
pub base_url: Option<String>,
pub model: Option<String>,
}
pub const DEFAULT_OLLAMA_BASE_URL: &str = cartog_rag::providers::DEFAULT_OLLAMA_BASE_URL;
pub const DEFAULT_OLLAMA_MODEL: &str = cartog_rag::providers::DEFAULT_OLLAMA_MODEL;
impl OllamaConfig {
pub fn base_url(&self) -> &str {
self.base_url.as_deref().unwrap_or(DEFAULT_OLLAMA_BASE_URL)
}
pub fn model(&self) -> &str {
self.model.as_deref().unwrap_or(DEFAULT_OLLAMA_MODEL)
}
}
#[derive(Debug, Default, Clone, Deserialize)]
pub struct OpenAiConfig {
pub base_url: Option<String>,
pub model: Option<String>,
pub api_key_env: Option<String>,
}
pub const DEFAULT_OPENAI_BASE_URL: &str = cartog_rag::providers::DEFAULT_OPENAI_BASE_URL;
pub const DEFAULT_OPENAI_MODEL: &str = cartog_rag::providers::DEFAULT_OPENAI_MODEL;
pub const DEFAULT_OPENAI_API_KEY_ENV: &str = cartog_rag::providers::DEFAULT_OPENAI_API_KEY_ENV;
impl OpenAiConfig {
pub fn base_url(&self) -> &str {
self.base_url.as_deref().unwrap_or(DEFAULT_OPENAI_BASE_URL)
}
pub fn model(&self) -> &str {
self.model.as_deref().unwrap_or(DEFAULT_OPENAI_MODEL)
}
pub fn api_key_env(&self) -> &str {
self.api_key_env
.as_deref()
.unwrap_or(DEFAULT_OPENAI_API_KEY_ENV)
}
}
#[derive(Debug, Default, Clone, Deserialize)]
pub struct RerankerConfig {
pub provider: Option<String>,
pub model: Option<String>,
}
pub const DEFAULT_RERANKER_PROVIDER: &str = "local";
impl RerankerConfig {
pub fn provider(&self) -> &str {
self.provider
.as_deref()
.unwrap_or(DEFAULT_RERANKER_PROVIDER)
}
}