summaryrefslogtreecommitdiff
path: root/internal/hexaiaction/prompts.go
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-06-11 00:28:27 +0300
committerPaul Buetow <paul@buetow.org>2026-06-11 00:28:27 +0300
commitb4f06c656d8d8733a9bf2e2e2a5eb2a7a48389bb (patch)
tree7df0eac26ded2b4854225711dfde8f40d80a0400 /internal/hexaiaction/prompts.go
parent2b2f4110da53a03742faff89cdec17c55e091a90 (diff)
Extract shared chatrun package to eliminate chat-runner DRY violations
The CLI, LSP server and tmux code-action tool each carried near-identical chat-running logic: invoking the LLM (streaming-aware), collecting the response, and accounting sent/received bytes into the stats package. Introduce internal/chatrun with: - Invoke: streaming-aware LLM call that collects the full response and optionally mirrors chunks to a writer (nil writer = collect only). - SentBytes / Account: shared byte counting and stats.Update. Wire all three surfaces to it: - hexaicli: runChatRequest delegates to chatrun.Invoke; summarizeChatRun uses chatrun.Account. Removed the duplicated streaming/simple helpers. - hexaiaction: runOnce uses chatrun.Invoke + chatrun.Account, keeping the tmux status update local. - lsp: chatWithStats and the completion path use chatrun.SentBytes/Invoke; extracted unavailableClientError to keep chatWithStats small. chatrun has 100% test coverage; full suite passes with -race. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Diffstat (limited to 'internal/hexaiaction/prompts.go')
-rw-r--r--internal/hexaiaction/prompts.go19
1 files changed, 11 insertions, 8 deletions
diff --git a/internal/hexaiaction/prompts.go b/internal/hexaiaction/prompts.go
index 4b2d8be..c5e496b 100644
--- a/internal/hexaiaction/prompts.go
+++ b/internal/hexaiaction/prompts.go
@@ -6,6 +6,7 @@ import (
"time"
"codeberg.org/snonux/hexai/internal/appconfig"
+ "codeberg.org/snonux/hexai/internal/chatrun"
"codeberg.org/snonux/hexai/internal/llm"
"codeberg.org/snonux/hexai/internal/llmutils"
"codeberg.org/snonux/hexai/internal/stats"
@@ -115,25 +116,27 @@ func runCustom(ctx context.Context, cfg actionConfig, client chatDoer, ca appcon
// runOnce sends a single system+user prompt pair to the LLM, strips code
// fences from the response, records stats, and updates the tmux status line.
// Pass a zero-value requestArgs{} when no extra options are needed.
+//
+// The LLM invocation and byte/stats accounting are delegated to the shared
+// chatrun package so this surface stays in lock-step with the CLI and LSP. The
+// tmux status update is kept here because it is specific to the action tool's
+// reporting style.
func runOnce(ctx context.Context, client chatDoer, sys, user string, req requestArgs) (string, error) {
msgs := []llm.Message{{Role: "system", Content: sys}, {Role: "user", Content: user}}
- txt, err := client.Chat(ctx, msgs, req.options...)
+ // runOnce never streams to a writer, so pass nil out.
+ txt, err := chatrun.Invoke(ctx, client, msgs, req.options, nil)
if err != nil {
return "", err
}
out := strings.TrimSpace(StripFences(txt))
- // Contribute to global stats and update tmux status
- sent := 0
- for _, m := range msgs {
- sent += len(m.Content)
- }
- recv := len(out)
model := strings.TrimSpace(req.model)
if model == "" {
model = client.DefaultModel()
}
provider := providerOf(client)
- _ = stats.Update(ctx, provider, model, sent, recv)
+ // Account for the exchange against the post-fence-strip response so the
+ // recorded recv bytes match what the user actually receives.
+ chatrun.Account(ctx, provider, model, msgs, out)
if snap, err := stats.TakeSnapshot(); err == nil {
scopeReqs := snap.ScopeReqs(provider, model)
scopeRPM := snap.ScopeRPM(provider, model)