feat(stats): make ST4TS model price table operator-tunable

The hive-wide cost estimate on the dashboard's ST4TS tab used a
hard-coded model->price table in hive_stats.rs. Anthropic list pricing
drifts, so move the table to a nix option operators can keep current
without a code change.

- New `services.hyperhive.modelPrices` option: attrset of model-family
  short name -> { input, output, cache_read, cache_write } USD per
  million tokens. Passed to `hive-c0re serve --model-prices <json>`.
- hive_stats: `Prices` is now public + Deserialize; add `PriceTable`
  type and `resolve_prices` (longest case-insensitive substring key
  wins) with the old hard-coded table preserved as `builtin_prices`
  fallback for any model not covered.
- Coordinator holds the parsed table (hive-c0re-local, not injected
  into containers, so not part of HiveEnv); `/api/stats-hive` reads it.
- Docs: dashboard.md ST4TS cost note updated; option self-documents
  via nixosOptionsDoc.

Closes #1434
This commit is contained in:
iris 2026-06-05 23:52:16 +02:00
commit cc8f58fb24
6 changed files with 172 additions and 23 deletions

View file

@ -86,6 +86,14 @@ pub struct Coordinator {
pub agent_cpu_quota: String,
/// Per-agent systemd `MemoryMax=` value (e.g. `"4G"`). Same drop-in.
pub agent_memory_max: String,
/// Operator-tunable model→price table backing the hive-wide cost
/// estimate on the ST4TS tab. Set via `services.hyperhive.modelPrices`
/// and passed to `hive-c0re serve --model-prices <json>`. Models not
/// in the table fall back to the built-in estimate in `hive_stats`.
/// hive-c0re-local (read only by the `/api/stats-hive` handler), so —
/// unlike `context_window_tokens` — it is *not* part of `HiveEnv` and
/// is never injected into containers.
pub model_prices: crate::hive_stats::PriceTable,
agents: Mutex<HashMap<String, AgentSocket>>,
/// Agents whose lifecycle action (currently just spawn) is in flight.
/// Read by the dashboard to render a spinner; cleared when the action
@ -279,6 +287,7 @@ impl Coordinator {
context_window_tokens: std::collections::HashMap<String, u64>,
agent_cpu_quota: String,
agent_memory_max: String,
model_prices: crate::hive_stats::PriceTable,
) -> Result<Self> {
let broker = Broker::open(db_path).context("open broker")?;
let approvals = Approvals::open(db_path).context("open approvals")?;
@ -314,6 +323,7 @@ impl Coordinator {
context_window_tokens,
agent_cpu_quota,
agent_memory_max,
model_prices,
agents: Mutex::new(HashMap::new()),
transient: Mutex::new(HashMap::new()),
recent_transient: Mutex::new(HashMap::new()),