1use std::time::Duration;
8
9use serde::{Deserialize, Serialize};
10
11use behest_core::cache::CacheTtl;
12
13use super::doom_loop::DoomLoopConfig;
14use super::input::InputAdmissionConfig;
15use super::tool_output::ToolOutputConfig;
16use behest_provider::{ModelName, ProviderId};
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
20#[non_exhaustive]
21pub struct CompactionConfig {
22 #[serde(default = "default_true")]
24 pub auto: bool,
25 #[serde(default)]
27 pub prune: bool,
28 #[serde(default = "default_buffer")]
30 pub buffer_tokens: usize,
31 #[serde(default = "default_keep")]
33 pub keep_tokens: usize,
34 #[serde(default = "default_tail_turns")]
36 pub tail_turns: usize,
37 #[serde(default)]
39 pub model: Option<ModelName>,
40 #[serde(default)]
42 pub provider: Option<ProviderId>,
43 #[serde(default = "default_circuit_breaker_threshold")]
46 pub circuit_breaker_threshold: u32,
47}
48
49const fn default_true() -> bool {
50 true
51}
52
53const fn default_buffer() -> usize {
54 20_000
55}
56
57const fn default_keep() -> usize {
58 8_000
59}
60
61const fn default_tail_turns() -> usize {
62 2
63}
64
65const fn default_circuit_breaker_threshold() -> u32 {
66 3
67}
68
69const fn default_max_output_recovery() -> usize {
70 3
71}
72
73impl Default for CompactionConfig {
74 fn default() -> Self {
75 Self {
76 auto: true,
77 prune: false,
78 buffer_tokens: 20_000,
79 keep_tokens: 8_000,
80 tail_turns: 2,
81 model: None,
82 provider: None,
83 circuit_breaker_threshold: 3,
84 }
85 }
86}
87
88impl CompactionConfig {
89 #[must_use]
91 pub fn new() -> Self {
92 Self::default()
93 }
94
95 #[must_use]
97 pub fn with_auto_disabled(mut self) -> Self {
98 self.auto = false;
99 self
100 }
101
102 #[must_use]
104 pub fn with_prune(mut self) -> Self {
105 self.prune = true;
106 self
107 }
108
109 #[must_use]
111 pub fn with_buffer_tokens(mut self, tokens: usize) -> Self {
112 self.buffer_tokens = tokens;
113 self
114 }
115
116 #[must_use]
118 pub fn with_keep_tokens(mut self, tokens: usize) -> Self {
119 self.keep_tokens = tokens;
120 self
121 }
122
123 #[must_use]
125 pub fn with_tail_turns(mut self, turns: usize) -> Self {
126 self.tail_turns = turns;
127 self
128 }
129
130 #[must_use]
132 pub fn with_model(mut self, model: ModelName) -> Self {
133 self.model = Some(model);
134 self
135 }
136
137 #[must_use]
139 pub fn with_provider(mut self, provider: ProviderId) -> Self {
140 self.provider = Some(provider);
141 self
142 }
143
144 #[must_use]
146 pub fn with_circuit_breaker_threshold(mut self, threshold: u32) -> Self {
147 self.circuit_breaker_threshold = threshold;
148 self
149 }
150}
151
152#[derive(Debug, Clone, Serialize, Deserialize)]
158#[non_exhaustive]
159pub struct PromptCacheConfig {
160 #[serde(default = "default_true")]
163 pub enabled: bool,
164 #[serde(default)]
167 pub default_ttl: CacheTtl,
168 #[serde(default = "default_true")]
173 pub auto_breakpoints: bool,
174 #[serde(default = "default_min_system_tokens")]
178 pub min_system_tokens: usize,
179}
180
181const fn default_min_system_tokens() -> usize {
182 1024
183}
184
185impl Default for PromptCacheConfig {
186 fn default() -> Self {
187 Self {
188 enabled: true,
189 default_ttl: CacheTtl::FiveMinutes,
190 auto_breakpoints: true,
191 min_system_tokens: 1024,
192 }
193 }
194}
195
196impl PromptCacheConfig {
197 #[must_use]
199 pub fn new() -> Self {
200 Self::default()
201 }
202
203 #[must_use]
205 pub fn disabled() -> Self {
206 Self {
207 enabled: false,
208 ..Self::default()
209 }
210 }
211
212 #[must_use]
214 pub fn with_default_ttl(mut self, ttl: CacheTtl) -> Self {
215 self.default_ttl = ttl;
216 self
217 }
218
219 #[must_use]
221 pub fn with_auto_breakpoints(mut self, enabled: bool) -> Self {
222 self.auto_breakpoints = enabled;
223 self
224 }
225
226 #[must_use]
228 pub fn with_min_system_tokens(mut self, tokens: usize) -> Self {
229 self.min_system_tokens = tokens;
230 self
231 }
232}
233
234#[derive(Debug, Clone, Serialize, Deserialize)]
236#[non_exhaustive]
237pub struct RuntimePolicy {
238 pub max_iterations: usize,
240 pub max_tokens: Option<usize>,
242 pub max_tool_concurrency: usize,
244 pub tool_timeout: Duration,
246 pub provider_timeout: Duration,
248 pub continue_on_tool_failure: bool,
250 pub retry_on_provider_error: bool,
252 pub max_retries: usize,
254 #[serde(default)]
256 pub compaction: CompactionConfig,
257 #[serde(default)]
259 pub tool_output: ToolOutputConfig,
260 #[serde(default)]
262 pub doom_loop: DoomLoopConfig,
263 #[serde(default)]
265 pub input_admission: InputAdmissionConfig,
266 #[serde(default = "default_max_output_recovery")]
270 pub max_output_recovery_attempts: usize,
271 #[serde(default)]
274 pub prompt_cache: PromptCacheConfig,
275}
276
277impl Default for RuntimePolicy {
278 fn default() -> Self {
279 Self {
280 max_iterations: 10,
281 max_tokens: None,
282 max_tool_concurrency: 4,
283 tool_timeout: Duration::from_secs(30),
284 provider_timeout: Duration::from_secs(60),
285 continue_on_tool_failure: true,
286 retry_on_provider_error: true,
287 max_retries: 2,
288 compaction: CompactionConfig::default(),
289 tool_output: ToolOutputConfig::default(),
290 doom_loop: DoomLoopConfig::default(),
291 input_admission: InputAdmissionConfig::default(),
292 max_output_recovery_attempts: 3,
293 prompt_cache: PromptCacheConfig::default(),
294 }
295 }
296}
297
298impl RuntimePolicy {
299 #[must_use]
301 pub fn new() -> Self {
302 Self::default()
303 }
304
305 #[must_use]
307 pub fn with_max_iterations(mut self, max_iterations: usize) -> Self {
308 self.max_iterations = max_iterations;
309 self
310 }
311
312 #[must_use]
314 pub fn with_max_tokens(mut self, max_tokens: usize) -> Self {
315 self.max_tokens = Some(max_tokens);
316 self
317 }
318
319 #[must_use]
321 pub fn with_max_tool_concurrency(mut self, max_tool_concurrency: usize) -> Self {
322 self.max_tool_concurrency = max_tool_concurrency.max(1);
323 self
324 }
325
326 #[must_use]
328 pub fn with_tool_timeout(mut self, tool_timeout: Duration) -> Self {
329 self.tool_timeout = tool_timeout;
330 self
331 }
332
333 #[must_use]
335 pub fn with_provider_timeout(mut self, provider_timeout: Duration) -> Self {
336 self.provider_timeout = provider_timeout;
337 self
338 }
339
340 #[must_use]
342 pub fn with_continue_on_tool_failure(mut self, continue_on_tool_failure: bool) -> Self {
343 self.continue_on_tool_failure = continue_on_tool_failure;
344 self
345 }
346
347 #[must_use]
349 pub fn with_retry_on_provider_error(mut self, retry_on_provider_error: bool) -> Self {
350 self.retry_on_provider_error = retry_on_provider_error;
351 self
352 }
353
354 #[must_use]
356 pub fn with_max_retries(mut self, max_retries: usize) -> Self {
357 self.max_retries = max_retries;
358 self
359 }
360
361 #[must_use]
363 pub fn with_compaction(mut self, compaction: CompactionConfig) -> Self {
364 self.compaction = compaction;
365 self
366 }
367
368 #[must_use]
370 pub fn with_tool_output(mut self, config: ToolOutputConfig) -> Self {
371 self.tool_output = config;
372 self
373 }
374
375 #[must_use]
377 pub fn with_doom_loop(mut self, config: DoomLoopConfig) -> Self {
378 self.doom_loop = config;
379 self
380 }
381
382 #[must_use]
384 pub fn with_input_admission(mut self, config: InputAdmissionConfig) -> Self {
385 self.input_admission = config;
386 self
387 }
388
389 #[must_use]
391 pub fn with_max_output_recovery_attempts(mut self, attempts: usize) -> Self {
392 self.max_output_recovery_attempts = attempts;
393 self
394 }
395
396 #[must_use]
398 pub fn with_prompt_cache(mut self, config: PromptCacheConfig) -> Self {
399 self.prompt_cache = config;
400 self
401 }
402}
403
404#[cfg(test)]
405mod tests {
406 use super::*;
407
408 #[test]
409 fn default_policy() {
410 let policy = RuntimePolicy::default();
411 assert_eq!(policy.max_iterations, 10);
412 assert_eq!(policy.max_tool_concurrency, 4);
413 assert!(policy.max_tokens.is_none());
414 assert!(policy.continue_on_tool_failure);
415 }
416
417 #[test]
418 fn policy_builder() {
419 let policy = RuntimePolicy::new()
420 .with_max_iterations(5)
421 .with_max_tokens(1000)
422 .with_max_tool_concurrency(2);
423
424 assert_eq!(policy.max_iterations, 5);
425 assert_eq!(policy.max_tokens, Some(1000));
426 assert_eq!(policy.max_tool_concurrency, 2);
427 }
428}