use std::collections::BTreeMap;
use std::fmt;
use std::path::PathBuf;
use defect_agent::error::BoxError;
use defect_agent::session::{
BackgroundProgressConfig, SessionCapabilitiesConfig, TurnConfig, WebSearchCapabilityConfig,
};
use serde::{Deserialize, Serialize};
use toml::Value as TomlValue;
pub(crate) const DEFAULT_ANTHROPIC_MODEL: &str = "claude-sonnet-4-5";
pub(crate) const DEFAULT_OPENAI_MODEL: &str = "gpt-4o-mini";
pub(crate) const DEFAULT_DEEPSEEK_MODEL: &str = "deepseek-chat";
pub(crate) const DEFAULT_ECHO_MODEL: &str = "echo";
pub(crate) const DEFAULT_BASH_TIMEOUT_MS: u64 = 30_000;
pub(crate) const DEFAULT_BASH_MAX_TIMEOUT_MS: u64 = 600_000;
pub(crate) const DEFAULT_BASH_OUTPUT_MAX_BYTES: usize = 1024 * 1024;
pub(crate) const DEFAULT_FS_READ_LIMIT: u32 = 2_000;
pub(crate) const DEFAULT_FS_READ_MAX_LIMIT: u32 = 5_000;
pub(crate) const USER_CONFIG_RELATIVE: &str = "defect/config.toml";
pub(crate) const PROJECT_CONFIG_RELATIVE: &str = ".defect/config.toml";
pub(crate) const PROJECT_LOCAL_CONFIG_RELATIVE: &str = ".defect/config.local.toml";
const PROVIDER_DEFECT: &str = "defect";
const PROVIDER_ANTHROPIC: &str = "anthropic";
const PROVIDER_OPENAI: &str = "openai";
const PROVIDER_DEEPSEEK: &str = "deepseek";
const PROVIDER_LITELLM: &str = "litellm";
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(from = "String", into = "String")]
pub enum ProviderKind {
#[default]
Defect,
Anthropic,
Openai,
Deepseek,
Litellm,
Custom(String),
}
impl ProviderKind {
#[must_use]
pub fn as_str(&self) -> &str {
match self {
Self::Defect => PROVIDER_DEFECT,
Self::Anthropic => PROVIDER_ANTHROPIC,
Self::Openai => PROVIDER_OPENAI,
Self::Deepseek => PROVIDER_DEEPSEEK,
Self::Litellm => PROVIDER_LITELLM,
Self::Custom(value) => value,
}
}
}
impl fmt::Display for ProviderKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl From<ProviderKind> for String {
fn from(value: ProviderKind) -> Self {
value.to_string()
}
}
impl From<String> for ProviderKind {
fn from(value: String) -> Self {
match value.as_str() {
PROVIDER_DEFECT => Self::Defect,
PROVIDER_ANTHROPIC => Self::Anthropic,
PROVIDER_OPENAI => Self::Openai,
PROVIDER_DEEPSEEK => Self::Deepseek,
PROVIDER_LITELLM => Self::Litellm,
_ => Self::Custom(value),
}
}
}
impl From<&str> for ProviderKind {
fn from(value: &str) -> Self {
match value {
PROVIDER_DEFECT => Self::Defect,
PROVIDER_ANTHROPIC => Self::Anthropic,
PROVIDER_OPENAI => Self::Openai,
PROVIDER_DEEPSEEK => Self::Deepseek,
PROVIDER_LITELLM => Self::Litellm,
other => Self::Custom(other.to_string()),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConfigSource {
Defaults,
User,
Project,
ProjectLocal,
Cli,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ConfigLayerEntry {
pub source: ConfigSource,
pub path: Option<PathBuf>,
pub raw_toml: Option<String>,
pub value: TomlValue,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct ConfigLayerStack {
pub layers: Vec<ConfigLayerEntry>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ConfigWarning {
DeprecatedKey {
path: PathBuf,
old: String,
new: String,
},
InactiveSection {
path: PathBuf,
section: String,
reason: String,
},
McpToolRenamed {
server: String,
original: String,
renamed: String,
},
McpJsonOverridden { path: PathBuf, server: String },
}
#[non_exhaustive]
#[derive(Debug, thiserror::Error)]
pub enum ConfigError {
#[error("failed to read config file {path}: {source}")]
Io {
path: PathBuf,
#[source]
source: BoxError,
},
#[error("failed to parse config file {path}: {source}")]
Parse {
path: PathBuf,
#[source]
source: BoxError,
},
#[error("invalid config at {path}: {message}")]
Invalid { path: PathBuf, message: String },
#[error(transparent)]
Source(#[from] BoxError),
}
#[derive(Debug, Clone, Default)]
pub struct CliOverrides {
pub provider: Option<ProviderKind>,
pub model: Option<String>,
pub sandbox: Option<SandboxMode>,
pub config_overrides: Vec<(String, TomlValue)>,
}
#[derive(Debug, Clone, Default)]
pub struct LoadConfigOptions {
pub cwd: PathBuf,
pub cli: CliOverrides,
pub xdg_config_home: Option<PathBuf>,
pub home_dir: Option<PathBuf>,
pub local: bool,
}
#[derive(Debug, Clone)]
pub struct LoadedConfig {
pub layers: ConfigLayerStack,
pub effective: EffectiveConfig,
pub warnings: Vec<ConfigWarning>,
}
#[derive(Debug, Clone)]
pub struct EffectiveConfig {
pub cli: CliConfig,
pub turn: TurnConfig,
pub base_prompt: BasePromptConfigFile,
pub prompt: PromptConfigFile,
pub capabilities: CapabilitiesConfig,
pub providers: ProviderConfigs,
pub tools: ToolsConfig,
pub sandbox: SandboxConfig,
pub tracing: TracingConfig,
pub mcp: McpConfig,
pub http: HttpClientConfig,
pub hooks: HooksConfig,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct HooksConfig {
pub buckets: std::collections::BTreeMap<String, Vec<HookEntry>>,
}
impl HooksConfig {
pub fn is_empty(&self) -> bool {
self.buckets.values().all(Vec::is_empty)
}
pub fn get(&self, event_name: &str) -> &[HookEntry] {
self.buckets
.get(event_name)
.map(Vec::as_slice)
.unwrap_or(&[])
}
pub fn push(&mut self, event_name: impl Into<String>, entry: HookEntry) {
self.buckets
.entry(event_name.into())
.or_default()
.push(entry);
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct HookEntry {
pub name: Option<String>,
pub matcher: HookMatcher,
pub handler: HookHandlerSpec,
pub source: ConfigSource,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
pub struct HookMatcher {
pub tool: Option<String>,
pub tool_glob: Option<String>,
pub safety: Vec<defect_agent::tool::SafetyClass>,
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum HookHandlerSpec {
Builtin { name: String },
Command(HookCommandSpec),
Prompt(HookPromptSpec),
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum HookCommandSpec {
Argv {
argv: Vec<String>,
argv_windows: Option<Vec<String>>,
cwd: Option<PathBuf>,
env: BTreeMap<String, String>,
timeout_sec: Option<u64>,
},
Shell {
shell: HookShellKind,
command: String,
cwd: Option<PathBuf>,
env: BTreeMap<String, String>,
timeout_sec: Option<u64>,
},
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum HookShellKind {
Sh,
Bash,
Pwsh,
Cmd,
Custom {
program: String,
args: Vec<String>,
},
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct HookPromptSpec {
pub model: Option<String>,
pub system: String,
pub render: HookPromptRender,
pub timeout_sec: Option<u64>,
}
impl HookPromptSpec {
#[must_use]
pub fn new(
model: Option<String>,
system: String,
render: HookPromptRender,
timeout_sec: Option<u64>,
) -> Self {
Self {
model,
system,
render,
timeout_sec,
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum HookPromptRender {
Json,
Template { template: String },
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct CapabilitiesConfig {
pub web_search: WebSearchCapabilityConfig,
}
impl CapabilitiesConfig {
#[must_use]
pub const fn with_web_search(web_search: WebSearchCapabilityConfig) -> Self {
Self { web_search }
}
#[must_use]
pub fn to_session_capabilities(self) -> SessionCapabilitiesConfig {
SessionCapabilitiesConfig::with_web_search(self.web_search)
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct ProviderCapabilityOverrides {
pub web_search: Option<WebSearchCapabilityConfig>,
}
impl ProviderCapabilityOverrides {
#[must_use]
pub const fn with_web_search(web_search: Option<WebSearchCapabilityConfig>) -> Self {
Self { web_search }
}
#[must_use]
pub fn merge_into(&self, base: CapabilitiesConfig) -> CapabilitiesConfig {
CapabilitiesConfig::with_web_search(self.web_search.unwrap_or(base.web_search))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CliConfig {
pub provider: ProviderKind,
pub model: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct BasePromptConfigFile {
pub file: Option<PathBuf>,
pub text: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct PromptConfigFile {
pub file: String,
pub text: Option<String>,
pub provider_overlays: BTreeMap<String, String>,
pub model_overlays: BTreeMap<String, String>,
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct ProviderConfigs {
pub anthropic: ProviderConfigFile,
pub openai: ProviderConfigFile,
pub deepseek: ProviderConfigFile,
pub litellm: ProviderConfigFile,
pub custom: BTreeMap<String, ProviderConfigFile>,
}
impl ProviderConfigs {
#[must_use]
pub fn get(&self, provider: &ProviderKind) -> Option<&ProviderConfigFile> {
match provider {
ProviderKind::Defect => None,
ProviderKind::Anthropic => Some(&self.anthropic),
ProviderKind::Openai => Some(&self.openai),
ProviderKind::Deepseek => Some(&self.deepseek),
ProviderKind::Litellm => Some(&self.litellm),
ProviderKind::Custom(name) => self.custom.get(name),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct ToolsConfig {
pub bash: BashToolConfig,
pub fs: FsToolConfig,
pub fetch: FetchToolConfig,
pub search: SearchToolConfig,
pub background: BackgroundProgressConfig,
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FetchToolConfig {
pub enabled: bool,
pub default_timeout_secs: u32,
pub max_timeout_secs: u32,
pub max_response_bytes: u64,
pub default_format: FetchFormat,
pub html_to_markdown: bool,
pub follow_redirects: bool,
}
impl Default for FetchToolConfig {
fn default() -> Self {
Self {
enabled: true,
default_timeout_secs: 30,
max_timeout_secs: 120,
max_response_bytes: 5 * 1024 * 1024,
default_format: FetchFormat::Markdown,
html_to_markdown: true,
follow_redirects: true,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum FetchFormat {
#[default]
Markdown,
Html,
Text,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BashToolConfig {
pub default_timeout_ms: u64,
pub max_timeout_ms: u64,
pub output_max_bytes: usize,
}
impl Default for BashToolConfig {
fn default() -> Self {
Self {
default_timeout_ms: DEFAULT_BASH_TIMEOUT_MS,
max_timeout_ms: DEFAULT_BASH_MAX_TIMEOUT_MS,
output_max_bytes: DEFAULT_BASH_OUTPUT_MAX_BYTES,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FsToolConfig {
pub read_default_limit: u32,
pub read_max_limit: u32,
}
impl Default for FsToolConfig {
fn default() -> Self {
Self {
read_default_limit: DEFAULT_FS_READ_LIMIT,
read_max_limit: DEFAULT_FS_READ_MAX_LIMIT,
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SearchToolConfig {
pub enabled: bool,
pub default_head_limit: u32,
pub max_head_limit: u32,
pub max_file_size_bytes: u64,
pub max_result_bytes: u64,
pub max_walk_files: u64,
pub respect_gitignore_default: bool,
}
impl Default for SearchToolConfig {
fn default() -> Self {
Self {
enabled: true,
default_head_limit: 100,
max_head_limit: 1000,
max_file_size_bytes: 16 * 1024 * 1024,
max_result_bytes: 256 * 1024,
max_walk_files: 100_000,
respect_gitignore_default: true,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SandboxConfig {
pub mode: SandboxMode,
}
impl Default for SandboxConfig {
fn default() -> Self {
Self {
mode: SandboxMode::AskWrites,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "kebab-case")]
pub enum SandboxMode {
ReadOnly,
#[default]
AskWrites,
Open,
DenyAll,
}
impl SandboxMode {
pub fn as_str(self) -> &'static str {
match self {
Self::ReadOnly => "read-only",
Self::AskWrites => "ask-writes",
Self::Open => "open",
Self::DenyAll => "deny-all",
}
}
}
pub type AnthropicConfigFile = ProviderConfigFile;
pub type OpenAiConfigFile = ProviderConfigFile;
pub type DeepSeekConfigFile = ProviderConfigFile;
pub type LiteLlmConfigFile = ProviderConfigFile;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum ProviderProtocol {
AnthropicMessages,
OpenaiChat,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(untagged)]
pub enum ModelEntry {
Id(String),
Detailed {
id: String,
#[serde(default)]
name: Option<String>,
#[serde(default)]
context_window: Option<u64>,
#[serde(default)]
max_output_tokens: Option<u64>,
},
}
impl ModelEntry {
#[must_use]
pub fn id(&self) -> &str {
match self {
Self::Id(id) => id,
Self::Detailed { id, .. } => id,
}
}
#[must_use]
pub fn name(&self) -> Option<&str> {
match self {
Self::Id(_) => None,
Self::Detailed { name, .. } => name.as_deref(),
}
}
#[must_use]
pub fn context_window(&self) -> Option<u64> {
match self {
Self::Id(_) => None,
Self::Detailed { context_window, .. } => *context_window,
}
}
#[must_use]
pub fn max_output_tokens(&self) -> Option<u64> {
match self {
Self::Id(_) => None,
Self::Detailed {
max_output_tokens, ..
} => *max_output_tokens,
}
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct ProviderConfigFile {
pub protocol: Option<ProviderProtocol>,
pub base_url: Option<String>,
pub default_model: Option<String>,
pub models: Option<Vec<ModelEntry>>,
pub display_name: Option<String>,
pub api_key_env: Option<String>,
pub organization: Option<String>,
pub project: Option<String>,
pub aws: Option<ProviderAwsConfigFile>,
pub headers: BTreeMap<String, String>,
pub capabilities: ProviderCapabilityOverrides,
pub reasoning_effort: Option<ReasoningEffort>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ProviderAwsConfigFile {
pub profile: Option<String>,
pub region: Option<String>,
pub anthropic_beta: Option<Vec<String>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ReasoningEffort {
None,
Minimal,
Low,
Medium,
High,
Xhigh,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct HttpClientConfig {
pub total_timeout_ms: Option<u64>,
pub transport_retries: Option<u8>,
pub initial_backoff_ms: Option<u64>,
pub user_agent: Option<String>,
pub proxy: HttpProxyConfig,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HttpProxyConfig {
pub mode: HttpProxyMode,
pub explicit: HttpProxySettings,
}
impl Default for HttpProxyConfig {
fn default() -> Self {
Self {
mode: HttpProxyMode::FromEnv,
explicit: HttpProxySettings::default(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "kebab-case")]
pub enum HttpProxyMode {
#[default]
FromEnv,
Disabled,
Explicit,
}
impl HttpProxyMode {
pub fn as_str(self) -> &'static str {
match self {
Self::FromEnv => "from-env",
Self::Disabled => "disabled",
Self::Explicit => "explicit",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct HttpProxySettings {
pub http_proxy: Option<String>,
pub https_proxy: Option<String>,
pub no_proxy: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct TracingConfig {
pub filter: Option<String>,
pub otlp: Option<OtlpTracingConfig>,
pub langfuse: Option<LangfuseConfig>,
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct OtlpTracingConfig {
pub endpoint: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct LangfuseConfig {
pub enabled: bool,
pub host: Option<String>,
pub public_key: Option<String>,
pub secret_key: Option<String>,
pub flush_interval_ms: Option<u64>,
pub max_batch: Option<usize>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct McpConfig {
pub enabled_servers: Vec<String>,
pub servers: BTreeMap<String, McpServerConfig>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum McpServerConfig {
Stdio(McpStdioServerConfig),
Http(McpRemoteServerConfig),
Sse(McpRemoteServerConfig),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct McpStdioServerConfig {
pub command: String,
pub args: Vec<String>,
pub env: BTreeMap<String, String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct McpRemoteServerConfig {
pub url: String,
pub headers: BTreeMap<String, String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "snake_case")]
pub(crate) enum McpTransportKind {
Stdio,
Http,
Sse,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct ConfigToml {
#[serde(default)]
pub(crate) default: DefaultSection,
#[serde(default)]
pub(crate) base_prompt: BasePromptSection,
#[serde(default)]
pub(crate) prompt: PromptSection,
#[serde(default)]
pub(crate) turn: TurnSection,
#[serde(default)]
pub(crate) capabilities: CapabilitiesSection,
#[serde(default)]
pub(crate) providers: ProvidersSection,
#[serde(default)]
pub(crate) tools: ToolsSection,
#[serde(default)]
pub(crate) sandbox: SandboxSection,
#[serde(default)]
pub(crate) tracing: TracingSection,
#[serde(default)]
pub(crate) mcp: McpSection,
#[serde(default)]
pub(crate) http: HttpSection,
#[serde(default)]
#[allow(dead_code)]
pub(crate) hooks: Option<TomlValue>,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct CapabilitiesSection {
pub(crate) web_search: Option<WebSearchCapabilitySection>,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct WebSearchCapabilitySection {
pub(crate) mode: Option<defect_agent::session::WebSearchCapabilityMode>,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct ProviderCapabilitiesSection {
pub(crate) web_search: Option<WebSearchCapabilitySection>,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct DefaultSection {
pub(crate) provider: Option<ProviderKind>,
pub(crate) model: Option<String>,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct BasePromptSection {
pub(crate) file: Option<String>,
pub(crate) text: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "snake_case")]
pub(crate) enum RequestLimitMode {
Fixed,
Adaptive,
Unbounded,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct TurnSection {
pub(crate) system_prompt: Option<String>,
pub(crate) sampling: Option<SamplingSection>,
pub(crate) request_limit: Option<u32>,
pub(crate) request_limit_mode: Option<RequestLimitMode>,
pub(crate) compact_threshold_tokens: Option<u64>,
pub(crate) compact_ratio: Option<f64>,
pub(crate) background_compact_enabled: Option<bool>,
pub(crate) compact_soft_ratio: Option<f64>,
pub(crate) microcompact_enabled: Option<bool>,
pub(crate) microcompact_ratio: Option<f64>,
pub(crate) max_llm_retries: Option<u32>,
pub(crate) max_concurrent_tools: Option<usize>,
pub(crate) max_hook_continues: Option<u32>,
pub(crate) subagent_max_depth: Option<u32>,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct SamplingSection {
pub(crate) max_tokens: Option<u32>,
pub(crate) temperature: Option<f32>,
pub(crate) top_p: Option<f32>,
pub(crate) top_k: Option<u32>,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct PromptSection {
pub(crate) file: Option<String>,
pub(crate) text: Option<String>,
pub(crate) providers: Option<BTreeMap<String, PromptOverlaySection>>,
pub(crate) models: Option<BTreeMap<String, String>>,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct PromptOverlaySection {
pub(crate) text: Option<String>,
}
#[derive(Debug, Clone, Default, Deserialize)]
pub(crate) struct ProvidersSection {
pub(crate) anthropic: Option<AnthropicProviderSection>,
pub(crate) openai: Option<OpenAiProviderSection>,
pub(crate) deepseek: Option<DeepSeekProviderSection>,
pub(crate) litellm: Option<LiteLlmProviderSection>,
#[serde(flatten)]
pub(crate) custom: BTreeMap<String, ProviderSection>,
}
pub(crate) type AnthropicProviderSection = ProviderSection;
pub(crate) type OpenAiProviderSection = ProviderSection;
pub(crate) type DeepSeekProviderSection = ProviderSection;
pub(crate) type LiteLlmProviderSection = ProviderSection;
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct ProviderSection {
pub(crate) protocol: Option<ProviderProtocol>,
pub(crate) base_url: Option<String>,
pub(crate) default_model: Option<String>,
pub(crate) models: Option<Vec<ModelEntry>>,
pub(crate) display_name: Option<String>,
pub(crate) api_key_env: Option<String>,
pub(crate) organization: Option<String>,
pub(crate) project: Option<String>,
pub(crate) aws: Option<ProviderAwsConfigFile>,
pub(crate) headers: Option<BTreeMap<String, String>>,
pub(crate) capabilities: Option<ProviderCapabilitiesSection>,
pub(crate) reasoning_effort: Option<ReasoningEffort>,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct ToolsSection {
pub(crate) bash: Option<BashToolSection>,
pub(crate) fs: Option<FsToolSection>,
pub(crate) fetch: Option<FetchToolSection>,
pub(crate) search: Option<SearchToolSection>,
pub(crate) background: Option<BackgroundToolSection>,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct BackgroundToolSection {
pub(crate) default_recent_blocks: Option<usize>,
pub(crate) block_text_limit: Option<usize>,
pub(crate) finished_tasks_cap: Option<usize>,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct SearchToolSection {
pub(crate) enabled: Option<bool>,
pub(crate) default_head_limit: Option<u32>,
pub(crate) max_head_limit: Option<u32>,
pub(crate) max_file_size_bytes: Option<u64>,
pub(crate) max_result_bytes: Option<u64>,
pub(crate) max_walk_files: Option<u64>,
pub(crate) respect_gitignore_default: Option<bool>,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct FetchToolSection {
pub(crate) enabled: Option<bool>,
pub(crate) default_timeout_secs: Option<u32>,
pub(crate) max_timeout_secs: Option<u32>,
pub(crate) max_response_bytes: Option<u64>,
pub(crate) default_format: Option<FetchFormat>,
pub(crate) html_to_markdown: Option<bool>,
pub(crate) follow_redirects: Option<bool>,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct BashToolSection {
pub(crate) default_timeout_ms: Option<u64>,
pub(crate) max_timeout_ms: Option<u64>,
pub(crate) output_max_bytes: Option<usize>,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct FsToolSection {
pub(crate) read_default_limit: Option<u32>,
pub(crate) read_max_limit: Option<u32>,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct SandboxSection {
pub(crate) mode: Option<SandboxMode>,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct TracingSection {
pub(crate) filter: Option<String>,
pub(crate) otlp: Option<OtlpTracingSection>,
pub(crate) langfuse: Option<LangfuseSection>,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct OtlpTracingSection {
pub(crate) endpoint: Option<String>,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub(crate) struct LangfuseSection {
pub(crate) enabled: Option<bool>,
pub(crate) host: Option<String>,
pub(crate) public_key: Option<String>,
pub(crate) secret_key: Option<String>,
pub(crate) flush_interval_ms: Option<u64>,
pub(crate) max_batch: Option<usize>,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct McpSection {
pub(crate) enabled_servers: Option<Vec<String>>,
pub(crate) servers: Option<BTreeMap<String, McpServerSection>>,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct HttpSection {
pub(crate) total_timeout_ms: Option<u64>,
pub(crate) transport_retries: Option<u8>,
pub(crate) initial_backoff_ms: Option<u64>,
pub(crate) user_agent: Option<String>,
pub(crate) proxy: Option<HttpProxySection>,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct HttpProxySection {
pub(crate) mode: Option<HttpProxyMode>,
pub(crate) http_proxy: Option<String>,
pub(crate) https_proxy: Option<String>,
pub(crate) no_proxy: Option<Vec<String>>,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct McpServerSection {
pub(crate) transport: Option<McpTransportKind>,
pub(crate) command: Option<String>,
pub(crate) args: Option<Vec<String>>,
pub(crate) env: Option<BTreeMap<String, String>>,
pub(crate) url: Option<String>,
pub(crate) headers: Option<BTreeMap<String, String>>,
}