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

@ -82,6 +82,18 @@ enum Cmd {
/// Set via `services.hyperhive.agentMemoryMax`.
#[arg(long, default_value = "4G")]
agent_memory_max: String,
/// Per-model USD prices (per million tokens) for the hive-wide
/// ST4TS cost estimate, as a JSON object mapping a model-family
/// short name to `{input, output, cache_read, cache_write}`.
/// Keys are matched case-insensitively as a substring of the
/// model id; models not covered fall back to the built-in
/// estimate. Set via the `services.hyperhive.modelPrices` NixOS
/// option.
#[arg(
long,
default_value = r#"{"opus":{"input":15.0,"output":75.0,"cache_read":1.5,"cache_write":18.75},"sonnet":{"input":3.0,"output":15.0,"cache_read":0.3,"cache_write":3.75},"haiku":{"input":0.8,"output":4.0,"cache_read":0.08,"cache_write":1.0}}"#
)]
model_prices: String,
},
/// Spawn a new agent container directly (`hive-agent-<name>`). Bypasses
/// the approval queue — use only as an operator on the host. For
@ -152,6 +164,7 @@ async fn main() -> Result<()> {
context_window_tokens,
agent_cpu_quota,
agent_memory_max,
model_prices,
} => {
cmd_serve(
hyperhive_flake,
@ -163,6 +176,7 @@ async fn main() -> Result<()> {
context_window_tokens,
agent_cpu_quota,
agent_memory_max,
model_prices,
&cli.socket,
)
.await
@ -221,6 +235,7 @@ async fn cmd_serve(
context_window_tokens: String,
agent_cpu_quota: String,
agent_memory_max: String,
model_prices: String,
socket: &std::path::Path,
) -> Result<()> {
// Move any host-side state still at the legacy flat layout into its
@ -230,6 +245,8 @@ async fn cmd_serve(
hive_c0re::paths::relocate_legacy_state();
let cwt: std::collections::HashMap<String, u64> = serde_json::from_str(&context_window_tokens)
.context("--context-window-tokens: invalid JSON")?;
let prices: hive_c0re::hive_stats::PriceTable =
serde_json::from_str(&model_prices).context("--model-prices: invalid JSON")?;
let coord = Arc::new(Coordinator::open(
&db,
hyperhive_flake,
@ -240,6 +257,7 @@ async fn cmd_serve(
cwt,
agent_cpu_quota,
agent_memory_max,
prices,
)?);
manager_server::start(coord.clone())?;
// Idempotent pre-flight: rewrite pre-meta-layout applied