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_FS_READ_LIMIT: u32 = 2_000;
19pub(crate) const DEFAULT_FS_READ_MAX_LIMIT: u32 = 5_000;
20
21pub(crate) const USER_CONFIG_RELATIVE: &str = "defect/config.toml";
22pub(crate) const PROJECT_CONFIG_RELATIVE: &str = ".defect/config.toml";
23pub(crate) const PROJECT_LOCAL_CONFIG_RELATIVE: &str = ".defect/config.local.toml";
24
25const PROVIDER_DEFECT: &str = "defect";
26const PROVIDER_ANTHROPIC: &str = "anthropic";
27const PROVIDER_OPENAI: &str = "openai";
28const PROVIDER_DEEPSEEK: &str = "deepseek";
29const PROVIDER_LITELLM: &str = "litellm";
30
31#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
32#[serde(from = "String", into = "String")]
33pub enum ProviderKind {
34 #[default]
39 Defect,
40 Anthropic,
41 Openai,
42 Deepseek,
43 Litellm,
44 Custom(String),
45}
46
47impl ProviderKind {
48 #[must_use]
49 pub fn as_str(&self) -> &str {
50 match self {
51 Self::Defect => PROVIDER_DEFECT,
52 Self::Anthropic => PROVIDER_ANTHROPIC,
53 Self::Openai => PROVIDER_OPENAI,
54 Self::Deepseek => PROVIDER_DEEPSEEK,
55 Self::Litellm => PROVIDER_LITELLM,
56 Self::Custom(value) => value,
57 }
58 }
59}
60
61impl fmt::Display for ProviderKind {
62 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63 f.write_str(self.as_str())
64 }
65}
66
67impl From<ProviderKind> for String {
68 fn from(value: ProviderKind) -> Self {
69 value.to_string()
70 }
71}
72
73impl From<String> for ProviderKind {
74 fn from(value: String) -> Self {
75 match value.as_str() {
76 PROVIDER_DEFECT => Self::Defect,
77 PROVIDER_ANTHROPIC => Self::Anthropic,
78 PROVIDER_OPENAI => Self::Openai,
79 PROVIDER_DEEPSEEK => Self::Deepseek,
80 PROVIDER_LITELLM => Self::Litellm,
81 _ => Self::Custom(value),
82 }
83 }
84}
85
86impl From<&str> for ProviderKind {
87 fn from(value: &str) -> Self {
88 match value {
89 PROVIDER_DEFECT => Self::Defect,
90 PROVIDER_ANTHROPIC => Self::Anthropic,
91 PROVIDER_OPENAI => Self::Openai,
92 PROVIDER_DEEPSEEK => Self::Deepseek,
93 PROVIDER_LITELLM => Self::Litellm,
94 other => Self::Custom(other.to_string()),
95 }
96 }
97}
98
99#[derive(Debug, Clone, Copy, PartialEq, Eq)]
100pub enum ConfigSource {
101 Defaults,
102 User,
103 Project,
104 ProjectLocal,
105 Cli,
106}
107
108#[derive(Debug, Clone, PartialEq)]
109pub struct ConfigLayerEntry {
110 pub source: ConfigSource,
111 pub path: Option<PathBuf>,
112 pub raw_toml: Option<String>,
113 pub value: TomlValue,
114}
115
116#[derive(Debug, Clone, Default, PartialEq)]
117pub struct ConfigLayerStack {
118 pub layers: Vec<ConfigLayerEntry>,
119}
120
121#[derive(Debug, Clone, PartialEq, Eq)]
122pub enum ConfigWarning {
123 DeprecatedKey {
124 path: PathBuf,
125 old: String,
126 new: String,
127 },
128 InactiveSection {
136 path: PathBuf,
137 section: String,
138 reason: String,
139 },
140 McpToolRenamed {
146 server: String,
147 original: String,
148 renamed: String,
149 },
150}
151
152#[non_exhaustive]
153#[derive(Debug, thiserror::Error)]
154pub enum ConfigError {
155 #[error("failed to read config file {path}: {source}")]
156 Io {
157 path: PathBuf,
158 #[source]
159 source: BoxError,
160 },
161
162 #[error("failed to parse config file {path}: {source}")]
163 Parse {
164 path: PathBuf,
165 #[source]
166 source: BoxError,
167 },
168
169 #[error("invalid config at {path}: {message}")]
170 Invalid { path: PathBuf, message: String },
171
172 #[error(transparent)]
173 Source(#[from] BoxError),
174}
175
176#[derive(Debug, Clone, Default)]
177pub struct CliOverrides {
178 pub provider: Option<ProviderKind>,
179 pub model: Option<String>,
180 pub sandbox: Option<SandboxMode>,
181 pub config_overrides: Vec<(String, TomlValue)>,
182}
183
184#[derive(Debug, Clone, Default)]
185pub struct LoadConfigOptions {
186 pub cwd: PathBuf,
187 pub cli: CliOverrides,
188 pub xdg_config_home: Option<PathBuf>,
189 pub home_dir: Option<PathBuf>,
190 pub local: bool,
197}
198
199#[derive(Debug, Clone)]
200pub struct LoadedConfig {
201 pub layers: ConfigLayerStack,
202 pub effective: EffectiveConfig,
203 pub warnings: Vec<ConfigWarning>,
204}
205
206#[derive(Debug, Clone)]
207pub struct EffectiveConfig {
208 pub cli: CliConfig,
209 pub turn: TurnConfig,
210 pub base_prompt: BasePromptConfigFile,
211 pub prompt: PromptConfigFile,
212 pub capabilities: CapabilitiesConfig,
215 pub providers: ProviderConfigs,
216 pub tools: ToolsConfig,
217 pub sandbox: SandboxConfig,
218 pub tracing: TracingConfig,
219 pub mcp: McpConfig,
220 pub http: HttpClientConfig,
221 pub hooks: HooksConfig,
223}
224
225#[derive(Debug, Clone, Default, PartialEq)]
235pub struct HooksConfig {
236 pub buckets: std::collections::BTreeMap<String, Vec<HookEntry>>,
238}
239
240impl HooksConfig {
241 pub fn is_empty(&self) -> bool {
244 self.buckets.values().all(Vec::is_empty)
245 }
246
247 pub fn get(&self, event_name: &str) -> &[HookEntry] {
249 self.buckets
250 .get(event_name)
251 .map(Vec::as_slice)
252 .unwrap_or(&[])
253 }
254
255 pub fn push(&mut self, event_name: impl Into<String>, entry: HookEntry) {
257 self.buckets
258 .entry(event_name.into())
259 .or_default()
260 .push(entry);
261 }
262}
263
264#[derive(Debug, Clone, PartialEq)]
266pub struct HookEntry {
267 pub name: Option<String>,
274 pub matcher: HookMatcher,
275 pub handler: HookHandlerSpec,
276 pub source: ConfigSource,
279}
280
281#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
284pub struct HookMatcher {
285 pub tool: Option<String>,
287 pub tool_glob: Option<String>,
289 pub safety: Vec<defect_agent::tool::SafetyClass>,
292}
293
294#[non_exhaustive]
296#[derive(Debug, Clone, PartialEq, Eq, Hash)]
297pub enum HookHandlerSpec {
298 Builtin { name: String },
301 Command(HookCommandSpec),
303 Prompt(HookPromptSpec),
305}
306
307#[non_exhaustive]
308#[derive(Debug, Clone, PartialEq, Eq, Hash)]
309pub enum HookCommandSpec {
310 Argv {
312 argv: Vec<String>,
313 argv_windows: Option<Vec<String>>,
315 cwd: Option<PathBuf>,
316 env: BTreeMap<String, String>,
317 timeout_sec: Option<u64>,
318 },
319 Shell {
322 shell: HookShellKind,
323 command: String,
324 cwd: Option<PathBuf>,
325 env: BTreeMap<String, String>,
326 timeout_sec: Option<u64>,
327 },
328}
329
330#[non_exhaustive]
331#[derive(Debug, Clone, PartialEq, Eq, Hash)]
332pub enum HookShellKind {
333 Sh,
334 Bash,
335 Pwsh,
336 Cmd,
337 Custom {
339 program: String,
340 args: Vec<String>,
341 },
342}
343
344#[non_exhaustive]
345#[derive(Debug, Clone, PartialEq, Eq, Hash)]
346pub struct HookPromptSpec {
347 pub model: Option<String>,
349 pub system: String,
350 pub render: HookPromptRender,
351 pub timeout_sec: Option<u64>,
352}
353
354impl HookPromptSpec {
355 #[must_use]
358 pub fn new(
359 model: Option<String>,
360 system: String,
361 render: HookPromptRender,
362 timeout_sec: Option<u64>,
363 ) -> Self {
364 Self {
365 model,
366 system,
367 render,
368 timeout_sec,
369 }
370 }
371}
372
373#[non_exhaustive]
374#[derive(Debug, Clone, PartialEq, Eq, Hash)]
375pub enum HookPromptRender {
376 Json,
378 Template { template: String },
380}
381
382#[non_exhaustive]
390#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
391pub struct CapabilitiesConfig {
392 pub web_search: WebSearchCapabilityConfig,
393}
394
395impl CapabilitiesConfig {
396 #[must_use]
400 pub const fn with_web_search(web_search: WebSearchCapabilityConfig) -> Self {
401 Self { web_search }
402 }
403
404 #[must_use]
408 pub fn to_session_capabilities(self) -> SessionCapabilitiesConfig {
409 SessionCapabilitiesConfig::with_web_search(self.web_search)
410 }
411}
412
413#[non_exhaustive]
417#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
418pub struct ProviderCapabilityOverrides {
419 pub web_search: Option<WebSearchCapabilityConfig>,
420}
421
422impl ProviderCapabilityOverrides {
423 #[must_use]
426 pub const fn with_web_search(web_search: Option<WebSearchCapabilityConfig>) -> Self {
427 Self { web_search }
428 }
429
430 #[must_use]
433 pub fn merge_into(&self, base: CapabilitiesConfig) -> CapabilitiesConfig {
434 CapabilitiesConfig::with_web_search(self.web_search.unwrap_or(base.web_search))
435 }
436}
437
438#[derive(Debug, Clone, PartialEq, Eq)]
439pub struct CliConfig {
440 pub provider: ProviderKind,
441 pub model: String,
442}
443
444#[derive(Debug, Clone, PartialEq, Eq, Default)]
445pub struct BasePromptConfigFile {
446 pub file: Option<PathBuf>,
447 pub text: Option<String>,
448}
449
450#[derive(Debug, Clone, PartialEq, Eq, Default)]
451pub struct PromptConfigFile {
452 pub file: String,
453 pub text: Option<String>,
454 pub provider_overlays: BTreeMap<String, String>,
455 pub model_overlays: BTreeMap<String, String>,
456}
457
458#[derive(Debug, Clone, PartialEq, Default)]
459pub struct ProviderConfigs {
460 pub anthropic: ProviderConfigFile,
461 pub openai: ProviderConfigFile,
462 pub deepseek: ProviderConfigFile,
463 pub litellm: ProviderConfigFile,
464 pub custom: BTreeMap<String, ProviderConfigFile>,
465}
466
467impl ProviderConfigs {
468 #[must_use]
469 pub fn get(&self, provider: &ProviderKind) -> Option<&ProviderConfigFile> {
470 match provider {
471 ProviderKind::Defect => None,
472 ProviderKind::Anthropic => Some(&self.anthropic),
473 ProviderKind::Openai => Some(&self.openai),
474 ProviderKind::Deepseek => Some(&self.deepseek),
475 ProviderKind::Litellm => Some(&self.litellm),
476 ProviderKind::Custom(name) => self.custom.get(name),
477 }
478 }
479}
480
481#[derive(Debug, Clone, PartialEq, Eq, Default)]
482pub struct ToolsConfig {
483 pub bash: BashToolConfig,
484 pub fs: FsToolConfig,
485 pub fetch: FetchToolConfig,
488 pub search: SearchToolConfig,
493 pub background: BackgroundProgressConfig,
498}
499
500#[non_exhaustive]
502#[derive(Debug, Clone, PartialEq, Eq)]
503pub struct FetchToolConfig {
504 pub enabled: bool,
505 pub default_timeout_secs: u32,
506 pub max_timeout_secs: u32,
507 pub max_response_bytes: u64,
508 pub default_format: FetchFormat,
509 pub html_to_markdown: bool,
510 pub follow_redirects: bool,
511}
512
513impl Default for FetchToolConfig {
514 fn default() -> Self {
515 Self {
516 enabled: true,
517 default_timeout_secs: 30,
518 max_timeout_secs: 120,
519 max_response_bytes: 5 * 1024 * 1024,
520 default_format: FetchFormat::Markdown,
521 html_to_markdown: true,
522 follow_redirects: true,
523 }
524 }
525}
526
527#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
528#[serde(rename_all = "snake_case")]
529pub enum FetchFormat {
530 #[default]
531 Markdown,
532 Html,
533 Text,
534}
535
536#[derive(Debug, Clone, PartialEq, Eq)]
537pub struct BashToolConfig {
538 pub default_timeout_ms: u64,
539 pub max_timeout_ms: u64,
540}
541
542impl Default for BashToolConfig {
543 fn default() -> Self {
544 Self {
545 default_timeout_ms: DEFAULT_BASH_TIMEOUT_MS,
546 max_timeout_ms: DEFAULT_BASH_MAX_TIMEOUT_MS,
547 }
548 }
549}
550
551#[derive(Debug, Clone, PartialEq, Eq)]
552pub struct FsToolConfig {
553 pub read_default_limit: u32,
554 pub read_max_limit: u32,
555}
556
557impl Default for FsToolConfig {
558 fn default() -> Self {
559 Self {
560 read_default_limit: DEFAULT_FS_READ_LIMIT,
561 read_max_limit: DEFAULT_FS_READ_MAX_LIMIT,
562 }
563 }
564}
565
566#[non_exhaustive]
568#[derive(Debug, Clone, PartialEq, Eq)]
569pub struct SearchToolConfig {
570 pub enabled: bool,
571 pub default_head_limit: u32,
572 pub max_head_limit: u32,
573 pub max_file_size_bytes: u64,
574 pub max_result_bytes: u64,
575 pub max_walk_files: u64,
576 pub respect_gitignore_default: bool,
577}
578
579impl Default for SearchToolConfig {
580 fn default() -> Self {
581 Self {
582 enabled: true,
583 default_head_limit: 100,
584 max_head_limit: 1000,
585 max_file_size_bytes: 16 * 1024 * 1024,
586 max_result_bytes: 256 * 1024,
587 max_walk_files: 100_000,
588 respect_gitignore_default: true,
589 }
590 }
591}
592
593#[derive(Debug, Clone, PartialEq, Eq)]
594pub struct SandboxConfig {
595 pub mode: SandboxMode,
596}
597
598impl Default for SandboxConfig {
599 fn default() -> Self {
600 Self {
601 mode: SandboxMode::AskWrites,
602 }
603 }
604}
605
606#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
607#[serde(rename_all = "kebab-case")]
608pub enum SandboxMode {
609 ReadOnly,
610 #[default]
611 AskWrites,
612 Open,
613 DenyAll,
614}
615
616impl SandboxMode {
617 pub fn as_str(self) -> &'static str {
618 match self {
619 Self::ReadOnly => "read-only",
620 Self::AskWrites => "ask-writes",
621 Self::Open => "open",
622 Self::DenyAll => "deny-all",
623 }
624 }
625}
626
627pub type AnthropicConfigFile = ProviderConfigFile;
628pub type OpenAiConfigFile = ProviderConfigFile;
629pub type DeepSeekConfigFile = ProviderConfigFile;
630pub type LiteLlmConfigFile = ProviderConfigFile;
631
632#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
633#[serde(rename_all = "kebab-case")]
634pub enum ProviderProtocol {
635 AnthropicMessages,
636 OpenaiChat,
637}
638
639#[derive(Debug, Clone, PartialEq, Deserialize)]
652#[serde(untagged)]
653pub enum ModelEntry {
654 Id(String),
656 Detailed {
658 id: String,
659 #[serde(default)]
660 name: Option<String>,
661 },
662}
663
664impl ModelEntry {
665 #[must_use]
667 pub fn id(&self) -> &str {
668 match self {
669 Self::Id(id) => id,
670 Self::Detailed { id, .. } => id,
671 }
672 }
673
674 #[must_use]
676 pub fn name(&self) -> Option<&str> {
677 match self {
678 Self::Id(_) => None,
679 Self::Detailed { name, .. } => name.as_deref(),
680 }
681 }
682}
683
684#[derive(Debug, Clone, PartialEq, Default)]
685pub struct ProviderConfigFile {
686 pub protocol: Option<ProviderProtocol>,
687 pub base_url: Option<String>,
688 pub default_model: Option<String>,
689 pub models: Option<Vec<ModelEntry>>,
690 pub display_name: Option<String>,
691 pub api_key_env: Option<String>,
692 pub organization: Option<String>,
693 pub project: Option<String>,
694 pub aws: Option<ProviderAwsConfigFile>,
695 pub headers: BTreeMap<String, String>,
696 pub capabilities: ProviderCapabilityOverrides,
697 pub reasoning_effort: Option<ReasoningEffort>,
699}
700
701#[derive(Debug, Clone, PartialEq, Eq, Default, Deserialize)]
702#[serde(deny_unknown_fields)]
703pub struct ProviderAwsConfigFile {
704 pub profile: Option<String>,
705 pub region: Option<String>,
706}
707
708#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
715#[serde(rename_all = "snake_case")]
716pub enum ReasoningEffort {
717 None,
718 Minimal,
719 Low,
720 Medium,
721 High,
722 Xhigh,
723}
724
725#[derive(Debug, Clone, PartialEq, Eq, Default)]
735pub struct HttpClientConfig {
736 pub total_timeout_ms: Option<u64>,
739 pub transport_retries: Option<u8>,
742 pub initial_backoff_ms: Option<u64>,
744 pub user_agent: Option<String>,
747 pub proxy: HttpProxyConfig,
749}
750
751#[derive(Debug, Clone, PartialEq, Eq)]
752pub struct HttpProxyConfig {
753 pub mode: HttpProxyMode,
754 pub explicit: HttpProxySettings,
756}
757
758impl Default for HttpProxyConfig {
759 fn default() -> Self {
760 Self {
761 mode: HttpProxyMode::FromEnv,
762 explicit: HttpProxySettings::default(),
763 }
764 }
765}
766
767#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
768#[serde(rename_all = "kebab-case")]
769pub enum HttpProxyMode {
770 #[default]
771 FromEnv,
772 Disabled,
773 Explicit,
774}
775
776impl HttpProxyMode {
777 pub fn as_str(self) -> &'static str {
778 match self {
779 Self::FromEnv => "from-env",
780 Self::Disabled => "disabled",
781 Self::Explicit => "explicit",
782 }
783 }
784}
785
786#[derive(Debug, Clone, PartialEq, Eq, Default)]
787pub struct HttpProxySettings {
788 pub http_proxy: Option<String>,
789 pub https_proxy: Option<String>,
790 pub no_proxy: Vec<String>,
791}
792
793#[derive(Debug, Clone, PartialEq, Default)]
794pub struct TracingConfig {
795 pub filter: Option<String>,
796 pub otlp: Option<OtlpTracingConfig>,
797 pub langfuse: Option<LangfuseConfig>,
798}
799
800#[derive(Debug, Clone, PartialEq, Default)]
801pub struct OtlpTracingConfig {
802 pub endpoint: Option<String>,
803}
804
805#[derive(Debug, Clone, PartialEq, Eq, Default)]
810pub struct LangfuseConfig {
811 pub enabled: bool,
812 pub host: Option<String>,
815 pub public_key: Option<String>,
816 pub secret_key: Option<String>,
817 pub flush_interval_ms: Option<u64>,
819 pub max_batch: Option<usize>,
821}
822
823#[derive(Debug, Clone, PartialEq, Eq, Default)]
824pub struct McpConfig {
825 pub enabled_servers: Vec<String>,
826 pub servers: BTreeMap<String, McpServerConfig>,
827}
828
829#[derive(Debug, Clone, PartialEq, Eq)]
830pub enum McpServerConfig {
831 Stdio(McpStdioServerConfig),
832 Http(McpRemoteServerConfig),
833 Sse(McpRemoteServerConfig),
834}
835
836#[derive(Debug, Clone, PartialEq, Eq)]
837pub struct McpStdioServerConfig {
838 pub command: String,
839 pub args: Vec<String>,
840 pub env: BTreeMap<String, String>,
841}
842
843#[derive(Debug, Clone, PartialEq, Eq)]
844pub struct McpRemoteServerConfig {
845 pub url: String,
846 pub headers: BTreeMap<String, String>,
847}
848
849#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
850#[serde(rename_all = "snake_case")]
851pub(crate) enum McpTransportKind {
852 Stdio,
853 Http,
854 Sse,
855}
856
857#[derive(Debug, Clone, Default, Deserialize)]
858#[serde(deny_unknown_fields)]
859pub(crate) struct ConfigToml {
860 #[serde(default)]
861 pub(crate) default: DefaultSection,
862 #[serde(default)]
863 pub(crate) base_prompt: BasePromptSection,
864 #[serde(default)]
865 pub(crate) prompt: PromptSection,
866 #[serde(default)]
867 pub(crate) turn: TurnSection,
868 #[serde(default)]
869 pub(crate) capabilities: CapabilitiesSection,
870 #[serde(default)]
871 pub(crate) providers: ProvidersSection,
872 #[serde(default)]
873 pub(crate) tools: ToolsSection,
874 #[serde(default)]
875 pub(crate) sandbox: SandboxSection,
876 #[serde(default)]
877 pub(crate) tracing: TracingSection,
878 #[serde(default)]
879 pub(crate) mcp: McpSection,
880 #[serde(default)]
881 pub(crate) http: HttpSection,
882 #[serde(default)]
887 #[allow(dead_code)]
888 pub(crate) hooks: Option<TomlValue>,
889}
890
891#[derive(Debug, Clone, Default, Deserialize)]
892#[serde(deny_unknown_fields)]
893pub(crate) struct CapabilitiesSection {
894 pub(crate) web_search: Option<WebSearchCapabilitySection>,
895}
896
897#[derive(Debug, Clone, Default, Deserialize)]
898#[serde(deny_unknown_fields)]
899pub(crate) struct WebSearchCapabilitySection {
900 pub(crate) mode: Option<defect_agent::session::WebSearchCapabilityMode>,
901}
902
903#[derive(Debug, Clone, Default, Deserialize)]
904#[serde(deny_unknown_fields)]
905pub(crate) struct ProviderCapabilitiesSection {
906 pub(crate) web_search: Option<WebSearchCapabilitySection>,
907}
908
909#[derive(Debug, Clone, Default, Deserialize)]
910#[serde(deny_unknown_fields)]
911pub(crate) struct DefaultSection {
912 pub(crate) provider: Option<ProviderKind>,
913 pub(crate) model: Option<String>,
914}
915
916#[derive(Debug, Clone, Default, Deserialize)]
917#[serde(deny_unknown_fields)]
918pub(crate) struct BasePromptSection {
919 pub(crate) file: Option<String>,
920 pub(crate) text: Option<String>,
921}
922
923#[derive(Debug, Clone, Default, Deserialize)]
924#[serde(deny_unknown_fields)]
925pub(crate) struct TurnSection {
926 pub(crate) system_prompt: Option<String>,
927 pub(crate) request_limit: Option<u32>,
928 pub(crate) compact_threshold_tokens: Option<u64>,
929 pub(crate) compact_ratio: Option<f64>,
930 pub(crate) background_compact_enabled: Option<bool>,
933 pub(crate) compact_soft_ratio: Option<f64>,
936 pub(crate) microcompact_enabled: Option<bool>,
939 pub(crate) microcompact_ratio: Option<f64>,
941 pub(crate) max_llm_retries: Option<u32>,
942 pub(crate) max_concurrent_tools: Option<usize>,
943 pub(crate) max_hook_continues: Option<u32>,
946 pub(crate) subagent_max_depth: Option<u32>,
950}
951
952#[derive(Debug, Clone, Default, Deserialize)]
953#[serde(deny_unknown_fields)]
954pub(crate) struct PromptSection {
955 pub(crate) file: Option<String>,
956 pub(crate) text: Option<String>,
957 pub(crate) providers: Option<BTreeMap<String, PromptOverlaySection>>,
958 pub(crate) models: Option<BTreeMap<String, String>>,
959}
960
961#[derive(Debug, Clone, Default, Deserialize)]
962#[serde(deny_unknown_fields)]
963pub(crate) struct PromptOverlaySection {
964 pub(crate) text: Option<String>,
965}
966
967#[derive(Debug, Clone, Default, Deserialize)]
968pub(crate) struct ProvidersSection {
969 pub(crate) anthropic: Option<AnthropicProviderSection>,
970 pub(crate) openai: Option<OpenAiProviderSection>,
971 pub(crate) deepseek: Option<DeepSeekProviderSection>,
972 pub(crate) litellm: Option<LiteLlmProviderSection>,
973 #[serde(flatten)]
974 pub(crate) custom: BTreeMap<String, ProviderSection>,
975}
976
977pub(crate) type AnthropicProviderSection = ProviderSection;
978pub(crate) type OpenAiProviderSection = ProviderSection;
979pub(crate) type DeepSeekProviderSection = ProviderSection;
980pub(crate) type LiteLlmProviderSection = ProviderSection;
981
982#[derive(Debug, Clone, Default, Deserialize)]
983#[serde(deny_unknown_fields)]
984pub(crate) struct ProviderSection {
985 pub(crate) protocol: Option<ProviderProtocol>,
986 pub(crate) base_url: Option<String>,
987 pub(crate) default_model: Option<String>,
988 pub(crate) models: Option<Vec<ModelEntry>>,
989 pub(crate) display_name: Option<String>,
990 pub(crate) api_key_env: Option<String>,
991 pub(crate) organization: Option<String>,
992 pub(crate) project: Option<String>,
993 pub(crate) aws: Option<ProviderAwsConfigFile>,
994 pub(crate) headers: Option<BTreeMap<String, String>>,
995 pub(crate) capabilities: Option<ProviderCapabilitiesSection>,
996 pub(crate) reasoning_effort: Option<ReasoningEffort>,
997}
998
999#[derive(Debug, Clone, Default, Deserialize)]
1000#[serde(deny_unknown_fields)]
1001pub(crate) struct ToolsSection {
1002 pub(crate) bash: Option<BashToolSection>,
1003 pub(crate) fs: Option<FsToolSection>,
1004 pub(crate) fetch: Option<FetchToolSection>,
1005 pub(crate) search: Option<SearchToolSection>,
1009 pub(crate) background: Option<BackgroundToolSection>,
1012}
1013
1014#[derive(Debug, Clone, Default, Deserialize)]
1015#[serde(deny_unknown_fields)]
1016pub(crate) struct BackgroundToolSection {
1017 pub(crate) default_recent_blocks: Option<usize>,
1020 pub(crate) block_text_limit: Option<usize>,
1023}
1024
1025#[derive(Debug, Clone, Default, Deserialize)]
1026#[serde(deny_unknown_fields)]
1027pub(crate) struct SearchToolSection {
1028 pub(crate) enabled: Option<bool>,
1029 pub(crate) default_head_limit: Option<u32>,
1030 pub(crate) max_head_limit: Option<u32>,
1031 pub(crate) max_file_size_bytes: Option<u64>,
1032 pub(crate) max_result_bytes: Option<u64>,
1033 pub(crate) max_walk_files: Option<u64>,
1034 pub(crate) respect_gitignore_default: Option<bool>,
1035}
1036
1037#[derive(Debug, Clone, Default, Deserialize)]
1038#[serde(deny_unknown_fields)]
1039pub(crate) struct FetchToolSection {
1040 pub(crate) enabled: Option<bool>,
1041 pub(crate) default_timeout_secs: Option<u32>,
1042 pub(crate) max_timeout_secs: Option<u32>,
1043 pub(crate) max_response_bytes: Option<u64>,
1044 pub(crate) default_format: Option<FetchFormat>,
1045 pub(crate) html_to_markdown: Option<bool>,
1046 pub(crate) follow_redirects: Option<bool>,
1047}
1048
1049#[derive(Debug, Clone, Default, Deserialize)]
1050#[serde(deny_unknown_fields)]
1051pub(crate) struct BashToolSection {
1052 pub(crate) default_timeout_ms: Option<u64>,
1053 pub(crate) max_timeout_ms: Option<u64>,
1054}
1055
1056#[derive(Debug, Clone, Default, Deserialize)]
1057#[serde(deny_unknown_fields)]
1058pub(crate) struct FsToolSection {
1059 pub(crate) read_default_limit: Option<u32>,
1060 pub(crate) read_max_limit: Option<u32>,
1061}
1062
1063#[derive(Debug, Clone, Default, Deserialize)]
1064#[serde(deny_unknown_fields)]
1065pub(crate) struct SandboxSection {
1066 pub(crate) mode: Option<SandboxMode>,
1067}
1068
1069#[derive(Debug, Clone, Default, Deserialize)]
1070#[serde(deny_unknown_fields)]
1071pub(crate) struct TracingSection {
1072 pub(crate) filter: Option<String>,
1073 pub(crate) otlp: Option<OtlpTracingSection>,
1074 pub(crate) langfuse: Option<LangfuseSection>,
1075}
1076
1077#[derive(Debug, Clone, Default, Deserialize)]
1078#[serde(deny_unknown_fields)]
1079pub(crate) struct OtlpTracingSection {
1080 pub(crate) endpoint: Option<String>,
1081}
1082
1083#[derive(Debug, Clone, Default, Deserialize)]
1084#[serde(rename_all = "snake_case", deny_unknown_fields)]
1085pub(crate) struct LangfuseSection {
1086 pub(crate) enabled: Option<bool>,
1087 pub(crate) host: Option<String>,
1088 pub(crate) public_key: Option<String>,
1089 pub(crate) secret_key: Option<String>,
1090 pub(crate) flush_interval_ms: Option<u64>,
1091 pub(crate) max_batch: Option<usize>,
1092}
1093
1094#[derive(Debug, Clone, Default, Deserialize)]
1095#[serde(deny_unknown_fields)]
1096pub(crate) struct McpSection {
1097 pub(crate) enabled_servers: Option<Vec<String>>,
1098 pub(crate) servers: Option<BTreeMap<String, McpServerSection>>,
1099}
1100
1101#[derive(Debug, Clone, Default, Deserialize)]
1102#[serde(deny_unknown_fields)]
1103pub(crate) struct HttpSection {
1104 pub(crate) total_timeout_ms: Option<u64>,
1105 pub(crate) transport_retries: Option<u8>,
1106 pub(crate) initial_backoff_ms: Option<u64>,
1107 pub(crate) user_agent: Option<String>,
1108 pub(crate) proxy: Option<HttpProxySection>,
1109}
1110
1111#[derive(Debug, Clone, Default, Deserialize)]
1112#[serde(deny_unknown_fields)]
1113pub(crate) struct HttpProxySection {
1114 pub(crate) mode: Option<HttpProxyMode>,
1115 pub(crate) http_proxy: Option<String>,
1116 pub(crate) https_proxy: Option<String>,
1117 pub(crate) no_proxy: Option<Vec<String>>,
1118}
1119
1120#[derive(Debug, Clone, Default, Deserialize)]
1121#[serde(deny_unknown_fields)]
1122pub(crate) struct McpServerSection {
1123 pub(crate) transport: Option<McpTransportKind>,
1124 pub(crate) command: Option<String>,
1125 pub(crate) args: Option<Vec<String>>,
1126 pub(crate) env: Option<BTreeMap<String, String>>,
1127 pub(crate) url: Option<String>,
1128 pub(crate) headers: Option<BTreeMap<String, String>>,
1129}