@github/copilot-sdk 1.0.0-beta.4 → 1.0.0-beta.6
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 +35 -33
- package/dist/cjs/client.js +222 -166
- package/dist/cjs/extension.js +2 -2
- package/dist/cjs/generated/rpc.js +1150 -13
- package/dist/cjs/index.js +9 -6
- package/dist/cjs/session.js +22 -61
- package/dist/cjs/sessionFsProvider.js +34 -0
- package/dist/cjs/types.js +30 -0
- package/dist/client.d.ts +45 -24
- package/dist/client.js +222 -166
- package/dist/extension.js +2 -2
- package/dist/generated/rpc.d.ts +8486 -1065
- package/dist/generated/rpc.js +1150 -13
- package/dist/generated/session-events.d.ts +1132 -110
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -0
- package/dist/session.d.ts +5 -212
- package/dist/session.js +22 -61
- package/dist/sessionFsProvider.d.ts +28 -1
- package/dist/sessionFsProvider.js +34 -0
- package/dist/types.d.ts +301 -115
- package/dist/types.js +29 -0
- package/docs/examples.md +3 -3
- package/package.json +3 -3
package/dist/client.js
CHANGED
|
@@ -22,11 +22,6 @@ import { CopilotSession, NO_RESULT_PERMISSION_V2_ERROR } from "./session.js";
|
|
|
22
22
|
import { createSessionFsAdapter } from "./sessionFsProvider.js";
|
|
23
23
|
import { getTraceContext } from "./telemetry.js";
|
|
24
24
|
import { defaultJoinSessionPermissionHandler } from "./types.js";
|
|
25
|
-
function toWireProviderConfig(provider) {
|
|
26
|
-
const { maxInputTokens, ...rest } = provider;
|
|
27
|
-
if (maxInputTokens === void 0) return rest;
|
|
28
|
-
return { ...rest, maxPromptTokens: maxInputTokens };
|
|
29
|
-
}
|
|
30
25
|
const MIN_PROTOCOL_VERSION = 2;
|
|
31
26
|
function isZodSchema(value) {
|
|
32
27
|
return value != null && typeof value === "object" && "toJSONSchema" in value && typeof value.toJSONSchema === "function";
|
|
@@ -38,6 +33,26 @@ function toJsonSchema(parameters) {
|
|
|
38
33
|
}
|
|
39
34
|
return parameters;
|
|
40
35
|
}
|
|
36
|
+
function toWireMcpServers(mcpServers) {
|
|
37
|
+
if (!mcpServers) return void 0;
|
|
38
|
+
return Object.fromEntries(
|
|
39
|
+
Object.entries(mcpServers).map(([name, server]) => {
|
|
40
|
+
if ("workingDirectory" in server) {
|
|
41
|
+
const { workingDirectory, ...rest } = server;
|
|
42
|
+
return [name, { ...rest, cwd: workingDirectory }];
|
|
43
|
+
}
|
|
44
|
+
return [name, server];
|
|
45
|
+
})
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
function toWireCustomAgents(agents) {
|
|
49
|
+
if (!agents) return void 0;
|
|
50
|
+
return agents.map((agent) => {
|
|
51
|
+
if (!agent.mcpServers) return agent;
|
|
52
|
+
const { mcpServers, ...rest } = agent;
|
|
53
|
+
return { ...rest, mcpServers: toWireMcpServers(mcpServers) };
|
|
54
|
+
});
|
|
55
|
+
}
|
|
41
56
|
function extractTransformCallbacks(systemMessage) {
|
|
42
57
|
if (!systemMessage || systemMessage.mode !== "customize" || !systemMessage.sections) {
|
|
43
58
|
return { wirePayload: systemMessage, transformCallbacks: void 0 };
|
|
@@ -91,12 +106,18 @@ class CopilotClient {
|
|
|
91
106
|
cliProcess = null;
|
|
92
107
|
connection = null;
|
|
93
108
|
socket = null;
|
|
94
|
-
|
|
109
|
+
runtimePort = null;
|
|
95
110
|
actualHost = "localhost";
|
|
96
111
|
state = "disconnected";
|
|
97
112
|
sessions = /* @__PURE__ */ new Map();
|
|
98
113
|
stderrBuffer = "";
|
|
99
114
|
// Captures CLI stderr for error messages
|
|
115
|
+
/** Resolved connection mode chosen in the constructor. */
|
|
116
|
+
connectionConfig;
|
|
117
|
+
/** Resolved path to the runtime executable (only used for child-process kinds). */
|
|
118
|
+
resolvedCliPath;
|
|
119
|
+
/** Resolved environment passed to the spawned runtime. */
|
|
120
|
+
resolvedEnv;
|
|
100
121
|
options;
|
|
101
122
|
isExternalServer = false;
|
|
102
123
|
forceStopping = false;
|
|
@@ -145,86 +166,79 @@ class CopilotClient {
|
|
|
145
166
|
* Creates a new CopilotClient instance.
|
|
146
167
|
*
|
|
147
168
|
* @param options - Configuration options for the client
|
|
148
|
-
* @throws Error if mutually exclusive options are provided (e.g., cliUrl with useStdio or cliPath)
|
|
149
169
|
*
|
|
150
170
|
* @example
|
|
151
171
|
* ```typescript
|
|
152
|
-
* // Default
|
|
172
|
+
* // Default: spawns the bundled runtime over stdio
|
|
153
173
|
* const client = new CopilotClient();
|
|
154
174
|
*
|
|
155
|
-
* // Connect to an existing
|
|
156
|
-
* const client = new CopilotClient({
|
|
175
|
+
* // Connect to an existing runtime
|
|
176
|
+
* const client = new CopilotClient({
|
|
177
|
+
* connection: RuntimeConnection.forUri("localhost:3000"),
|
|
178
|
+
* });
|
|
179
|
+
*
|
|
180
|
+
* // Spawn the runtime over TCP on a chosen port
|
|
181
|
+
* const client = new CopilotClient({
|
|
182
|
+
* connection: RuntimeConnection.forTcp({ port: 9001 }),
|
|
183
|
+
* });
|
|
157
184
|
*
|
|
158
|
-
* //
|
|
185
|
+
* // Use a custom runtime binary
|
|
159
186
|
* const client = new CopilotClient({
|
|
160
|
-
*
|
|
161
|
-
* logLevel: "debug"
|
|
187
|
+
* connection: RuntimeConnection.forStdio({ path: "/usr/local/bin/copilot" }),
|
|
188
|
+
* logLevel: "debug",
|
|
162
189
|
* });
|
|
163
190
|
* ```
|
|
164
191
|
*/
|
|
165
192
|
constructor(options = {}) {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
if (options.isChildProcess && (options.cliUrl || options.useStdio === false)) {
|
|
193
|
+
const conn = options._internalConnection ?? options.connection ?? { kind: "stdio" };
|
|
194
|
+
if (conn.kind === "uri" && (options.gitHubToken !== void 0 || options.useLoggedInUser !== void 0)) {
|
|
170
195
|
throw new Error(
|
|
171
|
-
"
|
|
196
|
+
"gitHubToken and useLoggedInUser cannot be used with RuntimeConnection.forUri (external server manages its own auth)"
|
|
172
197
|
);
|
|
173
198
|
}
|
|
174
|
-
if (
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
);
|
|
178
|
-
}
|
|
179
|
-
if (options.tcpConnectionToken !== void 0) {
|
|
180
|
-
if (typeof options.tcpConnectionToken !== "string" || options.tcpConnectionToken.length === 0) {
|
|
181
|
-
throw new Error("tcpConnectionToken must be a non-empty string");
|
|
182
|
-
}
|
|
183
|
-
if (options.useStdio === true) {
|
|
184
|
-
throw new Error("tcpConnectionToken cannot be used with useStdio: true");
|
|
199
|
+
if (conn.kind === "tcp" && conn.connectionToken !== void 0) {
|
|
200
|
+
if (typeof conn.connectionToken !== "string" || conn.connectionToken.length === 0) {
|
|
201
|
+
throw new Error("connectionToken must be a non-empty string");
|
|
185
202
|
}
|
|
186
203
|
}
|
|
187
|
-
|
|
188
|
-
const sdkSpawnsCli = !willUseStdio && !options.cliUrl && !options.isChildProcess;
|
|
189
|
-
this.effectiveConnectionToken = options.tcpConnectionToken ?? (sdkSpawnsCli ? randomUUID() : void 0);
|
|
204
|
+
this.connectionConfig = conn;
|
|
190
205
|
if (options.sessionFs) {
|
|
191
206
|
this.validateSessionFsConfig(options.sessionFs);
|
|
192
207
|
}
|
|
193
|
-
if (
|
|
194
|
-
const { host, port } = this.parseCliUrl(
|
|
208
|
+
if (conn.kind === "uri") {
|
|
209
|
+
const { host, port } = this.parseCliUrl(conn.url);
|
|
195
210
|
this.actualHost = host;
|
|
196
|
-
this.
|
|
211
|
+
this.runtimePort = port;
|
|
197
212
|
this.isExternalServer = true;
|
|
198
|
-
}
|
|
199
|
-
if (options.isChildProcess) {
|
|
213
|
+
} else if (conn.kind === "parent-process") {
|
|
200
214
|
this.isExternalServer = true;
|
|
201
215
|
}
|
|
216
|
+
if (conn.kind === "tcp") {
|
|
217
|
+
this.effectiveConnectionToken = conn.connectionToken ?? randomUUID();
|
|
218
|
+
} else if (conn.kind === "uri") {
|
|
219
|
+
this.effectiveConnectionToken = conn.connectionToken;
|
|
220
|
+
}
|
|
202
221
|
this.onListModels = options.onListModels;
|
|
203
222
|
this.onGetTraceContext = options.onGetTraceContext;
|
|
204
223
|
this.sessionFsConfig = options.sessionFs ?? null;
|
|
205
224
|
const effectiveEnv = options.env ?? process.env;
|
|
225
|
+
this.resolvedEnv = effectiveEnv;
|
|
226
|
+
this.resolvedCliPath = conn.kind === "stdio" || conn.kind === "tcp" ? conn.path ?? effectiveEnv.COPILOT_CLI_PATH ?? getBundledCliPath() : void 0;
|
|
227
|
+
const connArgs = conn.kind === "stdio" || conn.kind === "tcp" ? conn.args ?? [] : [];
|
|
228
|
+
this.connectionExtraArgs = [...connArgs];
|
|
206
229
|
this.options = {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
cwd: options.cwd ?? process.cwd(),
|
|
210
|
-
port: options.port || 0,
|
|
211
|
-
useStdio: options.cliUrl ? false : options.useStdio ?? true,
|
|
212
|
-
// Default to stdio unless cliUrl is provided
|
|
213
|
-
isChildProcess: options.isChildProcess ?? false,
|
|
214
|
-
cliUrl: options.cliUrl,
|
|
215
|
-
logLevel: options.logLevel || "debug",
|
|
216
|
-
autoStart: options.autoStart ?? true,
|
|
217
|
-
autoRestart: false,
|
|
218
|
-
env: effectiveEnv,
|
|
230
|
+
workingDirectory: options.workingDirectory ?? process.cwd(),
|
|
231
|
+
logLevel: options.logLevel,
|
|
219
232
|
gitHubToken: options.gitHubToken,
|
|
220
|
-
// Default useLoggedInUser to false when gitHubToken is provided, otherwise true
|
|
233
|
+
// Default useLoggedInUser to false when gitHubToken is provided, otherwise true.
|
|
221
234
|
useLoggedInUser: options.useLoggedInUser ?? (options.gitHubToken ? false : true),
|
|
222
235
|
telemetry: options.telemetry,
|
|
223
|
-
|
|
236
|
+
baseDirectory: options.baseDirectory,
|
|
224
237
|
sessionIdleTimeoutSeconds: options.sessionIdleTimeoutSeconds ?? 0,
|
|
225
|
-
|
|
238
|
+
enableRemoteSessions: options.enableRemoteSessions ?? false
|
|
226
239
|
};
|
|
227
240
|
}
|
|
241
|
+
connectionExtraArgs = [];
|
|
228
242
|
/**
|
|
229
243
|
* Parse CLI URL into host and port
|
|
230
244
|
* Supports formats: "host:port", "http://host:port", "https://host:port", or just "port"
|
|
@@ -258,20 +272,37 @@ class CopilotClient {
|
|
|
258
272
|
throw new Error("sessionFs.conventions must be either 'windows' or 'posix'");
|
|
259
273
|
}
|
|
260
274
|
}
|
|
275
|
+
setupSessionFs(session, config) {
|
|
276
|
+
if (!this.sessionFsConfig) {
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
if (!config.createSessionFsProvider) {
|
|
280
|
+
throw new Error(
|
|
281
|
+
"createSessionFsProvider is required in session config when sessionFs is enabled in client options."
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
const provider = config.createSessionFsProvider(session);
|
|
285
|
+
if (this.sessionFsConfig.capabilities?.sqlite && !provider.sqlite) {
|
|
286
|
+
throw new Error(
|
|
287
|
+
"SessionFsConfig declares capabilities.sqlite but the provider does not implement sqlite."
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
session.clientSessionApis.sessionFs = createSessionFsAdapter(provider);
|
|
291
|
+
}
|
|
261
292
|
/**
|
|
262
293
|
* Starts the CLI server and establishes a connection.
|
|
263
294
|
*
|
|
264
295
|
* If connecting to an external server (via cliUrl), only establishes the connection.
|
|
265
296
|
* Otherwise, spawns the CLI server process and then connects.
|
|
266
297
|
*
|
|
267
|
-
* This method is called automatically
|
|
298
|
+
* This method is called automatically the first time you create or resume a session.
|
|
268
299
|
*
|
|
269
300
|
* @returns A promise that resolves when the connection is established
|
|
270
301
|
* @throws Error if the server fails to start or the connection fails
|
|
271
302
|
*
|
|
272
303
|
* @example
|
|
273
304
|
* ```typescript
|
|
274
|
-
* const client = new CopilotClient(
|
|
305
|
+
* const client = new CopilotClient();
|
|
275
306
|
* await client.start();
|
|
276
307
|
* // Now ready to create sessions
|
|
277
308
|
* ```
|
|
@@ -291,7 +322,8 @@ class CopilotClient {
|
|
|
291
322
|
await this.connection.sendRequest("sessionFs.setProvider", {
|
|
292
323
|
initialCwd: this.sessionFsConfig.initialCwd,
|
|
293
324
|
sessionStatePath: this.sessionFsConfig.sessionStatePath,
|
|
294
|
-
conventions: this.sessionFsConfig.conventions
|
|
325
|
+
conventions: this.sessionFsConfig.conventions,
|
|
326
|
+
capabilities: this.sessionFsConfig.capabilities
|
|
295
327
|
});
|
|
296
328
|
}
|
|
297
329
|
this.state = "connected";
|
|
@@ -365,8 +397,15 @@ class CopilotClient {
|
|
|
365
397
|
}
|
|
366
398
|
this.modelsCache = null;
|
|
367
399
|
if (this.socket) {
|
|
400
|
+
const socket = this.socket;
|
|
401
|
+
this.socket = null;
|
|
368
402
|
try {
|
|
369
|
-
|
|
403
|
+
if (!socket.destroyed) {
|
|
404
|
+
await new Promise((resolve) => {
|
|
405
|
+
socket.once("close", () => resolve());
|
|
406
|
+
socket.end();
|
|
407
|
+
});
|
|
408
|
+
}
|
|
370
409
|
} catch (error) {
|
|
371
410
|
errors.push(
|
|
372
411
|
new Error(
|
|
@@ -374,11 +413,18 @@ class CopilotClient {
|
|
|
374
413
|
)
|
|
375
414
|
);
|
|
376
415
|
}
|
|
377
|
-
this.socket = null;
|
|
378
416
|
}
|
|
379
417
|
if (this.cliProcess && !this.isExternalServer) {
|
|
418
|
+
const child = this.cliProcess;
|
|
419
|
+
this.cliProcess = null;
|
|
380
420
|
try {
|
|
381
|
-
|
|
421
|
+
if (child.exitCode === null && child.signalCode === null) {
|
|
422
|
+
const exited = new Promise((resolve) => {
|
|
423
|
+
child.once("exit", () => resolve());
|
|
424
|
+
});
|
|
425
|
+
child.kill();
|
|
426
|
+
await exited;
|
|
427
|
+
}
|
|
382
428
|
} catch (error) {
|
|
383
429
|
errors.push(
|
|
384
430
|
new Error(
|
|
@@ -386,18 +432,32 @@ class CopilotClient {
|
|
|
386
432
|
)
|
|
387
433
|
);
|
|
388
434
|
}
|
|
389
|
-
this.cliProcess = null;
|
|
390
435
|
}
|
|
391
436
|
if (this.cliStartTimeout) {
|
|
392
437
|
clearTimeout(this.cliStartTimeout);
|
|
393
438
|
this.cliStartTimeout = null;
|
|
394
439
|
}
|
|
395
440
|
this.state = "disconnected";
|
|
396
|
-
this.
|
|
441
|
+
this.runtimePort = null;
|
|
397
442
|
this.stderrBuffer = "";
|
|
398
443
|
this.processExitPromise = null;
|
|
399
444
|
return errors;
|
|
400
445
|
}
|
|
446
|
+
/**
|
|
447
|
+
* Alias for {@link stop} that lets `CopilotClient` participate in `await using`
|
|
448
|
+
* blocks for automatic cleanup.
|
|
449
|
+
*
|
|
450
|
+
* @example
|
|
451
|
+
* ```typescript
|
|
452
|
+
* await using client = new CopilotClient();
|
|
453
|
+
* const session = await client.createSession({ onPermissionRequest: approveAll });
|
|
454
|
+
* await session.sendAndWait("Hello");
|
|
455
|
+
* // client.stop() is called automatically when the block exits.
|
|
456
|
+
* ```
|
|
457
|
+
*/
|
|
458
|
+
async [Symbol.asyncDispose]() {
|
|
459
|
+
await this.stop();
|
|
460
|
+
}
|
|
401
461
|
/**
|
|
402
462
|
* Forcefully stops the CLI server without graceful cleanup.
|
|
403
463
|
*
|
|
@@ -454,7 +514,7 @@ class CopilotClient {
|
|
|
454
514
|
this.cliStartTimeout = null;
|
|
455
515
|
}
|
|
456
516
|
this.state = "disconnected";
|
|
457
|
-
this.
|
|
517
|
+
this.runtimePort = null;
|
|
458
518
|
this.stderrBuffer = "";
|
|
459
519
|
this.processExitPromise = null;
|
|
460
520
|
}
|
|
@@ -462,12 +522,11 @@ class CopilotClient {
|
|
|
462
522
|
* Creates a new conversation session with the Copilot CLI.
|
|
463
523
|
*
|
|
464
524
|
* Sessions maintain conversation state, handle events, and manage tool execution.
|
|
465
|
-
* If the client is not connected
|
|
466
|
-
* start the connection.
|
|
525
|
+
* If the client is not connected, this method automatically starts the connection.
|
|
467
526
|
*
|
|
468
527
|
* @param config - Optional configuration for the session
|
|
469
528
|
* @returns A promise that resolves with the created session
|
|
470
|
-
* @throws Error if the client
|
|
529
|
+
* @throws Error if the client fails to start
|
|
471
530
|
*
|
|
472
531
|
* @example
|
|
473
532
|
* ```typescript
|
|
@@ -488,17 +547,8 @@ class CopilotClient {
|
|
|
488
547
|
* ```
|
|
489
548
|
*/
|
|
490
549
|
async createSession(config) {
|
|
491
|
-
if (!config?.onPermissionRequest) {
|
|
492
|
-
throw new Error(
|
|
493
|
-
"An onPermissionRequest handler is required when creating a session. For example, to allow all permissions, use { onPermissionRequest: approveAll }."
|
|
494
|
-
);
|
|
495
|
-
}
|
|
496
550
|
if (!this.connection) {
|
|
497
|
-
|
|
498
|
-
await this.start();
|
|
499
|
-
} else {
|
|
500
|
-
throw new Error("Client not connected. Call start() first.");
|
|
501
|
-
}
|
|
551
|
+
await this.start();
|
|
502
552
|
}
|
|
503
553
|
const sessionId = config.sessionId ?? randomUUID();
|
|
504
554
|
const session = new CopilotSession(
|
|
@@ -516,11 +566,11 @@ class CopilotClient {
|
|
|
516
566
|
if (config.onElicitationRequest) {
|
|
517
567
|
session.registerElicitationHandler(config.onElicitationRequest);
|
|
518
568
|
}
|
|
519
|
-
if (config.
|
|
520
|
-
session.registerExitPlanModeHandler(config.
|
|
569
|
+
if (config.onExitPlanModeRequest) {
|
|
570
|
+
session.registerExitPlanModeHandler(config.onExitPlanModeRequest);
|
|
521
571
|
}
|
|
522
|
-
if (config.
|
|
523
|
-
session.registerAutoModeSwitchHandler(config.
|
|
572
|
+
if (config.onAutoModeSwitchRequest) {
|
|
573
|
+
session.registerAutoModeSwitchHandler(config.onAutoModeSwitchRequest);
|
|
524
574
|
}
|
|
525
575
|
if (config.hooks) {
|
|
526
576
|
session.registerHooks(config.hooks);
|
|
@@ -535,17 +585,7 @@ class CopilotClient {
|
|
|
535
585
|
session.on(config.onEvent);
|
|
536
586
|
}
|
|
537
587
|
this.sessions.set(sessionId, session);
|
|
538
|
-
|
|
539
|
-
if (config.createSessionFsHandler) {
|
|
540
|
-
session.clientSessionApis.sessionFs = createSessionFsAdapter(
|
|
541
|
-
config.createSessionFsHandler(session)
|
|
542
|
-
);
|
|
543
|
-
} else {
|
|
544
|
-
throw new Error(
|
|
545
|
-
"createSessionFsHandler is required in session config when sessionFs is enabled in client options."
|
|
546
|
-
);
|
|
547
|
-
}
|
|
548
|
-
}
|
|
588
|
+
this.setupSessionFs(session, config);
|
|
549
589
|
try {
|
|
550
590
|
const response = await this.connection.sendRequest("session.create", {
|
|
551
591
|
...await getTraceContext(this.onGetTraceContext),
|
|
@@ -567,21 +607,21 @@ class CopilotClient {
|
|
|
567
607
|
systemMessage: wireSystemMessage,
|
|
568
608
|
availableTools: config.availableTools,
|
|
569
609
|
excludedTools: config.excludedTools,
|
|
570
|
-
provider: config.provider
|
|
610
|
+
provider: config.provider,
|
|
571
611
|
enableSessionTelemetry: config.enableSessionTelemetry,
|
|
572
612
|
modelCapabilities: config.modelCapabilities,
|
|
573
613
|
requestPermission: true,
|
|
574
614
|
requestUserInput: !!config.onUserInputRequest,
|
|
575
615
|
requestElicitation: !!config.onElicitationRequest,
|
|
576
|
-
requestExitPlanMode: !!config.
|
|
577
|
-
requestAutoModeSwitch: !!config.
|
|
616
|
+
requestExitPlanMode: !!config.onExitPlanModeRequest,
|
|
617
|
+
requestAutoModeSwitch: !!config.onAutoModeSwitchRequest,
|
|
578
618
|
hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
|
|
579
619
|
workingDirectory: config.workingDirectory,
|
|
580
620
|
streaming: config.streaming,
|
|
581
621
|
includeSubAgentStreamingEvents: config.includeSubAgentStreamingEvents ?? true,
|
|
582
|
-
mcpServers: config.mcpServers,
|
|
622
|
+
mcpServers: toWireMcpServers(config.mcpServers),
|
|
583
623
|
envValueMode: "direct",
|
|
584
|
-
customAgents: config.customAgents,
|
|
624
|
+
customAgents: toWireCustomAgents(config.customAgents),
|
|
585
625
|
defaultAgent: config.defaultAgent,
|
|
586
626
|
agent: config.agent,
|
|
587
627
|
configDir: config.configDir,
|
|
@@ -590,7 +630,9 @@ class CopilotClient {
|
|
|
590
630
|
instructionDirectories: config.instructionDirectories,
|
|
591
631
|
disabledSkills: config.disabledSkills,
|
|
592
632
|
infiniteSessions: config.infiniteSessions,
|
|
593
|
-
gitHubToken: config.gitHubToken
|
|
633
|
+
gitHubToken: config.gitHubToken,
|
|
634
|
+
remoteSession: config.remoteSession,
|
|
635
|
+
cloud: config.cloud
|
|
594
636
|
});
|
|
595
637
|
const { workspacePath, capabilities } = response;
|
|
596
638
|
session["_workspacePath"] = workspacePath;
|
|
@@ -626,17 +668,8 @@ class CopilotClient {
|
|
|
626
668
|
* ```
|
|
627
669
|
*/
|
|
628
670
|
async resumeSession(sessionId, config) {
|
|
629
|
-
if (!config?.onPermissionRequest) {
|
|
630
|
-
throw new Error(
|
|
631
|
-
"An onPermissionRequest handler is required when resuming a session. For example, to allow all permissions, use { onPermissionRequest: approveAll }."
|
|
632
|
-
);
|
|
633
|
-
}
|
|
634
671
|
if (!this.connection) {
|
|
635
|
-
|
|
636
|
-
await this.start();
|
|
637
|
-
} else {
|
|
638
|
-
throw new Error("Client not connected. Call start() first.");
|
|
639
|
-
}
|
|
672
|
+
await this.start();
|
|
640
673
|
}
|
|
641
674
|
const session = new CopilotSession(
|
|
642
675
|
sessionId,
|
|
@@ -653,11 +686,11 @@ class CopilotClient {
|
|
|
653
686
|
if (config.onElicitationRequest) {
|
|
654
687
|
session.registerElicitationHandler(config.onElicitationRequest);
|
|
655
688
|
}
|
|
656
|
-
if (config.
|
|
657
|
-
session.registerExitPlanModeHandler(config.
|
|
689
|
+
if (config.onExitPlanModeRequest) {
|
|
690
|
+
session.registerExitPlanModeHandler(config.onExitPlanModeRequest);
|
|
658
691
|
}
|
|
659
|
-
if (config.
|
|
660
|
-
session.registerAutoModeSwitchHandler(config.
|
|
692
|
+
if (config.onAutoModeSwitchRequest) {
|
|
693
|
+
session.registerAutoModeSwitchHandler(config.onAutoModeSwitchRequest);
|
|
661
694
|
}
|
|
662
695
|
if (config.hooks) {
|
|
663
696
|
session.registerHooks(config.hooks);
|
|
@@ -672,17 +705,7 @@ class CopilotClient {
|
|
|
672
705
|
session.on(config.onEvent);
|
|
673
706
|
}
|
|
674
707
|
this.sessions.set(sessionId, session);
|
|
675
|
-
|
|
676
|
-
if (config.createSessionFsHandler) {
|
|
677
|
-
session.clientSessionApis.sessionFs = createSessionFsAdapter(
|
|
678
|
-
config.createSessionFsHandler(session)
|
|
679
|
-
);
|
|
680
|
-
} else {
|
|
681
|
-
throw new Error(
|
|
682
|
-
"createSessionFsHandler is required in session config when sessionFs is enabled in client options."
|
|
683
|
-
);
|
|
684
|
-
}
|
|
685
|
-
}
|
|
708
|
+
this.setupSessionFs(session, config);
|
|
686
709
|
try {
|
|
687
710
|
const response = await this.connection.sendRequest("session.resume", {
|
|
688
711
|
...await getTraceContext(this.onGetTraceContext),
|
|
@@ -705,31 +728,32 @@ class CopilotClient {
|
|
|
705
728
|
name: cmd.name,
|
|
706
729
|
description: cmd.description
|
|
707
730
|
})),
|
|
708
|
-
provider: config.provider
|
|
731
|
+
provider: config.provider,
|
|
709
732
|
modelCapabilities: config.modelCapabilities,
|
|
710
733
|
requestPermission: config.onPermissionRequest !== defaultJoinSessionPermissionHandler,
|
|
711
734
|
requestUserInput: !!config.onUserInputRequest,
|
|
712
735
|
requestElicitation: !!config.onElicitationRequest,
|
|
713
|
-
requestExitPlanMode: !!config.
|
|
714
|
-
requestAutoModeSwitch: !!config.
|
|
736
|
+
requestExitPlanMode: !!config.onExitPlanModeRequest,
|
|
737
|
+
requestAutoModeSwitch: !!config.onAutoModeSwitchRequest,
|
|
715
738
|
hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
|
|
716
739
|
workingDirectory: config.workingDirectory,
|
|
717
740
|
configDir: config.configDir,
|
|
718
741
|
enableConfigDiscovery: config.enableConfigDiscovery,
|
|
719
742
|
streaming: config.streaming,
|
|
720
743
|
includeSubAgentStreamingEvents: config.includeSubAgentStreamingEvents ?? true,
|
|
721
|
-
mcpServers: config.mcpServers,
|
|
744
|
+
mcpServers: toWireMcpServers(config.mcpServers),
|
|
722
745
|
envValueMode: "direct",
|
|
723
|
-
customAgents: config.customAgents,
|
|
746
|
+
customAgents: toWireCustomAgents(config.customAgents),
|
|
724
747
|
defaultAgent: config.defaultAgent,
|
|
725
748
|
agent: config.agent,
|
|
726
749
|
skillDirectories: config.skillDirectories,
|
|
727
750
|
instructionDirectories: config.instructionDirectories,
|
|
728
751
|
disabledSkills: config.disabledSkills,
|
|
729
752
|
infiniteSessions: config.infiniteSessions,
|
|
730
|
-
|
|
753
|
+
suppressResumeEvent: config.suppressResumeEvent,
|
|
731
754
|
continuePendingWork: config.continuePendingWork,
|
|
732
|
-
gitHubToken: config.gitHubToken
|
|
755
|
+
gitHubToken: config.gitHubToken,
|
|
756
|
+
remoteSession: config.remoteSession
|
|
733
757
|
});
|
|
734
758
|
const { workspacePath, capabilities } = response;
|
|
735
759
|
session["_workspacePath"] = workspacePath;
|
|
@@ -956,8 +980,13 @@ class CopilotClient {
|
|
|
956
980
|
if (!this.connection) {
|
|
957
981
|
throw new Error("Client not connected");
|
|
958
982
|
}
|
|
983
|
+
let wireFilter;
|
|
984
|
+
if (filter) {
|
|
985
|
+
const { workingDirectory, ...rest } = filter;
|
|
986
|
+
wireFilter = { ...rest, cwd: workingDirectory };
|
|
987
|
+
}
|
|
959
988
|
const response = await this.connection.sendRequest("session.list", {
|
|
960
|
-
filter
|
|
989
|
+
filter: wireFilter
|
|
961
990
|
});
|
|
962
991
|
const { sessions } = response;
|
|
963
992
|
return sessions.map(CopilotClient.toSessionMetadata);
|
|
@@ -992,13 +1021,19 @@ class CopilotClient {
|
|
|
992
1021
|
return CopilotClient.toSessionMetadata(session);
|
|
993
1022
|
}
|
|
994
1023
|
static toSessionMetadata(raw) {
|
|
1024
|
+
const { context } = raw;
|
|
995
1025
|
return {
|
|
996
1026
|
sessionId: raw.sessionId,
|
|
997
1027
|
startTime: new Date(raw.startTime),
|
|
998
1028
|
modifiedTime: new Date(raw.modifiedTime),
|
|
999
1029
|
summary: raw.summary,
|
|
1000
1030
|
isRemote: raw.isRemote,
|
|
1001
|
-
context:
|
|
1031
|
+
context: context ? {
|
|
1032
|
+
workingDirectory: context.cwd,
|
|
1033
|
+
gitRoot: context.gitRoot,
|
|
1034
|
+
repository: context.repository,
|
|
1035
|
+
branch: context.branch
|
|
1036
|
+
} : void 0
|
|
1002
1037
|
};
|
|
1003
1038
|
}
|
|
1004
1039
|
/**
|
|
@@ -1051,7 +1086,7 @@ class CopilotClient {
|
|
|
1051
1086
|
throw new Error(result.error || "Failed to set foreground session");
|
|
1052
1087
|
}
|
|
1053
1088
|
}
|
|
1054
|
-
|
|
1089
|
+
onLifecycle(eventTypeOrHandler, handler) {
|
|
1055
1090
|
if (typeof eventTypeOrHandler === "string" && handler) {
|
|
1056
1091
|
const eventType = eventTypeOrHandler;
|
|
1057
1092
|
if (!this.typedLifecycleHandlers.has(eventType)) {
|
|
@@ -1078,17 +1113,17 @@ class CopilotClient {
|
|
|
1078
1113
|
async startCLIServer() {
|
|
1079
1114
|
return new Promise((resolve, reject) => {
|
|
1080
1115
|
this.stderrBuffer = "";
|
|
1081
|
-
const args = [
|
|
1082
|
-
|
|
1083
|
-
"--
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
this.options.logLevel
|
|
1087
|
-
];
|
|
1088
|
-
if (this.options.useStdio) {
|
|
1116
|
+
const args = [...this.connectionExtraArgs, "--headless", "--no-auto-update"];
|
|
1117
|
+
if (this.options.logLevel) {
|
|
1118
|
+
args.push("--log-level", this.options.logLevel);
|
|
1119
|
+
}
|
|
1120
|
+
if (this.connectionConfig.kind === "stdio") {
|
|
1089
1121
|
args.push("--stdio");
|
|
1090
|
-
} else if (this.
|
|
1091
|
-
|
|
1122
|
+
} else if (this.connectionConfig.kind === "tcp") {
|
|
1123
|
+
const requestedPort = this.connectionConfig.port ?? 0;
|
|
1124
|
+
if (requestedPort > 0) {
|
|
1125
|
+
args.push("--port", requestedPort.toString());
|
|
1126
|
+
}
|
|
1092
1127
|
}
|
|
1093
1128
|
if (this.options.gitHubToken) {
|
|
1094
1129
|
args.push("--auth-token-env", "COPILOT_SDK_AUTH_TOKEN");
|
|
@@ -1102,10 +1137,10 @@ class CopilotClient {
|
|
|
1102
1137
|
this.options.sessionIdleTimeoutSeconds.toString()
|
|
1103
1138
|
);
|
|
1104
1139
|
}
|
|
1105
|
-
if (this.options.
|
|
1140
|
+
if (this.options.enableRemoteSessions) {
|
|
1106
1141
|
args.push("--remote");
|
|
1107
1142
|
}
|
|
1108
|
-
const envWithoutNodeDebug = { ...this.
|
|
1143
|
+
const envWithoutNodeDebug = { ...this.resolvedEnv };
|
|
1109
1144
|
delete envWithoutNodeDebug.NODE_DEBUG;
|
|
1110
1145
|
if (this.options.gitHubToken) {
|
|
1111
1146
|
envWithoutNodeDebug.COPILOT_SDK_AUTH_TOKEN = this.options.gitHubToken;
|
|
@@ -1113,12 +1148,12 @@ class CopilotClient {
|
|
|
1113
1148
|
if (this.effectiveConnectionToken) {
|
|
1114
1149
|
envWithoutNodeDebug.COPILOT_CONNECTION_TOKEN = this.effectiveConnectionToken;
|
|
1115
1150
|
}
|
|
1116
|
-
if (this.options.
|
|
1117
|
-
envWithoutNodeDebug.COPILOT_HOME = this.options.
|
|
1151
|
+
if (this.options.baseDirectory) {
|
|
1152
|
+
envWithoutNodeDebug.COPILOT_HOME = this.options.baseDirectory;
|
|
1118
1153
|
}
|
|
1119
|
-
if (!this.
|
|
1154
|
+
if (!this.resolvedCliPath) {
|
|
1120
1155
|
throw new Error(
|
|
1121
|
-
"Path to Copilot CLI is required. Please
|
|
1156
|
+
"Path to Copilot CLI is required. Please supply it via `RuntimeConnection.forStdio({ path })` or `RuntimeConnection.forTcp({ path })`, set the COPILOT_CLI_PATH environment variable, or use `RuntimeConnection.forUri(...)` to connect to an already-running runtime."
|
|
1122
1157
|
);
|
|
1123
1158
|
}
|
|
1124
1159
|
if (this.options.telemetry) {
|
|
@@ -1137,31 +1172,31 @@ class CopilotClient {
|
|
|
1137
1172
|
t.captureContent
|
|
1138
1173
|
);
|
|
1139
1174
|
}
|
|
1140
|
-
if (!existsSync(this.
|
|
1175
|
+
if (!existsSync(this.resolvedCliPath)) {
|
|
1141
1176
|
throw new Error(
|
|
1142
|
-
`Copilot CLI not found at ${this.
|
|
1177
|
+
`Copilot CLI not found at ${this.resolvedCliPath}. Ensure @github/copilot is installed.`
|
|
1143
1178
|
);
|
|
1144
1179
|
}
|
|
1145
|
-
const stdioConfig = this.
|
|
1146
|
-
const isJsFile = this.
|
|
1180
|
+
const stdioConfig = this.connectionConfig.kind === "stdio" ? ["pipe", "pipe", "pipe"] : ["ignore", "pipe", "pipe"];
|
|
1181
|
+
const isJsFile = this.resolvedCliPath.endsWith(".js");
|
|
1147
1182
|
if (isJsFile) {
|
|
1148
|
-
this.cliProcess = spawn(getNodeExecPath(), [this.
|
|
1183
|
+
this.cliProcess = spawn(getNodeExecPath(), [this.resolvedCliPath, ...args], {
|
|
1149
1184
|
stdio: stdioConfig,
|
|
1150
|
-
cwd: this.options.
|
|
1185
|
+
cwd: this.options.workingDirectory,
|
|
1151
1186
|
env: envWithoutNodeDebug,
|
|
1152
1187
|
windowsHide: true
|
|
1153
1188
|
});
|
|
1154
1189
|
} else {
|
|
1155
|
-
this.cliProcess = spawn(this.
|
|
1190
|
+
this.cliProcess = spawn(this.resolvedCliPath, args, {
|
|
1156
1191
|
stdio: stdioConfig,
|
|
1157
|
-
cwd: this.options.
|
|
1192
|
+
cwd: this.options.workingDirectory,
|
|
1158
1193
|
env: envWithoutNodeDebug,
|
|
1159
1194
|
windowsHide: true
|
|
1160
1195
|
});
|
|
1161
1196
|
}
|
|
1162
1197
|
let stdout = "";
|
|
1163
1198
|
let resolved = false;
|
|
1164
|
-
if (this.
|
|
1199
|
+
if (this.connectionConfig.kind === "stdio") {
|
|
1165
1200
|
resolved = true;
|
|
1166
1201
|
resolve();
|
|
1167
1202
|
} else {
|
|
@@ -1169,7 +1204,7 @@ class CopilotClient {
|
|
|
1169
1204
|
stdout += data.toString();
|
|
1170
1205
|
const match = stdout.match(/listening on port (\d+)/i);
|
|
1171
1206
|
if (match && !resolved) {
|
|
1172
|
-
this.
|
|
1207
|
+
this.runtimePort = parseInt(match[1], 10);
|
|
1173
1208
|
resolved = true;
|
|
1174
1209
|
resolve();
|
|
1175
1210
|
}
|
|
@@ -1243,19 +1278,21 @@ stderr: ${stderrOutput}`
|
|
|
1243
1278
|
resolved = true;
|
|
1244
1279
|
reject(new Error("Timeout waiting for CLI server to start"));
|
|
1245
1280
|
}
|
|
1246
|
-
},
|
|
1281
|
+
}, 3e4);
|
|
1247
1282
|
});
|
|
1248
1283
|
}
|
|
1249
1284
|
/**
|
|
1250
1285
|
* Connect to the CLI server (via socket or stdio)
|
|
1251
1286
|
*/
|
|
1252
1287
|
async connectToServer() {
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1288
|
+
switch (this.connectionConfig.kind) {
|
|
1289
|
+
case "parent-process":
|
|
1290
|
+
return this.connectToParentProcessViaStdio();
|
|
1291
|
+
case "stdio":
|
|
1292
|
+
return this.connectToChildProcessViaStdio();
|
|
1293
|
+
case "tcp":
|
|
1294
|
+
case "uri":
|
|
1295
|
+
return this.connectViaTcp();
|
|
1259
1296
|
}
|
|
1260
1297
|
}
|
|
1261
1298
|
/**
|
|
@@ -1295,12 +1332,17 @@ stderr: ${stderrOutput}`
|
|
|
1295
1332
|
* Connect to the CLI server via TCP socket
|
|
1296
1333
|
*/
|
|
1297
1334
|
async connectViaTcp() {
|
|
1298
|
-
if (!this.
|
|
1335
|
+
if (!this.runtimePort) {
|
|
1299
1336
|
throw new Error("Server port not available");
|
|
1300
1337
|
}
|
|
1301
1338
|
return new Promise((resolve, reject) => {
|
|
1302
1339
|
this.socket = new Socket();
|
|
1303
|
-
|
|
1340
|
+
const connectionTimeout = setTimeout(() => {
|
|
1341
|
+
this.socket?.destroy();
|
|
1342
|
+
reject(new Error("Timeout connecting to CLI server"));
|
|
1343
|
+
}, 1e4);
|
|
1344
|
+
this.socket.connect(this.runtimePort, this.actualHost, () => {
|
|
1345
|
+
clearTimeout(connectionTimeout);
|
|
1304
1346
|
this.connection = createMessageConnection(
|
|
1305
1347
|
new StreamMessageReader(this.socket),
|
|
1306
1348
|
new StreamMessageWriter(this.socket)
|
|
@@ -1310,6 +1352,7 @@ stderr: ${stderrOutput}`
|
|
|
1310
1352
|
resolve();
|
|
1311
1353
|
});
|
|
1312
1354
|
this.socket.on("error", (error) => {
|
|
1355
|
+
clearTimeout(connectionTimeout);
|
|
1313
1356
|
reject(new Error(`Failed to connect to CLI server: ${error.message}`));
|
|
1314
1357
|
});
|
|
1315
1358
|
});
|
|
@@ -1378,7 +1421,20 @@ stderr: ${stderrOutput}`
|
|
|
1378
1421
|
if (typeof notification !== "object" || !notification || !("type" in notification) || typeof notification.type !== "string" || !("sessionId" in notification) || typeof notification.sessionId !== "string") {
|
|
1379
1422
|
return;
|
|
1380
1423
|
}
|
|
1381
|
-
const
|
|
1424
|
+
const raw = notification;
|
|
1425
|
+
let metadata;
|
|
1426
|
+
if (raw.metadata && raw.metadata.startTime && raw.metadata.modifiedTime) {
|
|
1427
|
+
metadata = {
|
|
1428
|
+
startTime: new Date(raw.metadata.startTime),
|
|
1429
|
+
modifiedTime: new Date(raw.metadata.modifiedTime),
|
|
1430
|
+
summary: raw.metadata.summary
|
|
1431
|
+
};
|
|
1432
|
+
}
|
|
1433
|
+
const event = {
|
|
1434
|
+
type: raw.type,
|
|
1435
|
+
sessionId: raw.sessionId,
|
|
1436
|
+
metadata
|
|
1437
|
+
};
|
|
1382
1438
|
const typedHandlers = this.typedLifecycleHandlers.get(event.type);
|
|
1383
1439
|
if (typedHandlers) {
|
|
1384
1440
|
for (const handler of typedHandlers) {
|