use std::time::Duration;
use serde::{Deserialize, Serialize};
use crate::provider::{ModelName, ProviderId};
use crate::runtime::doom_loop::DoomLoopConfig;
use crate::runtime::input::InputAdmissionConfig;
use crate::tool_output::ToolOutputConfig;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct CompactionConfig {
#[serde(default = "default_true")]
pub auto: bool,
#[serde(default)]
pub prune: bool,
#[serde(default = "default_buffer")]
pub buffer_tokens: usize,
#[serde(default = "default_keep")]
pub keep_tokens: usize,
#[serde(default = "default_tail_turns")]
pub tail_turns: usize,
#[serde(default)]
pub model: Option<ModelName>,
#[serde(default)]
pub provider: Option<ProviderId>,
#[serde(default = "default_circuit_breaker_threshold")]
pub circuit_breaker_threshold: u32,
}
const fn default_true() -> bool {
true
}
const fn default_buffer() -> usize {
20_000
}
const fn default_keep() -> usize {
8_000
}
const fn default_tail_turns() -> usize {
2
}
const fn default_circuit_breaker_threshold() -> u32 {
3
}
const fn default_max_output_recovery() -> usize {
3
}
impl Default for CompactionConfig {
fn default() -> Self {
Self {
auto: true,
prune: false,
buffer_tokens: 20_000,
keep_tokens: 8_000,
tail_turns: 2,
model: None,
provider: None,
circuit_breaker_threshold: 3,
}
}
}
impl CompactionConfig {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_auto_disabled(mut self) -> Self {
self.auto = false;
self
}
#[must_use]
pub fn with_prune(mut self) -> Self {
self.prune = true;
self
}
#[must_use]
pub fn with_buffer_tokens(mut self, tokens: usize) -> Self {
self.buffer_tokens = tokens;
self
}
#[must_use]
pub fn with_keep_tokens(mut self, tokens: usize) -> Self {
self.keep_tokens = tokens;
self
}
#[must_use]
pub fn with_tail_turns(mut self, turns: usize) -> Self {
self.tail_turns = turns;
self
}
#[must_use]
pub fn with_model(mut self, model: ModelName) -> Self {
self.model = Some(model);
self
}
#[must_use]
pub fn with_provider(mut self, provider: ProviderId) -> Self {
self.provider = Some(provider);
self
}
#[must_use]
pub fn with_circuit_breaker_threshold(mut self, threshold: u32) -> Self {
self.circuit_breaker_threshold = threshold;
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct RuntimePolicy {
pub max_iterations: usize,
pub max_tokens: Option<usize>,
pub max_tool_concurrency: usize,
pub tool_timeout: Duration,
pub provider_timeout: Duration,
pub continue_on_tool_failure: bool,
pub retry_on_provider_error: bool,
pub max_retries: usize,
#[serde(default)]
pub compaction: CompactionConfig,
#[serde(default)]
pub tool_output: ToolOutputConfig,
#[serde(default)]
pub doom_loop: DoomLoopConfig,
#[serde(default)]
pub input_admission: InputAdmissionConfig,
#[serde(default = "default_max_output_recovery")]
pub max_output_recovery_attempts: usize,
}
impl Default for RuntimePolicy {
fn default() -> Self {
Self {
max_iterations: 10,
max_tokens: None,
max_tool_concurrency: 4,
tool_timeout: Duration::from_secs(30),
provider_timeout: Duration::from_secs(60),
continue_on_tool_failure: true,
retry_on_provider_error: true,
max_retries: 2,
compaction: CompactionConfig::default(),
tool_output: ToolOutputConfig::default(),
doom_loop: DoomLoopConfig::default(),
input_admission: InputAdmissionConfig::default(),
max_output_recovery_attempts: 3,
}
}
}
impl RuntimePolicy {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_max_iterations(mut self, max_iterations: usize) -> Self {
self.max_iterations = max_iterations;
self
}
#[must_use]
pub fn with_max_tokens(mut self, max_tokens: usize) -> Self {
self.max_tokens = Some(max_tokens);
self
}
#[must_use]
pub fn with_max_tool_concurrency(mut self, max_tool_concurrency: usize) -> Self {
self.max_tool_concurrency = max_tool_concurrency;
self
}
#[must_use]
pub fn with_tool_timeout(mut self, tool_timeout: Duration) -> Self {
self.tool_timeout = tool_timeout;
self
}
#[must_use]
pub fn with_provider_timeout(mut self, provider_timeout: Duration) -> Self {
self.provider_timeout = provider_timeout;
self
}
#[must_use]
pub fn with_continue_on_tool_failure(mut self, continue_on_tool_failure: bool) -> Self {
self.continue_on_tool_failure = continue_on_tool_failure;
self
}
#[must_use]
pub fn with_retry_on_provider_error(mut self, retry_on_provider_error: bool) -> Self {
self.retry_on_provider_error = retry_on_provider_error;
self
}
#[must_use]
pub fn with_max_retries(mut self, max_retries: usize) -> Self {
self.max_retries = max_retries;
self
}
#[must_use]
pub fn with_compaction(mut self, compaction: CompactionConfig) -> Self {
self.compaction = compaction;
self
}
#[must_use]
pub fn with_tool_output(mut self, config: ToolOutputConfig) -> Self {
self.tool_output = config;
self
}
#[must_use]
pub fn with_doom_loop(mut self, config: DoomLoopConfig) -> Self {
self.doom_loop = config;
self
}
#[must_use]
pub fn with_input_admission(mut self, config: InputAdmissionConfig) -> Self {
self.input_admission = config;
self
}
#[must_use]
pub fn with_max_output_recovery_attempts(mut self, attempts: usize) -> Self {
self.max_output_recovery_attempts = attempts;
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_policy() {
let policy = RuntimePolicy::default();
assert_eq!(policy.max_iterations, 10);
assert_eq!(policy.max_tool_concurrency, 4);
assert!(policy.max_tokens.is_none());
assert!(policy.continue_on_tool_failure);
}
#[test]
fn policy_builder() {
let policy = RuntimePolicy::new()
.with_max_iterations(5)
.with_max_tokens(1000)
.with_max_tool_concurrency(2);
assert_eq!(policy.max_iterations, 5);
assert_eq!(policy.max_tokens, Some(1000));
assert_eq!(policy.max_tool_concurrency, 2);
}
}