Path: blob/main/extensions/copilot/src/extension/onboardDebug/node/copilotDebugWorker/rpc.ts
13405 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { Duplex } from 'stream';6import type { IDisposable } from '../../../../util/vs/base/common/lifecycle';7import { StreamSplitter } from './streamSplitter';89// JSON-RPC request object10interface Request {11id: number;12method: string;13params?: any;14}1516// JSON-RPC response object17interface Response {18id: number;19result?: any;20error?: {21code: number;22message: string;23data?: any;24};25}2627export interface ISimpleRPC extends IDisposable {28registerMethod(method: string, handler: (params: any) => Promise<any>): void;29callMethod(method: string, params?: any): Promise<any>;30}3132const terminator = process.platform === 'win32' ? '\r\n' : '\n';3334export class SimpleRPC implements ISimpleRPC {35private idCounter: number;36private methods = new Map<string, (...params: any[]) => Promise<any>>();37private pendingRequests = new Map<number, { resolve: (result: any) => void; reject: (error: Error) => void }>();38private didEnd?: boolean;3940public readonly ended: Promise<void>;4142constructor(private readonly stream: Duplex) {43this.idCounter = 0;44this.stream.pipe(new StreamSplitter('\n')).on('data', d => this.handleData(d));45this.ended = new Promise<void>((resolve) => this.stream.on('end', () => {46this.didEnd = true;47resolve();48}));49}5051public registerMethod(method: string, handler: (params: any) => Promise<any> | any) {52this.methods.set(method, handler);53}5455public async callMethod(method: string, params?: any): Promise<any> {56const id = this.idCounter++;57const request: Request = { id, method, params, };58const promise = new Promise<any>((resolve, reject) => {59this.pendingRequests.set(id, { resolve, reject });60});61this.stream.write(JSON.stringify(request) + terminator);62return Promise.race([promise, this.ended]);63}6465public dispose() {66this.didEnd = true;67this.stream.end();68for (const { reject } of this.pendingRequests.values()) {69reject(new Error('RPC connection closed'));70}71this.pendingRequests.clear();72}7374private async handleData(data: Buffer) {75// -1 to remove trailing split match76const incoming: Response | Request = JSON.parse(data.toString());7778if (!('method' in incoming)) {79const { id, result, error } = incoming;80const handler = this.pendingRequests.get(id);81this.pendingRequests.delete(id);82if (error !== undefined) {83handler?.reject(new Error(error.message));84} else {85handler?.resolve(result);86}87} else {88const { id, method, params } = incoming;89const response: Response = { id };9091try {92if (this.methods.has(method)) {93const result = await this.methods.get(method)!(params);94response.result = result;95} else {96throw new Error(`Method not found: ${method}`);97}98} catch (error) {99response.error = {100code: -1,101message: String(error.stack || error),102};103}104105if (!this.didEnd) {106this.stream.write(JSON.stringify(response) + terminator);107}108}109}110}111112113