LSP 代码智能
一句话版:
ctx.lsp是语义代码导航能力缝:跳转定义、找引用、找实现、hover:通过语言服务器,且只暴露这四个语义操作,没有泛型 JSON-RPC 逃生口。dsh-tool-lsp是它的模型可见工具。
让 agent 不只是 grep,而是真正"跳去定义 / 找所有引用 / 看实现"。
一、四个语义操作(没有逃生口)
ctx.lsp 恰好暴露四个:
goToDefinition 跳转定义
findReferences 找引用(总是包含声明)
goToImplementation 找实现
hover 悬停(内容或 null)
没有泛型 JSON-RPC 逃生口:所以不会有未经 review 的命令/突变经 ctx.lsp 到达 provider。这是刻意的安全边界。
二、包
| 包 | 角色 |
|---|---|
dsh-lsp | 服务定义:服务、按 brand id + 扩展名映射的 provider 注册表、按需选择、请求/结果词汇、LspError |
dsh-lsp-stdio | 服务 provider:通用 stdio 语言服务器后端 |
dsh-tool-lsp | 消费者:模型面向的 lsp 工具 |
三、Service API
| Member | 语义 |
|---|---|
registerProvider(provider) | 注册后端,原子保留它的 brand id + 每个归一化文件扩展名;输入非法/冲突不发布并抛 LspError(LSP_INVALID_PROVIDER/LSP_CONFLICT) |
query(request, signal?) | 按文件最终扩展名选 provider、derive languageId、跑一次查询;无匹配抛 LSP_UNAVAILABLE |
选择按查询、与顺序无关:provider 独家拥有一组扩展名,注册/HMR 顺序不影响路由。扩展名归一化成小写、带前导点形式;languageId 只同步瞬态文档,不参与选择。第一版没有 glob / language-id / 显式路由选择器。
Provider 注册的是能力,不是工具。
dsh-tool-lsp是模型可见名/描述/提示/schema/呈现的唯一所有者。
四、词汇
LspQueryRequest: { operation, filePath, position, workspaceRoot } 全必填:没有字段需要实现默认、无 resolve() 步。position/range 是零基 UTF-16(对应协议);工具的 one-based 光标惯例由它转。
LspQueryResult 是封闭判别联合:
- 导航:
{ kind: 'locations', locations, resolvedWorkspaceUri } - hover:
{ kind: 'hover', hover }(内容或 null)
resolvedWorkspaceUri 是 provider 的规范工作区 file: URI;调用方相对化 location URI 用它,而不是套宿主平台路径规则(可能 symlink)。
五、本地后端(lsp-stdio)
dsh-lsp-stdio 是通用 stdio 语言服务器后端:一个插件实例接受一个命名 server 表,每项注册一个隔离 provider。经 ctx.fs 读、ctx.subprocess 启动:server 与源码总在挂载的执行世界里。这是泛型 host,不是语言服务器目录或安装器;preset 放 cordis.yml overlay。
关键行为:
- 每个
(server id, 规范工作区)懒单飞一个 server 进程;live server 错误不重放,查询失败在 fresh 进程上重试一次 - transient-open:每次查询 resolve+字节限定源码,
didOpen(version 1 全文本)→ 请求 →finally里didClose。文档每次查询后关闭,第一版不需要 didChange/内容缓存/LRU - 每次 source-read/open/query/close 通过一个可 abort 的 per-workspace 队列串行;不同工作区并行
- 协议 shutdown 失败 → 经 subprocess 终止 server 子孙树(POSIX group / Windows taskkill)
ctx.fs规范 containment、file URI、流式文本校验;但不发fs/observed:只 LSP 结果对模型可见
六、配置
- id: lsp
name: '@deepseek-ai/dsh-lsp-stdio'
config:
servers:
typescript:
command: typescript-language-server # 必填:spawn 的可执行
args: ['--stdio']
extensionToLanguage: # 必填:小写点前缀扩展名 → languageId
'.ts': typescript
'.tsx': typescript
python:
command: pyright-langserver
extensionToLanguage:
'.py': python
servers 的 key 是在 ctx.lsp 上保留的稳定 provider id;command 必填,启动不用 shell。
七、验证
# 看 lsp 是否装载 + 配了几个 server
dsh web --dump-config | grep -iE "lsp"
# 问 agent "goToDefinition 某个符号",会话里看结果
zstdcat ~/.dsh/sessions/*/*/session.jsonl.zstd | grep -E '"tool/result"' | tail