
oh-my-pi lsp 工具深度解析语言服务器接入、14 种动作与参数/超时/限制全解【免费下载链接】oh-my-pi⌥ Coding agent with the IDE wired in项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-pi本文基于 oh-my-pi一个把 IDE 能力接入的 coding agent中的 lsp 工具参考文档 展开系统讲解其lsp工具如何通过 LSP 客户端进程、JSON-RPC 请求与服务器路由为 Agent 提供诊断、导航、符号、重命名、代码操作、能力查询与原始请求共 14 种动作。读完本文你能完整掌握lsp工具每个参数的语义action、file、line、symbol、query、apply、timeout等、超时钳制与各类上限常量的实际取值并能对照 工具实现、LSP 客户端、配置加载 与 内置服务器定义 等源码理解其底层调用链。为什么 Agent 需要 lsp 工具纯文本工具正则替换、sed、AST 编辑在跨文件重命名、查找引用、跳转定义时容易漏掉调用点遮蔽shadowing、再导出re-exports、跨文件使用都会让文本级修改静默失效。oh-my-pi 的模型侧提示词lsp 工具 prompt明确给出了纪律性要求凡是符号敏感的工作rename、references、definition、code actions只要语言服务器可用必须使用lsp工具当lsp的rename/rename_file可用时绝不用ast_edit/sed/手工编辑做跨文件重命名——文本重命名会静默丢失调用点在手工编辑前优先用code_actions处理 import、快速修复和服务器已知的重构。该工具定位为按需可发现discoverable而非急切加载它只在会话允许 LSP 且lsp.enabled开启时注册。注册与启用门控lsp 工具何时存在在 工具注册表 中lsp通过LspTool.createIf注册。其存在需要同时满足会话级session.enableLsp ! false源码中enableLsp默认取true见 tools/index.ts设置项lsp.enabled默认true允许门控逻辑位于 tools/index.tsif (name lsp) return enableLsp session.settings.get(lsp.enabled)。此外还有只读会话约束带有lspReadOnly的会话会拒绝一切不在LSP_READONLY_ACTIONS集合定义于 servers.ts内的动作受限会话若显式重新启用 LSP则默认同时置为禁用与只读。审批语义同样值得注意只读动作diagnostics、各类导航、hover、symbols、status、capabilities请求读审批而rename、rename_file、code_actions、reload、request无论apply取值如何一律请求写审批。输入参数全表字段类型必填说明action字符串枚举是diagnostics、definition、references、hover、symbols、rename、rename_file、code_actions、type_definition、implementation、status、reload、capabilities、request之一。file字符串否文件路径对diagnostics也可以是 glob工作区级形态使用*对rename_file是源路径。line数字否位置类动作的 1-based 行号单文件动作路径上默认1。symbol字符串否用于在line上解析列号的子串支持name#N出现次数选择器N为 1-based默认1。对definition/references/rename而言若对项目感知型服务器传了line则symbol必填。query字符串否工作区符号查询、代码操作选择/过滤或actionrequest时的 LSP 方法名。new_name字符串否rename与rename_file必填。apply布尔否rename/rename_file默认应用仅当显式false时预览code_actions默认为列表模式仅当显式true时才应用。timeout数字否秒默认20经clampTimeout(lsp, ...)处理——先套用正的tools.maxTimeout全局上限再套用该工具5..300的范围因此 5 秒下限仍会压过更低的全局上限。payload字符串否actionrequest的 JSON 参数串存在时覆盖自动构建的参数。超时钳制的实现可以直接在 tool-timeouts.ts 中看到TOOL_TIMEOUTS.lsp { default: 20, min: 5, max: 300 }clampTimeout()先用全局maxTimeout若为正限制解析值——包括未传 timeout 走默认值的路径——再夹到工具的 min/max 区间maxTimeout 0表示无全局上限。输出结构单一文本块 details结果是一个一次性AgentToolResultcontent恒为单个文本块[{ type: text, text: string }]。details为LspToolDetails类型定义见 types.ts包含action、success、可选serverName、可选原始request。空结果的导航/符号查询如No definition found会被额外标记useless: true以便上下文压缩时省略而干净的诊断结果会被保留作为验证证据。无流式更新、无 artifact URI、无后台任务。内联 TUI 渲染器会合并调用与结果支持按动作定制的格式化和折叠/展开视图。许多校验失败以普通文本返回并附带details.success: false中止abort则抛ToolAbortError。端到端执行流程对照源码LspTool.execute()实现位于 tool.ts文档中描述的入口packages/coding-agent/src/lsp/index.ts现为再导出层LspTool由 index.ts 导出的执行链如下超时钳制与信号合并clampTimeout(lsp, ...)得到秒数构建AbortSignal.timeout(...)并与调用方 signal 组合。配置加载getConfig()config.ts按 cwd 加载并缓存LspConfig后续调用复用缓存。工作区reload是显式例外它会先清除并重建该 cwd 的配置缓存再重新加载新选中的服务器。配置合并与自动发现配置加载把 defaults.json 与来自项目、项目配置目录、用户配置目录、插件根/市场元数据、home 的 JSON/YAML 覆盖合并若没有任何覆盖则根据根标记root markers加可执行文件发现来自动检测服务器。文件名与优先级详见 LSP 配置文档。服务器路由getServersForFile()/getServerForFile()config.ts先按扩展名或 basename 匹配再把主服务器排在 linter 之前getLspServersForFile()/getLspServerForFile()servers.ts进一步把自定义 linter 客户端排除在导航/重构路径之外。客户端获取getOrCreateClient()按command:cwd缓存每命令一个客户端。默认lsp.sharedtrue时先向 broker 管理的项目级 mux 请求共享传输失败则回退到私有ptree.spawn()外部lspmux包装优先于 broker 共享。随后客户端启动消息读取循环、发送initialize、保存能力、发送initialized。消息读取循环client.ts解析 LSP 帧、解决挂起请求、缓存publishDiagnostics、跟踪$/progresstoken 以判断项目加载完成、应答workspace/configuration并通过applyWorkspaceEdit()应用服务器发起的workspace/applyEdit请求。文件打开与列解析文件级动作在请求前调用ensureFileOpen()列号解析由 utils.ts 的resolveSymbolColumn()完成——读取目标文件symbol省略时取首个非空白字符列否则在目标行上精确或忽略大小写匹配并遵守#N选择器。动作分发工作区专属分支status、部分diagnostics、工作区symbols、工作区reload、capabilities、request先于单文件switch(action)执行其余单文件动作共享一次客户端查找。请求发送sendRequest()client.ts分配自增 JSON-RPC id、安装中止与超时处理中止时发送$/cancelRequest超时或进程退出时 reject。编辑落地返回编辑类动作要么用formatWorkspaceEdit()预览要么用 edits.ts 的applyWorkspaceEdit()应用rename_file还执行真正的文件系统重命名并发送workspace/didRenameFiles。失败转换单文件动作块内的非中止失败转为LSP error: ...文本大量前置条件失败直接返回显式文本而不抛异常。逐动作详解输入、执行与输出diagnostics单文件、glob 与工作区三形态file: *或省略 file进入工作区模式runWorkspaceDiagnostics()workspace-diagnostics.ts按 Rust → TypeScript → Go 工作区/模块 → Python 的顺序选取第一个匹配的项目类型实际执行的检查命令为Rustcargo check --message-formatshortTypeScriptnpx tsc --noEmitPythonpyrightGogo build其中go.mod项目用./...go.work项目先运行go work edit -json解析出每个Use[].DiskPath/...模式再逐一构建解析逻辑见 workspace-diagnostics.ts失败回退./...。未知项目直接返回受支持标记提示而不生成检查器。具体文件或 glob 形态则由resolveDiagnosticTargets()处理非 glob 视为单目标否则展开Bun.Glob最多取前MAX_GLOB_DIAGNOSTIC_TARGETS个匹配。对每个目标文件所有匹配服务器都会执行自定义 linter 客户端调用lint(file)真实 LSP 服务器可选地等待项目加载、记录diagnosticsVersion、refreshFile()后waitForDiagnostics()等待新鲜的publishDiagnostics以最新一次发布为准版本精确匹配则立即接受。结果按范围消息去重并按严重度排序。输出形态单目标无问题OK单目标有问题摘要:\n分组诊断批量/glob每文件一节glob 超出文件上限时前置截断警告工作区模式Workspace diagnostics (检测到的描述):\n命令输出。diagnostics是唯一同时查询常规 LSP 服务器与自定义 linter 客户端BiomeClient、SwiftLintClient或LspLinterClient见 clients 目录的动作。definition / type_definition / implementation三者共用同一套位置归一化与输出形态仅方法与措辞不同definition发送textDocument/definitiontype_definition发送textDocument/typeDefinition并报告type definition(s)implementation发送textDocument/implementation并报告implementation(s)。接受Location、Location[]、LocationLink、LocationLink[]四种返回normalizeLocationResult()把LocationLink归一为targetSelectionRange ?? targetRange。请求前会等待项目加载project load。关键差异definition在项目感知型服务器上若给了line则必须给symbol取首个非空白列的兜底对该动作被禁用而type_definition与implementation不强制symbol缺省时解析首个非空白列。输出No definition found或Found N definition(s):每个位置附file:line:col及上下各一行上下文。references发送textDocument/references且带includeDeclaration: true。与definition相同项目感知型服务器传line时symbol必填。针对项目感知型服务器若唯一命中就是被查询的声明本身会以REFERENCES_RETRY_COUNT次重试期间等待项目加载并睡眠REFERENCES_RETRY_DELAY_MS——这是对项目索引尚未完成导致引用不全的补偿策略。前REFERENCE_CONTEXT_LIMIT条引用带源码上下文其余仅给位置。输出为No references found或Found N reference(s):截断时附... M additional reference(s) shown without context。hover发送textDocument/hoverextractHoverText()把字符串、markup 内容、marked-string 对象或数组拍平为纯文本。输出为No hover information或拍平后的 hover 文本。symbols文档符号与工作区符号工作区模式要求file: *与query省略file会先触发Error: file parameter required...。向每个非自定义 LSP 服务器发送workspace/symbol再用filterWorkspaceSymbols()后过滤、dedupeWorkspaceSymbols()去重最后截断到WORKSPACE_SYMBOL_LIMIT。输出Found N symbol(s) matching query:加name file:line:col超限附省略行。文档模式向主服务器发送textDocument/documentSymbol。若首项带selectionRange按层级DocumentSymbol格式化否则按扁平SymbolInformation格式化。输出Symbols in file:加层级/扁平符号行。rename要求file、new_name可选line、symbol、apply、timeout。项目感知型服务器传line时要求symbol随后等待项目加载发送textDocument/rename并接收WorkspaceEditapply ! false默认立即applyWorkspaceEdit()应用输出Applied rename:加变更行apply falseformatWorkspaceEdit()渲染预览输出Rename preview:加摘要编辑无编辑返回Rename returned no edits。rename_file真正的移动文件 重写全部引用要求源路径file与目标new_name。执行链对应 tool.ts 中的MAX_RENAME_PAIRS与enumerateRenamePairs()解析绝对源/目标拒绝相同路径、源不存在、目标已存在、空重命名集或超过MAX_RENAME_PAIRS个文件的目录enumerateRenamePairs()对单文件返回一个{oldUri,newUri}对目录则遍历其下所有常规文件生成并行对向所有fileTypes匹配任一受影响路径的非自定义 LSP 服务器发送workspace/willRenameFiles: { files: pairs }收集返回的WorkspaceEdit与服务器备注预览模式apply false只格式化编辑应用模式按 URI 合并文本编辑项目感知型服务器在重叠区域胜出其他服务器的重叠编辑被丢弃并附注、每个 URI 从单一快照应用一次、创建目标父目录并在磁盘上重命名源路径、对每个被重命名的打开文件发送textDocument/didClose并删除对应openFiles条目最后发送workspace/didRenameFiles。输出预览为Rename preview: 文件数标签 → dest加各服务器编辑摘要与备注应用为Renamed 文件数标签 → dest加已应用编辑摘要、文件系统重命名行与备注。code_actions列表选择器与应用选择器的双语义query要求file可选line、symbol、query、apply、timeout。执行时从client.diagnostics读取该 URI 的缓存诊断在解析出的位置以零宽范围发送textDocument/codeAction。query有两种截然不同的语义列表模式apply ! truequery作为context.only: [query]传给服务器是服务端kind 过滤应用模式apply true且query非空query变成客户端侧选择器——零基数字索引或动作标题的忽略大小写子串注意apply true但省略query时当前实现落回列表模式不会应用任何动作。应用CodeAction走applyCodeAction()可选codeAction/resolve→applyWorkspaceEdit(edit)→ 可选workspace/executeCommand裸Command只执行workspace/executeCommand。输出列表模式N code action(s):加index: [kind] title行应用成功Applied title:加Workspace edit:和/或Executed command(s):段未命中No code action matches query. Available actions:无编辑无命令Action title has no workspace edit or command to apply。status / reload / capabilities / requeststatus无输入。从缓存LspConfig读配置服务器并用getActiveClients()交叉比对把每个服务器标注为(configured, not started)或其活动客户端状态调用detectLspmux()若lspmux已安装则附加状态行。输出形如Language servers: name (configured, not started) | name (status)或No language servers configured for this project可选附lspmux: active (multiplexing enabled)/lspmux: installed but server not running。reload工作区模式file: *或省略先失效按 cwd 的配置缓存、从磁盘重载配置再重载所有新配置的非自定义服务器单文件模式保留缓存配置、只重载该文件的主服务器。两种模式都会清除匹配的近期初始化失败负缓存以便立即重试。对 rust-analyzer 服务器reloadServer()优先尝试rust-analyzer/reloadWorkspace请求仅 rust-analyzer 实现它发给 Roslyn 等其他服务器可能导致崩溃因此按服务器二进制/名称门控所有服务器随后回退到携带当前配置的workspace/didChangeConfiguration通知若通知失败则拆除客户端让下次请求冷启动。共享 mux 客户端的拆除会先发送 mux 重启通知替换的是共享服务器本身而不只是本会话的链路。输出每服务器一行Reloaded server、Restarted server或Failed to reload server: ...。capabilities具体file时检查该文件的匹配非自定义服务器省略或*时检查所有非自定义配置服务器。按需启动服务器后把client.serverCapabilities ?? {}以缩进 JSON 输出失败则为server: failed to start (...)。request要求query方法名。参数构建优先级1) 有payload则解析 JSON 原样使用2) 否则具体file且有line构建{ textDocument: { uri }, position: { line: line - 1, character } }列号由resolveSymbolColumn()解析3) 否则具体file构建{ textDocument: { uri } }4) 否则{}。file具体时先打开文件。成功输出server ← method:\n格式化结果非字符串结果JSON.stringify(..., null, 2)nullish 变null失败输出LSP error from server on method: ...并附截断到 400 字符的params: 预览。注意file: *等价于省略 file不构建工作区专属参数。路由与工作区作用域规则小结file: *只对diagnostics、symbols、reload特殊化status忽略filecapabilities省略 file 或*时检查所有非自定义服务器request省略 file 或*时选第一个可用的非自定义服务器具体 file 时选该文件的主非 linter 服务器rename_file广播给所有fileTypes匹配的非自定义服务器而不只是单个文件级服务器getServersForFile()同时匹配扩展名与精确 basename配置若含Dockerfile这类名字即可命中见 defaults.json 中 dockerls 的fileTypes: [.dockerfile, Dockerfile]getLspServerForFile()排除createClient适配器与纯 linter 服务器——导航/重构动作永远不会打到 Biome/SwiftLint 等自定义客户端。内置服务器目录与自动发现defaults.json 内置了约 40 个服务器定义每个包含command、args、fileTypes、rootMarkers可选settings、initOptions、isLinter。摘选如下完整列表见源文件服务器command根标记rootMarkers备注rust-analyzerrust-analyzerCargo.toml,rust-analyzer.toml关闭checkOnSave声明 flycheck/ssr/expandMacro 等能力clangdclangd --background-index --clang-tidy --header-insertioniwyucompile_commands.json,CMakeLists.txt,.clangd,.clang-format,MakefileC/C/CUDA/ObjCgoplsgopls servego.mod,go.work,go.sum开启 unusedparams/shadow 分析、staticcheck、gofumpttypescript-language-servertypescript-language-server --stdiopackage.json,tsconfig.json,jsconfig.json初始化即请求 inlay hintspyright / basedpyrightpyright-langserver --stdiopyproject.toml,pyrightconfig.json,setup.py等diagnosticMode: openFilesOnlybiomebiome lsp-proxybiome.json,biome.jsoncisLinter: trueeslintvscode-eslint-language-server --stdio.eslintrc*,eslint.config.*isLinter: trueruffruff serverpyproject.toml,ruff.toml,.ruff.tomlisLinter: trueswiftlintswiftlint lint --quiet --reporter json.swiftlint.yml,Package.swift等isLinter: trueCLI 型dockerlsdocker-langserver --stdioDockerfile,docker-compose.yml等用 basename 匹配 Dockerfile当没有用户覆盖配置时自动发现流程即依据这些rootMarkers检测项目根、再探测可执行文件是否存在来决定启用哪些服务器。更多字段语义与配置优先级见 LSP 配置文档。限制与上限常量可验证清单以下常量均可在源码中逐一对照是阅读调试日志与超时问题时的重要参照限制值位置工具超时钳制默认20秒下限5上限300秒tool-timeouts.tsTOOL_TIMEOUTS.lspLSP 请求默认超时30_000msclient.tsDEFAULT_REQUEST_TIMEOUT_MS预热 initialize 超时5_000msclient.tsWARMUP_TIMEOUT_MS项目加载等待兜底15_000msclient.tsPROJECT_LOAD_TIMEOUT_MS空闲客户端扫描间隔启用时60_000msclient.tsIDLE_CHECK_INTERVAL_MS初始化失败退避3 * 60 * 1000ms匹配的reload会清掉负缓存使重试立即发生client.tsINIT_FAILURE_BACKOFF_MS诊断消息输出上限前50条DIAGNOSTIC_MESSAGE_LIMIT单文件诊断等待3_000msSINGLE_DIAGNOSTICS_WAIT_TIMEOUT_MS批量/glob 诊断每文件等待400msBATCH_DIAGNOSTICS_WAIT_TIMEOUT_MSglob 诊断目标上限前20个匹配MAX_GLOB_DIAGNOSTIC_TARGETS工作区符号上限前200条WORKSPACE_SYMBOL_LIMIT引用上下文上限前50条带源码上下文REFERENCE_CONTEXT_LIMIT引用重试2次、250ms退避REFERENCES_RETRY_COUNT/REFERENCES_RETRY_DELAY_MS目录重命名对数上限1_000对tool.tsMAX_RENAME_PAIRSlspmux 状态缓存 TTL / 存活检查超时5 * 60 * 1000ms/1_000mslspmux.tsSTATE_CACHE_TTL_MS/LIVENESS_TIMEOUT_MS工作区诊断输出上限子进程输出前50行workspace-diagnostics.ts错误处理语义缺少或非法输入通常以文本返回并带details.success: false而不是抛异常缺file/query/new_name、payload非法 JSON、无匹配服务器、非法的rename_file源/目标条件均属此类。resolveSymbolColumn()对文件缺失、符号缺失、#N越界会抛显式错误最终表现为LSP error: ...或动作专属错误文本。sendRequest()超时 reject 消息为LSP request method timed out after msms客户端进程退出会用退出码/stderr 组装错误并 reject 所有挂起请求。一些服务器失败被有意软化单个服务器失败不阻断 diagnosticsrename_file抑制workspace/willRenameFiles的 method not found 错误其他服务器错误记为备注code_actions忽略codeAction/resolve失败并尽量应用未解析的动作。调用方中止不转文本ToolAbortError原样重抛而墙钟工具超时无调用方中止抛ToolErrorLSP action timed out after Ns on server. ...。共享传输、lspmux 与副作用边界共享传输默认lsp.sharedtrue时SDK 会话先尝试本地 Unix socketWindows 上为命名管道连接 broker 管理的按项目 LSP muxmux 不可达或无法启动时静默回退到私有子进程。外部lspmux包装detectLspmux()可用PI_DISABLE_LSPMUX1禁用优先于 broker 共享内置支持列表DEFAULT_SUPPORTED_SERVERS目前仅含rust-analyzer支持被lspmux client包装。IPC/子进程私有与外部多路复用服务器均走本地 stdio JSON-RPC工具本身不做远程网络请求工作区诊断会派生cargo/npx/go/pyright子进程BiomeClient、SwiftLintClient派生 CLI 工具。文件系统读取配置文件、目标文件与根标记rename/code_actions可经applyWorkspaceEdit()编辑/创建/删除/重命名文件rename_file应用模式必定在磁盘上重命名源路径服务器发起的workspace/applyEdit也会经由applyWorkspaceEdit()变更文件。会话状态configCache按 cwd 按进程缓存、不会自动失效——需要工作区reload省略file或file: *重读配置、根标记与插件配置具体文件的 reload 只重载该服务器、保留缓存配置。LSP 客户端按command:cwd缓存附带pendingRequests、diagnostics、openFiles、serverCapabilities与项目加载状态传输层可能代表一条共享 mux 链路而非自有进程。自定义 linter 客户端按serverName:cwd缓存。空闲清理由workspace idleTimeoutMs或setIdleTimeout()驱动。取消与后台每个请求都带可中止的超时信号中止进行中的 LSP 请求会发送$/cancelRequest后台消息读取循环随客户端存活直至进程退出/关闭。启动发现与惰性行为启动期 LSP 发现sdk.ts中的discoverStartupLspServers(cwd)仅在enableLsp options.hasUI时运行后台预热另需!settings.get(lsp.lazy)。lsp.lazy默认为true因此默认情况下发现的服务器以available状态欢迎屏灰点呈现并通过getOrCreateClient()在首次使用时冷启动lsp 工具调用或对匹配文件类型的 edit/write。Print/RPC/ACP/脚本会话完全跳过发现与预热。详见 sdk 文档 的 Startup performance 小节。实现细节备忘Notes 精要status中(configured, not started)表示二进制可在 PATH 解析但尚无请求派生它活动客户端则报告其状态。symbol匹配顺序先精确、再忽略大小写、再按行内第 N 次出现回退只在指定行内查找绝不扫描其他行。对项目感知型服务器definition/references/rename在传line但省略symbol时会以ToolError拒绝而不是静默回退到首个非空白列。rename与rename_file默认应用预览必须显式apply: false。reload杀掉客户端后不会立即重建下一次请求触发重新初始化。workspace/applyEdit可以在直接工具动作结果路径之外应用服务器发起的编辑——这是理解工具调用后文件为何变了的关键旁路。本文所有路径均相对 oh-my-pi 仓库根目录。建议延伸阅读lsp 工具文档、LSP 配置、工具实现、客户端实现 与 内置服务器目录。【免费下载链接】oh-my-pi⌥ Coding agent with the IDE wired in项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-pi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考