diff options
| author | Paul Buetow <pbuetow@mimecast.com> | 2026-01-30 12:16:31 +0200 |
|---|---|---|
| committer | Paul Buetow <pbuetow@mimecast.com> | 2026-01-30 12:16:31 +0200 |
| commit | 3f1ab18cbc996c9467dae6d8deb2c26798aff30e (patch) | |
| tree | c1a6cdaad61a37bbdd70ddbe161c05aaf13d09ab /internal | |
| parent | d3e0edbe16459f07506f70611b639d0a0a7f054e (diff) | |
feat: add completion_wait_all config and fix Anthropic system messages
- Add completion_wait_all config option (default true) to wait for all
backends before returning results, or return first result immediately
- Fix Anthropic API: extract system messages to top-level system field
as required by Messages API (was causing 400 errors)
- Add anthropic case to server.go clientFor() for model overrides
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/appconfig/config.go | 18 | ||||
| -rw-r--r-- | internal/hexailsp/run.go | 1 | ||||
| -rw-r--r-- | internal/llm/anthropic.go | 19 | ||||
| -rw-r--r-- | internal/lsp/handlers_completion.go | 18 | ||||
| -rw-r--r-- | internal/lsp/server.go | 16 |
5 files changed, 64 insertions, 8 deletions
diff --git a/internal/appconfig/config.go b/internal/appconfig/config.go index f41d4d9..78237be 100644 --- a/internal/appconfig/config.go +++ b/internal/appconfig/config.go @@ -40,6 +40,10 @@ type App struct { // Completion throttle in milliseconds. When > 0, caps the minimum spacing // between LLM requests (both chat and code-completer paths). CompletionThrottleMs int `json:"completion_throttle_ms" toml:"completion_throttle_ms"` + // CompletionWaitAll controls whether to wait for all configured completion + // backends before returning results. When true (default), waits for all + // backends. When false, returns the first result immediately. + CompletionWaitAll *bool `json:"completion_wait_all" toml:"completion_wait_all"` TriggerCharacters []string `json:"trigger_characters" toml:"trigger_characters"` Provider string `json:"provider" toml:"provider"` @@ -259,9 +263,10 @@ type sectionLogging struct { } type sectionCompletion struct { - CompletionDebounceMs int `toml:"completion_debounce_ms"` - CompletionThrottleMs int `toml:"completion_throttle_ms"` - ManualInvokeMinPrefix int `toml:"manual_invoke_min_prefix"` + CompletionDebounceMs int `toml:"completion_debounce_ms"` + CompletionThrottleMs int `toml:"completion_throttle_ms"` + ManualInvokeMinPrefix int `toml:"manual_invoke_min_prefix"` + CompletionWaitAll *bool `toml:"completion_wait_all"` } type sectionTriggers struct { @@ -425,11 +430,13 @@ func (fc *fileConfig) toApp() App { } // completion - if (fc.Completion != sectionCompletion{}) { + if fc.Completion.CompletionDebounceMs != 0 || fc.Completion.CompletionThrottleMs != 0 || + fc.Completion.ManualInvokeMinPrefix != 0 || fc.Completion.CompletionWaitAll != nil { tmp := App{ CompletionDebounceMs: fc.Completion.CompletionDebounceMs, CompletionThrottleMs: fc.Completion.CompletionThrottleMs, ManualInvokeMinPrefix: fc.Completion.ManualInvokeMinPrefix, + CompletionWaitAll: fc.Completion.CompletionWaitAll, } out.mergeBasics(&tmp) } @@ -888,6 +895,9 @@ func (a *App) mergeBasics(other *App) { if other.CompletionThrottleMs > 0 { a.CompletionThrottleMs = other.CompletionThrottleMs } + if other.CompletionWaitAll != nil { + a.CompletionWaitAll = other.CompletionWaitAll + } if len(other.TriggerCharacters) > 0 { a.TriggerCharacters = slices.Clone(other.TriggerCharacters) } diff --git a/internal/hexailsp/run.go b/internal/hexailsp/run.go index f39ea96..1afa70a 100644 --- a/internal/hexailsp/run.go +++ b/internal/hexailsp/run.go @@ -195,6 +195,7 @@ func makeServerOptions(cfg appconfig.App, logContext bool, client llm.Client, lo ManualInvokeMinPrefix: cfg.ManualInvokeMinPrefix, CompletionDebounceMs: cfg.CompletionDebounceMs, CompletionThrottleMs: cfg.CompletionThrottleMs, + CompletionWaitAll: cfg.CompletionWaitAll, InlineOpen: cfg.InlineOpen, InlineClose: cfg.InlineClose, ChatSuffix: cfg.ChatSuffix, diff --git a/internal/llm/anthropic.go b/internal/llm/anthropic.go index ebb6826..c0cdc9a 100644 --- a/internal/llm/anthropic.go +++ b/internal/llm/anthropic.go @@ -230,8 +230,21 @@ func buildAnthropicChatRequest(o Options, messages []Message, defaultModel strin Stream: stream, MaxTokens: 4096, // Anthropic requires max_tokens } - req.Messages = make([]anthropicMessage, len(messages)) - for i, m := range messages { + // Anthropic requires system messages in a top-level "system" field, not in messages array + var systemParts []string + var nonSystemMessages []Message + for _, m := range messages { + if m.Role == "system" { + systemParts = append(systemParts, m.Content) + } else { + nonSystemMessages = append(nonSystemMessages, m) + } + } + if len(systemParts) > 0 { + req.System = strings.Join(systemParts, "\n\n") + } + req.Messages = make([]anthropicMessage, len(nonSystemMessages)) + for i, m := range nonSystemMessages { req.Messages[i] = anthropicMessage{ Role: m.Role, Content: m.Content, @@ -246,8 +259,6 @@ func buildAnthropicChatRequest(o Options, messages []Message, defaultModel strin if o.MaxTokens > 0 { req.MaxTokens = o.MaxTokens } - // Note: Anthropic's API doesn't support stop sequences in the same way as OpenAI, - // but we keep them in the request for future compatibility. return req } diff --git a/internal/lsp/handlers_completion.go b/internal/lsp/handlers_completion.go index 2fac1f3..28da503 100644 --- a/internal/lsp/handlers_completion.go +++ b/internal/lsp/handlers_completion.go @@ -155,6 +155,24 @@ func (s *Server) tryLLMCompletion(p CompletionParams, above, current, below, fun return res.items, true, false } + waitAll := s.completionWaitAll() + if waitAll { + // Wait for all backends, return combined results + defer end() + combined := make([]CompletionItem, 0) + for res := range results { + if !res.ok || len(res.items) == 0 { + continue + } + combined = append(combined, res.items...) + } + if len(combined) == 0 { + return nil, false, false + } + return combined, true, false + } + + // Return first result immediately, store combined for later firstCh := make(chan []CompletionItem, 1) go func(planKey string) { defer end() diff --git a/internal/lsp/server.go b/internal/lsp/server.go index 67e3cab..127b089 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -71,6 +71,7 @@ type ServerOptions struct { ManualInvokeMinPrefix int CompletionDebounceMs int CompletionThrottleMs int + CompletionWaitAll *bool // Inline/chat triggers InlineOpen string @@ -160,6 +161,7 @@ func (s *Server) applyOptions(opts ServerOptions) { s.cfg.ManualInvokeMinPrefix = opts.ManualInvokeMinPrefix s.cfg.CompletionDebounceMs = opts.CompletionDebounceMs s.cfg.CompletionThrottleMs = opts.CompletionThrottleMs + s.cfg.CompletionWaitAll = opts.CompletionWaitAll s.cfg.InlineOpen = opts.InlineOpen s.cfg.InlineClose = opts.InlineClose s.cfg.ChatSuffix = opts.ChatSuffix @@ -305,6 +307,12 @@ func (s *Server) clientFor(spec requestSpec) llm.Client { } else if spec.fallbackModel != "" { cfg.OllamaModel = spec.fallbackModel } + case "anthropic": + if modelOverride != "" { + cfg.AnthropicModel = modelOverride + } else if spec.fallbackModel != "" { + cfg.AnthropicModel = spec.fallbackModel + } } client, err := newClientForProvider(cfg, provider) if err != nil { @@ -451,6 +459,14 @@ func (s *Server) completionThrottle() time.Duration { return time.Duration(cfg.CompletionThrottleMs) * time.Millisecond } +func (s *Server) completionWaitAll() bool { + cfg := s.currentConfig() + if cfg.CompletionWaitAll == nil { + return true // default: wait for all backends + } + return *cfg.CompletionWaitAll +} + func (s *Server) inlineMarkers() (open string, close string, openChar byte, closeChar byte) { cfg := s.currentConfig() open = strings.TrimSpace(cfg.InlineOpen) |
