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 config_overrides: Vec<(String, TomlValue)>,
188}
189
190#[derive(Debug, Clone, Default)]
191pub struct LoadConfigOptions {
192 pub cwd: PathBuf,
193 pub cli: CliOverrides,
194 pub xdg_config_home: Option<PathBuf>,
195 pub home_dir: Option<PathBuf>,
196 pub local: bool,
203}
204
205#[derive(Debug, Clone)]
206pub struct LoadedConfig {
207 pub layers: ConfigLayerStack,
208 pub effective: EffectiveConfig,
209 pub warnings: Vec<ConfigWarning>,
210}
211
212#[derive(Debug, Clone)]
213pub struct EffectiveConfig {
214 pub cli: CliConfig,
215 pub turn: TurnConfig,
216 pub base_prompt: BasePromptConfigFile,
217 pub prompt: PromptConfigFile,
218 pub capabilities: CapabilitiesConfig,
221 pub providers: ProviderConfigs,
222 pub tools: ToolsConfig,
223 pub sandbox: SandboxConfig,
224 pub tracing: TracingConfig,
225 pub mcp: McpConfig,
226 pub http: HttpClientConfig,
227 pub hooks: HooksConfig,
229}
230
231#[derive(Debug, Clone, Default, PartialEq)]
241pub struct HooksConfig {
242 pub buckets: std::collections::BTreeMap<String, Vec<HookEntry>>,
244}
245
246impl HooksConfig {
247 pub fn is_empty(&self) -> bool {
250 self.buckets.values().all(Vec::is_empty)
251 }
252
253 pub fn get(&self, event_name: &str) -> &[HookEntry] {
255 self.buckets
256 .get(event_name)
257 .map(Vec::as_slice)
258 .unwrap_or(&[])
259 }
260
261 pub fn push(&mut self, event_name: impl Into<String>, entry: HookEntry) {
263 self.buckets
264 .entry(event_name.into())
265 .or_default()
266 .push(entry);
267 }
268}
269
270#[derive(Debug, Clone, PartialEq)]
272pub struct HookEntry {
273 pub name: Option<String>,
280 pub matcher: HookMatcher,
281 pub handler: HookHandlerSpec,
282 pub source: ConfigSource,
285}
286
287#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
290pub struct HookMatcher {
291 pub tool: Option<String>,
293 pub tool_glob: Option<String>,
295 pub safety: Vec<defect_agent::tool::SafetyClass>,
298}
299
300#[non_exhaustive]
302#[derive(Debug, Clone, PartialEq, Eq, Hash)]
303pub enum HookHandlerSpec {
304 Builtin { name: String },
307 Command(HookCommandSpec),
309 Prompt(HookPromptSpec),
311}
312
313#[non_exhaustive]
314#[derive(Debug, Clone, PartialEq, Eq, Hash)]
315pub enum HookCommandSpec {
316 Argv {
318 argv: Vec<String>,
319 argv_windows: Option<Vec<String>>,
321 cwd: Option<PathBuf>,
322 env: BTreeMap<String, String>,
323 timeout_sec: Option<u64>,
324 },
325 Shell {
328 shell: HookShellKind,
329 command: String,
330 cwd: Option<PathBuf>,
331 env: BTreeMap<String, String>,
332 timeout_sec: Option<u64>,
333 },
334}
335
336#[non_exhaustive]
337#[derive(Debug, Clone, PartialEq, Eq, Hash)]
338pub enum HookShellKind {
339 Sh,
340 Bash,
341 Pwsh,
342 Cmd,
343 Custom {
345 program: String,
346 args: Vec<String>,
347 },
348}
349
350#[non_exhaustive]
351#[derive(Debug, Clone, PartialEq, Eq, Hash)]
352pub struct HookPromptSpec {
353 pub model: Option<String>,
355 pub system: String,
356 pub render: HookPromptRender,
357 pub timeout_sec: Option<u64>,
358}
359
360impl HookPromptSpec {
361 #[must_use]
364 pub fn new(
365 model: Option<String>,
366 system: String,
367 render: HookPromptRender,
368 timeout_sec: Option<u64>,
369 ) -> Self {
370 Self {
371 model,
372 system,
373 render,
374 timeout_sec,
375 }
376 }
377}
378
379#[non_exhaustive]
380#[derive(Debug, Clone, PartialEq, Eq, Hash)]
381pub enum HookPromptRender {
382 Json,
384 Template { template: String },
386}
387
388#[non_exhaustive]
396#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
397pub struct CapabilitiesConfig {
398 pub web_search: WebSearchCapabilityConfig,
399}
400
401impl CapabilitiesConfig {
402 #[must_use]
406 pub const fn with_web_search(web_search: WebSearchCapabilityConfig) -> Self {
407 Self { web_search }
408 }
409
410 #[must_use]
414 pub fn to_session_capabilities(self) -> SessionCapabilitiesConfig {
415 SessionCapabilitiesConfig::with_web_search(self.web_search)
416 }
417}
418
419#[non_exhaustive]
423#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
424pub struct ProviderCapabilityOverrides {
425 pub web_search: Option<WebSearchCapabilityConfig>,
426}
427
428impl ProviderCapabilityOverrides {
429 #[must_use]
432 pub const fn with_web_search(web_search: Option<WebSearchCapabilityConfig>) -> Self {
433 Self { web_search }
434 }
435
436 #[must_use]
439 pub fn merge_into(&self, base: CapabilitiesConfig) -> CapabilitiesConfig {
440 CapabilitiesConfig::with_web_search(self.web_search.unwrap_or(base.web_search))
441 }
442}
443
444#[derive(Debug, Clone, PartialEq, Eq)]
445pub struct CliConfig {
446 pub provider: ProviderKind,
447 pub model: String,
448}
449
450#[derive(Debug, Clone, PartialEq, Eq, Default)]
451pub struct BasePromptConfigFile {
452 pub file: Option<PathBuf>,
453 pub text: Option<String>,
454}
455
456#[derive(Debug, Clone, PartialEq, Eq, Default)]
457pub struct PromptConfigFile {
458 pub file: String,
459 pub text: Option<String>,
460 pub provider_overlays: BTreeMap<String, String>,
461 pub model_overlays: BTreeMap<String, String>,
462}
463
464#[derive(Debug, Clone, PartialEq, Default)]
465pub struct ProviderConfigs {
466 pub anthropic: ProviderConfigFile,
467 pub openai: ProviderConfigFile,
468 pub deepseek: ProviderConfigFile,
469 pub litellm: ProviderConfigFile,
470 pub custom: BTreeMap<String, ProviderConfigFile>,
471}
472
473impl ProviderConfigs {
474 #[must_use]
475 pub fn get(&self, provider: &ProviderKind) -> Option<&ProviderConfigFile> {
476 match provider {
477 ProviderKind::Defect => None,
478 ProviderKind::Anthropic => Some(&self.anthropic),
479 ProviderKind::Openai => Some(&self.openai),
480 ProviderKind::Deepseek => Some(&self.deepseek),
481 ProviderKind::Litellm => Some(&self.litellm),
482 ProviderKind::Custom(name) => self.custom.get(name),
483 }
484 }
485}
486
487#[derive(Debug, Clone, PartialEq, Eq, Default)]
488pub struct ToolsConfig {
489 pub bash: BashToolConfig,
490 pub fs: FsToolConfig,
491 pub fetch: FetchToolConfig,
494 pub search: SearchToolConfig,
499 pub background: BackgroundProgressConfig,
504}
505
506#[non_exhaustive]
508#[derive(Debug, Clone, PartialEq, Eq)]
509pub struct FetchToolConfig {
510 pub enabled: bool,
511 pub default_timeout_secs: u32,
512 pub max_timeout_secs: u32,
513 pub max_response_bytes: u64,
514 pub default_format: FetchFormat,
515 pub html_to_markdown: bool,
516 pub follow_redirects: bool,
517}
518
519impl Default for FetchToolConfig {
520 fn default() -> Self {
521 Self {
522 enabled: true,
523 default_timeout_secs: 30,
524 max_timeout_secs: 120,
525 max_response_bytes: 5 * 1024 * 1024,
526 default_format: FetchFormat::Markdown,
527 html_to_markdown: true,
528 follow_redirects: true,
529 }
530 }
531}
532
533#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
534#[serde(rename_all = "snake_case")]
535pub enum FetchFormat {
536 #[default]
537 Markdown,
538 Html,
539 Text,
540}
541
542#[derive(Debug, Clone, PartialEq, Eq)]
543pub struct BashToolConfig {
544 pub default_timeout_ms: u64,
545 pub max_timeout_ms: u64,
546 pub output_max_bytes: usize,
549}
550
551impl Default for BashToolConfig {
552 fn default() -> Self {
553 Self {
554 default_timeout_ms: DEFAULT_BASH_TIMEOUT_MS,
555 max_timeout_ms: DEFAULT_BASH_MAX_TIMEOUT_MS,
556 output_max_bytes: DEFAULT_BASH_OUTPUT_MAX_BYTES,
557 }
558 }
559}
560
561#[derive(Debug, Clone, PartialEq, Eq)]
562pub struct FsToolConfig {
563 pub read_default_limit: u32,
564 pub read_max_limit: u32,
565}
566
567impl Default for FsToolConfig {
568 fn default() -> Self {
569 Self {
570 read_default_limit: DEFAULT_FS_READ_LIMIT,
571 read_max_limit: DEFAULT_FS_READ_MAX_LIMIT,
572 }
573 }
574}
575
576#[non_exhaustive]
578#[derive(Debug, Clone, PartialEq, Eq)]
579pub struct SearchToolConfig {
580 pub enabled: bool,
581 pub default_head_limit: u32,
582 pub max_head_limit: u32,
583 pub max_file_size_bytes: u64,
584 pub max_result_bytes: u64,
585 pub max_walk_files: u64,
586 pub respect_gitignore_default: bool,
587}
588
589impl Default for SearchToolConfig {
590 fn default() -> Self {
591 Self {
592 enabled: true,
593 default_head_limit: 100,
594 max_head_limit: 1000,
595 max_file_size_bytes: 16 * 1024 * 1024,
596 max_result_bytes: 256 * 1024,
597 max_walk_files: 100_000,
598 respect_gitignore_default: true,
599 }
600 }
601}
602
603#[derive(Debug, Clone, PartialEq, Eq)]
604pub struct SandboxConfig {
605 pub mode: SandboxMode,
606}
607
608impl Default for SandboxConfig {
609 fn default() -> Self {
610 Self {
611 mode: SandboxMode::AskWrites,
612 }
613 }
614}
615
616#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
617#[serde(rename_all = "kebab-case")]
618pub enum SandboxMode {
619 ReadOnly,
620 #[default]
621 AskWrites,
622 Open,
623 DenyAll,
624}
625
626impl SandboxMode {
627 pub fn as_str(self) -> &'static str {
628 match self {
629 Self::ReadOnly => "read-only",
630 Self::AskWrites => "ask-writes",
631 Self::Open => "open",
632 Self::DenyAll => "deny-all",
633 }
634 }
635}
636
637pub type AnthropicConfigFile = ProviderConfigFile;
638pub type OpenAiConfigFile = ProviderConfigFile;
639pub type DeepSeekConfigFile = ProviderConfigFile;
640pub type LiteLlmConfigFile = ProviderConfigFile;
641
642#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
643#[serde(rename_all = "kebab-case")]
644pub enum ProviderProtocol {
645 AnthropicMessages,
646 OpenaiChat,
647}
648
649#[derive(Debug, Clone, PartialEq, Deserialize)]
667#[serde(untagged)]
668pub enum ModelEntry {
669 Id(String),
671 Detailed {
673 id: String,
674 #[serde(default)]
675 name: Option<String>,
676 #[serde(default)]
677 context_window: Option<u64>,
678 #[serde(default)]
679 max_output_tokens: Option<u64>,
680 },
681}
682
683impl ModelEntry {
684 #[must_use]
686 pub fn id(&self) -> &str {
687 match self {
688 Self::Id(id) => id,
689 Self::Detailed { id, .. } => id,
690 }
691 }
692
693 #[must_use]
695 pub fn name(&self) -> Option<&str> {
696 match self {
697 Self::Id(_) => None,
698 Self::Detailed { name, .. } => name.as_deref(),
699 }
700 }
701
702 #[must_use]
704 pub fn context_window(&self) -> Option<u64> {
705 match self {
706 Self::Id(_) => None,
707 Self::Detailed { context_window, .. } => *context_window,
708 }
709 }
710
711 #[must_use]
713 pub fn max_output_tokens(&self) -> Option<u64> {
714 match self {
715 Self::Id(_) => None,
716 Self::Detailed {
717 max_output_tokens, ..
718 } => *max_output_tokens,
719 }
720 }
721}
722
723#[derive(Debug, Clone, PartialEq, Default)]
724pub struct ProviderConfigFile {
725 pub protocol: Option<ProviderProtocol>,
726 pub base_url: Option<String>,
727 pub default_model: Option<String>,
728 pub models: Option<Vec<ModelEntry>>,
729 pub display_name: Option<String>,
730 pub api_key_env: Option<String>,
731 pub organization: Option<String>,
732 pub project: Option<String>,
733 pub aws: Option<ProviderAwsConfigFile>,
734 pub headers: BTreeMap<String, String>,
735 pub capabilities: ProviderCapabilityOverrides,
736 pub reasoning_effort: Option<ReasoningEffort>,
738}
739
740#[derive(Debug, Clone, PartialEq, Eq, Default, Deserialize)]
741#[serde(deny_unknown_fields)]
742pub struct ProviderAwsConfigFile {
743 pub profile: Option<String>,
744 pub region: Option<String>,
745 pub anthropic_beta: Option<Vec<String>>,
752}
753
754#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
761#[serde(rename_all = "snake_case")]
762pub enum ReasoningEffort {
763 None,
764 Minimal,
765 Low,
766 Medium,
767 High,
768 Xhigh,
769}
770
771#[derive(Debug, Clone, PartialEq, Eq, Default)]
781pub struct HttpClientConfig {
782 pub total_timeout_ms: Option<u64>,
785 pub transport_retries: Option<u8>,
788 pub initial_backoff_ms: Option<u64>,
790 pub user_agent: Option<String>,
793 pub proxy: HttpProxyConfig,
795}
796
797#[derive(Debug, Clone, PartialEq, Eq)]
798pub struct HttpProxyConfig {
799 pub mode: HttpProxyMode,
800 pub explicit: HttpProxySettings,
802}
803
804impl Default for HttpProxyConfig {
805 fn default() -> Self {
806 Self {
807 mode: HttpProxyMode::FromEnv,
808 explicit: HttpProxySettings::default(),
809 }
810 }
811}
812
813#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
814#[serde(rename_all = "kebab-case")]
815pub enum HttpProxyMode {
816 #[default]
817 FromEnv,
818 Disabled,
819 Explicit,
820}
821
822impl HttpProxyMode {
823 pub fn as_str(self) -> &'static str {
824 match self {
825 Self::FromEnv => "from-env",
826 Self::Disabled => "disabled",
827 Self::Explicit => "explicit",
828 }
829 }
830}
831
832#[derive(Debug, Clone, PartialEq, Eq, Default)]
833pub struct HttpProxySettings {
834 pub http_proxy: Option<String>,
835 pub https_proxy: Option<String>,
836 pub no_proxy: Vec<String>,
837}
838
839#[derive(Debug, Clone, PartialEq, Default)]
840pub struct TracingConfig {
841 pub filter: Option<String>,
842 pub otlp: Option<OtlpTracingConfig>,
843 pub langfuse: Option<LangfuseConfig>,
844}
845
846#[derive(Debug, Clone, PartialEq, Default)]
847pub struct OtlpTracingConfig {
848 pub endpoint: Option<String>,
849}
850
851#[derive(Debug, Clone, PartialEq, Eq, Default)]
856pub struct LangfuseConfig {
857 pub enabled: bool,
858 pub host: Option<String>,
861 pub public_key: Option<String>,
862 pub secret_key: Option<String>,
863 pub flush_interval_ms: Option<u64>,
865 pub max_batch: Option<usize>,
867}
868
869#[derive(Debug, Clone, PartialEq, Eq, Default)]
870pub struct McpConfig {
871 pub enabled_servers: Vec<String>,
872 pub servers: BTreeMap<String, McpServerConfig>,
873}
874
875#[derive(Debug, Clone, PartialEq, Eq)]
876pub enum McpServerConfig {
877 Stdio(McpStdioServerConfig),
878 Http(McpRemoteServerConfig),
879 Sse(McpRemoteServerConfig),
880}
881
882#[derive(Debug, Clone, PartialEq, Eq)]
883pub struct McpStdioServerConfig {
884 pub command: String,
885 pub args: Vec<String>,
886 pub env: BTreeMap<String, String>,
887}
888
889#[derive(Debug, Clone, PartialEq, Eq)]
890pub struct McpRemoteServerConfig {
891 pub url: String,
892 pub headers: BTreeMap<String, String>,
893}
894
895#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
896#[serde(rename_all = "snake_case")]
897pub(crate) enum McpTransportKind {
898 Stdio,
899 Http,
900 Sse,
901}
902
903#[derive(Debug, Clone, Default, Deserialize)]
904#[serde(deny_unknown_fields)]
905pub(crate) struct ConfigToml {
906 #[serde(default)]
907 pub(crate) default: DefaultSection,
908 #[serde(default)]
909 pub(crate) base_prompt: BasePromptSection,
910 #[serde(default)]
911 pub(crate) prompt: PromptSection,
912 #[serde(default)]
913 pub(crate) turn: TurnSection,
914 #[serde(default)]
915 pub(crate) capabilities: CapabilitiesSection,
916 #[serde(default)]
917 pub(crate) providers: ProvidersSection,
918 #[serde(default)]
919 pub(crate) tools: ToolsSection,
920 #[serde(default)]
921 pub(crate) sandbox: SandboxSection,
922 #[serde(default)]
923 pub(crate) tracing: TracingSection,
924 #[serde(default)]
925 pub(crate) mcp: McpSection,
926 #[serde(default)]
927 pub(crate) http: HttpSection,
928 #[serde(default)]
933 #[allow(dead_code)]
934 pub(crate) hooks: Option<TomlValue>,
935}
936
937#[derive(Debug, Clone, Default, Deserialize)]
938#[serde(deny_unknown_fields)]
939pub(crate) struct CapabilitiesSection {
940 pub(crate) web_search: Option<WebSearchCapabilitySection>,
941}
942
943#[derive(Debug, Clone, Default, Deserialize)]
944#[serde(deny_unknown_fields)]
945pub(crate) struct WebSearchCapabilitySection {
946 pub(crate) mode: Option<defect_agent::session::WebSearchCapabilityMode>,
947}
948
949#[derive(Debug, Clone, Default, Deserialize)]
950#[serde(deny_unknown_fields)]
951pub(crate) struct ProviderCapabilitiesSection {
952 pub(crate) web_search: Option<WebSearchCapabilitySection>,
953}
954
955#[derive(Debug, Clone, Default, Deserialize)]
956#[serde(deny_unknown_fields)]
957pub(crate) struct DefaultSection {
958 pub(crate) provider: Option<ProviderKind>,
959 pub(crate) model: Option<String>,
960}
961
962#[derive(Debug, Clone, Default, Deserialize)]
963#[serde(deny_unknown_fields)]
964pub(crate) struct BasePromptSection {
965 pub(crate) file: Option<String>,
966 pub(crate) text: Option<String>,
967}
968
969#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
973#[serde(rename_all = "snake_case")]
974pub(crate) enum RequestLimitMode {
975 Fixed,
977 Adaptive,
979 Unbounded,
981}
982
983#[derive(Debug, Clone, Default, Deserialize)]
984#[serde(deny_unknown_fields)]
985pub(crate) struct TurnSection {
986 pub(crate) system_prompt: Option<String>,
987 pub(crate) sampling: Option<SamplingSection>,
994 pub(crate) request_limit: Option<u32>,
995 pub(crate) request_limit_mode: Option<RequestLimitMode>,
997 pub(crate) compact_threshold_tokens: Option<u64>,
998 pub(crate) compact_ratio: Option<f64>,
999 pub(crate) background_compact_enabled: Option<bool>,
1002 pub(crate) compact_soft_ratio: Option<f64>,
1005 pub(crate) microcompact_enabled: Option<bool>,
1008 pub(crate) microcompact_ratio: Option<f64>,
1010 pub(crate) max_llm_retries: Option<u32>,
1011 pub(crate) max_concurrent_tools: Option<usize>,
1012 pub(crate) max_hook_continues: Option<u32>,
1015 pub(crate) subagent_max_depth: Option<u32>,
1019}
1020
1021#[derive(Debug, Clone, Default, Deserialize)]
1025#[serde(deny_unknown_fields)]
1026pub(crate) struct SamplingSection {
1027 pub(crate) max_tokens: Option<u32>,
1030 pub(crate) temperature: Option<f32>,
1031 pub(crate) top_p: Option<f32>,
1032 pub(crate) top_k: Option<u32>,
1033}
1034
1035#[derive(Debug, Clone, Default, Deserialize)]
1036#[serde(deny_unknown_fields)]
1037pub(crate) struct PromptSection {
1038 pub(crate) file: Option<String>,
1039 pub(crate) text: Option<String>,
1040 pub(crate) providers: Option<BTreeMap<String, PromptOverlaySection>>,
1041 pub(crate) models: Option<BTreeMap<String, String>>,
1042}
1043
1044#[derive(Debug, Clone, Default, Deserialize)]
1045#[serde(deny_unknown_fields)]
1046pub(crate) struct PromptOverlaySection {
1047 pub(crate) text: Option<String>,
1048}
1049
1050#[derive(Debug, Clone, Default, Deserialize)]
1051pub(crate) struct ProvidersSection {
1052 pub(crate) anthropic: Option<AnthropicProviderSection>,
1053 pub(crate) openai: Option<OpenAiProviderSection>,
1054 pub(crate) deepseek: Option<DeepSeekProviderSection>,
1055 pub(crate) litellm: Option<LiteLlmProviderSection>,
1056 #[serde(flatten)]
1057 pub(crate) custom: BTreeMap<String, ProviderSection>,
1058}
1059
1060pub(crate) type AnthropicProviderSection = ProviderSection;
1061pub(crate) type OpenAiProviderSection = ProviderSection;
1062pub(crate) type DeepSeekProviderSection = ProviderSection;
1063pub(crate) type LiteLlmProviderSection = ProviderSection;
1064
1065#[derive(Debug, Clone, Default, Deserialize)]
1066#[serde(deny_unknown_fields)]
1067pub(crate) struct ProviderSection {
1068 pub(crate) protocol: Option<ProviderProtocol>,
1069 pub(crate) base_url: Option<String>,
1070 pub(crate) default_model: Option<String>,
1071 pub(crate) models: Option<Vec<ModelEntry>>,
1072 pub(crate) display_name: Option<String>,
1073 pub(crate) api_key_env: Option<String>,
1074 pub(crate) organization: Option<String>,
1075 pub(crate) project: Option<String>,
1076 pub(crate) aws: Option<ProviderAwsConfigFile>,
1077 pub(crate) headers: Option<BTreeMap<String, String>>,
1078 pub(crate) capabilities: Option<ProviderCapabilitiesSection>,
1079 pub(crate) reasoning_effort: Option<ReasoningEffort>,
1080}
1081
1082#[derive(Debug, Clone, Default, Deserialize)]
1083#[serde(deny_unknown_fields)]
1084pub(crate) struct ToolsSection {
1085 pub(crate) bash: Option<BashToolSection>,
1086 pub(crate) fs: Option<FsToolSection>,
1087 pub(crate) fetch: Option<FetchToolSection>,
1088 pub(crate) search: Option<SearchToolSection>,
1092 pub(crate) background: Option<BackgroundToolSection>,
1095}
1096
1097#[derive(Debug, Clone, Default, Deserialize)]
1098#[serde(deny_unknown_fields)]
1099pub(crate) struct BackgroundToolSection {
1100 pub(crate) default_recent_blocks: Option<usize>,
1103 pub(crate) block_text_limit: Option<usize>,
1106 pub(crate) finished_tasks_cap: Option<usize>,
1109}
1110
1111#[derive(Debug, Clone, Default, Deserialize)]
1112#[serde(deny_unknown_fields)]
1113pub(crate) struct SearchToolSection {
1114 pub(crate) enabled: Option<bool>,
1115 pub(crate) default_head_limit: Option<u32>,
1116 pub(crate) max_head_limit: Option<u32>,
1117 pub(crate) max_file_size_bytes: Option<u64>,
1118 pub(crate) max_result_bytes: Option<u64>,
1119 pub(crate) max_walk_files: Option<u64>,
1120 pub(crate) respect_gitignore_default: Option<bool>,
1121}
1122
1123#[derive(Debug, Clone, Default, Deserialize)]
1124#[serde(deny_unknown_fields)]
1125pub(crate) struct FetchToolSection {
1126 pub(crate) enabled: Option<bool>,
1127 pub(crate) default_timeout_secs: Option<u32>,
1128 pub(crate) max_timeout_secs: Option<u32>,
1129 pub(crate) max_response_bytes: Option<u64>,
1130 pub(crate) default_format: Option<FetchFormat>,
1131 pub(crate) html_to_markdown: Option<bool>,
1132 pub(crate) follow_redirects: Option<bool>,
1133}
1134
1135#[derive(Debug, Clone, Default, Deserialize)]
1136#[serde(deny_unknown_fields)]
1137pub(crate) struct BashToolSection {
1138 pub(crate) default_timeout_ms: Option<u64>,
1139 pub(crate) max_timeout_ms: Option<u64>,
1140 pub(crate) output_max_bytes: Option<usize>,
1141}
1142
1143#[derive(Debug, Clone, Default, Deserialize)]
1144#[serde(deny_unknown_fields)]
1145pub(crate) struct FsToolSection {
1146 pub(crate) read_default_limit: Option<u32>,
1147 pub(crate) read_max_limit: Option<u32>,
1148}
1149
1150#[derive(Debug, Clone, Default, Deserialize)]
1151#[serde(deny_unknown_fields)]
1152pub(crate) struct SandboxSection {
1153 pub(crate) mode: Option<SandboxMode>,
1154}
1155
1156#[derive(Debug, Clone, Default, Deserialize)]
1157#[serde(deny_unknown_fields)]
1158pub(crate) struct TracingSection {
1159 pub(crate) filter: Option<String>,
1160 pub(crate) otlp: Option<OtlpTracingSection>,
1161 pub(crate) langfuse: Option<LangfuseSection>,
1162}
1163
1164#[derive(Debug, Clone, Default, Deserialize)]
1165#[serde(deny_unknown_fields)]
1166pub(crate) struct OtlpTracingSection {
1167 pub(crate) endpoint: Option<String>,
1168}
1169
1170#[derive(Debug, Clone, Default, Deserialize)]
1171#[serde(rename_all = "snake_case", deny_unknown_fields)]
1172pub(crate) struct LangfuseSection {
1173 pub(crate) enabled: Option<bool>,
1174 pub(crate) host: Option<String>,
1175 pub(crate) public_key: Option<String>,
1176 pub(crate) secret_key: Option<String>,
1177 pub(crate) flush_interval_ms: Option<u64>,
1178 pub(crate) max_batch: Option<usize>,
1179}
1180
1181#[derive(Debug, Clone, Default, Deserialize)]
1182#[serde(deny_unknown_fields)]
1183pub(crate) struct McpSection {
1184 pub(crate) enabled_servers: Option<Vec<String>>,
1185 pub(crate) servers: Option<BTreeMap<String, McpServerSection>>,
1186}
1187
1188#[derive(Debug, Clone, Default, Deserialize)]
1189#[serde(deny_unknown_fields)]
1190pub(crate) struct HttpSection {
1191 pub(crate) total_timeout_ms: Option<u64>,
1192 pub(crate) transport_retries: Option<u8>,
1193 pub(crate) initial_backoff_ms: Option<u64>,
1194 pub(crate) user_agent: Option<String>,
1195 pub(crate) proxy: Option<HttpProxySection>,
1196}
1197
1198#[derive(Debug, Clone, Default, Deserialize)]
1199#[serde(deny_unknown_fields)]
1200pub(crate) struct HttpProxySection {
1201 pub(crate) mode: Option<HttpProxyMode>,
1202 pub(crate) http_proxy: Option<String>,
1203 pub(crate) https_proxy: Option<String>,
1204 pub(crate) no_proxy: Option<Vec<String>>,
1205}
1206
1207#[derive(Debug, Clone, Default, Deserialize)]
1208#[serde(deny_unknown_fields)]
1209pub(crate) struct McpServerSection {
1210 pub(crate) transport: Option<McpTransportKind>,
1211 pub(crate) command: Option<String>,
1212 pub(crate) args: Option<Vec<String>>,
1213 pub(crate) env: Option<BTreeMap<String, String>>,
1214 pub(crate) url: Option<String>,
1215 pub(crate) headers: Option<BTreeMap<String, String>>,
1216}