1use anyhow::{Context, Result, ensure};
2use serde::{Deserialize, Serialize};
3
4#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
10#[derive(Debug, Clone, Deserialize, Serialize)]
11pub struct DynamicContextConfig {
12 #[serde(default = "default_dynamic_enabled")]
14 pub enabled: bool,
15
16 #[serde(default = "default_tool_output_threshold")]
18 pub tool_output_threshold: usize,
19
20 #[serde(default = "default_sync_terminals")]
22 pub sync_terminals: bool,
23
24 #[serde(default = "default_persist_history")]
26 pub persist_history: bool,
27
28 #[serde(default = "default_sync_mcp_tools")]
30 pub sync_mcp_tools: bool,
31
32 #[serde(default = "default_sync_skills")]
34 pub sync_skills: bool,
35
36 #[serde(default = "default_spool_max_age_secs")]
38 pub spool_max_age_secs: u64,
39
40 #[serde(default = "default_max_spooled_files")]
42 pub max_spooled_files: usize,
43}
44
45impl Default for DynamicContextConfig {
46 fn default() -> Self {
47 Self {
48 enabled: default_dynamic_enabled(),
49 tool_output_threshold: default_tool_output_threshold(),
50 sync_terminals: default_sync_terminals(),
51 persist_history: default_persist_history(),
52 sync_mcp_tools: default_sync_mcp_tools(),
53 sync_skills: default_sync_skills(),
54 spool_max_age_secs: default_spool_max_age_secs(),
55 max_spooled_files: default_max_spooled_files(),
56 }
57 }
58}
59
60impl DynamicContextConfig {
61 pub fn validate(&self) -> Result<()> {
62 ensure!(
63 self.tool_output_threshold >= 1024,
64 "Tool output threshold must be at least 1024 bytes"
65 );
66 ensure!(
67 self.max_spooled_files > 0,
68 "Max spooled files must be greater than zero"
69 );
70 Ok(())
71 }
72}
73
74fn default_dynamic_enabled() -> bool {
75 true
76}
77
78fn default_tool_output_threshold() -> usize {
79 8192 }
81
82fn default_sync_terminals() -> bool {
83 true
84}
85
86fn default_persist_history() -> bool {
87 true
88}
89
90fn default_sync_mcp_tools() -> bool {
91 true
92}
93
94fn default_sync_skills() -> bool {
95 true
96}
97
98fn default_spool_max_age_secs() -> u64 {
99 3600 }
101
102fn default_max_spooled_files() -> usize {
103 100
104}
105
106#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
107#[derive(Debug, Clone, Deserialize, Serialize)]
108pub struct LedgerConfig {
109 #[serde(default = "default_enabled")]
110 pub enabled: bool,
111 #[serde(default = "default_max_entries")]
112 pub max_entries: usize,
113 #[serde(default = "default_include_in_prompt")]
115 pub include_in_prompt: bool,
116 #[serde(default = "default_preserve_in_compression")]
118 pub preserve_in_compression: bool,
119}
120
121impl Default for LedgerConfig {
122 fn default() -> Self {
123 Self {
124 enabled: default_enabled(),
125 max_entries: default_max_entries(),
126 include_in_prompt: default_include_in_prompt(),
127 preserve_in_compression: default_preserve_in_compression(),
128 }
129 }
130}
131
132impl LedgerConfig {
133 pub fn validate(&self) -> Result<()> {
134 ensure!(
135 self.max_entries > 0,
136 "Ledger max_entries must be greater than zero"
137 );
138 Ok(())
139 }
140}
141
142#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
143#[derive(Debug, Clone, Deserialize, Serialize)]
144pub struct ContextFeaturesConfig {
145 #[serde(default = "default_max_context_tokens")]
149 pub max_context_tokens: usize,
150
151 #[serde(default = "default_trim_to_percent")]
154 pub trim_to_percent: u8,
155
156 #[serde(default = "default_preserve_recent_turns")]
159 pub preserve_recent_turns: usize,
160
161 #[serde(default)]
162 pub ledger: LedgerConfig,
163
164 #[serde(default)]
166 pub dynamic: DynamicContextConfig,
167}
168
169impl Default for ContextFeaturesConfig {
170 fn default() -> Self {
171 Self {
172 max_context_tokens: default_max_context_tokens(),
173 trim_to_percent: default_trim_to_percent(),
174 preserve_recent_turns: default_preserve_recent_turns(),
175 ledger: LedgerConfig::default(),
176 dynamic: DynamicContextConfig::default(),
177 }
178 }
179}
180
181impl ContextFeaturesConfig {
182 pub fn validate(&self) -> Result<()> {
183 self.ledger
184 .validate()
185 .context("Invalid ledger configuration")?;
186 self.dynamic
187 .validate()
188 .context("Invalid dynamic context configuration")?;
189 Ok(())
190 }
191}
192
193fn default_enabled() -> bool {
194 true
195}
196fn default_max_entries() -> usize {
197 12
198}
199fn default_include_in_prompt() -> bool {
200 true
201}
202fn default_max_context_tokens() -> usize {
203 90000
204}
205
206fn default_trim_to_percent() -> u8 {
207 60
208}
209
210fn default_preserve_recent_turns() -> usize {
211 10
212}
213
214fn default_preserve_in_compression() -> bool {
215 true
216}