summaryrefslogtreecommitdiff
path: root/internal/hexaiaction
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2025-09-17 21:33:45 +0300
committerPaul Buetow <paul@buetow.org>2025-09-17 21:33:45 +0300
commit88103657fb230bb41217a06aa5602ae23e7acb8b (patch)
tree524c437e4e40ee5d6713b6ea5414ad975654cc52 /internal/hexaiaction
parent2b6232704ecc90630196b9f829f966533e5cdccd (diff)
feat(stats,tmux): global Σ@window stats across processes with flocked cache; width mitigation (narrow/maxlen); configurable [stats] window_minutes; robust coverage parsing; docs update\n\n- Add internal/stats with windowed event cache + flock + atomic writes\n- Wire stats into LSP/CLI/Tmux Action; tmux shows Σ@window with per-model tail\n- HEXAI_TMUX_STATUS_NARROW and HEXAI_TMUX_STATUS_MAXLEN for width control\n- Add [stats] window_minutes to config and apply on startup\n- Improve Magefile coverage handling; add tests to lift coverage >85%\n- Update docs/tmux.md and config example
Diffstat (limited to 'internal/hexaiaction')
-rw-r--r--internal/hexaiaction/prompts.go45
-rw-r--r--internal/hexaiaction/prompts_simplify_test.go27
-rw-r--r--internal/hexaiaction/run.go5
3 files changed, 63 insertions, 14 deletions
diff --git a/internal/hexaiaction/prompts.go b/internal/hexaiaction/prompts.go
index 97af32f..393e9e4 100644
--- a/internal/hexaiaction/prompts.go
+++ b/internal/hexaiaction/prompts.go
@@ -7,6 +7,7 @@ import (
"codeberg.org/snonux/hexai/internal/appconfig"
"codeberg.org/snonux/hexai/internal/llm"
+ "codeberg.org/snonux/hexai/internal/stats"
"codeberg.org/snonux/hexai/internal/textutil"
"codeberg.org/snonux/hexai/internal/tmux"
)
@@ -88,47 +89,63 @@ func runCustom(ctx context.Context, cfg appconfig.App, client chatDoer, ca appco
func runOnce(ctx context.Context, client chatDoer, sys, user string) (string, error) {
msgs := []llm.Message{{Role: "system", Content: sys}, {Role: "user", Content: user}}
- start := time.Now()
txt, err := client.Chat(ctx, msgs)
if err != nil {
return "", err
}
out := strings.TrimSpace(StripFences(txt))
- // Update tmux heartbeat with simple one-request stats
+ // Contribute to global stats and update tmux status
sent := 0
for _, m := range msgs {
sent += len(m.Content)
}
recv := len(out)
- mins := time.Since(start).Minutes()
- if mins <= 0 {
- mins = 0.001
+ _ = stats.Update(ctx, providerOf(client), client.DefaultModel(), 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[client.DefaultModel()]; 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), client.DefaultModel(), scopeRPM, scopeReqs, snap.Window))
}
- rpm := float64(1) / mins
- _ = tmux.SetStatus(tmux.FormatLLMStatsStatusColored(providerOf(client), client.DefaultModel(), 1, rpm, int64(sent), int64(recv)))
return out, nil
}
func runOnceWithOpts(ctx context.Context, client chatDoer, sys, user string, opts []llm.RequestOption) (string, error) {
msgs := []llm.Message{{Role: "system", Content: sys}, {Role: "user", Content: user}}
- start := time.Now()
txt, err := client.Chat(ctx, msgs, opts...)
if err != nil {
return "", err
}
out := strings.TrimSpace(StripFences(txt))
- // Update tmux heartbeat with simple one-request stats
+ // Contribute to global stats and update tmux status
sent := 0
for _, m := range msgs {
sent += len(m.Content)
}
recv := len(out)
- mins := time.Since(start).Minutes()
- if mins <= 0 {
- mins = 0.001
+ _ = stats.Update(ctx, providerOf(client), client.DefaultModel(), 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[client.DefaultModel()]; 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), client.DefaultModel(), scopeRPM, scopeReqs, snap.Window))
}
- rpm := float64(1) / mins
- _ = tmux.SetStatus(tmux.FormatLLMStatsStatusColored(providerOf(client), client.DefaultModel(), 1, rpm, int64(sent), int64(recv)))
return out, nil
}
diff --git a/internal/hexaiaction/prompts_simplify_test.go b/internal/hexaiaction/prompts_simplify_test.go
new file mode 100644
index 0000000..4daba38
--- /dev/null
+++ b/internal/hexaiaction/prompts_simplify_test.go
@@ -0,0 +1,27 @@
+package hexaiaction
+
+import (
+ "context"
+ "testing"
+
+ "codeberg.org/snonux/hexai/internal/appconfig"
+ "codeberg.org/snonux/hexai/internal/llm"
+)
+
+type simplifyClient struct{}
+
+func (simplifyClient) Chat(_ context.Context, _ []llm.Message, _ ...llm.RequestOption) (string, error) {
+ return "OUT", nil
+}
+func (simplifyClient) DefaultModel() string { return "m" }
+
+func TestRunSimplify_Smoke(t *testing.T) {
+ cfg := appconfig.Load(nil)
+ out, err := runSimplify(context.Background(), cfg, simplifyClient{}, "code")
+ if err != nil {
+ t.Fatalf("runSimplify: %v", err)
+ }
+ if out == "" {
+ t.Fatalf("expected output")
+ }
+}
diff --git a/internal/hexaiaction/run.go b/internal/hexaiaction/run.go
index b07fbbb..45eacc2 100644
--- a/internal/hexaiaction/run.go
+++ b/internal/hexaiaction/run.go
@@ -6,11 +6,13 @@ import (
"io"
"log"
"strings"
+ "time"
"codeberg.org/snonux/hexai/internal/appconfig"
"codeberg.org/snonux/hexai/internal/editor"
"codeberg.org/snonux/hexai/internal/llmutils"
"codeberg.org/snonux/hexai/internal/logging"
+ "codeberg.org/snonux/hexai/internal/stats"
"codeberg.org/snonux/hexai/internal/tmux"
)
@@ -28,6 +30,9 @@ var selectedCustom *appconfig.CustomAction
func Run(ctx context.Context, stdin io.Reader, stdout, stderr io.Writer) error {
logger := log.New(stderr, "hexai-tmux-action ", log.LstdFlags|log.Lmsgprefix)
cfg := appconfig.Load(logger)
+ if cfg.StatsWindowMinutes > 0 {
+ stats.SetWindow(time.Duration(cfg.StatsWindowMinutes) * time.Minute)
+ }
if err := cfg.Validate(); err != nil {
fmt.Fprintf(stderr, logging.AnsiBase+"hexai-tmux-action: %v"+logging.AnsiReset+"\n", err)
return err