refactor: enhance message shaping with progressive summarization and improved handling of dropped messages
This commit is contained in:
@@ -209,28 +209,102 @@ pub(super) fn handle_abort_turn(state: &mut AppStateRest) {
|
||||
pub(super) fn handle_compact(state: &mut AppStateRest) {
|
||||
let max_wire_tokens = window::resolve(&state.app_config, &state.settings);
|
||||
|
||||
if let Some(ref mut rt) = state.session_runtime {
|
||||
let token_estimate: usize = rt
|
||||
.messages
|
||||
.iter()
|
||||
.filter_map(|m| m.content.as_deref())
|
||||
.map(count_tokens)
|
||||
.sum();
|
||||
rt.messages =
|
||||
crate::app::runtime::context::shaping::shape_messages(
|
||||
// Extract config before borrowing session_runtime mutably to avoid
|
||||
// borrow conflicts. An LLM client is needed for summarization so the
|
||||
// compacted result preserves meaningful context (goals, decisions,
|
||||
// files, state) instead of a useless static placeholder.
|
||||
let api_key = state
|
||||
.settings
|
||||
.api_keys
|
||||
.get(&state.settings.provider)
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
let model = state.settings.model.clone();
|
||||
let base_url = state
|
||||
.app_config
|
||||
.providers
|
||||
.get(&state.settings.provider)
|
||||
.map(|p| p.api_base.clone());
|
||||
let abort_flag = state.abort_flag.clone();
|
||||
|
||||
// Build the LLM client if we have a configured base_url.
|
||||
let llm_client = base_url.map(|url| {
|
||||
let key = if api_key.is_empty() {
|
||||
state
|
||||
.app_config
|
||||
.providers
|
||||
.get(&state.settings.provider)
|
||||
.and_then(|cfg| {
|
||||
cfg.api_key_env
|
||||
.as_ref()
|
||||
.and_then(|env| std::env::var(env).ok())
|
||||
})
|
||||
.or_else(|| {
|
||||
state
|
||||
.app_config
|
||||
.providers
|
||||
.get(&state.settings.provider)
|
||||
.and_then(|cfg| cfg.default_api_key.clone())
|
||||
})
|
||||
.unwrap_or_else(|| crate::service::provider::DEFAULT_API_KEY.to_string())
|
||||
} else {
|
||||
api_key.clone()
|
||||
};
|
||||
crate::service::provider::LlmClient::new(key, model.clone(), Some(url))
|
||||
});
|
||||
|
||||
if llm_client.is_none() {
|
||||
state.push_toast(Toast::new(
|
||||
ToastKind::Error,
|
||||
"Cannot compact: no AI provider configured. Set up a provider in Settings first."
|
||||
.to_string(),
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
let (before_tokens, after_tokens, msg_count) =
|
||||
if let Some(ref mut rt) = state.session_runtime {
|
||||
let token_estimate: usize = rt
|
||||
.messages
|
||||
.iter()
|
||||
.filter_map(|m| m.content.as_deref())
|
||||
.map(count_tokens)
|
||||
.sum();
|
||||
|
||||
let before = token_estimate;
|
||||
rt.messages = crate::app::runtime::context::shaping::shape_messages(
|
||||
&rt.messages,
|
||||
token_estimate,
|
||||
max_wire_tokens,
|
||||
true,
|
||||
None,
|
||||
None,
|
||||
llm_client.as_ref(),
|
||||
Some(&*abort_flag),
|
||||
);
|
||||
state.push_toast(Toast::new(
|
||||
ToastKind::Success,
|
||||
"Conversation history compacted.".to_string(),
|
||||
));
|
||||
state.dirty = true;
|
||||
}
|
||||
let after: usize = rt
|
||||
.messages
|
||||
.iter()
|
||||
.filter_map(|m| m.content.as_deref())
|
||||
.map(count_tokens)
|
||||
.sum();
|
||||
(before, after, rt.messages.len())
|
||||
} else {
|
||||
(0, 0, 0)
|
||||
};
|
||||
|
||||
let dropped = before_tokens.saturating_sub(after_tokens);
|
||||
let msg_label = if before_tokens > 0 {
|
||||
format!(
|
||||
"Compacted ({} msgs, ~{}K → ~{}K tokens, dropped ~{}K).",
|
||||
msg_count,
|
||||
before_tokens / 1000,
|
||||
after_tokens / 1000,
|
||||
dropped / 1000,
|
||||
)
|
||||
} else {
|
||||
"No active session to compact.".to_string()
|
||||
};
|
||||
state.push_toast(Toast::new(ToastKind::Success, msg_label));
|
||||
state.dirty = true;
|
||||
}
|
||||
|
||||
pub(super) fn handle_lesson_accept(state: &mut AppStateRest, name: String) {
|
||||
|
||||
Reference in New Issue
Block a user