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 },
682}
683
684impl ModelEntry {
685 #[must_use]
687 pub fn id(&self) -> &str {
688 match self {
689 Self::Id(id) => id,
690 Self::Detailed { id, .. } => id,
691 }
692 }
693
694 #[must_use]
696 pub fn name(&self) -> Option<&str> {
697 match self {
698 Self::Id(_) => None,
699 Self::Detailed { name, .. } => name.as_deref(),
700 }
701 }
702
703 #[must_use]
705 pub fn context_window(&self) -> Option<u64> {
706 match self {
707 Self::Id(_) => None,
708 Self::Detailed { context_window, .. } => *context_window,
709 }
710 }
711
712 #[must_use]
714 pub fn max_output_tokens(&self) -> Option<u64> {
715 match self {
716 Self::Id(_) => None,
717 Self::Detailed {
718 max_output_tokens, ..
719 } => *max_output_tokens,
720 }
721 }
722}
723
724#[derive(Debug, Clone, PartialEq, Default)]
725pub struct ProviderConfigFile {
726 pub protocol: Option<ProviderProtocol>,
727 pub base_url: Option<String>,
728 pub default_model: Option<String>,
729 pub models: Option<Vec<ModelEntry>>,
730 pub display_name: Option<String>,
731 pub api_key_env: Option<String>,
732 pub organization: Option<String>,
733 pub project: Option<String>,
734 pub aws: Option<ProviderAwsConfigFile>,
735 pub headers: BTreeMap<String, String>,
736 pub capabilities: ProviderCapabilityOverrides,
737 pub reasoning_effort: Option<ReasoningEffort>,
739}
740
741#[derive(Debug, Clone, PartialEq, Eq, Default, Deserialize)]
742#[serde(deny_unknown_fields)]
743pub struct ProviderAwsConfigFile {
744 pub profile: Option<String>,
745 pub region: Option<String>,
746 pub anthropic_beta: Option<Vec<String>>,
753}
754
755#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
762#[serde(rename_all = "snake_case")]
763pub enum ReasoningEffort {
764 None,
765 Minimal,
766 Low,
767 Medium,
768 High,
769 Xhigh,
770}
771
772#[derive(Debug, Clone, PartialEq, Eq, Default)]
782pub struct HttpClientConfig {
783 pub total_timeout_ms: Option<u64>,
786 pub transport_retries: Option<u8>,
789 pub initial_backoff_ms: Option<u64>,
791 pub user_agent: Option<String>,
794 pub proxy: HttpProxyConfig,
796}
797
798#[derive(Debug, Clone, PartialEq, Eq)]
799pub struct HttpProxyConfig {
800 pub mode: HttpProxyMode,
801 pub explicit: HttpProxySettings,
803}
804
805impl Default for HttpProxyConfig {
806 fn default() -> Self {
807 Self {
808 mode: HttpProxyMode::FromEnv,
809 explicit: HttpProxySettings::default(),
810 }
811 }
812}
813
814#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
815#[serde(rename_all = "kebab-case")]
816pub enum HttpProxyMode {
817 #[default]
818 FromEnv,
819 Disabled,
820 Explicit,
821}
822
823impl HttpProxyMode {
824 pub fn as_str(self) -> &'static str {
825 match self {
826 Self::FromEnv => "from-env",
827 Self::Disabled => "disabled",
828 Self::Explicit => "explicit",
829 }
830 }
831}
832
833#[derive(Debug, Clone, PartialEq, Eq, Default)]
834pub struct HttpProxySettings {
835 pub http_proxy: Option<String>,
836 pub https_proxy: Option<String>,
837 pub no_proxy: Vec<String>,
838}
839
840#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
844#[serde(rename_all = "kebab-case")]
845pub enum LogFormat {
846 #[default]
848 Text,
849 Jsonl,
851}
852
853impl LogFormat {
854 pub fn as_str(self) -> &'static str {
855 match self {
856 Self::Text => "text",
857 Self::Jsonl => "jsonl",
858 }
859 }
860}
861
862#[derive(Debug, Clone, PartialEq, Default)]
863pub struct TracingConfig {
864 pub filter: Option<String>,
865 pub format: LogFormat,
866 pub otlp: Option<OtlpTracingConfig>,
867 pub langfuse: Option<LangfuseConfig>,
868}
869
870#[derive(Debug, Clone, PartialEq, Default)]
871pub struct OtlpTracingConfig {
872 pub endpoint: Option<String>,
873}
874
875#[derive(Debug, Clone, PartialEq, Eq, Default)]
880pub struct LangfuseConfig {
881 pub enabled: bool,
882 pub host: Option<String>,
885 pub public_key: Option<String>,
886 pub secret_key: Option<String>,
887 pub flush_interval_ms: Option<u64>,
889 pub max_batch: Option<usize>,
891}
892
893#[derive(Debug, Clone, PartialEq, Eq, Default)]
894pub struct McpConfig {
895 pub enabled_servers: Vec<String>,
896 pub servers: BTreeMap<String, McpServerConfig>,
897}
898
899#[derive(Debug, Clone, PartialEq, Eq)]
900pub enum McpServerConfig {
901 Stdio(McpStdioServerConfig),
902 Http(McpRemoteServerConfig),
903 Sse(McpRemoteServerConfig),
904}
905
906#[derive(Debug, Clone, PartialEq, Eq)]
907pub struct McpStdioServerConfig {
908 pub command: String,
909 pub args: Vec<String>,
910 pub env: BTreeMap<String, String>,
911}
912
913#[derive(Debug, Clone, PartialEq, Eq)]
914pub struct McpRemoteServerConfig {
915 pub url: String,
916 pub headers: BTreeMap<String, String>,
917}
918
919#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
920#[serde(rename_all = "snake_case")]
921pub(crate) enum McpTransportKind {
922 Stdio,
923 Http,
924 Sse,
925}
926
927#[derive(Debug, Clone, Default, Deserialize)]
928#[serde(deny_unknown_fields)]
929pub(crate) struct ConfigToml {
930 #[serde(default)]
931 pub(crate) default: DefaultSection,
932 #[serde(default)]
933 pub(crate) base_prompt: BasePromptSection,
934 #[serde(default)]
935 pub(crate) prompt: PromptSection,
936 #[serde(default)]
937 pub(crate) turn: TurnSection,
938 #[serde(default)]
939 pub(crate) capabilities: CapabilitiesSection,
940 #[serde(default)]
941 pub(crate) providers: ProvidersSection,
942 #[serde(default)]
943 pub(crate) tools: ToolsSection,
944 #[serde(default)]
945 pub(crate) sandbox: SandboxSection,
946 #[serde(default)]
947 pub(crate) tracing: TracingSection,
948 #[serde(default)]
949 pub(crate) mcp: McpSection,
950 #[serde(default)]
951 pub(crate) http: HttpSection,
952 #[serde(default)]
957 #[allow(dead_code)]
958 pub(crate) hooks: Option<TomlValue>,
959}
960
961#[derive(Debug, Clone, Default, Deserialize)]
962#[serde(deny_unknown_fields)]
963pub(crate) struct CapabilitiesSection {
964 pub(crate) web_search: Option<WebSearchCapabilitySection>,
965}
966
967#[derive(Debug, Clone, Default, Deserialize)]
968#[serde(deny_unknown_fields)]
969pub(crate) struct WebSearchCapabilitySection {
970 pub(crate) mode: Option<defect_agent::session::WebSearchCapabilityMode>,
971}
972
973#[derive(Debug, Clone, Default, Deserialize)]
974#[serde(deny_unknown_fields)]
975pub(crate) struct ProviderCapabilitiesSection {
976 pub(crate) web_search: Option<WebSearchCapabilitySection>,
977}
978
979#[derive(Debug, Clone, Default, Deserialize)]
980#[serde(deny_unknown_fields)]
981pub(crate) struct DefaultSection {
982 pub(crate) provider: Option<ProviderKind>,
983 pub(crate) model: Option<String>,
984}
985
986#[derive(Debug, Clone, Default, Deserialize)]
987#[serde(deny_unknown_fields)]
988pub(crate) struct BasePromptSection {
989 pub(crate) file: Option<String>,
990 pub(crate) text: Option<String>,
991}
992
993#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
997#[serde(rename_all = "snake_case")]
998pub(crate) enum RequestLimitMode {
999 Fixed,
1001 Adaptive,
1003 Unbounded,
1005}
1006
1007#[derive(Debug, Clone, Default, Deserialize)]
1008#[serde(deny_unknown_fields)]
1009pub(crate) struct TurnSection {
1010 pub(crate) system_prompt: Option<String>,
1011 pub(crate) sampling: Option<SamplingSection>,
1018 pub(crate) request_limit: Option<u32>,
1019 pub(crate) request_limit_mode: Option<RequestLimitMode>,
1021 pub(crate) compact_threshold_tokens: Option<u64>,
1022 pub(crate) compact_ratio: Option<f64>,
1023 pub(crate) background_compact_enabled: Option<bool>,
1026 pub(crate) compact_soft_ratio: Option<f64>,
1029 pub(crate) microcompact_enabled: Option<bool>,
1032 pub(crate) microcompact_ratio: Option<f64>,
1034 pub(crate) max_llm_retries: Option<u32>,
1035 pub(crate) max_concurrent_tools: Option<usize>,
1036 pub(crate) max_hook_continues: Option<u32>,
1039 pub(crate) subagent_max_depth: Option<u32>,
1043}
1044
1045#[derive(Debug, Clone, Default, Deserialize)]
1049#[serde(deny_unknown_fields)]
1050pub(crate) struct SamplingSection {
1051 pub(crate) max_tokens: Option<u32>,
1054 pub(crate) temperature: Option<f32>,
1055 pub(crate) top_p: Option<f32>,
1056 pub(crate) top_k: Option<u32>,
1057}
1058
1059#[derive(Debug, Clone, Default, Deserialize)]
1060#[serde(deny_unknown_fields)]
1061pub(crate) struct PromptSection {
1062 pub(crate) file: Option<String>,
1063 pub(crate) text: Option<String>,
1064 pub(crate) providers: Option<BTreeMap<String, PromptOverlaySection>>,
1065 pub(crate) models: Option<BTreeMap<String, String>>,
1066}
1067
1068#[derive(Debug, Clone, Default, Deserialize)]
1069#[serde(deny_unknown_fields)]
1070pub(crate) struct PromptOverlaySection {
1071 pub(crate) text: Option<String>,
1072}
1073
1074#[derive(Debug, Clone, Default, Deserialize)]
1075pub(crate) struct ProvidersSection {
1076 pub(crate) anthropic: Option<AnthropicProviderSection>,
1077 pub(crate) openai: Option<OpenAiProviderSection>,
1078 pub(crate) deepseek: Option<DeepSeekProviderSection>,
1079 pub(crate) litellm: Option<LiteLlmProviderSection>,
1080 #[serde(flatten)]
1081 pub(crate) custom: BTreeMap<String, ProviderSection>,
1082}
1083
1084pub(crate) type AnthropicProviderSection = ProviderSection;
1085pub(crate) type OpenAiProviderSection = ProviderSection;
1086pub(crate) type DeepSeekProviderSection = ProviderSection;
1087pub(crate) type LiteLlmProviderSection = ProviderSection;
1088
1089#[derive(Debug, Clone, Default, Deserialize)]
1090#[serde(deny_unknown_fields)]
1091pub(crate) struct ProviderSection {
1092 pub(crate) protocol: Option<ProviderProtocol>,
1093 pub(crate) base_url: Option<String>,
1094 pub(crate) default_model: Option<String>,
1095 pub(crate) models: Option<Vec<ModelEntry>>,
1096 pub(crate) display_name: Option<String>,
1097 pub(crate) api_key_env: Option<String>,
1098 pub(crate) organization: Option<String>,
1099 pub(crate) project: Option<String>,
1100 pub(crate) aws: Option<ProviderAwsConfigFile>,
1101 pub(crate) headers: Option<BTreeMap<String, String>>,
1102 pub(crate) capabilities: Option<ProviderCapabilitiesSection>,
1103 pub(crate) reasoning_effort: Option<ReasoningEffort>,
1104}
1105
1106#[derive(Debug, Clone, Default, Deserialize)]
1107#[serde(deny_unknown_fields)]
1108pub(crate) struct ToolsSection {
1109 pub(crate) bash: Option<BashToolSection>,
1110 pub(crate) fs: Option<FsToolSection>,
1111 pub(crate) fetch: Option<FetchToolSection>,
1112 pub(crate) search: Option<SearchToolSection>,
1116 pub(crate) background: Option<BackgroundToolSection>,
1119}
1120
1121#[derive(Debug, Clone, Default, Deserialize)]
1122#[serde(deny_unknown_fields)]
1123pub(crate) struct BackgroundToolSection {
1124 pub(crate) default_recent_blocks: Option<usize>,
1127 pub(crate) block_text_limit: Option<usize>,
1130 pub(crate) finished_tasks_cap: Option<usize>,
1133}
1134
1135#[derive(Debug, Clone, Default, Deserialize)]
1136#[serde(deny_unknown_fields)]
1137pub(crate) struct SearchToolSection {
1138 pub(crate) enabled: Option<bool>,
1139 pub(crate) default_head_limit: Option<u32>,
1140 pub(crate) max_head_limit: Option<u32>,
1141 pub(crate) max_file_size_bytes: Option<u64>,
1142 pub(crate) max_result_bytes: Option<u64>,
1143 pub(crate) max_walk_files: Option<u64>,
1144 pub(crate) respect_gitignore_default: Option<bool>,
1145}
1146
1147#[derive(Debug, Clone, Default, Deserialize)]
1148#[serde(deny_unknown_fields)]
1149pub(crate) struct FetchToolSection {
1150 pub(crate) enabled: Option<bool>,
1151 pub(crate) default_timeout_secs: Option<u32>,
1152 pub(crate) max_timeout_secs: Option<u32>,
1153 pub(crate) max_response_bytes: Option<u64>,
1154 pub(crate) default_format: Option<FetchFormat>,
1155 pub(crate) html_to_markdown: Option<bool>,
1156 pub(crate) follow_redirects: Option<bool>,
1157}
1158
1159#[derive(Debug, Clone, Default, Deserialize)]
1160#[serde(deny_unknown_fields)]
1161pub(crate) struct BashToolSection {
1162 pub(crate) default_timeout_ms: Option<u64>,
1163 pub(crate) max_timeout_ms: Option<u64>,
1164 pub(crate) output_max_bytes: Option<usize>,
1165}
1166
1167#[derive(Debug, Clone, Default, Deserialize)]
1168#[serde(deny_unknown_fields)]
1169pub(crate) struct FsToolSection {
1170 pub(crate) read_default_limit: Option<u32>,
1171 pub(crate) read_max_limit: Option<u32>,
1172}
1173
1174#[derive(Debug, Clone, Default, Deserialize)]
1175#[serde(deny_unknown_fields)]
1176pub(crate) struct SandboxSection {
1177 pub(crate) mode: Option<SandboxMode>,
1178}
1179
1180#[derive(Debug, Clone, Default, Deserialize)]
1181#[serde(deny_unknown_fields)]
1182pub(crate) struct TracingSection {
1183 pub(crate) filter: Option<String>,
1184 pub(crate) format: Option<LogFormat>,
1185 pub(crate) otlp: Option<OtlpTracingSection>,
1186 pub(crate) langfuse: Option<LangfuseSection>,
1187}
1188
1189#[derive(Debug, Clone, Default, Deserialize)]
1190#[serde(deny_unknown_fields)]
1191pub(crate) struct OtlpTracingSection {
1192 pub(crate) endpoint: Option<String>,
1193}
1194
1195#[derive(Debug, Clone, Default, Deserialize)]
1196#[serde(rename_all = "snake_case", deny_unknown_fields)]
1197pub(crate) struct LangfuseSection {
1198 pub(crate) enabled: Option<bool>,
1199 pub(crate) host: Option<String>,
1200 pub(crate) public_key: Option<String>,
1201 pub(crate) secret_key: Option<String>,
1202 pub(crate) flush_interval_ms: Option<u64>,
1203 pub(crate) max_batch: Option<usize>,
1204}
1205
1206#[derive(Debug, Clone, Default, Deserialize)]
1207#[serde(deny_unknown_fields)]
1208pub(crate) struct McpSection {
1209 pub(crate) enabled_servers: Option<Vec<String>>,
1210 pub(crate) servers: Option<BTreeMap<String, McpServerSection>>,
1211}
1212
1213#[derive(Debug, Clone, Default, Deserialize)]
1214#[serde(deny_unknown_fields)]
1215pub(crate) struct HttpSection {
1216 pub(crate) total_timeout_ms: Option<u64>,
1217 pub(crate) transport_retries: Option<u8>,
1218 pub(crate) initial_backoff_ms: Option<u64>,
1219 pub(crate) user_agent: Option<String>,
1220 pub(crate) proxy: Option<HttpProxySection>,
1221}
1222
1223#[derive(Debug, Clone, Default, Deserialize)]
1224#[serde(deny_unknown_fields)]
1225pub(crate) struct HttpProxySection {
1226 pub(crate) mode: Option<HttpProxyMode>,
1227 pub(crate) http_proxy: Option<String>,
1228 pub(crate) https_proxy: Option<String>,
1229 pub(crate) no_proxy: Option<Vec<String>>,
1230}
1231
1232#[derive(Debug, Clone, Default, Deserialize)]
1233#[serde(deny_unknown_fields)]
1234pub(crate) struct McpServerSection {
1235 pub(crate) transport: Option<McpTransportKind>,
1236 pub(crate) command: Option<String>,
1237 pub(crate) args: Option<Vec<String>>,
1238 pub(crate) env: Option<BTreeMap<String, String>>,
1239 pub(crate) url: Option<String>,
1240 pub(crate) headers: Option<BTreeMap<String, String>>,
1241}