summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-03-02 14:21:21 +0200
committerPaul Buetow <paul@buetow.org>2026-03-02 14:21:21 +0200
commit43456c479fe497ff1c857aadd66556863c81118c (patch)
treea1ab4027f4def044d27252c0b1b57a641d634ea3
parent0928e675046fa5b4d4f2b030e7054cf91e864c41 (diff)
hexaiaction: use sectioned config interface instead of full App (task 409)
-rw-r--r--internal/hexaiaction/config_view.go18
-rw-r--r--internal/hexaiaction/prompts.go64
-rw-r--r--internal/hexaiaction/run.go38
3 files changed, 74 insertions, 46 deletions
diff --git a/internal/hexaiaction/config_view.go b/internal/hexaiaction/config_view.go
new file mode 100644
index 0000000..127ef6f
--- /dev/null
+++ b/internal/hexaiaction/config_view.go
@@ -0,0 +1,18 @@
+package hexaiaction
+
+import "codeberg.org/snonux/hexai/internal/appconfig"
+
+// actionConfig narrows dependencies to the config sections needed by actions.
+type actionConfig interface {
+ CoreSection() appconfig.CoreConfig
+ ProviderSection() appconfig.ProviderConfig
+ PromptSection() appconfig.PromptConfig
+}
+
+func actionConfigAsApp(cfg actionConfig) appconfig.App {
+ app := appconfig.App{}
+ app.ApplyCoreSection(cfg.CoreSection())
+ app.ApplyProviderSection(cfg.ProviderSection())
+ app.ApplyPromptSection(cfg.PromptSection())
+ return app
+}
diff --git a/internal/hexaiaction/prompts.go b/internal/hexaiaction/prompts.go
index 7c134c5..8c67110 100644
--- a/internal/hexaiaction/prompts.go
+++ b/internal/hexaiaction/prompts.go
@@ -42,12 +42,13 @@ func canonicalProvider(name string) string {
return llmutils.CanonicalProvider(name)
}
-func selectActionTemperature(cfg appconfig.App, provider string, entry appconfig.SurfaceConfig, model string) (float64, bool) {
+func selectActionTemperature(cfg actionConfig, provider string, entry appconfig.SurfaceConfig, model string) (float64, bool) {
+ core := cfg.CoreSection()
if entry.Temperature != nil {
return *entry.Temperature, true
}
- if cfg.CodingTemperature != nil {
- temp := *cfg.CodingTemperature
+ if core.CodingTemperature != nil {
+ temp := *core.CodingTemperature
if provider == "openai" && strings.HasPrefix(strings.ToLower(model), "gpt-5") && temp == 0.2 {
temp = 1.0
}
@@ -59,13 +60,14 @@ func selectActionTemperature(cfg appconfig.App, provider string, entry appconfig
return 0, false
}
-func runRewrite(ctx context.Context, cfg appconfig.App, client chatDoer, instruction, selection string) (string, error) {
- sys := cfg.PromptCodeActionRewriteSystem
- user := Render(cfg.PromptCodeActionRewriteUser, map[string]string{"instruction": instruction, "selection": selection})
+func runRewrite(ctx context.Context, cfg actionConfig, client chatDoer, instruction, selection string) (string, error) {
+ prompts := cfg.PromptSection()
+ sys := prompts.PromptCodeActionRewriteSystem
+ user := Render(prompts.PromptCodeActionRewriteUser, map[string]string{"instruction": instruction, "selection": selection})
return runOnceWithOpts(ctx, client, sys, user, reqOptsFrom(cfg))
}
-func runDiagnostics(ctx context.Context, cfg appconfig.App, client chatDoer, diags []string, selection string) (string, error) {
+func runDiagnostics(ctx context.Context, cfg actionConfig, client chatDoer, diags []string, selection string) (string, error) {
var b strings.Builder
for i, d := range diags {
if strings.TrimSpace(d) == "" {
@@ -76,33 +78,38 @@ func runDiagnostics(ctx context.Context, cfg appconfig.App, client chatDoer, dia
b.WriteString("\n")
}
}
- sys := cfg.PromptCodeActionDiagnosticsSystem
- user := Render(cfg.PromptCodeActionDiagnosticsUser, map[string]string{"diagnostics": b.String(), "selection": selection})
+ prompts := cfg.PromptSection()
+ sys := prompts.PromptCodeActionDiagnosticsSystem
+ user := Render(prompts.PromptCodeActionDiagnosticsUser, map[string]string{"diagnostics": b.String(), "selection": selection})
return runOnceWithOpts(ctx, client, sys, user, reqOptsFrom(cfg))
}
-func runDocument(ctx context.Context, cfg appconfig.App, client chatDoer, selection string) (string, error) {
- sys := cfg.PromptCodeActionDocumentSystem
- user := Render(cfg.PromptCodeActionDocumentUser, map[string]string{"selection": selection})
+func runDocument(ctx context.Context, cfg actionConfig, client chatDoer, selection string) (string, error) {
+ prompts := cfg.PromptSection()
+ sys := prompts.PromptCodeActionDocumentSystem
+ user := Render(prompts.PromptCodeActionDocumentUser, map[string]string{"selection": selection})
return runOnceWithOpts(ctx, client, sys, user, reqOptsFrom(cfg))
}
-func runSimplify(ctx context.Context, cfg appconfig.App, client chatDoer, selection string) (string, error) {
- sys := cfg.PromptCodeActionSimplifySystem
- user := Render(cfg.PromptCodeActionSimplifyUser, map[string]string{"selection": selection})
+func runSimplify(ctx context.Context, cfg actionConfig, client chatDoer, selection string) (string, error) {
+ prompts := cfg.PromptSection()
+ sys := prompts.PromptCodeActionSimplifySystem
+ user := Render(prompts.PromptCodeActionSimplifyUser, map[string]string{"selection": selection})
return runOnceWithOpts(ctx, client, sys, user, reqOptsFrom(cfg))
}
-func runGoTest(ctx context.Context, cfg appconfig.App, client chatDoer, funcCode string) (string, error) {
- sys := cfg.PromptCodeActionGoTestSystem
- user := Render(cfg.PromptCodeActionGoTestUser, map[string]string{"function": funcCode})
+func runGoTest(ctx context.Context, cfg actionConfig, client chatDoer, funcCode string) (string, error) {
+ prompts := cfg.PromptSection()
+ sys := prompts.PromptCodeActionGoTestSystem
+ user := Render(prompts.PromptCodeActionGoTestUser, map[string]string{"function": funcCode})
return runOnceWithOpts(ctx, client, sys, user, reqOptsFrom(cfg))
}
-func runCustom(ctx context.Context, cfg appconfig.App, client chatDoer, ca appconfig.CustomAction, parts InputParts) (string, error) {
+func runCustom(ctx context.Context, cfg actionConfig, client chatDoer, ca appconfig.CustomAction, parts InputParts) (string, error) {
+ prompts := cfg.PromptSection()
// If user template is provided, prefer it and optional system
if strings.TrimSpace(ca.User) != "" {
- sys := cfg.PromptCodeActionRewriteSystem
+ sys := prompts.PromptCodeActionRewriteSystem
if strings.TrimSpace(ca.System) != "" {
sys = ca.System
}
@@ -181,15 +188,18 @@ func runOnceWithOpts(ctx context.Context, client chatDoer, sys, user string, req
}
// reqOptsFrom builds LLM request options similar to LSP behavior.
-func reqOptsFrom(cfg appconfig.App) requestArgs {
+func reqOptsFrom(cfg actionConfig) requestArgs {
+ core := cfg.CoreSection()
+ providers := cfg.ProviderSection()
+ fullCfg := actionConfigAsApp(cfg)
opts := make([]llm.RequestOption, 0, 3)
- if cfg.MaxTokens > 0 {
- opts = append(opts, llm.WithMaxTokens(cfg.MaxTokens))
+ if core.MaxTokens > 0 {
+ opts = append(opts, llm.WithMaxTokens(core.MaxTokens))
}
- provider := canonicalProvider(cfg.Provider)
- entries := cfg.CodeActionConfigs
+ provider := canonicalProvider(core.Provider)
+ entries := providers.CodeActionConfigs
if len(entries) == 0 {
- entries = []appconfig.SurfaceConfig{{Provider: cfg.Provider, Model: strings.TrimSpace(llmutils.DefaultModelForProvider(cfg, provider))}}
+ entries = []appconfig.SurfaceConfig{{Provider: core.Provider, Model: strings.TrimSpace(llmutils.DefaultModelForProvider(fullCfg, provider))}}
}
primary := entries[0]
if strings.TrimSpace(primary.Provider) != "" {
@@ -197,7 +207,7 @@ func reqOptsFrom(cfg appconfig.App) requestArgs {
}
model := strings.TrimSpace(primary.Model)
if model == "" {
- model = strings.TrimSpace(llmutils.DefaultModelForProvider(cfg, provider))
+ model = strings.TrimSpace(llmutils.DefaultModelForProvider(fullCfg, provider))
}
if strings.TrimSpace(primary.Model) != "" {
opts = append(opts, llm.WithModel(strings.TrimSpace(primary.Model)))
diff --git a/internal/hexaiaction/run.go b/internal/hexaiaction/run.go
index ffd31f1..625f40a 100644
--- a/internal/hexaiaction/run.go
+++ b/internal/hexaiaction/run.go
@@ -36,15 +36,15 @@ type actionPlan struct {
// CodeActionHandler builds a plan for an action and resolves it.
type CodeActionHandler interface {
- Build(parts InputParts, cfg appconfig.App, client chatDoer, stderr io.Writer) (actionPlan, bool)
+ Build(parts InputParts, cfg actionConfig, client chatDoer, stderr io.Writer) (actionPlan, bool)
Resolve(ctx context.Context, plan actionPlan) (string, error)
}
type codeActionHandler struct {
- build func(parts InputParts, cfg appconfig.App, client chatDoer, stderr io.Writer) (actionPlan, bool)
+ build func(parts InputParts, cfg actionConfig, client chatDoer, stderr io.Writer) (actionPlan, bool)
}
-func (h codeActionHandler) Build(parts InputParts, cfg appconfig.App, client chatDoer, stderr io.Writer) (actionPlan, bool) {
+func (h codeActionHandler) Build(parts InputParts, cfg actionConfig, client chatDoer, stderr io.Writer) (actionPlan, bool) {
if h.build == nil {
return actionPlan{}, false
}
@@ -126,7 +126,7 @@ func configPathFromContext(ctx context.Context) string {
return ""
}
-func executeAction(ctx context.Context, kind ActionKind, parts InputParts, cfg appconfig.App, client chatDoer, stderr io.Writer) (string, error) {
+func executeAction(ctx context.Context, kind ActionKind, parts InputParts, cfg actionConfig, client chatDoer, stderr io.Writer) (string, error) {
handler, ok := codeActionHandlers()[kind]
if !ok {
return parts.Selection, nil
@@ -151,11 +151,11 @@ func codeActionHandlers() map[ActionKind]CodeActionHandler {
}
}
-func buildSkipPlan(parts InputParts, _ appconfig.App, _ chatDoer, _ io.Writer) (actionPlan, bool) {
+func buildSkipPlan(parts InputParts, _ actionConfig, _ chatDoer, _ io.Writer) (actionPlan, bool) {
return actionPlan{fallback: parts.Selection}, true
}
-func buildRewritePlan(parts InputParts, cfg appconfig.App, client chatDoer, stderr io.Writer) (actionPlan, bool) {
+func buildRewritePlan(parts InputParts, cfg actionConfig, client chatDoer, stderr io.Writer) (actionPlan, bool) {
return actionPlan{
fallback: parts.Selection,
run: func(ctx context.Context) (string, error) {
@@ -164,7 +164,7 @@ func buildRewritePlan(parts InputParts, cfg appconfig.App, client chatDoer, stde
}, true
}
-func buildDiagnosticsPlan(parts InputParts, cfg appconfig.App, client chatDoer, _ io.Writer) (actionPlan, bool) {
+func buildDiagnosticsPlan(parts InputParts, cfg actionConfig, client chatDoer, _ io.Writer) (actionPlan, bool) {
return actionPlan{
fallback: parts.Selection,
run: func(ctx context.Context) (string, error) {
@@ -173,7 +173,7 @@ func buildDiagnosticsPlan(parts InputParts, cfg appconfig.App, client chatDoer,
}, true
}
-func buildDocumentPlan(parts InputParts, cfg appconfig.App, client chatDoer, _ io.Writer) (actionPlan, bool) {
+func buildDocumentPlan(parts InputParts, cfg actionConfig, client chatDoer, _ io.Writer) (actionPlan, bool) {
return actionPlan{
fallback: parts.Selection,
run: func(ctx context.Context) (string, error) {
@@ -182,7 +182,7 @@ func buildDocumentPlan(parts InputParts, cfg appconfig.App, client chatDoer, _ i
}, true
}
-func buildGoTestPlan(parts InputParts, cfg appconfig.App, client chatDoer, _ io.Writer) (actionPlan, bool) {
+func buildGoTestPlan(parts InputParts, cfg actionConfig, client chatDoer, _ io.Writer) (actionPlan, bool) {
return actionPlan{
fallback: parts.Selection,
run: func(ctx context.Context) (string, error) {
@@ -191,7 +191,7 @@ func buildGoTestPlan(parts InputParts, cfg appconfig.App, client chatDoer, _ io.
}, true
}
-func buildSimplifyPlan(parts InputParts, cfg appconfig.App, client chatDoer, _ io.Writer) (actionPlan, bool) {
+func buildSimplifyPlan(parts InputParts, cfg actionConfig, client chatDoer, _ io.Writer) (actionPlan, bool) {
return actionPlan{
fallback: parts.Selection,
run: func(ctx context.Context) (string, error) {
@@ -200,7 +200,7 @@ func buildSimplifyPlan(parts InputParts, cfg appconfig.App, client chatDoer, _ i
}, true
}
-func buildCustomPlan(parts InputParts, cfg appconfig.App, client chatDoer, _ io.Writer) (actionPlan, bool) {
+func buildCustomPlan(parts InputParts, cfg actionConfig, client chatDoer, _ io.Writer) (actionPlan, bool) {
return actionPlan{
fallback: parts.Selection,
run: func(ctx context.Context) (string, error) {
@@ -209,7 +209,7 @@ func buildCustomPlan(parts InputParts, cfg appconfig.App, client chatDoer, _ io.
}, true
}
-func buildCustomPromptPlan(parts InputParts, cfg appconfig.App, client chatDoer, stderr io.Writer) (actionPlan, bool) {
+func buildCustomPromptPlan(parts InputParts, cfg actionConfig, client chatDoer, stderr io.Writer) (actionPlan, bool) {
return actionPlan{
fallback: parts.Selection,
run: func(ctx context.Context) (string, error) {
@@ -218,7 +218,7 @@ func buildCustomPromptPlan(parts InputParts, cfg appconfig.App, client chatDoer,
}, true
}
-func handleRewriteAction(ctx context.Context, parts InputParts, cfg appconfig.App, client chatDoer, stderr io.Writer) (string, error) {
+func handleRewriteAction(ctx context.Context, parts InputParts, cfg actionConfig, client chatDoer, stderr io.Writer) (string, error) {
instr, cleaned := ExtractInstruction(parts.Selection)
if strings.TrimSpace(instr) == "" {
_, _ = fmt.Fprintln(stderr, logging.AnsiBase+"hexai-tmux-action: no inline instruction found; echoing input"+logging.AnsiReset)
@@ -229,31 +229,31 @@ func handleRewriteAction(ctx context.Context, parts InputParts, cfg appconfig.Ap
})
}
-func handleDiagnosticsAction(ctx context.Context, parts InputParts, cfg appconfig.App, client chatDoer) (string, error) {
+func handleDiagnosticsAction(ctx context.Context, parts InputParts, cfg actionConfig, client chatDoer) (string, error) {
return runWithTimeout(ctx, timeout10s, func(cctx context.Context) (string, error) {
return runDiagnostics(cctx, cfg, client, parts.Diagnostics, parts.Selection)
})
}
-func handleDocumentAction(ctx context.Context, parts InputParts, cfg appconfig.App, client chatDoer) (string, error) {
+func handleDocumentAction(ctx context.Context, parts InputParts, cfg actionConfig, client chatDoer) (string, error) {
return runWithTimeout(ctx, timeout10s, func(cctx context.Context) (string, error) {
return runDocument(cctx, cfg, client, parts.Selection)
})
}
-func handleGoTestAction(ctx context.Context, parts InputParts, cfg appconfig.App, client chatDoer) (string, error) {
+func handleGoTestAction(ctx context.Context, parts InputParts, cfg actionConfig, client chatDoer) (string, error) {
return runWithTimeout(ctx, timeout8s, func(cctx context.Context) (string, error) {
return runGoTest(cctx, cfg, client, parts.Selection)
})
}
-func handleSimplifyAction(ctx context.Context, parts InputParts, cfg appconfig.App, client chatDoer) (string, error) {
+func handleSimplifyAction(ctx context.Context, parts InputParts, cfg actionConfig, client chatDoer) (string, error) {
return runWithTimeout(ctx, timeout10s, func(cctx context.Context) (string, error) {
return runSimplify(cctx, cfg, client, parts.Selection)
})
}
-func handleCustomAction(ctx context.Context, parts InputParts, cfg appconfig.App, client chatDoer) (string, error) {
+func handleCustomAction(ctx context.Context, parts InputParts, cfg actionConfig, client chatDoer) (string, error) {
if selectedCustom == nil {
return parts.Selection, nil
}
@@ -264,7 +264,7 @@ func handleCustomAction(ctx context.Context, parts InputParts, cfg appconfig.App
})
}
-func handleCustomPromptAction(ctx context.Context, parts InputParts, cfg appconfig.App, client chatDoer, stderr io.Writer) (string, error) {
+func handleCustomPromptAction(ctx context.Context, parts InputParts, cfg actionConfig, client chatDoer, stderr io.Writer) (string, error) {
prompt, err := editor.OpenTempAndEdit(nil)
if err != nil || strings.TrimSpace(prompt) == "" {
_, _ = fmt.Fprintln(stderr, logging.AnsiBase+"hexai-tmux-action: custom prompt canceled or empty; echoing input"+logging.AnsiReset)