@github/copilot-sdk-win32-x64 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/copilot-sdk/client.d.ts +27 -1
- package/copilot-sdk/extension.js +470 -10
- package/copilot-sdk/generated/rpc.d.ts +997 -14
- package/copilot-sdk/generated/session-events.d.ts +81 -1
- package/copilot-sdk/host.d.ts +86 -0
- package/copilot-sdk/index.d.ts +3 -1
- package/copilot-sdk/index.js +471 -10
- package/copilot-sdk/types.d.ts +3 -0
- package/package.json +1 -1
- package/prebuilds/win32-x64/OneAuthInterop.dll +0 -0
- package/prebuilds/win32-x64/copilot-runtime.exe +0 -0
- package/prebuilds/win32-x64/runtime.node +2 -2
- package/ripgrep/bin/win32-x64/rg.exe +0 -0
- package/schemas/api.schema.json +1093 -8
- package/schemas/session-events.schema.json +305 -3
- package/tgrep/bin/win32-x64/tgrep.exe +0 -0
package/copilot-sdk/extension.js
CHANGED
|
@@ -4041,6 +4041,60 @@ import { dirname as dirname2, isAbsolute, join as join2, resolve as resolve2 } f
|
|
|
4041
4041
|
// src/sdk/nodejs/dist/generated/rpc.js
|
|
4042
4042
|
function createServerRpc(connection) {
|
|
4043
4043
|
return {
|
|
4044
|
+
/** @experimental */
|
|
4045
|
+
environments: {
|
|
4046
|
+
/**
|
|
4047
|
+
* Lists GitHub Mission Control environments visible to the authenticated identity. Does not require a running host and excludes host relay credentials.
|
|
4048
|
+
*
|
|
4049
|
+
* @param params Optional discovery filters supported by GitHub Mission Control.
|
|
4050
|
+
*
|
|
4051
|
+
* @returns Environments visible to the authenticated caller and matching the supplied filters.
|
|
4052
|
+
*/
|
|
4053
|
+
list: async (params) => connection.sendRequest("environments.list", params),
|
|
4054
|
+
/**
|
|
4055
|
+
* Gets safe discovery information for a GitHub Mission Control environment without requiring a running host.
|
|
4056
|
+
*
|
|
4057
|
+
* @param params Identify a Mission Control environment to retrieve.
|
|
4058
|
+
*
|
|
4059
|
+
* @returns Safe discovery information for the requested environment.
|
|
4060
|
+
*/
|
|
4061
|
+
get: async (params) => connection.sendRequest("environments.get", params),
|
|
4062
|
+
/**
|
|
4063
|
+
* Deletes a user-managed GitHub Mission Control environment. GitHub-managed environments cannot be deleted. Does not stop a running host, which may register again.
|
|
4064
|
+
*
|
|
4065
|
+
* @param params Identify a user-managed Mission Control environment to delete.
|
|
4066
|
+
*
|
|
4067
|
+
* @returns Acknowledgement that the requested environment was deleted.
|
|
4068
|
+
*/
|
|
4069
|
+
delete: async (params) => connection.sendRequest("environments.delete", params)
|
|
4070
|
+
},
|
|
4071
|
+
/** @experimental */
|
|
4072
|
+
host: {
|
|
4073
|
+
/**
|
|
4074
|
+
* Publishes an attached resident session for this listener's lifetime without copying it.
|
|
4075
|
+
*
|
|
4076
|
+
* @param params Publishes a resident session attached to the listener's owning connection.
|
|
4077
|
+
*
|
|
4078
|
+
* @returns The existing runtime identity and its resource on the listener.
|
|
4079
|
+
*/
|
|
4080
|
+
publishSession: async (params) => connection.sendRequest("host.publishSession", params),
|
|
4081
|
+
/**
|
|
4082
|
+
* Starts a connection-owned AHP host with explicit localServer and/or githubEnvironment transports as a supervised SDK participant.
|
|
4083
|
+
*
|
|
4084
|
+
* @param params Starts a supervised AHP host with at least one explicitly selected transport.
|
|
4085
|
+
*
|
|
4086
|
+
* @returns Listener readiness, returned only after binding and the supervised participant's SDK handshake.
|
|
4087
|
+
*/
|
|
4088
|
+
start: async (params) => connection.sendRequest("host.start", params),
|
|
4089
|
+
/**
|
|
4090
|
+
* Stops a listener owned by this SDK connection and joins its cleanup without deleting sessions.
|
|
4091
|
+
*
|
|
4092
|
+
* @param params Stops a connection-owned listener and joins its teardown.
|
|
4093
|
+
*
|
|
4094
|
+
* @returns Empty acknowledgement for a completed host lifecycle operation.
|
|
4095
|
+
*/
|
|
4096
|
+
dispose: async (params) => connection.sendRequest("host.dispose", params)
|
|
4097
|
+
},
|
|
4044
4098
|
/**
|
|
4045
4099
|
* Checks server responsiveness and returns protocol information.
|
|
4046
4100
|
*
|
|
@@ -4873,6 +4927,45 @@ function createServerRpc(connection) {
|
|
|
4873
4927
|
}
|
|
4874
4928
|
function createInternalServerRpc(connection) {
|
|
4875
4929
|
return {
|
|
4930
|
+
/** @experimental */
|
|
4931
|
+
host: {
|
|
4932
|
+
/**
|
|
4933
|
+
* Requests app-owned materialization over the owning SDK participant.
|
|
4934
|
+
*
|
|
4935
|
+
* @param params One application-owned session handoff, requested by the supervised hosting participant.
|
|
4936
|
+
*
|
|
4937
|
+
* @returns The resident session the application has materialized on its own connection.
|
|
4938
|
+
*/
|
|
4939
|
+
createSession: async (params) => connection.sendRequest("host.createSession", params),
|
|
4940
|
+
/**
|
|
4941
|
+
* Releases app ownership retention after AHP detaches.
|
|
4942
|
+
*
|
|
4943
|
+
* @param params Ends one participation, not the application's session lifetime.
|
|
4944
|
+
*
|
|
4945
|
+
* @returns Empty acknowledgement for a completed host lifecycle operation.
|
|
4946
|
+
*/
|
|
4947
|
+
releaseSession: async (params) => connection.sendRequest("host.releaseSession", params),
|
|
4948
|
+
/**
|
|
4949
|
+
* Resolves current authenticated credentials and remote-control policy only for the runtime-owned Mission Control hosting participant.
|
|
4950
|
+
*
|
|
4951
|
+
* @returns Private credentials delivered only to a runtime-owned Mission Control hosting participant.
|
|
4952
|
+
*/
|
|
4953
|
+
getEnvironmentCredentials: async () => connection.sendRequest("host.getEnvironmentCredentials", {}),
|
|
4954
|
+
/**
|
|
4955
|
+
* Returns listener settings only to the supervised hosting participant over its SDK connection.
|
|
4956
|
+
*
|
|
4957
|
+
* @returns Normalized listener settings delivered only to the supervised hosting participant.
|
|
4958
|
+
*/
|
|
4959
|
+
getConfiguration: async () => connection.sendRequest("host.getConfiguration", {}),
|
|
4960
|
+
/**
|
|
4961
|
+
* Reports a supervised hosting participant's bound AHP endpoint after its SDK handshake.
|
|
4962
|
+
*
|
|
4963
|
+
* @param params Readiness reported by the supervised hosting participant on its own SDK connection.
|
|
4964
|
+
*
|
|
4965
|
+
* @returns Empty acknowledgement for a completed host lifecycle operation.
|
|
4966
|
+
*/
|
|
4967
|
+
ready: async (params) => connection.sendRequest("host.ready", params)
|
|
4968
|
+
},
|
|
4876
4969
|
/**
|
|
4877
4970
|
* Performs the SDK server connection handshake and validates the optional connection token. Marked internal because this is JSON-RPC transport plumbing invoked automatically by an SDK client's own `connect()` wrapper, not a user-facing method. Stays internal as long as the SDK client owns the handshake; would only become public if the SDK ever exposed the raw schema surface to consumers without a connection wrapper.
|
|
4878
4971
|
*
|
|
@@ -4994,7 +5087,15 @@ function createSessionRpc(connection, sessionId) {
|
|
|
4994
5087
|
*
|
|
4995
5088
|
* @returns Result of attempting to disable sandboxing for the current session.
|
|
4996
5089
|
*/
|
|
4997
|
-
disableForSession: async (params) => connection.sendRequest("session.sandbox.disableForSession", { sessionId, ...params })
|
|
5090
|
+
disableForSession: async (params) => connection.sendRequest("session.sandbox.disableForSession", { sessionId, ...params }),
|
|
5091
|
+
/**
|
|
5092
|
+
* Adds the path offered by a pending sandbox escalation permission request's sandboxPathGrant to the session's sandbox policy and approves the request, so the blocked operation re-runs inside the sandbox rather than outside it. The request is rejected unless the exact request is still pending, carries a sandboxPathGrant, and the grant still takes effect under the current managed policy. Does not persist the path; hosts that store sandbox settings save it themselves.
|
|
5093
|
+
*
|
|
5094
|
+
* @param params Request to accept the sandbox path grant offered on an active sandbox escalation permission prompt.
|
|
5095
|
+
*
|
|
5096
|
+
* @returns Result of accepting a sandbox path grant.
|
|
5097
|
+
*/
|
|
5098
|
+
grantPathForRequest: async (params) => connection.sendRequest("session.sandbox.grantPathForRequest", { sessionId, ...params })
|
|
4998
5099
|
},
|
|
4999
5100
|
/**
|
|
5000
5101
|
* Aborts the current agent turn.
|
|
@@ -6202,7 +6303,15 @@ function createSessionRpc(connection, sessionId) {
|
|
|
6202
6303
|
*
|
|
6203
6304
|
* @returns The selectable model entries and selection ids synthesized for the synchronized BYOK models.
|
|
6204
6305
|
*/
|
|
6205
|
-
sync: async (params) => connection.sendRequest("session.provider.sync", { sessionId, ...params })
|
|
6306
|
+
sync: async (params) => connection.sendRequest("session.provider.sync", { sessionId, ...params }),
|
|
6307
|
+
/**
|
|
6308
|
+
* Withdraws named host-managed models from the session's BYOK registry, leaving every other entry untouched. The scoped counterpart to `provider.sync`: a snapshot can only describe entries the caller knows about, so using it to remove one model silently withdraws rows registered by another source, such as a plugin calling `provider.add` at runtime. Naming what to remove leaves unrelated entries alone. Selection ids that are not registered are ignored, so withdrawal is idempotent. A provider is removed only when one of the withdrawn models was the last entry referencing it; a provider that simply has no models, which is the normal state while its rows are supplied by catalog discovery, is left in place.
|
|
6309
|
+
*
|
|
6310
|
+
* @param params Host-managed model selection ids to withdraw from the session's BYOK registry.
|
|
6311
|
+
*
|
|
6312
|
+
* @returns What the withdrawal actually removed from the registry.
|
|
6313
|
+
*/
|
|
6314
|
+
withdraw: async (params) => connection.sendRequest("session.provider.withdraw", { sessionId, ...params })
|
|
6206
6315
|
},
|
|
6207
6316
|
/** @experimental */
|
|
6208
6317
|
options: {
|
|
@@ -7455,6 +7564,11 @@ function registerClientSessionApiHandlers(connection, getHandlers) {
|
|
|
7455
7564
|
});
|
|
7456
7565
|
}
|
|
7457
7566
|
function registerClientGlobalApiHandlers(connection, handlers) {
|
|
7567
|
+
connection.onNotification("host.exited", async (params) => {
|
|
7568
|
+
const handler = handlers.host;
|
|
7569
|
+
if (!handler) return;
|
|
7570
|
+
await handler.exited(params);
|
|
7571
|
+
});
|
|
7458
7572
|
connection.onRequest("extensionLaunchProvider.resolve", async (params) => {
|
|
7459
7573
|
const handler = handlers.extensionLaunchProvider;
|
|
7460
7574
|
if (!handler) throw new Error("No extensionLaunchProvider client-global handler registered");
|
|
@@ -10568,6 +10682,42 @@ function assertWorkflowStepResult(value, key) {
|
|
|
10568
10682
|
});
|
|
10569
10683
|
}
|
|
10570
10684
|
|
|
10685
|
+
// src/sdk/nodejs/dist/host.js
|
|
10686
|
+
var AhpHost = class {
|
|
10687
|
+
/** @internal */
|
|
10688
|
+
constructor(info, disposeHost, publishHostSession) {
|
|
10689
|
+
this.disposeHost = disposeHost;
|
|
10690
|
+
this.publishHostSession = publishHostSession;
|
|
10691
|
+
this.hostId = info.hostId;
|
|
10692
|
+
this.url = info.url;
|
|
10693
|
+
this.environmentId = info.environmentId;
|
|
10694
|
+
this.token = info.token;
|
|
10695
|
+
this.pid = info.pid;
|
|
10696
|
+
}
|
|
10697
|
+
disposeHost;
|
|
10698
|
+
publishHostSession;
|
|
10699
|
+
hostId;
|
|
10700
|
+
/** Local WebSocket URL. Absent when only the GitHub environment is enabled. */
|
|
10701
|
+
url;
|
|
10702
|
+
/** Mission Control environment ID, when GitHub hosting is enabled. */
|
|
10703
|
+
environmentId;
|
|
10704
|
+
/** Connection token, when required by the listener. Treat this value as a secret. */
|
|
10705
|
+
token;
|
|
10706
|
+
/** Legacy separate host process ID. Absent for in-process listeners; use dispose() to stop. */
|
|
10707
|
+
pid;
|
|
10708
|
+
/** Ask the runtime to stop the listener and await its cleanup, on every call. */
|
|
10709
|
+
dispose() {
|
|
10710
|
+
return this.disposeHost();
|
|
10711
|
+
}
|
|
10712
|
+
/** Publish an existing session attached to this client, until this listener stops. */
|
|
10713
|
+
publishSession(sessionId) {
|
|
10714
|
+
return this.publishHostSession(sessionId);
|
|
10715
|
+
}
|
|
10716
|
+
async [Symbol.asyncDispose]() {
|
|
10717
|
+
await this.dispose();
|
|
10718
|
+
}
|
|
10719
|
+
};
|
|
10720
|
+
|
|
10571
10721
|
// src/sdk/nodejs/dist/runtimeArtifacts.js
|
|
10572
10722
|
import {
|
|
10573
10723
|
chmodSync,
|
|
@@ -10914,6 +11064,57 @@ var ToolSet = class {
|
|
|
10914
11064
|
// src/sdk/nodejs/dist/client.js
|
|
10915
11065
|
var MIN_PROTOCOL_VERSION = 3;
|
|
10916
11066
|
var RUNTIME_SHUTDOWN_TIMEOUT_MS = 1e4;
|
|
11067
|
+
function createMessageConnection(reader, writer) {
|
|
11068
|
+
let dispatch;
|
|
11069
|
+
let finishDrain;
|
|
11070
|
+
let drained;
|
|
11071
|
+
let disposed = false;
|
|
11072
|
+
const barrier = { jsonrpc: "2.0", method: "$/sdkDrain" };
|
|
11073
|
+
const connection = (0, import_node3.createMessageConnection)(
|
|
11074
|
+
{
|
|
11075
|
+
onError: reader.onError,
|
|
11076
|
+
onClose: reader.onClose,
|
|
11077
|
+
onPartialMessage: reader.onPartialMessage,
|
|
11078
|
+
listen: (callback) => {
|
|
11079
|
+
dispatch = callback;
|
|
11080
|
+
return reader.listen(callback);
|
|
11081
|
+
},
|
|
11082
|
+
dispose: () => reader.dispose()
|
|
11083
|
+
},
|
|
11084
|
+
writer,
|
|
11085
|
+
void 0,
|
|
11086
|
+
{
|
|
11087
|
+
messageStrategy: {
|
|
11088
|
+
handleMessage: (message, next) => {
|
|
11089
|
+
if (message === barrier) {
|
|
11090
|
+
finishDrain?.();
|
|
11091
|
+
} else {
|
|
11092
|
+
next(message);
|
|
11093
|
+
}
|
|
11094
|
+
}
|
|
11095
|
+
}
|
|
11096
|
+
}
|
|
11097
|
+
);
|
|
11098
|
+
connection.onDispose(() => {
|
|
11099
|
+
disposed = true;
|
|
11100
|
+
finishDrain?.();
|
|
11101
|
+
});
|
|
11102
|
+
return Object.assign(connection, {
|
|
11103
|
+
drain: () => {
|
|
11104
|
+
if (!drained) {
|
|
11105
|
+
drained = new Promise((resolve22) => {
|
|
11106
|
+
finishDrain = resolve22;
|
|
11107
|
+
if (disposed || !dispatch) {
|
|
11108
|
+
resolve22();
|
|
11109
|
+
} else {
|
|
11110
|
+
dispatch(barrier);
|
|
11111
|
+
}
|
|
11112
|
+
});
|
|
11113
|
+
}
|
|
11114
|
+
return drained;
|
|
11115
|
+
}
|
|
11116
|
+
});
|
|
11117
|
+
}
|
|
10917
11118
|
async function withTimeout(promise, timeoutMs, message) {
|
|
10918
11119
|
let timeout;
|
|
10919
11120
|
try {
|
|
@@ -11100,6 +11301,9 @@ var CopilotClient = class _CopilotClient {
|
|
|
11100
11301
|
/** Shared in-flight start; concurrent callers await it instead of spawning another CLI. */
|
|
11101
11302
|
startPromise = null;
|
|
11102
11303
|
sessions = /* @__PURE__ */ new Map();
|
|
11304
|
+
hostExitCallbacks = /* @__PURE__ */ new Map();
|
|
11305
|
+
hostSessionFactories = /* @__PURE__ */ new Map();
|
|
11306
|
+
hostHandoffs = /* @__PURE__ */ new Map();
|
|
11103
11307
|
stderrBuffer = "";
|
|
11104
11308
|
// Captures CLI stderr for error messages
|
|
11105
11309
|
/** Resolved connection mode chosen in the constructor. */
|
|
@@ -11391,6 +11595,11 @@ var CopilotClient = class _CopilotClient {
|
|
|
11391
11595
|
}
|
|
11392
11596
|
setupClientGlobalHandlers() {
|
|
11393
11597
|
const handlers = {};
|
|
11598
|
+
handlers.host = {
|
|
11599
|
+
exited: async (exit) => {
|
|
11600
|
+
this.handleHostExit(exit);
|
|
11601
|
+
}
|
|
11602
|
+
};
|
|
11394
11603
|
handlers.extensionLaunchProvider = this.extensionLaunchProvider;
|
|
11395
11604
|
if (this.requestHandler) {
|
|
11396
11605
|
this.requestAdapter = createCopilotRequestAdapter(this.requestHandler, () => {
|
|
@@ -11544,7 +11753,8 @@ var CopilotClient = class _CopilotClient {
|
|
|
11544
11753
|
* 1. Closes all active sessions (releases in-memory resources)
|
|
11545
11754
|
* 2. Requests runtime shutdown for SDK-owned CLI processes
|
|
11546
11755
|
* 3. Closes the JSON-RPC connection
|
|
11547
|
-
* 4.
|
|
11756
|
+
* 4. Signals EOF to an owned stdio process and waits for host cleanup, then
|
|
11757
|
+
* terminates the process if it does not exit within the shutdown timeout
|
|
11548
11758
|
*
|
|
11549
11759
|
* Note: session data on disk is preserved, so sessions can be resumed later.
|
|
11550
11760
|
* To permanently remove session data before stopping, call
|
|
@@ -11662,7 +11872,23 @@ var CopilotClient = class _CopilotClient {
|
|
|
11662
11872
|
}
|
|
11663
11873
|
if (this.cliProcess && !this.isExternalServer) {
|
|
11664
11874
|
const child = this.cliProcess;
|
|
11665
|
-
this.
|
|
11875
|
+
if (this.connectionConfig.kind === "stdio" && child.stdin && child.exitCode == null && child.signalCode == null) {
|
|
11876
|
+
const gracefulExitStart = Date.now();
|
|
11877
|
+
try {
|
|
11878
|
+
child.stdin.end();
|
|
11879
|
+
const exited = await waitForChildExit(child, RUNTIME_SHUTDOWN_TIMEOUT_MS);
|
|
11880
|
+
this.logDebugTiming(
|
|
11881
|
+
exited ? "CopilotClient.stop graceful stdio exit complete" : "CopilotClient.stop graceful stdio exit timed out; terminating child",
|
|
11882
|
+
gracefulExitStart
|
|
11883
|
+
);
|
|
11884
|
+
} catch (error) {
|
|
11885
|
+
errors.push(
|
|
11886
|
+
new Error(
|
|
11887
|
+
`Failed to close CLI stdin: ${error instanceof Error ? error.message : String(error)}`
|
|
11888
|
+
)
|
|
11889
|
+
);
|
|
11890
|
+
}
|
|
11891
|
+
}
|
|
11666
11892
|
try {
|
|
11667
11893
|
if (child.exitCode == null && child.signalCode == null) {
|
|
11668
11894
|
child.kill();
|
|
@@ -11680,6 +11906,10 @@ var CopilotClient = class _CopilotClient {
|
|
|
11680
11906
|
`Failed to kill CLI process: ${error instanceof Error ? error.message : String(error)}`
|
|
11681
11907
|
)
|
|
11682
11908
|
);
|
|
11909
|
+
} finally {
|
|
11910
|
+
if (this.cliProcess === child) {
|
|
11911
|
+
this.cliProcess = null;
|
|
11912
|
+
}
|
|
11683
11913
|
}
|
|
11684
11914
|
}
|
|
11685
11915
|
if (this.ffiHost) {
|
|
@@ -11703,6 +11933,7 @@ var CopilotClient = class _CopilotClient {
|
|
|
11703
11933
|
this.runtimePort = null;
|
|
11704
11934
|
this.stderrBuffer = "";
|
|
11705
11935
|
this.processExitPromise = null;
|
|
11936
|
+
this.disconnectHosts();
|
|
11706
11937
|
return errors;
|
|
11707
11938
|
}
|
|
11708
11939
|
/**
|
|
@@ -11747,6 +11978,7 @@ var CopilotClient = class _CopilotClient {
|
|
|
11747
11978
|
*/
|
|
11748
11979
|
async forceStop() {
|
|
11749
11980
|
this.forceStopping = true;
|
|
11981
|
+
this.disconnectHosts();
|
|
11750
11982
|
for (const session of this.sessions.values()) {
|
|
11751
11983
|
session._markDisconnected();
|
|
11752
11984
|
}
|
|
@@ -12046,6 +12278,7 @@ var CopilotClient = class _CopilotClient {
|
|
|
12046
12278
|
const response = await this.connection.sendRequest("session.create", {
|
|
12047
12279
|
...await getTraceContext(this.onGetTraceContext),
|
|
12048
12280
|
model: config.model,
|
|
12281
|
+
allowedModels: config.allowedModels,
|
|
12049
12282
|
sessionId: localSessionId,
|
|
12050
12283
|
clientName: config.clientName,
|
|
12051
12284
|
reasoningEffort: config.reasoningEffort,
|
|
@@ -12176,6 +12409,11 @@ var CopilotClient = class _CopilotClient {
|
|
|
12176
12409
|
}
|
|
12177
12410
|
throw e;
|
|
12178
12411
|
}
|
|
12412
|
+
for (const entry of this.hostHandoffs.values()) {
|
|
12413
|
+
if (entry.requestedSessionId === session.sessionId) {
|
|
12414
|
+
entry.configs.set(session, config);
|
|
12415
|
+
}
|
|
12416
|
+
}
|
|
12179
12417
|
return session;
|
|
12180
12418
|
}
|
|
12181
12419
|
/**
|
|
@@ -12282,6 +12520,7 @@ var CopilotClient = class _CopilotClient {
|
|
|
12282
12520
|
}
|
|
12283
12521
|
try {
|
|
12284
12522
|
const response = await this.connection.sendRequest("session.resume", {
|
|
12523
|
+
allowedModels: config.allowedModels,
|
|
12285
12524
|
...await getTraceContext(this.onGetTraceContext),
|
|
12286
12525
|
sessionId,
|
|
12287
12526
|
clientName: config.clientName,
|
|
@@ -12410,8 +12649,205 @@ var CopilotClient = class _CopilotClient {
|
|
|
12410
12649
|
}
|
|
12411
12650
|
throw e;
|
|
12412
12651
|
}
|
|
12652
|
+
for (const entry of this.hostHandoffs.values()) {
|
|
12653
|
+
if (entry.requestedSessionId === session.sessionId) {
|
|
12654
|
+
entry.configs.set(session, { ...config, sessionId });
|
|
12655
|
+
}
|
|
12656
|
+
}
|
|
12413
12657
|
return session;
|
|
12414
12658
|
}
|
|
12659
|
+
/**
|
|
12660
|
+
* Start a complete AHP listener hosted in-process by this runtime.
|
|
12661
|
+
*
|
|
12662
|
+
* This connection owns the host. Disposing it or disconnecting the client
|
|
12663
|
+
* stops the listener without deleting sessions. The host uses a separate
|
|
12664
|
+
* SDK connection to the same runtime, not another runtime process.
|
|
12665
|
+
* Only one AHP host may own the catalog in an effective Copilot home
|
|
12666
|
+
* at a time. Other SDK clients and sessions remain usable in that home.
|
|
12667
|
+
* Select localServer, githubEnvironment, or both. An empty localServer
|
|
12668
|
+
* selects 127.0.0.1 on an available port. `onExit` runs at most once; owner disconnection cannot
|
|
12669
|
+
* acknowledge listener cleanup over the disconnected transport.
|
|
12670
|
+
*
|
|
12671
|
+
* @experimental
|
|
12672
|
+
*/
|
|
12673
|
+
async startAhpHost(options) {
|
|
12674
|
+
if (!options?.localServer && !options?.githubEnvironment) {
|
|
12675
|
+
throw new Error("At least one of localServer or githubEnvironment is required");
|
|
12676
|
+
}
|
|
12677
|
+
if (this.state !== "connected") {
|
|
12678
|
+
await this.start();
|
|
12679
|
+
}
|
|
12680
|
+
const rpc = this.rpc;
|
|
12681
|
+
const hostId = randomUUID();
|
|
12682
|
+
const {
|
|
12683
|
+
localServer,
|
|
12684
|
+
githubEnvironment,
|
|
12685
|
+
onExit,
|
|
12686
|
+
createSession,
|
|
12687
|
+
resumeSession,
|
|
12688
|
+
onSessionReleased
|
|
12689
|
+
} = options;
|
|
12690
|
+
if (createSession || resumeSession) {
|
|
12691
|
+
this.hostSessionFactories.set(hostId, {
|
|
12692
|
+
createSession,
|
|
12693
|
+
resumeSession,
|
|
12694
|
+
onSessionReleased
|
|
12695
|
+
});
|
|
12696
|
+
}
|
|
12697
|
+
if (onExit) {
|
|
12698
|
+
this.hostExitCallbacks.set(hostId, onExit);
|
|
12699
|
+
}
|
|
12700
|
+
try {
|
|
12701
|
+
const info = await rpc.host.start({
|
|
12702
|
+
hostId,
|
|
12703
|
+
localServer,
|
|
12704
|
+
githubEnvironment,
|
|
12705
|
+
sessionFactory: createSession ? true : void 0,
|
|
12706
|
+
resumeFactory: resumeSession ? true : void 0
|
|
12707
|
+
});
|
|
12708
|
+
return new AhpHost(
|
|
12709
|
+
info,
|
|
12710
|
+
async () => {
|
|
12711
|
+
await rpc.host.dispose({ hostId });
|
|
12712
|
+
},
|
|
12713
|
+
(sessionId) => rpc.host.publishSession({ hostId, sessionId })
|
|
12714
|
+
);
|
|
12715
|
+
} catch (error) {
|
|
12716
|
+
this.hostExitCallbacks.delete(hostId);
|
|
12717
|
+
this.releaseHostSessions(hostId);
|
|
12718
|
+
throw error;
|
|
12719
|
+
}
|
|
12720
|
+
}
|
|
12721
|
+
handleHostExit(exit) {
|
|
12722
|
+
this.releaseHostSessions(exit.hostId);
|
|
12723
|
+
const onExit = this.hostExitCallbacks.get(exit.hostId);
|
|
12724
|
+
if (onExit) {
|
|
12725
|
+
this.hostExitCallbacks.delete(exit.hostId);
|
|
12726
|
+
const reportError = (error) => {
|
|
12727
|
+
console.error("AHP host exit callback failed", { hostId: exit.hostId, error });
|
|
12728
|
+
};
|
|
12729
|
+
try {
|
|
12730
|
+
void Promise.resolve(onExit(exit)).catch(reportError);
|
|
12731
|
+
} catch (error) {
|
|
12732
|
+
reportError(error);
|
|
12733
|
+
}
|
|
12734
|
+
}
|
|
12735
|
+
}
|
|
12736
|
+
disconnectHosts() {
|
|
12737
|
+
for (const hostId of /* @__PURE__ */ new Set([
|
|
12738
|
+
...this.hostExitCallbacks.keys(),
|
|
12739
|
+
...this.hostSessionFactories.keys()
|
|
12740
|
+
])) {
|
|
12741
|
+
this.handleHostExit({
|
|
12742
|
+
hostId,
|
|
12743
|
+
reason: "ownerDisconnected",
|
|
12744
|
+
error: "Owner connection closed; runtime cleanup cannot be acknowledged on this connection."
|
|
12745
|
+
});
|
|
12746
|
+
}
|
|
12747
|
+
}
|
|
12748
|
+
releaseHostSessions(hostId) {
|
|
12749
|
+
this.hostSessionFactories.delete(hostId);
|
|
12750
|
+
for (const [handoffId, entry] of this.hostHandoffs) {
|
|
12751
|
+
if (entry.hostId === hostId) this.releaseHostSession(handoffId);
|
|
12752
|
+
}
|
|
12753
|
+
}
|
|
12754
|
+
releaseHostSession(handoffId) {
|
|
12755
|
+
const entry = this.hostHandoffs.get(handoffId);
|
|
12756
|
+
if (!entry) return;
|
|
12757
|
+
this.hostHandoffs.delete(handoffId);
|
|
12758
|
+
entry.released = true;
|
|
12759
|
+
entry.configs.clear();
|
|
12760
|
+
entry.controller.abort();
|
|
12761
|
+
if (entry.session) this.notifyHostSessionReleased(entry);
|
|
12762
|
+
}
|
|
12763
|
+
notifyHostSessionReleased(entry) {
|
|
12764
|
+
const { session, onReleased } = entry;
|
|
12765
|
+
entry.session = void 0;
|
|
12766
|
+
if (session && onReleased) {
|
|
12767
|
+
try {
|
|
12768
|
+
void Promise.resolve(onReleased(session)).catch(() => {
|
|
12769
|
+
console.error("AHP session release callback failed");
|
|
12770
|
+
});
|
|
12771
|
+
} catch {
|
|
12772
|
+
console.error("AHP session release callback failed");
|
|
12773
|
+
}
|
|
12774
|
+
}
|
|
12775
|
+
}
|
|
12776
|
+
async createHostSession(params) {
|
|
12777
|
+
const options = this.hostSessionFactories.get(params.hostId);
|
|
12778
|
+
if (!(params.resume ? options?.resumeSession : options?.createSession) || this.hostHandoffs.has(params.handoffId)) {
|
|
12779
|
+
throw new Error("AHP session factory is unavailable or handoff already exists");
|
|
12780
|
+
}
|
|
12781
|
+
const entry = {
|
|
12782
|
+
hostId: params.hostId,
|
|
12783
|
+
requestedSessionId: params.config.sessionId,
|
|
12784
|
+
controller: new AbortController(),
|
|
12785
|
+
configs: /* @__PURE__ */ new Map(),
|
|
12786
|
+
released: false,
|
|
12787
|
+
onReleased: options.onSessionReleased
|
|
12788
|
+
};
|
|
12789
|
+
this.hostHandoffs.set(params.handoffId, entry);
|
|
12790
|
+
const { configDir, ...config } = params.config;
|
|
12791
|
+
if (configDir !== void 0) config.configDirectory = configDir;
|
|
12792
|
+
const expected = structuredClone(config);
|
|
12793
|
+
const cancelled = new Promise((_resolve, reject) => {
|
|
12794
|
+
entry.controller.signal.addEventListener(
|
|
12795
|
+
"abort",
|
|
12796
|
+
() => {
|
|
12797
|
+
reject(new Error("AHP session handoff ended"));
|
|
12798
|
+
},
|
|
12799
|
+
{ once: true }
|
|
12800
|
+
);
|
|
12801
|
+
});
|
|
12802
|
+
const materialized = Promise.resolve().then(() => {
|
|
12803
|
+
if (params.resume) {
|
|
12804
|
+
const { sessionId, ...resumeConfig } = config;
|
|
12805
|
+
if (!sessionId) throw new Error("AHP resume requires sessionId");
|
|
12806
|
+
return options.resumeSession({
|
|
12807
|
+
sessionId,
|
|
12808
|
+
config: resumeConfig,
|
|
12809
|
+
signal: entry.controller.signal
|
|
12810
|
+
});
|
|
12811
|
+
}
|
|
12812
|
+
return options.createSession({
|
|
12813
|
+
config,
|
|
12814
|
+
signal: entry.controller.signal
|
|
12815
|
+
});
|
|
12816
|
+
}).then((session) => {
|
|
12817
|
+
entry.session = session;
|
|
12818
|
+
const actual = entry.configs.get(session);
|
|
12819
|
+
entry.configs.clear();
|
|
12820
|
+
if (entry.released) {
|
|
12821
|
+
this.notifyHostSessionReleased(entry);
|
|
12822
|
+
throw new Error("AHP session handoff ended");
|
|
12823
|
+
}
|
|
12824
|
+
if (this.sessions.get(session.sessionId) !== session || session.sessionId !== expected.sessionId) {
|
|
12825
|
+
throw new Error(
|
|
12826
|
+
"AHP callback must return the requested session from this client"
|
|
12827
|
+
);
|
|
12828
|
+
}
|
|
12829
|
+
const contains = (value, required) => {
|
|
12830
|
+
if (required && typeof required === "object" && !Array.isArray(required)) {
|
|
12831
|
+
return !!value && typeof value === "object" && Object.entries(required).every(
|
|
12832
|
+
([key, child]) => contains(value[key], child)
|
|
12833
|
+
);
|
|
12834
|
+
}
|
|
12835
|
+
return JSON.stringify(value) === JSON.stringify(required);
|
|
12836
|
+
};
|
|
12837
|
+
if ((!params.resume || actual !== void 0) && !contains(actual, expected)) {
|
|
12838
|
+
throw new Error(
|
|
12839
|
+
"AHP callback must preserve the supplied session configuration"
|
|
12840
|
+
);
|
|
12841
|
+
}
|
|
12842
|
+
return { sessionId: session.sessionId };
|
|
12843
|
+
});
|
|
12844
|
+
try {
|
|
12845
|
+
return await Promise.race([materialized, cancelled]);
|
|
12846
|
+
} catch (error) {
|
|
12847
|
+
this.releaseHostSession(params.handoffId);
|
|
12848
|
+
throw error;
|
|
12849
|
+
}
|
|
12850
|
+
}
|
|
12415
12851
|
/**
|
|
12416
12852
|
* Sends a ping request to the server to verify connectivity.
|
|
12417
12853
|
*
|
|
@@ -12951,8 +13387,13 @@ stderr: ${stderrOutput}`
|
|
|
12951
13387
|
case "inprocess":
|
|
12952
13388
|
return this.connectViaFfi();
|
|
12953
13389
|
case "tcp":
|
|
12954
|
-
case "uri":
|
|
12955
13390
|
return this.connectViaTcp();
|
|
13391
|
+
case "uri": {
|
|
13392
|
+
const { host, port } = this.parseCliUrl(this.connectionConfig.url);
|
|
13393
|
+
this.actualHost = host;
|
|
13394
|
+
this.runtimePort = port;
|
|
13395
|
+
return this.connectViaTcp();
|
|
13396
|
+
}
|
|
12956
13397
|
}
|
|
12957
13398
|
}
|
|
12958
13399
|
/** Starts the in-process FFI runtime with SDK-managed typed options. */
|
|
@@ -13011,7 +13452,7 @@ stderr: ${stderrOutput}`
|
|
|
13011
13452
|
throw new Error("In-process FFI runtime host not started");
|
|
13012
13453
|
}
|
|
13013
13454
|
this.messageWriter = new TeardownResilientStreamMessageWriter(this.ffiHost.sendStream);
|
|
13014
|
-
this.connection =
|
|
13455
|
+
this.connection = createMessageConnection(
|
|
13015
13456
|
new import_node3.StreamMessageReader(this.ffiHost.receiveStream),
|
|
13016
13457
|
this.messageWriter
|
|
13017
13458
|
);
|
|
@@ -13065,7 +13506,7 @@ stderr: ${stderrOutput}` : ""}`
|
|
|
13065
13506
|
}
|
|
13066
13507
|
});
|
|
13067
13508
|
this.messageWriter = new TeardownResilientStreamMessageWriter(this.cliProcess.stdin);
|
|
13068
|
-
this.connection =
|
|
13509
|
+
this.connection = createMessageConnection(
|
|
13069
13510
|
new import_node3.StreamMessageReader(this.cliProcess.stdout),
|
|
13070
13511
|
this.messageWriter
|
|
13071
13512
|
);
|
|
@@ -13080,7 +13521,7 @@ stderr: ${stderrOutput}` : ""}`
|
|
|
13080
13521
|
throw new Error("CLI child process was unexpectedly started in parent process mode");
|
|
13081
13522
|
}
|
|
13082
13523
|
this.messageWriter = new TeardownResilientStreamMessageWriter(process.stdout);
|
|
13083
|
-
this.connection =
|
|
13524
|
+
this.connection = createMessageConnection(
|
|
13084
13525
|
new import_node3.StreamMessageReader(process.stdin),
|
|
13085
13526
|
this.messageWriter
|
|
13086
13527
|
);
|
|
@@ -13103,7 +13544,7 @@ stderr: ${stderrOutput}` : ""}`
|
|
|
13103
13544
|
this.socket.connect(this.runtimePort, this.actualHost, () => {
|
|
13104
13545
|
clearTimeout(connectionTimeout);
|
|
13105
13546
|
this.messageWriter = new TeardownResilientStreamMessageWriter(this.socket);
|
|
13106
|
-
this.connection =
|
|
13547
|
+
this.connection = createMessageConnection(
|
|
13107
13548
|
new import_node3.StreamMessageReader(this.socket),
|
|
13108
13549
|
this.messageWriter
|
|
13109
13550
|
);
|
|
@@ -13160,6 +13601,18 @@ stderr: ${stderrOutput}` : ""}`
|
|
|
13160
13601
|
(params, token) => confirmation.confirm(params, token)
|
|
13161
13602
|
);
|
|
13162
13603
|
}
|
|
13604
|
+
this.connection.onRequest(
|
|
13605
|
+
"host.materializeSession",
|
|
13606
|
+
(params) => this.createHostSession(params)
|
|
13607
|
+
);
|
|
13608
|
+
this.connection.onNotification(
|
|
13609
|
+
"host.sessionReleased",
|
|
13610
|
+
(params) => {
|
|
13611
|
+
if (this.hostHandoffs.get(params.handoffId)?.hostId === params.hostId) {
|
|
13612
|
+
this.releaseHostSession(params.handoffId);
|
|
13613
|
+
}
|
|
13614
|
+
}
|
|
13615
|
+
);
|
|
13163
13616
|
this.connection.onRequest(
|
|
13164
13617
|
"hooks.invoke",
|
|
13165
13618
|
async (params) => {
|
|
@@ -13167,20 +13620,27 @@ stderr: ${stderrOutput}` : ""}`
|
|
|
13167
13620
|
}
|
|
13168
13621
|
);
|
|
13169
13622
|
const connection = this.connection;
|
|
13623
|
+
const messageWriter = this.messageWriter;
|
|
13170
13624
|
const markDisconnected = () => {
|
|
13171
13625
|
if (this.connection !== connection) {
|
|
13626
|
+
connection.dispose();
|
|
13172
13627
|
return;
|
|
13173
13628
|
}
|
|
13174
13629
|
this.connectionClosed = true;
|
|
13175
13630
|
this.state = "disconnected";
|
|
13631
|
+
this.disconnectHosts();
|
|
13176
13632
|
for (const session of this.sessions.values()) {
|
|
13177
13633
|
session._markDisconnected();
|
|
13178
13634
|
}
|
|
13179
13635
|
this.sessions.clear();
|
|
13180
13636
|
this.githubTokenProviders.clear();
|
|
13181
13637
|
this.requestAdapter?.cancelPending();
|
|
13638
|
+
connection.dispose();
|
|
13182
13639
|
};
|
|
13183
|
-
this.connection.onClose(
|
|
13640
|
+
this.connection.onClose(() => {
|
|
13641
|
+
if (messageWriter) messageWriter.suppressWriteErrors = true;
|
|
13642
|
+
void connection.drain().then(markDisconnected);
|
|
13643
|
+
});
|
|
13184
13644
|
this.connection.onError(() => {
|
|
13185
13645
|
if (this.connection === connection) {
|
|
13186
13646
|
this.state = "disconnected";
|