feat: Support specifying the compression model to use when compressing a session for greater cost controls

This commit is contained in:
Dark-Alex-17 committed 2026-09-14 15:49:44 -06:00
1 parent 58b0dbd812
commit ffb75cb168
7 files changed
+118 -4

No files matched your search

+89 -4
View File
@@ -1006,6 +1006,13 @@ impl RequestContext {
.unwrap_or(self.app.config.compression_keep_last)
}
pub fn compression_model(&self) -> Option<String> {
self.agent
.as_ref()
.and_then(|a| a.compression_model().map(|s| s.to_string()))
.or_else(|| self.app.config.compression_model.clone())
}
pub fn role_like_mut(&mut self) -> Option<&mut dyn RoleLike> {
if let Some(session) = self.session.as_mut() {
Some(session)
@@ -5129,10 +5136,45 @@ impl RequestContext {
.summarization_prompt
.clone()
.unwrap_or_else(|| SUMMARIZATION_PROMPT.into());
let input = Input::from_str(self, &prompt, None)?;
let summary = tokio::time::timeout(Duration::from_secs(120), input.fetch_chat_text())
.await
.map_err(|_| anyhow::anyhow!("Compression LLM call timed out after 120 s"))??;
async fn timed_fetch(input: &Input) -> Result<String> {
tokio::time::timeout(Duration::from_secs(120), input.fetch_chat_text())
.await
.map_err(|_| anyhow::anyhow!("Compression LLM call timed out after 120 s"))?
}
let mut input = Input::from_str(self, &prompt, None)?;
let compression_model_id = self
.compression_model()
.filter(|id| *id != self.current_model().id());
let summary = match compression_model_id {
Some(model_id) => {
match Model::retrieve_model(self.app.config.as_ref(), &model_id, ModelType::Chat) {
Ok(model) => {
input.set_role_model(model);
match timed_fetch(&input).await {
Ok(summary) => summary,
Err(err) => {
eprintln!(
"Warning: compression model '{model_id}' failed: {err}; falling back to the current model '{}' for session compression",
self.current_model().id()
);
let input = Input::from_str(self, &prompt, None)?;
timed_fetch(&input).await?
}
}
}
Err(err) => {
eprintln!(
"Warning: compression model '{model_id}' could not be used ({err}); falling back to the current model '{}' for session compression",
self.current_model().id()
);
timed_fetch(&input).await?
}
}
}
None => timed_fetch(&input).await?,
};
let summary_context_prompt = self
.app
.config
@@ -5800,6 +5842,49 @@ mod tests {
assert_eq!(extracted.reasoning_effort(), Some("low".to_string()));
}
#[test]
fn compression_model_none_when_unset() {
let ctx = create_test_ctx();
assert_eq!(ctx.compression_model(), None);
}
#[test]
fn compression_model_uses_app_config_without_agent() {
let mut ctx = create_test_ctx();
ctx.app = Arc::new(AppState {
config: Arc::new(AppConfig {
compression_model: Some("openai:app-model".to_string()),
..(*ctx.app.config).clone()
}),
..(*ctx.app).clone()
});
assert_eq!(
ctx.compression_model(),
Some("openai:app-model".to_string())
);
}
#[test]
fn compression_model_agent_overrides_app_config() {
let mut ctx = create_test_ctx();
ctx.app = Arc::new(AppState {
config: Arc::new(AppConfig {
compression_model: Some("openai:app-model".to_string()),
..(*ctx.app.config).clone()
}),
..(*ctx.app).clone()
});
ctx.agent = Some(Agent::test_new(AgentConfig {
name: "test-agent".to_string(),
compression_model: Some("openai:agent-model".to_string()),
..AgentConfig::default()
}));
assert_eq!(
ctx.compression_model(),
Some("openai:agent-model".to_string())
);
}
#[test]
fn should_inject_skill_instructions_requires_function_calling() {
let app = AppConfig {