@github/copilot-sdk 0.1.33-unstable.1 → 0.2.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.
@@ -0,0 +1,1317 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var client_exports = {};
20
+ __export(client_exports, {
21
+ CopilotClient: () => CopilotClient
22
+ });
23
+ module.exports = __toCommonJS(client_exports);
24
+ var import_node_child_process = require("node:child_process");
25
+ var import_node_crypto = require("node:crypto");
26
+ var import_node_fs = require("node:fs");
27
+ var import_node_module = require("node:module");
28
+ var import_node_net = require("node:net");
29
+ var import_node_path = require("node:path");
30
+ var import_node_url = require("node:url");
31
+ var import_node = require("vscode-jsonrpc/node.js");
32
+ var import_rpc = require("./generated/rpc.js");
33
+ var import_sdkProtocolVersion = require("./sdkProtocolVersion.js");
34
+ var import_session = require("./session.js");
35
+ var import_telemetry = require("./telemetry.js");
36
+ const import_meta = {};
37
+ const MIN_PROTOCOL_VERSION = 2;
38
+ function isZodSchema(value) {
39
+ return value != null && typeof value === "object" && "toJSONSchema" in value && typeof value.toJSONSchema === "function";
40
+ }
41
+ function toJsonSchema(parameters) {
42
+ if (!parameters) return void 0;
43
+ if (isZodSchema(parameters)) {
44
+ return parameters.toJSONSchema();
45
+ }
46
+ return parameters;
47
+ }
48
+ function extractTransformCallbacks(systemMessage) {
49
+ if (!systemMessage || systemMessage.mode !== "customize" || !systemMessage.sections) {
50
+ return { wirePayload: systemMessage, transformCallbacks: void 0 };
51
+ }
52
+ const transformCallbacks = /* @__PURE__ */ new Map();
53
+ const wireSections = {};
54
+ for (const [sectionId, override] of Object.entries(systemMessage.sections)) {
55
+ if (!override) continue;
56
+ if (typeof override.action === "function") {
57
+ transformCallbacks.set(sectionId, override.action);
58
+ wireSections[sectionId] = { action: "transform" };
59
+ } else {
60
+ wireSections[sectionId] = { action: override.action, content: override.content };
61
+ }
62
+ }
63
+ if (transformCallbacks.size === 0) {
64
+ return { wirePayload: systemMessage, transformCallbacks: void 0 };
65
+ }
66
+ const wirePayload = {
67
+ ...systemMessage,
68
+ sections: wireSections
69
+ };
70
+ return { wirePayload, transformCallbacks };
71
+ }
72
+ function getNodeExecPath() {
73
+ if (process.versions.bun) {
74
+ return "node";
75
+ }
76
+ return process.execPath;
77
+ }
78
+ function getBundledCliPath() {
79
+ if (typeof import_meta.resolve === "function") {
80
+ const sdkUrl = import_meta.resolve("@github/copilot/sdk");
81
+ const sdkPath = (0, import_node_url.fileURLToPath)(sdkUrl);
82
+ return (0, import_node_path.join)((0, import_node_path.dirname)((0, import_node_path.dirname)(sdkPath)), "index.js");
83
+ }
84
+ const req = (0, import_node_module.createRequire)(__filename);
85
+ const searchPaths = req.resolve.paths("@github/copilot") ?? [];
86
+ for (const base of searchPaths) {
87
+ const candidate = (0, import_node_path.join)(base, "@github", "copilot", "index.js");
88
+ if ((0, import_node_fs.existsSync)(candidate)) {
89
+ return candidate;
90
+ }
91
+ }
92
+ throw new Error(
93
+ `Could not find @github/copilot package. Searched ${searchPaths.length} paths. Ensure it is installed, or pass cliPath/cliUrl to CopilotClient.`
94
+ );
95
+ }
96
+ class CopilotClient {
97
+ cliProcess = null;
98
+ connection = null;
99
+ socket = null;
100
+ actualPort = null;
101
+ actualHost = "localhost";
102
+ state = "disconnected";
103
+ sessions = /* @__PURE__ */ new Map();
104
+ stderrBuffer = "";
105
+ // Captures CLI stderr for error messages
106
+ options;
107
+ isExternalServer = false;
108
+ forceStopping = false;
109
+ onListModels;
110
+ onGetTraceContext;
111
+ modelsCache = null;
112
+ modelsCacheLock = Promise.resolve();
113
+ sessionLifecycleHandlers = /* @__PURE__ */ new Set();
114
+ typedLifecycleHandlers = /* @__PURE__ */ new Map();
115
+ _rpc = null;
116
+ processExitPromise = null;
117
+ // Rejects when CLI process exits
118
+ negotiatedProtocolVersion = null;
119
+ /**
120
+ * Typed server-scoped RPC methods.
121
+ * @throws Error if the client is not connected
122
+ */
123
+ get rpc() {
124
+ if (!this.connection) {
125
+ throw new Error("Client is not connected. Call start() first.");
126
+ }
127
+ if (!this._rpc) {
128
+ this._rpc = (0, import_rpc.createServerRpc)(this.connection);
129
+ }
130
+ return this._rpc;
131
+ }
132
+ /**
133
+ * Creates a new CopilotClient instance.
134
+ *
135
+ * @param options - Configuration options for the client
136
+ * @throws Error if mutually exclusive options are provided (e.g., cliUrl with useStdio or cliPath)
137
+ *
138
+ * @example
139
+ * ```typescript
140
+ * // Default options - spawns CLI server using stdio
141
+ * const client = new CopilotClient();
142
+ *
143
+ * // Connect to an existing server
144
+ * const client = new CopilotClient({ cliUrl: "localhost:3000" });
145
+ *
146
+ * // Custom CLI path with specific log level
147
+ * const client = new CopilotClient({
148
+ * cliPath: "/usr/local/bin/copilot",
149
+ * logLevel: "debug"
150
+ * });
151
+ * ```
152
+ */
153
+ constructor(options = {}) {
154
+ if (options.cliUrl && (options.useStdio === true || options.cliPath)) {
155
+ throw new Error("cliUrl is mutually exclusive with useStdio and cliPath");
156
+ }
157
+ if (options.isChildProcess && (options.cliUrl || options.useStdio === false)) {
158
+ throw new Error(
159
+ "isChildProcess must be used in conjunction with useStdio and not with cliUrl"
160
+ );
161
+ }
162
+ if (options.cliUrl && (options.githubToken || options.useLoggedInUser !== void 0)) {
163
+ throw new Error(
164
+ "githubToken and useLoggedInUser cannot be used with cliUrl (external server manages its own auth)"
165
+ );
166
+ }
167
+ if (options.cliUrl) {
168
+ const { host, port } = this.parseCliUrl(options.cliUrl);
169
+ this.actualHost = host;
170
+ this.actualPort = port;
171
+ this.isExternalServer = true;
172
+ }
173
+ if (options.isChildProcess) {
174
+ this.isExternalServer = true;
175
+ }
176
+ this.onListModels = options.onListModels;
177
+ this.onGetTraceContext = options.onGetTraceContext;
178
+ this.options = {
179
+ cliPath: options.cliUrl ? void 0 : options.cliPath || getBundledCliPath(),
180
+ cliArgs: options.cliArgs ?? [],
181
+ cwd: options.cwd ?? process.cwd(),
182
+ port: options.port || 0,
183
+ useStdio: options.cliUrl ? false : options.useStdio ?? true,
184
+ // Default to stdio unless cliUrl is provided
185
+ isChildProcess: options.isChildProcess ?? false,
186
+ cliUrl: options.cliUrl,
187
+ logLevel: options.logLevel || "debug",
188
+ autoStart: options.autoStart ?? true,
189
+ autoRestart: false,
190
+ env: options.env ?? process.env,
191
+ githubToken: options.githubToken,
192
+ // Default useLoggedInUser to false when githubToken is provided, otherwise true
193
+ useLoggedInUser: options.useLoggedInUser ?? (options.githubToken ? false : true),
194
+ telemetry: options.telemetry
195
+ };
196
+ }
197
+ /**
198
+ * Parse CLI URL into host and port
199
+ * Supports formats: "host:port", "http://host:port", "https://host:port", or just "port"
200
+ */
201
+ parseCliUrl(url) {
202
+ let cleanUrl = url.replace(/^https?:\/\//, "");
203
+ if (/^\d+$/.test(cleanUrl)) {
204
+ return { host: "localhost", port: parseInt(cleanUrl, 10) };
205
+ }
206
+ const parts = cleanUrl.split(":");
207
+ if (parts.length !== 2) {
208
+ throw new Error(
209
+ `Invalid cliUrl format: ${url}. Expected "host:port", "http://host:port", or "port"`
210
+ );
211
+ }
212
+ const host = parts[0] || "localhost";
213
+ const port = parseInt(parts[1], 10);
214
+ if (isNaN(port) || port <= 0 || port > 65535) {
215
+ throw new Error(`Invalid port in cliUrl: ${url}`);
216
+ }
217
+ return { host, port };
218
+ }
219
+ /**
220
+ * Starts the CLI server and establishes a connection.
221
+ *
222
+ * If connecting to an external server (via cliUrl), only establishes the connection.
223
+ * Otherwise, spawns the CLI server process and then connects.
224
+ *
225
+ * This method is called automatically when creating a session if `autoStart` is true (default).
226
+ *
227
+ * @returns A promise that resolves when the connection is established
228
+ * @throws Error if the server fails to start or the connection fails
229
+ *
230
+ * @example
231
+ * ```typescript
232
+ * const client = new CopilotClient({ autoStart: false });
233
+ * await client.start();
234
+ * // Now ready to create sessions
235
+ * ```
236
+ */
237
+ async start() {
238
+ if (this.state === "connected") {
239
+ return;
240
+ }
241
+ this.state = "connecting";
242
+ try {
243
+ if (!this.isExternalServer) {
244
+ await this.startCLIServer();
245
+ }
246
+ await this.connectToServer();
247
+ await this.verifyProtocolVersion();
248
+ this.state = "connected";
249
+ } catch (error) {
250
+ this.state = "error";
251
+ throw error;
252
+ }
253
+ }
254
+ /**
255
+ * Stops the CLI server and closes all active sessions.
256
+ *
257
+ * This method performs graceful cleanup:
258
+ * 1. Closes all active sessions (releases in-memory resources)
259
+ * 2. Closes the JSON-RPC connection
260
+ * 3. Terminates the CLI server process (if spawned by this client)
261
+ *
262
+ * Note: session data on disk is preserved, so sessions can be resumed later.
263
+ * To permanently remove session data before stopping, call
264
+ * {@link deleteSession} for each session first.
265
+ *
266
+ * @returns A promise that resolves with an array of errors encountered during cleanup.
267
+ * An empty array indicates all cleanup succeeded.
268
+ *
269
+ * @example
270
+ * ```typescript
271
+ * const errors = await client.stop();
272
+ * if (errors.length > 0) {
273
+ * console.error("Cleanup errors:", errors);
274
+ * }
275
+ * ```
276
+ */
277
+ async stop() {
278
+ const errors = [];
279
+ for (const session of this.sessions.values()) {
280
+ const sessionId = session.sessionId;
281
+ let lastError = null;
282
+ for (let attempt = 1; attempt <= 3; attempt++) {
283
+ try {
284
+ await session.disconnect();
285
+ lastError = null;
286
+ break;
287
+ } catch (error) {
288
+ lastError = error instanceof Error ? error : new Error(String(error));
289
+ if (attempt < 3) {
290
+ const delay = 100 * Math.pow(2, attempt - 1);
291
+ await new Promise((resolve) => setTimeout(resolve, delay));
292
+ }
293
+ }
294
+ }
295
+ if (lastError) {
296
+ errors.push(
297
+ new Error(
298
+ `Failed to disconnect session ${sessionId} after 3 attempts: ${lastError.message}`
299
+ )
300
+ );
301
+ }
302
+ }
303
+ this.sessions.clear();
304
+ if (this.connection) {
305
+ try {
306
+ this.connection.dispose();
307
+ } catch (error) {
308
+ errors.push(
309
+ new Error(
310
+ `Failed to dispose connection: ${error instanceof Error ? error.message : String(error)}`
311
+ )
312
+ );
313
+ }
314
+ this.connection = null;
315
+ this._rpc = null;
316
+ }
317
+ this.modelsCache = null;
318
+ if (this.socket) {
319
+ try {
320
+ this.socket.end();
321
+ } catch (error) {
322
+ errors.push(
323
+ new Error(
324
+ `Failed to close socket: ${error instanceof Error ? error.message : String(error)}`
325
+ )
326
+ );
327
+ }
328
+ this.socket = null;
329
+ }
330
+ if (this.cliProcess && !this.isExternalServer) {
331
+ try {
332
+ this.cliProcess.kill();
333
+ } catch (error) {
334
+ errors.push(
335
+ new Error(
336
+ `Failed to kill CLI process: ${error instanceof Error ? error.message : String(error)}`
337
+ )
338
+ );
339
+ }
340
+ this.cliProcess = null;
341
+ }
342
+ this.state = "disconnected";
343
+ this.actualPort = null;
344
+ this.stderrBuffer = "";
345
+ this.processExitPromise = null;
346
+ return errors;
347
+ }
348
+ /**
349
+ * Forcefully stops the CLI server without graceful cleanup.
350
+ *
351
+ * Use this when {@link stop} fails or takes too long. This method:
352
+ * - Clears all sessions immediately without destroying them
353
+ * - Force closes the connection
354
+ * - Sends SIGKILL to the CLI process (if spawned by this client)
355
+ *
356
+ * @returns A promise that resolves when the force stop is complete
357
+ *
358
+ * @example
359
+ * ```typescript
360
+ * // If normal stop hangs, force stop
361
+ * const stopPromise = client.stop();
362
+ * const timeout = new Promise((_, reject) =>
363
+ * setTimeout(() => reject(new Error("Timeout")), 5000)
364
+ * );
365
+ *
366
+ * try {
367
+ * await Promise.race([stopPromise, timeout]);
368
+ * } catch {
369
+ * await client.forceStop();
370
+ * }
371
+ * ```
372
+ */
373
+ async forceStop() {
374
+ this.forceStopping = true;
375
+ this.sessions.clear();
376
+ if (this.connection) {
377
+ try {
378
+ this.connection.dispose();
379
+ } catch {
380
+ }
381
+ this.connection = null;
382
+ this._rpc = null;
383
+ }
384
+ this.modelsCache = null;
385
+ if (this.socket) {
386
+ try {
387
+ this.socket.destroy();
388
+ } catch {
389
+ }
390
+ this.socket = null;
391
+ }
392
+ if (this.cliProcess && !this.isExternalServer) {
393
+ try {
394
+ this.cliProcess.kill("SIGKILL");
395
+ } catch {
396
+ }
397
+ this.cliProcess = null;
398
+ }
399
+ this.state = "disconnected";
400
+ this.actualPort = null;
401
+ this.stderrBuffer = "";
402
+ this.processExitPromise = null;
403
+ }
404
+ /**
405
+ * Creates a new conversation session with the Copilot CLI.
406
+ *
407
+ * Sessions maintain conversation state, handle events, and manage tool execution.
408
+ * If the client is not connected and `autoStart` is enabled, this will automatically
409
+ * start the connection.
410
+ *
411
+ * @param config - Optional configuration for the session
412
+ * @returns A promise that resolves with the created session
413
+ * @throws Error if the client is not connected and autoStart is disabled
414
+ *
415
+ * @example
416
+ * ```typescript
417
+ * // Basic session
418
+ * const session = await client.createSession({ onPermissionRequest: approveAll });
419
+ *
420
+ * // Session with model and tools
421
+ * const session = await client.createSession({
422
+ * onPermissionRequest: approveAll,
423
+ * model: "gpt-4",
424
+ * tools: [{
425
+ * name: "get_weather",
426
+ * description: "Get weather for a location",
427
+ * parameters: { type: "object", properties: { location: { type: "string" } } },
428
+ * handler: async (args) => ({ temperature: 72 })
429
+ * }]
430
+ * });
431
+ * ```
432
+ */
433
+ async createSession(config) {
434
+ if (!config?.onPermissionRequest) {
435
+ throw new Error(
436
+ "An onPermissionRequest handler is required when creating a session. For example, to allow all permissions, use { onPermissionRequest: approveAll }."
437
+ );
438
+ }
439
+ if (!this.connection) {
440
+ if (this.options.autoStart) {
441
+ await this.start();
442
+ } else {
443
+ throw new Error("Client not connected. Call start() first.");
444
+ }
445
+ }
446
+ const sessionId = config.sessionId ?? (0, import_node_crypto.randomUUID)();
447
+ const session = new import_session.CopilotSession(
448
+ sessionId,
449
+ this.connection,
450
+ void 0,
451
+ this.onGetTraceContext
452
+ );
453
+ session.registerTools(config.tools);
454
+ session.registerPermissionHandler(config.onPermissionRequest);
455
+ if (config.onUserInputRequest) {
456
+ session.registerUserInputHandler(config.onUserInputRequest);
457
+ }
458
+ if (config.hooks) {
459
+ session.registerHooks(config.hooks);
460
+ }
461
+ const { wirePayload: wireSystemMessage, transformCallbacks } = extractTransformCallbacks(
462
+ config.systemMessage
463
+ );
464
+ if (transformCallbacks) {
465
+ session.registerTransformCallbacks(transformCallbacks);
466
+ }
467
+ if (config.onEvent) {
468
+ session.on(config.onEvent);
469
+ }
470
+ this.sessions.set(sessionId, session);
471
+ try {
472
+ const response = await this.connection.sendRequest("session.create", {
473
+ ...await (0, import_telemetry.getTraceContext)(this.onGetTraceContext),
474
+ model: config.model,
475
+ sessionId,
476
+ clientName: config.clientName,
477
+ reasoningEffort: config.reasoningEffort,
478
+ tools: config.tools?.map((tool) => ({
479
+ name: tool.name,
480
+ description: tool.description,
481
+ parameters: toJsonSchema(tool.parameters),
482
+ overridesBuiltInTool: tool.overridesBuiltInTool,
483
+ skipPermission: tool.skipPermission
484
+ })),
485
+ systemMessage: wireSystemMessage,
486
+ availableTools: config.availableTools,
487
+ excludedTools: config.excludedTools,
488
+ provider: config.provider,
489
+ requestPermission: true,
490
+ requestUserInput: !!config.onUserInputRequest,
491
+ hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
492
+ workingDirectory: config.workingDirectory,
493
+ streaming: config.streaming,
494
+ mcpServers: config.mcpServers,
495
+ envValueMode: "direct",
496
+ customAgents: config.customAgents,
497
+ agent: config.agent,
498
+ configDir: config.configDir,
499
+ skillDirectories: config.skillDirectories,
500
+ disabledSkills: config.disabledSkills,
501
+ infiniteSessions: config.infiniteSessions
502
+ });
503
+ const { workspacePath } = response;
504
+ session["_workspacePath"] = workspacePath;
505
+ } catch (e) {
506
+ this.sessions.delete(sessionId);
507
+ throw e;
508
+ }
509
+ return session;
510
+ }
511
+ /**
512
+ * Resumes an existing conversation session by its ID.
513
+ *
514
+ * This allows you to continue a previous conversation, maintaining all
515
+ * conversation history. The session must have been previously created
516
+ * and not deleted.
517
+ *
518
+ * @param sessionId - The ID of the session to resume
519
+ * @param config - Optional configuration for the resumed session
520
+ * @returns A promise that resolves with the resumed session
521
+ * @throws Error if the session does not exist or the client is not connected
522
+ *
523
+ * @example
524
+ * ```typescript
525
+ * // Resume a previous session
526
+ * const session = await client.resumeSession("session-123", { onPermissionRequest: approveAll });
527
+ *
528
+ * // Resume with new tools
529
+ * const session = await client.resumeSession("session-123", {
530
+ * onPermissionRequest: approveAll,
531
+ * tools: [myNewTool]
532
+ * });
533
+ * ```
534
+ */
535
+ async resumeSession(sessionId, config) {
536
+ if (!config?.onPermissionRequest) {
537
+ throw new Error(
538
+ "An onPermissionRequest handler is required when resuming a session. For example, to allow all permissions, use { onPermissionRequest: approveAll }."
539
+ );
540
+ }
541
+ if (!this.connection) {
542
+ if (this.options.autoStart) {
543
+ await this.start();
544
+ } else {
545
+ throw new Error("Client not connected. Call start() first.");
546
+ }
547
+ }
548
+ const session = new import_session.CopilotSession(
549
+ sessionId,
550
+ this.connection,
551
+ void 0,
552
+ this.onGetTraceContext
553
+ );
554
+ session.registerTools(config.tools);
555
+ session.registerPermissionHandler(config.onPermissionRequest);
556
+ if (config.onUserInputRequest) {
557
+ session.registerUserInputHandler(config.onUserInputRequest);
558
+ }
559
+ if (config.hooks) {
560
+ session.registerHooks(config.hooks);
561
+ }
562
+ const { wirePayload: wireSystemMessage, transformCallbacks } = extractTransformCallbacks(
563
+ config.systemMessage
564
+ );
565
+ if (transformCallbacks) {
566
+ session.registerTransformCallbacks(transformCallbacks);
567
+ }
568
+ if (config.onEvent) {
569
+ session.on(config.onEvent);
570
+ }
571
+ this.sessions.set(sessionId, session);
572
+ try {
573
+ const response = await this.connection.sendRequest("session.resume", {
574
+ ...await (0, import_telemetry.getTraceContext)(this.onGetTraceContext),
575
+ sessionId,
576
+ clientName: config.clientName,
577
+ model: config.model,
578
+ reasoningEffort: config.reasoningEffort,
579
+ systemMessage: wireSystemMessage,
580
+ availableTools: config.availableTools,
581
+ excludedTools: config.excludedTools,
582
+ tools: config.tools?.map((tool) => ({
583
+ name: tool.name,
584
+ description: tool.description,
585
+ parameters: toJsonSchema(tool.parameters),
586
+ overridesBuiltInTool: tool.overridesBuiltInTool,
587
+ skipPermission: tool.skipPermission
588
+ })),
589
+ provider: config.provider,
590
+ requestPermission: true,
591
+ requestUserInput: !!config.onUserInputRequest,
592
+ hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
593
+ workingDirectory: config.workingDirectory,
594
+ configDir: config.configDir,
595
+ streaming: config.streaming,
596
+ mcpServers: config.mcpServers,
597
+ envValueMode: "direct",
598
+ customAgents: config.customAgents,
599
+ agent: config.agent,
600
+ skillDirectories: config.skillDirectories,
601
+ disabledSkills: config.disabledSkills,
602
+ infiniteSessions: config.infiniteSessions,
603
+ disableResume: config.disableResume
604
+ });
605
+ const { workspacePath } = response;
606
+ session["_workspacePath"] = workspacePath;
607
+ } catch (e) {
608
+ this.sessions.delete(sessionId);
609
+ throw e;
610
+ }
611
+ return session;
612
+ }
613
+ /**
614
+ * Gets the current connection state of the client.
615
+ *
616
+ * @returns The current connection state: "disconnected", "connecting", "connected", or "error"
617
+ *
618
+ * @example
619
+ * ```typescript
620
+ * if (client.getState() === "connected") {
621
+ * const session = await client.createSession({ onPermissionRequest: approveAll });
622
+ * }
623
+ * ```
624
+ */
625
+ getState() {
626
+ return this.state;
627
+ }
628
+ /**
629
+ * Sends a ping request to the server to verify connectivity.
630
+ *
631
+ * @param message - Optional message to include in the ping
632
+ * @returns A promise that resolves with the ping response containing the message and timestamp
633
+ * @throws Error if the client is not connected
634
+ *
635
+ * @example
636
+ * ```typescript
637
+ * const response = await client.ping("health check");
638
+ * console.log(`Server responded at ${new Date(response.timestamp)}`);
639
+ * ```
640
+ */
641
+ async ping(message) {
642
+ if (!this.connection) {
643
+ throw new Error("Client not connected");
644
+ }
645
+ const result = await this.connection.sendRequest("ping", { message });
646
+ return result;
647
+ }
648
+ /**
649
+ * Get CLI status including version and protocol information
650
+ */
651
+ async getStatus() {
652
+ if (!this.connection) {
653
+ throw new Error("Client not connected");
654
+ }
655
+ const result = await this.connection.sendRequest("status.get", {});
656
+ return result;
657
+ }
658
+ /**
659
+ * Get current authentication status
660
+ */
661
+ async getAuthStatus() {
662
+ if (!this.connection) {
663
+ throw new Error("Client not connected");
664
+ }
665
+ const result = await this.connection.sendRequest("auth.getStatus", {});
666
+ return result;
667
+ }
668
+ /**
669
+ * List available models with their metadata.
670
+ *
671
+ * If an `onListModels` handler was provided in the client options,
672
+ * it is called instead of querying the CLI server.
673
+ *
674
+ * Results are cached after the first successful call to avoid rate limiting.
675
+ * The cache is cleared when the client disconnects.
676
+ *
677
+ * @throws Error if not connected (when no custom handler is set)
678
+ */
679
+ async listModels() {
680
+ await this.modelsCacheLock;
681
+ let resolveLock;
682
+ this.modelsCacheLock = new Promise((resolve) => {
683
+ resolveLock = resolve;
684
+ });
685
+ try {
686
+ if (this.modelsCache !== null) {
687
+ return [...this.modelsCache];
688
+ }
689
+ let models;
690
+ if (this.onListModels) {
691
+ models = await this.onListModels();
692
+ } else {
693
+ if (!this.connection) {
694
+ throw new Error("Client not connected");
695
+ }
696
+ const result = await this.connection.sendRequest("models.list", {});
697
+ const response = result;
698
+ models = response.models;
699
+ }
700
+ this.modelsCache = [...models];
701
+ return [...models];
702
+ } finally {
703
+ resolveLock();
704
+ }
705
+ }
706
+ /**
707
+ * Verify that the server's protocol version is within the supported range
708
+ * and store the negotiated version.
709
+ */
710
+ async verifyProtocolVersion() {
711
+ const maxVersion = (0, import_sdkProtocolVersion.getSdkProtocolVersion)();
712
+ let pingResult;
713
+ if (this.processExitPromise) {
714
+ pingResult = await Promise.race([this.ping(), this.processExitPromise]);
715
+ } else {
716
+ pingResult = await this.ping();
717
+ }
718
+ const serverVersion = pingResult.protocolVersion;
719
+ if (serverVersion === void 0) {
720
+ throw new Error(
721
+ `SDK protocol version mismatch: SDK supports versions ${MIN_PROTOCOL_VERSION}-${maxVersion}, but server does not report a protocol version. Please update your server to ensure compatibility.`
722
+ );
723
+ }
724
+ if (serverVersion < MIN_PROTOCOL_VERSION || serverVersion > maxVersion) {
725
+ throw new Error(
726
+ `SDK protocol version mismatch: SDK supports versions ${MIN_PROTOCOL_VERSION}-${maxVersion}, but server reports version ${serverVersion}. Please update your SDK or server to ensure compatibility.`
727
+ );
728
+ }
729
+ this.negotiatedProtocolVersion = serverVersion;
730
+ }
731
+ /**
732
+ * Gets the ID of the most recently updated session.
733
+ *
734
+ * This is useful for resuming the last conversation when the session ID
735
+ * was not stored.
736
+ *
737
+ * @returns A promise that resolves with the session ID, or undefined if no sessions exist
738
+ * @throws Error if the client is not connected
739
+ *
740
+ * @example
741
+ * ```typescript
742
+ * const lastId = await client.getLastSessionId();
743
+ * if (lastId) {
744
+ * const session = await client.resumeSession(lastId, { onPermissionRequest: approveAll });
745
+ * }
746
+ * ```
747
+ */
748
+ async getLastSessionId() {
749
+ if (!this.connection) {
750
+ throw new Error("Client not connected");
751
+ }
752
+ const response = await this.connection.sendRequest("session.getLastId", {});
753
+ return response.sessionId;
754
+ }
755
+ /**
756
+ * Permanently deletes a session and all its data from disk, including
757
+ * conversation history, planning state, and artifacts.
758
+ *
759
+ * Unlike {@link CopilotSession.disconnect}, which only releases in-memory
760
+ * resources and preserves session data for later resumption, this method
761
+ * is irreversible. The session cannot be resumed after deletion.
762
+ *
763
+ * @param sessionId - The ID of the session to delete
764
+ * @returns A promise that resolves when the session is deleted
765
+ * @throws Error if the session does not exist or deletion fails
766
+ *
767
+ * @example
768
+ * ```typescript
769
+ * await client.deleteSession("session-123");
770
+ * ```
771
+ */
772
+ async deleteSession(sessionId) {
773
+ if (!this.connection) {
774
+ throw new Error("Client not connected");
775
+ }
776
+ const response = await this.connection.sendRequest("session.delete", {
777
+ sessionId
778
+ });
779
+ const { success, error } = response;
780
+ if (!success) {
781
+ throw new Error(`Failed to delete session ${sessionId}: ${error || "Unknown error"}`);
782
+ }
783
+ this.sessions.delete(sessionId);
784
+ }
785
+ /**
786
+ * List all available sessions.
787
+ *
788
+ * @param filter - Optional filter to limit returned sessions by context fields
789
+ *
790
+ * @example
791
+ * // List all sessions
792
+ * const sessions = await client.listSessions();
793
+ *
794
+ * @example
795
+ * // List sessions for a specific repository
796
+ * const sessions = await client.listSessions({ repository: "owner/repo" });
797
+ */
798
+ async listSessions(filter) {
799
+ if (!this.connection) {
800
+ throw new Error("Client not connected");
801
+ }
802
+ const response = await this.connection.sendRequest("session.list", { filter });
803
+ const { sessions } = response;
804
+ return sessions.map((s) => ({
805
+ sessionId: s.sessionId,
806
+ startTime: new Date(s.startTime),
807
+ modifiedTime: new Date(s.modifiedTime),
808
+ summary: s.summary,
809
+ isRemote: s.isRemote,
810
+ context: s.context
811
+ }));
812
+ }
813
+ /**
814
+ * Gets the foreground session ID in TUI+server mode.
815
+ *
816
+ * This returns the ID of the session currently displayed in the TUI.
817
+ * Only available when connecting to a server running in TUI+server mode (--ui-server).
818
+ *
819
+ * @returns A promise that resolves with the foreground session ID, or undefined if none
820
+ * @throws Error if the client is not connected
821
+ *
822
+ * @example
823
+ * ```typescript
824
+ * const sessionId = await client.getForegroundSessionId();
825
+ * if (sessionId) {
826
+ * console.log(`TUI is displaying session: ${sessionId}`);
827
+ * }
828
+ * ```
829
+ */
830
+ async getForegroundSessionId() {
831
+ if (!this.connection) {
832
+ throw new Error("Client not connected");
833
+ }
834
+ const response = await this.connection.sendRequest("session.getForeground", {});
835
+ return response.sessionId;
836
+ }
837
+ /**
838
+ * Sets the foreground session in TUI+server mode.
839
+ *
840
+ * This requests the TUI to switch to displaying the specified session.
841
+ * Only available when connecting to a server running in TUI+server mode (--ui-server).
842
+ *
843
+ * @param sessionId - The ID of the session to display in the TUI
844
+ * @returns A promise that resolves when the session is switched
845
+ * @throws Error if the client is not connected or if the operation fails
846
+ *
847
+ * @example
848
+ * ```typescript
849
+ * // Switch the TUI to display a specific session
850
+ * await client.setForegroundSessionId("session-123");
851
+ * ```
852
+ */
853
+ async setForegroundSessionId(sessionId) {
854
+ if (!this.connection) {
855
+ throw new Error("Client not connected");
856
+ }
857
+ const response = await this.connection.sendRequest("session.setForeground", { sessionId });
858
+ const result = response;
859
+ if (!result.success) {
860
+ throw new Error(result.error || "Failed to set foreground session");
861
+ }
862
+ }
863
+ on(eventTypeOrHandler, handler) {
864
+ if (typeof eventTypeOrHandler === "string" && handler) {
865
+ const eventType = eventTypeOrHandler;
866
+ if (!this.typedLifecycleHandlers.has(eventType)) {
867
+ this.typedLifecycleHandlers.set(eventType, /* @__PURE__ */ new Set());
868
+ }
869
+ const storedHandler = handler;
870
+ this.typedLifecycleHandlers.get(eventType).add(storedHandler);
871
+ return () => {
872
+ const handlers = this.typedLifecycleHandlers.get(eventType);
873
+ if (handlers) {
874
+ handlers.delete(storedHandler);
875
+ }
876
+ };
877
+ }
878
+ const wildcardHandler = eventTypeOrHandler;
879
+ this.sessionLifecycleHandlers.add(wildcardHandler);
880
+ return () => {
881
+ this.sessionLifecycleHandlers.delete(wildcardHandler);
882
+ };
883
+ }
884
+ /**
885
+ * Start the CLI server process
886
+ */
887
+ async startCLIServer() {
888
+ return new Promise((resolve, reject) => {
889
+ this.stderrBuffer = "";
890
+ const args = [
891
+ ...this.options.cliArgs,
892
+ "--headless",
893
+ "--no-auto-update",
894
+ "--log-level",
895
+ this.options.logLevel
896
+ ];
897
+ if (this.options.useStdio) {
898
+ args.push("--stdio");
899
+ } else if (this.options.port > 0) {
900
+ args.push("--port", this.options.port.toString());
901
+ }
902
+ if (this.options.githubToken) {
903
+ args.push("--auth-token-env", "COPILOT_SDK_AUTH_TOKEN");
904
+ }
905
+ if (!this.options.useLoggedInUser) {
906
+ args.push("--no-auto-login");
907
+ }
908
+ const envWithoutNodeDebug = { ...this.options.env };
909
+ delete envWithoutNodeDebug.NODE_DEBUG;
910
+ if (this.options.githubToken) {
911
+ envWithoutNodeDebug.COPILOT_SDK_AUTH_TOKEN = this.options.githubToken;
912
+ }
913
+ if (!this.options.cliPath) {
914
+ throw new Error(
915
+ "Path to Copilot CLI is required. Please provide it via the cliPath option, or use cliUrl to rely on a remote CLI."
916
+ );
917
+ }
918
+ if (this.options.telemetry) {
919
+ const t = this.options.telemetry;
920
+ envWithoutNodeDebug.COPILOT_OTEL_ENABLED = "true";
921
+ if (t.otlpEndpoint !== void 0)
922
+ envWithoutNodeDebug.OTEL_EXPORTER_OTLP_ENDPOINT = t.otlpEndpoint;
923
+ if (t.filePath !== void 0)
924
+ envWithoutNodeDebug.COPILOT_OTEL_FILE_EXPORTER_PATH = t.filePath;
925
+ if (t.exporterType !== void 0)
926
+ envWithoutNodeDebug.COPILOT_OTEL_EXPORTER_TYPE = t.exporterType;
927
+ if (t.sourceName !== void 0)
928
+ envWithoutNodeDebug.COPILOT_OTEL_SOURCE_NAME = t.sourceName;
929
+ if (t.captureContent !== void 0)
930
+ envWithoutNodeDebug.OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT = String(
931
+ t.captureContent
932
+ );
933
+ }
934
+ if (!(0, import_node_fs.existsSync)(this.options.cliPath)) {
935
+ throw new Error(
936
+ `Copilot CLI not found at ${this.options.cliPath}. Ensure @github/copilot is installed.`
937
+ );
938
+ }
939
+ const stdioConfig = this.options.useStdio ? ["pipe", "pipe", "pipe"] : ["ignore", "pipe", "pipe"];
940
+ const isJsFile = this.options.cliPath.endsWith(".js");
941
+ if (isJsFile) {
942
+ this.cliProcess = (0, import_node_child_process.spawn)(getNodeExecPath(), [this.options.cliPath, ...args], {
943
+ stdio: stdioConfig,
944
+ cwd: this.options.cwd,
945
+ env: envWithoutNodeDebug,
946
+ windowsHide: true
947
+ });
948
+ } else {
949
+ this.cliProcess = (0, import_node_child_process.spawn)(this.options.cliPath, args, {
950
+ stdio: stdioConfig,
951
+ cwd: this.options.cwd,
952
+ env: envWithoutNodeDebug,
953
+ windowsHide: true
954
+ });
955
+ }
956
+ let stdout = "";
957
+ let resolved = false;
958
+ if (this.options.useStdio) {
959
+ resolved = true;
960
+ resolve();
961
+ } else {
962
+ this.cliProcess.stdout?.on("data", (data) => {
963
+ stdout += data.toString();
964
+ const match = stdout.match(/listening on port (\d+)/i);
965
+ if (match && !resolved) {
966
+ this.actualPort = parseInt(match[1], 10);
967
+ resolved = true;
968
+ resolve();
969
+ }
970
+ });
971
+ }
972
+ this.cliProcess.stderr?.on("data", (data) => {
973
+ this.stderrBuffer += data.toString();
974
+ const lines = data.toString().split("\n");
975
+ for (const line of lines) {
976
+ if (line.trim()) {
977
+ process.stderr.write(`[CLI subprocess] ${line}
978
+ `);
979
+ }
980
+ }
981
+ });
982
+ this.cliProcess.on("error", (error) => {
983
+ if (!resolved) {
984
+ resolved = true;
985
+ const stderrOutput = this.stderrBuffer.trim();
986
+ if (stderrOutput) {
987
+ reject(
988
+ new Error(
989
+ `Failed to start CLI server: ${error.message}
990
+ stderr: ${stderrOutput}`
991
+ )
992
+ );
993
+ } else {
994
+ reject(new Error(`Failed to start CLI server: ${error.message}`));
995
+ }
996
+ }
997
+ });
998
+ this.processExitPromise = new Promise((_, rejectProcessExit) => {
999
+ this.cliProcess.on("exit", (code) => {
1000
+ setTimeout(() => {
1001
+ const stderrOutput = this.stderrBuffer.trim();
1002
+ if (stderrOutput) {
1003
+ rejectProcessExit(
1004
+ new Error(
1005
+ `CLI server exited with code ${code}
1006
+ stderr: ${stderrOutput}`
1007
+ )
1008
+ );
1009
+ } else {
1010
+ rejectProcessExit(
1011
+ new Error(`CLI server exited unexpectedly with code ${code}`)
1012
+ );
1013
+ }
1014
+ }, 50);
1015
+ });
1016
+ });
1017
+ this.processExitPromise.catch(() => {
1018
+ });
1019
+ this.cliProcess.on("exit", (code) => {
1020
+ if (!resolved) {
1021
+ resolved = true;
1022
+ const stderrOutput = this.stderrBuffer.trim();
1023
+ if (stderrOutput) {
1024
+ reject(
1025
+ new Error(
1026
+ `CLI server exited with code ${code}
1027
+ stderr: ${stderrOutput}`
1028
+ )
1029
+ );
1030
+ } else {
1031
+ reject(new Error(`CLI server exited with code ${code}`));
1032
+ }
1033
+ }
1034
+ });
1035
+ setTimeout(() => {
1036
+ if (!resolved) {
1037
+ resolved = true;
1038
+ reject(new Error("Timeout waiting for CLI server to start"));
1039
+ }
1040
+ }, 1e4);
1041
+ });
1042
+ }
1043
+ /**
1044
+ * Connect to the CLI server (via socket or stdio)
1045
+ */
1046
+ async connectToServer() {
1047
+ if (this.options.isChildProcess) {
1048
+ return this.connectToParentProcessViaStdio();
1049
+ } else if (this.options.useStdio) {
1050
+ return this.connectToChildProcessViaStdio();
1051
+ } else {
1052
+ return this.connectViaTcp();
1053
+ }
1054
+ }
1055
+ /**
1056
+ * Connect to child via stdio pipes
1057
+ */
1058
+ async connectToChildProcessViaStdio() {
1059
+ if (!this.cliProcess) {
1060
+ throw new Error("CLI process not started");
1061
+ }
1062
+ this.cliProcess.stdin?.on("error", (err) => {
1063
+ if (!this.forceStopping) {
1064
+ throw err;
1065
+ }
1066
+ });
1067
+ this.connection = (0, import_node.createMessageConnection)(
1068
+ new import_node.StreamMessageReader(this.cliProcess.stdout),
1069
+ new import_node.StreamMessageWriter(this.cliProcess.stdin)
1070
+ );
1071
+ this.attachConnectionHandlers();
1072
+ this.connection.listen();
1073
+ }
1074
+ /**
1075
+ * Connect to parent via stdio pipes
1076
+ */
1077
+ async connectToParentProcessViaStdio() {
1078
+ if (this.cliProcess) {
1079
+ throw new Error("CLI child process was unexpectedly started in parent process mode");
1080
+ }
1081
+ this.connection = (0, import_node.createMessageConnection)(
1082
+ new import_node.StreamMessageReader(process.stdin),
1083
+ new import_node.StreamMessageWriter(process.stdout)
1084
+ );
1085
+ this.attachConnectionHandlers();
1086
+ this.connection.listen();
1087
+ }
1088
+ /**
1089
+ * Connect to the CLI server via TCP socket
1090
+ */
1091
+ async connectViaTcp() {
1092
+ if (!this.actualPort) {
1093
+ throw new Error("Server port not available");
1094
+ }
1095
+ return new Promise((resolve, reject) => {
1096
+ this.socket = new import_node_net.Socket();
1097
+ this.socket.connect(this.actualPort, this.actualHost, () => {
1098
+ this.connection = (0, import_node.createMessageConnection)(
1099
+ new import_node.StreamMessageReader(this.socket),
1100
+ new import_node.StreamMessageWriter(this.socket)
1101
+ );
1102
+ this.attachConnectionHandlers();
1103
+ this.connection.listen();
1104
+ resolve();
1105
+ });
1106
+ this.socket.on("error", (error) => {
1107
+ reject(new Error(`Failed to connect to CLI server: ${error.message}`));
1108
+ });
1109
+ });
1110
+ }
1111
+ attachConnectionHandlers() {
1112
+ if (!this.connection) {
1113
+ return;
1114
+ }
1115
+ this.connection.onNotification("session.event", (notification) => {
1116
+ this.handleSessionEventNotification(notification);
1117
+ });
1118
+ this.connection.onNotification("session.lifecycle", (notification) => {
1119
+ this.handleSessionLifecycleNotification(notification);
1120
+ });
1121
+ this.connection.onRequest(
1122
+ "tool.call",
1123
+ async (params) => await this.handleToolCallRequestV2(params)
1124
+ );
1125
+ this.connection.onRequest(
1126
+ "permission.request",
1127
+ async (params) => await this.handlePermissionRequestV2(params)
1128
+ );
1129
+ this.connection.onRequest(
1130
+ "userInput.request",
1131
+ async (params) => await this.handleUserInputRequest(params)
1132
+ );
1133
+ this.connection.onRequest(
1134
+ "hooks.invoke",
1135
+ async (params) => await this.handleHooksInvoke(params)
1136
+ );
1137
+ this.connection.onRequest(
1138
+ "systemMessage.transform",
1139
+ async (params) => await this.handleSystemMessageTransform(params)
1140
+ );
1141
+ this.connection.onClose(() => {
1142
+ this.state = "disconnected";
1143
+ });
1144
+ this.connection.onError((_error) => {
1145
+ this.state = "disconnected";
1146
+ });
1147
+ }
1148
+ handleSessionEventNotification(notification) {
1149
+ if (typeof notification !== "object" || !notification || !("sessionId" in notification) || typeof notification.sessionId !== "string" || !("event" in notification)) {
1150
+ return;
1151
+ }
1152
+ const session = this.sessions.get(notification.sessionId);
1153
+ if (session) {
1154
+ session._dispatchEvent(notification.event);
1155
+ }
1156
+ }
1157
+ handleSessionLifecycleNotification(notification) {
1158
+ if (typeof notification !== "object" || !notification || !("type" in notification) || typeof notification.type !== "string" || !("sessionId" in notification) || typeof notification.sessionId !== "string") {
1159
+ return;
1160
+ }
1161
+ const event = notification;
1162
+ const typedHandlers = this.typedLifecycleHandlers.get(event.type);
1163
+ if (typedHandlers) {
1164
+ for (const handler of typedHandlers) {
1165
+ try {
1166
+ handler(event);
1167
+ } catch {
1168
+ }
1169
+ }
1170
+ }
1171
+ for (const handler of this.sessionLifecycleHandlers) {
1172
+ try {
1173
+ handler(event);
1174
+ } catch {
1175
+ }
1176
+ }
1177
+ }
1178
+ async handleUserInputRequest(params) {
1179
+ if (!params || typeof params.sessionId !== "string" || typeof params.question !== "string") {
1180
+ throw new Error("Invalid user input request payload");
1181
+ }
1182
+ const session = this.sessions.get(params.sessionId);
1183
+ if (!session) {
1184
+ throw new Error(`Session not found: ${params.sessionId}`);
1185
+ }
1186
+ const result = await session._handleUserInputRequest({
1187
+ question: params.question,
1188
+ choices: params.choices,
1189
+ allowFreeform: params.allowFreeform
1190
+ });
1191
+ return result;
1192
+ }
1193
+ async handleHooksInvoke(params) {
1194
+ if (!params || typeof params.sessionId !== "string" || typeof params.hookType !== "string") {
1195
+ throw new Error("Invalid hooks invoke payload");
1196
+ }
1197
+ const session = this.sessions.get(params.sessionId);
1198
+ if (!session) {
1199
+ throw new Error(`Session not found: ${params.sessionId}`);
1200
+ }
1201
+ const output = await session._handleHooksInvoke(params.hookType, params.input);
1202
+ return { output };
1203
+ }
1204
+ async handleSystemMessageTransform(params) {
1205
+ if (!params || typeof params.sessionId !== "string" || !params.sections || typeof params.sections !== "object") {
1206
+ throw new Error("Invalid systemMessage.transform payload");
1207
+ }
1208
+ const session = this.sessions.get(params.sessionId);
1209
+ if (!session) {
1210
+ throw new Error(`Session not found: ${params.sessionId}`);
1211
+ }
1212
+ return await session._handleSystemMessageTransform(params.sections);
1213
+ }
1214
+ // ========================================================================
1215
+ // Protocol v2 backward-compatibility adapters
1216
+ // ========================================================================
1217
+ /**
1218
+ * Handles a v2-style tool.call RPC request from the server.
1219
+ * Looks up the session and tool handler, executes it, and returns the result
1220
+ * in the v2 response format.
1221
+ */
1222
+ async handleToolCallRequestV2(params) {
1223
+ if (!params || typeof params.sessionId !== "string" || typeof params.toolCallId !== "string" || typeof params.toolName !== "string") {
1224
+ throw new Error("Invalid tool call payload");
1225
+ }
1226
+ const session = this.sessions.get(params.sessionId);
1227
+ if (!session) {
1228
+ throw new Error(`Unknown session ${params.sessionId}`);
1229
+ }
1230
+ const handler = session.getToolHandler(params.toolName);
1231
+ if (!handler) {
1232
+ return {
1233
+ result: {
1234
+ textResultForLlm: `Tool '${params.toolName}' is not supported by this client instance.`,
1235
+ resultType: "failure",
1236
+ error: `tool '${params.toolName}' not supported`,
1237
+ toolTelemetry: {}
1238
+ }
1239
+ };
1240
+ }
1241
+ try {
1242
+ const traceparent = params.traceparent;
1243
+ const tracestate = params.tracestate;
1244
+ const invocation = {
1245
+ sessionId: params.sessionId,
1246
+ toolCallId: params.toolCallId,
1247
+ toolName: params.toolName,
1248
+ arguments: params.arguments,
1249
+ traceparent,
1250
+ tracestate
1251
+ };
1252
+ const result = await handler(params.arguments, invocation);
1253
+ return { result: this.normalizeToolResultV2(result) };
1254
+ } catch (error) {
1255
+ const message = error instanceof Error ? error.message : String(error);
1256
+ return {
1257
+ result: {
1258
+ textResultForLlm: "Invoking this tool produced an error. Detailed information is not available.",
1259
+ resultType: "failure",
1260
+ error: message,
1261
+ toolTelemetry: {}
1262
+ }
1263
+ };
1264
+ }
1265
+ }
1266
+ /**
1267
+ * Handles a v2-style permission.request RPC request from the server.
1268
+ */
1269
+ async handlePermissionRequestV2(params) {
1270
+ if (!params || typeof params.sessionId !== "string" || !params.permissionRequest) {
1271
+ throw new Error("Invalid permission request payload");
1272
+ }
1273
+ const session = this.sessions.get(params.sessionId);
1274
+ if (!session) {
1275
+ throw new Error(`Session not found: ${params.sessionId}`);
1276
+ }
1277
+ try {
1278
+ const result = await session._handlePermissionRequestV2(params.permissionRequest);
1279
+ return { result };
1280
+ } catch (error) {
1281
+ if (error instanceof Error && error.message === import_session.NO_RESULT_PERMISSION_V2_ERROR) {
1282
+ throw error;
1283
+ }
1284
+ return {
1285
+ result: {
1286
+ kind: "denied-no-approval-rule-and-could-not-request-from-user"
1287
+ }
1288
+ };
1289
+ }
1290
+ }
1291
+ normalizeToolResultV2(result) {
1292
+ if (result === void 0 || result === null) {
1293
+ return {
1294
+ textResultForLlm: "Tool returned no result",
1295
+ resultType: "failure",
1296
+ error: "tool returned no result",
1297
+ toolTelemetry: {}
1298
+ };
1299
+ }
1300
+ if (this.isToolResultObject(result)) {
1301
+ return result;
1302
+ }
1303
+ const textResult = typeof result === "string" ? result : JSON.stringify(result);
1304
+ return {
1305
+ textResultForLlm: textResult,
1306
+ resultType: "success",
1307
+ toolTelemetry: {}
1308
+ };
1309
+ }
1310
+ isToolResultObject(value) {
1311
+ return typeof value === "object" && value !== null && "textResultForLlm" in value && typeof value.textResultForLlm === "string" && "resultType" in value;
1312
+ }
1313
+ }
1314
+ // Annotate the CommonJS export names for ESM import in node:
1315
+ 0 && (module.exports = {
1316
+ CopilotClient
1317
+ });