DSH plugin 接 LSP:LspQueryRequest、四种操作与 LspProvider 契约
DSH plugin 的 LSP seam 只有四项操作,而且这个联合是闭合的:goToDefinition、findReferences、goToImplementation、hover。 闭合不是为了少写代码,而是让「新增一种查询」变成跨 seam、提供方与工具的编译期联动——漏改一处就编译不过。另一个容易踩的点是坐标:seam 用 0 基 UTF-16,面向模型的工具用 1 基光标,转换在边界处完成;把两层的坐标直接拼起来,导航就会偏一行。
最后,结果里的 resolvedWorkspaceUri 才是相对化的正确基准,不要拿请求路径去套宿主路径规则。
DSH plugin 的 LSP seam:四项闭合操作
一句话结论:四项语义查询构成闭合联合,符号与调用层级不在这里——它们需要不同的 schema。
- 四项操作:定义跳转、引用查找、实现跳转、悬停。
- 闭合联合:新增操作会编译失败,强制同步修改 seam、提供方与工具。
- 不包含什么:符号列表与调用层级不属于这四项,别硬塞。
- 分层:服务定义、通用 stdio 提供方、面向模型的
lsp工具,各管一层。 - 可选能力:不在 agent loop 主干内,换提供方不改变模型请求导航的方式。
DSH plugin 的 LspQueryRequest:每个字段必填,没有 resolve
一句话结论:没有需要补默认值的字段,所以这套接口没有 resolve() 这一步。
interface LspQueryRequest {
operation: LspOperation
filePath: string // 相对 workspaceRoot 或绝对路径,由提供方规范化
position: LspPosition // 0 基 UTF-16
workspaceRoot: string // 必填,绝不默认
}
- workspaceRoot 由调用方给:提供方据此解析与索引。
- languageId 不在请求里:它来自提供方的扩展名映射,只用于同步瞬态文档,不参与提供方选择。
- 超时与结果上限归消费方:提供方不负责这些策略。
- 提供方收到的形状:请求加上派生出的
languageId。 - 验证:传一个不带
workspaceRoot的请求,应在调用前就被类型或校验拒绝。
DSH plugin 的坐标约定:0 基 UTF-16 与 1 基光标
一句话结论:协议层用 0 基 UTF-16,工具层用 1 基;转换只发生在边界。
- 位置:从零开始的行号与行内 UTF-16 码元偏移。
- 范围:半开区间
[start, end)。 - 工具层:面向模型的光标从 1 开始,输入与输出各转换一次。
- 常见错误:把工具层坐标直接传给提供方,导航整体偏移一行。
- 验证:在一个已知符号上做定义跳转,返回范围应精确覆盖该符号。
DSH plugin 的结果联合:locations 与 hover
一句话结论:导航统一成 locations,hover 统一成内容或 null;findReferences 一定包含声明。
| 操作 | 结果形态 |
|---|---|
goToDefinition | locations + resolvedWorkspaceUri |
findReferences | locations(含声明) |
goToImplementation | locations |
hover | hover 内容或 null |
- 穷尽处理:对
kind做switch,新增分支必须处理。 - 声明无开关:
findReferences始终包含声明,由提供方内部保证。 - 位置 URI 原样返回:
uri来自语言服务器,可能是file:也可能是别的。 - hover 范围可选:服务器给了就有,没给就没有。
- 提供方转换:把 LSP 原始响应规范化成这两类,别把协议细节漏给消费方。
DSH plugin 的 resolvedWorkspaceUri 与 LspProvider 契约
一句话结论:位置 URI 相对化时用提供方的规范工作区 URI,不要解析请求路径。
- 为什么不能用请求路径:它可能经过符号链接,而且执行平台可能与调用方不同。
- 正确做法:用结果里的
resolvedWorkspaceUri作为基准做相对化。 - 跨平台:提供方在它的执行世界里规范化,调用方不应套用自己的宿主路径规则。
- 验证:在带符号链接的工作区里做一次引用查找,相对路径应落在真实工作区内。
- 边界:如果结果 URI 不在工作区内,保持原样,不要强行相对化。
DSH plugin 排查注意事项
一句话结论:LSP 问题先分「选错提供方」「坐标偏移」「结果未规范化」三类。
- 没有结果 → 检查提供方是否注册、语言服务器是否就绪、
workspaceRoot是否正确。 - 偏移一行 → 坐标基准搞混了,检查 0 基与 1 基的转换。
- 引用里没有声明 → 不该发生,
findReferences由提供方保证包含。 - 相对路径错乱 → 用了请求路径而不是
resolvedWorkspaceUri。 - 想加第五种查询 → 那是一次跨包改动,不是加个分支就行。
- 修复状态如实:本文的四项操作、请求必填约束、坐标约定与结果联合均取自官方「LSP 导航」页面;接口签名以该页 Cordis API 与你所用版本为准。
常见问题
DSH plugin 的 LSP seam 恰好公开四项:goToDefinition、findReferences、goToImplementation 与 hover。这个联合是闭合的,新增操作会编译失败,强制同步修改 seam、提供方与工具。
DSH plugin 的 LspQueryRequest 每个字段都必填:workspaceRoot 由调用方给,languageId 来自提供方注册,超时与结果上限由消费方定。没有字段需要实现补默认值,因此不需要 resolve。
DSH plugin 的 seam 用 0 基 UTF-16 坐标,与 LSP 协议一致;面向模型的工具用 1 基光标约定,在输入与输出时做转换。跨层拼坐标之前先确认自己在哪一层。
不需要,DSH plugin 的 findReferences 始终包含声明,由提供方内部强制保证,调用方没有对应开关。位置结果统一走 locations 联合,不需要另写分支。
不应该,DSH plugin 要求用结果里的 resolvedWorkspaceUri:它是提供方的规范 file: URI。请求路径可能经过符号链接,执行平台也可能与调用方不同。
相关术语
- LspOperation(导航操作)
- LspOperation 是 DSH plugin 的 LSP seam 公开的四项语义查询,取值限定为 goToDefinition、findReferences、goToImplementation 与 hover。它是闭合联合,新增操作需要同步改 seam、提供方与工具。— https://deepseek-harness.github.io/deepseek-harness/reference/subsystems/lsp
- LspQueryRequest(查询请求)
- LspQueryRequest 是一次规范化导航查询,operation、filePath、position 与 workspaceRoot 全部必填。languageId 不在请求里,而由提供方的扩展名映射派生。— https://deepseek-harness.github.io/deepseek-harness/reference/subsystems/lsp
- LspQueryResult(查询结果)
- LspQueryResult 是闭合的可辨识联合:导航操作规范化为 locations,hover 规范化为内容或 null。消费方对 kind 做穷尽处理,新增分支会编译失败。— https://deepseek-harness.github.io/deepseek-harness/reference/subsystems/lsp
- resolvedWorkspaceUri
- resolvedWorkspaceUri 是提供方为请求工作区给出的规范 file: URI,随 locations 结果返回。相对化位置 URI 时必须用它,而不是请求里可能经过符号链接的路径。— https://deepseek-harness.github.io/deepseek-harness/reference/subsystems/lsp
来源
- DeepSeek Harness 文档 — LSP 导航(操作、请求、结果、提供方与服务)· deepseek-harness.github.io
- dsh-lsp 源码(跨包词汇)· deepseek-ai(GitHub)
- dsh-lsp-stdio(经配置的 stdio 语言服务器宿主)· deepseek-ai(GitHub)