@github/copilot-sdk 0.1.33-unstable.1 → 0.2.1-preview.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1329 @@
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.registerCommands(config.commands);
455
+ session.registerPermissionHandler(config.onPermissionRequest);
456
+ if (config.onUserInputRequest) {
457
+ session.registerUserInputHandler(config.onUserInputRequest);
458
+ }
459
+ if (config.hooks) {
460
+ session.registerHooks(config.hooks);
461
+ }
462
+ const { wirePayload: wireSystemMessage, transformCallbacks } = extractTransformCallbacks(
463
+ config.systemMessage
464
+ );
465
+ if (transformCallbacks) {
466
+ session.registerTransformCallbacks(transformCallbacks);
467
+ }
468
+ if (config.onEvent) {
469
+ session.on(config.onEvent);
470
+ }
471
+ this.sessions.set(sessionId, session);
472
+ try {
473
+ const response = await this.connection.sendRequest("session.create", {
474
+ ...await (0, import_telemetry.getTraceContext)(this.onGetTraceContext),
475
+ model: config.model,
476
+ sessionId,
477
+ clientName: config.clientName,
478
+ reasoningEffort: config.reasoningEffort,
479
+ tools: config.tools?.map((tool) => ({
480
+ name: tool.name,
481
+ description: tool.description,
482
+ parameters: toJsonSchema(tool.parameters),
483
+ overridesBuiltInTool: tool.overridesBuiltInTool,
484
+ skipPermission: tool.skipPermission
485
+ })),
486
+ commands: config.commands?.map((cmd) => ({
487
+ name: cmd.name,
488
+ description: cmd.description
489
+ })),
490
+ systemMessage: wireSystemMessage,
491
+ availableTools: config.availableTools,
492
+ excludedTools: config.excludedTools,
493
+ provider: config.provider,
494
+ requestPermission: true,
495
+ requestUserInput: !!config.onUserInputRequest,
496
+ hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
497
+ workingDirectory: config.workingDirectory,
498
+ streaming: config.streaming,
499
+ mcpServers: config.mcpServers,
500
+ envValueMode: "direct",
501
+ customAgents: config.customAgents,
502
+ agent: config.agent,
503
+ configDir: config.configDir,
504
+ skillDirectories: config.skillDirectories,
505
+ disabledSkills: config.disabledSkills,
506
+ infiniteSessions: config.infiniteSessions
507
+ });
508
+ const { workspacePath, capabilities } = response;
509
+ session["_workspacePath"] = workspacePath;
510
+ session.setCapabilities(capabilities);
511
+ } catch (e) {
512
+ this.sessions.delete(sessionId);
513
+ throw e;
514
+ }
515
+ return session;
516
+ }
517
+ /**
518
+ * Resumes an existing conversation session by its ID.
519
+ *
520
+ * This allows you to continue a previous conversation, maintaining all
521
+ * conversation history. The session must have been previously created
522
+ * and not deleted.
523
+ *
524
+ * @param sessionId - The ID of the session to resume
525
+ * @param config - Optional configuration for the resumed session
526
+ * @returns A promise that resolves with the resumed session
527
+ * @throws Error if the session does not exist or the client is not connected
528
+ *
529
+ * @example
530
+ * ```typescript
531
+ * // Resume a previous session
532
+ * const session = await client.resumeSession("session-123", { onPermissionRequest: approveAll });
533
+ *
534
+ * // Resume with new tools
535
+ * const session = await client.resumeSession("session-123", {
536
+ * onPermissionRequest: approveAll,
537
+ * tools: [myNewTool]
538
+ * });
539
+ * ```
540
+ */
541
+ async resumeSession(sessionId, config) {
542
+ if (!config?.onPermissionRequest) {
543
+ throw new Error(
544
+ "An onPermissionRequest handler is required when resuming a session. For example, to allow all permissions, use { onPermissionRequest: approveAll }."
545
+ );
546
+ }
547
+ if (!this.connection) {
548
+ if (this.options.autoStart) {
549
+ await this.start();
550
+ } else {
551
+ throw new Error("Client not connected. Call start() first.");
552
+ }
553
+ }
554
+ const session = new import_session.CopilotSession(
555
+ sessionId,
556
+ this.connection,
557
+ void 0,
558
+ this.onGetTraceContext
559
+ );
560
+ session.registerTools(config.tools);
561
+ session.registerCommands(config.commands);
562
+ session.registerPermissionHandler(config.onPermissionRequest);
563
+ if (config.onUserInputRequest) {
564
+ session.registerUserInputHandler(config.onUserInputRequest);
565
+ }
566
+ if (config.hooks) {
567
+ session.registerHooks(config.hooks);
568
+ }
569
+ const { wirePayload: wireSystemMessage, transformCallbacks } = extractTransformCallbacks(
570
+ config.systemMessage
571
+ );
572
+ if (transformCallbacks) {
573
+ session.registerTransformCallbacks(transformCallbacks);
574
+ }
575
+ if (config.onEvent) {
576
+ session.on(config.onEvent);
577
+ }
578
+ this.sessions.set(sessionId, session);
579
+ try {
580
+ const response = await this.connection.sendRequest("session.resume", {
581
+ ...await (0, import_telemetry.getTraceContext)(this.onGetTraceContext),
582
+ sessionId,
583
+ clientName: config.clientName,
584
+ model: config.model,
585
+ reasoningEffort: config.reasoningEffort,
586
+ systemMessage: wireSystemMessage,
587
+ availableTools: config.availableTools,
588
+ excludedTools: config.excludedTools,
589
+ tools: config.tools?.map((tool) => ({
590
+ name: tool.name,
591
+ description: tool.description,
592
+ parameters: toJsonSchema(tool.parameters),
593
+ overridesBuiltInTool: tool.overridesBuiltInTool,
594
+ skipPermission: tool.skipPermission
595
+ })),
596
+ commands: config.commands?.map((cmd) => ({
597
+ name: cmd.name,
598
+ description: cmd.description
599
+ })),
600
+ provider: config.provider,
601
+ requestPermission: true,
602
+ requestUserInput: !!config.onUserInputRequest,
603
+ hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
604
+ workingDirectory: config.workingDirectory,
605
+ configDir: config.configDir,
606
+ streaming: config.streaming,
607
+ mcpServers: config.mcpServers,
608
+ envValueMode: "direct",
609
+ customAgents: config.customAgents,
610
+ agent: config.agent,
611
+ skillDirectories: config.skillDirectories,
612
+ disabledSkills: config.disabledSkills,
613
+ infiniteSessions: config.infiniteSessions,
614
+ disableResume: config.disableResume
615
+ });
616
+ const { workspacePath, capabilities } = response;
617
+ session["_workspacePath"] = workspacePath;
618
+ session.setCapabilities(capabilities);
619
+ } catch (e) {
620
+ this.sessions.delete(sessionId);
621
+ throw e;
622
+ }
623
+ return session;
624
+ }
625
+ /**
626
+ * Gets the current connection state of the client.
627
+ *
628
+ * @returns The current connection state: "disconnected", "connecting", "connected", or "error"
629
+ *
630
+ * @example
631
+ * ```typescript
632
+ * if (client.getState() === "connected") {
633
+ * const session = await client.createSession({ onPermissionRequest: approveAll });
634
+ * }
635
+ * ```
636
+ */
637
+ getState() {
638
+ return this.state;
639
+ }
640
+ /**
641
+ * Sends a ping request to the server to verify connectivity.
642
+ *
643
+ * @param message - Optional message to include in the ping
644
+ * @returns A promise that resolves with the ping response containing the message and timestamp
645
+ * @throws Error if the client is not connected
646
+ *
647
+ * @example
648
+ * ```typescript
649
+ * const response = await client.ping("health check");
650
+ * console.log(`Server responded at ${new Date(response.timestamp)}`);
651
+ * ```
652
+ */
653
+ async ping(message) {
654
+ if (!this.connection) {
655
+ throw new Error("Client not connected");
656
+ }
657
+ const result = await this.connection.sendRequest("ping", { message });
658
+ return result;
659
+ }
660
+ /**
661
+ * Get CLI status including version and protocol information
662
+ */
663
+ async getStatus() {
664
+ if (!this.connection) {
665
+ throw new Error("Client not connected");
666
+ }
667
+ const result = await this.connection.sendRequest("status.get", {});
668
+ return result;
669
+ }
670
+ /**
671
+ * Get current authentication status
672
+ */
673
+ async getAuthStatus() {
674
+ if (!this.connection) {
675
+ throw new Error("Client not connected");
676
+ }
677
+ const result = await this.connection.sendRequest("auth.getStatus", {});
678
+ return result;
679
+ }
680
+ /**
681
+ * List available models with their metadata.
682
+ *
683
+ * If an `onListModels` handler was provided in the client options,
684
+ * it is called instead of querying the CLI server.
685
+ *
686
+ * Results are cached after the first successful call to avoid rate limiting.
687
+ * The cache is cleared when the client disconnects.
688
+ *
689
+ * @throws Error if not connected (when no custom handler is set)
690
+ */
691
+ async listModels() {
692
+ await this.modelsCacheLock;
693
+ let resolveLock;
694
+ this.modelsCacheLock = new Promise((resolve) => {
695
+ resolveLock = resolve;
696
+ });
697
+ try {
698
+ if (this.modelsCache !== null) {
699
+ return [...this.modelsCache];
700
+ }
701
+ let models;
702
+ if (this.onListModels) {
703
+ models = await this.onListModels();
704
+ } else {
705
+ if (!this.connection) {
706
+ throw new Error("Client not connected");
707
+ }
708
+ const result = await this.connection.sendRequest("models.list", {});
709
+ const response = result;
710
+ models = response.models;
711
+ }
712
+ this.modelsCache = [...models];
713
+ return [...models];
714
+ } finally {
715
+ resolveLock();
716
+ }
717
+ }
718
+ /**
719
+ * Verify that the server's protocol version is within the supported range
720
+ * and store the negotiated version.
721
+ */
722
+ async verifyProtocolVersion() {
723
+ const maxVersion = (0, import_sdkProtocolVersion.getSdkProtocolVersion)();
724
+ let pingResult;
725
+ if (this.processExitPromise) {
726
+ pingResult = await Promise.race([this.ping(), this.processExitPromise]);
727
+ } else {
728
+ pingResult = await this.ping();
729
+ }
730
+ const serverVersion = pingResult.protocolVersion;
731
+ if (serverVersion === void 0) {
732
+ throw new Error(
733
+ `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.`
734
+ );
735
+ }
736
+ if (serverVersion < MIN_PROTOCOL_VERSION || serverVersion > maxVersion) {
737
+ throw new Error(
738
+ `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.`
739
+ );
740
+ }
741
+ this.negotiatedProtocolVersion = serverVersion;
742
+ }
743
+ /**
744
+ * Gets the ID of the most recently updated session.
745
+ *
746
+ * This is useful for resuming the last conversation when the session ID
747
+ * was not stored.
748
+ *
749
+ * @returns A promise that resolves with the session ID, or undefined if no sessions exist
750
+ * @throws Error if the client is not connected
751
+ *
752
+ * @example
753
+ * ```typescript
754
+ * const lastId = await client.getLastSessionId();
755
+ * if (lastId) {
756
+ * const session = await client.resumeSession(lastId, { onPermissionRequest: approveAll });
757
+ * }
758
+ * ```
759
+ */
760
+ async getLastSessionId() {
761
+ if (!this.connection) {
762
+ throw new Error("Client not connected");
763
+ }
764
+ const response = await this.connection.sendRequest("session.getLastId", {});
765
+ return response.sessionId;
766
+ }
767
+ /**
768
+ * Permanently deletes a session and all its data from disk, including
769
+ * conversation history, planning state, and artifacts.
770
+ *
771
+ * Unlike {@link CopilotSession.disconnect}, which only releases in-memory
772
+ * resources and preserves session data for later resumption, this method
773
+ * is irreversible. The session cannot be resumed after deletion.
774
+ *
775
+ * @param sessionId - The ID of the session to delete
776
+ * @returns A promise that resolves when the session is deleted
777
+ * @throws Error if the session does not exist or deletion fails
778
+ *
779
+ * @example
780
+ * ```typescript
781
+ * await client.deleteSession("session-123");
782
+ * ```
783
+ */
784
+ async deleteSession(sessionId) {
785
+ if (!this.connection) {
786
+ throw new Error("Client not connected");
787
+ }
788
+ const response = await this.connection.sendRequest("session.delete", {
789
+ sessionId
790
+ });
791
+ const { success, error } = response;
792
+ if (!success) {
793
+ throw new Error(`Failed to delete session ${sessionId}: ${error || "Unknown error"}`);
794
+ }
795
+ this.sessions.delete(sessionId);
796
+ }
797
+ /**
798
+ * List all available sessions.
799
+ *
800
+ * @param filter - Optional filter to limit returned sessions by context fields
801
+ *
802
+ * @example
803
+ * // List all sessions
804
+ * const sessions = await client.listSessions();
805
+ *
806
+ * @example
807
+ * // List sessions for a specific repository
808
+ * const sessions = await client.listSessions({ repository: "owner/repo" });
809
+ */
810
+ async listSessions(filter) {
811
+ if (!this.connection) {
812
+ throw new Error("Client not connected");
813
+ }
814
+ const response = await this.connection.sendRequest("session.list", { filter });
815
+ const { sessions } = response;
816
+ return sessions.map((s) => ({
817
+ sessionId: s.sessionId,
818
+ startTime: new Date(s.startTime),
819
+ modifiedTime: new Date(s.modifiedTime),
820
+ summary: s.summary,
821
+ isRemote: s.isRemote,
822
+ context: s.context
823
+ }));
824
+ }
825
+ /**
826
+ * Gets the foreground session ID in TUI+server mode.
827
+ *
828
+ * This returns the ID of the session currently displayed in the TUI.
829
+ * Only available when connecting to a server running in TUI+server mode (--ui-server).
830
+ *
831
+ * @returns A promise that resolves with the foreground session ID, or undefined if none
832
+ * @throws Error if the client is not connected
833
+ *
834
+ * @example
835
+ * ```typescript
836
+ * const sessionId = await client.getForegroundSessionId();
837
+ * if (sessionId) {
838
+ * console.log(`TUI is displaying session: ${sessionId}`);
839
+ * }
840
+ * ```
841
+ */
842
+ async getForegroundSessionId() {
843
+ if (!this.connection) {
844
+ throw new Error("Client not connected");
845
+ }
846
+ const response = await this.connection.sendRequest("session.getForeground", {});
847
+ return response.sessionId;
848
+ }
849
+ /**
850
+ * Sets the foreground session in TUI+server mode.
851
+ *
852
+ * This requests the TUI to switch to displaying the specified session.
853
+ * Only available when connecting to a server running in TUI+server mode (--ui-server).
854
+ *
855
+ * @param sessionId - The ID of the session to display in the TUI
856
+ * @returns A promise that resolves when the session is switched
857
+ * @throws Error if the client is not connected or if the operation fails
858
+ *
859
+ * @example
860
+ * ```typescript
861
+ * // Switch the TUI to display a specific session
862
+ * await client.setForegroundSessionId("session-123");
863
+ * ```
864
+ */
865
+ async setForegroundSessionId(sessionId) {
866
+ if (!this.connection) {
867
+ throw new Error("Client not connected");
868
+ }
869
+ const response = await this.connection.sendRequest("session.setForeground", { sessionId });
870
+ const result = response;
871
+ if (!result.success) {
872
+ throw new Error(result.error || "Failed to set foreground session");
873
+ }
874
+ }
875
+ on(eventTypeOrHandler, handler) {
876
+ if (typeof eventTypeOrHandler === "string" && handler) {
877
+ const eventType = eventTypeOrHandler;
878
+ if (!this.typedLifecycleHandlers.has(eventType)) {
879
+ this.typedLifecycleHandlers.set(eventType, /* @__PURE__ */ new Set());
880
+ }
881
+ const storedHandler = handler;
882
+ this.typedLifecycleHandlers.get(eventType).add(storedHandler);
883
+ return () => {
884
+ const handlers = this.typedLifecycleHandlers.get(eventType);
885
+ if (handlers) {
886
+ handlers.delete(storedHandler);
887
+ }
888
+ };
889
+ }
890
+ const wildcardHandler = eventTypeOrHandler;
891
+ this.sessionLifecycleHandlers.add(wildcardHandler);
892
+ return () => {
893
+ this.sessionLifecycleHandlers.delete(wildcardHandler);
894
+ };
895
+ }
896
+ /**
897
+ * Start the CLI server process
898
+ */
899
+ async startCLIServer() {
900
+ return new Promise((resolve, reject) => {
901
+ this.stderrBuffer = "";
902
+ const args = [
903
+ ...this.options.cliArgs,
904
+ "--headless",
905
+ "--no-auto-update",
906
+ "--log-level",
907
+ this.options.logLevel
908
+ ];
909
+ if (this.options.useStdio) {
910
+ args.push("--stdio");
911
+ } else if (this.options.port > 0) {
912
+ args.push("--port", this.options.port.toString());
913
+ }
914
+ if (this.options.githubToken) {
915
+ args.push("--auth-token-env", "COPILOT_SDK_AUTH_TOKEN");
916
+ }
917
+ if (!this.options.useLoggedInUser) {
918
+ args.push("--no-auto-login");
919
+ }
920
+ const envWithoutNodeDebug = { ...this.options.env };
921
+ delete envWithoutNodeDebug.NODE_DEBUG;
922
+ if (this.options.githubToken) {
923
+ envWithoutNodeDebug.COPILOT_SDK_AUTH_TOKEN = this.options.githubToken;
924
+ }
925
+ if (!this.options.cliPath) {
926
+ throw new Error(
927
+ "Path to Copilot CLI is required. Please provide it via the cliPath option, or use cliUrl to rely on a remote CLI."
928
+ );
929
+ }
930
+ if (this.options.telemetry) {
931
+ const t = this.options.telemetry;
932
+ envWithoutNodeDebug.COPILOT_OTEL_ENABLED = "true";
933
+ if (t.otlpEndpoint !== void 0)
934
+ envWithoutNodeDebug.OTEL_EXPORTER_OTLP_ENDPOINT = t.otlpEndpoint;
935
+ if (t.filePath !== void 0)
936
+ envWithoutNodeDebug.COPILOT_OTEL_FILE_EXPORTER_PATH = t.filePath;
937
+ if (t.exporterType !== void 0)
938
+ envWithoutNodeDebug.COPILOT_OTEL_EXPORTER_TYPE = t.exporterType;
939
+ if (t.sourceName !== void 0)
940
+ envWithoutNodeDebug.COPILOT_OTEL_SOURCE_NAME = t.sourceName;
941
+ if (t.captureContent !== void 0)
942
+ envWithoutNodeDebug.OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT = String(
943
+ t.captureContent
944
+ );
945
+ }
946
+ if (!(0, import_node_fs.existsSync)(this.options.cliPath)) {
947
+ throw new Error(
948
+ `Copilot CLI not found at ${this.options.cliPath}. Ensure @github/copilot is installed.`
949
+ );
950
+ }
951
+ const stdioConfig = this.options.useStdio ? ["pipe", "pipe", "pipe"] : ["ignore", "pipe", "pipe"];
952
+ const isJsFile = this.options.cliPath.endsWith(".js");
953
+ if (isJsFile) {
954
+ this.cliProcess = (0, import_node_child_process.spawn)(getNodeExecPath(), [this.options.cliPath, ...args], {
955
+ stdio: stdioConfig,
956
+ cwd: this.options.cwd,
957
+ env: envWithoutNodeDebug,
958
+ windowsHide: true
959
+ });
960
+ } else {
961
+ this.cliProcess = (0, import_node_child_process.spawn)(this.options.cliPath, args, {
962
+ stdio: stdioConfig,
963
+ cwd: this.options.cwd,
964
+ env: envWithoutNodeDebug,
965
+ windowsHide: true
966
+ });
967
+ }
968
+ let stdout = "";
969
+ let resolved = false;
970
+ if (this.options.useStdio) {
971
+ resolved = true;
972
+ resolve();
973
+ } else {
974
+ this.cliProcess.stdout?.on("data", (data) => {
975
+ stdout += data.toString();
976
+ const match = stdout.match(/listening on port (\d+)/i);
977
+ if (match && !resolved) {
978
+ this.actualPort = parseInt(match[1], 10);
979
+ resolved = true;
980
+ resolve();
981
+ }
982
+ });
983
+ }
984
+ this.cliProcess.stderr?.on("data", (data) => {
985
+ this.stderrBuffer += data.toString();
986
+ const lines = data.toString().split("\n");
987
+ for (const line of lines) {
988
+ if (line.trim()) {
989
+ process.stderr.write(`[CLI subprocess] ${line}
990
+ `);
991
+ }
992
+ }
993
+ });
994
+ this.cliProcess.on("error", (error) => {
995
+ if (!resolved) {
996
+ resolved = true;
997
+ const stderrOutput = this.stderrBuffer.trim();
998
+ if (stderrOutput) {
999
+ reject(
1000
+ new Error(
1001
+ `Failed to start CLI server: ${error.message}
1002
+ stderr: ${stderrOutput}`
1003
+ )
1004
+ );
1005
+ } else {
1006
+ reject(new Error(`Failed to start CLI server: ${error.message}`));
1007
+ }
1008
+ }
1009
+ });
1010
+ this.processExitPromise = new Promise((_, rejectProcessExit) => {
1011
+ this.cliProcess.on("exit", (code) => {
1012
+ setTimeout(() => {
1013
+ const stderrOutput = this.stderrBuffer.trim();
1014
+ if (stderrOutput) {
1015
+ rejectProcessExit(
1016
+ new Error(
1017
+ `CLI server exited with code ${code}
1018
+ stderr: ${stderrOutput}`
1019
+ )
1020
+ );
1021
+ } else {
1022
+ rejectProcessExit(
1023
+ new Error(`CLI server exited unexpectedly with code ${code}`)
1024
+ );
1025
+ }
1026
+ }, 50);
1027
+ });
1028
+ });
1029
+ this.processExitPromise.catch(() => {
1030
+ });
1031
+ this.cliProcess.on("exit", (code) => {
1032
+ if (!resolved) {
1033
+ resolved = true;
1034
+ const stderrOutput = this.stderrBuffer.trim();
1035
+ if (stderrOutput) {
1036
+ reject(
1037
+ new Error(
1038
+ `CLI server exited with code ${code}
1039
+ stderr: ${stderrOutput}`
1040
+ )
1041
+ );
1042
+ } else {
1043
+ reject(new Error(`CLI server exited with code ${code}`));
1044
+ }
1045
+ }
1046
+ });
1047
+ setTimeout(() => {
1048
+ if (!resolved) {
1049
+ resolved = true;
1050
+ reject(new Error("Timeout waiting for CLI server to start"));
1051
+ }
1052
+ }, 1e4);
1053
+ });
1054
+ }
1055
+ /**
1056
+ * Connect to the CLI server (via socket or stdio)
1057
+ */
1058
+ async connectToServer() {
1059
+ if (this.options.isChildProcess) {
1060
+ return this.connectToParentProcessViaStdio();
1061
+ } else if (this.options.useStdio) {
1062
+ return this.connectToChildProcessViaStdio();
1063
+ } else {
1064
+ return this.connectViaTcp();
1065
+ }
1066
+ }
1067
+ /**
1068
+ * Connect to child via stdio pipes
1069
+ */
1070
+ async connectToChildProcessViaStdio() {
1071
+ if (!this.cliProcess) {
1072
+ throw new Error("CLI process not started");
1073
+ }
1074
+ this.cliProcess.stdin?.on("error", (err) => {
1075
+ if (!this.forceStopping) {
1076
+ throw err;
1077
+ }
1078
+ });
1079
+ this.connection = (0, import_node.createMessageConnection)(
1080
+ new import_node.StreamMessageReader(this.cliProcess.stdout),
1081
+ new import_node.StreamMessageWriter(this.cliProcess.stdin)
1082
+ );
1083
+ this.attachConnectionHandlers();
1084
+ this.connection.listen();
1085
+ }
1086
+ /**
1087
+ * Connect to parent via stdio pipes
1088
+ */
1089
+ async connectToParentProcessViaStdio() {
1090
+ if (this.cliProcess) {
1091
+ throw new Error("CLI child process was unexpectedly started in parent process mode");
1092
+ }
1093
+ this.connection = (0, import_node.createMessageConnection)(
1094
+ new import_node.StreamMessageReader(process.stdin),
1095
+ new import_node.StreamMessageWriter(process.stdout)
1096
+ );
1097
+ this.attachConnectionHandlers();
1098
+ this.connection.listen();
1099
+ }
1100
+ /**
1101
+ * Connect to the CLI server via TCP socket
1102
+ */
1103
+ async connectViaTcp() {
1104
+ if (!this.actualPort) {
1105
+ throw new Error("Server port not available");
1106
+ }
1107
+ return new Promise((resolve, reject) => {
1108
+ this.socket = new import_node_net.Socket();
1109
+ this.socket.connect(this.actualPort, this.actualHost, () => {
1110
+ this.connection = (0, import_node.createMessageConnection)(
1111
+ new import_node.StreamMessageReader(this.socket),
1112
+ new import_node.StreamMessageWriter(this.socket)
1113
+ );
1114
+ this.attachConnectionHandlers();
1115
+ this.connection.listen();
1116
+ resolve();
1117
+ });
1118
+ this.socket.on("error", (error) => {
1119
+ reject(new Error(`Failed to connect to CLI server: ${error.message}`));
1120
+ });
1121
+ });
1122
+ }
1123
+ attachConnectionHandlers() {
1124
+ if (!this.connection) {
1125
+ return;
1126
+ }
1127
+ this.connection.onNotification("session.event", (notification) => {
1128
+ this.handleSessionEventNotification(notification);
1129
+ });
1130
+ this.connection.onNotification("session.lifecycle", (notification) => {
1131
+ this.handleSessionLifecycleNotification(notification);
1132
+ });
1133
+ this.connection.onRequest(
1134
+ "tool.call",
1135
+ async (params) => await this.handleToolCallRequestV2(params)
1136
+ );
1137
+ this.connection.onRequest(
1138
+ "permission.request",
1139
+ async (params) => await this.handlePermissionRequestV2(params)
1140
+ );
1141
+ this.connection.onRequest(
1142
+ "userInput.request",
1143
+ async (params) => await this.handleUserInputRequest(params)
1144
+ );
1145
+ this.connection.onRequest(
1146
+ "hooks.invoke",
1147
+ async (params) => await this.handleHooksInvoke(params)
1148
+ );
1149
+ this.connection.onRequest(
1150
+ "systemMessage.transform",
1151
+ async (params) => await this.handleSystemMessageTransform(params)
1152
+ );
1153
+ this.connection.onClose(() => {
1154
+ this.state = "disconnected";
1155
+ });
1156
+ this.connection.onError((_error) => {
1157
+ this.state = "disconnected";
1158
+ });
1159
+ }
1160
+ handleSessionEventNotification(notification) {
1161
+ if (typeof notification !== "object" || !notification || !("sessionId" in notification) || typeof notification.sessionId !== "string" || !("event" in notification)) {
1162
+ return;
1163
+ }
1164
+ const session = this.sessions.get(notification.sessionId);
1165
+ if (session) {
1166
+ session._dispatchEvent(notification.event);
1167
+ }
1168
+ }
1169
+ handleSessionLifecycleNotification(notification) {
1170
+ if (typeof notification !== "object" || !notification || !("type" in notification) || typeof notification.type !== "string" || !("sessionId" in notification) || typeof notification.sessionId !== "string") {
1171
+ return;
1172
+ }
1173
+ const event = notification;
1174
+ const typedHandlers = this.typedLifecycleHandlers.get(event.type);
1175
+ if (typedHandlers) {
1176
+ for (const handler of typedHandlers) {
1177
+ try {
1178
+ handler(event);
1179
+ } catch {
1180
+ }
1181
+ }
1182
+ }
1183
+ for (const handler of this.sessionLifecycleHandlers) {
1184
+ try {
1185
+ handler(event);
1186
+ } catch {
1187
+ }
1188
+ }
1189
+ }
1190
+ async handleUserInputRequest(params) {
1191
+ if (!params || typeof params.sessionId !== "string" || typeof params.question !== "string") {
1192
+ throw new Error("Invalid user input request payload");
1193
+ }
1194
+ const session = this.sessions.get(params.sessionId);
1195
+ if (!session) {
1196
+ throw new Error(`Session not found: ${params.sessionId}`);
1197
+ }
1198
+ const result = await session._handleUserInputRequest({
1199
+ question: params.question,
1200
+ choices: params.choices,
1201
+ allowFreeform: params.allowFreeform
1202
+ });
1203
+ return result;
1204
+ }
1205
+ async handleHooksInvoke(params) {
1206
+ if (!params || typeof params.sessionId !== "string" || typeof params.hookType !== "string") {
1207
+ throw new Error("Invalid hooks invoke payload");
1208
+ }
1209
+ const session = this.sessions.get(params.sessionId);
1210
+ if (!session) {
1211
+ throw new Error(`Session not found: ${params.sessionId}`);
1212
+ }
1213
+ const output = await session._handleHooksInvoke(params.hookType, params.input);
1214
+ return { output };
1215
+ }
1216
+ async handleSystemMessageTransform(params) {
1217
+ if (!params || typeof params.sessionId !== "string" || !params.sections || typeof params.sections !== "object") {
1218
+ throw new Error("Invalid systemMessage.transform payload");
1219
+ }
1220
+ const session = this.sessions.get(params.sessionId);
1221
+ if (!session) {
1222
+ throw new Error(`Session not found: ${params.sessionId}`);
1223
+ }
1224
+ return await session._handleSystemMessageTransform(params.sections);
1225
+ }
1226
+ // ========================================================================
1227
+ // Protocol v2 backward-compatibility adapters
1228
+ // ========================================================================
1229
+ /**
1230
+ * Handles a v2-style tool.call RPC request from the server.
1231
+ * Looks up the session and tool handler, executes it, and returns the result
1232
+ * in the v2 response format.
1233
+ */
1234
+ async handleToolCallRequestV2(params) {
1235
+ if (!params || typeof params.sessionId !== "string" || typeof params.toolCallId !== "string" || typeof params.toolName !== "string") {
1236
+ throw new Error("Invalid tool call payload");
1237
+ }
1238
+ const session = this.sessions.get(params.sessionId);
1239
+ if (!session) {
1240
+ throw new Error(`Unknown session ${params.sessionId}`);
1241
+ }
1242
+ const handler = session.getToolHandler(params.toolName);
1243
+ if (!handler) {
1244
+ return {
1245
+ result: {
1246
+ textResultForLlm: `Tool '${params.toolName}' is not supported by this client instance.`,
1247
+ resultType: "failure",
1248
+ error: `tool '${params.toolName}' not supported`,
1249
+ toolTelemetry: {}
1250
+ }
1251
+ };
1252
+ }
1253
+ try {
1254
+ const traceparent = params.traceparent;
1255
+ const tracestate = params.tracestate;
1256
+ const invocation = {
1257
+ sessionId: params.sessionId,
1258
+ toolCallId: params.toolCallId,
1259
+ toolName: params.toolName,
1260
+ arguments: params.arguments,
1261
+ traceparent,
1262
+ tracestate
1263
+ };
1264
+ const result = await handler(params.arguments, invocation);
1265
+ return { result: this.normalizeToolResultV2(result) };
1266
+ } catch (error) {
1267
+ const message = error instanceof Error ? error.message : String(error);
1268
+ return {
1269
+ result: {
1270
+ textResultForLlm: "Invoking this tool produced an error. Detailed information is not available.",
1271
+ resultType: "failure",
1272
+ error: message,
1273
+ toolTelemetry: {}
1274
+ }
1275
+ };
1276
+ }
1277
+ }
1278
+ /**
1279
+ * Handles a v2-style permission.request RPC request from the server.
1280
+ */
1281
+ async handlePermissionRequestV2(params) {
1282
+ if (!params || typeof params.sessionId !== "string" || !params.permissionRequest) {
1283
+ throw new Error("Invalid permission request payload");
1284
+ }
1285
+ const session = this.sessions.get(params.sessionId);
1286
+ if (!session) {
1287
+ throw new Error(`Session not found: ${params.sessionId}`);
1288
+ }
1289
+ try {
1290
+ const result = await session._handlePermissionRequestV2(params.permissionRequest);
1291
+ return { result };
1292
+ } catch (error) {
1293
+ if (error instanceof Error && error.message === import_session.NO_RESULT_PERMISSION_V2_ERROR) {
1294
+ throw error;
1295
+ }
1296
+ return {
1297
+ result: {
1298
+ kind: "denied-no-approval-rule-and-could-not-request-from-user"
1299
+ }
1300
+ };
1301
+ }
1302
+ }
1303
+ normalizeToolResultV2(result) {
1304
+ if (result === void 0 || result === null) {
1305
+ return {
1306
+ textResultForLlm: "Tool returned no result",
1307
+ resultType: "failure",
1308
+ error: "tool returned no result",
1309
+ toolTelemetry: {}
1310
+ };
1311
+ }
1312
+ if (this.isToolResultObject(result)) {
1313
+ return result;
1314
+ }
1315
+ const textResult = typeof result === "string" ? result : JSON.stringify(result);
1316
+ return {
1317
+ textResultForLlm: textResult,
1318
+ resultType: "success",
1319
+ toolTelemetry: {}
1320
+ };
1321
+ }
1322
+ isToolResultObject(value) {
1323
+ return typeof value === "object" && value !== null && "textResultForLlm" in value && typeof value.textResultForLlm === "string" && "resultType" in value;
1324
+ }
1325
+ }
1326
+ // Annotate the CommonJS export names for ESM import in node:
1327
+ 0 && (module.exports = {
1328
+ CopilotClient
1329
+ });