@github/copilot-sdk 1.0.15 → 1.0.16
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.
- package/README.md +136 -0
- package/dist/cjs/cliVersion.js +1 -1
- package/dist/cjs/client.js +319 -8
- package/dist/cjs/generated/rpc.js +116 -2
- package/dist/cjs/host.js +61 -0
- package/dist/cjs/index.js +3 -0
- package/dist/cliVersion.d.ts +1 -1
- package/dist/cliVersion.js +1 -1
- package/dist/client.d.ts +27 -1
- package/dist/client.js +316 -5
- package/dist/generated/rpc.d.ts +997 -14
- package/dist/generated/rpc.js +116 -2
- package/dist/generated/session-events.d.ts +81 -1
- package/dist/host.d.ts +86 -0
- package/dist/host.js +37 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +4 -0
- package/dist/types.d.ts +3 -0
- package/package.json +11 -10
package/dist/client.js
CHANGED
|
@@ -4,7 +4,7 @@ import { existsSync } from "node:fs";
|
|
|
4
4
|
import { isIPv6, Socket } from "node:net";
|
|
5
5
|
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
6
6
|
import {
|
|
7
|
-
createMessageConnection,
|
|
7
|
+
createMessageConnection as createRpcMessageConnection,
|
|
8
8
|
ErrorCodes,
|
|
9
9
|
ResponseError,
|
|
10
10
|
StreamMessageReader,
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
} from "./generated/rpc.js";
|
|
19
19
|
import { getSdkProtocolVersion } from "./sdkProtocolVersion.js";
|
|
20
20
|
import { CopilotSession } from "./session.js";
|
|
21
|
+
import { AhpHost } from "./host.js";
|
|
21
22
|
import { ensureRuntimeBundle } from "./runtimeArtifacts.js";
|
|
22
23
|
import { COPILOT_CLI_VERSION } from "./cliVersion.js";
|
|
23
24
|
import { createSessionFsAdapter } from "./sessionFsProvider.js";
|
|
@@ -31,6 +32,57 @@ import { ToolSet } from "./toolSet.js";
|
|
|
31
32
|
import { defaultJoinSessionPermissionHandler } from "./types.js";
|
|
32
33
|
const MIN_PROTOCOL_VERSION = 3;
|
|
33
34
|
const RUNTIME_SHUTDOWN_TIMEOUT_MS = 1e4;
|
|
35
|
+
function createMessageConnection(reader, writer) {
|
|
36
|
+
let dispatch;
|
|
37
|
+
let finishDrain;
|
|
38
|
+
let drained;
|
|
39
|
+
let disposed = false;
|
|
40
|
+
const barrier = { jsonrpc: "2.0", method: "$/sdkDrain" };
|
|
41
|
+
const connection = createRpcMessageConnection(
|
|
42
|
+
{
|
|
43
|
+
onError: reader.onError,
|
|
44
|
+
onClose: reader.onClose,
|
|
45
|
+
onPartialMessage: reader.onPartialMessage,
|
|
46
|
+
listen: (callback) => {
|
|
47
|
+
dispatch = callback;
|
|
48
|
+
return reader.listen(callback);
|
|
49
|
+
},
|
|
50
|
+
dispose: () => reader.dispose()
|
|
51
|
+
},
|
|
52
|
+
writer,
|
|
53
|
+
void 0,
|
|
54
|
+
{
|
|
55
|
+
messageStrategy: {
|
|
56
|
+
handleMessage: (message, next) => {
|
|
57
|
+
if (message === barrier) {
|
|
58
|
+
finishDrain?.();
|
|
59
|
+
} else {
|
|
60
|
+
next(message);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
connection.onDispose(() => {
|
|
67
|
+
disposed = true;
|
|
68
|
+
finishDrain?.();
|
|
69
|
+
});
|
|
70
|
+
return Object.assign(connection, {
|
|
71
|
+
drain: () => {
|
|
72
|
+
if (!drained) {
|
|
73
|
+
drained = new Promise((resolve2) => {
|
|
74
|
+
finishDrain = resolve2;
|
|
75
|
+
if (disposed || !dispatch) {
|
|
76
|
+
resolve2();
|
|
77
|
+
} else {
|
|
78
|
+
dispatch(barrier);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
return drained;
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
34
86
|
async function withTimeout(promise, timeoutMs, message) {
|
|
35
87
|
let timeout;
|
|
36
88
|
try {
|
|
@@ -217,6 +269,9 @@ class CopilotClient {
|
|
|
217
269
|
/** Shared in-flight start; concurrent callers await it instead of spawning another CLI. */
|
|
218
270
|
startPromise = null;
|
|
219
271
|
sessions = /* @__PURE__ */ new Map();
|
|
272
|
+
hostExitCallbacks = /* @__PURE__ */ new Map();
|
|
273
|
+
hostSessionFactories = /* @__PURE__ */ new Map();
|
|
274
|
+
hostHandoffs = /* @__PURE__ */ new Map();
|
|
220
275
|
stderrBuffer = "";
|
|
221
276
|
// Captures CLI stderr for error messages
|
|
222
277
|
/** Resolved connection mode chosen in the constructor. */
|
|
@@ -508,6 +563,11 @@ class CopilotClient {
|
|
|
508
563
|
}
|
|
509
564
|
setupClientGlobalHandlers() {
|
|
510
565
|
const handlers = {};
|
|
566
|
+
handlers.host = {
|
|
567
|
+
exited: async (exit) => {
|
|
568
|
+
this.handleHostExit(exit);
|
|
569
|
+
}
|
|
570
|
+
};
|
|
511
571
|
handlers.extensionLaunchProvider = this.extensionLaunchProvider;
|
|
512
572
|
if (this.requestHandler) {
|
|
513
573
|
this.requestAdapter = createCopilotRequestAdapter(this.requestHandler, () => {
|
|
@@ -661,7 +721,8 @@ class CopilotClient {
|
|
|
661
721
|
* 1. Closes all active sessions (releases in-memory resources)
|
|
662
722
|
* 2. Requests runtime shutdown for SDK-owned CLI processes
|
|
663
723
|
* 3. Closes the JSON-RPC connection
|
|
664
|
-
* 4.
|
|
724
|
+
* 4. Signals EOF to an owned stdio process and waits for host cleanup, then
|
|
725
|
+
* terminates the process if it does not exit within the shutdown timeout
|
|
665
726
|
*
|
|
666
727
|
* Note: session data on disk is preserved, so sessions can be resumed later.
|
|
667
728
|
* To permanently remove session data before stopping, call
|
|
@@ -779,7 +840,23 @@ class CopilotClient {
|
|
|
779
840
|
}
|
|
780
841
|
if (this.cliProcess && !this.isExternalServer) {
|
|
781
842
|
const child = this.cliProcess;
|
|
782
|
-
this.
|
|
843
|
+
if (this.connectionConfig.kind === "stdio" && child.stdin && child.exitCode == null && child.signalCode == null) {
|
|
844
|
+
const gracefulExitStart = Date.now();
|
|
845
|
+
try {
|
|
846
|
+
child.stdin.end();
|
|
847
|
+
const exited = await waitForChildExit(child, RUNTIME_SHUTDOWN_TIMEOUT_MS);
|
|
848
|
+
this.logDebugTiming(
|
|
849
|
+
exited ? "CopilotClient.stop graceful stdio exit complete" : "CopilotClient.stop graceful stdio exit timed out; terminating child",
|
|
850
|
+
gracefulExitStart
|
|
851
|
+
);
|
|
852
|
+
} catch (error) {
|
|
853
|
+
errors.push(
|
|
854
|
+
new Error(
|
|
855
|
+
`Failed to close CLI stdin: ${error instanceof Error ? error.message : String(error)}`
|
|
856
|
+
)
|
|
857
|
+
);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
783
860
|
try {
|
|
784
861
|
if (child.exitCode == null && child.signalCode == null) {
|
|
785
862
|
child.kill();
|
|
@@ -797,6 +874,10 @@ class CopilotClient {
|
|
|
797
874
|
`Failed to kill CLI process: ${error instanceof Error ? error.message : String(error)}`
|
|
798
875
|
)
|
|
799
876
|
);
|
|
877
|
+
} finally {
|
|
878
|
+
if (this.cliProcess === child) {
|
|
879
|
+
this.cliProcess = null;
|
|
880
|
+
}
|
|
800
881
|
}
|
|
801
882
|
}
|
|
802
883
|
if (this.ffiHost) {
|
|
@@ -820,6 +901,7 @@ class CopilotClient {
|
|
|
820
901
|
this.runtimePort = null;
|
|
821
902
|
this.stderrBuffer = "";
|
|
822
903
|
this.processExitPromise = null;
|
|
904
|
+
this.disconnectHosts();
|
|
823
905
|
return errors;
|
|
824
906
|
}
|
|
825
907
|
/**
|
|
@@ -864,6 +946,7 @@ class CopilotClient {
|
|
|
864
946
|
*/
|
|
865
947
|
async forceStop() {
|
|
866
948
|
this.forceStopping = true;
|
|
949
|
+
this.disconnectHosts();
|
|
867
950
|
for (const session of this.sessions.values()) {
|
|
868
951
|
session._markDisconnected();
|
|
869
952
|
}
|
|
@@ -1163,6 +1246,7 @@ class CopilotClient {
|
|
|
1163
1246
|
const response = await this.connection.sendRequest("session.create", {
|
|
1164
1247
|
...await getTraceContext(this.onGetTraceContext),
|
|
1165
1248
|
model: config.model,
|
|
1249
|
+
allowedModels: config.allowedModels,
|
|
1166
1250
|
sessionId: localSessionId,
|
|
1167
1251
|
clientName: config.clientName,
|
|
1168
1252
|
reasoningEffort: config.reasoningEffort,
|
|
@@ -1293,6 +1377,11 @@ class CopilotClient {
|
|
|
1293
1377
|
}
|
|
1294
1378
|
throw e;
|
|
1295
1379
|
}
|
|
1380
|
+
for (const entry of this.hostHandoffs.values()) {
|
|
1381
|
+
if (entry.requestedSessionId === session.sessionId) {
|
|
1382
|
+
entry.configs.set(session, config);
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1296
1385
|
return session;
|
|
1297
1386
|
}
|
|
1298
1387
|
/**
|
|
@@ -1399,6 +1488,7 @@ class CopilotClient {
|
|
|
1399
1488
|
}
|
|
1400
1489
|
try {
|
|
1401
1490
|
const response = await this.connection.sendRequest("session.resume", {
|
|
1491
|
+
allowedModels: config.allowedModels,
|
|
1402
1492
|
...await getTraceContext(this.onGetTraceContext),
|
|
1403
1493
|
sessionId,
|
|
1404
1494
|
clientName: config.clientName,
|
|
@@ -1527,8 +1617,205 @@ class CopilotClient {
|
|
|
1527
1617
|
}
|
|
1528
1618
|
throw e;
|
|
1529
1619
|
}
|
|
1620
|
+
for (const entry of this.hostHandoffs.values()) {
|
|
1621
|
+
if (entry.requestedSessionId === session.sessionId) {
|
|
1622
|
+
entry.configs.set(session, { ...config, sessionId });
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1530
1625
|
return session;
|
|
1531
1626
|
}
|
|
1627
|
+
/**
|
|
1628
|
+
* Start a complete AHP listener hosted in-process by this runtime.
|
|
1629
|
+
*
|
|
1630
|
+
* This connection owns the host. Disposing it or disconnecting the client
|
|
1631
|
+
* stops the listener without deleting sessions. The host uses a separate
|
|
1632
|
+
* SDK connection to the same runtime, not another runtime process.
|
|
1633
|
+
* Only one AHP host may own the catalog in an effective Copilot home
|
|
1634
|
+
* at a time. Other SDK clients and sessions remain usable in that home.
|
|
1635
|
+
* Select localServer, githubEnvironment, or both. An empty localServer
|
|
1636
|
+
* selects 127.0.0.1 on an available port. `onExit` runs at most once; owner disconnection cannot
|
|
1637
|
+
* acknowledge listener cleanup over the disconnected transport.
|
|
1638
|
+
*
|
|
1639
|
+
* @experimental
|
|
1640
|
+
*/
|
|
1641
|
+
async startAhpHost(options) {
|
|
1642
|
+
if (!options?.localServer && !options?.githubEnvironment) {
|
|
1643
|
+
throw new Error("At least one of localServer or githubEnvironment is required");
|
|
1644
|
+
}
|
|
1645
|
+
if (this.state !== "connected") {
|
|
1646
|
+
await this.start();
|
|
1647
|
+
}
|
|
1648
|
+
const rpc = this.rpc;
|
|
1649
|
+
const hostId = randomUUID();
|
|
1650
|
+
const {
|
|
1651
|
+
localServer,
|
|
1652
|
+
githubEnvironment,
|
|
1653
|
+
onExit,
|
|
1654
|
+
createSession,
|
|
1655
|
+
resumeSession,
|
|
1656
|
+
onSessionReleased
|
|
1657
|
+
} = options;
|
|
1658
|
+
if (createSession || resumeSession) {
|
|
1659
|
+
this.hostSessionFactories.set(hostId, {
|
|
1660
|
+
createSession,
|
|
1661
|
+
resumeSession,
|
|
1662
|
+
onSessionReleased
|
|
1663
|
+
});
|
|
1664
|
+
}
|
|
1665
|
+
if (onExit) {
|
|
1666
|
+
this.hostExitCallbacks.set(hostId, onExit);
|
|
1667
|
+
}
|
|
1668
|
+
try {
|
|
1669
|
+
const info = await rpc.host.start({
|
|
1670
|
+
hostId,
|
|
1671
|
+
localServer,
|
|
1672
|
+
githubEnvironment,
|
|
1673
|
+
sessionFactory: createSession ? true : void 0,
|
|
1674
|
+
resumeFactory: resumeSession ? true : void 0
|
|
1675
|
+
});
|
|
1676
|
+
return new AhpHost(
|
|
1677
|
+
info,
|
|
1678
|
+
async () => {
|
|
1679
|
+
await rpc.host.dispose({ hostId });
|
|
1680
|
+
},
|
|
1681
|
+
(sessionId) => rpc.host.publishSession({ hostId, sessionId })
|
|
1682
|
+
);
|
|
1683
|
+
} catch (error) {
|
|
1684
|
+
this.hostExitCallbacks.delete(hostId);
|
|
1685
|
+
this.releaseHostSessions(hostId);
|
|
1686
|
+
throw error;
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
handleHostExit(exit) {
|
|
1690
|
+
this.releaseHostSessions(exit.hostId);
|
|
1691
|
+
const onExit = this.hostExitCallbacks.get(exit.hostId);
|
|
1692
|
+
if (onExit) {
|
|
1693
|
+
this.hostExitCallbacks.delete(exit.hostId);
|
|
1694
|
+
const reportError = (error) => {
|
|
1695
|
+
console.error("AHP host exit callback failed", { hostId: exit.hostId, error });
|
|
1696
|
+
};
|
|
1697
|
+
try {
|
|
1698
|
+
void Promise.resolve(onExit(exit)).catch(reportError);
|
|
1699
|
+
} catch (error) {
|
|
1700
|
+
reportError(error);
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
disconnectHosts() {
|
|
1705
|
+
for (const hostId of /* @__PURE__ */ new Set([
|
|
1706
|
+
...this.hostExitCallbacks.keys(),
|
|
1707
|
+
...this.hostSessionFactories.keys()
|
|
1708
|
+
])) {
|
|
1709
|
+
this.handleHostExit({
|
|
1710
|
+
hostId,
|
|
1711
|
+
reason: "ownerDisconnected",
|
|
1712
|
+
error: "Owner connection closed; runtime cleanup cannot be acknowledged on this connection."
|
|
1713
|
+
});
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
releaseHostSessions(hostId) {
|
|
1717
|
+
this.hostSessionFactories.delete(hostId);
|
|
1718
|
+
for (const [handoffId, entry] of this.hostHandoffs) {
|
|
1719
|
+
if (entry.hostId === hostId) this.releaseHostSession(handoffId);
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
releaseHostSession(handoffId) {
|
|
1723
|
+
const entry = this.hostHandoffs.get(handoffId);
|
|
1724
|
+
if (!entry) return;
|
|
1725
|
+
this.hostHandoffs.delete(handoffId);
|
|
1726
|
+
entry.released = true;
|
|
1727
|
+
entry.configs.clear();
|
|
1728
|
+
entry.controller.abort();
|
|
1729
|
+
if (entry.session) this.notifyHostSessionReleased(entry);
|
|
1730
|
+
}
|
|
1731
|
+
notifyHostSessionReleased(entry) {
|
|
1732
|
+
const { session, onReleased } = entry;
|
|
1733
|
+
entry.session = void 0;
|
|
1734
|
+
if (session && onReleased) {
|
|
1735
|
+
try {
|
|
1736
|
+
void Promise.resolve(onReleased(session)).catch(() => {
|
|
1737
|
+
console.error("AHP session release callback failed");
|
|
1738
|
+
});
|
|
1739
|
+
} catch {
|
|
1740
|
+
console.error("AHP session release callback failed");
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
async createHostSession(params) {
|
|
1745
|
+
const options = this.hostSessionFactories.get(params.hostId);
|
|
1746
|
+
if (!(params.resume ? options?.resumeSession : options?.createSession) || this.hostHandoffs.has(params.handoffId)) {
|
|
1747
|
+
throw new Error("AHP session factory is unavailable or handoff already exists");
|
|
1748
|
+
}
|
|
1749
|
+
const entry = {
|
|
1750
|
+
hostId: params.hostId,
|
|
1751
|
+
requestedSessionId: params.config.sessionId,
|
|
1752
|
+
controller: new AbortController(),
|
|
1753
|
+
configs: /* @__PURE__ */ new Map(),
|
|
1754
|
+
released: false,
|
|
1755
|
+
onReleased: options.onSessionReleased
|
|
1756
|
+
};
|
|
1757
|
+
this.hostHandoffs.set(params.handoffId, entry);
|
|
1758
|
+
const { configDir, ...config } = params.config;
|
|
1759
|
+
if (configDir !== void 0) config.configDirectory = configDir;
|
|
1760
|
+
const expected = structuredClone(config);
|
|
1761
|
+
const cancelled = new Promise((_resolve, reject) => {
|
|
1762
|
+
entry.controller.signal.addEventListener(
|
|
1763
|
+
"abort",
|
|
1764
|
+
() => {
|
|
1765
|
+
reject(new Error("AHP session handoff ended"));
|
|
1766
|
+
},
|
|
1767
|
+
{ once: true }
|
|
1768
|
+
);
|
|
1769
|
+
});
|
|
1770
|
+
const materialized = Promise.resolve().then(() => {
|
|
1771
|
+
if (params.resume) {
|
|
1772
|
+
const { sessionId, ...resumeConfig } = config;
|
|
1773
|
+
if (!sessionId) throw new Error("AHP resume requires sessionId");
|
|
1774
|
+
return options.resumeSession({
|
|
1775
|
+
sessionId,
|
|
1776
|
+
config: resumeConfig,
|
|
1777
|
+
signal: entry.controller.signal
|
|
1778
|
+
});
|
|
1779
|
+
}
|
|
1780
|
+
return options.createSession({
|
|
1781
|
+
config,
|
|
1782
|
+
signal: entry.controller.signal
|
|
1783
|
+
});
|
|
1784
|
+
}).then((session) => {
|
|
1785
|
+
entry.session = session;
|
|
1786
|
+
const actual = entry.configs.get(session);
|
|
1787
|
+
entry.configs.clear();
|
|
1788
|
+
if (entry.released) {
|
|
1789
|
+
this.notifyHostSessionReleased(entry);
|
|
1790
|
+
throw new Error("AHP session handoff ended");
|
|
1791
|
+
}
|
|
1792
|
+
if (this.sessions.get(session.sessionId) !== session || session.sessionId !== expected.sessionId) {
|
|
1793
|
+
throw new Error(
|
|
1794
|
+
"AHP callback must return the requested session from this client"
|
|
1795
|
+
);
|
|
1796
|
+
}
|
|
1797
|
+
const contains = (value, required) => {
|
|
1798
|
+
if (required && typeof required === "object" && !Array.isArray(required)) {
|
|
1799
|
+
return !!value && typeof value === "object" && Object.entries(required).every(
|
|
1800
|
+
([key, child]) => contains(value[key], child)
|
|
1801
|
+
);
|
|
1802
|
+
}
|
|
1803
|
+
return JSON.stringify(value) === JSON.stringify(required);
|
|
1804
|
+
};
|
|
1805
|
+
if ((!params.resume || actual !== void 0) && !contains(actual, expected)) {
|
|
1806
|
+
throw new Error(
|
|
1807
|
+
"AHP callback must preserve the supplied session configuration"
|
|
1808
|
+
);
|
|
1809
|
+
}
|
|
1810
|
+
return { sessionId: session.sessionId };
|
|
1811
|
+
});
|
|
1812
|
+
try {
|
|
1813
|
+
return await Promise.race([materialized, cancelled]);
|
|
1814
|
+
} catch (error) {
|
|
1815
|
+
this.releaseHostSession(params.handoffId);
|
|
1816
|
+
throw error;
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1532
1819
|
/**
|
|
1533
1820
|
* Sends a ping request to the server to verify connectivity.
|
|
1534
1821
|
*
|
|
@@ -2068,8 +2355,13 @@ stderr: ${stderrOutput}`
|
|
|
2068
2355
|
case "inprocess":
|
|
2069
2356
|
return this.connectViaFfi();
|
|
2070
2357
|
case "tcp":
|
|
2071
|
-
case "uri":
|
|
2072
2358
|
return this.connectViaTcp();
|
|
2359
|
+
case "uri": {
|
|
2360
|
+
const { host, port } = this.parseCliUrl(this.connectionConfig.url);
|
|
2361
|
+
this.actualHost = host;
|
|
2362
|
+
this.runtimePort = port;
|
|
2363
|
+
return this.connectViaTcp();
|
|
2364
|
+
}
|
|
2073
2365
|
}
|
|
2074
2366
|
}
|
|
2075
2367
|
/** Starts the in-process FFI runtime with SDK-managed typed options. */
|
|
@@ -2277,6 +2569,18 @@ stderr: ${stderrOutput}` : ""}`
|
|
|
2277
2569
|
(params, token) => confirmation.confirm(params, token)
|
|
2278
2570
|
);
|
|
2279
2571
|
}
|
|
2572
|
+
this.connection.onRequest(
|
|
2573
|
+
"host.materializeSession",
|
|
2574
|
+
(params) => this.createHostSession(params)
|
|
2575
|
+
);
|
|
2576
|
+
this.connection.onNotification(
|
|
2577
|
+
"host.sessionReleased",
|
|
2578
|
+
(params) => {
|
|
2579
|
+
if (this.hostHandoffs.get(params.handoffId)?.hostId === params.hostId) {
|
|
2580
|
+
this.releaseHostSession(params.handoffId);
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2583
|
+
);
|
|
2280
2584
|
this.connection.onRequest(
|
|
2281
2585
|
"hooks.invoke",
|
|
2282
2586
|
async (params) => {
|
|
@@ -2284,20 +2588,27 @@ stderr: ${stderrOutput}` : ""}`
|
|
|
2284
2588
|
}
|
|
2285
2589
|
);
|
|
2286
2590
|
const connection = this.connection;
|
|
2591
|
+
const messageWriter = this.messageWriter;
|
|
2287
2592
|
const markDisconnected = () => {
|
|
2288
2593
|
if (this.connection !== connection) {
|
|
2594
|
+
connection.dispose();
|
|
2289
2595
|
return;
|
|
2290
2596
|
}
|
|
2291
2597
|
this.connectionClosed = true;
|
|
2292
2598
|
this.state = "disconnected";
|
|
2599
|
+
this.disconnectHosts();
|
|
2293
2600
|
for (const session of this.sessions.values()) {
|
|
2294
2601
|
session._markDisconnected();
|
|
2295
2602
|
}
|
|
2296
2603
|
this.sessions.clear();
|
|
2297
2604
|
this.githubTokenProviders.clear();
|
|
2298
2605
|
this.requestAdapter?.cancelPending();
|
|
2606
|
+
connection.dispose();
|
|
2299
2607
|
};
|
|
2300
|
-
this.connection.onClose(
|
|
2608
|
+
this.connection.onClose(() => {
|
|
2609
|
+
if (messageWriter) messageWriter.suppressWriteErrors = true;
|
|
2610
|
+
void connection.drain().then(markDisconnected);
|
|
2611
|
+
});
|
|
2301
2612
|
this.connection.onError(() => {
|
|
2302
2613
|
if (this.connection === connection) {
|
|
2303
2614
|
this.state = "disconnected";
|