DSH plugin LSP: LspQueryRequest and four navigation ops

Plugin DevelopmentPublished 2026-10-17Author: DeepSeek Plugin Market
DeepSeek HarnessDSH pluginLspQueryRequestLspProvidercode navigationseam
How a DSH plugin plugs into LSP: four closed operations, an all-required request, zero-based UTF-16 versus one-based cursors, and resolvedWorkspaceUri.

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.

  1. Four operations: definition, references, implementation and hover.
  2. Closed union: adding one breaks compilation across seam, providers and tool.
  3. What is excluded: symbol lists and call hierarchy are not among the four.
  4. Layering: definition, the generic stdio provider and the model-facing lsp tool each own a layer.
  5. 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.

ts
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
}
  1. workspaceRoot is caller-supplied: the provider resolves against and indexes it.
  2. languageId is not in the request: it comes from the provider's extension mapping, only synchronizes the transient document, and never participates in selection.
  3. Timeouts and result limits belong to consumers.
  4. Provider shape: the request plus the derived languageId.
  5. Verify: a request without workspaceRoot should 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.

  1. Positions: zero-based line and in-line UTF-16 code-unit offset.
  2. Ranges: half-open [start, end).
  3. Tool layer: model-facing cursors start at one, converted on input and output.
  4. Common mistake: passing tool coordinates straight to a provider shifts navigation by a line.
  5. 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.

OperationResult shape
goToDefinitionlocations + resolvedWorkspaceUri
findReferenceslocations (declaration included)
goToImplementationlocations
hoverhover content or null
  1. Exhaustive handling: switch on kind and handle every arm.
  2. No declaration flag: the provider guarantees the declaration is included.
  3. URIs verbatim: uri comes from the language server and may or may not be file:.
  4. Optional hover range: present when the server supplies one.
  5. 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.

  1. Why not the request path: it may pass through symlinks, and the execution platform may differ.
  2. Correct approach: use resolvedWorkspaceUri from the result.
  3. Cross-platform: the provider canonicalizes in its own execution world; do not apply host path rules.
  4. Verify: run a reference search in a symlinked workspace and confirm relative paths land inside the real workspace.
  5. 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.

  1. No results — check provider registration, language-server readiness and workspaceRoot.
  2. Off by one line — mixed coordinate bases; check the zero/one-based conversion.
  3. Declaration missing from references — should not happen; the provider guarantees it.
  4. Mangled relative paths — you used the request path instead of resolvedWorkspaceUri.
  5. Wanting a fifth operation — that is a cross-package change, not a branch.
  6. 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

Which navigation operations does the DSH plugin LSP seam expose?

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.

Why is there no resolve() step in a DSH plugin LspQueryRequest?

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.

Are DSH plugin LSP coordinates zero-based or one-based?

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.

Does DSH plugin findReferences need a flag to include the declaration?

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.

Should I relativize DSH plugin location URIs against the request workspaceRoot?

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