diff options
| author | Paul Buetow <paul@buetow.org> | 2025-09-17 21:33:45 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2025-09-17 21:33:45 +0300 |
| commit | 88103657fb230bb41217a06aa5602ae23e7acb8b (patch) | |
| tree | 524c437e4e40ee5d6713b6ea5414ad975654cc52 | |
| parent | 2b6232704ecc90630196b9f829f966533e5cdccd (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
| -rw-r--r-- | AGENTS.md | 8 | ||||
| -rw-r--r-- | MAKEITSO.md | 131 | ||||
| -rw-r--r-- | Magefile.go | 23 | ||||
| -rw-r--r-- | PROJECTSTATUS.md | 23 | ||||
| -rw-r--r-- | SCRATCHPAD.md | 10 | ||||
| -rw-r--r-- | config.toml.example | 5 | ||||
| -rw-r--r-- | docs/coverage.html | 1215 | ||||
| -rw-r--r-- | docs/coverage.out | 29536 | ||||
| -rw-r--r-- | docs/tmux.md | 20 | ||||
| -rw-r--r-- | internal/appconfig/config.go | 15 | ||||
| -rw-r--r-- | internal/hexaiaction/prompts.go | 45 | ||||
| -rw-r--r-- | internal/hexaiaction/prompts_simplify_test.go | 27 | ||||
| -rw-r--r-- | internal/hexaiaction/run.go | 5 | ||||
| -rw-r--r-- | internal/hexaicli/run.go | 30 | ||||
| -rw-r--r-- | internal/hexailsp/run.go | 5 | ||||
| -rw-r--r-- | internal/lsp/handlers_completion.go | 5 | ||||
| -rw-r--r-- | internal/lsp/handlers_utils.go | 31 | ||||
| -rw-r--r-- | internal/stats/debugstring_test.go | 22 | ||||
| -rw-r--r-- | internal/stats/stats.go | 247 | ||||
| -rw-r--r-- | internal/stats/stats_test.go | 85 | ||||
| -rw-r--r-- | internal/tmux/status.go | 96 | ||||
| -rw-r--r-- | internal/tmux/status_more_test.go | 62 |
22 files changed, 18516 insertions, 13130 deletions
@@ -18,3 +18,11 @@ - Filenames: docs use `lowercase-with-dashes.md`; images use kebab‑case with size/purpose suffix (e.g., `hexai-small.png`). - Code (when added): follow language idioms - Any type with more than 3 methods should be in it's own source code file, whereas the filename contains the name of the type. + + +## Incrementing version + +- Never draft a changelog entry +- Whenever incrementing the version, update the version number in the project, commit to git, tag the version and push to git. +- When a major feature was introduced, increment ?.X.? +- When only minor changes were done or only bugs were fixed, increment the version as ?.?.X diff --git a/MAKEITSO.md b/MAKEITSO.md new file mode 100644 index 0000000..81c68c7 --- /dev/null +++ b/MAKEITSO.md @@ -0,0 +1,131 @@ +## Global Hexai LLM Stats (Plan) + +### Goals +- Unify LLM usage stats across all Hexai processes: `hexai-lsp`, `hexai` (CLI), and `hexai-tmux-action`). +- Persist stats on disk so concurrent processes contribute to a single, shared view. +- Show consistent stats in logs and in the tmux status line regardless of which binary triggered the last request. +- Track both per-provider:model and global totals; include request count and total bytes sent/received; compute RPM. +- Always display stats for a sliding recent window (default: last 1 hour). + +### Non-Goals (for this iteration) +- No networked metrics backends, no long-term history beyond recent minutes needed to compute RPM. +- No user-facing commands to reset/export stats (can be a follow-up). + +### Cache Location and Layout +- Directory: `XDG_CACHE_HOME/hexai` (fallback to `~/.cache/hexai` when `XDG_CACHE_HOME` is unset). +- File: `stats.json` (atomically written via temp file + rename). +- Schema (v1): + { + "version": 1, + "updated_at": "RFC3339", + "window_seconds": 3600, + "events": [ + { "ts": "RFC3339Nano", "provider": "openai", "model": "gpt-4.1", "sent": 1234, "recv": 5678 } + ] + } +- Notes: + - Array-like append-only event list with periodic compaction: on update, drop entries older than `window_seconds` (default 3600 seconds = 1h). + - Aggregations (global totals, per provider/model, RPM) are computed on read from events within the current window only. + - Keep file size bounded: compact (prune + optionally coalesce older sub-minute events into minute buckets) when length exceeds a threshold (e.g., 10k events) or on a time basis. + +### Concurrency & File Locking +- Use advisory file locks for Unix-like systems. + - Create/`open` lock file `stats.lock` in the same cache directory. + - Apply `flock(LOCK_EX)` (via `syscall`/`golang.org/x/sys/unix`) around the read-modify-write cycle. + - Ensure the lock file is held for the shortest duration (milliseconds). +- Atomic update: + - Read existing `stats.json` (if missing, start with empty events and default window). + - Append one event for the just-finished request; prune entries older than `window_seconds` (relative to now). + - Write to `stats.json.tmp`, `fsync`, then `rename` to `stats.json`. +- Retry strategy: + - Bounded retries with small backoff if lock acquisition or IO fails; log a single warning and continue without crashing. + +### Package Design +- New package: `internal/stats` + - `func Update(ctx, provider, model string, sentBytes, recvBytes int) error` (append event, prune old). + - `func Snapshot(ctx context.Context) (S, error)` to read current state (aggregate from events within window). + - `func RPM(s S) float64` computes requests/minute over the configured window. + - `func SetWindow(d time.Duration)` and `func Window() time.Duration` to configure the window (default 1h; read from `config.toml`). + - `func CacheDir() (string, error)` honoring XDG; `func Path() string` for `stats.json`. + - Careful with allocations and zero/empty-state handling. +- Types: + - `type Event struct { TS time.Time; Provider, Model string; Sent, Recv int64 }` + - `type StatFile struct { Version int; UpdatedAt time.Time; WindowSeconds int; Events []Event }` + - Aggregated snapshot (in-memory): + - `type Counters struct { Reqs int64; Sent int64; Recv int64 }` + - `type ProviderEntry struct { Totals Counters; Models map[string]Counters }` + - `type Snapshot struct { Global Counters; Providers map[string]ProviderEntry; RPM float64; Window time.Duration }` + +### Integration Points +- Common approach: update stats exactly where we already compute per-process counters. + +1) LSP (`internal/lsp`) +- Hook at the end of: + - `chatWithStats`: after successful Chat, call `stats.Update(provider, model, sentBytes, recvBytes)`. + - Provider-native completion path: when we get suggestions, also update using `sentBytes` and received bytes of first suggestion (consistent with current local counters). +- After update, read a `Snapshot` (window-aware by design) and compute: + - Per current provider:model totals (for context), and global totals over the last window (default 1h). + - RPM computed from events in the current window. +- Display: + - Logs: extend existing LLM stats line to include Σ (global) view. + - tmux: replace current status with a compact global view, e.g.: + - `⏳ Σ reqs=123 rpm=4.2 ↑1.2MB ↓3.4MB | openai:gpt-4.1 reqs=80 rpm=3.1`. + - Use `tmux.FormatLLMStatsStatusColoredGlobal(...)` (new) to render. + +2) CLI (`cmd/hexai`/`internal/hexaicli`) +- Where Chat is invoked (current CLI flow calls LLM directly): wrap the LLM client or count bytes and call `stats.Update` after each request. +- Print a one-line summary to stderr (consistent with LSP logging format). + +3) Tmux Action (`cmd/hexai-tmux-action` / `internal/hexaiaction`) +- In the code paths that call `client.Chat` (runOnce / runOnceWithOpts), after success call `stats.Update`. +- Update tmux status the same way as LSP by reusing the same formatter function in `internal/tmux`. + +### Tmux Status API +- Extend `internal/tmux` with a new helper: + - `func FormatGlobalStatsStatusColored(s stats.Snapshot, preferProvider, preferModel string) string` (include window indicator like `Σ@1h`). + - Or a smaller data struct extracted from snapshot to avoid leaking types. +- Keep existing `FormatLLMStatsStatusColored` for backward compatibility; LSP/CLI/TUI all switch to the new global formatter. + +### Logging +- Reuse existing logging but compute and append global counters: + - `llm stats reqs=local avg_sent=... rpm_local=... | Σ reqs=... rpm=... sent_total=... recv_total=...` +- Keep logs short to avoid noise; gate with existing log level. + +### Configuration +- New section in `config.toml`: + - `[stats] window_minutes = 60` (default 60; min 1, max 1440) + - All displays and RPM calculations operate over this sliding window. + +### Error Handling +- Stats update failures must never fail the user-facing operation. +- Log at `info` once per process when disk write fails and then mute repeated errors for a cooldown period. + +- Unit tests for `internal/stats`: + - Cache dir resolution (XDG vs HOME). + - Locking: concurrent goroutines updating stats in a temp XDG cache dir; assert totals match expected; ensure no partial writes. + - Event pruning (older than window) and RPM calculation over the configured window. + - JSON round-trip and version field. +- Integration tests (lightweight): + - Override `XDG_CACHE_HOME` to a temp directory. + - Simulate 2 processes: spawn subtests that call `stats.Update` interleaved; assert final snapshot. + - LSP and hexaiaction: hook fakes that perform `Chat` and then verify `stats.Snapshot` reflects the calls. + +### Migration / Backward Compatibility +- On first run, create cache dir and empty stats file lazily under lock. +- If file is invalid JSON or version mismatch, start from zero and overwrite. + +### Rollout Plan +- [x] Scaffold `internal/stats` with types, JSON read/write, cache dir, and lock helpers (Unix). +- [x] Implement `Update()` with lock → read → mutate → write (atomic) and pruning. +- [x] Implement `Snapshot()` and helpers to compute aggregates and RPM over the configured window (pruning done; optional compaction TBD). +- [x] Add tmux formatter in `internal/tmux` to display global stats (compact view). +- [x] Integrate LSP: update stats in `chatWithStats` and provider-native path; use global snapshot for tmux status. +- [x] Integrate CLI and Tmux Action: update stats after each Chat; stderr/tmux show global view. +- [x] Add tests for `internal/stats` (window pruning, concurrency, XDG path). +- [x] Run mage Coverage and update docs/screenshots if needed. +- [x] Verify all LLM call paths contribute to the new stats mechanism. + +### Estimation & Risks +- Est. 4–6 hours including tests and integration. +- Risk: file locking portability (Linux/macOS OK with flock). Mitigation: implement Unix only now; detect/disable gracefully elsewhere. +- Risk: tmux status width. Mitigation: show Σ-only by default and elide per-model when narrow (or truncate labels). diff --git a/Magefile.go b/Magefile.go index b297a9d..fdf5389 100644 --- a/Magefile.go +++ b/Magefile.go @@ -109,8 +109,8 @@ func RunTmuxAction() error { // printCoverage prints a warning if an existing coverage profile shows total < coverateThreshold. func printCoverage() { - // Ensure the top-level coverage profile is refreshed at least once per day. - ensureDailyCoverage(24 * time.Hour) + // Ensure the top-level coverage profile is refreshed at least once per day. + ensureDailyCoverage(24 * time.Hour) select { case coveragePrinted <- struct{}{}: default: @@ -126,11 +126,20 @@ func printCoverage() { fmt.Println("[coverage] No coverage profile found (run 'mage cover' or 'mage coverall').") return } - pct, ok := totalCoveragePercent(profile) - if !ok { - fmt.Println("[coverage] Could not parse total coverage from", profile) - return - } + pct, ok := totalCoveragePercent(profile) + if !ok { + // Attempt a one-time regen if the profile is malformed + if err := Coverage(); err == nil { + if p2, ok2 := totalCoveragePercent(profile); ok2 { + pct = p2 + ok = true + } + } + } + if !ok { + fmt.Println("[coverage] Could not parse total coverage from", profile) + return + } if pct < coverageThreshold { fmt.Printf("[coverage] WARNING: total test coverage is %.1f%% (< %.1f%%)\n", pct, coverageThreshold) } else { diff --git a/PROJECTSTATUS.md b/PROJECTSTATUS.md deleted file mode 100644 index 230193d..0000000 --- a/PROJECTSTATUS.md +++ /dev/null @@ -1,23 +0,0 @@ -# Project status - -This document shows future items and items in progress. Already completed ones are deleted from this document as updates occur. - -## Features - -* [ ] In-editor chat triggers should be context aware of the current file, buffer and function! -* [ ] Kagi FastGPT for in-editor search - - Think about an in-editor chat trigger, maybe with S> for search! -* [ ] Test whethe GitHub Copilot support actually works now, and if not, fix it! -* [ ] Be able to re-configure the temperature in-editor -* [ ] Be able to switch LLMs. - -## More - -* [ ] Review documentation -* [ ] Manual review the code -* [ ] Useful: https://deepwiki.com/helix-editor/helix/4.3-language-server-protocol` - - - - - diff --git a/SCRATCHPAD.md b/SCRATCHPAD.md index afea5aa..d9874bc 100644 --- a/SCRATCHPAD.md +++ b/SCRATCHPAD.md @@ -4,9 +4,12 @@ This document shows future items and items in progress. Already completed ones a ## Features -* [ ] Keep global stats about LLM usage for the tmux pane! -* [ ] No a feature, but verify my OpenAI API account so I can use GPT-5 via the API. -* [ ] In-editor chat triggers should be context aware of the current file, buffer and function! +* [/] Keep global stats about LLM usage for the tmux pane! +* [/] No a feature, but verify my OpenAI API account so I can use GPT-5 via the API. + * [ ] Temperature must by default be 1 for GPT-5 + * [ ] Answers aren't streamed ot th eCLI anymore? + * [ ] GPT-5 is timing out on large responses? + * [ ] Any more tweaks for GPT-5 API? * [ ] Kagi FastGPT for in-editor search - Think about an in-editor chat trigger, maybe with S> for search! * [ ] Test whethe GitHub Copilot support actually works now, and if not, fix it! @@ -25,6 +28,7 @@ This document shows future items and items in progress. Already completed ones a * [/] Review documentation * [/] Manual review the code * [ ] Useful: https://deepwiki.com/helix-editor/helix/4.3-language-server-protocol +* [/] Code review with another LLM ## Additional Ideas (Nice to Have) diff --git a/config.toml.example b/config.toml.example index c237d5b..9ac6f51 100644 --- a/config.toml.example +++ b/config.toml.example @@ -12,7 +12,7 @@ coding_temperature = 0.2 # single knob for LSP calls (optional) log_preview_limit = 100 # chars shown in log previews [completion] -completion_debounce_ms = 200 # idle ms before sending a request +completion_debounce_ms = 800 # idle ms before sending a request completion_throttle_ms = 0 # min ms between requests (0 disables) manual_invoke_min_prefix = 0 # required identifier chars for manual invoke @@ -100,3 +100,6 @@ temperature = 0.2 [tmux] # custom_menu_hotkey = "a" # hotkey to open the custom actions submenu in hexai-tmux-action + +[stats] +# window_minutes = 60 # sliding window for global stats (Σ@window); min 1, max 1440 diff --git a/docs/coverage.html b/docs/coverage.html index 2d72d59..4c7532e 100644 --- a/docs/coverage.html +++ b/docs/coverage.html @@ -61,7 +61,7 @@ <option value="file2">codeberg.org/snonux/hexai/cmd/hexai/main.go (71.4%)</option> - <option value="file3">codeberg.org/snonux/hexai/internal/appconfig/config.go (90.8%)</option> + <option value="file3">codeberg.org/snonux/hexai/internal/appconfig/config.go (90.6%)</option> <option value="file4">codeberg.org/snonux/hexai/internal/editor/editor.go (58.3%)</option> @@ -69,9 +69,9 @@ <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 (87.5%)</option> + <option value="file7">codeberg.org/snonux/hexai/internal/hexaiaction/prompts.go (92.7%)</option> - <option value="file8">codeberg.org/snonux/hexai/internal/hexaiaction/run.go (67.2%)</option> + <option value="file8">codeberg.org/snonux/hexai/internal/hexaiaction/run.go (69.7%)</option> <option value="file9">codeberg.org/snonux/hexai/internal/hexaiaction/tui.go (65.5%)</option> @@ -79,9 +79,9 @@ <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 (88.6%)</option> + <option value="file12">codeberg.org/snonux/hexai/internal/hexaicli/run.go (89.7%)</option> - <option value="file13">codeberg.org/snonux/hexai/internal/hexailsp/run.go (83.7%)</option> + <option value="file13">codeberg.org/snonux/hexai/internal/hexailsp/run.go (90.2%)</option> <option value="file14">codeberg.org/snonux/hexai/internal/llm/copilot.go (82.4%)</option> @@ -99,37 +99,39 @@ <option value="file21">codeberg.org/snonux/hexai/internal/logging/logging.go (90.9%)</option> - <option value="file22">codeberg.org/snonux/hexai/internal/lsp/context.go (74.4%)</option> + <option value="file22">codeberg.org/snonux/hexai/internal/lsp/context.go (76.9%)</option> - <option value="file23">codeberg.org/snonux/hexai/internal/lsp/document.go (90.1%)</option> + <option value="file23">codeberg.org/snonux/hexai/internal/lsp/document.go (91.5%)</option> <option value="file24">codeberg.org/snonux/hexai/internal/lsp/handlers.go (92.9%)</option> <option value="file25">codeberg.org/snonux/hexai/internal/lsp/handlers_codeaction.go (82.3%)</option> - <option value="file26">codeberg.org/snonux/hexai/internal/lsp/handlers_completion.go (87.9%)</option> + <option value="file26">codeberg.org/snonux/hexai/internal/lsp/handlers_completion.go (88.0%)</option> - <option value="file27">codeberg.org/snonux/hexai/internal/lsp/handlers_document.go (88.9%)</option> + <option value="file27">codeberg.org/snonux/hexai/internal/lsp/handlers_document.go (90.1%)</option> <option value="file28">codeberg.org/snonux/hexai/internal/lsp/handlers_execute.go (75.0%)</option> <option value="file29">codeberg.org/snonux/hexai/internal/lsp/handlers_init.go (63.6%)</option> - <option value="file30">codeberg.org/snonux/hexai/internal/lsp/handlers_utils.go (89.4%)</option> + <option value="file30">codeberg.org/snonux/hexai/internal/lsp/handlers_utils.go (89.5%)</option> - <option value="file31">codeberg.org/snonux/hexai/internal/lsp/server.go (81.8%)</option> + <option value="file31">codeberg.org/snonux/hexai/internal/lsp/server.go (83.0%)</option> <option value="file32">codeberg.org/snonux/hexai/internal/lsp/transport.go (71.4%)</option> - <option value="file33">codeberg.org/snonux/hexai/internal/testutil/fixtures.go (100.0%)</option> + <option value="file33">codeberg.org/snonux/hexai/internal/stats/stats.go (75.4%)</option> - <option value="file34">codeberg.org/snonux/hexai/internal/textutil/human.go (92.3%)</option> + <option value="file34">codeberg.org/snonux/hexai/internal/testutil/fixtures.go (100.0%)</option> - <option value="file35">codeberg.org/snonux/hexai/internal/textutil/textutil.go (90.4%)</option> + <option value="file35">codeberg.org/snonux/hexai/internal/textutil/human.go (92.3%)</option> - <option value="file36">codeberg.org/snonux/hexai/internal/tmux/status.go (68.5%)</option> + <option value="file36">codeberg.org/snonux/hexai/internal/textutil/textutil.go (90.4%)</option> - <option value="file37">codeberg.org/snonux/hexai/internal/tmux/tmux.go (88.6%)</option> + <option value="file37">codeberg.org/snonux/hexai/internal/tmux/status.go (73.8%)</option> + + <option value="file38">codeberg.org/snonux/hexai/internal/tmux/tmux.go (88.6%)</option> </select> </div> @@ -328,6 +330,8 @@ type App struct { // 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. @@ -343,7 +347,7 @@ type CustomAction struct { } // Constructor: defaults for App (kept first among functions) -func newDefaultConfig() App <span class="cov5" title="30">{ +func newDefaultConfig() App <span class="cov5" title="31">{ // Coding-friendly default temperature across providers // Users can override per provider in config.toml (including 0.0). t := 0.2 @@ -358,7 +362,7 @@ func newDefaultConfig() App <span class="cov5" title="30">{ OllamaTemperature: &t, CopilotTemperature: &t, ManualInvokeMinPrefix: 0, - CompletionDebounceMs: 200, + CompletionDebounceMs: 800, CompletionThrottleMs: 0, // Inline/chat trigger defaults InlineOpen: ">", @@ -391,14 +395,17 @@ func newDefaultConfig() App <span class="cov5" title="30">{ PromptCLIDefaultSystem: "You are Hexai CLI. Default to very short, concise answers. If the user asks for commands, output only the commands (one per line) with no commentary or explanation. Only when the word 'explain' appears in the prompt, produce a verbose explanation.", PromptCLIExplainSystem: "You are Hexai CLI. The user requested an explanation. Provide a clear, verbose explanation with reasoning and details. If commands are needed, include them with brief context.", + + // Stats + StatsWindowMinutes: 60, } }</span> // Load reads configuration from a file and merges with defaults. // It respects the XDG Base Directory Specification. -func Load(logger *log.Logger) App <span class="cov5" title="29">{ +func Load(logger *log.Logger) App <span class="cov5" title="30">{ cfg := newDefaultConfig() - if logger == nil </span><span class="cov4" title="8">{ + if logger == nil </span><span class="cov4" title="9">{ return cfg // Return defaults if no logger is provided (e.g. in tests) }</span> @@ -407,7 +414,7 @@ func Load(logger *log.Logger) App <span class="cov5" title="29">{ logger.Printf("%v", err) // Even if config path cannot be resolved, still allow env overrides below. }</span> else<span class="cov5" title="21"> { - if fileCfg, err := loadFromFile(configPath, logger); err == nil && fileCfg != nil </span><span class="cov4" title="11">{ + if fileCfg, err := loadFromFile(configPath, logger); err == nil && fileCfg != nil </span><span class="cov5" title="16">{ cfg.mergeWith(fileCfg) }</span> // When the config file is missing or invalid, we keep defaults and still @@ -415,7 +422,7 @@ func Load(logger *log.Logger) App <span class="cov5" title="29">{ } // Environment overrides (take precedence over file) - <span class="cov5" title="21">if envCfg := loadFromEnv(logger); envCfg != nil </span><span class="cov1" title="1">{ + <span class="cov5" title="21">if envCfg := loadFromEnv(logger); envCfg != nil </span><span class="cov4" title="12">{ cfg.mergeWith(envCfg) }</span> <span class="cov5" title="21">return cfg</span> @@ -437,6 +444,7 @@ type fileConfig struct { Ollama sectionOllama `toml:"ollama"` Prompts sectionPrompts `toml:"prompts"` Tmux sectionTmux `toml:"tmux"` + Stats sectionStats `toml:"stats"` } type sectionGeneral struct { @@ -475,6 +483,10 @@ type sectionProvider struct { Name string `toml:"name"` } +type sectionStats struct { + WindowMinutes int `toml:"window_minutes"` +} + type sectionOpenAI struct { Model string `toml:"model"` BaseURL string `toml:"base_url"` @@ -553,7 +565,7 @@ type sectionTmux struct { CustomMenuHotkey string `toml:"custom_menu_hotkey"` } -func (fc *fileConfig) toApp() App <span class="cov4" title="11">{ +func (fc *fileConfig) toApp() App <span class="cov5" title="16">{ out := App{} // Merge section: general @@ -569,13 +581,13 @@ func (fc *fileConfig) toApp() App <span class="cov4" title="11">{ }</span> // logging - <span class="cov4" title="11">if (fc.Logging != sectionLogging{}) </span><span class="cov1" title="1">{ + <span class="cov5" title="16">if (fc.Logging != sectionLogging{}) </span><span class="cov1" title="1">{ tmp := App{LogPreviewLimit: fc.Logging.LogPreviewLimit} out.mergeBasics(&tmp) }</span> // completion - <span class="cov4" title="11">if (fc.Completion != sectionCompletion{}) </span><span class="cov2" title="3">{ + <span class="cov5" title="16">if (fc.Completion != sectionCompletion{}) </span><span class="cov2" title="3">{ tmp := App{ CompletionDebounceMs: fc.Completion.CompletionDebounceMs, CompletionThrottleMs: fc.Completion.CompletionThrottleMs, @@ -585,31 +597,31 @@ func (fc *fileConfig) toApp() App <span class="cov4" title="11">{ }</span> // triggers - <span class="cov4" title="11">if len(fc.Triggers.TriggerCharacters) > 0 </span><span class="cov2" title="3">{ + <span class="cov5" title="16">if len(fc.Triggers.TriggerCharacters) > 0 </span><span class="cov2" title="3">{ tmp := App{TriggerCharacters: fc.Triggers.TriggerCharacters} out.mergeBasics(&tmp) }</span> // inline - <span class="cov4" title="11">if (fc.Inline != sectionInline{}) </span><span class="cov1" title="1">{ + <span class="cov5" title="16">if (fc.Inline != sectionInline{}) </span><span class="cov1" title="1">{ tmp := App{InlineOpen: fc.Inline.InlineOpen, InlineClose: fc.Inline.InlineClose} out.mergeBasics(&tmp) }</span> // chat - <span class="cov4" title="11">if strings.TrimSpace(fc.Chat.ChatSuffix) != "" || len(fc.Chat.ChatPrefixes) > 0 </span><span class="cov1" title="1">{ + <span class="cov5" title="16">if strings.TrimSpace(fc.Chat.ChatSuffix) != "" || len(fc.Chat.ChatPrefixes) > 0 </span><span class="cov1" title="1">{ tmp := App{ChatSuffix: fc.Chat.ChatSuffix, ChatPrefixes: fc.Chat.ChatPrefixes} out.mergeBasics(&tmp) }</span> // provider - <span class="cov4" title="11">if strings.TrimSpace(fc.Provider.Name) != "" </span><span class="cov2" title="3">{ + <span class="cov5" title="16">if strings.TrimSpace(fc.Provider.Name) != "" </span><span class="cov2" title="3">{ tmp := App{Provider: fc.Provider.Name} out.mergeBasics(&tmp) }</span> // openai - <span class="cov4" title="11">if (fc.OpenAI != sectionOpenAI{}) || fc.OpenAI.Temperature != nil </span><span class="cov2" title="3">{ + <span class="cov5" title="16">if (fc.OpenAI != sectionOpenAI{}) || fc.OpenAI.Temperature != nil </span><span class="cov2" title="3">{ tmp := App{ OpenAIBaseURL: fc.OpenAI.BaseURL, OpenAIModel: fc.OpenAI.Model, @@ -619,7 +631,7 @@ func (fc *fileConfig) toApp() App <span class="cov4" title="11">{ }</span> // copilot - <span class="cov4" title="11">if (fc.Copilot != sectionCopilot{}) || fc.Copilot.Temperature != nil </span><span class="cov2" title="3">{ + <span class="cov5" title="16">if (fc.Copilot != sectionCopilot{}) || fc.Copilot.Temperature != nil </span><span class="cov2" title="3">{ tmp := App{ CopilotBaseURL: fc.Copilot.BaseURL, CopilotModel: fc.Copilot.Model, @@ -629,7 +641,7 @@ func (fc *fileConfig) toApp() App <span class="cov4" title="11">{ }</span> // ollama - <span class="cov4" title="11">if (fc.Ollama != sectionOllama{}) || fc.Ollama.Temperature != nil </span><span class="cov2" title="3">{ + <span class="cov5" title="16">if (fc.Ollama != sectionOllama{}) || fc.Ollama.Temperature != nil </span><span class="cov2" title="3">{ tmp := App{ OllamaBaseURL: fc.Ollama.BaseURL, OllamaModel: fc.Ollama.Model, @@ -640,7 +652,7 @@ func (fc *fileConfig) toApp() App <span class="cov4" title="11">{ // prompts // completion - <span class="cov4" title="11">if (fc.Prompts.Completion != sectionPromptsCompletion{}) </span><span class="cov1" title="1">{ + <span class="cov5" title="16">if (fc.Prompts.Completion != sectionPromptsCompletion{}) </span><span class="cov1" title="1">{ if strings.TrimSpace(fc.Prompts.Completion.SystemGeneral) != "" </span><span class="cov1" title="1">{ out.PromptCompletionSystemGeneral = fc.Prompts.Completion.SystemGeneral }</span> @@ -661,11 +673,11 @@ func (fc *fileConfig) toApp() App <span class="cov4" title="11">{ }</span> } // chat - <span class="cov4" title="11">if strings.TrimSpace(fc.Prompts.Chat.System) != "" </span><span class="cov1" title="1">{ + <span class="cov5" title="16">if strings.TrimSpace(fc.Prompts.Chat.System) != "" </span><span class="cov1" title="1">{ out.PromptChatSystem = fc.Prompts.Chat.System }</span> // code action - <span class="cov4" title="11">if strings.TrimSpace(fc.Prompts.CodeAction.RewriteSystem) != "" || + <span class="cov5" title="16">if strings.TrimSpace(fc.Prompts.CodeAction.RewriteSystem) != "" || strings.TrimSpace(fc.Prompts.CodeAction.DiagnosticsSystem) != "" || strings.TrimSpace(fc.Prompts.CodeAction.DocumentSystem) != "" || strings.TrimSpace(fc.Prompts.CodeAction.RewriteUser) != "" || @@ -675,39 +687,39 @@ func (fc *fileConfig) toApp() App <span class="cov4" title="11">{ strings.TrimSpace(fc.Prompts.CodeAction.GoTestUser) != "" || strings.TrimSpace(fc.Prompts.CodeAction.SimplifySystem) != "" || strings.TrimSpace(fc.Prompts.CodeAction.SimplifyUser) != "" || - len(fc.Prompts.CodeAction.Custom) > 0 </span><span class="cov3" title="7">{ + len(fc.Prompts.CodeAction.Custom) > 0 </span><span class="cov4" title="12">{ if strings.TrimSpace(fc.Prompts.CodeAction.RewriteSystem) != "" </span><span class="cov1" title="1">{ out.PromptCodeActionRewriteSystem = fc.Prompts.CodeAction.RewriteSystem }</span> - <span class="cov3" title="7">if strings.TrimSpace(fc.Prompts.CodeAction.DiagnosticsSystem) != "" </span><span class="cov1" title="1">{ + <span class="cov4" title="12">if strings.TrimSpace(fc.Prompts.CodeAction.DiagnosticsSystem) != "" </span><span class="cov1" title="1">{ out.PromptCodeActionDiagnosticsSystem = fc.Prompts.CodeAction.DiagnosticsSystem }</span> - <span class="cov3" title="7">if strings.TrimSpace(fc.Prompts.CodeAction.DocumentSystem) != "" </span><span class="cov1" title="1">{ + <span class="cov4" title="12">if strings.TrimSpace(fc.Prompts.CodeAction.DocumentSystem) != "" </span><span class="cov1" title="1">{ out.PromptCodeActionDocumentSystem = fc.Prompts.CodeAction.DocumentSystem }</span> - <span class="cov3" title="7">if strings.TrimSpace(fc.Prompts.CodeAction.RewriteUser) != "" </span><span class="cov1" title="1">{ + <span class="cov4" title="12">if strings.TrimSpace(fc.Prompts.CodeAction.RewriteUser) != "" </span><span class="cov1" title="1">{ out.PromptCodeActionRewriteUser = fc.Prompts.CodeAction.RewriteUser }</span> - <span class="cov3" title="7">if strings.TrimSpace(fc.Prompts.CodeAction.DiagnosticsUser) != "" </span><span class="cov1" title="1">{ + <span class="cov4" title="12">if strings.TrimSpace(fc.Prompts.CodeAction.DiagnosticsUser) != "" </span><span class="cov1" title="1">{ out.PromptCodeActionDiagnosticsUser = fc.Prompts.CodeAction.DiagnosticsUser }</span> - <span class="cov3" title="7">if strings.TrimSpace(fc.Prompts.CodeAction.DocumentUser) != "" </span><span class="cov1" title="1">{ + <span class="cov4" title="12">if strings.TrimSpace(fc.Prompts.CodeAction.DocumentUser) != "" </span><span class="cov1" title="1">{ out.PromptCodeActionDocumentUser = fc.Prompts.CodeAction.DocumentUser }</span> - <span class="cov3" title="7">if strings.TrimSpace(fc.Prompts.CodeAction.GoTestSystem) != "" </span><span class="cov1" title="1">{ + <span class="cov4" title="12">if strings.TrimSpace(fc.Prompts.CodeAction.GoTestSystem) != "" </span><span class="cov1" title="1">{ out.PromptCodeActionGoTestSystem = fc.Prompts.CodeAction.GoTestSystem }</span> - <span class="cov3" title="7">if strings.TrimSpace(fc.Prompts.CodeAction.GoTestUser) != "" </span><span class="cov1" title="1">{ + <span class="cov4" title="12">if strings.TrimSpace(fc.Prompts.CodeAction.GoTestUser) != "" </span><span class="cov1" title="1">{ out.PromptCodeActionGoTestUser = fc.Prompts.CodeAction.GoTestUser }</span> - <span class="cov3" title="7">if strings.TrimSpace(fc.Prompts.CodeAction.SimplifySystem) != "" </span><span class="cov0" title="0">{ + <span class="cov4" title="12">if strings.TrimSpace(fc.Prompts.CodeAction.SimplifySystem) != "" </span><span class="cov0" title="0">{ out.PromptCodeActionSimplifySystem = fc.Prompts.CodeAction.SimplifySystem }</span> - <span class="cov3" title="7">if strings.TrimSpace(fc.Prompts.CodeAction.SimplifyUser) != "" </span><span class="cov0" title="0">{ + <span class="cov4" title="12">if strings.TrimSpace(fc.Prompts.CodeAction.SimplifyUser) != "" </span><span class="cov0" title="0">{ out.PromptCodeActionSimplifyUser = fc.Prompts.CodeAction.SimplifyUser }</span> - <span class="cov3" title="7">if len(fc.Prompts.CodeAction. |
