feat(admin): add per-model usage statistics to the charts section

The Charts view could break usage down by user, group or total, but not
by model. Per-model data existed only as day-window attempt and failure
counts, which measure availability rather than delivered usage.

A served model now accumulates the same four metrics a user or group
does. The scope kind is "model" and its id is the canonical
"provider/model", so a request that fails over counts against the
provider that answered it, and one bare id served by two providers ranks
as two rows.

The request counter is written after the response, in the batch that
already writes tokens and cost. That is the first point at which the
serving model is known, so no extra round trip is needed and admission
never double counts.

GET /admin/api/usage/models ranks models for one metric and window. It
returns only models with non-zero usage, by operator requirement: the
catalog runs to hundreds of models and a padded ranking is unreadable.
The read stays one storeGetMulti over the catalog.

The Charts view gains a Models tab with its own metric picker, and the
scope picker gains the models that have traffic.
This commit is contained in:
2026-08-27 23:30:22 +01:00
parent bcd7c32c3d
commit 4bb49b4fe9
15 changed files with 1236 additions and 53 deletions
+28
View File
@@ -281,6 +281,34 @@ export interface UsageHistoryResponse {
points: UsageHistoryPoint[]
}
/**
* One ranked model in GET /admin/api/usage/models (admin.go:
* adminUsageModelEntryView). `id` is the canonical "provider/model" — the
* provider that actually SERVED the traffic, which is why a failover
* target and its primary appear as two separate rows.
*/
export interface AdminUsageModelEntry {
id: string
value: number
}
/**
* GET /admin/api/usage/models (admin.go: adminUsageModelsResponse) — the
* model ranking behind the Charts view's "Models" tab, and the source of
* the scope picker's model list.
*
* ONLY MODELS WITH NON-ZERO USAGE ARE RETURNED (server-side, by operator
* requirement): the catalog runs to hundreds of models, so neither the
* ranking nor the picker may pad itself with idle ones. An empty `models`
* array therefore means "nothing used in this window", never "no models
* configured" — and a failed store read is a 503, never an empty array.
*/
export interface AdminUsageModelsResponse {
metric: string
window: string
models: AdminUsageModelEntry[]
}
/** One MCP-server or agent target's current-window request counters (admin.go: adminTargetCountersView) — requests only, no tokens or cost. */
export interface AdminTargetCountersView {
requestsPerMinute: number