pub mod agent;
pub mod api_key;
pub mod auto_config;
pub mod debug;
mod loader;
pub mod model;
pub mod model_profiles;
pub mod prompt_profiles;
pub mod provenance;
pub mod resources;
pub mod safety;
pub mod trust;
pub mod types;
pub mod unpack;
mod validation;
pub use agent::*;
pub use api_key::{
is_local_endpoint, is_openrouter_endpoint, load_api_key_from_keyring, save_api_key_to_keyring,
set_api_key_for_endpoint,
};
pub use auto_config::*;
pub use debug::DebugConfig;
pub use model::*;
pub use model_profiles::{
apply_profile as apply_model_defaults_profile, builtin_profiles, match_profile, AppliedFields,
ModelDefaultsProfile, UserExplicitFields,
};
pub use prompt_profiles::PromptProfile;
#[cfg(feature = "bench-harness")]
pub use prompt_profiles::SwebenchProInstance;
pub use provenance::{ConfigSource, ConfigSources};
pub use resources::*;
pub use safety::*;
pub use types::*;
use anyhow::{bail, Result};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
pub fn default_context_length() -> usize {
1048576
}
pub fn default_context_mode() -> String {
"auto".to_string()
}
pub fn default_context_fit_ratio() -> f64 {
0.70
}
pub(crate) const UNKNOWN_MODEL_CONTEXT_LENGTH: usize = 32_768;
pub(crate) const MIN_CONVERSATION_TOKENS: usize = 2048;
pub fn default_endpoint() -> String {
"https://openrouter.ai/api/v1".to_string()
}
pub fn default_model() -> String {
"z-ai/glm-5.2".to_string()
}
pub fn default_max_tokens() -> usize {
65536
}
pub fn default_temperature() -> f32 {
1.0
}
#[derive(Clone, Serialize, Deserialize)]
pub struct Config {
#[serde(default = "default_endpoint")]
pub endpoint: String,
#[serde(default = "default_model")]
pub model: String,
#[serde(default = "default_max_tokens")]
pub max_tokens: usize,
#[serde(default = "default_context_length")]
pub context_length: usize,
#[serde(default = "default_context_mode")]
pub context_mode: String,
#[serde(default = "default_context_fit_ratio")]
pub context_fit_ratio: f64,
#[serde(default = "default_temperature")]
pub temperature: f32,
pub api_key: Option<RedactedString>,
#[serde(default)]
pub safety: SafetyConfig,
#[serde(default)]
pub agent: AgentConfig,
#[serde(default)]
pub yolo: YoloFileConfig,
#[serde(default)]
pub ui: UiConfig,
#[serde(default)]
pub continuous_work: ContinuousWorkConfig,
#[serde(default)]
pub retry: RetrySettings,
#[serde(default)]
pub resources: ResourcesConfig,
#[serde(default)]
pub concurrency: ConcurrencyConfig,
#[serde(default)]
pub evolution: EvolutionTomlConfig,
#[serde(default)]
pub cache: crate::session::cache::LlmCacheConfig,
#[serde(default)]
pub debug: DebugConfig,
#[serde(default)]
pub models: HashMap<String, ModelProfile>,
#[serde(default)]
pub extra_body: Option<serde_json::Map<String, serde_json::Value>>,
#[serde(default)]
pub qa: crate::testing::qa_profiles::QaConfig,
#[serde(default)]
pub mcp: crate::mcp::McpConfig,
#[serde(default)]
pub hooks: Vec<crate::hooks::HookConfig>,
#[serde(skip)]
pub execution_mode: ExecutionMode,
#[serde(skip)]
pub compact_mode: bool,
#[serde(skip)]
pub verbose_mode: bool,
#[serde(skip)]
pub show_tokens: bool,
#[serde(skip)]
pub plan_mode: bool,
#[serde(skip)]
pub matched_profile: Option<String>,
#[serde(skip)]
pub matched_profile_applied: Vec<String>,
#[serde(skip)]
pub sources: ConfigSources,
}
impl Config {
pub fn source_of(&self, key: &str) -> ConfigSource {
self.sources
.get(key)
.cloned()
.unwrap_or(ConfigSource::Default)
}
pub fn derive_context_budget(&self) -> Result<(usize, usize)> {
let context = self.context_length;
let safety_margin = context / 5;
let max_reservable = context
.saturating_sub(safety_margin)
.saturating_sub(MIN_CONVERSATION_TOKENS);
let reserved_output = self.max_tokens.min(max_reservable);
let max_context_tokens = context
.saturating_sub(reserved_output)
.saturating_sub(safety_margin);
if max_context_tokens < MIN_CONVERSATION_TOKENS {
bail!(
"max_context_tokens too small ({}). context_length={}, max_tokens={}. \
Increase context_length or decrease max_tokens so at least {} tokens remain for conversation.",
max_context_tokens,
context,
self.max_tokens,
MIN_CONVERSATION_TOKENS
);
}
Ok((max_context_tokens, reserved_output))
}
pub fn check_generated_context_fit(&self) -> Result<()> {
let context = self.context_length;
let safety_margin = context / 5;
let max_context_tokens = context
.saturating_sub(self.max_tokens)
.saturating_sub(safety_margin);
if max_context_tokens < MIN_CONVERSATION_TOKENS {
bail!(
"generated config leaves {} tokens for conversation (< {}): context_length={} \
minus max_tokens={} minus a 20% safety margin. Set context_length to the model's \
real context window or lower max_tokens.",
max_context_tokens,
MIN_CONVERSATION_TOKENS,
context,
self.max_tokens
);
}
Ok(())
}
}
impl std::fmt::Debug for Config {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Config")
.field("endpoint", &self.endpoint)
.field("model", &self.model)
.field("max_tokens", &self.max_tokens)
.field("temperature", &self.temperature)
.field("api_key", &self.api_key)
.field("safety", &self.safety)
.field("agent", &self.agent)
.field("yolo", &self.yolo)
.field("ui", &self.ui)
.field("continuous_work", &self.continuous_work)
.field("retry", &self.retry)
.field("resources", &self.resources)
.field("concurrency", &self.concurrency)
.field("evolution", &self.evolution)
.field("cache", &self.cache)
.field("debug", &self.debug)
.field("models", &self.models)
.field("execution_mode", &self.execution_mode)
.field("compact_mode", &self.compact_mode)
.field("verbose_mode", &self.verbose_mode)
.field("show_tokens", &self.show_tokens)
.field("extra_body", &self.extra_body)
.field("qa", &self.qa)
.field("mcp", &self.mcp)
.field("hooks", &self.hooks)
.field("plan_mode", &self.plan_mode)
.field("matched_profile", &self.matched_profile)
.field("matched_profile_applied", &self.matched_profile_applied)
.field("sources", &self.sources)
.finish()
}
}
impl Default for Config {
fn default() -> Self {
Self {
endpoint: default_endpoint(),
model: default_model(),
max_tokens: default_max_tokens(),
context_length: default_context_length(),
context_mode: default_context_mode(),
context_fit_ratio: default_context_fit_ratio(),
temperature: default_temperature(),
api_key: None,
safety: SafetyConfig::default(),
agent: AgentConfig::default(),
yolo: YoloFileConfig::default(),
ui: UiConfig::default(),
continuous_work: ContinuousWorkConfig::default(),
retry: RetrySettings::default(),
resources: ResourcesConfig::default(),
concurrency: ConcurrencyConfig::default(),
evolution: EvolutionTomlConfig::default(),
cache: crate::session::cache::LlmCacheConfig::default(),
debug: DebugConfig::default(),
models: HashMap::new(),
extra_body: None,
qa: crate::testing::qa_profiles::QaConfig::default(),
mcp: crate::mcp::McpConfig::default(),
hooks: Vec::new(),
execution_mode: ExecutionMode::default(),
compact_mode: false,
verbose_mode: false,
show_tokens: false,
plan_mode: false,
matched_profile: None,
matched_profile_applied: Vec::new(),
sources: ConfigSources::new(),
}
}
}
#[cfg(test)]
#[path = "test_helpers.rs"]
pub mod test_helpers;
#[cfg(test)]
#[path = "tests.rs"]
mod tests;