Harness Academy
Tài liệu gốc 17 9 phútTiếng Việt + English
docs/adr/0007-lsp-integration.mdXem bản gốc
Ngôn ngữ bài đọc
Bản dịch tiếng Việt

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 **:

  1. 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;
  2. 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

  1. 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 lsp dụ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ên ApprovalRequired cho đế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ưới default chế độ (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ới ModelRetry (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/edit trên một .py tậ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ởi lsp_diagnostics_on_edit (mặc định True) dưới sự chủ lsp_enabled.
  2. 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ự án ruff + uv, được cài đặt qua uv 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/diagnostic yê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ÔNG publishDiagnostics xử lý đẩy. Máy chủ là một cài đặt có thể thay đổi (lsp_server_command + lsp_server_args), vậy pylsp (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: ty là pre-1.0 / xem trước (~v0.0.55) — được ghim bởi uv.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.

  3. Ứ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ên services/ nhập cảnh. Không có thư viện giao thức (không multilspy, không lsprotocol): dạy dây là điểm. Nó sinh sản ty server với ống stdio, không Content-Length+Khung JSON, initialize/initialized bắt tay, textDocument/didOpen, sau đó definition/references/hover + sự kéo diagnostic yêu cầu, khớp các câu trả lời theo id; shutdown/exit khi đó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ới read's cat -n và grep's path:lineno), chuyển đổi tại ranh giới duy nhất này

  4. 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ơi ty khô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ực ty/quy trình con trong các bài kiểm tra đơn vị. các lsp cô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).

  5. Đồng bộ→cầu nối không đồng bộ cho bộ làm giàu. write/edit là 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ề None trê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ế.

  6. Nỗ lực hết mình ở mọi nơi; lsp_enabled là 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 định 10.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 == False chậ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ại run_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.

  7. 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). ty là 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 .py chỉ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/_TRANSPORT mẫ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ộ.
  • ty là phiên bản trước 1.0 — được ghi lại một cách trung thực. Nó được ghim bởi uv.lock và sống trong nhóm dev; cái cài đặt máy chủ có thể trao đổi là lối thoát nếu ty rời bỏ hoặc người dùng thích pylsp. Một cài đặt không có dev deps (hoặc không có ty trê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 ModelRetry yêu cầu model quay trở lại read/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/edit duy 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. lsp cô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), publishDiagnostics push, ngôn ngữ không phải Python, đăng ký nhiều máy chủ và quảng cáo ty đế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.
Mục lục nguồn