docs/adr/0007-lsp-integration.mdXem bản gốc Bản dịch đầy đủ theo đúng cấu trúc tài liệu; code, liên kết và định danh kỹ thuật được giữ nguyên.
0007. Tích hợp LSP — ty-thông tin mã Python được hỗ trợ thông qua hai kênh, trên máy khách stdio được cuộn bằng tay
Trạng thái: Đã chấp nhận Ngày: 2026-06-27
Bối cảnh
Các công cụ của decode sẽ xem cơ sở mã dưới dạng văn bản: read dòng số, grep kết quả phù hợp với biểu thức chính quy, glob danh sách
những con đường. Họ không thể trả lời "ký hiệu này được xác định ở đâu?", "ai gọi nó?" hoặc "điều này có chỉnh sửa không?
kiểm tra kiểu?" - chế độ xem biểu đồ ngữ nghĩa. Khung nghiên cứu (dự án wiki
concepts/lsp-integration.md và comparisons/lsp-integration-3way.md) đặt trục thực là
codebase-as-semantic-graph so với codebase-as-text và xác định hai kênh phân phối trực giao
để tích hợp LSP decode sẽ gửi ** cả hai **:
- kênh hoạt động — một công cụ có thể gọi theo mô hình mà agent gọi theo yêu cầu;
- kênh thụ động — chẩn đoán sau chỉnh sửa được xếp vào đầu ra của chỉnh sửa, được gọi là "ROI tốt nhất của việc tích hợp LSP."
Cây mục tiêu AGENTS.md đã dự trữ services/ dành cho "máy chủ LSP"; đây là bê tông đầu tiên
nhập cảnh. Các bài học xuyên suốt đáng tôn vinh: khởi chạy từng root một cách lười biếng được khóa bởi (root, server), cache
sinh sản bị hỏng và giữ mọi thứ nỗ lực tối đa để máy chủ bị thiếu/chậm không bao giờ bị hỏng lượt.
Những hạn chế của dự án: cơ sở hạ tầng được nhập/gọi trực tiếp, không bị trừu tượng hóa, cho đến khi triển khai thứ hai tồn tại (AGENTS.md); không đồng bộ cho I/O mạng/quy trình con; bí mật không bao giờ quan trọng ở đây (không có thông tin xác thực); CI đang ngoại tuyến và các bài kiểm tra đơn vị không được tạo ra các quy trình con nặng.
ADR này ghi lại tính năng LSP (nhiệm vụ 050-056, tính năng lsp-integration) dưới dạng thiết kế một
quyết định.
Quyết định
-
Cả hai kênh, tối thiểu, chỉ dành cho Python.
- Hoạt động: một mô hình duy nhất có thể gọi được
lspdụng cụ có bề mặt bốn lớp —definition,references,hover,diagnostics. Đó làToolKind.READ_ONLY(nó chỉ đọc mã trí thông minh), vì vậy - chính xác nhưread/web_fetch- nó giơ lênApprovalRequiredcho đến khi có cuộc gọi được phê duyệt và cổng cấp phép tự động cho phép nó bên dướidefaultchế độ (không có lời nhắc của con người). Ánh xạ không có sẵn/không xác định-op/bad-args tớiModelRetry(có thể đọc được trên mô hình), không bao giờ gặp sự cố. - Bị động: Công cụ hỗ trợ chẩn đoán — sau thành công
write/edittrên một.pytập tin, một yêu cầu chẩn đoán kéo chạy và một bản tóm tắt chỉ có lỗi được thêm vào phần trả về của công cụ chuỗi (hiện cóWrote …/Edited …chuỗi cơ sở được giữ chính xác; khối được nối thêm chỉ khi có lỗi). Nó đi theo sự chấp thuận đã được cấp của bản chỉnh sửa — không có cổng bổ sung — và im lặng trên các tệp sạch và bất cứ khi nào máy chủ không khả dụng. Kiểm soát bởilsp_diagnostics_on_edit(mặc địnhTrue) dưới sự chủlsp_enabled.
- Hoạt động: một mô hình duy nhất có thể gọi được
-
Máy chủ:
ty(Astral), có thể hoán đổi. Máy chủ ngôn ngữ mặc định làty server(stdio LSP) — cùng nhà cung cấp với dự ánruff+uv, được cài đặt quauv add --dev ty(được gọi là quy trình con, không bao giờ được nhập). Nó hỗ trợ tất cả bốn hoạt động cộng với ký hiệu/phần hoàn thành tài liệu (chúng tôi gửi chỉ có bốn) và mô hình chẩn đoán kéo (textDocument/diagnosticyêu cầu → phản hồi), vì vậy khách hàng hoàn toàn là yêu cầu/phản hồi - KHÔNGpublishDiagnosticsxử lý đẩy. Máy chủ là một cài đặt có thể thay đổi (lsp_server_command+lsp_server_args), vậypylsp(hoặc bất kỳ LSP stdio nào server) là một tài liệu thả vào. Cảnh báo trung thực:tylà pre-1.0 / xem trước (~v0.0.55) — được ghim bởiuv.lock; chúng tôi chấp nhận rủi ro rời bỏ đối với sự liên kết và tốc độ của nhà cung cấp cũng như đường may có thể tráo đổi là cửa thoát hiểm. -
Ứng dụng khách: một ứng dụng khách JSON-RPC-over-stdio mỏng được cuộn bằng tay trong một giao diện mới
src/decode/services/lsp/gói (~230 dòng lệnh, nặng chuỗi tài liệu) — đầu tiênservices/nhập cảnh. Không có thư viện giao thức (khôngmultilspy, khônglsprotocol): dạy dây là điểm. Nó sinh sảnty servervới ống stdio, khôngContent-Length+Khung JSON,initialize/initializedbắt tay,textDocument/didOpen, sau đódefinition/references/hover+ sự kéodiagnosticyêu cầu, khớp các câu trả lời theo id;shutdown/exitkhi đóng ứng dụng. Đó là async (quy trình con asyncio) để phù hợp với các công cụ không đồng bộ của bộ decode/ vòng lặp. Nó đọc các vị trí 0-dựa trên của LSP trên dây nhưng hiển thị dòng/cột 1 dựa trên bề mặt cho người gọi (phù hợp vớiread'scat -nvàgrep'spath:lineno), chuyển đổi tại ranh giới duy nhất này -
Lười biếng, một máy chủ cho mỗi gốc dự án, đằng sau một đường nối cấp mô-đun — phản ánh bash's
_EXECUTOR. Máy chủ được khởi tạo trong lần sử dụng đầu tiên và được lưu vào bộ nhớ đệm cho mỗi root; một sinh sản bị hỏng/không thành công được lưu vào bộ nhớ đệm (a canh gác) nên bị mất tích hoặc bị rơitykhông gây ra cơn bão thử lại trong mỗi lần gọi công cụ/chỉnh sửa — gốc đó "không khả dụng" cho đến khi quá trình khởi động lại. Điểm sinh sản là đường nối có thể vá được (nhưweb.py's_TRANSPORT) vì vậy các bài kiểm tra đơn vị sẽ đưa vào một quy trình giả mạo các câu trả lời được đóng khung sẵn — không thựcty/quy trình con trong các bài kiểm tra đơn vị. cáclspcông cụ tiếp cận khách hàng thông qua đường nối này; cái trình làm giàu tiếp cận chẩn đoán thông qua trình trợ giúp nỗ lực tốt nhất đồng bộ hóa trên cùng một dịch vụ (một bộ nhớ đệm, một khách hàng). -
Đồng bộ→cầu nối không đồng bộ cho bộ làm giàu.
write/editlà các hàm đồng bộ (I/O tệp cục bộ; pydantic-ai chạy chúng trong chuỗi công việc Anyio) trong khi máy khách không đồng bộ. Do đó, chất làm giàu gọi trình trợ giúp dịch vụ sync kết nối với ứng dụng khách không đồng bộ trong nội bộ (ví dụ:anyio.from_thread.run, hợp lệ từ chuỗi công việc), là nỗ lực tốt nhất và trả vềNonetrên bất kỳ lỗi nào (không có cổng thông tin, hết thời gian chờ, không khả dụng hoặc không có lỗi). Điều này giữfiles.pyđồng bộ và đơn giản và giới hạn cây cầu ở một nơi; kiểm tra đơn vị vá trình trợ giúp đồng bộ hóa, do đó cầu nối chỉ chạy trong tích hợp/sử dụng thực tế. -
Nỗ lực hết mình ở mọi nơi;
lsp_enabledlà cổng chính. Lỗi sinh sản, hết thời gian theo yêu cầu (lsp_request_timeout_s, mặc định10.0s; việc khởi tạo cũng bị giới hạn), đường ống kín hoặc tất cả khung không đúng định dạng đều chuyển thành "không khả dụng" — không bao giờ có ngoại lệ trong vòng lặp, lớp công cụ hoặc sự trở lại của một bản chỉnh sửa.lsp_enabled == Falsechập mạch đến mức "không khả dụng" và không xuất hiện chút nào. Máy chủ đã tắt trên máy chủ hiện tạirun_appđường dẫn thoát (bên cạnh việc ghi lại bộ nhớ), không gây tử vong và không hoạt động khi không có gì được sinh ra. -
Cài đặt (theo cấu hình, được phản ánh trong
.env.example):lsp_enabled(True),lsp_server_command("ty"),lsp_server_args(["server"]),lsp_diagnostics_on_edit(True),lsp_request_timeout_s(10.0).tylà một phần phụ thuộc dev-group — vì vậy một lượt cài đặt không có dev deps chỉ đơn giản là chuyển sang trạng thái "không khả dụng", phù hợp với tư thế nỗ lực hết sức.
Sơ đồ
flowchart TB
subgraph agent["Agent loop / tools"]
lsptool["ACTIVE: lsp tool (READ_ONLY, auto-allowed)<br/>op = definition | references | hover | diagnostics<br/>1-based line/col · unavailable/bad-args → ModelRetry"]
edit["write / edit (sync, gated)<br/>base string kept EXACT"]
enrich["PASSIVE: Diagnostics Enricher<br/>.py-only · errors-only · rides edit approval<br/>append block only when errors · else silent"]
end
subgraph svc["LSP Service — services/lsp/ (first services/ entry)"]
seam["module-level per-root cache + spawn seam<br/>(mirrors bash _EXECUTOR / web _TRANSPORT)<br/>lazy · 1 server/root · broken spawn cached"]
client["hand-rolled JSON-RPC/stdio client (~230 statement lines, NO lib)<br/>Content-Length framing · initialize/initialized<br/>didOpen · match-by-id · 0-based↔1-based"]
bridge["sync diagnostics-on-edit helper<br/>anyio.from_thread.run · best-effort → None"]
end
server(["ty server (stdio LSP) — swappable<br/>lsp_server_command/args · pre-1.0, uv.lock-pinned<br/>PULL diagnostics (no publishDiagnostics)"])
lsptool -->|async ops| seam
edit --> enrich --> bridge --> seam
seam --> client -->|spawn + JSON-RPC over stdio pipes| server
server -.responses matched by id.-> client
exitp["run_app exit path<br/>shutdown→exit→terminate · non-fatal · no-op if unspawned"]
exitp --> seam
gate{"lsp_enabled?"}
gate -.False: never spawn, always 'unavailable'.-> seam
classDef active fill:#1565c0,stroke:#0d47a1,color:#ffffff;
classDef passive fill:#6a1b9a,stroke:#4a148c,color:#ffffff;
classDef service fill:#2e7d32,stroke:#1b5e20,color:#ffffff;
classDef ext fill:#e65100,stroke:#bf360c,color:#ffffff;
classDef plain fill:#eceff1,stroke:#607d8b,color:#102027;
class lsptool active;
class edit,enrich passive;
class seam,client,bridge service;
class server ext;
class exitp,gate plain;
hậu quả
- decode có được chế độ xem biểu đồ ngữ nghĩa cùng với các công cụ văn bản, thông qua hai kênh hữu ích
độc lập: mô hình có thể truy vấn Code Intelligence theo yêu cầu và mọi
.pychỉnh sửa được miễn phí kiểm tra lỗi gấp nội tuyến - nửa ROI cao nhất. - Đầu tiên
services/nhập, không trừu tượng sớm. Dịch vụ LSP là cụ thể và trực tiếp (một khách hàng stdio); không có giao diện "dịch vụ" chia sẻ nào được giới thiệu cho đến khi máy chủ thứ hai (MCP, cổng LLM) đến - tôn vinh AGENTS.md "không trừu tượng nếu không có người gọi thứ hai." Cấp mô-đun trên mỗi gốc đường may tái sử dụng thành lập_EXECUTOR/_TRANSPORTmẫu, do đó nó có thể mô phỏng và hoán đổi được. - Dây cuộn bằng tay mang lại lợi ích cho việc giảng dạy và một khoản chi phí bảo trì nhỏ. Đóng khung/bắt tay/kết hợp- by-id là ~230 dòng lệnh mà chúng tôi sở hữu; việc nâng cấp thư viện giao thức bị cố tình bỏ qua. Chỉ kéo mô hình chẩn đoán giữ yêu cầu/phản hồi của khách hàng - không có máy trạng thái thông báo không đồng bộ.
tylà phiên bản trước 1.0 — được ghi lại một cách trung thực. Nó được ghim bởiuv.lockvà sống trong nhóm dev; cái cài đặt máy chủ có thể trao đổi là lối thoát nếutyrời bỏ hoặc người dùng thíchpylsp. Một cài đặt không có dev deps (hoặc không cótytrên PATH) âm thầm xuống cấp thành "không khả dụng" — theo thiết kế.- Nỗ lực tốt nhất là chịu tải. Máy chủ bị thiếu/chậm/gặp sự cố, cầu nối đồng bộ→không đồng bộ không có cổng,
hoặc khung không đúng định dạng không bao giờ ngắt lượt hoặc chỉnh sửa - trường hợp xấu nhất là không có nội tuyến
chẩn đoán hoặc một
ModelRetryyêu cầu model quay trở lạiread/grep. Sinh sản bị hỏng là được lưu vào bộ nhớ đệm để tránh các cơn bão thử lại trong mỗi lần chỉnh sửa. - Đường nối đồng bộ/không đồng bộ bị giới hạn.
write/editduy trì đồng bộ hóa (chuỗi cơ sở của chúng không thay đổi); cái một cầu nối đồng bộ→không đồng bộ tồn tại trong dịch vụ và được vá trong các thử nghiệm đơn vị, vì vậy bộ thử nghiệm không bao giờ sinh ra một quy trình con và CI vẫn ngoại tuyến. - CHỈ ĐỌC + không có cổng bổ sung giúp đảm bảo trải nghiệm người dùng nhất quán.
lspcông cụ tự động cho phép như các công cụ chỉ đọc khác công cụ; trình làm phong phú dựa trên sự chấp thuận hiện có của bản chỉnh sửa — không có lời nhắc mới, không có cổng kép. - Không phải mục tiêu (cố ý): hoạt động ngoài bốn (ký hiệu tài liệu, hoàn thành, đổi tên, định dạng),
publishDiagnosticspush, ngôn ngữ không phải Python, đăng ký nhiều máy chủ và quảng cáotyđến một phụ thuộc runtime - tất cả đều bị trì hoãn. Đường nối máy chủ đơn có thể tráo đổi là điểm mở rộng nếu có bất kỳ những điều này được xem xét lại.