Add LSP multiplexer to support multiple LSP toolsets#1970
Add LSP multiplexer to support multiple LSP toolsets#1970dgageot wants to merge 1 commit intodocker:mainfrom
Conversation
There was a problem hiding this comment.
Review Summary
Assessment: 🔴 CRITICAL
This PR adds LSP multiplexing functionality to combine multiple LSP toolsets. The core routing logic is sound, but there are 1 high-severity resource leak and 4 medium-severity issues that should be addressed before merging.
Critical Issues
HIGH: Resource leak in Start method - if initialization fails partway through, previously started LSP backends are left running.
Notable Issues
- Empty file arguments may route to unintended backends
- Type assertion failures are silently ignored in teamloader
- Broadcast operations abort on first error instead of aggregating results
- Constructor panics on invalid input (could return error instead)
Details
See inline comments below.
pkg/tools/builtin/lsp_multiplexer.go
Outdated
|
|
||
| func (m *LSPMultiplexer) Start(ctx context.Context) error { | ||
| for _, b := range m.backends { | ||
| if err := b.lsp.Start(ctx); err != nil { |
There was a problem hiding this comment.
HIGH SEVERITY: Resource leak on partial Start failure
If Start fails on any backend after the first, the previously started backends are not cleaned up. This leaves LSP processes running.
Example scenario:
- Backend 0 (gopls) starts successfully
- Backend 1 (pyright) fails to start
- Function returns error
- Backend 0 remains running
Recommendation:
Track successfully started backends and call Stop on them before returning the error:
func (m *LSPMultiplexer) Start(ctx context.Context) error {
var started []lspBackend
for _, b := range m.backends {
if err := b.lsp.Start(ctx); err != nil {
// Clean up previously started backends
for _, s := range started {
_ = s.lsp.Stop(ctx)
}
return fmt.Errorf("starting LSP backend %q: %w", b.lsp.handler.command, err)
}
started = append(started, b)
}
return nil
}012c509 to
aa55338
Compare
When multiple LSP toolsets are configured (e.g., gopls for Go and pyright for Python), each produces tools with identical names (lsp_hover, lsp_definition, etc.), causing LLM APIs to reject the request because tool names must be unique. Add an LSPMultiplexer that combines multiple LSP backends into a single toolset. File-based tools (hover, definition, references, etc.) are routed to the correct backend by matching the file extension. Non-file tools (workspace, workspace_symbols) are broadcast to all backends and results are merged. The teamloader detects multiple LSP toolsets during agent loading and automatically wraps them in a multiplexer. Per-toolset config (tool filters, instructions, toon) is preserved through wrapping. Fixes docker#1969 Assisted-By: cagent
aa55338 to
9221d03
Compare
When multiple LSP toolsets are configured (e.g.,
goplsfor Go andpyrightfor Python), each produces tools with identical names (lsp_hover,lsp_definition, etc.), causing LLM APIs to reject the request because tool names must be unique.Changes
pkg/tools/builtin/lsp_multiplexer.go— NewLSPMultiplexerthat combines multiple LSP backends into a single toolset:HandlesFile()ToolSet,Startable, andInstructableinterfacespkg/teamloader/teamloader.go— ModifiedgetToolsForAgentto detect multiple LSP toolsets and combine them into a single multiplexer. Per-toolset config (tool filters, instructions, toon, defer) is preserved.Tests — Comprehensive test coverage for routing, broadcasting, error handling, deduplication, and teamloader integration.
Fixes #1969