@github/copilot-sdk 1.0.3 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,629 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var copilotRequestHandler_exports = {};
20
+ __export(copilotRequestHandler_exports, {
21
+ CopilotRequestHandler: () => CopilotRequestHandler,
22
+ CopilotWebSocketCloseStatus: () => CopilotWebSocketCloseStatus,
23
+ CopilotWebSocketForwarder: () => CopilotWebSocketForwarder,
24
+ CopilotWebSocketHandler: () => CopilotWebSocketHandler,
25
+ createCopilotRequestAdapter: () => createCopilotRequestAdapter
26
+ });
27
+ module.exports = __toCommonJS(copilotRequestHandler_exports);
28
+ const sharedTextDecoder = new TextDecoder("utf-8", { fatal: false });
29
+ const sharedTextEncoder = new TextEncoder();
30
+ const kBridge = /* @__PURE__ */ Symbol("copilotWebSocketResponseBridge");
31
+ const kCompletion = /* @__PURE__ */ Symbol("copilotWebSocketCompletion");
32
+ const kOpen = /* @__PURE__ */ Symbol("copilotWebSocketOpen");
33
+ const kSuppressCloseOnDispose = /* @__PURE__ */ Symbol("copilotWebSocketSuppressCloseOnDispose");
34
+ const kHandle = /* @__PURE__ */ Symbol("copilotRequestHandle");
35
+ class CopilotWebSocketCloseStatus {
36
+ constructor(description, errorCode, error) {
37
+ this.description = description;
38
+ this.errorCode = errorCode;
39
+ this.error = error;
40
+ }
41
+ description;
42
+ errorCode;
43
+ error;
44
+ static normalClosure = new CopilotWebSocketCloseStatus();
45
+ }
46
+ class CopilotWebSocketHandler {
47
+ #response;
48
+ #completion;
49
+ #resolveCompletion;
50
+ #closed = false;
51
+ [kSuppressCloseOnDispose] = false;
52
+ context;
53
+ constructor(context) {
54
+ this.context = context;
55
+ const bridge = context[kBridge];
56
+ if (!bridge) {
57
+ throw new Error("WebSocket response bridge is not attached");
58
+ }
59
+ this.#response = bridge;
60
+ this.#completion = new Promise((resolve) => {
61
+ this.#resolveCompletion = resolve;
62
+ });
63
+ }
64
+ async sendResponseMessage(data) {
65
+ await this.#response.write(data);
66
+ }
67
+ async close(status = CopilotWebSocketCloseStatus.normalClosure) {
68
+ if (this.#closed) {
69
+ return;
70
+ }
71
+ this.#closed = true;
72
+ if (status.error) {
73
+ await this.#response.error({
74
+ message: status.description ?? status.error.message,
75
+ code: status.errorCode
76
+ });
77
+ } else {
78
+ await this.#response.end();
79
+ }
80
+ this.#resolveCompletion(status);
81
+ }
82
+ async [Symbol.asyncDispose]() {
83
+ if (!this[kSuppressCloseOnDispose] && !this.#closed) {
84
+ await this.close(CopilotWebSocketCloseStatus.normalClosure);
85
+ }
86
+ }
87
+ /** @internal */
88
+ get [kCompletion]() {
89
+ return this.#completion;
90
+ }
91
+ /** @internal */
92
+ async [kOpen]() {
93
+ }
94
+ }
95
+ class CopilotWebSocketForwarder extends CopilotWebSocketHandler {
96
+ #upstream = null;
97
+ constructor(context) {
98
+ super(context);
99
+ }
100
+ sendRequestMessage(data) {
101
+ if (this.#upstream?.readyState !== WebSocket.OPEN) {
102
+ return;
103
+ }
104
+ this.#upstream.send(data);
105
+ }
106
+ /** @internal */
107
+ async [kOpen]() {
108
+ if (this.#upstream) {
109
+ return;
110
+ }
111
+ const upstream = new WebSocket(this.context.url);
112
+ upstream.binaryType = "arraybuffer";
113
+ this.#upstream = upstream;
114
+ upstream.addEventListener("message", (event) => {
115
+ void this.sendResponseMessage(normalizeWsData(event.data)).catch(
116
+ async (err) => {
117
+ await this.close(
118
+ new CopilotWebSocketCloseStatus(
119
+ err instanceof Error ? err.message : String(err),
120
+ void 0,
121
+ err instanceof Error ? err : new Error(String(err))
122
+ )
123
+ );
124
+ }
125
+ );
126
+ });
127
+ upstream.addEventListener("close", () => {
128
+ void this.close(CopilotWebSocketCloseStatus.normalClosure);
129
+ });
130
+ upstream.addEventListener("error", () => {
131
+ void this.close(
132
+ new CopilotWebSocketCloseStatus(
133
+ "WebSocket error",
134
+ void 0,
135
+ new Error("WebSocket error")
136
+ )
137
+ );
138
+ });
139
+ await new Promise((resolve, reject) => {
140
+ if (upstream.readyState === WebSocket.OPEN) {
141
+ resolve();
142
+ return;
143
+ }
144
+ upstream.addEventListener("open", () => resolve(), { once: true });
145
+ upstream.addEventListener("error", () => reject(new Error("WebSocket error")), {
146
+ once: true
147
+ });
148
+ });
149
+ }
150
+ async close(status = CopilotWebSocketCloseStatus.normalClosure) {
151
+ try {
152
+ if (this.#upstream?.readyState === WebSocket.OPEN || this.#upstream?.readyState === WebSocket.CONNECTING) {
153
+ this.#upstream?.close();
154
+ }
155
+ } catch {
156
+ }
157
+ await super.close(status);
158
+ }
159
+ async [Symbol.asyncDispose]() {
160
+ try {
161
+ await super[Symbol.asyncDispose]();
162
+ } finally {
163
+ try {
164
+ this.#upstream?.close();
165
+ } catch {
166
+ }
167
+ }
168
+ }
169
+ }
170
+ class CopilotRequestHandler {
171
+ sendRequest(request, ctx) {
172
+ return fetch(request, { signal: ctx.signal });
173
+ }
174
+ openWebSocket(ctx) {
175
+ return Promise.resolve(new CopilotWebSocketForwarder(ctx));
176
+ }
177
+ /** @internal */
178
+ async [kHandle](exchange) {
179
+ const bridge = new CopilotWebSocketResponseBridge(exchange);
180
+ const ctx = {
181
+ requestId: exchange.requestId,
182
+ sessionId: exchange.sessionId,
183
+ transport: exchange.transport,
184
+ url: exchange.url,
185
+ headers: exchange.headers,
186
+ signal: exchange.signal,
187
+ [kBridge]: bridge
188
+ };
189
+ if (exchange.transport === "websocket") {
190
+ await this.#handleWebSocket(exchange, ctx);
191
+ } else {
192
+ await this.#handleHttp(exchange, ctx);
193
+ }
194
+ }
195
+ async #handleHttp(exchange, ctx) {
196
+ const request = await buildFetchRequest(exchange);
197
+ const response = await this.sendRequest(request, ctx);
198
+ await streamResponse(response, exchange);
199
+ }
200
+ async #handleWebSocket(exchange, ctx) {
201
+ const handler = await this.openWebSocket(ctx);
202
+ try {
203
+ await handler[kOpen]();
204
+ await ctx[kBridge].start();
205
+ let cancelled;
206
+ const clientSettled = (async () => {
207
+ for await (const chunk of exchange.requestBody) {
208
+ await handler.sendRequestMessage(decodeFrame(chunk));
209
+ }
210
+ return "client-complete";
211
+ })().catch((err) => {
212
+ cancelled = err;
213
+ return "client-error";
214
+ });
215
+ const first = await Promise.race([
216
+ clientSettled,
217
+ handler[kCompletion].then(() => "server-done")
218
+ ]);
219
+ if (first === "client-error") {
220
+ handler[kSuppressCloseOnDispose] = true;
221
+ throw cancelled instanceof Error ? cancelled : new Error(String(cancelled));
222
+ }
223
+ if (first === "client-complete") {
224
+ await handler.close(CopilotWebSocketCloseStatus.normalClosure);
225
+ await handler[kCompletion];
226
+ return;
227
+ }
228
+ const status = await handler[kCompletion];
229
+ if (status.error) {
230
+ throw status.error;
231
+ }
232
+ } finally {
233
+ await handler[Symbol.asyncDispose]();
234
+ }
235
+ }
236
+ }
237
+ function createCopilotRequestAdapter(handler, getServerRpc) {
238
+ const pending = /* @__PURE__ */ new Map();
239
+ function getOrCreate(requestId) {
240
+ let exchange = pending.get(requestId);
241
+ if (!exchange) {
242
+ exchange = new CopilotRequestExchange(requestId, getServerRpc);
243
+ pending.set(requestId, exchange);
244
+ }
245
+ return exchange;
246
+ }
247
+ async function run(exchange) {
248
+ try {
249
+ await handler[kHandle](exchange);
250
+ if (!exchange.finished) {
251
+ await finalize(
252
+ exchange,
253
+ 502,
254
+ "Copilot request handler returned without finalising the response (call responseBody.end() or .error())."
255
+ );
256
+ }
257
+ } catch (err) {
258
+ if (exchange.cancelled || exchange.signal.aborted) {
259
+ await finalize(exchange, 499, "Request cancelled by runtime", "cancelled");
260
+ return;
261
+ }
262
+ const message = err instanceof Error ? err.message : String(err);
263
+ await finalize(exchange, 502, message);
264
+ } finally {
265
+ pending.delete(exchange.requestId);
266
+ }
267
+ }
268
+ return {
269
+ async httpRequestStart(params) {
270
+ const exchange = getOrCreate(params.requestId);
271
+ exchange.setContext(params);
272
+ void run(exchange);
273
+ return {};
274
+ },
275
+ async httpRequestChunk(params) {
276
+ routeChunk(getOrCreate(params.requestId), params);
277
+ return {};
278
+ }
279
+ };
280
+ }
281
+ async function finalize(exchange, status, message, code) {
282
+ if (exchange.finished) {
283
+ return;
284
+ }
285
+ try {
286
+ if (!exchange.started) {
287
+ await exchange.startResponse({ status, headers: {} });
288
+ }
289
+ await exchange.errorResponse({ message, code });
290
+ } catch {
291
+ }
292
+ }
293
+ function routeChunk(exchange, params) {
294
+ if (params.cancel) {
295
+ exchange.pushCancel(params.cancelReason);
296
+ return;
297
+ }
298
+ if (params.data && params.data.length > 0) {
299
+ exchange.pushChunk(decodeChunkData(params.data, !!params.binary));
300
+ }
301
+ if (params.end) {
302
+ exchange.pushEnd();
303
+ }
304
+ }
305
+ class CopilotRequestExchange {
306
+ requestId;
307
+ sessionId;
308
+ method = "GET";
309
+ url = "";
310
+ headers = {};
311
+ transport = "http";
312
+ #getServerRpc;
313
+ #abort = new AbortController();
314
+ #buffer = [];
315
+ #waker = null;
316
+ #drained = false;
317
+ #started = false;
318
+ #finished = false;
319
+ #cancelled = false;
320
+ constructor(requestId, getServerRpc) {
321
+ this.requestId = requestId;
322
+ this.#getServerRpc = getServerRpc;
323
+ }
324
+ /** Fill in the request context once the matching start frame arrives. */
325
+ setContext(params) {
326
+ this.sessionId = params.sessionId;
327
+ this.method = params.method;
328
+ this.url = params.url;
329
+ this.headers = params.headers;
330
+ this.transport = params.transport ?? "http";
331
+ }
332
+ get signal() {
333
+ return this.#abort.signal;
334
+ }
335
+ get started() {
336
+ return this.#started;
337
+ }
338
+ get finished() {
339
+ return this.#finished;
340
+ }
341
+ get cancelled() {
342
+ return this.#cancelled;
343
+ }
344
+ // --- Request body feed (driven by the adapter as chunk frames arrive) ---
345
+ pushChunk(chunk) {
346
+ this.#push({ chunk });
347
+ }
348
+ pushEnd() {
349
+ this.#push({ end: true });
350
+ }
351
+ pushCancel(reason) {
352
+ this.#cancelled = true;
353
+ this.#abort.abort();
354
+ this.#push({ cancel: { reason } });
355
+ }
356
+ #push(item) {
357
+ this.#buffer.push(item);
358
+ const w = this.#waker;
359
+ this.#waker = null;
360
+ w?.();
361
+ }
362
+ /**
363
+ * Request body bytes, yielded as they arrive. A cancel frame surfaces as a
364
+ * thrown error so the handler's upstream call is torn down.
365
+ */
366
+ get requestBody() {
367
+ return {
368
+ [Symbol.asyncIterator]: () => ({
369
+ next: async () => {
370
+ if (this.#drained) {
371
+ return { value: void 0, done: true };
372
+ }
373
+ while (this.#buffer.length === 0) {
374
+ await new Promise((resolve) => {
375
+ this.#waker = resolve;
376
+ });
377
+ }
378
+ const item = this.#buffer.shift();
379
+ if (item.cancel) {
380
+ this.#drained = true;
381
+ throw new Error(
382
+ item.cancel.reason ? `Request cancelled by runtime: ${item.cancel.reason}` : "Request cancelled by runtime"
383
+ );
384
+ }
385
+ if (item.end) {
386
+ this.#drained = true;
387
+ return { value: void 0, done: true };
388
+ }
389
+ return { value: item.chunk ?? new Uint8Array(), done: false };
390
+ }
391
+ })
392
+ };
393
+ }
394
+ // --- Response emit (driven by the handler). Strict state machine: ---
395
+ // startResponse once -> 0..N writeResponse -> exactly one of
396
+ // endResponse / errorResponse.
397
+ async startResponse(init) {
398
+ if (this.#started) {
399
+ throw new Error("Copilot request response start() called twice.");
400
+ }
401
+ if (this.#finished) {
402
+ throw new Error("Copilot request response already finished.");
403
+ }
404
+ this.#started = true;
405
+ await this.#rpc().llmInference.httpResponseStart({
406
+ requestId: this.requestId,
407
+ status: init.status,
408
+ statusText: init.statusText,
409
+ headers: init.headers ?? {}
410
+ });
411
+ }
412
+ async writeResponse(data) {
413
+ if (this.#cancelled) {
414
+ throw new Error("Copilot request was cancelled by the runtime.");
415
+ }
416
+ if (!this.#started) {
417
+ throw new Error("Copilot request response write() called before start().");
418
+ }
419
+ if (this.#finished) {
420
+ throw new Error("Copilot request response write() called after end()/error().");
421
+ }
422
+ const isString = typeof data === "string";
423
+ await this.#rpc().llmInference.httpResponseChunk({
424
+ requestId: this.requestId,
425
+ data: isString ? data : Buffer.from(data).toString("base64"),
426
+ binary: !isString,
427
+ end: false
428
+ });
429
+ }
430
+ async endResponse() {
431
+ if (this.#finished) {
432
+ return;
433
+ }
434
+ this.#finished = true;
435
+ await this.#rpc().llmInference.httpResponseChunk({
436
+ requestId: this.requestId,
437
+ data: "",
438
+ end: true
439
+ });
440
+ }
441
+ async errorResponse(error) {
442
+ if (this.#finished) {
443
+ return;
444
+ }
445
+ this.#finished = true;
446
+ await this.#rpc().llmInference.httpResponseChunk({
447
+ requestId: this.requestId,
448
+ data: "",
449
+ end: true,
450
+ error: { message: error.message, code: error.code }
451
+ });
452
+ }
453
+ #rpc() {
454
+ const r = this.#getServerRpc();
455
+ if (!r) {
456
+ throw new Error("Copilot request response used after RPC connection closed.");
457
+ }
458
+ return r;
459
+ }
460
+ }
461
+ const FORBIDDEN_REQUEST_HEADERS = /* @__PURE__ */ new Set([
462
+ "host",
463
+ "connection",
464
+ "content-length",
465
+ "transfer-encoding",
466
+ "keep-alive",
467
+ "upgrade",
468
+ "proxy-connection",
469
+ "te",
470
+ "trailer"
471
+ ]);
472
+ async function buildFetchRequest(exchange) {
473
+ const headers = new Headers();
474
+ for (const [name, values] of Object.entries(exchange.headers)) {
475
+ if (!values) {
476
+ continue;
477
+ }
478
+ if (FORBIDDEN_REQUEST_HEADERS.has(name.toLowerCase())) {
479
+ continue;
480
+ }
481
+ for (const value of values) {
482
+ headers.append(name, value);
483
+ }
484
+ }
485
+ const method = exchange.method.toUpperCase();
486
+ const hasBody = method !== "GET" && method !== "HEAD";
487
+ let body;
488
+ if (hasBody) {
489
+ const buffered = await drainAsync(exchange.requestBody);
490
+ if (buffered.length > 0) {
491
+ body = buffered;
492
+ }
493
+ } else {
494
+ await drainAsync(exchange.requestBody);
495
+ }
496
+ return new Request(exchange.url, { method, headers, body });
497
+ }
498
+ async function drainAsync(stream) {
499
+ const parts = [];
500
+ let total = 0;
501
+ for await (const chunk of stream) {
502
+ parts.push(chunk);
503
+ total += chunk.byteLength;
504
+ }
505
+ if (parts.length === 0) {
506
+ return new Uint8Array(0);
507
+ }
508
+ if (parts.length === 1) {
509
+ return parts[0];
510
+ }
511
+ const out = new Uint8Array(total);
512
+ let off = 0;
513
+ for (const part of parts) {
514
+ out.set(part, off);
515
+ off += part.byteLength;
516
+ }
517
+ return out;
518
+ }
519
+ async function streamResponse(response, exchange) {
520
+ await exchange.startResponse({
521
+ status: response.status,
522
+ statusText: response.statusText || void 0,
523
+ headers: headersToMultiMap(response.headers)
524
+ });
525
+ const body = response.body;
526
+ if (!body) {
527
+ await exchange.endResponse();
528
+ return;
529
+ }
530
+ const reader = body.getReader();
531
+ try {
532
+ for (; ; ) {
533
+ const { value, done } = await reader.read();
534
+ if (done) {
535
+ break;
536
+ }
537
+ if (value && value.byteLength > 0) {
538
+ await exchange.writeResponse(value);
539
+ }
540
+ }
541
+ await exchange.endResponse();
542
+ } finally {
543
+ reader.releaseLock();
544
+ }
545
+ }
546
+ function headersToMultiMap(headers) {
547
+ const out = {};
548
+ headers.forEach((value, name) => {
549
+ if (name.toLowerCase() === "set-cookie") {
550
+ return;
551
+ }
552
+ const list = out[name] ?? (out[name] = []);
553
+ list.push(value);
554
+ });
555
+ const setCookies = headers.getSetCookie();
556
+ if (setCookies.length > 0) {
557
+ out["set-cookie"] = setCookies;
558
+ }
559
+ return out;
560
+ }
561
+ function decodeChunkData(data, binary) {
562
+ if (binary) {
563
+ return new Uint8Array(Buffer.from(data, "base64"));
564
+ }
565
+ return sharedTextEncoder.encode(data);
566
+ }
567
+ function decodeFrame(chunk) {
568
+ return sharedTextDecoder.decode(chunk);
569
+ }
570
+ function normalizeWsData(data) {
571
+ if (typeof data === "string") {
572
+ return data;
573
+ }
574
+ if (data instanceof Uint8Array) {
575
+ return data;
576
+ }
577
+ if (data instanceof ArrayBuffer) {
578
+ return new Uint8Array(data);
579
+ }
580
+ return new Uint8Array();
581
+ }
582
+ class CopilotWebSocketResponseBridge {
583
+ #exchange;
584
+ #started = false;
585
+ #completed = false;
586
+ #serial = Promise.resolve();
587
+ constructor(exchange) {
588
+ this.#exchange = exchange;
589
+ }
590
+ /** Emit the 101 upgrade head now, acknowledging the WebSocket connect. */
591
+ start() {
592
+ return this.#run(false, () => Promise.resolve());
593
+ }
594
+ write(data) {
595
+ return this.#run(false, () => this.#exchange.writeResponse(data));
596
+ }
597
+ end() {
598
+ return this.#run(true, () => this.#exchange.endResponse());
599
+ }
600
+ error(error) {
601
+ return this.#run(true, () => this.#exchange.errorResponse(error));
602
+ }
603
+ #run(terminal, action) {
604
+ const task = this.#serial.then(async () => {
605
+ if (this.#completed) {
606
+ return;
607
+ }
608
+ if (!this.#started) {
609
+ this.#started = true;
610
+ await this.#exchange.startResponse({ status: 101, headers: {} });
611
+ }
612
+ if (terminal) {
613
+ this.#completed = true;
614
+ }
615
+ await action();
616
+ });
617
+ this.#serial = task.catch(() => {
618
+ });
619
+ return task;
620
+ }
621
+ }
622
+ // Annotate the CommonJS export names for ESM import in node:
623
+ 0 && (module.exports = {
624
+ CopilotRequestHandler,
625
+ CopilotWebSocketCloseStatus,
626
+ CopilotWebSocketForwarder,
627
+ CopilotWebSocketHandler,
628
+ createCopilotRequestAdapter
629
+ });