1use std::collections::BTreeMap;
2use std::fmt;
3use std::path::PathBuf;
4
5use defect_agent::error::BoxError;
6use defect_agent::session::{
7 BackgroundProgressConfig, SessionCapabilitiesConfig, TurnConfig, WebSearchCapabilityConfig,
8};
9use serde::{Deserialize, Serialize};
10use toml::Value as TomlValue;
11
12pub(crate) const DEFAULT_ANTHROPIC_MODEL: &str = "claude-sonnet-4-5";
13pub(crate) const DEFAULT_OPENAI_MODEL: &str = "gpt-4o-mini";
14pub(crate) const DEFAULT_DEEPSEEK_MODEL: &str = "deepseek-chat";
15pub(crate) const DEFAULT_ECHO_MODEL: &str = "echo";
16pub(crate) const DEFAULT_BASH_TIMEOUT_MS: u64 = 30_000;
17pub(crate) const DEFAULT_BASH_MAX_TIMEOUT_MS: u64 = 600_000;
18pub(crate) const DEFAULT_BASH_OUTPUT_MAX_BYTES: usize = 1024 * 1024;
19pub(crate) const DEFAULT_FS_READ_LIMIT: u32 = 2_000;
20pub(crate) const DEFAULT_FS_READ_MAX_LIMIT: u32 = 5_000;
21
22pub(crate) const USER_CONFIG_RELATIVE: &str = "defect/config.toml";
23pub(crate) const PROJECT_CONFIG_RELATIVE: &str = ".defect/config.toml";
24pub(crate) const PROJECT_LOCAL_CONFIG_RELATIVE: &str = ".defect/config.local.toml";
25
26const PROVIDER_DEFECT: &str = "defect";
27const PROVIDER_ANTHROPIC: &str = "anthropic";
28const PROVIDER_OPENAI: &str = "openai";
29const PROVIDER_DEEPSEEK: &str = "deepseek";
30const PROVIDER_LITELLM: &str = "litellm";
31
32#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
33#[serde(from = "String", into = "String")]
34pub enum ProviderKind {
35 #[default]
40 Defect,
41 Anthropic,
42 Openai,
43 Deepseek,
44 Litellm,
45 Custom(String),
46}
47
48impl ProviderKind {
49 #[must_use]
50 pub fn as_str(&self) -> &str {
51 match self {
52 Self::Defect => PROVIDER_DEFECT,
53 Self::Anthropic => PROVIDER_ANTHROPIC,
54 Self::Openai => PROVIDER_OPENAI,
55 Self::Deepseek => PROVIDER_DEEPSEEK,
56 Self::Litellm => PROVIDER_LITELLM,
57 Self::Custom(value) => value,
58 }
59 }
60}
61
62impl fmt::Display for ProviderKind {
63 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64 f.write_str(self.as_str())
65 }
66}
67
68impl From<ProviderKind> for String {
69 fn from(value: ProviderKind) -> Self {
70 value.to_string()
71 }
72}
73
74impl From<String> for ProviderKind {
75 fn from(value: String) -> Self {
76 match value.as_str() {
77 PROVIDER_DEFECT => Self::Defect,
78 PROVIDER_ANTHROPIC => Self::Anthropic,
79 PROVIDER_OPENAI => Self::Openai,
80 PROVIDER_DEEPSEEK => Self::Deepseek,
81 PROVIDER_LITELLM => Self::Litellm,
82 _ => Self::Custom(value),
83 }
84 }
85}
86
87impl From<&str> for ProviderKind {
88 fn from(value: &str) -> Self {
89 match value {
90 PROVIDER_DEFECT => Self::Defect,
91 PROVIDER_ANTHROPIC => Self::Anthropic,
92 PROVIDER_OPENAI => Self::Openai,
93 PROVIDER_DEEPSEEK => Self::Deepseek,
94 PROVIDER_LITELLM => Self::Litellm,
95 other => Self::Custom(other.to_string()),
96 }
97 }
98}
99
100#[derive(Debug, Clone, Copy, PartialEq, Eq)]
101pub enum ConfigSource {
102 Defaults,
103 User,
104 Project,
105 ProjectLocal,
106 Cli,
107}
108
109#[derive(Debug, Clone, PartialEq)]
110pub struct ConfigLayerEntry {
111 pub source: ConfigSource,
112 pub path: Option<PathBuf>,
113 pub raw_toml: Option<String>,
114 pub value: TomlValue,
115}
116
117#[derive(Debug, Clone, Default, PartialEq)]
118pub struct ConfigLayerStack {
119 pub layers: Vec<ConfigLayerEntry>,
120}
121
122#[derive(Debug, Clone, PartialEq, Eq)]
123pub enum ConfigWarning {
124 DeprecatedKey {
125 path: PathBuf,
126 old: String,
127 new: String,
128 },
129 InactiveSection {
137 path: PathBuf,
138 section: String,
139 reason: String,
140 },
141 McpToolRenamed {
148 server: String,
149 original: String,
150 renamed: String,
151 },
152 McpJsonOverridden { path: PathBuf, server: String },
156}
157
158#[non_exhaustive]
159#[derive(Debug, thiserror::Error)]
160pub enum ConfigError {
161 #[error("failed to read config file {path}: {source}")]
162 Io {
163 path: PathBuf,
164 #[source]
165 source: BoxError,
166 },
167
168 #[error("failed to parse config file {path}: {source}")]
169 Parse {
170 path: PathBuf,
171 #[source]
172 source: BoxError,
173 },
174
175 #[error("invalid config at {path}: {message}")]
176 Invalid { path: PathBuf, message: String },
177
178 #[error(transparent)]
179 Source(#[from] BoxError),
180}
181
182#[derive(Debug, Clone, Default)]
183pub struct CliOverrides {
184 pub provider: Option<ProviderKind>,
185 pub model: Option<String>,
186 pub sandbox: Option<SandboxMode>,
187 pub log_format: Option<LogFormat>,
188 pub config_overrides: Vec<(String, TomlValue)>,
189}
190
191#[derive(Debug, Clone, Default)]
192pub struct LoadConfigOptions {
193 pub cwd: PathBuf,
194 pub cli: CliOverrides,
195 pub xdg_config_home: Option<PathBuf>,
196 pub home_dir: Option<PathBuf>,
197 pub local: bool,
204}
205
206#[derive(Debug, Clone)]
207pub struct LoadedConfig {
208 pub layers: ConfigLayerStack,
209 pub effective: EffectiveConfig,
210 pub warnings: Vec<ConfigWarning>,
211}
212
213#[derive(Debug, Clone)]
214pub struct EffectiveConfig {
215 pub cli: CliConfig,
216 pub turn: TurnConfig,
217 pub base_prompt: BasePromptConfigFile,
218 pub prompt: PromptConfigFile,
219 pub capabilities: CapabilitiesConfig,
222 pub providers: ProviderConfigs,
223 pub tools: ToolsConfig,
224 pub sandbox: SandboxConfig,
225 pub tracing: TracingConfig,
226 pub mcp: McpConfig,
227 pub http: HttpClientConfig,
228 pub hooks: HooksConfig,
230}
231
232#[derive(Debug, Clone, Default, PartialEq)]
242pub struct HooksConfig {
243 pub buckets: std::collections::BTreeMap<String, Vec<HookEntry>>,
245}
246
247impl HooksConfig {
248 pub fn is_empty(&self) -> bool {
251 self.buckets.values().all(Vec::is_empty)
252 }
253
254 pub fn get(&self, event_name: &str) -> &[HookEntry] {
256 self.buckets
257 .get(event_name)
258 .map(Vec::as_slice)
259 .unwrap_or(&[])
260 }
261
262 pub fn push(&mut self, event_name: impl Into<String>, entry: HookEntry) {
264 self.buckets
265 .entry(event_name.into())
266 .or_default()
267 .push(entry);
268 }
269}
270
271#[derive(Debug, Clone, PartialEq)]
273pub struct HookEntry {
274 pub name: Option<String>,
281 pub matcher: HookMatcher,
282 pub handler: HookHandlerSpec,
283 pub source: ConfigSource,
286}
287
288#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
291pub struct HookMatcher {
292 pub tool: Option<String>,
294 pub tool_glob: Option<String>,
296 pub safety: Vec<defect_agent::tool::SafetyClass>,
299}
300
301#[non_exhaustive]
303#[derive(Debug, Clone, PartialEq, Eq, Hash)]
304pub enum HookHandlerSpec {
305 Builtin { name: String },
308 Command(HookCommandSpec),
310 Prompt(HookPromptSpec),
312}
313
314#[non_exhaustive]
315#[derive(Debug, Clone, PartialEq, Eq, Hash)]
316pub enum HookCommandSpec {
317 Argv {
319 argv: Vec<String>,
320 argv_windows: Option<Vec<String>>,
322 cwd: Option<PathBuf>,
323 env: BTreeMap<String, String>,
324 timeout_sec: Option<u64>,
325 },
326 Shell {
329 shell: HookShellKind,
330 command: String,
331 cwd: Option<PathBuf>,
332 env: BTreeMap<String, String>,
333 timeout_sec: Option<u64>,
334 },
335}
336
337#[non_exhaustive]
338#[derive(Debug, Clone, PartialEq, Eq, Hash)]
339pub enum HookShellKind {
340 Sh,
341 Bash,
342 Pwsh,
343 Cmd,
344 Custom {
346 program: String,
347 args: Vec<String>,
348 },
349}
350
351#[non_exhaustive]
352#[derive(Debug, Clone, PartialEq, Eq, Hash)]
353pub struct HookPromptSpec {
354 pub model: Option<String>,
356 pub system: String,
357 pub render: HookPromptRender,
358 pub timeout_sec: Option<u64>,
359}
360
361impl HookPromptSpec {
362 #[must_use]
365 pub fn new(
366 model: Option<String>,
367 system: String,
368 render: HookPromptRender,
369 timeout_sec: Option<u64>,
370 ) -> Self {
371 Self {
372 model,
373 system,
374 render,
375 timeout_sec,
376 }
377 }
378}
379
380#[non_exhaustive]
381#[derive(Debug, Clone, PartialEq, Eq, Hash)]
382pub enum HookPromptRender {
383 Json,
385 Template { template: String },
387}
388
389#[non_exhaustive]
397#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
398pub struct CapabilitiesConfig {
399 pub web_search: WebSearchCapabilityConfig,
400}
401
402impl CapabilitiesConfig {
403 #[must_use]
407 pub const fn with_web_search(web_search: WebSearchCapabilityConfig) -> Self {
408 Self { web_search }
409 }
410
411 #[must_use]
415 pub fn to_session_capabilities(self) -> SessionCapabilitiesConfig {
416 SessionCapabilitiesConfig::with_web_search(self.web_search)
417 }
418}
419
420#[non_exhaustive]
424#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
425pub struct ProviderCapabilityOverrides {
426 pub web_search: Option<WebSearchCapabilityConfig>,
427}
428
429impl ProviderCapabilityOverrides {
430 #[must_use]
433 pub const fn with_web_search(web_search: Option<WebSearchCapabilityConfig>) -> Self {
434 Self { web_search }
435 }
436
437 #[must_use]
440 pub fn merge_into(&self, base: CapabilitiesConfig) -> CapabilitiesConfig {
441 CapabilitiesConfig::with_web_search(self.web_search.unwrap_or(base.web_search))
442 }
443}
444
445#[derive(Debug, Clone, PartialEq, Eq)]
446pub struct CliConfig {
447 pub provider: ProviderKind,
448 pub model: String,
449}
450
451#[derive(Debug, Clone, PartialEq, Eq, Default)]
452pub struct BasePromptConfigFile {
453 pub file: Option<PathBuf>,
454 pub text: Option<String>,
455}
456
457#[derive(Debug, Clone, PartialEq, Eq, Default)]
458pub struct PromptConfigFile {
459 pub file: String,
460 pub text: Option<String>,
461 pub provider_overlays: BTreeMap<String, String>,
462 pub model_overlays: BTreeMap<String, String>,
463}
464
465#[derive(Debug, Clone, PartialEq, Default)]
466pub struct ProviderConfigs {
467 pub anthropic: ProviderConfigFile,
468 pub openai: ProviderConfigFile,
469 pub deepseek: ProviderConfigFile,
470 pub litellm: ProviderConfigFile,
471 pub custom: BTreeMap<String, ProviderConfigFile>,
472}
473
474impl ProviderConfigs {
475 #[must_use]
476 pub fn get(&self, provider: &ProviderKind) -> Option<&ProviderConfigFile> {
477 match provider {
478 ProviderKind::Defect => None,
479 ProviderKind::Anthropic => Some(&self.anthropic),
480 ProviderKind::Openai => Some(&self.openai),
481 ProviderKind::Deepseek => Some(&self.deepseek),
482 ProviderKind::Litellm => Some(&self.litellm),
483 ProviderKind::Custom(name) => self.custom.get(name),
484 }
485 }
486}
487
488#[derive(Debug, Clone, PartialEq, Eq, Default)]
489pub struct ToolsConfig {
490 pub bash: BashToolConfig,
491 pub fs: FsToolConfig,
492 pub fetch: FetchToolConfig,
495 pub search: SearchToolConfig,
500 pub background: BackgroundProgressConfig,
505}
506
507#[non_exhaustive]
509#[derive(Debug, Clone, PartialEq, Eq)]
510pub struct FetchToolConfig {
511 pub enabled: bool,
512 pub default_timeout_secs: u32,
513 pub max_timeout_secs: u32,
514 pub max_response_bytes: u64,
515 pub default_format: FetchFormat,
516 pub html_to_markdown: bool,
517 pub follow_redirects: bool,
518}
519
520impl Default for FetchToolConfig {
521 fn default() -> Self {
522 Self {
523 enabled: true,
524 default_timeout_secs: 30,
525 max_timeout_secs: 120,
526 max_response_bytes: 5 * 1024 * 1024,
527 default_format: FetchFormat::Markdown,
528 html_to_markdown: true,
529 follow_redirects: true,
530 }
531 }
532}
533
534#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
535#[serde(rename_all = "snake_case")]
536pub enum FetchFormat {
537 #[default]
538 Markdown,
539 Html,
540 Text,
541}
542
543#[derive(Debug, Clone, PartialEq, Eq)]
544pub struct BashToolConfig {
545 pub default_timeout_ms: u64,
546 pub max_timeout_ms: u64,
547 pub output_max_bytes: usize,
550}
551
552impl Default for BashToolConfig {
553 fn default() -> Self {
554 Self {
555 default_timeout_ms: DEFAULT_BASH_TIMEOUT_MS,
556 max_timeout_ms: DEFAULT_BASH_MAX_TIMEOUT_MS,
557 output_max_bytes: DEFAULT_BASH_OUTPUT_MAX_BYTES,
558 }
559 }
560}
561
562#[derive(Debug, Clone, PartialEq, Eq)]
563pub struct FsToolConfig {
564 pub read_default_limit: u32,
565 pub read_max_limit: u32,
566}
567
568impl Default for FsToolConfig {
569 fn default() -> Self {
570 Self {
571 read_default_limit: DEFAULT_FS_READ_LIMIT,
572 read_max_limit: DEFAULT_FS_READ_MAX_LIMIT,
573 }
574 }
575}
576
577#[non_exhaustive]
579#[derive(Debug, Clone, PartialEq, Eq)]
580pub struct SearchToolConfig {
581 pub enabled: bool,
582 pub default_head_limit: u32,
583 pub max_head_limit: u32,
584 pub max_file_size_bytes: u64,
585 pub max_result_bytes: u64,
586 pub max_walk_files: u64,
587 pub respect_gitignore_default: bool,
588}
589
590impl Default for SearchToolConfig {
591 fn default() -> Self {
592 Self {
593 enabled: true,
594 default_head_limit: 100,
595 max_head_limit: 1000,
596 max_file_size_bytes: 16 * 1024 * 1024,
597 max_result_bytes: 256 * 1024,
598 max_walk_files: 100_000,
599 respect_gitignore_default: true,
600 }
601 }
602}
603
604#[derive(Debug, Clone, PartialEq, Eq)]
605pub struct SandboxConfig {
606 pub mode: SandboxMode,
607}
608
609impl Default for SandboxConfig {
610 fn default() -> Self {
611 Self {
612 mode: SandboxMode::AskWrites,
613 }
614 }
615}
616
617#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
618#[serde(rename_all = "kebab-case")]
619pub enum SandboxMode {
620 ReadOnly,
621 #[default]
622 AskWrites,
623 Open,
624 DenyAll,
625}
626
627impl SandboxMode {
628 pub fn as_str(self) -> &'static str {
629 match self {
630 Self::ReadOnly => "read-only",
631 Self::AskWrites => "ask-writes",
632 Self::Open => "open",
633 Self::DenyAll => "deny-all",
634 }
635 }
636}
637
638pub type AnthropicConfigFile = ProviderConfigFile;
639pub type OpenAiConfigFile = ProviderConfigFile;
640pub type DeepSeekConfigFile = ProviderConfigFile;
641pub type LiteLlmConfigFile = ProviderConfigFile;
642
643#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
644#[serde(rename_all = "kebab-case")]
645pub enum ProviderProtocol {
646 AnthropicMessages,
647 OpenaiChat,
648}
649
650#[derive(Debug, Clone, PartialEq, Deserialize)]
668#[serde(untagged)]
669pub enum ModelEntry {
670 Id(String),
672 Detailed {
674 id: String,
675 #[serde(default)]
676 name: Option<String>,
677 #[serde(default)]
678 context_window: Option<u64>,
679 #[serde(default)]
680 max_output_tokens: Option<u64>,
681 #[serde(default)]
688 thinking_format: Option<ThinkingFormat>,
689 },
690}
691
692#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
698#[serde(rename_all = "snake_case")]
699pub enum ThinkingFormat {
700 Adaptive,
702 Legacy,
704}
705
706impl ModelEntry {
707 #[must_use]
709 pub fn id(&self) -> &str {
710 match self {
711 Self::Id(id) => id,
712 Self::Detailed { id, .. } => id,
713 }
714 }
715
716 #[must_use]
718 pub fn name(&self) -> Option<&str> {
719 match self {
720 Self::Id(_) => None,
721 Self::Detailed { name, .. } => name.as_deref(),
722 }
723 }
724
725 #[must_use]
727 pub fn context_window(&self) -> Option<u64> {
728 match self {
729 Self::Id(_) => None,
730 Self::Detailed { context_window, .. } => *context_window,
731 }
732 }
733
734 #[must_use]
736 pub fn max_output_tokens(&self) -> Option<u64> {
737 match self {
738 Self::Id(_) => None,
739 Self::Detailed {
740 max_output_tokens, ..
741 } => *max_output_tokens,
742 }
743 }
744
745 #[must_use]
747 pub fn thinking_format(&self) -> Option<ThinkingFormat> {
748 match self {
749 Self::Id(_) => None,
750 Self::Detailed {
751 thinking_format, ..
752 } => *thinking_format,
753 }
754 }
755}
756
757#[derive(Debug, Clone, PartialEq, Default)]
758pub struct ProviderConfigFile {
759 pub protocol: Option<ProviderProtocol>,
760 pub base_url: Option<String>,
761 pub default_model: Option<String>,
762 pub models: Option<Vec<ModelEntry>>,
763 pub display_name: Option<String>,
764 pub api_key_env: Option<String>,
765 pub organization: Option<String>,
766 pub project: Option<String>,
767 pub aws: Option<ProviderAwsConfigFile>,
768 pub headers: BTreeMap<String, String>,
769 pub auth_header: Option<String>,
774 pub capabilities: ProviderCapabilityOverrides,
775 pub reasoning_effort: Option<ReasoningEffort>,
777}
778
779#[derive(Debug, Clone, PartialEq, Eq, Default, Deserialize)]
780#[serde(deny_unknown_fields)]
781pub struct ProviderAwsConfigFile {
782 pub profile: Option<String>,
783 pub region: Option<String>,
784 pub anthropic_beta: Option<Vec<String>>,
791}
792
793#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
800#[serde(rename_all = "snake_case")]
801pub enum ReasoningEffort {
802 None,
803 Minimal,
804 Low,
805 Medium,
806 High,
807 Xhigh,
808}
809
810#[derive(Debug, Clone, PartialEq, Eq, Default)]
820pub struct HttpClientConfig {
821 pub total_timeout_ms: Option<u64>,
824 pub transport_retries: Option<u8>,
827 pub initial_backoff_ms: Option<u64>,
829 pub user_agent: Option<String>,
832 pub proxy: HttpProxyConfig,
834}
835
836#[derive(Debug, Clone, PartialEq, Eq)]
837pub struct HttpProxyConfig {
838 pub mode: HttpProxyMode,
839 pub explicit: HttpProxySettings,
841}
842
843impl Default for HttpProxyConfig {
844 fn default() -> Self {
845 Self {
846 mode: HttpProxyMode::FromEnv,
847 explicit: HttpProxySettings::default(),
848 }
849 }
850}
851
852#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
853#[serde(rename_all = "kebab-case")]
854pub enum HttpProxyMode {
855 #[default]
856 FromEnv,
857 Disabled,
858 Explicit,
859}
860
861impl HttpProxyMode {
862 pub fn as_str(self) -> &'static str {
863 match self {
864 Self::FromEnv => "from-env",
865 Self::Disabled => "disabled",
866 Self::Explicit => "explicit",
867 }
868 }
869}
870
871#[derive(Debug, Clone, PartialEq, Eq, Default)]
872pub struct HttpProxySettings {
873 pub http_proxy: Option<String>,
874 pub https_proxy: Option<String>,
875 pub no_proxy: Vec<String>,
876}
877
878#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
882#[serde(rename_all = "kebab-case")]
883pub enum LogFormat {
884 #[default]
886 Text,
887 Jsonl,
889}
890
891impl LogFormat {
892 pub fn as_str(self) -> &'static str {
893 match self {
894 Self::Text => "text",
895 Self::Jsonl => "jsonl",
896 }
897 }
898}
899
900#[derive(Debug, Clone, PartialEq, Default)]
901pub struct TracingConfig {
902 pub filter: Option<String>,
903 pub format: LogFormat,
904 pub otlp: Option<OtlpTracingConfig>,
905 pub langfuse: Option<LangfuseConfig>,
906}
907
908#[derive(Debug, Clone, PartialEq, Default)]
909pub struct OtlpTracingConfig {
910 pub endpoint: Option<String>,
911}
912
913#[derive(Debug, Clone, PartialEq, Eq, Default)]
918pub struct LangfuseConfig {
919 pub enabled: bool,
920 pub host: Option<String>,
923 pub public_key: Option<String>,
924 pub secret_key: Option<String>,
925 pub flush_interval_ms: Option<u64>,
927 pub max_batch: Option<usize>,
929}
930
931#[derive(Debug, Clone, PartialEq, Eq, Default)]
932pub struct McpConfig {
933 pub enabled_servers: Vec<String>,
934 pub servers: BTreeMap<String, McpServerConfig>,
935}
936
937#[derive(Debug, Clone, PartialEq, Eq)]
938pub enum McpServerConfig {
939 Stdio(McpStdioServerConfig),
940 Http(McpRemoteServerConfig),
941 Sse(McpRemoteServerConfig),
942}
943
944#[derive(Debug, Clone, PartialEq, Eq)]
945pub struct McpStdioServerConfig {
946 pub command: String,
947 pub args: Vec<String>,
948 pub env: BTreeMap<String, String>,
949}
950
951#[derive(Debug, Clone, PartialEq, Eq)]
952pub struct McpRemoteServerConfig {
953 pub url: String,
954 pub headers: BTreeMap<String, String>,
955}
956
957#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
958#[serde(rename_all = "snake_case")]
959pub(crate) enum McpTransportKind {
960 Stdio,
961 Http,
962 Sse,
963}
964
965#[derive(Debug, Clone, Default, Deserialize)]
966#[serde(deny_unknown_fields)]
967pub(crate) struct ConfigToml {
968 #[serde(default)]
969 pub(crate) default: DefaultSection,
970 #[serde(default)]
971 pub(crate) base_prompt: BasePromptSection,
972 #[serde(default)]
973 pub(crate) prompt: PromptSection,
974 #[serde(default)]
975 pub(crate) turn: TurnSection,
976 #[serde(default)]
977 pub(crate) capabilities: CapabilitiesSection,
978 #[serde(default)]
979 pub(crate) providers: ProvidersSection,
980 #[serde(default)]
981 pub(crate) tools: ToolsSection,
982 #[serde(default)]
983 pub(crate) sandbox: SandboxSection,
984 #[serde(default)]
985 pub(crate) tracing: TracingSection,
986 #[serde(default)]
987 pub(crate) mcp: McpSection,
988 #[serde(default)]
989 pub(crate) http: HttpSection,
990 #[serde(default)]
995 #[allow(dead_code)]
996 pub(crate) hooks: Option<TomlValue>,
997}
998
999#[derive(Debug, Clone, Default, Deserialize)]
1000#[serde(deny_unknown_fields)]
1001pub(crate) struct CapabilitiesSection {
1002 pub(crate) web_search: Option<WebSearchCapabilitySection>,
1003}
1004
1005#[derive(Debug, Clone, Default, Deserialize)]
1006#[serde(deny_unknown_fields)]
1007pub(crate) struct WebSearchCapabilitySection {
1008 pub(crate) mode: Option<defect_agent::session::WebSearchCapabilityMode>,
1009}
1010
1011#[derive(Debug, Clone, Default, Deserialize)]
1012#[serde(deny_unknown_fields)]
1013pub(crate) struct ProviderCapabilitiesSection {
1014 pub(crate) web_search: Option<WebSearchCapabilitySection>,
1015}
1016
1017#[derive(Debug, Clone, Default, Deserialize)]
1018#[serde(deny_unknown_fields)]
1019pub(crate) struct DefaultSection {
1020 pub(crate) provider: Option<ProviderKind>,
1021 pub(crate) model: Option<String>,
1022}
1023
1024#[derive(Debug, Clone, Default, Deserialize)]
1025#[serde(deny_unknown_fields)]
1026pub(crate) struct BasePromptSection {
1027 pub(crate) file: Option<String>,
1028 pub(crate) text: Option<String>,
1029}
1030
1031#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
1035#[serde(rename_all = "snake_case")]
1036pub(crate) enum RequestLimitMode {
1037 Fixed,
1039 Adaptive,
1041 Unbounded,
1043}
1044
1045#[derive(Debug, Clone, Default, Deserialize)]
1046#[serde(deny_unknown_fields)]
1047pub(crate) struct TurnSection {
1048 pub(crate) system_prompt: Option<String>,
1049 pub(crate) sampling: Option<SamplingSection>,
1056 pub(crate) request_limit: Option<u32>,
1057 pub(crate) request_limit_mode: Option<RequestLimitMode>,
1059 pub(crate) compact_threshold_tokens: Option<u64>,
1060 pub(crate) compact_ratio: Option<f64>,
1061 pub(crate) background_compact_enabled: Option<bool>,
1064 pub(crate) compact_soft_ratio: Option<f64>,
1067 pub(crate) microcompact_enabled: Option<bool>,
1070 pub(crate) microcompact_ratio: Option<f64>,
1072 pub(crate) max_llm_retries: Option<u32>,
1073 pub(crate) max_concurrent_tools: Option<usize>,
1074 pub(crate) max_hook_continues: Option<u32>,
1077 pub(crate) subagent_max_depth: Option<u32>,
1081}
1082
1083#[derive(Debug, Clone, Default, Deserialize)]
1087#[serde(deny_unknown_fields)]
1088pub(crate) struct SamplingSection {
1089 pub(crate) max_tokens: Option<u32>,
1092 pub(crate) temperature: Option<f32>,
1093 pub(crate) top_p: Option<f32>,
1094 pub(crate) top_k: Option<u32>,
1095}
1096
1097#[derive(Debug, Clone, Default, Deserialize)]
1098#[serde(deny_unknown_fields)]
1099pub(crate) struct PromptSection {
1100 pub(crate) file: Option<String>,
1101 pub(crate) text: Option<String>,
1102 pub(crate) providers: Option<BTreeMap<String, PromptOverlaySection>>,
1103 pub(crate) models: Option<BTreeMap<String, String>>,
1104}
1105
1106#[derive(Debug, Clone, Default, Deserialize)]
1107#[serde(deny_unknown_fields)]
1108pub(crate) struct PromptOverlaySection {
1109 pub(crate) text: Option<String>,
1110}
1111
1112#[derive(Debug, Clone, Default, Deserialize)]
1113pub(crate) struct ProvidersSection {
1114 pub(crate) anthropic: Option<AnthropicProviderSection>,
1115 pub(crate) openai: Option<OpenAiProviderSection>,
1116 pub(crate) deepseek: Option<DeepSeekProviderSection>,
1117 pub(crate) litellm: Option<LiteLlmProviderSection>,
1118 #[serde(flatten)]
1119 pub(crate) custom: BTreeMap<String, ProviderSection>,
1120}
1121
1122pub(crate) type AnthropicProviderSection = ProviderSection;
1123pub(crate) type OpenAiProviderSection = ProviderSection;
1124pub(crate) type DeepSeekProviderSection = ProviderSection;
1125pub(crate) type LiteLlmProviderSection = ProviderSection;
1126
1127#[derive(Debug, Clone, Default, Deserialize)]
1128#[serde(deny_unknown_fields)]
1129pub(crate) struct ProviderSection {
1130 pub(crate) protocol: Option<ProviderProtocol>,
1131 pub(crate) base_url: Option<String>,
1132 pub(crate) default_model: Option<String>,
1133 pub(crate) models: Option<Vec<ModelEntry>>,
1134 pub(crate) display_name: Option<String>,
1135 pub(crate) api_key_env: Option<String>,
1136 pub(crate) organization: Option<String>,
1137 pub(crate) project: Option<String>,
1138 pub(crate) aws: Option<ProviderAwsConfigFile>,
1139 pub(crate) headers: Option<BTreeMap<String, String>>,
1140 pub(crate) auth_header: Option<String>,
1141 pub(crate) capabilities: Option<ProviderCapabilitiesSection>,
1142 pub(crate) reasoning_effort: Option<ReasoningEffort>,
1143}
1144
1145#[derive(Debug, Clone, Default, Deserialize)]
1146#[serde(deny_unknown_fields)]
1147pub(crate) struct ToolsSection {
1148 pub(crate) bash: Option<BashToolSection>,
1149 pub(crate) fs: Option<FsToolSection>,
1150 pub(crate) fetch: Option<FetchToolSection>,
1151 pub(crate) search: Option<SearchToolSection>,
1155 pub(crate) background: Option<BackgroundToolSection>,
1158}
1159
1160#[derive(Debug, Clone, Default, Deserialize)]
1161#[serde(deny_unknown_fields)]
1162pub(crate) struct BackgroundToolSection {
1163 pub(crate) default_recent_blocks: Option<usize>,
1166 pub(crate) block_text_limit: Option<usize>,
1169 pub(crate) finished_tasks_cap: Option<usize>,
1172}
1173
1174#[derive(Debug, Clone, Default, Deserialize)]
1175#[serde(deny_unknown_fields)]
1176pub(crate) struct SearchToolSection {
1177 pub(crate) enabled: Option<bool>,
1178 pub(crate) default_head_limit: Option<u32>,
1179 pub(crate) max_head_limit: Option<u32>,
1180 pub(crate) max_file_size_bytes: Option<u64>,
1181 pub(crate) max_result_bytes: Option<u64>,
1182 pub(crate) max_walk_files: Option<u64>,
1183 pub(crate) respect_gitignore_default: Option<bool>,
1184}
1185
1186#[derive(Debug, Clone, Default, Deserialize)]
1187#[serde(deny_unknown_fields)]
1188pub(crate) struct FetchToolSection {
1189 pub(crate) enabled: Option<bool>,
1190 pub(crate) default_timeout_secs: Option<u32>,
1191 pub(crate) max_timeout_secs: Option<u32>,
1192 pub(crate) max_response_bytes: Option<u64>,
1193 pub(crate) default_format: Option<FetchFormat>,
1194 pub(crate) html_to_markdown: Option<bool>,
1195 pub(crate) follow_redirects: Option<bool>,
1196}
1197
1198#[derive(Debug, Clone, Default, Deserialize)]
1199#[serde(deny_unknown_fields)]
1200pub(crate) struct BashToolSection {
1201 pub(crate) default_timeout_ms: Option<u64>,
1202 pub(crate) max_timeout_ms: Option<u64>,
1203 pub(crate) output_max_bytes: Option<usize>,
1204}
1205
1206#[derive(Debug, Clone, Default, Deserialize)]
1207#[serde(deny_unknown_fields)]
1208pub(crate) struct FsToolSection {
1209 pub(crate) read_default_limit: Option<u32>,
1210 pub(crate) read_max_limit: Option<u32>,
1211}
1212
1213#[derive(Debug, Clone, Default, Deserialize)]
1214#[serde(deny_unknown_fields)]
1215pub(crate) struct SandboxSection {
1216 pub(crate) mode: Option<SandboxMode>,
1217}
1218
1219#[derive(Debug, Clone, Default, Deserialize)]
1220#[serde(deny_unknown_fields)]
1221pub(crate) struct TracingSection {
1222 pub(crate) filter: Option<String>,
1223 pub(crate) format: Option<LogFormat>,
1224 pub(crate) otlp: Option<OtlpTracingSection>,
1225 pub(crate) langfuse: Option<LangfuseSection>,
1226}
1227
1228#[derive(Debug, Clone, Default, Deserialize)]
1229#[serde(deny_unknown_fields)]
1230pub(crate) struct OtlpTracingSection {
1231 pub(crate) endpoint: Option<String>,
1232}
1233
1234#[derive(Debug, Clone, Default, Deserialize)]
1235#[serde(rename_all = "snake_case", deny_unknown_fields)]
1236pub(crate) struct LangfuseSection {
1237 pub(crate) enabled: Option<bool>,
1238 pub(crate) host: Option<String>,
1239 pub(crate) public_key: Option<String>,
1240 pub(crate) secret_key: Option<String>,
1241 pub(crate) flush_interval_ms: Option<u64>,
1242 pub(crate) max_batch: Option<usize>,
1243}
1244
1245#[derive(Debug, Clone, Default, Deserialize)]
1246#[serde(deny_unknown_fields)]
1247pub(crate) struct McpSection {
1248 pub(crate) enabled_servers: Option<Vec<String>>,
1249 pub(crate) servers: Option<BTreeMap<String, McpServerSection>>,
1250}
1251
1252#[derive(Debug, Clone, Default, Deserialize)]
1253#[serde(deny_unknown_fields)]
1254pub(crate) struct HttpSection {
1255 pub(crate) total_timeout_ms: Option<u64>,
1256 pub(crate) transport_retries: Option<u8>,
1257 pub(crate) initial_backoff_ms: Option<u64>,
1258 pub(crate) user_agent: Option<String>,
1259 pub(crate) proxy: Option<HttpProxySection>,
1260}
1261
1262#[derive(Debug, Clone, Default, Deserialize)]
1263#[serde(deny_unknown_fields)]
1264pub(crate) struct HttpProxySection {
1265 pub(crate) mode: Option<HttpProxyMode>,
1266 pub(crate) http_proxy: Option<String>,
1267 pub(crate) https_proxy: Option<String>,
1268 pub(crate) no_proxy: Option<Vec<String>>,
1269}
1270
1271#[derive(Debug, Clone, Default, Deserialize)]
1272#[serde(deny_unknown_fields)]
1273pub(crate) struct McpServerSection {
1274 pub(crate) transport: Option<McpTransportKind>,
1275 pub(crate) command: Option<String>,
1276 pub(crate) args: Option<Vec<String>>,
1277 pub(crate) env: Option<BTreeMap<String, String>>,
1278 pub(crate) url: Option<String>,
1279 pub(crate) headers: Option<BTreeMap<String, String>>,
1280}