diff options
| author | Paul Buetow <paul@buetow.org> | 2026-03-02 14:21:21 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-03-02 14:21:21 +0200 |
| commit | 43456c479fe497ff1c857aadd66556863c81118c (patch) | |
| tree | a1ab4027f4def044d27252c0b1b57a641d634ea3 | |
| parent | 0928e675046fa5b4d4f2b030e7054cf91e864c41 (diff) | |
hexaiaction: use sectioned config interface instead of full App (task 409)
| -rw-r--r-- | internal/hexaiaction/config_view.go | 18 | ||||
| -rw-r--r-- | internal/hexaiaction/prompts.go | 64 | ||||
| -rw-r--r-- | internal/hexaiaction/run.go | 38 |
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) |
