From 66f5b8720d72cdae465ccbe7fb4abfa5d0936b9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?m=C3=BCde?= Date: Sun, 5 Jul 2026 20:21:33 +0200 Subject: [PATCH] refactor(agent): use hive_claude::TokenUsage directly, drop the duplicate --- Cargo.lock | 1 + hive-ag3nt/src/events.rs | 23 +---------------------- hive-ag3nt/src/turn.rs | 18 ++---------------- hive-ag3nt/src/turn_stats.rs | 10 +++++----- hive-ag3nt/src/web_ui.rs | 4 ++-- hive-claude/Cargo.toml | 1 + hive-claude/src/telemetry.rs | 7 ++++--- 7 files changed, 16 insertions(+), 48 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 29eb0c04..315feef0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1397,6 +1397,7 @@ dependencies = [ name = "hive-claude" version = "0.1.0" dependencies = [ + "serde", "serde_json", "thiserror 2.0.18", "tokio", diff --git a/hive-ag3nt/src/events.rs b/hive-ag3nt/src/events.rs index a9cf80ca..d9721b34 100644 --- a/hive-ag3nt/src/events.rs +++ b/hive-ag3nt/src/events.rs @@ -12,6 +12,7 @@ use std::path::{Path, PathBuf}; use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU64, Ordering}; use std::sync::{Arc, Mutex}; +use hive_claude::TokenUsage; use rusqlite::{Connection, params}; use serde::{Deserialize, Serialize}; use tokio::sync::broadcast; @@ -432,28 +433,6 @@ impl EventStore { } } -/// Token usage emitted by claude in the final `result` stream-json event. -/// All counts are in tokens. `None` fields mean the server didn't report them. -#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)] -pub struct TokenUsage { - pub input_tokens: u64, - pub output_tokens: u64, - pub cache_read_input_tokens: u64, - pub cache_creation_input_tokens: u64, -} - -impl TokenUsage { - /// Total context consumed this turn (input + cache reads + cache writes). - /// This is the per-inference context footprint that counts against the - /// model's `contextWindow` limit. Tracked from the last `assistant` event - /// in the stream-json (per-inference usage, not the cumulative `result` - /// event which sums across all inferences in a tool-heavy turn and can - /// far exceed the per-inference window). - #[must_use] - pub fn context_tokens(&self) -> u64 { - self.input_tokens + self.cache_read_input_tokens + self.cache_creation_input_tokens - } -} /// Authoritative turn-loop state. The harness owns it; the web UI /// reads via `/api/state` and renders. Lives alongside the bus diff --git a/hive-ag3nt/src/turn.rs b/hive-ag3nt/src/turn.rs index 00451a9b..62661fbe 100644 --- a/hive-ag3nt/src/turn.rs +++ b/hive-ag3nt/src/turn.rs @@ -11,7 +11,7 @@ use anyhow::Result; use hive_claude::{Config, InfiniteSession, PercentPolicy, Sink}; use serde_json::Value; -use crate::events::{Bus, LiveEvent, TokenUsage}; +use crate::events::{Bus, LiveEvent}; use crate::mcp; // Hive-enforced claude settings ship at `/etc/claude-code/managed-settings.json` @@ -561,27 +561,13 @@ fn apply_telemetry(bus: &Bus, telemetry: &hive_claude::Telemetry) { if telemetry.context.context_tokens() == 0 && telemetry.cost.context_tokens() == 0 { return; } - bus.record_turn_usage( - to_bus_usage(telemetry.context), - to_bus_usage(telemetry.cost), - ); + bus.record_turn_usage(telemetry.context, telemetry.cost); bus.set_resolved_model(telemetry.model.clone()); if let Some(window) = telemetry.context_window { bus.set_api_context_window(window); } } -/// Convert the lib's `TokenUsage` into the bus/stats `TokenUsage` (identical -/// fields; the two crates keep their own types to avoid coupling). -fn to_bus_usage(u: hive_claude::TokenUsage) -> TokenUsage { - TokenUsage { - input_tokens: u.input_tokens, - output_tokens: u.output_tokens, - cache_read_input_tokens: u.cache_read_input_tokens, - cache_creation_input_tokens: u.cache_creation_input_tokens, - } -} - /// Archive (do NOT delete) the harness's own session so the next turn's /// `--resume ` misses and self-heals into a fresh `--name <title>` /// session. Delegates the rename to [`hive_claude::SessionStore::archive_by_title`] diff --git a/hive-ag3nt/src/turn_stats.rs b/hive-ag3nt/src/turn_stats.rs index c239aff2..e314923e 100644 --- a/hive-ag3nt/src/turn_stats.rs +++ b/hive-ag3nt/src/turn_stats.rs @@ -268,8 +268,8 @@ impl TurnStats { pub fn last_usage( &self, ) -> ( - Option<crate::events::TokenUsage>, - Option<crate::events::TokenUsage>, + Option<hive_claude::TokenUsage>, + Option<hive_claude::TokenUsage>, ) { let conn = self.inner.lock().unwrap(); conn.query_row( @@ -285,19 +285,19 @@ impl TurnStats { let g = |i: usize| -> rusqlite::Result<u64> { Ok(u64::try_from(row.get::<_, i64>(i)?).unwrap_or(0)) }; - let cost = crate::events::TokenUsage { + let cost = hive_claude::TokenUsage { input_tokens: g(0)?, output_tokens: g(1)?, cache_read_input_tokens: g(2)?, cache_creation_input_tokens: g(3)?, }; - let last = crate::events::TokenUsage { + let last = hive_claude::TokenUsage { input_tokens: g(4)?, output_tokens: g(5)?, cache_read_input_tokens: g(6)?, cache_creation_input_tokens: g(7)?, }; - let ctx = if last == crate::events::TokenUsage::default() { + let ctx = if last == hive_claude::TokenUsage::default() { None } else { Some(last) diff --git a/hive-ag3nt/src/web_ui.rs b/hive-ag3nt/src/web_ui.rs index bbb9b889..0383ca0c 100644 --- a/hive-ag3nt/src/web_ui.rs +++ b/hive-ag3nt/src/web_ui.rs @@ -412,10 +412,10 @@ struct StateSnapshot { /// Last-inference token usage from the most recent completed /// turn — represents the current context-window size at turn-end. /// `null` until the first turn finishes. - ctx_usage: Option<crate::events::TokenUsage>, + ctx_usage: Option<hive_claude::TokenUsage>, /// Cumulative token usage across the most recent turn's inferences /// (cost signal). `null` until the first turn finishes. - cost_usage: Option<crate::events::TokenUsage>, + cost_usage: Option<hive_claude::TokenUsage>, /// Navigation links for this agent page. Also served via /// `DashboardState.links` (`GET /api/dashboard-state`) for the /// dashboard card's icon strip. Both are produced by `agent_links()` diff --git a/hive-claude/Cargo.toml b/hive-claude/Cargo.toml index 6218d201..63e6f608 100644 --- a/hive-claude/Cargo.toml +++ b/hive-claude/Cargo.toml @@ -7,6 +7,7 @@ version.workspace = true workspace = true [dependencies] +serde = { workspace = true } serde_json.workspace = true thiserror.workspace = true tokio.workspace = true diff --git a/hive-claude/src/telemetry.rs b/hive-claude/src/telemetry.rs index 3c1c490f..7bc62f5b 100644 --- a/hive-claude/src/telemetry.rs +++ b/hive-claude/src/telemetry.rs @@ -6,10 +6,11 @@ //! also want the raw events (for their own SSE / tool-call accounting) still //! get them through their [`crate::Sink`]. +use serde::{Deserialize, Serialize}; use serde_json::Value; /// Token counts from one `usage` block. All in tokens; missing fields read `0`. -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct TokenUsage { pub input_tokens: u64, pub output_tokens: u64, @@ -37,7 +38,7 @@ impl TokenUsage { } /// Everything the driver tracks from one turn's stream-json output. -#[derive(Debug, Clone, Default, PartialEq, Eq)] +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct Telemetry { /// Most recent per-inference usage (from `assistant` events) — the live /// context footprint (the number to watch for compaction). @@ -108,7 +109,7 @@ fn context_window_from_result(event: &Value) -> Option<u64> { /// The compaction signal: the live context size and the window it's measured /// against. Derived from [`Telemetry`] via [`Telemetry::usage`]. -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct Usage { /// Tokens in the last inference's context. `0` until the first `assistant` /// event.