fix: synchronize live worker context metadata
This commit is contained in:
+62
-4
@@ -247,6 +247,9 @@ pub struct App {
|
||||
/// Latest session context tokens reported by the Worker. This is the raw
|
||||
/// `input_tokens` value and is independent from per-run upload totals.
|
||||
pub session_context_tokens: u64,
|
||||
/// Availability and provenance for `session_context_tokens`. `None` means
|
||||
/// the current value is unknown and clients must not render a fabricated 0%.
|
||||
pub session_context_source: Option<protocol::ContextTokenSource>,
|
||||
pub context_window: u64,
|
||||
pub turn_index: usize,
|
||||
pub current_tool: Option<String>,
|
||||
@@ -352,6 +355,7 @@ impl App {
|
||||
run_upload_tokens: 0,
|
||||
run_output_tokens: 0,
|
||||
session_context_tokens: 0,
|
||||
session_context_source: None,
|
||||
context_window: 0,
|
||||
turn_index: 0,
|
||||
current_tool: None,
|
||||
@@ -1405,6 +1409,9 @@ impl App {
|
||||
} => {
|
||||
if let Some(input_tokens) = input_tokens {
|
||||
self.session_context_tokens = input_tokens;
|
||||
self.session_context_source = Some(protocol::ContextTokenSource::Measured);
|
||||
} else {
|
||||
self.session_context_source = None;
|
||||
}
|
||||
// Subtract the cache-hit portion so a tool loop that
|
||||
// re-sends the same prefix on every request doesn't
|
||||
@@ -1416,6 +1423,14 @@ impl App {
|
||||
self.run_upload_tokens += net_input;
|
||||
self.run_output_tokens += output_tokens.unwrap_or(0);
|
||||
}
|
||||
Event::ContextUsage { usage } => {
|
||||
if let Some(usage) = usage {
|
||||
self.session_context_tokens = usage.tokens;
|
||||
self.session_context_source = Some(usage.source);
|
||||
} else {
|
||||
self.session_context_source = None;
|
||||
}
|
||||
}
|
||||
Event::Error { code, message } => {
|
||||
self.handle_error(code, message);
|
||||
}
|
||||
@@ -2429,10 +2444,14 @@ impl App {
|
||||
) {
|
||||
self.greeting = Some(greeting.clone());
|
||||
self.context_window = greeting.context_window;
|
||||
self.session_context_tokens = greeting
|
||||
.context_usage
|
||||
.map(|usage| usage.tokens)
|
||||
.unwrap_or(greeting.context_tokens);
|
||||
let context_usage = greeting.context_usage.or_else(|| {
|
||||
(greeting.context_window > 0).then_some(protocol::ContextUsage {
|
||||
tokens: greeting.context_tokens,
|
||||
source: protocol::ContextTokenSource::Estimated,
|
||||
})
|
||||
});
|
||||
self.session_context_tokens = context_usage.map(|usage| usage.tokens).unwrap_or_default();
|
||||
self.session_context_source = context_usage.map(|usage| usage.source);
|
||||
self.restore_session(session, Some(greeting));
|
||||
self.apply_in_flight_snapshot(in_flight);
|
||||
}
|
||||
@@ -2458,6 +2477,7 @@ impl App {
|
||||
self.greeting = Some(greeting.clone());
|
||||
self.context_window = greeting.context_window;
|
||||
self.session_context_tokens = 0;
|
||||
self.session_context_source = None;
|
||||
}
|
||||
let missing_greeting = greeting.is_none();
|
||||
self.restore_session(session, greeting);
|
||||
@@ -2850,6 +2870,7 @@ mod rewind_refresh_tests {
|
||||
assert_eq!(composer_text(&app), "selected rewind input");
|
||||
assert!(blocks_contain(&app, "Rewound session"));
|
||||
assert_eq!(app.session_context_tokens, 0);
|
||||
assert_eq!(app.session_context_source, None);
|
||||
assert_eq!(app.greeting.as_ref().and_then(|g| g.context_usage), None);
|
||||
}
|
||||
|
||||
@@ -4575,6 +4596,10 @@ mod completion_flow_tests {
|
||||
|
||||
assert_eq!(app.context_window, 123_000);
|
||||
assert_eq!(app.session_context_tokens, 45_000);
|
||||
assert_eq!(
|
||||
app.session_context_source,
|
||||
Some(protocol::ContextTokenSource::Measured)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -4597,6 +4622,10 @@ mod completion_flow_tests {
|
||||
|
||||
assert_eq!(app.context_window, 100_000);
|
||||
assert_eq!(app.session_context_tokens, 25_000);
|
||||
assert_eq!(
|
||||
app.session_context_source,
|
||||
Some(protocol::ContextTokenSource::Estimated)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -4610,10 +4639,39 @@ mod completion_flow_tests {
|
||||
});
|
||||
|
||||
assert_eq!(app.session_context_tokens, 42_000);
|
||||
assert_eq!(
|
||||
app.session_context_source,
|
||||
Some(protocol::ContextTokenSource::Measured)
|
||||
);
|
||||
assert_eq!(app.run_upload_tokens, 2_000);
|
||||
assert_eq!(app.run_output_tokens, 9);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn context_usage_event_converges_live_state_without_changing_run_traffic() {
|
||||
let mut app = App::new("test".into());
|
||||
app.run_upload_tokens = 2_000;
|
||||
app.run_output_tokens = 9;
|
||||
|
||||
app.handle_worker_event(Event::ContextUsage {
|
||||
usage: Some(protocol::ContextUsage {
|
||||
tokens: 45_000,
|
||||
source: protocol::ContextTokenSource::Estimated,
|
||||
}),
|
||||
});
|
||||
|
||||
assert_eq!(app.session_context_tokens, 45_000);
|
||||
assert_eq!(
|
||||
app.session_context_source,
|
||||
Some(protocol::ContextTokenSource::Estimated)
|
||||
);
|
||||
assert_eq!(app.run_upload_tokens, 2_000);
|
||||
assert_eq!(app.run_output_tokens, 9);
|
||||
|
||||
app.handle_worker_event(Event::ContextUsage { usage: None });
|
||||
assert_eq!(app.session_context_source, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn memory_worker_event_updates_actionbar_state() {
|
||||
let mut app = App::new("test".into());
|
||||
|
||||
+29
-5
@@ -1830,13 +1830,20 @@ fn draw_separator(frame: &mut Frame, area: Rect) {
|
||||
}
|
||||
|
||||
fn context_usage_text(app: &App) -> String {
|
||||
let pct = if app.context_window == 0 {
|
||||
0
|
||||
} else {
|
||||
((app.session_context_tokens as f64 / app.context_window as f64) * 100.0).round() as u64
|
||||
let Some(source) = app.session_context_source else {
|
||||
return "Context unavailable".to_string();
|
||||
};
|
||||
if app.context_window == 0 {
|
||||
return "Context unavailable".to_string();
|
||||
}
|
||||
let pct =
|
||||
((app.session_context_tokens as f64 / app.context_window as f64) * 100.0).round() as u64;
|
||||
let estimate = matches!(source, protocol::ContextTokenSource::Estimated)
|
||||
.then_some("~")
|
||||
.unwrap_or_default();
|
||||
format!(
|
||||
"{} / {} ({}%)",
|
||||
"{}{} / {} ({}%)",
|
||||
estimate,
|
||||
fmt_tokens(app.session_context_tokens),
|
||||
fmt_tokens(app.context_window),
|
||||
pct
|
||||
@@ -2194,6 +2201,23 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn context_usage_distinguishes_unknown_measured_and_estimated_values() {
|
||||
let mut app = App::new("worker".into());
|
||||
app.context_window = 100_000;
|
||||
app.session_context_tokens = 0;
|
||||
app.session_context_source = None;
|
||||
assert_eq!(context_usage_text(&app), "Context unavailable");
|
||||
|
||||
app.session_context_tokens = 25_000;
|
||||
app.session_context_source = Some(protocol::ContextTokenSource::Measured);
|
||||
assert_eq!(context_usage_text(&app), "25.0k / 100.0k (25%)");
|
||||
|
||||
app.session_context_tokens = 20_000;
|
||||
app.session_context_source = Some(protocol::ContextTokenSource::Estimated);
|
||||
assert_eq!(context_usage_text(&app), "~20.0k / 100.0k (20%)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_elapsed_uses_console_style_units() {
|
||||
assert_eq!(fmt_run_elapsed(9), "9s");
|
||||
|
||||
Reference in New Issue
Block a user