refactor(agent): use hive_claude::TokenUsage directly, drop the duplicate
This commit is contained in:
parent
c9de2eeb97
commit
66f5b8720d
7 changed files with 16 additions and 48 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -1397,6 +1397,7 @@ dependencies = [
|
||||||
name = "hive-claude"
|
name = "hive-claude"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ use std::path::{Path, PathBuf};
|
||||||
use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU64, Ordering};
|
use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU64, Ordering};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use hive_claude::TokenUsage;
|
||||||
use rusqlite::{Connection, params};
|
use rusqlite::{Connection, params};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::sync::broadcast;
|
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
|
/// Authoritative turn-loop state. The harness owns it; the web UI
|
||||||
/// reads via `/api/state` and renders. Lives alongside the bus
|
/// reads via `/api/state` and renders. Lives alongside the bus
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ use anyhow::Result;
|
||||||
use hive_claude::{Config, InfiniteSession, PercentPolicy, Sink};
|
use hive_claude::{Config, InfiniteSession, PercentPolicy, Sink};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
use crate::events::{Bus, LiveEvent, TokenUsage};
|
use crate::events::{Bus, LiveEvent};
|
||||||
use crate::mcp;
|
use crate::mcp;
|
||||||
|
|
||||||
// Hive-enforced claude settings ship at `/etc/claude-code/managed-settings.json`
|
// 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 {
|
if telemetry.context.context_tokens() == 0 && telemetry.cost.context_tokens() == 0 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
bus.record_turn_usage(
|
bus.record_turn_usage(telemetry.context, telemetry.cost);
|
||||||
to_bus_usage(telemetry.context),
|
|
||||||
to_bus_usage(telemetry.cost),
|
|
||||||
);
|
|
||||||
bus.set_resolved_model(telemetry.model.clone());
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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
|
/// Archive (do NOT delete) the harness's own session so the next turn's
|
||||||
/// `--resume <title>` misses and self-heals into a fresh `--name <title>`
|
/// `--resume <title>` misses and self-heals into a fresh `--name <title>`
|
||||||
/// session. Delegates the rename to [`hive_claude::SessionStore::archive_by_title`]
|
/// session. Delegates the rename to [`hive_claude::SessionStore::archive_by_title`]
|
||||||
|
|
|
||||||
|
|
@ -268,8 +268,8 @@ impl TurnStats {
|
||||||
pub fn last_usage(
|
pub fn last_usage(
|
||||||
&self,
|
&self,
|
||||||
) -> (
|
) -> (
|
||||||
Option<crate::events::TokenUsage>,
|
Option<hive_claude::TokenUsage>,
|
||||||
Option<crate::events::TokenUsage>,
|
Option<hive_claude::TokenUsage>,
|
||||||
) {
|
) {
|
||||||
let conn = self.inner.lock().unwrap();
|
let conn = self.inner.lock().unwrap();
|
||||||
conn.query_row(
|
conn.query_row(
|
||||||
|
|
@ -285,19 +285,19 @@ impl TurnStats {
|
||||||
let g = |i: usize| -> rusqlite::Result<u64> {
|
let g = |i: usize| -> rusqlite::Result<u64> {
|
||||||
Ok(u64::try_from(row.get::<_, i64>(i)?).unwrap_or(0))
|
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)?,
|
input_tokens: g(0)?,
|
||||||
output_tokens: g(1)?,
|
output_tokens: g(1)?,
|
||||||
cache_read_input_tokens: g(2)?,
|
cache_read_input_tokens: g(2)?,
|
||||||
cache_creation_input_tokens: g(3)?,
|
cache_creation_input_tokens: g(3)?,
|
||||||
};
|
};
|
||||||
let last = crate::events::TokenUsage {
|
let last = hive_claude::TokenUsage {
|
||||||
input_tokens: g(4)?,
|
input_tokens: g(4)?,
|
||||||
output_tokens: g(5)?,
|
output_tokens: g(5)?,
|
||||||
cache_read_input_tokens: g(6)?,
|
cache_read_input_tokens: g(6)?,
|
||||||
cache_creation_input_tokens: g(7)?,
|
cache_creation_input_tokens: g(7)?,
|
||||||
};
|
};
|
||||||
let ctx = if last == crate::events::TokenUsage::default() {
|
let ctx = if last == hive_claude::TokenUsage::default() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(last)
|
Some(last)
|
||||||
|
|
|
||||||
|
|
@ -412,10 +412,10 @@ struct StateSnapshot {
|
||||||
/// Last-inference token usage from the most recent completed
|
/// Last-inference token usage from the most recent completed
|
||||||
/// turn — represents the current context-window size at turn-end.
|
/// turn — represents the current context-window size at turn-end.
|
||||||
/// `null` until the first turn finishes.
|
/// `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
|
/// Cumulative token usage across the most recent turn's inferences
|
||||||
/// (cost signal). `null` until the first turn finishes.
|
/// (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
|
/// Navigation links for this agent page. Also served via
|
||||||
/// `DashboardState.links` (`GET /api/dashboard-state`) for the
|
/// `DashboardState.links` (`GET /api/dashboard-state`) for the
|
||||||
/// dashboard card's icon strip. Both are produced by `agent_links()`
|
/// dashboard card's icon strip. Both are produced by `agent_links()`
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ version.workspace = true
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
serde = { workspace = true }
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,11 @@
|
||||||
//! also want the raw events (for their own SSE / tool-call accounting) still
|
//! also want the raw events (for their own SSE / tool-call accounting) still
|
||||||
//! get them through their [`crate::Sink`].
|
//! get them through their [`crate::Sink`].
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
/// Token counts from one `usage` block. All in tokens; missing fields read `0`.
|
/// 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 struct TokenUsage {
|
||||||
pub input_tokens: u64,
|
pub input_tokens: u64,
|
||||||
pub output_tokens: u64,
|
pub output_tokens: u64,
|
||||||
|
|
@ -37,7 +38,7 @@ impl TokenUsage {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Everything the driver tracks from one turn's stream-json output.
|
/// 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 {
|
pub struct Telemetry {
|
||||||
/// Most recent per-inference usage (from `assistant` events) — the live
|
/// Most recent per-inference usage (from `assistant` events) — the live
|
||||||
/// context footprint (the number to watch for compaction).
|
/// 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
|
/// The compaction signal: the live context size and the window it's measured
|
||||||
/// against. Derived from [`Telemetry`] via [`Telemetry::usage`].
|
/// 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 {
|
pub struct Usage {
|
||||||
/// Tokens in the last inference's context. `0` until the first `assistant`
|
/// Tokens in the last inference's context. `0` until the first `assistant`
|
||||||
/// event.
|
/// event.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue