Centralize g3 status message formatting
Extract a new g3_status module in g3-cli that provides consistent formatting for all 'g3:' prefixed system status messages. Key changes: - Add G3Status struct with methods for progress, done, failed, error, etc. - Add Status enum with Done, Failed, Error, Resolved, Insufficient, NoChanges - Add ThinResult struct in g3-core for semantic thinning data - Update UiWriter trait with print_thin_result() method - Refactor context thinning to return ThinResult instead of formatted strings - Update all callers to use the new centralized formatting - Session resume/decline messages now use G3Status - Compaction status messages now use G3Status This maintains clean separation of concerns: g3-core emits semantic data, g3-cli handles all terminal formatting and colors.
This commit is contained in:
@@ -234,9 +234,9 @@ fn apply_summary_fallback_sequence<W: UiWriter>(
|
||||
|
||||
// Step 1: Try thinnify (first third of context)
|
||||
ui_writer.print_context_status("🥒 Step 1: Trying thinnify...\n");
|
||||
let (thin_msg, chars_saved) = context_window.thin_context(None);
|
||||
thinning_events.push(chars_saved);
|
||||
ui_writer.print_context_thinning(&thin_msg);
|
||||
let thin_result = context_window.thin_context(None);
|
||||
thinning_events.push(thin_result.chars_saved);
|
||||
ui_writer.print_thin_result(&thin_result);
|
||||
|
||||
// Recalculate after thinnify
|
||||
let (new_max, still_needs_reduction) = provider_config::calculate_summary_max_tokens(
|
||||
@@ -253,9 +253,9 @@ fn apply_summary_fallback_sequence<W: UiWriter>(
|
||||
|
||||
// Step 2: Try skinnify (entire context)
|
||||
ui_writer.print_context_status("🦴 Step 2: Trying skinnify...\n");
|
||||
let (skinny_msg, chars_saved) = context_window.thin_context_all(None);
|
||||
thinning_events.push(chars_saved);
|
||||
ui_writer.print_context_thinning(&skinny_msg);
|
||||
let skinny_result = context_window.thin_context_all(None);
|
||||
thinning_events.push(skinny_result.chars_saved);
|
||||
ui_writer.print_thin_result(&skinny_result);
|
||||
|
||||
// Recalculate after skinnify
|
||||
let (final_max, final_needs_reduction) = provider_config::calculate_summary_max_tokens(
|
||||
|
||||
@@ -13,6 +13,26 @@ use tracing::{debug, warn};
|
||||
use crate::paths::get_thinned_dir;
|
||||
use crate::ToolCall;
|
||||
|
||||
/// Result of a context thinning operation.
|
||||
/// Contains semantic data for the UI layer to format.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ThinResult {
|
||||
/// Scope of the thinning operation
|
||||
pub scope: ThinScope,
|
||||
/// Context percentage before thinning
|
||||
pub before_percentage: u32,
|
||||
/// Context percentage after thinning
|
||||
pub after_percentage: u32,
|
||||
/// Number of tool result messages that were thinned
|
||||
pub leaned_count: usize,
|
||||
/// Number of tool calls in assistant messages that were thinned
|
||||
pub tool_call_leaned_count: usize,
|
||||
/// Total characters saved
|
||||
pub chars_saved: usize,
|
||||
/// Whether any changes were made
|
||||
pub had_changes: bool,
|
||||
}
|
||||
|
||||
/// Scope for context thinning operations
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ThinScope {
|
||||
@@ -349,12 +369,12 @@ Format this as a detailed but concise summary that can be used to resume the con
|
||||
/// * `scope` - Controls which messages to process (first third or all)
|
||||
///
|
||||
/// # Returns
|
||||
/// A tuple of (summary message, chars saved)
|
||||
/// A `ThinResult` with semantic data about the operation
|
||||
pub fn thin_context_with_scope(
|
||||
&mut self,
|
||||
session_id: Option<&str>,
|
||||
scope: ThinScope,
|
||||
) -> (String, usize) {
|
||||
) -> ThinResult {
|
||||
let current_percentage = self.percentage_used() as u32;
|
||||
|
||||
// Only update last_thinning_percentage for incremental thinning
|
||||
@@ -373,7 +393,17 @@ Format this as a detailed but concise summary that can be used to resume the con
|
||||
// Determine output directory: use session dir if available, otherwise ~/tmp
|
||||
let tmp_dir = match Self::resolve_thinned_dir(session_id, scope) {
|
||||
Ok(dir) => dir,
|
||||
Err(msg) => return (msg, 0),
|
||||
Err(_) => {
|
||||
return ThinResult {
|
||||
scope,
|
||||
before_percentage: current_percentage,
|
||||
after_percentage: current_percentage,
|
||||
leaned_count: 0,
|
||||
tool_call_leaned_count: 0,
|
||||
chars_saved: 0,
|
||||
had_changes: false,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// Collect modifications to apply (avoids borrow checker issues)
|
||||
@@ -409,7 +439,7 @@ Format this as a detailed but concise summary that can be used to resume the con
|
||||
let new_percentage = self.percentage_used() as u32;
|
||||
|
||||
// Build result message
|
||||
self.build_thin_result_message(
|
||||
self.build_thin_result(
|
||||
scope,
|
||||
current_percentage,
|
||||
new_percentage,
|
||||
@@ -466,13 +496,13 @@ Format this as a detailed but concise summary that can be used to resume the con
|
||||
modifications
|
||||
}
|
||||
|
||||
/// Backward-compatible wrapper for thin_context (first third only)
|
||||
pub fn thin_context(&mut self, session_id: Option<&str>) -> (String, usize) {
|
||||
/// Thin context (first third only)
|
||||
pub fn thin_context(&mut self, session_id: Option<&str>) -> ThinResult {
|
||||
self.thin_context_with_scope(session_id, ThinScope::FirstThird)
|
||||
}
|
||||
|
||||
/// Backward-compatible wrapper for thin_context_all (entire history)
|
||||
pub fn thin_context_all(&mut self, session_id: Option<&str>) -> (String, usize) {
|
||||
/// Thin entire context (all messages)
|
||||
pub fn thin_context_all(&mut self, session_id: Option<&str>) -> ThinResult {
|
||||
self.thin_context_with_scope(session_id, ThinScope::All)
|
||||
}
|
||||
|
||||
@@ -697,7 +727,7 @@ Format this as a detailed but concise summary that can be used to resume the con
|
||||
}
|
||||
|
||||
/// Build the result message for thinning operations
|
||||
fn build_thin_result_message(
|
||||
fn build_thin_result(
|
||||
&self,
|
||||
scope: ThinScope,
|
||||
current_percentage: u32,
|
||||
@@ -705,27 +735,17 @@ Format this as a detailed but concise summary that can be used to resume the con
|
||||
leaned_count: usize,
|
||||
tool_call_leaned_count: usize,
|
||||
chars_saved: usize,
|
||||
) -> (String, usize) {
|
||||
// Nothing was thinned
|
||||
if leaned_count == 0 && tool_call_leaned_count == 0 {
|
||||
let scope_desc = match scope {
|
||||
ThinScope::FirstThird => "",
|
||||
ThinScope::All => " (full)",
|
||||
};
|
||||
let msg = format!(
|
||||
"\x1b[1;32mg3:\x1b[0m thinning context{} ... {}% ... \x1b[38;5;208m[no changes]\x1b[0m",
|
||||
scope_desc, current_percentage
|
||||
);
|
||||
return (msg, 0);
|
||||
) -> ThinResult {
|
||||
let had_changes = leaned_count > 0 || tool_call_leaned_count > 0;
|
||||
ThinResult {
|
||||
scope,
|
||||
before_percentage: current_percentage,
|
||||
after_percentage: new_percentage,
|
||||
leaned_count,
|
||||
tool_call_leaned_count,
|
||||
chars_saved: if had_changes { chars_saved } else { 0 },
|
||||
had_changes,
|
||||
}
|
||||
|
||||
// Format: "g3: thinning context ... 70% -> 40% ... [done]"
|
||||
// with "g3:" and "[done]" in bold green
|
||||
let msg = format!(
|
||||
"\x1b[1;32mg3:\x1b[0m thinning context ... {}% -> {}% ... \x1b[1;32m[done]\x1b[0m",
|
||||
current_percentage, new_percentage
|
||||
);
|
||||
(msg, chars_saved)
|
||||
}
|
||||
|
||||
/// Recalculate token usage based on current conversation history
|
||||
|
||||
@@ -35,7 +35,7 @@ pub use session_continuation::{
|
||||
pub use task_result::TaskResult;
|
||||
|
||||
// Re-export context window types
|
||||
pub use context_window::{ContextWindow, ThinScope};
|
||||
pub use context_window::{ContextWindow, ThinResult, ThinScope};
|
||||
|
||||
// Export agent prompt generation for CLI use
|
||||
pub use prompts::get_agent_system_prompt;
|
||||
@@ -500,8 +500,8 @@ impl<W: UiWriter> Agent<W> {
|
||||
// Step 1: Try thinnify (first third of context)
|
||||
self.ui_writer
|
||||
.print_context_status("🥒 Step 1: Trying thinnify...\n");
|
||||
let thin_msg = self.do_thin_context();
|
||||
self.ui_writer.print_context_thinning(&thin_msg);
|
||||
let thin_result = self.do_thin_context();
|
||||
self.ui_writer.print_thin_result(&thin_result);
|
||||
|
||||
// Recalculate after thinnify
|
||||
let (new_max, still_needs_reduction) =
|
||||
@@ -516,8 +516,8 @@ impl<W: UiWriter> Agent<W> {
|
||||
// Step 2: Try skinnify (entire context)
|
||||
self.ui_writer
|
||||
.print_context_status("🦴 Step 2: Trying skinnify...\n");
|
||||
let skinny_msg = self.do_thin_context_all();
|
||||
self.ui_writer.print_context_thinning(&skinny_msg);
|
||||
let skinny_result = self.do_thin_context_all();
|
||||
self.ui_writer.print_thin_result(&skinny_result);
|
||||
|
||||
// Recalculate after skinnify
|
||||
let (final_max, final_needs_reduction) =
|
||||
@@ -1084,32 +1084,32 @@ impl<W: UiWriter> Agent<W> {
|
||||
}
|
||||
}
|
||||
/// Manually trigger context thinning regardless of thresholds
|
||||
pub fn force_thin(&mut self) -> String {
|
||||
pub fn force_thin(&mut self) -> ThinResult {
|
||||
debug!("Manual context thinning triggered");
|
||||
self.do_thin_context()
|
||||
}
|
||||
|
||||
/// Manually trigger context thinning for the ENTIRE context window
|
||||
/// Unlike force_thin which only processes the first third, this processes all messages
|
||||
pub fn force_thin_all(&mut self) -> String {
|
||||
pub fn force_thin_all(&mut self) -> ThinResult {
|
||||
debug!("Manual full context skinnifying triggered");
|
||||
self.do_thin_context_all()
|
||||
}
|
||||
|
||||
/// Internal helper: thin context and track the event
|
||||
fn do_thin_context(&mut self) -> String {
|
||||
let (message, chars_saved) = self.context_window.thin_context(self.session_id.as_deref());
|
||||
self.thinning_events.push(chars_saved);
|
||||
message
|
||||
fn do_thin_context(&mut self) -> ThinResult {
|
||||
let result = self.context_window.thin_context(self.session_id.as_deref());
|
||||
self.thinning_events.push(result.chars_saved);
|
||||
result
|
||||
}
|
||||
|
||||
/// Internal helper: thin all context and track the event
|
||||
fn do_thin_context_all(&mut self) -> String {
|
||||
let (message, chars_saved) = self
|
||||
fn do_thin_context_all(&mut self) -> ThinResult {
|
||||
let result = self
|
||||
.context_window
|
||||
.thin_context_all(self.session_id.as_deref());
|
||||
self.thinning_events.push(chars_saved);
|
||||
message
|
||||
self.thinning_events.push(result.chars_saved);
|
||||
result
|
||||
}
|
||||
|
||||
/// Ensure context window has capacity before streaming.
|
||||
@@ -1127,8 +1127,8 @@ impl<W: UiWriter> Agent<W> {
|
||||
self.context_window.percentage_used() as u32
|
||||
));
|
||||
|
||||
let thin_summary = self.do_thin_context();
|
||||
self.ui_writer.print_context_thinning(&thin_summary);
|
||||
let thin_result = self.do_thin_context();
|
||||
self.ui_writer.print_thin_result(&thin_result);
|
||||
|
||||
if !self.context_window.should_compact() {
|
||||
self.ui_writer.print_g3_status("thinning", "resolved");
|
||||
@@ -2096,8 +2096,8 @@ Skip if nothing new. Be brief."#;
|
||||
|
||||
// Thin context if needed before tool execution
|
||||
if self.context_window.should_thin() {
|
||||
let thin_summary = self.do_thin_context();
|
||||
self.ui_writer.print_context_thinning(&thin_summary);
|
||||
let thin_result = self.do_thin_context();
|
||||
self.ui_writer.print_thin_result(&thin_result);
|
||||
}
|
||||
|
||||
// Calculate new content to display (skip already-shown text)
|
||||
|
||||
@@ -27,8 +27,11 @@ pub trait UiWriter: Send + Sync {
|
||||
/// - "g3:" should be bold green, "failed"/"error" status should be red
|
||||
fn print_g3_status(&self, message: &str, status: &str);
|
||||
|
||||
/// Print a context thinning success message with highlight and animation
|
||||
fn print_context_thinning(&self, message: &str);
|
||||
/// Print a context thinning result
|
||||
fn print_thin_result(&self, result: &crate::ThinResult);
|
||||
|
||||
/// Print a context thinning message (legacy - for pre-formatted messages)
|
||||
fn print_context_thinning(&self, _message: &str) {}
|
||||
|
||||
/// Print a tool execution header
|
||||
fn print_tool_header(&self, tool_name: &str, tool_args: Option<&serde_json::Value>);
|
||||
@@ -136,7 +139,7 @@ impl UiWriter for NullUiWriter {
|
||||
fn print_context_status(&self, _message: &str) {}
|
||||
fn print_g3_progress(&self, _message: &str) {}
|
||||
fn print_g3_status(&self, _message: &str, _status: &str) {}
|
||||
fn print_context_thinning(&self, _message: &str) {}
|
||||
fn print_thin_result(&self, _result: &crate::ThinResult) {}
|
||||
fn print_tool_header(&self, _tool_name: &str, _tool_args: Option<&serde_json::Value>) {}
|
||||
fn print_tool_arg(&self, _key: &str, _value: &str) {}
|
||||
fn print_tool_output_header(&self) {}
|
||||
|
||||
Reference in New Issue
Block a user