@github/copilot-sdk 1.0.16-preview.0 → 1.0.17-preview.0
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 +12 -2
- package/dist/cjs/cliVersion.js +1 -1
- package/dist/cjs/client.js +69 -9
- package/dist/cjs/ffiRuntimeHost.js +14 -3
- package/dist/cjs/generated/rpc.js +40 -4
- package/dist/cjs/session.js +9 -0
- package/dist/cliVersion.d.ts +1 -1
- package/dist/cliVersion.js +1 -1
- package/dist/client.d.ts +1 -0
- package/dist/client.js +69 -9
- package/dist/ffiRuntimeHost.d.ts +1 -0
- package/dist/ffiRuntimeHost.js +14 -3
- package/dist/generated/rpc.d.ts +349 -5
- package/dist/generated/rpc.js +40 -4
- package/dist/generated/session-events.d.ts +3 -3
- package/dist/index.d.ts +1 -1
- package/dist/session.d.ts +4 -1
- package/dist/session.js +9 -0
- package/dist/types.d.ts +17 -2
- package/package.json +17 -12
package/README.md
CHANGED
|
@@ -9,8 +9,8 @@ To use the SDK, you'll need:
|
|
|
9
9
|
- Node.js ^20.19.0 or >=22.12.0
|
|
10
10
|
|
|
11
11
|
The SDK uses an optional `@github/copilot-sdk-<platform>` package containing the
|
|
12
|
-
Copilot CLI runtime for the host platform. These packages
|
|
13
|
-
|
|
12
|
+
Copilot CLI runtime for the host platform. These packages contain the verified
|
|
13
|
+
runtime artifacts for that SDK release, so
|
|
14
14
|
starting the SDK performs no runtime download. Set `COPILOT_CLI_PATH` to use an
|
|
15
15
|
existing installation instead.
|
|
16
16
|
|
|
@@ -20,6 +20,10 @@ replaces both with the CLI version published for that snapshot.
|
|
|
20
20
|
|
|
21
21
|
`npm run pack:release` builds the main package and all platform packages. Set
|
|
22
22
|
`COPILOT_CLI_DOWNLOAD_BASE_URL` to use a release mirror while packaging.
|
|
23
|
+
Standalone source builds acquire stable/prerelease runtime assets from
|
|
24
|
+
`github/copilot-cli` at `v<runtime-version>`, and canonical unstable assets from
|
|
25
|
+
`github/copilot-sdk` at `runtime-<runtime-version>`. Both checksum and package
|
|
26
|
+
downloads use that release location; mirror overrides retain the same tag scheme.
|
|
23
27
|
Release workflows instead set `COPILOT_SDK_RUNTIME_PACKAGE_DIR` to a directory
|
|
24
28
|
containing validated runtime npm package roots named for all eight platforms.
|
|
25
29
|
This keeps `COPILOT_CLI_USE_NPM_PACKAGE` false and embeds those runtime files in
|
|
@@ -368,6 +372,12 @@ Initial acquisition runs during session creation or resume. Cancellation, provid
|
|
|
368
372
|
##### `resumeSession(sessionId: string, config?: ResumeSessionConfig): Promise<CopilotSession>`
|
|
369
373
|
|
|
370
374
|
Resume an existing session. Returns the session with `workspacePath` populated if infinite sessions were enabled.
|
|
375
|
+
`allowTranscriptRecovery` controls whether the runtime may repair a damaged transcript during
|
|
376
|
+
resume. It defaults to `true` in all modes; set it to `false` to reject recovery.
|
|
377
|
+
When repair occurs, inspect `session.transcriptRecovery` for the planned backup path,
|
|
378
|
+
invalid line numbers, and whether `session.start` was moved. The backup
|
|
379
|
+
is written when the repaired transcript is next appended, not during resume. Disabling recovery
|
|
380
|
+
still permits adding a missing newline after an intact final record; it rejects torn tails.
|
|
371
381
|
|
|
372
382
|
##### `ping(message?: string): Promise<{ message: string; timestamp: string }>`
|
|
373
383
|
|
package/dist/cjs/cliVersion.js
CHANGED
|
@@ -22,7 +22,7 @@ __export(cliVersion_exports, {
|
|
|
22
22
|
COPILOT_CLI_VERSION: () => COPILOT_CLI_VERSION
|
|
23
23
|
});
|
|
24
24
|
module.exports = __toCommonJS(cliVersion_exports);
|
|
25
|
-
const COPILOT_CLI_VERSION = "1.0.
|
|
25
|
+
const COPILOT_CLI_VERSION = "1.0.92-0";
|
|
26
26
|
const COPILOT_CLI_USE_NPM_PACKAGE = false;
|
|
27
27
|
// Annotate the CommonJS export names for ESM import in node:
|
|
28
28
|
0 && (module.exports = {
|
package/dist/cjs/client.js
CHANGED
|
@@ -282,6 +282,7 @@ class CopilotClient {
|
|
|
282
282
|
requestAdapter = null;
|
|
283
283
|
messageWriter = null;
|
|
284
284
|
connectionClosed = false;
|
|
285
|
+
connectionEpoch = 0;
|
|
285
286
|
socket = null;
|
|
286
287
|
runtimePort = null;
|
|
287
288
|
actualHost = "localhost";
|
|
@@ -691,6 +692,7 @@ class CopilotClient {
|
|
|
691
692
|
}
|
|
692
693
|
async doStart() {
|
|
693
694
|
this.forceStopping = false;
|
|
695
|
+
this.connectionEpoch++;
|
|
694
696
|
this.connectionClosed = false;
|
|
695
697
|
this.processTransportError = null;
|
|
696
698
|
this.state = "connecting";
|
|
@@ -726,6 +728,9 @@ class CopilotClient {
|
|
|
726
728
|
if (this.requestHandler) {
|
|
727
729
|
await this.connection.sendRequest("llmInference.setProvider", {});
|
|
728
730
|
}
|
|
731
|
+
if (this.connectionClosed) {
|
|
732
|
+
throw new Error("CLI server connection closed during startup");
|
|
733
|
+
}
|
|
729
734
|
this.state = "connected";
|
|
730
735
|
} catch (error) {
|
|
731
736
|
const startupError = this.processTransportError ?? error;
|
|
@@ -845,10 +850,23 @@ class CopilotClient {
|
|
|
845
850
|
this.socket = null;
|
|
846
851
|
try {
|
|
847
852
|
if (!socket.destroyed) {
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
853
|
+
let timeout;
|
|
854
|
+
try {
|
|
855
|
+
await new Promise((resolve2) => {
|
|
856
|
+
socket.once("close", () => resolve2());
|
|
857
|
+
if (this.connectionClosed) {
|
|
858
|
+
socket.destroy();
|
|
859
|
+
} else {
|
|
860
|
+
timeout = setTimeout(
|
|
861
|
+
() => socket.destroy(),
|
|
862
|
+
RUNTIME_SHUTDOWN_TIMEOUT_MS
|
|
863
|
+
);
|
|
864
|
+
socket.end();
|
|
865
|
+
}
|
|
866
|
+
});
|
|
867
|
+
} finally {
|
|
868
|
+
if (timeout !== void 0) clearTimeout(timeout);
|
|
869
|
+
}
|
|
852
870
|
}
|
|
853
871
|
} catch (error) {
|
|
854
872
|
errors.push(
|
|
@@ -1511,6 +1529,7 @@ class CopilotClient {
|
|
|
1511
1529
|
allowedModels: config.allowedModels,
|
|
1512
1530
|
...await (0, import_telemetry.getTraceContext)(this.onGetTraceContext),
|
|
1513
1531
|
sessionId,
|
|
1532
|
+
allowTranscriptRecovery: config.allowTranscriptRecovery,
|
|
1514
1533
|
clientName: config.clientName,
|
|
1515
1534
|
model: config.model,
|
|
1516
1535
|
reasoningEffort: config.reasoningEffort,
|
|
@@ -1617,10 +1636,11 @@ class CopilotClient {
|
|
|
1617
1636
|
}
|
|
1618
1637
|
}
|
|
1619
1638
|
}
|
|
1620
|
-
const { workspacePath, capabilities, openCanvases } = response;
|
|
1639
|
+
const { workspacePath, capabilities, openCanvases, transcriptRecovery } = response;
|
|
1621
1640
|
session["_workspacePath"] = workspacePath;
|
|
1622
1641
|
session.setCapabilities(capabilities);
|
|
1623
1642
|
session.setOpenCanvases(openCanvases ?? []);
|
|
1643
|
+
session.setTranscriptRecovery(transcriptRecovery);
|
|
1624
1644
|
if (config.onMcpAuthRequest) {
|
|
1625
1645
|
await this.connection.sendRequest("session.eventLog.registerInterest", {
|
|
1626
1646
|
sessionId,
|
|
@@ -2609,8 +2629,13 @@ stderr: ${stderrOutput}` : ""}`
|
|
|
2609
2629
|
);
|
|
2610
2630
|
const connection = this.connection;
|
|
2611
2631
|
const messageWriter = this.messageWriter;
|
|
2632
|
+
const cliProcess = this.isExternalServer ? null : this.cliProcess;
|
|
2633
|
+
const connectionEpoch = this.connectionEpoch;
|
|
2634
|
+
const isCurrentConnection = () => connectionEpoch === this.connectionEpoch && this.connection === connection && (cliProcess === null || this.cliProcess === cliProcess);
|
|
2635
|
+
let disconnecting = false;
|
|
2636
|
+
let exitFallback;
|
|
2612
2637
|
const markDisconnected = () => {
|
|
2613
|
-
if (
|
|
2638
|
+
if (!isCurrentConnection()) {
|
|
2614
2639
|
connection.dispose();
|
|
2615
2640
|
return;
|
|
2616
2641
|
}
|
|
@@ -2624,13 +2649,48 @@ stderr: ${stderrOutput}` : ""}`
|
|
|
2624
2649
|
this.githubTokenProviders.clear();
|
|
2625
2650
|
this.requestAdapter?.cancelPending();
|
|
2626
2651
|
connection.dispose();
|
|
2652
|
+
this.socket?.destroy();
|
|
2653
|
+
this.connection = null;
|
|
2654
|
+
this.messageWriter = null;
|
|
2655
|
+
this._rpc = null;
|
|
2656
|
+
this._internalRpc = null;
|
|
2657
|
+
this.modelsCache = null;
|
|
2627
2658
|
};
|
|
2628
|
-
|
|
2659
|
+
const disconnectAfterDrain = () => {
|
|
2660
|
+
if (exitFallback !== void 0) {
|
|
2661
|
+
clearTimeout(exitFallback);
|
|
2662
|
+
exitFallback = void 0;
|
|
2663
|
+
}
|
|
2664
|
+
if (!isCurrentConnection()) {
|
|
2665
|
+
connection.dispose();
|
|
2666
|
+
return;
|
|
2667
|
+
}
|
|
2668
|
+
if (disconnecting) return;
|
|
2669
|
+
disconnecting = true;
|
|
2670
|
+
this.connectionClosed = true;
|
|
2629
2671
|
if (messageWriter) messageWriter.suppressWriteErrors = true;
|
|
2630
2672
|
void connection.drain().then(markDisconnected);
|
|
2631
|
-
}
|
|
2673
|
+
};
|
|
2674
|
+
this.connection.onClose(disconnectAfterDrain);
|
|
2675
|
+
if (cliProcess) {
|
|
2676
|
+
cliProcess.once("exit", () => {
|
|
2677
|
+
if (!isCurrentConnection()) {
|
|
2678
|
+
connection.dispose();
|
|
2679
|
+
return;
|
|
2680
|
+
}
|
|
2681
|
+
if (disconnecting) return;
|
|
2682
|
+
if (this.socket) {
|
|
2683
|
+
disconnectAfterDrain();
|
|
2684
|
+
return;
|
|
2685
|
+
}
|
|
2686
|
+
this.connectionClosed = true;
|
|
2687
|
+
if (messageWriter) messageWriter.suppressWriteErrors = true;
|
|
2688
|
+
exitFallback = setTimeout(disconnectAfterDrain, RUNTIME_SHUTDOWN_TIMEOUT_MS);
|
|
2689
|
+
exitFallback.unref();
|
|
2690
|
+
});
|
|
2691
|
+
}
|
|
2632
2692
|
this.connection.onError(() => {
|
|
2633
|
-
if (this.connection === connection) {
|
|
2693
|
+
if (connectionEpoch === this.connectionEpoch && this.connection === connection) {
|
|
2634
2694
|
this.state = "disconnected";
|
|
2635
2695
|
}
|
|
2636
2696
|
});
|
|
@@ -208,7 +208,7 @@ class FfiRuntimeHost {
|
|
|
208
208
|
);
|
|
209
209
|
if (!this.connectionId) {
|
|
210
210
|
this.unregisterCallback();
|
|
211
|
-
this.
|
|
211
|
+
await this.shutdownHost();
|
|
212
212
|
this.serverId = 0;
|
|
213
213
|
throw new Error("copilot_runtime_connection_open failed.");
|
|
214
214
|
}
|
|
@@ -217,7 +217,7 @@ class FfiRuntimeHost {
|
|
|
217
217
|
} finally {
|
|
218
218
|
this.starting = false;
|
|
219
219
|
if (this.disposed) {
|
|
220
|
-
|
|
220
|
+
await this.tryFinalizeCleanup();
|
|
221
221
|
}
|
|
222
222
|
}
|
|
223
223
|
}
|
|
@@ -256,6 +256,17 @@ class FfiRuntimeHost {
|
|
|
256
256
|
);
|
|
257
257
|
}
|
|
258
258
|
}
|
|
259
|
+
shutdownHost() {
|
|
260
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
261
|
+
this.lib.hostShutdown.async(this.serverId, (error, result) => {
|
|
262
|
+
if (error) {
|
|
263
|
+
rejectPromise(error);
|
|
264
|
+
} else {
|
|
265
|
+
resolvePromise(result);
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
}
|
|
259
270
|
unregisterCallback() {
|
|
260
271
|
if (this.outboundCallback === void 0) {
|
|
261
272
|
return true;
|
|
@@ -328,7 +339,7 @@ class FfiRuntimeHost {
|
|
|
328
339
|
}
|
|
329
340
|
if (this.serverId) {
|
|
330
341
|
try {
|
|
331
|
-
if (!this.
|
|
342
|
+
if (!await this.shutdownHost()) {
|
|
332
343
|
console.error(
|
|
333
344
|
`In-process FFI host shutdown did not recognize server ${this.serverId}.`
|
|
334
345
|
);
|
|
@@ -647,15 +647,45 @@ function createServerRpc(connection) {
|
|
|
647
647
|
/** @experimental */
|
|
648
648
|
managedSettings: {
|
|
649
649
|
/**
|
|
650
|
-
* Discovers device-managed settings from production MDM and managed-file sources, validates them against the runtime-owned managed-settings schema, and returns the canonical JSON without requiring a session.
|
|
650
|
+
* Discovers device-managed settings from production MDM and managed-file sources, validates them against the runtime-owned managed-settings schema, and returns the canonical JSON without requiring a session. `managedSettings.resolve` returns the same device settings together with the account's server policy.
|
|
651
651
|
*
|
|
652
652
|
* @returns Validated device-managed settings discovered before a session exists.
|
|
653
653
|
*/
|
|
654
654
|
read: async () => connection.sendRequest("managedSettings.read", {}),
|
|
655
655
|
/**
|
|
656
|
-
* Force-refreshes enterprise managed settings for every account: wipes the persistent server-policy cache (the whole `<cacheHome>/managed-settings` directory) and drops this runtime process's in-memory retained server policy. It does not itself fetch policy — the effect is that the next time a session resolves managed settings for an account, that resolution re-fetches the account's org policy from the network instead of serving a cached response. Note that `managedSettings.read` returns only device/MDM settings and never triggers the account server-policy fetch, so a host implementing "sync account policy" should start a fresh session resolution rather than treat a subsequent `managedSettings.read` as the refreshed org policy. Mirrors the invalidation a sign-out performs, broadened from the one signing-out account to all of them; device/MDM layers describe the machine, not the account, and are left untouched. Rejects if the on-disk cache cannot be removed.
|
|
656
|
+
* Force-refreshes enterprise managed settings for every account: wipes the persistent server-policy cache (the whole `<cacheHome>/managed-settings` directory) and drops this runtime process's in-memory retained server policy. It does not itself fetch policy — the effect is that the next time a session resolves managed settings for an account, that resolution re-fetches the account's org policy from the network instead of serving a cached response. Note that `managedSettings.read` returns only device/MDM settings and never triggers the account server-policy fetch, so a host implementing "sync account policy" should call `managedSettings.resolve` or start a fresh session resolution rather than treat a subsequent `managedSettings.read` as the refreshed org policy. Mirrors the invalidation a sign-out performs, broadened from the one signing-out account to all of them; device/MDM layers describe the machine, not the account, and are left untouched. Rejects if the on-disk cache cannot be removed.
|
|
657
657
|
*/
|
|
658
|
-
clearCache: async () => connection.sendRequest("managedSettings.clearCache", {})
|
|
658
|
+
clearCache: async () => connection.sendRequest("managedSettings.clearCache", {}),
|
|
659
|
+
/**
|
|
660
|
+
* Resolves the effective enterprise managed settings without a session, from the device channel and, when an account is available, the account's server policy through the same per-account cache sessions use. A cached server policy less than an hour old is used without a fetch; otherwise the policy is fetched, and when the fetch fails a cached policy up to 24 hours old is used instead, unless `forceRemoteSettingsRefresh` requires a live fetch. With no account requested or signed in, it reports device policy only; signing out removes the account's cached policy. It can fetch server policy over the network when the cache is stale, so call it off latency-critical paths such as startup rather than before listing models. The policy helper is not run. `layers` lists each channel's document before merging, and `values` and `meta` carry typed effective values and their lock state for the keys typed so far.
|
|
661
|
+
*
|
|
662
|
+
* @param params Optional opaque account selection or GitHub token whose managed settings are resolved.
|
|
663
|
+
*
|
|
664
|
+
* @returns Effective enterprise managed settings for an account, resolved without a session.
|
|
665
|
+
*/
|
|
666
|
+
resolve: async (params) => connection.sendRequest("managedSettings.resolve", params),
|
|
667
|
+
/**
|
|
668
|
+
* Returns the managed-settings authoring JSON schema with descriptive `x-composition` annotations aligned with the shared settings-engine vocabulary. These annotations are not a complete runtime composition contract: model, effortLevel, and contextTier remain coupled. Use `managedSettings.compose` for the runtime's effective result. Performs no I/O.
|
|
669
|
+
*
|
|
670
|
+
* @returns The authoring JSON schema for managed settings recognized by this runtime.
|
|
671
|
+
*/
|
|
672
|
+
schema: async () => connection.sendRequest("managedSettings.schema", {}),
|
|
673
|
+
/**
|
|
674
|
+
* Validates a candidate managed-settings document the way the runtime validates delivered policy, without applying it. Reports errors that would reject the document, warnings for content the runtime ignores, and the canonical document it would apply. Document text nested more than 64 levels deep is rejected. Performs no I/O.
|
|
675
|
+
*
|
|
676
|
+
* @param params A candidate managed-settings document to validate without applying it.
|
|
677
|
+
*
|
|
678
|
+
* @returns Result of validating a managed-settings document.
|
|
679
|
+
*/
|
|
680
|
+
validate: async (params) => connection.sendRequest("managedSettings.validate", params),
|
|
681
|
+
/**
|
|
682
|
+
* Merges candidate managed-settings documents for the device, server, and policy-helper channels into the effective settings the runtime would enforce on this host, using the same precedence and composition rules as live resolution, without applying them. Like live resolution, a server's advisory sandbox force-enable is declined on a host that cannot run the sandbox. Does not fetch policy or read policy files, but may perform blocking OS or subprocess probes for sandbox support. Preview documents are limited to 1 MiB and 64 levels of nesting.
|
|
683
|
+
*
|
|
684
|
+
* @param params Candidate managed-settings documents to merge without applying them.
|
|
685
|
+
*
|
|
686
|
+
* @returns The effective managed settings the runtime would enforce for the given documents, in the same shape `managedSettings.resolve` returns.
|
|
687
|
+
*/
|
|
688
|
+
compose: async (params) => connection.sendRequest("managedSettings.compose", params)
|
|
659
689
|
},
|
|
660
690
|
/** @experimental */
|
|
661
691
|
runtime: {
|
|
@@ -2103,6 +2133,12 @@ function createSessionRpc(connection, sessionId) {
|
|
|
2103
2133
|
* @returns Feature detection and hard polling limits for the EXPERIMENTAL session connector API.
|
|
2104
2134
|
*/
|
|
2105
2135
|
getCapabilities: async () => connection.sendRequest("session.connectors.getCapabilities", { sessionId }),
|
|
2136
|
+
/**
|
|
2137
|
+
* Returns the session account selection, or null.
|
|
2138
|
+
*
|
|
2139
|
+
* @returns Session account selection, or null.
|
|
2140
|
+
*/
|
|
2141
|
+
getAccount: async () => connection.sendRequest("session.connectors.getAccount", { sessionId }),
|
|
2106
2142
|
/**
|
|
2107
2143
|
* Returns authoritative session Connector state from current availability, pinned account selection, cached catalog, and live MCP projection without performing a Connector service request.
|
|
2108
2144
|
*
|
|
@@ -2171,7 +2207,7 @@ function createSessionRpc(connection, sessionId) {
|
|
|
2171
2207
|
/**
|
|
2172
2208
|
* Waits for the live session's in-flight managed-settings application, then returns the retained effective snapshot used by runtime enforcement and by `session.managed_settings_resolved`. It does not perform another account, device, or server resolution, and rejects when resolution has not produced a snapshot.
|
|
2173
2209
|
*
|
|
2174
|
-
* @returns
|
|
2210
|
+
* @returns Effective enterprise managed settings and contributing channels. Session events report applied policy; sessionless resolve reports an account/device snapshot, and compose reports a non-applying preview of candidate documents. Device values take precedence over server values, then the policy helper, per ordinary key, while permissions compose restrictively. Session-local SDK-client policy is included only in session results. Marked experimental while the managed-settings surface stabilizes.
|
|
2175
2211
|
*/
|
|
2176
2212
|
get: async () => connection.sendRequest("session.managedSettings.get", { sessionId })
|
|
2177
2213
|
},
|
package/dist/cjs/session.js
CHANGED
|
@@ -254,6 +254,7 @@ class CopilotSession {
|
|
|
254
254
|
sessionId;
|
|
255
255
|
connection;
|
|
256
256
|
_workspacePath;
|
|
257
|
+
_transcriptRecovery;
|
|
257
258
|
eventHandlers = /* @__PURE__ */ new Set();
|
|
258
259
|
typedEventHandlers = /* @__PURE__ */ new Map();
|
|
259
260
|
toolHandlers = /* @__PURE__ */ new Map();
|
|
@@ -443,6 +444,14 @@ class CopilotSession {
|
|
|
443
444
|
get workspacePath() {
|
|
444
445
|
return this._workspacePath;
|
|
445
446
|
}
|
|
447
|
+
/** Recovery observed while loading this session; undefined on a clean resume. */
|
|
448
|
+
get transcriptRecovery() {
|
|
449
|
+
return this._transcriptRecovery;
|
|
450
|
+
}
|
|
451
|
+
/** @internal */
|
|
452
|
+
setTranscriptRecovery(recovery) {
|
|
453
|
+
this._transcriptRecovery = recovery;
|
|
454
|
+
}
|
|
446
455
|
/**
|
|
447
456
|
* Host capabilities reported when the session was created or resumed.
|
|
448
457
|
* Use this to check feature support before calling capability-gated APIs.
|
package/dist/cliVersion.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const COPILOT_CLI_VERSION = "1.0.
|
|
1
|
+
export declare const COPILOT_CLI_VERSION = "1.0.92-0";
|
|
2
2
|
export declare const COPILOT_CLI_USE_NPM_PACKAGE = false;
|
package/dist/cliVersion.js
CHANGED
package/dist/client.d.ts
CHANGED
package/dist/client.js
CHANGED
|
@@ -262,6 +262,7 @@ class CopilotClient {
|
|
|
262
262
|
requestAdapter = null;
|
|
263
263
|
messageWriter = null;
|
|
264
264
|
connectionClosed = false;
|
|
265
|
+
connectionEpoch = 0;
|
|
265
266
|
socket = null;
|
|
266
267
|
runtimePort = null;
|
|
267
268
|
actualHost = "localhost";
|
|
@@ -671,6 +672,7 @@ class CopilotClient {
|
|
|
671
672
|
}
|
|
672
673
|
async doStart() {
|
|
673
674
|
this.forceStopping = false;
|
|
675
|
+
this.connectionEpoch++;
|
|
674
676
|
this.connectionClosed = false;
|
|
675
677
|
this.processTransportError = null;
|
|
676
678
|
this.state = "connecting";
|
|
@@ -706,6 +708,9 @@ class CopilotClient {
|
|
|
706
708
|
if (this.requestHandler) {
|
|
707
709
|
await this.connection.sendRequest("llmInference.setProvider", {});
|
|
708
710
|
}
|
|
711
|
+
if (this.connectionClosed) {
|
|
712
|
+
throw new Error("CLI server connection closed during startup");
|
|
713
|
+
}
|
|
709
714
|
this.state = "connected";
|
|
710
715
|
} catch (error) {
|
|
711
716
|
const startupError = this.processTransportError ?? error;
|
|
@@ -825,10 +830,23 @@ class CopilotClient {
|
|
|
825
830
|
this.socket = null;
|
|
826
831
|
try {
|
|
827
832
|
if (!socket.destroyed) {
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
833
|
+
let timeout;
|
|
834
|
+
try {
|
|
835
|
+
await new Promise((resolve2) => {
|
|
836
|
+
socket.once("close", () => resolve2());
|
|
837
|
+
if (this.connectionClosed) {
|
|
838
|
+
socket.destroy();
|
|
839
|
+
} else {
|
|
840
|
+
timeout = setTimeout(
|
|
841
|
+
() => socket.destroy(),
|
|
842
|
+
RUNTIME_SHUTDOWN_TIMEOUT_MS
|
|
843
|
+
);
|
|
844
|
+
socket.end();
|
|
845
|
+
}
|
|
846
|
+
});
|
|
847
|
+
} finally {
|
|
848
|
+
if (timeout !== void 0) clearTimeout(timeout);
|
|
849
|
+
}
|
|
832
850
|
}
|
|
833
851
|
} catch (error) {
|
|
834
852
|
errors.push(
|
|
@@ -1491,6 +1509,7 @@ class CopilotClient {
|
|
|
1491
1509
|
allowedModels: config.allowedModels,
|
|
1492
1510
|
...await getTraceContext(this.onGetTraceContext),
|
|
1493
1511
|
sessionId,
|
|
1512
|
+
allowTranscriptRecovery: config.allowTranscriptRecovery,
|
|
1494
1513
|
clientName: config.clientName,
|
|
1495
1514
|
model: config.model,
|
|
1496
1515
|
reasoningEffort: config.reasoningEffort,
|
|
@@ -1597,10 +1616,11 @@ class CopilotClient {
|
|
|
1597
1616
|
}
|
|
1598
1617
|
}
|
|
1599
1618
|
}
|
|
1600
|
-
const { workspacePath, capabilities, openCanvases } = response;
|
|
1619
|
+
const { workspacePath, capabilities, openCanvases, transcriptRecovery } = response;
|
|
1601
1620
|
session["_workspacePath"] = workspacePath;
|
|
1602
1621
|
session.setCapabilities(capabilities);
|
|
1603
1622
|
session.setOpenCanvases(openCanvases ?? []);
|
|
1623
|
+
session.setTranscriptRecovery(transcriptRecovery);
|
|
1604
1624
|
if (config.onMcpAuthRequest) {
|
|
1605
1625
|
await this.connection.sendRequest("session.eventLog.registerInterest", {
|
|
1606
1626
|
sessionId,
|
|
@@ -2589,8 +2609,13 @@ stderr: ${stderrOutput}` : ""}`
|
|
|
2589
2609
|
);
|
|
2590
2610
|
const connection = this.connection;
|
|
2591
2611
|
const messageWriter = this.messageWriter;
|
|
2612
|
+
const cliProcess = this.isExternalServer ? null : this.cliProcess;
|
|
2613
|
+
const connectionEpoch = this.connectionEpoch;
|
|
2614
|
+
const isCurrentConnection = () => connectionEpoch === this.connectionEpoch && this.connection === connection && (cliProcess === null || this.cliProcess === cliProcess);
|
|
2615
|
+
let disconnecting = false;
|
|
2616
|
+
let exitFallback;
|
|
2592
2617
|
const markDisconnected = () => {
|
|
2593
|
-
if (
|
|
2618
|
+
if (!isCurrentConnection()) {
|
|
2594
2619
|
connection.dispose();
|
|
2595
2620
|
return;
|
|
2596
2621
|
}
|
|
@@ -2604,13 +2629,48 @@ stderr: ${stderrOutput}` : ""}`
|
|
|
2604
2629
|
this.githubTokenProviders.clear();
|
|
2605
2630
|
this.requestAdapter?.cancelPending();
|
|
2606
2631
|
connection.dispose();
|
|
2632
|
+
this.socket?.destroy();
|
|
2633
|
+
this.connection = null;
|
|
2634
|
+
this.messageWriter = null;
|
|
2635
|
+
this._rpc = null;
|
|
2636
|
+
this._internalRpc = null;
|
|
2637
|
+
this.modelsCache = null;
|
|
2607
2638
|
};
|
|
2608
|
-
|
|
2639
|
+
const disconnectAfterDrain = () => {
|
|
2640
|
+
if (exitFallback !== void 0) {
|
|
2641
|
+
clearTimeout(exitFallback);
|
|
2642
|
+
exitFallback = void 0;
|
|
2643
|
+
}
|
|
2644
|
+
if (!isCurrentConnection()) {
|
|
2645
|
+
connection.dispose();
|
|
2646
|
+
return;
|
|
2647
|
+
}
|
|
2648
|
+
if (disconnecting) return;
|
|
2649
|
+
disconnecting = true;
|
|
2650
|
+
this.connectionClosed = true;
|
|
2609
2651
|
if (messageWriter) messageWriter.suppressWriteErrors = true;
|
|
2610
2652
|
void connection.drain().then(markDisconnected);
|
|
2611
|
-
}
|
|
2653
|
+
};
|
|
2654
|
+
this.connection.onClose(disconnectAfterDrain);
|
|
2655
|
+
if (cliProcess) {
|
|
2656
|
+
cliProcess.once("exit", () => {
|
|
2657
|
+
if (!isCurrentConnection()) {
|
|
2658
|
+
connection.dispose();
|
|
2659
|
+
return;
|
|
2660
|
+
}
|
|
2661
|
+
if (disconnecting) return;
|
|
2662
|
+
if (this.socket) {
|
|
2663
|
+
disconnectAfterDrain();
|
|
2664
|
+
return;
|
|
2665
|
+
}
|
|
2666
|
+
this.connectionClosed = true;
|
|
2667
|
+
if (messageWriter) messageWriter.suppressWriteErrors = true;
|
|
2668
|
+
exitFallback = setTimeout(disconnectAfterDrain, RUNTIME_SHUTDOWN_TIMEOUT_MS);
|
|
2669
|
+
exitFallback.unref();
|
|
2670
|
+
});
|
|
2671
|
+
}
|
|
2612
2672
|
this.connection.onError(() => {
|
|
2613
|
-
if (this.connection === connection) {
|
|
2673
|
+
if (connectionEpoch === this.connectionEpoch && this.connection === connection) {
|
|
2614
2674
|
this.state = "disconnected";
|
|
2615
2675
|
}
|
|
2616
2676
|
});
|
package/dist/ffiRuntimeHost.d.ts
CHANGED
package/dist/ffiRuntimeHost.js
CHANGED
|
@@ -175,7 +175,7 @@ class FfiRuntimeHost {
|
|
|
175
175
|
);
|
|
176
176
|
if (!this.connectionId) {
|
|
177
177
|
this.unregisterCallback();
|
|
178
|
-
this.
|
|
178
|
+
await this.shutdownHost();
|
|
179
179
|
this.serverId = 0;
|
|
180
180
|
throw new Error("copilot_runtime_connection_open failed.");
|
|
181
181
|
}
|
|
@@ -184,7 +184,7 @@ class FfiRuntimeHost {
|
|
|
184
184
|
} finally {
|
|
185
185
|
this.starting = false;
|
|
186
186
|
if (this.disposed) {
|
|
187
|
-
|
|
187
|
+
await this.tryFinalizeCleanup();
|
|
188
188
|
}
|
|
189
189
|
}
|
|
190
190
|
}
|
|
@@ -223,6 +223,17 @@ class FfiRuntimeHost {
|
|
|
223
223
|
);
|
|
224
224
|
}
|
|
225
225
|
}
|
|
226
|
+
shutdownHost() {
|
|
227
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
228
|
+
this.lib.hostShutdown.async(this.serverId, (error, result) => {
|
|
229
|
+
if (error) {
|
|
230
|
+
rejectPromise(error);
|
|
231
|
+
} else {
|
|
232
|
+
resolvePromise(result);
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
}
|
|
226
237
|
unregisterCallback() {
|
|
227
238
|
if (this.outboundCallback === void 0) {
|
|
228
239
|
return true;
|
|
@@ -295,7 +306,7 @@ class FfiRuntimeHost {
|
|
|
295
306
|
}
|
|
296
307
|
if (this.serverId) {
|
|
297
308
|
try {
|
|
298
|
-
if (!this.
|
|
309
|
+
if (!await this.shutdownHost()) {
|
|
299
310
|
console.error(
|
|
300
311
|
`In-process FFI host shutdown did not recognize server ${this.serverId}.`
|
|
301
312
|
);
|