summaryrefslogtreecommitdiff
path: root/internal/hexailsp
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2025-09-24 23:21:43 +0300
committerPaul Buetow <paul@buetow.org>2025-09-24 23:21:43 +0300
commitc3c71345db9086392cd9b7529c7f5287009c226e (patch)
treed227894ab900d6050cbe1418984526088a692db5 /internal/hexailsp
parent127844a4ee481590ef53b6777d34bf2114cb3ab1 (diff)
Add runtime config store and reload command
Diffstat (limited to 'internal/hexailsp')
-rw-r--r--internal/hexailsp/run.go23
-rw-r--r--internal/hexailsp/run_more_test.go54
2 files changed, 76 insertions, 1 deletions
diff --git a/internal/hexailsp/run.go b/internal/hexailsp/run.go
index 554e604..ffb9f86 100644
--- a/internal/hexailsp/run.go
+++ b/internal/hexailsp/run.go
@@ -13,6 +13,7 @@ import (
"codeberg.org/snonux/hexai/internal/llm"
"codeberg.org/snonux/hexai/internal/logging"
"codeberg.org/snonux/hexai/internal/lsp"
+ "codeberg.org/snonux/hexai/internal/runtimeconfig"
"codeberg.org/snonux/hexai/internal/stats"
)
@@ -55,8 +56,26 @@ func RunWithFactory(logPath string, stdin io.Reader, stdout io.Writer, logger *l
client = buildClientIfNil(cfg, client)
factory = ensureFactory(factory)
- opts := makeServerOptions(cfg, strings.TrimSpace(logPath) != "", client)
+ store := runtimeconfig.New(cfg)
+ logContext := strings.TrimSpace(logPath) != ""
+ opts := makeServerOptions(cfg, logContext, client)
+ opts.ConfigStore = store
server := factory(stdin, stdout, logger, opts)
+ if configurable, ok := server.(interface{ ApplyOptions(lsp.ServerOptions) }); ok {
+ store.Subscribe(func(oldCfg, newCfg appconfig.App) {
+ updated := newCfg
+ normalizeLoggingConfig(&updated)
+ if updated.StatsWindowMinutes > 0 {
+ stats.SetWindow(time.Duration(updated.StatsWindowMinutes) * time.Minute)
+ }
+ if newClient := buildClientIfNil(updated, nil); newClient != nil {
+ client = newClient
+ }
+ opts := makeServerOptions(updated, logContext, client)
+ opts.ConfigStore = store
+ configurable.ApplyOptions(opts)
+ })
+ }
if err := server.Run(); err != nil {
logger.Fatalf("server error: %v", err)
}
@@ -135,6 +154,8 @@ func makeServerOptions(cfg appconfig.App, logContext bool, client llm.Client) ls
}
return lsp.ServerOptions{
LogContext: logContext,
+ ConfigStore: nil,
+ Config: &cfg,
MaxTokens: cfg.MaxTokens,
ContextMode: cfg.ContextMode,
WindowLines: cfg.ContextWindowLines,
diff --git a/internal/hexailsp/run_more_test.go b/internal/hexailsp/run_more_test.go
index 00b79c1..faaae41 100644
--- a/internal/hexailsp/run_more_test.go
+++ b/internal/hexailsp/run_more_test.go
@@ -2,18 +2,34 @@ package hexailsp
import (
"bytes"
+ "context"
"io"
"log"
"testing"
"codeberg.org/snonux/hexai/internal/appconfig"
+ "codeberg.org/snonux/hexai/internal/llm"
"codeberg.org/snonux/hexai/internal/lsp"
+ "codeberg.org/snonux/hexai/internal/runtimeconfig"
)
type recRunner struct{ ran bool }
func (r *recRunner) Run() error { r.ran = true; return nil }
+type applyRunner struct{ opts []lsp.ServerOptions }
+
+func (r *applyRunner) Run() error { return nil }
+func (r *applyRunner) ApplyOptions(opts lsp.ServerOptions) { r.opts = append(r.opts, opts) }
+
+type stubClient struct{}
+
+func (stubClient) Chat(context.Context, []llm.Message, ...llm.RequestOption) (string, error) {
+ return "", nil
+}
+func (stubClient) Name() string { return "stub" }
+func (stubClient) DefaultModel() string { return "stub-model" }
+
func TestRunWithFactory_BuildsOptionsAndClient(t *testing.T) {
var captured lsp.ServerOptions
factory := func(r io.Reader, w io.Writer, logger *log.Logger, opts lsp.ServerOptions) ServerRunner {
@@ -41,3 +57,41 @@ func TestRunWithFactory_BuildsOptionsAndClient(t *testing.T) {
t.Fatalf("expected client to be constructed")
}
}
+
+func TestRunWithFactory_SubscriptionAppliesUpdates(t *testing.T) {
+ var in, out bytes.Buffer
+ logger := log.New(io.Discard, "", 0)
+ runner := &applyRunner{}
+ var capturedStore *runtimeconfig.Store
+ factory := func(r io.Reader, w io.Writer, logger *log.Logger, opts lsp.ServerOptions) ServerRunner {
+ capturedStore = opts.ConfigStore
+ runner.opts = append(runner.opts, opts)
+ return runner
+ }
+ cfg := appconfig.Load(nil)
+ cfg.StatsWindowMinutes = 0
+ cfg.ContextMode = " WINDOW "
+ if err := RunWithFactory("", &in, &out, logger, cfg, stubClient{}, factory); err != nil {
+ t.Fatalf("RunWithFactory error: %v", err)
+ }
+ if capturedStore == nil {
+ t.Fatal("expected config store to be passed to factory")
+ }
+ if len(runner.opts) == 0 {
+ t.Fatal("expected initial options to be recorded")
+ }
+ updated := cfg
+ updated.MaxTokens = cfg.MaxTokens + 10
+ updated.ContextMode = "always-full"
+ capturedStore.Set(updated)
+ if len(runner.opts) < 2 {
+ t.Fatalf("expected ApplyOptions to be invoked on config update, got %d calls", len(runner.opts))
+ }
+ latest := runner.opts[len(runner.opts)-1]
+ if latest.MaxTokens != updated.MaxTokens {
+ t.Fatalf("expected updated max tokens, got %+v", latest)
+ }
+ if latest.ContextMode != "always-full" {
+ t.Fatalf("expected normalized context mode, got %+v", latest)
+ }
+}