use super::CtxBuilder;
use crate::config::provider::ProviderConfig;
use crate::conversation::message::Message;
use crate::conversation::{ContextStats, Conversation};
use crate::tool::ToolResult;
#[derive(Debug, Clone)]
pub struct DefaultCtx {
pub ctx_window: usize,
model_id: String,
}
impl DefaultCtx {
pub fn new(provider: &ProviderConfig) -> Self {
Self {
ctx_window: provider.context_window.max(8000),
model_id: provider.model.to_lowercase(),
}
}
}
impl CtxBuilder for DefaultCtx {
fn build_messages(
&self,
conv: &Conversation,
system_prompt: &str,
turn_reminder: &str,
) -> (Vec<Message>, ContextStats) {
let sys = crate::ctx::render::apply_model_directives(system_prompt, &self.model_id);
crate::ctx::render::build_messages(conv, &sys, self.ctx_window, turn_reminder)
}
fn needs_compression(&self, conv: &Conversation, system_tokens: usize) -> bool {
crate::ctx::render::needs_compression(conv, system_tokens, self.ctx_window)
}
fn compression_plan(
&self,
conv: &Conversation,
keep_ceiling: usize,
) -> Option<(String, usize)> {
let (content, n) = crate::ctx::render::build_compression_content(conv, keep_ceiling);
if content.is_empty() || n == 0 {
None
} else {
Some((content, n))
}
}
fn truncate_tool_output(&self, result: &mut ToolResult, tool_name: &str) {
crate::ctx::truncate::truncate_output(result, tool_name, self.ctx_window);
}
fn ctx_window(&self) -> usize {
self.ctx_window
}
fn name(&self) -> &'static str {
"default"
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::provider::ProviderConfig;
fn test_provider(ctx: usize) -> ProviderConfig {
ProviderConfig {
provider_type: "test".into(),
api_key: None,
model: "test-model".into(),
base_url: None,
system_prompt: None,
user_agent: None,
context_window: ctx,
max_tokens: None,
thinking_type: None,
thinking_keep: None,
reasoning_history: None,
thinking_enabled: None,
thinking_budget: None,
skip_tls_verify: false,
ephemeral: false,
}
}
#[test]
fn name_is_default() {
let d = DefaultCtx::new(&test_provider(128_000));
assert_eq!(d.name(), "default");
}
#[test]
fn ctx_window_clamped_to_8k_minimum() {
let d = DefaultCtx::new(&test_provider(0));
assert_eq!(d.ctx_window, 8000);
let d = DefaultCtx::new(&test_provider(4_000));
assert_eq!(d.ctx_window, 8000);
let d = DefaultCtx::new(&test_provider(32_000));
assert_eq!(d.ctx_window, 32_000);
}
#[test]
fn build_messages_empty_conv_returns_system_only() {
let d = DefaultCtx::new(&test_provider(128_000));
let conv = Conversation::new();
let (msgs, _stats) = d.build_messages(&conv, "SYS", "");
assert_eq!(msgs.len(), 1);
assert!(matches!(
msgs[0].role,
crate::conversation::message::Role::System
));
}
#[test]
fn compression_plan_none_below_threshold() {
let d = DefaultCtx::new(&test_provider(128_000));
let conv = Conversation::new();
assert!(d.compression_plan(&conv, usize::MAX).is_none());
}
}