DSH plugin LSP: LspQueryRequest and four navigation ops
The DSH plugin LSP seam exposes exactly four operations, and the union is closed: goToDefinition, findReferences, goToImplementation and hover. Closing it is not about less code — it makes "add a query" a compile-time change across seam, providers and tool, so missing one is a build error. The other common trap is coordinates: the seam uses zero-based UTF-16 while the model-facing tool uses a one-based cursor, with conversion at the boundary; mixing them shifts navigation by a line. Finally, resolvedWorkspaceUri from the result is the correct basis for relativizing URIs, not the request path.
DSH plugin LSP seam: four closed operations
Bottom line: the four semantic queries form a closed union; symbols and call hierarchy live elsewhere because they need different schemas.
- Four operations: definition, references, implementation and hover.
- Closed union: adding one breaks compilation across seam, providers and tool.
- What is excluded: symbol lists and call hierarchy are not among the four.
- Layering: definition, the generic stdio provider and the model-facing
lsptool each own a layer. - Optional capability: outside the agent loop mainline; swapping a provider never changes how the model asks for navigation.
DSH plugin LspQueryRequest: every field required, no resolve
Bottom line: nothing needs defaulting, so this interface has no resolve() step.
interface LspQueryRequest {
operation: LspOperation
filePath: string // relative to workspaceRoot or absolute; the provider canonicalizes
position: LspPosition // zero-based UTF-16
workspaceRoot: string // required, never defaulted
}
- workspaceRoot is caller-supplied: the provider resolves against and indexes it.
- languageId is not in the request: it comes from the provider's extension mapping, only synchronizes the transient document, and never participates in selection.
- Timeouts and result limits belong to consumers.
- Provider shape: the request plus the derived
languageId. - Verify: a request without
workspaceRootshould fail before execution, not inside a provider.
DSH plugin coordinates: zero-based UTF-16 versus one-based cursors
Bottom line: the protocol layer is zero-based UTF-16, the tool layer is one-based, and conversion happens only at the boundary.
- Positions: zero-based line and in-line UTF-16 code-unit offset.
- Ranges: half-open
[start, end). - Tool layer: model-facing cursors start at one, converted on input and output.
- Common mistake: passing tool coordinates straight to a provider shifts navigation by a line.
- Verify: go to a definition on a known symbol and confirm the returned range covers it exactly.
DSH plugin result union: locations and hover
Bottom line: navigation normalizes to locations, hover to content or null, and findReferences always includes the declaration.
| Operation | Result shape |
|---|---|
goToDefinition | locations + resolvedWorkspaceUri |
findReferences | locations (declaration included) |
goToImplementation | locations |
hover | hover content or null |
- Exhaustive handling: switch on
kindand handle every arm. - No declaration flag: the provider guarantees the declaration is included.
- URIs verbatim:
uricomes from the language server and may or may not befile:. - Optional hover range: present when the server supplies one.
- Provider normalization: turn raw LSP responses into these two shapes and never leak protocol details.
DSH plugin resolvedWorkspaceUri and the LspProvider contract
Bottom line: relativize against the provider's canonical workspace URI, not the request path.
- Why not the request path: it may pass through symlinks, and the execution platform may differ.
- Correct approach: use
resolvedWorkspaceUrifrom the result. - Cross-platform: the provider canonicalizes in its own execution world; do not apply host path rules.
- Verify: run a reference search in a symlinked workspace and confirm relative paths land inside the real workspace.
- Boundary: if a result URI is outside the workspace, leave it as is rather than forcing a relative path.
DSH plugin LSP troubleshooting
Bottom line: separate wrong provider, shifted coordinates and unnormalized results.
- No results — check provider registration, language-server readiness and
workspaceRoot. - Off by one line — mixed coordinate bases; check the zero/one-based conversion.
- Declaration missing from references — should not happen; the provider guarantees it.
- Mangled relative paths — you used the request path instead of
resolvedWorkspaceUri. - Wanting a fifth operation — that is a cross-package change, not a branch.
- Repair status, stated honestly: the four operations, required-field contract, coordinates and result union follow the official LSP navigation page; confirm signatures against its Cordis API and your version.
Sources: LSP navigation, dsh-lsp source, dsh-lsp-stdio.
FAQ
The DSH plugin LSP seam exposes exactly four: goToDefinition, findReferences, goToImplementation and hover. The union is closed, so adding an operation breaks compilation and forces coordinated changes across seam, providers and tool.
Every DSH plugin LspQueryRequest field is required: workspaceRoot is caller-supplied, languageId comes from provider registration, and timeouts plus result limits belong to consumers. Nothing needs implementation defaulting, so there is no resolve step.
The DSH plugin LSP seam uses zero-based UTF-16 coordinates to match the protocol, while the model-facing tool uses a one-based cursor convention and converts on input and output. Confirm which layer you are in before composing positions.
No: DSH plugin findReferences always includes the declaration, enforced inside the provider, so callers have no flag for it. Navigation results normalize into the locations union either way.
No: use the DSH plugin result's resolvedWorkspaceUri, the provider's canonical file: URI. The request path may pass through symlinks and the execution platform may differ from the caller's.
Related Terms
- LspOperation
- LspOperation is one of the four semantic queries the DSH plugin LSP seam exposes: goToDefinition, findReferences, goToImplementation or hover. It is a closed union, so adding an operation requires coordinated changes.— https://deepseek-harness.github.io/deepseek-harness/reference/subsystems/lsp
- LspQueryRequest
- LspQueryRequest is a normalized navigation query whose operation, filePath, position and workspaceRoot are all required. languageId is not part of the request; the provider's extension mapping derives it.— https://deepseek-harness.github.io/deepseek-harness/reference/subsystems/lsp
- LspQueryResult
- LspQueryResult is a closed discriminated union: navigation operations normalize to locations while hover normalizes to content or null. Consumers switch on kind exhaustively so a new arm fails compilation.— https://deepseek-harness.github.io/deepseek-harness/reference/subsystems/lsp
- resolvedWorkspaceUri
- resolvedWorkspaceUri is the provider's canonical file: URI for the requested workspace root, returned with locations results. Relativizing location URIs must use it rather than the possibly symlinked request path.— https://deepseek-harness.github.io/deepseek-harness/reference/subsystems/lsp
Sources
- DeepSeek Harness docs — LSP navigation (operations, requests, results, providers and service)· deepseek-harness.github.io
- dsh-lsp source (cross-package types)· deepseek-ai (GitHub)
- dsh-lsp-stdio (configured stdio language-server host)· deepseek-ai (GitHub)