summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-03-16 04:25:26 +0200
committerPaul Buetow <paul@buetow.org>2026-03-16 04:25:26 +0200
commitf55a1e88ea5948582d0e5a33efea0c5d806e1f4b (patch)
treea180dc8c0266c87b0bc8d05a8e9c18d18748d751
parent7dd1bfa797b462791dc398690f599a2c5e5d1962 (diff)
Add Snapshot.ScopeReqs/ScopeRPM and simplify 3 callers
Centralizes the provider+model map traversal and window-minutes guard that was duplicated in hexaiaction, hexaicli, and lsp. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
-rw-r--r--internal/hexaiaction/prompts.go18
-rw-r--r--internal/hexaicli/run.go12
-rw-r--r--internal/lsp/handlers_utils.go14
-rw-r--r--internal/stats/stats.go25
-rw-r--r--internal/stats/stats_test.go47
5 files changed, 81 insertions, 35 deletions
diff --git a/internal/hexaiaction/prompts.go b/internal/hexaiaction/prompts.go
index 61775d4..8cf8c48 100644
--- a/internal/hexaiaction/prompts.go
+++ b/internal/hexaiaction/prompts.go
@@ -129,20 +129,12 @@ func runOnce(ctx context.Context, client chatDoer, sys, user string, req request
if model == "" {
model = client.DefaultModel()
}
- _ = stats.Update(ctx, providerOf(client), model, sent, recv)
+ provider := providerOf(client)
+ _ = stats.Update(ctx, provider, model, sent, recv)
if snap, err := stats.TakeSnapshot(); err == nil {
- minsWin := snap.Window.Minutes()
- if minsWin <= 0 {
- minsWin = 0.001
- }
- scopeReqs := int64(0)
- if pe, ok := snap.Providers[providerOf(client)]; ok {
- if mc, ok2 := pe.Models[model]; ok2 {
- scopeReqs = mc.Reqs
- }
- }
- scopeRPM := float64(scopeReqs) / minsWin
- _ = tmux.SetStatus(tmux.FormatGlobalStatusColored(snap.Global.Reqs, snap.RPM, snap.Global.Sent, snap.Global.Recv, providerOf(client), model, scopeRPM, scopeReqs, snap.Window))
+ scopeReqs := snap.ScopeReqs(provider, model)
+ scopeRPM := snap.ScopeRPM(provider, model)
+ _ = tmux.SetStatus(tmux.FormatGlobalStatusColored(snap.Global.Reqs, snap.RPM, snap.Global.Sent, snap.Global.Recv, provider, model, scopeRPM, scopeReqs, snap.Window))
}
return out, nil
}
diff --git a/internal/hexaicli/run.go b/internal/hexaicli/run.go
index 1563f5e..806f824 100644
--- a/internal/hexaicli/run.go
+++ b/internal/hexaicli/run.go
@@ -593,16 +593,8 @@ func summarizeChatRun(ctx context.Context, client llm.Client, model string, msgs
if err == nil {
summary.snapshot = snap
}
- minsWin := summary.snapshot.Window.Minutes()
- if minsWin <= 0 {
- minsWin = 0.001
- }
- if pe, ok := summary.snapshot.Providers[client.Name()]; ok {
- if mc, ok2 := pe.Models[model]; ok2 {
- summary.scopeReq = mc.Reqs
- }
- }
- summary.scopeRPM = float64(summary.scopeReq) / minsWin
+ summary.scopeReq = summary.snapshot.ScopeReqs(client.Name(), model)
+ summary.scopeRPM = summary.snapshot.ScopeRPM(client.Name(), model)
return summary
}
diff --git a/internal/lsp/handlers_utils.go b/internal/lsp/handlers_utils.go
index 7fb17d2..408fdb1 100644
--- a/internal/lsp/handlers_utils.go
+++ b/internal/lsp/handlers_utils.go
@@ -170,18 +170,8 @@ func (s *Server) logLLMStats(model string) {
if modelName == "" {
modelName = client.DefaultModel()
}
- // Per-scope rpm estimated from window
- scopeReqs := int64(0)
- if pe, ok := snap.Providers[provider]; ok {
- if mc, ok2 := pe.Models[modelName]; ok2 {
- scopeReqs = mc.Reqs
- }
- }
- minsWin := snap.Window.Minutes()
- if minsWin <= 0 {
- minsWin = 0.001
- }
- scopeRPM := float64(scopeReqs) / minsWin
+ scopeReqs := snap.ScopeReqs(provider, modelName)
+ scopeRPM := snap.ScopeRPM(provider, modelName)
s.emitGlobalStatus(snap.Global.Reqs, snap.RPM, snap.Global.Sent, snap.Global.Recv, provider, modelName, scopeRPM, scopeReqs, snap.Window)
}
}
diff --git a/internal/stats/stats.go b/internal/stats/stats.go
index 939e1aa..742a5be 100644
--- a/internal/stats/stats.go
+++ b/internal/stats/stats.go
@@ -75,6 +75,31 @@ type Snapshot struct {
Window time.Duration
}
+// ScopeReqs returns the request count for a specific provider+model pair.
+// Returns 0 when the provider or model is not present in the snapshot.
+func (s Snapshot) ScopeReqs(provider, model string) int64 {
+ if pe, ok := s.Providers[provider]; ok {
+ if mc, ok2 := pe.Models[model]; ok2 {
+ return mc.Reqs
+ }
+ }
+ return 0
+}
+
+// ScopeRPM returns the requests-per-minute for a specific provider+model
+// pair, derived from ScopeReqs and the snapshot's sliding window.
+func (s Snapshot) ScopeRPM(provider, model string) float64 {
+ reqs := s.ScopeReqs(provider, model)
+ if reqs == 0 {
+ return 0
+ }
+ mins := s.Window.Minutes()
+ if mins <= 0 {
+ mins = 0.001
+ }
+ return float64(reqs) / mins
+}
+
// Update appends one event and prunes old entries under lock.
func Update(ctx context.Context, provider, model string, sentBytes, recvBytes int) error {
dir, err := CacheDir()
diff --git a/internal/stats/stats_test.go b/internal/stats/stats_test.go
index 45f9e2a..a9b3d22 100644
--- a/internal/stats/stats_test.go
+++ b/internal/stats/stats_test.go
@@ -334,3 +334,50 @@ func TestUpdate_CancelledContext(t *testing.T) {
t.Fatal("expected error from cancelled context, got nil")
}
}
+
+func TestSnapshot_ScopeReqs(t *testing.T) {
+ snap := Snapshot{
+ Providers: map[string]ProviderEntry{
+ "openai": {Models: map[string]Counters{"gpt-5.0": {Reqs: 42}}},
+ },
+ }
+ if got := snap.ScopeReqs("openai", "gpt-5.0"); got != 42 {
+ t.Fatalf("expected 42, got %d", got)
+ }
+ if got := snap.ScopeReqs("openai", "gpt-4.1"); got != 0 {
+ t.Fatalf("expected 0 for missing model, got %d", got)
+ }
+ if got := snap.ScopeReqs("anthropic", "gpt-5.0"); got != 0 {
+ t.Fatalf("expected 0 for missing provider, got %d", got)
+ }
+}
+
+func TestSnapshot_ScopeRPM(t *testing.T) {
+ snap := Snapshot{
+ Providers: map[string]ProviderEntry{
+ "openai": {Models: map[string]Counters{"gpt-5.0": {Reqs: 60}}},
+ },
+ Window: time.Hour,
+ }
+ rpm := snap.ScopeRPM("openai", "gpt-5.0")
+ if rpm != 1.0 {
+ t.Fatalf("expected 1.0 rpm, got %v", rpm)
+ }
+ // Missing model should return 0
+ if rpm := snap.ScopeRPM("openai", "missing"); rpm != 0 {
+ t.Fatalf("expected 0 rpm for missing, got %v", rpm)
+ }
+}
+
+func TestSnapshot_ScopeRPM_ZeroWindow(t *testing.T) {
+ snap := Snapshot{
+ Providers: map[string]ProviderEntry{
+ "openai": {Models: map[string]Counters{"gpt-5.0": {Reqs: 10}}},
+ },
+ Window: 0, // edge case
+ }
+ rpm := snap.ScopeRPM("openai", "gpt-5.0")
+ if rpm <= 0 {
+ t.Fatalf("expected positive rpm even with zero window, got %v", rpm)
+ }
+}