feat(admin): rates + discovery flag in API

Extend GET /admin/api/overview's existing providers payload rather than
adding a sibling endpoint: the route already returns one providers array
per request on the dashboard's 5s poll, is already admin-gated and
stats-excluded, and the new fields are one more batched limiter read
sharing that same request — a second endpoint would duplicate all three
properties for no benefit.

adminProviderView gains AttemptsDay/FailuresDay/AttemptsMinute/
FailuresMinute (the provider's own current-window counters) and
ModelRates (a per-model breakdown, keyed by upstream model id, via the
new adminModelRateView) — Feature A. buildAdminOverview builds every
provider's and every (provider, model) pair's limitScope in two passes
and reads them all through limiter.providerUsage in ONE storeGetMulti
call, then slices the flat result back by index.

adminProviderView also gains DiscoveryEnabled, mirroring a new
providerSnapshot.discoveryEnabled field (registry.go) read straight off
providerState — it is set once at construction and never mutated
afterward, so it needs neither providerState's mutex nor a place in its
existing snapshot() method's mutable-field return shape. Feature B: this
lets the webui tell "discovery is off" apart from "discovery is on but
has not refreshed yet", which previously both showed the same zero
lastRefresh as an identical "refreshed never".
This commit is contained in:
2026-08-21 23:22:51 +01:00
parent aafd2a0433
commit bf23b41568
4 changed files with 317 additions and 18 deletions
+33
View File
@@ -707,6 +707,39 @@ func TestModelRegistry_Snapshot_ModelsStaleWhileError(t *testing.T) {
}
}
// TestModelRegistry_Snapshot_DiscoveryEnabledReflectsConfig proves
// providerSnapshot.discoveryEnabled (Feature B, v0.22 — last-refresh label
// honesty) mirrors each provider's own configured Discovery flag exactly,
// read straight from providerState rather than through providerState.
// snapshot()'s mutex-guarded fields, since it never changes after
// construction.
func TestModelRegistry_Snapshot_DiscoveryEnabledReflectsConfig(t *testing.T) {
t.Parallel()
adapters := map[string]providerAdapter{
"on": newFakeAdapter("on"),
"off": newFakeAdapter("off"),
}
cfg := &Config{Providers: map[string]*ProviderConfig{
"on": {Discovery: true, DiscoveryInterval: "1h"},
"off": {Models: []string{"pinned"}},
}}
reg, err := newModelRegistry(adapters, cfg, func(string, ...any) {})
if err != nil {
t.Fatalf("newModelRegistry: %v", err)
}
byName := map[string]providerSnapshot{}
for _, s := range reg.snapshot() {
byName[s.name] = s
}
if !byName["on"].discoveryEnabled {
t.Error(`provider "on" discoveryEnabled = false, want true`)
}
if byName["off"].discoveryEnabled {
t.Error(`provider "off" discoveryEnabled = true, want false`)
}
}
// --- listFor: group filtering, sorting, collision presentation (ruling h) ---
func TestModelRegistry_ListFor_GroupFiltered(t *testing.T) {