GitHub Blog
95

Thủ thuật

GitHub dùng AI Agent để chuyển đổi 830.000 dòng code Copilot từ TypeScript sang Rust

(giờ Việt Nam)

Tóm tắt AI

GitHub đã hoàn tất việc viết lại toàn bộ runtime của Copilot từ Node.js sang Rust trong 14,5 tuần nhờ sự hỗ trợ của AI Agent, với hơn 128 PR được hợp nhất thành công vào hệ thống sản xuất.

Bản dịch AI

Việc viết lại với quy mô này là điều không thể thực hiện được trước khi có các tác nhân (agents). Dưới đây là những gì thực sự cần thiết để chuyển đổi (port) runtime của Copilot agent sang 800.000 dòng mã Rust trong môi trường production.

Ngày 16 tháng 9 năm 2026

65 phút

GitHub Copilot CLI, ứng dụng GitHub Copilot và GitHub Copilot SDK đều được vận hành bởi runtime của Copilot agent, một bộ khung tác nhân (agentic harness) có thể được nhúng vào các ứng dụng và dịch vụ. Ban đầu, nó được viết bằng TypeScript trên Node.js và engine JavaScript V8 cho cái mà hiện nay là GitHub Copilot cloud agent (CCA), và runtime này vẫn duy trì trên nền tảng đó khi runtime cùng các khả năng của nó phát triển nhanh chóng.

Điều đó giờ đây đã thay đổi. Sử dụng ứng dụng GitHub Copilot và Copilot CLI, chúng tôi đã viết lại hoàn toàn runtime thành hơn 800.000 dòng mã Rust trong môi trường production. Các AI agent đã viết phần lớn mã nguồn, trải dài trên 128 pull request được hợp nhất vào nhánh chính (main) và được triển khai dần dần thay vì chờ đợi một lần chuyển đổi duy nhất vào cuối dự án. Một vài lỗi hồi quy (regression) không thể tránh khỏi đã được phát hiện và khắc phục nhanh chóng trong quá trình thực hiện, trong khi hiệu suất của runtime đã cải thiện vượt bậc. Một dự án mà trước khi có các agent sẽ cần cả một đội ngũ lập trình viên làm việc trong một hoặc hai năm, nay đã được hoàn thành chủ yếu bởi một lập trình viên duy nhất, chỉ trong vài tháng, trong khi phần còn lại của đội ngũ vẫn tiếp tục mở rộng đáng kể các khả năng và phạm vi của runtime.

Tại sao chúng tôi cần thực hiện chuyển đổi

Runtime của Copilot agent không chỉ là engine đằng sau Copilot CLI. Nó hỗ trợ một tập hợp ngày càng tăng các giải pháp của Microsoft, GitHub và hệ sinh thái, mà đối với mỗi giải pháp đó, về mặt kiến trúc, hỗ trợ AI chỉ là một lớp vỏ bao quanh cùng một runtime cộng với bất kỳ tùy chỉnh nào mà giải pháp đó cần. Điều này bao gồm không chỉ GitHub Copilot CLI và ứng dụng GitHub Copilot, mà còn cả các bản phát hành mới nhất của VS Code, Visual Studio, CCA, Copilot Code Review (CCR), Copilot Cowork, Copilot Studio, và Excel, Outlook, PowerPoint, Word... và còn tiếp tục nữa.

Đây là những sản phẩm rất khác biệt và không sản phẩm nào muốn hoặc cần phải tự triển khai mọi thứ cấu thành nên một bộ khung tác nhân trong môi trường production. Họ muốn tất cả sự thông minh, bảo mật, độ tin cậy và hiệu suất, và họ muốn chia sẻ điều đó để một bản sửa lỗi ở một nơi sẽ khắc phục được cho tất cả. Hầu hết các sản phẩm được liệt kê ở đoạn trước ban đầu đều tự triển khai vòng lặp tác nhân (agent loop) của riêng mình, nhưng kể từ đó đã thay thế nó bằng GitHub Copilot SDK, vốn là điểm truy cập vào runtime của Copilot agent. Việc làm này cho phép họ tập trung vào giá trị kinh doanh cốt lõi và để các chi tiết kỹ thuật cho runtime xử lý. Điều đó càng quan trọng hơn khi xét đến tốc độ của ngành công nghiệp và việc vòng lặp tác nhân được sử dụng cần phải luôn là giải pháp tốt nhất trước sự cạnh tranh gay gắt.

Vậy, runtime dùng chung là điều tốt. Vấn đề nằm ở bản chất của thứ được chia sẻ.

Nếu nhìn vào CLI, về mặt logic, nó là một giao diện dòng lệnh (TUI) nằm trên một vòng lặp tác nhân. Thực tế là toàn bộ stack được triển khai bằng TypeScript, sử dụng Node.js làm framework và V8 làm engine thực thi, với Ink và React cho giao diện người dùng. Đó là một lựa chọn hợp lý cho một ứng dụng TUI; TypeScript và Node.js rất dễ tiếp cận và cho phép phát triển ứng dụng rất nhanh. Và đối với nhu cầu của một ứng dụng console, các tác động về hiệu suất liên quan đến khởi động, khả năng phản hồi, thông lượng và mức tiêu thụ bộ nhớ cũng ở mức chấp nhận được. Tuy nhiên, thật không may, chúng lại ít hợp lý hơn nhiều khi bạn nghĩ đến việc triển khai đó được sử dụng trong các môi trường khác, với các ràng buộc khác, cùng những yêu cầu như khởi động nhanh và mật độ server tuyệt vời nhờ mức tiêu thụ bộ nhớ thấp.

Kiến trúc của CLI và runtime của nó cũng góp phần tạo ra những thách thức ở đây. Toàn bộ ngành công nghiệp đang vận động cực kỳ nhanh, và trong bối cảnh đó, những người thực sự tài năng đưa ra các quyết định vì tốc độ phân phối và phạm vi tiếp cận thị trường. Copilot CLI ban đầu được viết và phát hành nhanh chóng, và khi làm như vậy, TUI và runtime khá gắn kết với nhau thay vì được tách thành các lớp riêng biệt. Sau đó, khi cần một SDK để truy cập lập trình vào runtime đó, do không có sự phân tách rõ ràng giữa các lớp, một quyết định thực dụng đã được đưa ra là đặt SDK lên trên CLI, mặc dù về mặt logic, bạn sẽ mong đợi kiến trúc ngược lại. Thay vì chỉ có thể truy cập thông qua các lệnh do người dùng cung cấp tại dòng lệnh, CLI đã được cập nhật với một chế độ có thể chạy headless, đọc các lệnh tương tự từ stdin và ghi phản hồi ra stdout. Một giao thức JSON-RPC sau đó có thể được sử dụng để điều phối các lệnh gọi hàm từ một tiến trình bên ngoài đến và đi từ CLI. SDK sau đó có thể được nhúng vào các chương trình tiêu thụ tùy ý, các chương trình này sẽ tạo ra một tiến trình CLI để lưu trữ vòng lặp tác nhân bên ngoài tiến trình, với SDK gọi các hàm trong tiến trình từ xa thông qua cơ chế JSON-RPC này. Rất gọn gàng. Nhanh chóng để ra mắt. Linh hoạt. Nhưng không tốt cho hiệu suất (khởi động, bộ nhớ, thông lượng) và độ tin cậy của các ứng dụng tiêu thụ đó. Việc tạo một CopilotClient mới từ SDK đồng nghĩa với việc tạo ra một tiến trình khác:

Tiến trình này sẽ cần khởi chạy và lưu trữ Node và V8. Điều đó có nghĩa là phải phân tích cú pháp một lượng lớn JavaScript được tạo ra từ mã TypeScript trong CLI, tạo bytecode cho nó và có khả năng tối ưu hóa mã "nóng" trong các tầng JIT sau này. Nó đồng nghĩa với tất cả các chi phí bộ nhớ liên quan đến V8. Nó đồng nghĩa với việc kế thừa mô hình luồng của Node, vốn mặc định đẩy chúng ta về phía một mô hình mà mọi công việc liên quan đến CPU đều được tuần tự hóa. Và nó đồng nghĩa với việc buộc phải giao tiếp ngoài tiến trình chỉ để thực hiện các lệnh gọi hàm. Nó đồng nghĩa với việc mọi người tiêu thụ SDK, bằng mọi ngôn ngữ, đều phải vận chuyển Node.js hoặc một tệp nhị phân đi kèm chứa V8. Nó đồng nghĩa với việc các SDK C#, Python, Go, Java và Rust đều phải trả giá cho toàn bộ một runtime ngôn ngữ thứ hai trên mỗi client, với mức tiêu thụ bộ nhớ làm việc tối thiểu khoảng 100 MB, cho một runtime mà ứng dụng của họ không hề sử dụng. Nó đồng nghĩa với mọi sự kiện, mọi thông điệp và mọi thao tác đọc/ghi hệ thống tệp phiên được trừu tượng hóa đều bị đẩy qua ranh giới tiến trình. Nó đồng nghĩa với việc một sự cố trong Node sẽ kéo theo cả phiên làm việc. Và nó đồng nghĩa với việc bất kỳ ai triển khai điều này đều có, tối thiểu, hai tiến trình cần giám sát, theo dõi và gỡ lỗi.

Thay vào đó, chúng tôi muốn một runtime:

Vì tất cả những lý do đó, cũng như những lý do mang tính chủ quan hơn (như kinh nghiệm của đội ngũ và định hướng của ngành), chúng tôi đã chọn Rust. Đây không phải là khẳng định rằng mọi chương trình TypeScript lớn đều nên chuyển sang Rust. Các yêu cầu của chúng tôi nhấn mạnh vào việc nhúng thông qua C ABI, khởi động nhanh, chi phí vận hành ổn định thấp và mức sử dụng tài nguyên có thể dự đoán được. Rust đã biến những mục tiêu đó thành hiện thực, dù phải đánh đổi bằng những phức tạp khác, ví dụ: chúng tôi phải biểu diễn vòng đời (lifetimes) và trạng thái chia sẻ một cách rõ ràng (các lỗi hồi quy về vòng đời được thảo luận sau đây làm nổi bật những tác động của điều đó). Ngôn ngữ mục tiêu phù hợp thực sự thay đổi tùy theo từng ứng dụng.

Sau đó, có hai nhiệm vụ chính liên quan đã được thực hiện:

Bài viết này chủ yếu đề cập đến nhiệm vụ thứ hai: chuyển đổi runtime sang Rust.

Mọi thứ trông như thế nào trước đây

Kế hoạch chuyển đổi ban đầu vào đầu tháng 5 năm 2026 ước tính runtime có khoảng 130.000 dòng TypeScript. Đối với mục đích xác định phạm vi, phép đo ban đầu này khá chính xác, nhưng hóa ra lại gây hiểu lầm nghiêm trọng theo hai cách chính. Song song với việc chuyển đổi:

Tính đến tất cả các yếu tố, tôi ước tính có khoảng 430.000 dòng TypeScript trong môi trường production đã đi qua quá trình chuyển đổi. Những yếu tố tương tự đó cũng khiến việc nhìn thấy tiến độ trở nên khó khăn: cho đến gần cuối, khối lượng TypeScript trong production dường như vẫn giữ tương đối ổn định, nếu không muốn nói là tăng nhẹ, vì việc chuyển đổi diễn ra song song với các công việc mới phát sinh.

Điều này càng trở nên khó hiểu hơn vì cũng có mã Rust mới được đưa vào, tách biệt với quá trình chuyển đổi, trong cùng khung thời gian; vào giai đoạn đầu của nỗ lực chuyển đổi, mã mới có khả năng bị chi phối bởi TypeScript, trong khi ở giai đoạn sau, nó có khả năng bị chi phối bởi Rust hơn.

Trong quá trình chuyển đổi, runtime đã tiếp nhận khoảng 300.000 dòng TypeScript trong production và loại bỏ khoảng 430.000 dòng, trong khi khoảng 1.200.000 dòng Rust trong production được thêm vào và 365.000 dòng được loại bỏ. Nói cách khác, sự ổn định rõ ràng của dòng TypeScript trong biểu đồ trên thực tế đã che giấu một lượng lớn sự thay đổi (churn) của TypeScript.

Chiến lược chuyển đổi tại chỗ (In-place porting)

Biểu đồ đó cũng làm nổi bật một khía cạnh quan trọng về cách thức thực hiện chuyển đổi: tại chỗ.

Có hai cách tiếp cận chính cho việc viết lại ở quy mô này:

Chúng tôi đã chọn phương án 2a vì nhiều lý do:

Chúng tôi cũng tránh biến thể 2b liên quan đến việc duy trì nhiều phiên bản của cùng một thành phần cùng một lúc. Với hàng trăm pull request được hợp nhất vào repo mỗi tuần trong vài tháng qua, codebase liên tục phát triển và rất nhanh chóng. Việc có hai phiên bản khác nhau của cùng một mã bằng hai ngôn ngữ khác nhau và sử dụng hai bộ thư viện phụ thuộc khác nhau làm tăng thêm rất nhiều sự phức tạp. Một số thành phần này cũng không được cô lập hoàn hảo; trong khi một số thành phần độc lập về mặt logic với các API đơn giản để các phần còn lại của hệ thống truy cập, thì những thành phần khác lại có các mối liên kết chằng chịt, và việc làm cho biểu đồ đó có thể thay thế nóng (hot swappable) cho từng thành phần là một cơn ác mộng. Các hệ thống con có khả năng hưởng lợi nhiều nhất từ việc chuyển đổi song song thận trọng lại chính là những hệ thống khó thực hiện song song nhất. Ví dụ, điều phối phiên (session orchestration) không phải là một hàm thuần túy mà bạn có thể gọi hai phiên bản khác nhau với một câu lệnh if/else dựa trên một cờ thử nghiệm nào đó. Nó sở hữu trạng thái có thể thay đổi (mutable state), điều khiển các callback theo cả hai hướng và xuyên suốt gần như mọi hệ thống con khác, vì vậy "chạy cả hai và so sánh" sẽ có nghĩa là duy trì hai bản sao khác biệt của thành phần nắm giữ trạng thái và dịch vụ của cuộc hội thoại, và cầu nguyện rằng chúng luôn đồng bộ qua hàng trăm chỉnh sửa đồng thời. Sự gắn kết khiến một thành phần khó chuyển đổi cũng chính là sự gắn kết khiến nó gần như không thể thực hiện shadow (chạy song song để kiểm chứng) mà không có nguy cơ gây ra nhiều lỗi hồi quy hơn là tránh được. Lợi ích của việc có thể thay thế theo cách này chủ yếu là để đạt được sự tự tin, điều mà chúng tôi có thể thực hiện bằng các cách khác.

Việc xác thực cũng diễn ra thông qua triển khai tăng dần. Với cách tiếp cận "big-bang" (chuyển đổi toàn bộ một lần), chúng tôi sẽ giữ mọi thứ trong một nhánh dài hạn, chuyển đổi toàn bộ runtime và chuyển đổi một lần duy nhất. Điều đó có nghĩa là người tiêu thụ sẽ trải nghiệm mọi dòng mã đã chuyển đổi cùng một lúc, bao gồm tất cả các lỗi hồi quy đã lọt qua quá trình kiểm thử trong repo. Việc triển khai dần dần các phần thay đổi, hai thành phần ở đây, một thành phần ở kia, cho phép chúng tôi thực hiện bước xác thực cuối cùng trong các bản dựng đã triển khai với việc sử dụng thực tế từ người tiêu dùng (thường là nội bộ trong Microsoft và GitHub) nhưng vẫn giữ rủi ro hồi quy ở mức tối thiểu. Trong khoảng thời gian chuyển đổi kéo dài khoảng 14,5 tuần, nhánh main đã xuất xưởng 135 bản phát hành, bao gồm 100 phiên bản tiền phát hành (pre-release) và 35 phiên bản ổn định, trung bình khoảng 1,3 bản phát hành mỗi ngày. Khoảng 1,3 pull request chuyển đổi cũng được mở mỗi ngày, sao cho mỗi bản phát hành đều mang một tập hợp nhỏ và có thể kiểm soát được các thành phần đã chuyển đổi (chúng tôi thường cố gắng nhưng không phải lúc nào cũng thành công trong việc xuất xưởng các bản chuyển đổi trong phiên bản tiền phát hành trước). Trong một mẫu npm bảy ngày gần nhất, các phiên bản tiền phát hành chỉ chiếm 10,5% lượt tải xuống, cho thấy mức độ tiếp xúc ban đầu tương đối hạn chế trong khi chúng tôi theo dõi các kênh phản hồi để tìm tín hiệu về những thứ bị hỏng và nhanh chóng đưa ra các bản sửa lỗi trong phiên bản tiền phát hành tiếp theo. Các vấn đề được báo cáo dễ dàng liên quan đến các thay đổi gần đây đã biết hơn, và dễ dàng tìm ra nguyên nhân gốc rễ và khắc phục nhanh chóng hơn. Theo cách này, việc thực hiện chuyển đổi dần dần trong một khoảng thời gian dài hơn thực sự là một tính năng chứ không phải là một trở ngại (tức là nhanh hơn không phải lúc nào cũng tốt hơn). Đến ngày 21 tháng 8, runtime đã là 100% Rust trong production: 832.378 dòng Rust production và 468.689 dòng unit test Rust, cùng với 174.675 dòng test E2E TypeScript. Kho lưu trữ GitHub Copilot SDK riêng biệt đã thêm khoảng 130.000 dòng mã test E2E trên Node.js, Python, Go, C#, Rust và Java.

Bắt đầu

Trước khi thực hiện toàn diện, chúng tôi đã xây dựng sự tự tin và chứng minh mọi thứ. Chúng tôi bắt đầu với hai pull request thiết lập không gian làm việc Rust, toolchain, quy tắc lint, CI, đường ống xây dựng (build pipeline) và hướng dẫn viết mã, sau đó giới thiệu crate runtime cùng với các mẫu tạo mã và tương tác (interop) trong khi chuyển đổi một tập hợp các nguyên hàm logic thuần túy được chọn lọc đặc biệt vì chúng không có I/O hoặc trạng thái chia sẻ và đã có các bài kiểm thử mạnh mẽ. Chỉ sau khi những phần đó được hợp nhất, pull request chuyển đổi chính đầu tiên mới đưa ba trình trợ giúp không có tác dụng phụ (side-effect-free) qua toàn bộ quy trình. Những phần này đóng vai trò là các dự án thí điểm, biến các giả định về bố cục kho lưu trữ, FFI, đóng gói, kiểm thử và đánh giá thành các quy ước mà các bản chuyển đổi lớn hơn sau đó sẽ tái sử dụng. Về cơ bản, chúng tôi đã kiểm thử toàn bộ hệ thống từ đầu đến cuối. Kế hoạch tiếp tục bằng cách sắp xếp công việc từ các "lá" (phần ngoại vi) vào trong, với các trình trợ giúp thuần túy, loại trừ nội dung, tiện ích shell và các thao tác hệ thống tệp phiên thiết lập mô hình dịch và kiểm thử. Các hệ thống con có trạng thái theo sau, và các công cụ, hook, model client và MCP được xây dựng dựa trên những phần đó. Điều phối phiên (phần gắn kết nhất và ít có khả năng song song tự nhiên nhất của runtime) sẽ được thực hiện gần cuối.

Các bản chuyển đổi sớm, các thành phần lá nhỏ, đã di chuyển nhanh chóng. Nhưng các hệ thống con lớn hơn không di chuyển trong một bước nguyên tử; ví dụ, hỗ trợ MCP đã tiến triển qua bảy pull request chuyên biệt, trong khi các công cụ tiến triển qua một loạt sáu phần và sau đó cần thêm công việc để di chuyển điều phối và loại bỏ TypeScript còn lại. Hooks, xác thực, telemetry, plugin, cài đặt và tính bền vững (persistence) đã đi theo những con đường tương tự.

Trên thực tế, đơn vị chuyển đổi hữu ích không phải lúc nào cũng là "một thành phần". Nó thường là một làn sóng đi qua các vùng hành vi liên quan: trước tiên di chuyển logic thuần túy, sau đó di chuyển quyền sở hữu trạng thái, sau đó di chuyển điều phối, sau đó loại bỏ các phương án dự phòng và cuối cùng đơn giản hóa Rust sau khi quá trình tương tác tạm thời đã biến mất.

Tương tác (Interop)

Có hai lớp chính trong quá trình chuyển đổi này liên quan đến tương tác:

Chúng tôi đã đạt được (1) thông qua crate napi Rust từ dự án napi-rs, tồn tại để xây dựng các addon gốc của Node bằng Rust. Bạn chú thích một hàm với #[napi], và một macro napi-rs sẽ tạo ra phần keo đăng ký N-API giúp hàm đó có thể được gọi từ JavaScript, cộng với một khai báo TypeScript cho nó trong tệp index.d.ts được tạo. Một hàm Rust đồng bộ trở thành một hàm JavaScript thông thường, một async fn trở thành một hàm JavaScript trả về một promise, và các struct được chú thích #[napi(object)] trở thành các đối tượng thuần túy ở phía bên kia.

Lưu lượng truy cập cũng phải di chuyển theo cả hai hướng. Nhiều thành phần đã chuyển đổi tạm thời phụ thuộc vào thứ gì đó chưa được chuyển đổi, vì vậy Rust cần gọi ngược lại TypeScript, ví dụ như một triển khai công cụ trong Rust yêu cầu lớp mô hình vẫn còn là TypeScript thực hiện suy luận, hoặc kích hoạt một hook, hoặc yêu cầu quyết định cấp quyền cho một lệnh mà nó muốn chạy. napi-rs xử lý việc này bằng "threadsafe functions", cho phép mã Rust chạy trên luồng worker của Tokio gọi một callback JavaScript ngược lại trên luồng chính của Node. Node cài đặt callback một lần, Rust giữ nó và gọi nó bất cứ khi nào nó cần đi theo hướng ngược lại. Mỗi cái trong số này đều mang tính tạm thời theo cấu trúc: callback chỉ tồn tại vì thứ ở đầu kia vẫn là TypeScript, và nó sẽ bị xóa khi thứ đó được chuyển đổi.

Mối nối tạm thời đạt đỉnh điểm vào ngày 3 tháng 8, với 2.019 export N-API nội bộ và 3.356 vị trí gọi TypeScript. Khi hoàn thành, runtime hoàn toàn là Rust, do đó không còn tương tác nội bộ: 0 export N-API nội bộ tạm thời và 0 vị trí gọi TypeScript còn lại. (Tôi đã đề cập trước đó rằng CLI vẫn có một số quyền truy cập nội bộ vào runtime mà chúng tôi đang nỗ lực loại bỏ; các export đó không được tính ở đây.)

Lớp tương tác thứ hai, bề mặt SDK, là lớp vĩnh viễn trong hai lớp. Copilot SDK xuất xưởng cho sáu ngôn ngữ: TypeScript, Python, Go, C#, Java và Rust. Tất cả chúng đều sử dụng cùng một hợp đồng JSON-RPC hai chiều, và ban đầu tất cả chúng đều đạt được nó theo cùng một cách: tạo Copilot CLI ở chế độ headless dưới dạng một tiến trình con và giao tiếp với nó qua pipe hoặc socket. Điều đó vẫn là mặc định trong quá trình chuyển đổi. Nó cũng có nghĩa là một người tiêu thụ SDK bằng bất kỳ ngôn ngữ nào cũng phải vận chuyển hoặc định vị một triển khai Node đầy đủ, trả giá cho một bước nhảy tiến trình trên mỗi sự kiện và mỗi thông điệp, và giám sát hai tiến trình thay vì một.

Việc chuyển đổi runtime sang Rust là điều làm cho tùy chọn kia trở nên khả thi. runtime.node được xuất xưởng là một thư viện chia sẻ nền tảng thông thường (phần mở rộng.node là quy ước addon gốc của Node.js; bên dưới nó là một tệp.dll,.so hoặc.dylib), và giờ đây nó cung cấp hai cửa trước vào cùng một engine. Có cửa napi, mà một tiến trình Node tải dưới dạng một addon gốc; đó là con đường của CLI (hiện tại... trong tương lai, ý định là nó sẽ đi qua con đường SDK hoàn toàn). Và có một cửa C ABI, mà bất kỳ ngôn ngữ nào cũng có thể tải vào tiến trình riêng của nó và gọi thông qua FFI. Cùng một runtime trong tiến trình được chọn thông qua cơ chế tương tác gốc của mỗi ngôn ngữ:

Việc viết lại bằng Rust và sự lựa chọn giữa lưu trữ trong tiến trình (in-process) và ngoài tiến trình (out-of-process) là các chiều độc lập. Runtime Rust đã hoàn thiện hỗ trợ cả hai: nó có thể chạy bên trong tiến trình của người tiêu thụ SDK hoặc phía sau ranh giới server JSON-RPC hiện có. Các điểm truy cập trong tiến trình đó hiện là tùy chọn (opt-in) trong khi chúng tôi xây dựng sự tự tin vào việc chia sẻ một tiến trình, và do đó là một ranh giới lỗi, với ứng dụng tiêu thụ. Mọi thứ phía trên lớp vận chuyển vẫn là cùng một API SDK: các phiên, sự kiện, công cụ, quyền và callback không quan tâm liệu các byte JSON-RPC của chúng đã đi qua pipe hay một lệnh gọi hàm.

Điều thú vị về cánh cửa thứ hai là kích thước của nó. Nó chỉ có 19 hàm được export: bốn cho vòng đời server, bốn cho đăng ký và cấu hình phiên, tám cho kết nối và ba cho host nhúng. Đằng sau các hàm đó, hợp đồng chia sẻ hiện chứa 364 lộ trình điều phối (dispatch routes): 340 có thể được gọi bởi người tiêu thụ SDK, trong khi 24 chạy theo hướng ngược lại dưới dạng callback từ runtime đến SDK. Cửa napi lớn hơn nhiều, cần các hàm cho mỗi lộ trình điều phối đó. Cửa C ABI dựa trên điều phối: các phương thức API không có export nào cả, mà thay vào đó di chuyển dưới dạng các byte JSON-RPC được ghi vào một kết nối, và kết quả, sự kiện và các yêu cầu từ server đến client quay trở lại trên các callback do host cung cấp. Việc thêm, thay đổi hoặc xóa một phương thức API sẽ tác động đến bảng điều phối của engine nhưng không bao giờ tác động đến ABI. Một SDK liên kết 19 điểm nhập đó một lần và tiếp cận toàn bộ bề mặt API đang phát triển thông qua chúng một cách linh hoạt.

Đọc bài gốc

Bài viết được AI dịch và tổng hợp tự động từ GitHub Blog. Liên kết bài gốc ở phía trên. AIHOT.vn luôn dẫn nguồn đầy đủ — nếu bạn thấy điểm cần chỉnh sửa, hãy gửi ý kiến tại trang phản hồi.