Watch
0
0
Fork
You've already forked hyperhive
0

turn: clear the resolved model on a turn that parsed nothing

This commit is contained in:
damocles 2026-10-21 21:19:36 +02:00 • committed by mara
commit c3ff0815ff

View file

@ -790,8 +790,9 @@ impl Sink for BusSink<'_> {
/// Apply a completed turn's parsed [`hive_claude::Telemetry`] to the bus: /// Apply a completed turn's parsed [`hive_claude::Telemetry`] to the bus:
/// per-inference context usage + cumulative cost, the resolved model id, and /// per-inference context usage + cumulative cost, the resolved model id, and
/// the API-reported context window (the authoritative window for the auto-reset /// the API-reported context window (the authoritative window for the auto-reset
/// watermark). Skips a degenerate turn that parsed nothing so it doesn't reset /// watermark). A degenerate turn that parsed nothing leaves the usage badges
/// the badges to zero. /// alone (it doesn't reset them to zero) but still replaces the resolved model,
/// so it is never credited to the previous turn's.
fn apply_telemetry(bus: &Bus, telemetry: &hive_claude::Telemetry) { fn apply_telemetry(bus: &Bus, telemetry: &hive_claude::Telemetry) {
// On a degenerate turn that emitted a `result` but no `assistant` event, // On a degenerate turn that emitted a `result` but no `assistant` event,
// the per-inference `context` stays zero while `cost` (cumulative) is not. // the per-inference `context` stays zero while `cost` (cumulative) is not.
@ -803,11 +804,13 @@ fn apply_telemetry(bus: &Bus, telemetry: &hive_claude::Telemetry) {
} else { } else {
telemetry.context telemetry.context
}; };
// Recorded before the skip so a degenerate turn clears the previous
// turn's id instead of being credited to it.
bus.set_resolved_model(telemetry.model.clone());
if ctx.context_tokens() == 0 { if ctx.context_tokens() == 0 {
return; return;
} }
bus.record_turn_usage(ctx, telemetry.cost); bus.record_turn_usage(ctx, telemetry.cost);
bus.set_resolved_model(telemetry.model.clone());
if let Some(window) = telemetry.context_window { if let Some(window) = telemetry.context_window {
bus.set_api_context_window(window); bus.set_api_context_window(window);
} }
@ -853,12 +856,22 @@ fn archive_session(bus: &Bus, session: &AgentSession) {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{ use super::{
PermissionAsk, TurnError, acp_permits, nonzero_or, parse_u64, report_compact, PermissionAsk, TurnError, acp_permits, apply_telemetry, nonzero_or, parse_u64,
runtime_error_to_turn, with_turn_end_compact, report_compact, runtime_error_to_turn, with_turn_end_compact,
}; };
use crate::events::{Bus, LiveEvent}; use crate::events::{Bus, LiveEvent};
use hive_runtime::{AcpError, Error}; use hive_runtime::{AcpError, Error};
#[test]
fn a_turn_that_parsed_nothing_does_not_keep_the_previous_resolved_model() {
let bus = Bus::new();
bus.set_resolved_model(Some("claude-opus-4-8".to_owned()));
apply_telemetry(&bus, &hive_claude::Telemetry::default());
assert_eq!(bus.last_resolved_model(), None);
}
#[test] #[test]
fn an_absent_or_blank_value_is_not_a_number() { fn an_absent_or_blank_value_is_not_a_number() {
assert_eq!(parse_u64(None), None); assert_eq!(parse_u64(None), None);