use serde::Deserialize;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct SessionConfig {
pub system_prompt: Option<String>,
pub context_window: u64,
#[serde(deserialize_with = "deserialize_compact_threshold")]
pub compact_threshold: u8,
pub auto_compact: bool,
}
impl Default for SessionConfig {
fn default() -> Self {
let mut config = Self {
system_prompt: None,
context_window: 200_000,
compact_threshold: 80,
auto_compact: true,
};
config.clamp_compact_threshold();
config
}
}
impl SessionConfig {
fn clamp_compact_threshold(&mut self) {
if self.compact_threshold > 100 {
self.compact_threshold = 100;
}
}
#[must_use]
pub fn with_system_prompt(mut self, system_prompt: impl Into<String>) -> Self {
self.system_prompt = Some(system_prompt.into());
self
}
#[must_use]
pub fn with_context_window(mut self, context_window: u64) -> Self {
self.context_window = context_window;
self
}
#[must_use]
pub fn with_compact_threshold(mut self, compact_threshold: u8) -> Self {
self.compact_threshold = compact_threshold;
self.clamp_compact_threshold();
self
}
#[must_use]
pub fn with_auto_compact(mut self, auto_compact: bool) -> Self {
self.auto_compact = auto_compact;
self
}
}
fn deserialize_compact_threshold<'de, D>(deserializer: D) -> Result<u8, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = u8::deserialize(deserializer)?;
Ok(value.min(100))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ParallelMode {
Sequential,
Parallel,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ParallelDispatchConfig {
pub mode: ParallelMode,
pub max_concurrency: usize,
}
impl Default for ParallelDispatchConfig {
fn default() -> Self {
Self {
mode: ParallelMode::Sequential,
max_concurrency: 8,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn session_config_default_context_window() {
let config = SessionConfig::default();
assert_eq!(config.context_window, 200_000);
assert!(config.system_prompt.is_none());
}
#[test]
fn session_config_with_system_prompt_sets_some() {
let config = SessionConfig::default().with_system_prompt("be helpful");
assert_eq!(config.system_prompt.as_deref(), Some("be helpful"));
}
#[test]
fn session_config_with_context_window_sets_field() {
let config = SessionConfig::default().with_context_window(8192);
assert_eq!(config.context_window, 8192);
}
#[test]
fn session_config_builder_chain_composes_without_clobbering() {
let config = SessionConfig::default()
.with_system_prompt("p")
.with_context_window(128_000);
assert_eq!(config.system_prompt.as_deref(), Some("p"));
assert_eq!(config.context_window, 128_000);
}
#[test]
fn session_config_builder_does_not_mutate_source() {
let original = SessionConfig::default();
let _modified = original.clone().with_context_window(1);
assert_eq!(original.context_window, 200_000);
}
#[test]
fn parallel_dispatch_default_is_sequential_concurrency_8() {
let dispatch = ParallelDispatchConfig::default();
assert_eq!(dispatch.mode, ParallelMode::Sequential);
assert_eq!(dispatch.max_concurrency, 8);
}
#[test]
fn parallel_dispatch_struct_literal_round_trips() {
let dispatch = ParallelDispatchConfig {
mode: ParallelMode::Parallel,
max_concurrency: 4,
};
assert_eq!(dispatch.mode, ParallelMode::Parallel);
assert_eq!(dispatch.max_concurrency, 4);
}
#[test]
fn default_compact_threshold_is_valid() {
let config = SessionConfig::default();
assert!(
config.compact_threshold <= 100,
"default compact_threshold must be in range; got {}",
config.compact_threshold
);
}
#[test]
fn with_compact_threshold_clamps_high() {
let config = SessionConfig::default().with_compact_threshold(200);
assert_eq!(config.compact_threshold, 100);
}
#[test]
fn with_compact_threshold_preserves_low() {
let config = SessionConfig::default().with_compact_threshold(50);
assert_eq!(config.compact_threshold, 50);
}
#[test]
fn deserialize_clamps_high() {
let json = r#"{"system_prompt":null,"context_window":200000,"compact_threshold":200,"auto_compact":true}"#;
let config: SessionConfig = serde_json::from_str(json).expect("deserialize should succeed");
assert_eq!(
config.compact_threshold, 100,
"deserialize must clamp out-of-range compact_threshold to 100"
);
}
#[test]
fn deserialize_preserves_valid() {
let json = r#"{"system_prompt":null,"context_window":200000,"compact_threshold":80,"auto_compact":true}"#;
let config: SessionConfig = serde_json::from_str(json).expect("deserialize should succeed");
assert_eq!(config.compact_threshold, 80);
}
}