summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--PR.md1
-rw-r--r--README.md2
-rw-r--r--cmd/hexai/main.go4
-rw-r--r--config.toml.example11
-rw-r--r--docs/configuration.md8
-rw-r--r--docs/coverage.html9825
-rw-r--r--docs/coverage.out52849
-rw-r--r--internal/appconfig/config.go51
-rw-r--r--internal/appconfig/config_env_model_test.go8
-rw-r--r--internal/appconfig/config_test.go41
-rw-r--r--internal/hexaiaction/prompts.go4
-rw-r--r--internal/hexaiaction/prompts_more_test.go4
-rw-r--r--internal/hexaiaction/run_more_test.go2
-rw-r--r--internal/hexaicli/run.go8
-rw-r--r--internal/hexaicli/run_test.go20
-rw-r--r--internal/hexaicli/testhelpers_test.go1
-rw-r--r--internal/hexailsp/run.go10
-rw-r--r--internal/llm/copilot.go412
-rw-r--r--internal/llm/copilot_http_test.go276
-rw-r--r--internal/llm/copilot_test.go35
-rw-r--r--internal/llm/openai_temp_test.go6
-rw-r--r--internal/llm/provider.go15
-rw-r--r--internal/llm/provider_more2_test.go12
-rw-r--r--internal/llm/provider_more_test.go10
-rw-r--r--internal/llm/provider_test.go8
-rw-r--r--internal/llmutils/client.go9
-rw-r--r--internal/lsp/handlers_utils.go2
-rw-r--r--internal/lsp/llm_request_opts_test.go8
-rw-r--r--internal/lsp/server.go15
-rw-r--r--internal/runtimeconfig/store_test.go4
30 files changed, 28903 insertions, 34758 deletions
diff --git a/PR.md b/PR.md
index 50a5393..686c7ab 100644
--- a/PR.md
+++ b/PR.md
@@ -36,7 +36,6 @@ export HEXAI_REQUEST_TIMEOUT=120
- `internal/llm/openai.go` - Added `newOpenAIWithTimeout`
- `internal/llm/ollama.go` - Added `newOllamaWithTimeout`
- `internal/llm/openrouter.go` - Added `newOpenRouterWithTimeout`
-- `internal/llm/copilot.go` - Added `newCopilotWithTimeout`
- `internal/llm/anthropic.go` - Added `newAnthropicWithTimeout`
- `internal/hexailsp/run.go` - Pass `RequestTimeout` to `llm.Config`
- `internal/llmutils/client.go` - Pass `RequestTimeout` to `llm.Config`
diff --git a/README.md b/README.md
index 4ffc0ef..88ee031 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@ It has got improved capabilities for Go code understanding (for example, create
* Parallel completions and CLI responses from multiple providers/models for side-by-side comparison
* TUI AI code-action runner (`hexai-tmux-action`) with Bubble Tea
- Includes a “Custom prompt” action (hotkey `p`) that opens your editor (`$HEXAI_EDITOR` or `$EDITOR`) on a temporary Markdown file.
-* Support for OpenAI, OpenRouter, GitHub Copilot, Anthropic, and Ollama
+* Support for OpenAI, OpenRouter, Anthropic, and Ollama
## Documentation
diff --git a/cmd/hexai/main.go b/cmd/hexai/main.go
index 7caedc6..15fda42 100644
--- a/cmd/hexai/main.go
+++ b/cmd/hexai/main.go
@@ -100,8 +100,8 @@ func pickDefaultModel(cfg appconfig.App, provider string) string {
switch strings.ToLower(strings.TrimSpace(provider)) {
case "ollama":
return strings.TrimSpace(cfg.OllamaModel)
- case "copilot":
- return strings.TrimSpace(cfg.CopilotModel)
+ case "anthropic":
+ return strings.TrimSpace(cfg.AnthropicModel)
default:
return strings.TrimSpace(cfg.OpenAIModel)
}
diff --git a/config.toml.example b/config.toml.example
index cc34e04..bb8165d 100644
--- a/config.toml.example
+++ b/config.toml.example
@@ -52,8 +52,8 @@ chat_prefixes = ["?", "!", ":", ";"] # single-character items
# [[models.code_action]]
# # Only the first entry is used; extras are ignored with a warning.
-# provider = "copilot"
-# model = "gpt-4o"
+# provider = "anthropic"
+# model = "claude-3-5-sonnet"
# temperature = 0.4
# [[models.cli]]
@@ -62,7 +62,7 @@ chat_prefixes = ["?", "!", ":", ";"] # single-character items
# temperature = 0.6
[provider]
-name = "openai" # openai | openrouter | copilot | ollama | anthropic
+name = "openai" # openai | openrouter | anthropic | ollama
[openai]
model = "gpt-4.1"
@@ -74,11 +74,6 @@ model = "openrouter/auto"
base_url = "https://openrouter.ai/api/v1"
temperature = 0.2
-[copilot]
-model = "gpt-4o-mini"
-base_url = "https://api.githubcopilot.com"
-temperature = 0.2
-
[ollama]
model = "qwen3-coder:30b-a3b-q4_K_M"
base_url = "http://localhost:11434"
diff --git a/docs/configuration.md b/docs/configuration.md
index 54ac85f..f4469a9 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -20,7 +20,6 @@ Environment overrides
- `HEXAI_CHAT_SUFFIX`, `HEXAI_CHAT_PREFIXES` (comma-separated)
- `HEXAI_OPENAI_MODEL`, `HEXAI_OPENAI_BASE_URL`, `HEXAI_OPENAI_TEMPERATURE`
- `HEXAI_OPENROUTER_MODEL`, `HEXAI_OPENROUTER_BASE_URL`, `HEXAI_OPENROUTER_TEMPERATURE`
- - `HEXAI_COPILOT_MODEL`, `HEXAI_COPILOT_BASE_URL`, `HEXAI_COPILOT_TEMPERATURE`
- `HEXAI_OLLAMA_MODEL`, `HEXAI_OLLAMA_BASE_URL`, `HEXAI_OLLAMA_TEMPERATURE`
- Per-surface overrides: `HEXAI_MODEL_COMPLETION`, `HEXAI_MODEL_CODE_ACTION`, `HEXAI_MODEL_CHAT`, `HEXAI_MODEL_CLI`
- Per-surface temperatures: `HEXAI_TEMPERATURE_COMPLETION`, `HEXAI_TEMPERATURE_CODE_ACTION`, `HEXAI_TEMPERATURE_CHAT`, `HEXAI_TEMPERATURE_CLI`
@@ -37,7 +36,7 @@ Per-surface models
[models.code_action]
model = "gpt-4o"
- provider = "copilot"
+ provider = "openai"
temperature = 0.4
[models.cli]
@@ -47,7 +46,7 @@ Per-surface models
- Repeating the table (`[[models.<surface>]]`) configures multiple provider/model pairs. Completion requests and the Hexai CLI fan out to every configured entry concurrently and label the responses with `provider:model`. Code actions continue to use the first entry only; any extra [[models.code_action]] tables are ignored at runtime and the loader logs a warning so you know an additional entry was skipped.
-- When a per-surface value is omitted, Hexai falls back to the provider’s configured default. Temperatures inherit from `coding_temperature` unless explicitly set, and OpenAI `gpt-5*` models automatically raise an unspecified coding temperature to `1.0` for exploratory behavior. Provider overrides support `"openai"`, `"openrouter"`, `"copilot"`, or `"ollama"` and read the matching credential variables.
+- When a per-surface value is omitted, Hexai falls back to the provider’s configured default. Temperatures inherit from `coding_temperature` unless explicitly set, and OpenAI `gpt-5*` models automatically raise an unspecified coding temperature to `1.0` for exploratory behavior. Provider overrides support `"openai"`, `"openrouter"`, `"anthropic"`, or `"ollama"` and read the matching credential variables.
Runtime reloads
@@ -61,11 +60,10 @@ API keys:
- OpenAI: prefer `HEXAI_OPENAI_API_KEY`, falling back to `OPENAI_API_KEY`.
- OpenRouter: prefer `HEXAI_OPENROUTER_API_KEY`, falling back to `OPENROUTER_API_KEY`.
-- Copilot: prefer `HEXAI_COPILOT_API_KEY`, falling back to `COPILOT_API_KEY`.
Selecting a provider
-- Sectioned: set `[provider] name = "openai" | "openrouter" | "copilot" | "ollama"`.
+- Sectioned: set `[provider] name = "openai" | "openrouter" | "anthropic" | "ollama"`.
- If omitted, Hexai defaults to `openai`.
- Selecting `openrouter` uses https://openrouter.ai/api/v1 by default and automatically sends the required `HTTP-Referer` (`https://github.com/snonux/hexai`) and `X-Title` (`Hexai`) headers. Override the base URL via `[openrouter]` or environment variables when needed.
diff --git a/docs/coverage.html b/docs/coverage.html
deleted file mode 100644
index 4526ad1..0000000
--- a/docs/coverage.html
+++ /dev/null
@@ -1,9825 +0,0 @@
-
-<!DOCTYPE html>
-<html>
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
- <title>hexai-lsp: Go Coverage Report</title>
- <style>
- body {
- background: black;
- color: rgb(80, 80, 80);
- }
- body, pre, #legend span {
- font-family: Menlo, monospace;
- font-weight: bold;
- }
- #topbar {
- background: black;
- position: fixed;
- top: 0; left: 0; right: 0;
- height: 42px;
- border-bottom: 1px solid rgb(80, 80, 80);
- }
- #content {
- margin-top: 50px;
- }
- #nav, #legend {
- float: left;
- margin-left: 10px;
- }
- #legend {
- margin-top: 12px;
- }
- #nav {
- margin-top: 10px;
- }
- #legend span {
- margin: 0 5px;
- }
- .cov0 { color: rgb(192, 0, 0) }
-.cov1 { color: rgb(128, 128, 128) }
-.cov2 { color: rgb(116, 140, 131) }
-.cov3 { color: rgb(104, 152, 134) }
-.cov4 { color: rgb(92, 164, 137) }
-.cov5 { color: rgb(80, 176, 140) }
-.cov6 { color: rgb(68, 188, 143) }
-.cov7 { color: rgb(56, 200, 146) }
-.cov8 { color: rgb(44, 212, 149) }
-.cov9 { color: rgb(32, 224, 152) }
-.cov10 { color: rgb(20, 236, 155) }
-
- </style>
- </head>
- <body>
- <div id="topbar">
- <div id="nav">
- <select id="files">
-
- <option value="file0">codeberg.org/snonux/hexai/cmd/hexai-lsp/main.go (73.3%)</option>
-
- <option value="file1">codeberg.org/snonux/hexai/cmd/hexai-tmux-action/main.go (0.0%)</option>
-
- <option value="file2">codeberg.org/snonux/hexai/cmd/hexai/main.go (61.9%)</option>
-
- <option value="file3">codeberg.org/snonux/hexai/internal/appconfig/config.go (82.5%)</option>
-
- <option value="file4">codeberg.org/snonux/hexai/internal/editor/editor.go (58.3%)</option>
-
- <option value="file5">codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go (84.5%)</option>
-
- <option value="file6">codeberg.org/snonux/hexai/internal/hexaiaction/parse.go (92.6%)</option>
-
- <option value="file7">codeberg.org/snonux/hexai/internal/hexaiaction/prompts.go (92.3%)</option>
-
- <option value="file8">codeberg.org/snonux/hexai/internal/hexaiaction/run.go (71.1%)</option>
-
- <option value="file9">codeberg.org/snonux/hexai/internal/hexaiaction/tui.go (65.5%)</option>
-
- <option value="file10">codeberg.org/snonux/hexai/internal/hexaiaction/tui_custom.go (83.8%)</option>
-
- <option value="file11">codeberg.org/snonux/hexai/internal/hexaiaction/tui_delegate.go (100.0%)</option>
-
- <option value="file12">codeberg.org/snonux/hexai/internal/hexaicli/run.go (72.3%)</option>
-
- <option value="file13">codeberg.org/snonux/hexai/internal/hexailsp/run.go (88.9%)</option>
-
- <option value="file14">codeberg.org/snonux/hexai/internal/llm/copilot.go (82.4%)</option>
-
- <option value="file15">codeberg.org/snonux/hexai/internal/llm/ollama.go (89.8%)</option>
-
- <option value="file16">codeberg.org/snonux/hexai/internal/llm/openai.go (87.1%)</option>
-
- <option value="file17">codeberg.org/snonux/hexai/internal/llm/openrouter.go (76.2%)</option>
-
- <option value="file18">codeberg.org/snonux/hexai/internal/llm/provider.go (86.0%)</option>
-
- <option value="file19">codeberg.org/snonux/hexai/internal/llm/util.go (100.0%)</option>
-
- <option value="file20">codeberg.org/snonux/hexai/internal/llmutils/client.go (100.0%)</option>
-
- <option value="file21">codeberg.org/snonux/hexai/internal/logging/chatlogger.go (100.0%)</option>
-
- <option value="file22">codeberg.org/snonux/hexai/internal/logging/logging.go (90.9%)</option>
-
- <option value="file23">codeberg.org/snonux/hexai/internal/lsp/chat_commands.go (83.3%)</option>
-
- <option value="file24">codeberg.org/snonux/hexai/internal/lsp/context.go (74.4%)</option>
-
- <option value="file25">codeberg.org/snonux/hexai/internal/lsp/document.go (91.5%)</option>
-
- <option value="file26">codeberg.org/snonux/hexai/internal/lsp/handlers.go (89.8%)</option>
-
- <option value="file27">codeberg.org/snonux/hexai/internal/lsp/handlers_codeaction.go (82.0%)</option>
-
- <option value="file28">codeberg.org/snonux/hexai/internal/lsp/handlers_completion.go (79.0%)</option>
-
- <option value="file29">codeberg.org/snonux/hexai/internal/lsp/handlers_document.go (78.1%)</option>
-
- <option value="file30">codeberg.org/snonux/hexai/internal/lsp/handlers_execute.go (75.0%)</option>
-
- <option value="file31">codeberg.org/snonux/hexai/internal/lsp/handlers_init.go (66.7%)</option>
-
- <option value="file32">codeberg.org/snonux/hexai/internal/lsp/handlers_utils.go (85.3%)</option>
-
- <option value="file33">codeberg.org/snonux/hexai/internal/lsp/server.go (82.1%)</option>
-
- <option value="file34">codeberg.org/snonux/hexai/internal/lsp/transport.go (73.0%)</option>
-
- <option value="file35">codeberg.org/snonux/hexai/internal/runtimeconfig/store.go (88.1%)</option>
-
- <option value="file36">codeberg.org/snonux/hexai/internal/stats/lock_posix.go (83.3%)</option>
-
- <option value="file37">codeberg.org/snonux/hexai/internal/stats/stats.go (75.8%)</option>
-
- <option value="file38">codeberg.org/snonux/hexai/internal/testutil/fixtures.go (100.0%)</option>
-
- <option value="file39">codeberg.org/snonux/hexai/internal/textutil/human.go (92.3%)</option>
-
- <option value="file40">codeberg.org/snonux/hexai/internal/textutil/textutil.go (90.4%)</option>
-
- <option value="file41">codeberg.org/snonux/hexai/internal/tmux/status.go (76.7%)</option>
-
- <option value="file42">codeberg.org/snonux/hexai/internal/tmux/tmux.go (88.6%)</option>
-
- </select>
- </div>
- <div id="legend">
- <span>not tracked</span>
-
- <span class="cov0">no coverage</span>
- <span class="cov1">low coverage</span>
- <span class="cov2">*</span>
- <span class="cov3">*</span>
- <span class="cov4">*</span>
- <span class="cov5">*</span>
- <span class="cov6">*</span>
- <span class="cov7">*</span>
- <span class="cov8">*</span>
- <span class="cov9">*</span>
- <span class="cov10">high coverage</span>
-
- </div>
- </div>
- <div id="content">
-
- <pre class="file" id="file0" style="display: none">// Summary: Hexai LSP entrypoint; parses flags and delegates to internal/hexailsp.
-package main
-
-import (
- "flag"
- "fmt"
- "log"
- "os"
- "strings"
-
- "codeberg.org/snonux/hexai/internal"
- "codeberg.org/snonux/hexai/internal/appconfig"
- "codeberg.org/snonux/hexai/internal/hexailsp"
-)
-
-func main() <span class="cov8" title="1">{
- logPath := flag.String("log", "/tmp/hexai-lsp.log", "path to log file (optional)")
- defaultCfg := defaultConfigPath()
- configPath := flag.String("config", "", fmt.Sprintf("path to config file (default: %s)", defaultCfg))
- showVersion := flag.Bool("version", false, "print version and exit")
- flag.Parse()
- if *showVersion </span><span class="cov8" title="1">{
- log.Println(internal.Version)
- return
- }</span>
-
- <span class="cov0" title="0">path := strings.TrimSpace(*configPath)
- if err := hexailsp.RunWithConfig(*logPath, path, os.Stdin, os.Stdout, os.Stderr); err != nil </span><span class="cov0" title="0">{
- log.Fatalf("server error: %v", err)
- }</span>
-}
-
-func defaultConfigPath() string <span class="cov8" title="1">{
- path, err := appconfig.ConfigPath()
- if err != nil </span><span class="cov0" title="0">{
- return "$XDG_CONFIG_HOME/hexai/config.toml"
- }</span>
- <span class="cov8" title="1">return path</span>
-}
-</pre>
-
- <pre class="file" id="file1" style="display: none">package main
-
-import (
- "context"
- "flag"
- "fmt"
- "os"
- "strings"
-
- "codeberg.org/snonux/hexai/internal/appconfig"
- "codeberg.org/snonux/hexai/internal/hexaiaction"
-)
-
-func main() <span class="cov0" title="0">{
- infile := flag.String("infile", "", "Read input from this file instead of stdin")
- outfile := flag.String("outfile", "", "Write output to this file instead of stdout")
- uiChild := flag.Bool("ui-child", false, "INTERNAL: run interactive UI and write to -outfile atomically")
- defaultPath := defaultConfigPath()
- configPath := flag.String("config", "", fmt.Sprintf("path to config file (default: %s)", defaultPath))
- tmuxTarget := flag.String("tmux-target", "", "tmux split target (advanced)")
- tmuxSplit := flag.String("tmux-split", "v", "tmux split orientation: v or h")
- tmuxPercent := flag.Int("tmux-percent", 33, "tmux split size percentage (1-100)")
- flag.Parse()
-
- opts := hexaiaction.Options{
- Infile: *infile, Outfile: *outfile,
- UIChild: *uiChild, TmuxTarget: *tmuxTarget, TmuxSplit: *tmuxSplit, TmuxPercent: *tmuxPercent,
- }
- ctx := context.Background()
- if path := strings.TrimSpace(*configPath); path != "" </span><span class="cov0" title="0">{
- ctx = hexaiaction.WithConfigPath(ctx, path)
- }</span>
- <span class="cov0" title="0">if err := hexaiaction.RunCommand(ctx, opts, os.Stdin, os.Stdout, os.Stderr); err != nil </span><span class="cov0" title="0">{
- fmt.Fprintln(os.Stderr, err)
- os.Exit(1)
- }</span>
-}
-
-func defaultConfigPath() string <span class="cov0" title="0">{
- path, err := appconfig.ConfigPath()
- if err != nil </span><span class="cov0" title="0">{
- return "$XDG_CONFIG_HOME/hexai/config.toml"
- }</span>
- <span class="cov0" title="0">return path</span>
-}
-</pre>
-
- <pre class="file" id="file2" style="display: none">// Summary: Hexai CLI entrypoint; parses flags and delegates to internal/hexaicli.
-package main
-
-import (
- "context"
- "flag"
- "fmt"
- "io"
- "log"
- "os"
- "strconv"
- "strings"
-
- "codeberg.org/snonux/hexai/internal"
- "codeberg.org/snonux/hexai/internal/appconfig"
- "codeberg.org/snonux/hexai/internal/hexaicli"
-)
-
-func main() <span class="cov8" title="1">{
- configPath, remaining := splitConfigPath(os.Args[1:])
- logger := log.New(io.Discard, "", 0)
- cfg := appconfig.LoadWithOptions(logger, appconfig.LoadOptions{ConfigPath: configPath})
- cliEntries := cfg.CLIConfigs
- if len(cliEntries) == 0 </span><span class="cov8" title="1">{
- cliEntries = []appconfig.SurfaceConfig{{Provider: cfg.Provider}}
- }</span>
- <span class="cov8" title="1">fs := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
- defaultPath := defaultConfigPath()
- configFlag := fs.String("config", configPath, fmt.Sprintf("path to config file (default: %s)", defaultPath))
- showVersion := fs.Bool("version", false, "print version and exit")
- selectedFlags := make([]bool, len(cliEntries))
- for i, entry := range cliEntries </span><span class="cov8" title="1">{
- name := strconv.Itoa(i)
- provider := strings.TrimSpace(entry.Provider)
- if provider == "" </span><span class="cov8" title="1">{
- provider = cfg.Provider
- }</span>
- <span class="cov8" title="1">model := strings.TrimSpace(entry.Model)
- if model == "" </span><span class="cov8" title="1">{
- model = pickDefaultModel(cfg, provider)
- }</span>
- <span class="cov8" title="1">desc := fmt.Sprintf("use only provider #%d (%s:%s)", i, provider, model)
- fs.BoolVar(&amp;selectedFlags[i], name, false, desc)</span>
- }
- <span class="cov8" title="1">_ = fs.Parse(remaining)
- if *showVersion </span><span class="cov8" title="1">{
- fmt.Fprintln(os.Stdout, internal.Version)
- return
- }</span>
- <span class="cov0" title="0">var selection []int
- for i, sel := range selectedFlags </span><span class="cov0" title="0">{
- if sel </span><span class="cov0" title="0">{
- selection = append(selection, i)
- }</span>
- }
- <span class="cov0" title="0">finalPath := strings.TrimSpace(*configFlag)
- if finalPath == "" </span><span class="cov0" title="0">{
- finalPath = configPath
- }</span>
- <span class="cov0" title="0">ctx := context.Background()
- if finalPath != "" </span><span class="cov0" title="0">{
- ctx = hexaicli.WithCLIConfigPath(ctx, finalPath)
- }</span>
- <span class="cov0" title="0">if len(selection) &gt; 0 </span><span class="cov0" title="0">{
- ctx = hexaicli.WithCLISelection(ctx, selection)
- }</span>
- <span class="cov0" title="0">if err := hexaicli.Run(ctx, fs.Args(), os.Stdin, os.Stdout, os.Stderr); err != nil </span><span class="cov0" title="0">{
- os.Exit(1)
- }</span>
-}
-
-func splitConfigPath(args []string) (string, []string) <span class="cov8" title="1">{
- var path string
- rest := make([]string, 0, len(args))
- skip := false
- for i := 0; i &lt; len(args); i++ </span><span class="cov8" title="1">{
- if skip </span><span class="cov0" title="0">{
- skip = false
- continue</span>
- }
- <span class="cov8" title="1">arg := args[i]
- switch </span>{
- case arg == "--config" || arg == "-config":<span class="cov0" title="0">
- if i+1 &lt; len(args) </span><span class="cov0" title="0">{
- path = args[i+1]
- skip = true
- }</span>
- case strings.HasPrefix(arg, "--config="):<span class="cov0" title="0">
- path = arg[len("--config="):]</span>
- case strings.HasPrefix(arg, "-config="):<span class="cov0" title="0">
- path = arg[len("-config="):]</span>
- default:<span class="cov8" title="1">
- rest = append(rest, arg)</span>
- }
- }
- <span class="cov8" title="1">return strings.TrimSpace(path), rest</span>
-}
-
-func pickDefaultModel(cfg appconfig.App, provider string) string <span class="cov8" title="1">{
- switch strings.ToLower(strings.TrimSpace(provider)) </span>{
- case "ollama":<span class="cov0" title="0">
- return strings.TrimSpace(cfg.OllamaModel)</span>
- case "copilot":<span class="cov0" title="0">
- return strings.TrimSpace(cfg.CopilotModel)</span>
- default:<span class="cov8" title="1">
- return strings.TrimSpace(cfg.OpenAIModel)</span>
- }
-}
-
-func defaultConfigPath() string <span class="cov8" title="1">{
- cfgPath, err := appconfig.ConfigPath()
- if err != nil </span><span class="cov0" title="0">{
- return "$XDG_CONFIG_HOME/hexai/config.toml"
- }</span>
- <span class="cov8" title="1">return cfgPath</span>
-}
-</pre>
-
- <pre class="file" id="file3" style="display: none">// Summary: Application configuration model and loader; reads ~/.config/hexai/config.toml and merges defaults.
-package appconfig
-
-import (
- "fmt"
- "log"
- "os"
- "path/filepath"
- "slices"
- "strconv"
- "strings"
-
- "github.com/pelletier/go-toml/v2"
-)
-
-// SurfaceConfig describes a provider/model pairing (with optional temperature).
-type SurfaceConfig struct {
- Provider string
- Model string
- Temperature *float64
-}
-
-// App holds user-configurable settings read from ~/.config/hexai/config.toml.
-type App struct {
- MaxTokens int `json:"max_tokens" toml:"max_tokens"`
- ContextMode string `json:"context_mode" toml:"context_mode"`
- ContextWindowLines int `json:"context_window_lines" toml:"context_window_lines"`
- MaxContextTokens int `json:"max_context_tokens" toml:"max_context_tokens"`
- LogPreviewLimit int `json:"log_preview_limit" toml:"log_preview_limit"`
- // Single knob for LSP requests; if set, overrides hardcoded temps in LSP.
- CodingTemperature *float64 `json:"coding_temperature" toml:"coding_temperature"`
- // Minimum identifier characters required for manual (TriggerKind=1) invoke
- // to proceed without structural triggers. 0 means always allow.
- ManualInvokeMinPrefix int `json:"manual_invoke_min_prefix" toml:"manual_invoke_min_prefix"`
-
- // Completion debounce in milliseconds. When &gt; 0, the server waits until
- // there has been no text change for at least this duration before sending
- // an LLM completion request.
- CompletionDebounceMs int `json:"completion_debounce_ms" toml:"completion_debounce_ms"`
- // Completion throttle in milliseconds. When &gt; 0, caps the minimum spacing
- // between LLM requests (both chat and code-completer paths).
- CompletionThrottleMs int `json:"completion_throttle_ms" toml:"completion_throttle_ms"`
-
- TriggerCharacters []string `json:"trigger_characters" toml:"trigger_characters"`
- Provider string `json:"provider" toml:"provider"`
-
- // Inline prompt trigger characters (default: &gt;!text&gt; and &gt;&gt;!text&gt;)
- InlineOpen string `json:"inline_open" toml:"inline_open"`
- InlineClose string `json:"inline_close" toml:"inline_close"`
- // In-editor chat triggers (default: suffix "&gt;" after one of [?, !, :, ;])
- ChatSuffix string `json:"chat_suffix" toml:"chat_suffix"`
- ChatPrefixes []string `json:"chat_prefixes" toml:"chat_prefixes"`
-
- // Provider-specific options
- OpenAIBaseURL string `json:"openai_base_url" toml:"openai_base_url"`
- OpenAIModel string `json:"openai_model" toml:"openai_model"`
- // Default temperature for OpenAI requests (nil means use provider default)
- OpenAITemperature *float64 `json:"openai_temperature" toml:"openai_temperature"`
- OpenRouterBaseURL string `json:"openrouter_base_url" toml:"openrouter_base_url"`
- OpenRouterModel string `json:"openrouter_model" toml:"openrouter_model"`
- // Default temperature for OpenRouter requests (nil means use provider default)
- OpenRouterTemperature *float64 `json:"openrouter_temperature" toml:"openrouter_temperature"`
- OllamaBaseURL string `json:"ollama_base_url" toml:"ollama_base_url"`
- OllamaModel string `json:"ollama_model" toml:"ollama_model"`
- // Default temperature for Ollama requests (nil means use provider default)
- OllamaTemperature *float64 `json:"ollama_temperature" toml:"ollama_temperature"`
- CopilotBaseURL string `json:"copilot_base_url" toml:"copilot_base_url"`
- CopilotModel string `json:"copilot_model" toml:"copilot_model"`
- // Default temperature for Copilot requests (nil means use provider default)
- CopilotTemperature *float64 `json:"copilot_temperature" toml:"copilot_temperature"`
-
- // Per-surface provider/model configurations (ordered; first entry is primary)
- CompletionConfigs []SurfaceConfig `json:"-" toml:"-"`
- CodeActionConfigs []SurfaceConfig `json:"-" toml:"-"`
- ChatConfigs []SurfaceConfig `json:"-" toml:"-"`
- CLIConfigs []SurfaceConfig `json:"-" toml:"-"`
-
- // Prompt templates (configured only via file; no env overrides)
- // Completion/chat/code action/CLI prompt strings. See config.toml.example for placeholders.
- // Completion
- PromptCompletionSystemGeneral string `json:"-" toml:"-"`
- PromptCompletionSystemParams string `json:"-" toml:"-"`
- PromptCompletionSystemInline string `json:"-" toml:"-"`
- PromptCompletionUserGeneral string `json:"-" toml:"-"`
- PromptCompletionUserParams string `json:"-" toml:"-"`
- PromptCompletionExtraHeader string `json:"-" toml:"-"`
- // Provider-native code-completer
- PromptNativeCompletion string `json:"-" toml:"-"`
- // In-editor chat
- PromptChatSystem string `json:"-" toml:"-"`
- // Code actions
- PromptCodeActionRewriteSystem string `json:"-" toml:"-"`
- PromptCodeActionDiagnosticsSystem string `json:"-" toml:"-"`
- PromptCodeActionDocumentSystem string `json:"-" toml:"-"`
- PromptCodeActionRewriteUser string `json:"-" toml:"-"`
- PromptCodeActionDiagnosticsUser string `json:"-" toml:"-"`
- PromptCodeActionDocumentUser string `json:"-" toml:"-"`
- PromptCodeActionGoTestSystem string `json:"-" toml:"-"`
- PromptCodeActionGoTestUser string `json:"-" toml:"-"`
- PromptCodeActionSimplifySystem string `json:"-" toml:"-"`
- PromptCodeActionSimplifyUser string `json:"-" toml:"-"`
- // CLI
- PromptCLIDefaultSystem string `json:"-" toml:"-"`
- PromptCLIExplainSystem string `json:"-" toml:"-"`
-
- // Custom code actions and tmux integration
- CustomActions []CustomAction `json:"-" toml:"-"`
- TmuxCustomMenuHotkey string `json:"-" toml:"-"`
- // Stats
- StatsWindowMinutes int `json:"-" toml:"-"`
-}
-
-// CustomAction describes a user-defined code action.
-type CustomAction struct {
- ID string
- Title string
- Kind string // optional; default "refactor"
- Scope string // "selection" (default) | "diagnostics"
- Hotkey string // optional, used by tmux submenu
- Instruction string // optional; if set and User is empty, use global rewrite templates
- System string // optional; used only when User is set
- User string // optional; if set, render with available vars
-}
-
-// Constructor: defaults for App (kept first among functions)
-func newDefaultConfig() App <span class="cov5" title="51">{
- // Coding-friendly default temperature across providers
- // Users can override per provider in config.toml (including 0.0).
- t := 0.2
- return App{
- MaxTokens: 4000,
- ContextMode: "always-full",
- ContextWindowLines: 120,
- MaxContextTokens: 4000,
- LogPreviewLimit: 100,
- CodingTemperature: &amp;t,
- OpenAITemperature: &amp;t,
- OllamaTemperature: &amp;t,
- CopilotTemperature: &amp;t,
- ManualInvokeMinPrefix: 0,
- CompletionDebounceMs: 800,
- CompletionThrottleMs: 0,
- // Inline/chat trigger defaults
- InlineOpen: "&gt;!",
- InlineClose: "&gt;",
- ChatSuffix: "&gt;",
- ChatPrefixes: []string{"?", "!", ":", ";"},
-
- // Default prompt templates (match current hard-coded strings)
- PromptCompletionSystemParams: "You are a code completion engine for function signatures. Return only the parameter list contents (without parentheses), no braces, no prose. Prefer idiomatic names and types.",
- PromptCompletionUserParams: "Cursor is inside the function parameter list. Suggest only the parameter list (no parentheses).\nFunction line: {{function}}\nCurrent line (cursor at {{char}}): {{current}}",
- PromptCompletionSystemGeneral: "You are a terse code completion engine. Return only the code to insert, no surrounding prose or backticks. Only continue from the cursor; never repeat characters already present to the left of the cursor on the current line (e.g., if 'name :=' is already typed, only return the right-hand side expression).",
- PromptCompletionUserGeneral: "Provide the next likely code to insert at the cursor.\nFile: {{file}}\nFunction/context: {{function}}\nAbove line: {{above}}\nCurrent line (cursor at character {{char}}): {{current}}\nBelow line: {{below}}\nOnly return the completion snippet.",
- PromptCompletionSystemInline: "You are a precise code completion/refactoring engine. Output only the code to insert with no prose, no comments, and no backticks. Return raw code only.",
- PromptCompletionExtraHeader: "Additional context:\n{{context}}",
-
- PromptNativeCompletion: "// Path: {{path}}\n{{before}}",
-
- PromptChatSystem: "You are a helpful coding assistant. Answer concisely and clearly.",
-
- PromptCodeActionRewriteSystem: "You are a precise code refactoring engine. Rewrite the given code strictly according to the instruction. Return only the updated code with no prose or backticks. Preserve formatting where reasonable.",
- PromptCodeActionDiagnosticsSystem: "You are a precise code fixer. Resolve the given diagnostics by editing only the selected code. Return only the co