@github/copilot-language-server 1.527.5 → 1.529.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.
@@ -25,13 +25,22 @@ var import_node = require("vscode-jsonrpc/node.js");
25
25
  var import_rpc = require("./generated/rpc.js");
26
26
  var import_canvas = require("./canvas.js");
27
27
  var import_telemetry = require("./telemetry.js");
28
+ var import_factory = require("./factory.js");
29
+ function isFactoryResumeErrorCode(value) {
30
+ return value === "not_found" || value === "non_resumable" || value === "already_active" || value === "reapproval_declined" || value === "no_approval_provider";
31
+ }
28
32
  function deserializeHookInput(raw) {
29
33
  if (!raw || typeof raw !== "object" || typeof raw.timestamp !== "number") {
30
34
  return raw;
31
35
  }
32
36
  const obj = raw;
33
- const { cwd, ...rest } = obj;
34
- return { ...rest, timestamp: new Date(obj.timestamp), workingDirectory: cwd };
37
+ const { cwd, stop_hook_active, ...rest } = obj;
38
+ return {
39
+ ...rest,
40
+ timestamp: new Date(obj.timestamp),
41
+ workingDirectory: cwd,
42
+ ...stop_hook_active === void 0 ? {} : { stopHookActive: stop_hook_active }
43
+ };
35
44
  }
36
45
  function isOpenCanvasInstance(value) {
37
46
  if (!value || typeof value !== "object") {
@@ -40,6 +49,165 @@ function isOpenCanvasInstance(value) {
40
49
  const instance = value;
41
50
  return typeof instance.instanceId === "string" && instance.instanceId.length > 0 && typeof instance.extensionId === "string" && instance.extensionId.length > 0 && typeof instance.canvasId === "string" && instance.canvasId.length > 0;
42
51
  }
52
+ const FACTORY_LOG_FLUSH_DELAY_MS = 10;
53
+ const MAX_FACTORY_FANOUT_ITEMS = 4096;
54
+ function assertFactoryFanoutSize(kind, size) {
55
+ if (size > MAX_FACTORY_FANOUT_ITEMS) {
56
+ throw new Error(
57
+ `${kind}() accepts at most ${MAX_FACTORY_FANOUT_ITEMS} items; got ${size}.`
58
+ );
59
+ }
60
+ }
61
+ async function runFactoryParallel(thunks) {
62
+ if (!Array.isArray(thunks)) {
63
+ throw new Error(
64
+ "parallel() expects an array of functions, not promises. Wrap each call: () => agent(...)"
65
+ );
66
+ }
67
+ assertFactoryFanoutSize("parallel", thunks.length);
68
+ if (thunks.some((thunk) => typeof thunk !== "function")) {
69
+ throw new Error(
70
+ "parallel() expects an array of functions, not promises. Wrap each call: () => agent(...)"
71
+ );
72
+ }
73
+ return Promise.all(
74
+ thunks.map(
75
+ (thunk) => Promise.resolve().then(() => thunk()).catch((error) => {
76
+ if (isFactoryFatalError(error)) {
77
+ throw error;
78
+ }
79
+ return null;
80
+ })
81
+ )
82
+ );
83
+ }
84
+ async function runFactoryPipeline(items, ...stages) {
85
+ if (!Array.isArray(items)) {
86
+ throw new Error("pipeline(items, ...stages): items must be an array");
87
+ }
88
+ assertFactoryFanoutSize("pipeline", items.length);
89
+ return Promise.all(
90
+ items.map(async (item, index) => {
91
+ let previous = item;
92
+ for (const stage of stages) {
93
+ try {
94
+ previous = await stage(previous, item, index);
95
+ } catch (error) {
96
+ if (isFactoryFatalError(error)) {
97
+ throw error;
98
+ }
99
+ return null;
100
+ }
101
+ }
102
+ return previous;
103
+ })
104
+ );
105
+ }
106
+ class FactoryProgressBuffer {
107
+ constructor(send) {
108
+ this.send = send;
109
+ }
110
+ send;
111
+ nextSeq = 0;
112
+ pending = [];
113
+ flushTimer;
114
+ flushTail = Promise.resolve();
115
+ flushError;
116
+ flushFailed = false;
117
+ closed = false;
118
+ enqueue(kind, text) {
119
+ if (this.closed) {
120
+ throw new Error("Cannot log after the factory run has settled");
121
+ }
122
+ this.pending.push({ seq: this.nextSeq++, kind, text });
123
+ this.scheduleFlush();
124
+ }
125
+ async flush() {
126
+ this.clearFlushTimer();
127
+ const lines = this.pending.splice(0);
128
+ if (lines.length > 0) {
129
+ this.flushTail = this.flushTail.then(async () => {
130
+ try {
131
+ await this.send(lines);
132
+ } catch (error) {
133
+ if (!this.flushFailed) {
134
+ this.flushFailed = true;
135
+ this.flushError = error;
136
+ }
137
+ }
138
+ });
139
+ }
140
+ await this.flushTail;
141
+ if (this.flushFailed) {
142
+ throw this.flushError;
143
+ }
144
+ }
145
+ async close() {
146
+ this.closed = true;
147
+ this.clearFlushTimer();
148
+ const lines = this.pending.splice(0);
149
+ await this.flushTail;
150
+ if (this.flushFailed) {
151
+ throw this.flushError;
152
+ }
153
+ if (lines.length > 0) {
154
+ try {
155
+ await this.send(lines);
156
+ } catch (error) {
157
+ console.warn(
158
+ "Failed to flush final factory progress after the factory body settled",
159
+ error
160
+ );
161
+ }
162
+ }
163
+ }
164
+ scheduleFlush() {
165
+ if (this.flushTimer !== void 0) {
166
+ return;
167
+ }
168
+ this.flushTimer = setTimeout(() => {
169
+ this.flushTimer = void 0;
170
+ void this.flush().catch(() => {
171
+ });
172
+ }, FACTORY_LOG_FLUSH_DELAY_MS);
173
+ this.flushTimer.unref?.();
174
+ }
175
+ clearFlushTimer() {
176
+ if (this.flushTimer !== void 0) {
177
+ clearTimeout(this.flushTimer);
178
+ this.flushTimer = void 0;
179
+ }
180
+ }
181
+ }
182
+ function toPublicFactoryRunResult(envelope) {
183
+ return envelope;
184
+ }
185
+ async function awaitFactoryOperation(operation, signal) {
186
+ let rejectAbort;
187
+ const abortPromise = new Promise((_resolve, reject) => {
188
+ rejectAbort = reject;
189
+ });
190
+ const onAbort = () => rejectAbort?.(signal.reason ?? new DOMException("Factory run was aborted", "AbortError"));
191
+ signal.addEventListener("abort", onAbort, { once: true });
192
+ try {
193
+ throwIfFactoryAborted(signal);
194
+ return await Promise.race([operation(), abortPromise]);
195
+ } finally {
196
+ signal.removeEventListener("abort", onAbort);
197
+ }
198
+ }
199
+ function throwIfFactoryAborted(signal) {
200
+ if (signal.aborted) {
201
+ throw signal.reason ?? new DOMException("Factory run was aborted", "AbortError");
202
+ }
203
+ }
204
+ function isFactoryAbortError(error) {
205
+ return typeof error === "object" && error !== null && "name" in error && error.name === "AbortError";
206
+ }
207
+ function isFactoryFatalError(error) {
208
+ return isFactoryAbortError(error) || error instanceof import_node.ResponseError || error instanceof import_node.ConnectionError;
209
+ }
210
+ const TOOL_SEARCH_TOOL_NAME = "tool_search_tool";
43
211
  class CopilotSession {
44
212
  /**
45
213
  * Creates a new CopilotSession instance.
@@ -56,6 +224,7 @@ class CopilotSession {
56
224
  this._workspacePath = _workspacePath;
57
225
  this.traceContextProvider = traceContextProvider;
58
226
  this.mcpAuthHandler = options?.mcpAuthHandler;
227
+ this.managedSettingsEnabled = options?.managedSettingsEnabled === true;
59
228
  }
60
229
  sessionId;
61
230
  connection;
@@ -66,6 +235,8 @@ class CopilotSession {
66
235
  canvases = /* @__PURE__ */ new Map();
67
236
  bearerTokenProviders = /* @__PURE__ */ new Map();
68
237
  commandHandlers = /* @__PURE__ */ new Map();
238
+ factories = /* @__PURE__ */ new Map();
239
+ factoryAbortControllers = /* @__PURE__ */ new Map();
69
240
  permissionHandler;
70
241
  mcpAuthHandler;
71
242
  userInputHandler;
@@ -76,11 +247,147 @@ class CopilotSession {
76
247
  transformCallbacks;
77
248
  _rpc = null;
78
249
  traceContextProvider;
250
+ managedSettingsEnabled;
79
251
  _capabilities = {};
80
252
  openCanvasInstances = [];
81
253
  disconnected = false;
82
254
  /** @internal Client session API handlers, populated by CopilotClient during create/resume. */
83
255
  clientSessionApis = {};
256
+ /**
257
+ * Friendly factory API for running registered factories by name or handle.
258
+ *
259
+ * @experimental Part of the experimental Agent Factories surface and may
260
+ * change or be removed in future SDK or CLI releases.
261
+ */
262
+ factory = {
263
+ run: (async (nameOrHandle, options) => {
264
+ const name = typeof nameOrHandle === "string" ? nameOrHandle : (0, import_factory.getFactoryDefinition)(nameOrHandle).meta.name;
265
+ if (options?.resumeFromRunId !== void 0) {
266
+ return this.factory.resume(options.resumeFromRunId, {
267
+ limits: options.limits
268
+ });
269
+ }
270
+ const envelope = await this.rpc.factory.run({
271
+ name,
272
+ args: options?.args === void 0 ? {} : options.args,
273
+ options: {
274
+ limits: options?.limits
275
+ }
276
+ });
277
+ return this.settleFactoryRun(envelope);
278
+ }),
279
+ resume: (async (runId, options) => {
280
+ let response;
281
+ try {
282
+ response = await this.rpc.factory.resume({
283
+ runId,
284
+ limits: options?.limits
285
+ });
286
+ } catch (error) {
287
+ if (error instanceof import_node.ResponseError && typeof error.data === "object" && error.data !== null) {
288
+ const code = error.data.code;
289
+ if (isFactoryResumeErrorCode(code)) {
290
+ throw new import_factory.FactoryResumeError(code, error.message);
291
+ }
292
+ }
293
+ throw error;
294
+ }
295
+ return this.settleFactoryRun(response.run);
296
+ }),
297
+ getRun: async (runId) => toPublicFactoryRunResult(await this.rpc.factory.getRun({ runId })),
298
+ waitForRun: (runId, options) => this.waitForFactoryRun(runId, options?.signal),
299
+ listRuns: async () => (await this.rpc.factory.listRuns()).runs,
300
+ getRunDetail: (runId) => this.rpc.factory.getRunDetail({ runId }),
301
+ getRunProgress: (runId, options = {}) => this.rpc.factory.getRunProgress({ runId, ...options }),
302
+ cancel: async (runId) => toPublicFactoryRunResult(await this.rpc.factory.cancel({ runId }))
303
+ };
304
+ /**
305
+ * Resolve a start/resume envelope into the terminal envelope callers expect.
306
+ *
307
+ * The CLI may answer `session.factory.run` and `session.factory.resume`
308
+ * before the run settles, so a non-terminal envelope is followed by a wait
309
+ * on the run's terminal state.
310
+ */
311
+ settleFactoryRun(envelope) {
312
+ if ((0, import_factory.isFactoryRunTerminal)(envelope.status)) {
313
+ return Promise.resolve(toPublicFactoryRunResult(envelope));
314
+ }
315
+ return this.waitForFactoryRun(envelope.runId);
316
+ }
317
+ /**
318
+ * Resolve when a factory run reaches a terminal status.
319
+ *
320
+ * The subscription is installed *before* the first read so a transition
321
+ * landing between the two cannot be missed, and re-reads are serialized so
322
+ * overlapping invalidation events cannot interleave — the run's revision
323
+ * advances once per operation, so a burst of events is common and must
324
+ * collapse into a single in-flight read. A bounded periodic re-read keeps a
325
+ * dropped invalidation from leaving the wait pending forever.
326
+ */
327
+ waitForFactoryRun(runId, signal) {
328
+ const abortError = () => signal?.reason ?? new DOMException("Factory run wait was aborted", "AbortError");
329
+ if (signal?.aborted === true) {
330
+ return Promise.reject(abortError());
331
+ }
332
+ return new Promise((resolve, reject) => {
333
+ let settled = false;
334
+ let reading = false;
335
+ let rereadRequested = false;
336
+ let pollHandle;
337
+ let unsubscribe;
338
+ let onAbort;
339
+ const finish = (complete) => {
340
+ if (settled) {
341
+ return;
342
+ }
343
+ settled = true;
344
+ if (pollHandle !== void 0) {
345
+ clearInterval(pollHandle);
346
+ }
347
+ unsubscribe?.();
348
+ if (onAbort !== void 0) {
349
+ signal?.removeEventListener("abort", onAbort);
350
+ }
351
+ complete();
352
+ };
353
+ const read = async () => {
354
+ if (settled) {
355
+ return;
356
+ }
357
+ if (reading) {
358
+ rereadRequested = true;
359
+ return;
360
+ }
361
+ reading = true;
362
+ try {
363
+ do {
364
+ rereadRequested = false;
365
+ const envelope = await this.rpc.factory.getRun({ runId });
366
+ if ((0, import_factory.isFactoryRunTerminal)(envelope.status)) {
367
+ finish(() => resolve(toPublicFactoryRunResult(envelope)));
368
+ return;
369
+ }
370
+ } while (rereadRequested && !settled);
371
+ } catch (error) {
372
+ finish(() => reject(error));
373
+ } finally {
374
+ reading = false;
375
+ }
376
+ };
377
+ if (signal !== void 0) {
378
+ onAbort = () => finish(() => reject(abortError()));
379
+ signal.addEventListener("abort", onAbort, { once: true });
380
+ }
381
+ unsubscribe = this.on("factory.run_updated", (event) => {
382
+ if (event.data.runId === runId) {
383
+ void read();
384
+ }
385
+ });
386
+ pollHandle = setInterval(() => void read(), 5e3);
387
+ pollHandle.unref?.();
388
+ void read();
389
+ });
390
+ }
84
391
  /**
85
392
  * Typed session-scoped RPC methods.
86
393
  */
@@ -142,22 +449,20 @@ class CopilotSession {
142
449
  async sendAndWait(optionsOrPrompt, timeout) {
143
450
  const options = typeof optionsOrPrompt === "string" ? { prompt: optionsOrPrompt } : optionsOrPrompt;
144
451
  const effectiveTimeout = timeout ?? 6e4;
145
- let resolveIdle;
146
- let rejectWithError;
147
- const idlePromise = new Promise((resolve, reject) => {
148
- resolveIdle = resolve;
149
- rejectWithError = reject;
452
+ let resolveOutcome;
453
+ const outcomePromise = new Promise((resolve) => {
454
+ resolveOutcome = resolve;
150
455
  });
151
456
  let lastAssistantMessage;
152
457
  const unsubscribe = this.on((event) => {
153
458
  if (event.type === "assistant.message") {
154
459
  lastAssistantMessage = event;
155
460
  } else if (event.type === "session.idle") {
156
- resolveIdle();
461
+ resolveOutcome({ kind: "idle" });
157
462
  } else if (event.type === "session.error") {
158
463
  const error = new Error(event.data.message);
159
464
  error.stack = event.data.stack;
160
- rejectWithError(error);
465
+ resolveOutcome({ kind: "error", error });
161
466
  }
162
467
  });
163
468
  let timeoutId;
@@ -173,7 +478,10 @@ class CopilotSession {
173
478
  effectiveTimeout
174
479
  );
175
480
  });
176
- await Promise.race([idlePromise, timeoutPromise]);
481
+ const outcome = await Promise.race([outcomePromise, timeoutPromise]);
482
+ if (outcome.kind === "error") {
483
+ throw outcome.error;
484
+ }
177
485
  return lastAssistantMessage;
178
486
  } finally {
179
487
  if (timeoutId !== void 0) {
@@ -195,6 +503,13 @@ class CopilotSession {
195
503
  this.autoModeSwitchHandler = void 0;
196
504
  this.commandHandlers.clear();
197
505
  this.canvases.clear();
506
+ this.factories.clear();
507
+ for (const controllersForRun of this.factoryAbortControllers.values()) {
508
+ for (const controller of controllersForRun.values()) {
509
+ controller.abort();
510
+ }
511
+ }
512
+ this.factoryAbortControllers.clear();
198
513
  this.transformCallbacks?.clear();
199
514
  }
200
515
  on(eventTypeOrHandler, handler) {
@@ -352,11 +667,21 @@ class CopilotSession {
352
667
  */
353
668
  async _executeToolAndRespond(requestId, toolName, toolCallId, args, handler, traceparent, tracestate) {
354
669
  try {
670
+ let availableTools;
671
+ if (toolName === TOOL_SEARCH_TOOL_NAME) {
672
+ try {
673
+ const metadata = await this.rpc.tools.getCurrentMetadata();
674
+ availableTools = metadata.tools ?? void 0;
675
+ } catch {
676
+ availableTools = void 0;
677
+ }
678
+ }
355
679
  const rawResult = await handler(args, {
356
680
  sessionId: this.sessionId,
357
681
  toolCallId,
358
682
  toolName,
359
683
  arguments: args,
684
+ availableTools,
360
685
  traceparent,
361
686
  tracestate
362
687
  });
@@ -395,7 +720,8 @@ class CopilotSession {
395
720
  async _executePermissionAndRespond(requestId, permissionRequest) {
396
721
  try {
397
722
  const result = await this.permissionHandler(permissionRequest, {
398
- sessionId: this.sessionId
723
+ sessionId: this.sessionId,
724
+ managedSettingsEnabled: this.managedSettingsEnabled
399
725
  });
400
726
  if (result.kind === "no-result") {
401
727
  return;
@@ -404,10 +730,15 @@ class CopilotSession {
404
730
  return;
405
731
  }
406
732
  await this.rpc.permissions.handlePendingPermissionRequest({ requestId, result });
407
- } catch (_error) {
733
+ } catch (error) {
408
734
  if (this.disconnected) {
409
735
  return;
410
736
  }
737
+ console.error("Permission handler or response delivery failed", {
738
+ sessionId: this.sessionId,
739
+ requestId,
740
+ error
741
+ });
411
742
  try {
412
743
  await this.rpc.permissions.handlePendingPermissionRequest({
413
744
  requestId,
@@ -571,6 +902,157 @@ class CopilotSession {
571
902
  }
572
903
  };
573
904
  }
905
+ /**
906
+ * Registers factory closures and reverse-RPC handlers for this session.
907
+ *
908
+ * @param factories - Factory handles declared by the joining extension.
909
+ * @internal Called by the SDK when an extension joins a session.
910
+ */
911
+ registerFactories(factories) {
912
+ this.factories.clear();
913
+ if (!factories || factories.length === 0) {
914
+ delete this.clientSessionApis.factory;
915
+ return;
916
+ }
917
+ for (const handle of factories) {
918
+ const definition = (0, import_factory.getFactoryDefinition)(handle);
919
+ if (this.factories.has(definition.meta.name)) {
920
+ throw new Error(
921
+ `Duplicate factory name "${definition.meta.name}". Factory names must be unique within a joinSession call.`
922
+ );
923
+ }
924
+ this.factories.set(definition.meta.name, definition);
925
+ }
926
+ const self = this;
927
+ this.clientSessionApis.factory = {
928
+ async execute(params) {
929
+ const definition = self.factories.get(params.name);
930
+ if (!definition) {
931
+ const message = `No factory registered with name "${params.name}"`;
932
+ throw new import_node.ResponseError(import_node.ErrorCodes.InvalidParams, message, {
933
+ code: "factory_not_found",
934
+ name: params.name
935
+ });
936
+ }
937
+ const controller = new AbortController();
938
+ let controllersForRun = self.factoryAbortControllers.get(params.runId);
939
+ if (controllersForRun === void 0) {
940
+ controllersForRun = /* @__PURE__ */ new Map();
941
+ self.factoryAbortControllers.set(params.runId, controllersForRun);
942
+ }
943
+ controllersForRun.set(params.executionToken, controller);
944
+ const progress = new FactoryProgressBuffer(async (lines) => {
945
+ await self.rpc.factory.log({
946
+ runId: params.runId,
947
+ executionToken: params.executionToken,
948
+ lines
949
+ });
950
+ });
951
+ try {
952
+ const context = {
953
+ runId: params.runId,
954
+ args: params.args,
955
+ session: self,
956
+ signal: controller.signal,
957
+ phase: (title) => {
958
+ throwIfFactoryAborted(controller.signal);
959
+ progress.enqueue("phase", title);
960
+ },
961
+ log: (message) => {
962
+ throwIfFactoryAborted(controller.signal);
963
+ progress.enqueue("log", message);
964
+ },
965
+ agent: async (prompt, options = {}) => {
966
+ await progress.flush();
967
+ const response = await awaitFactoryOperation(
968
+ () => self.rpc.factory.agent({
969
+ factoryRunId: params.runId,
970
+ executionToken: params.executionToken,
971
+ prompt,
972
+ opts: {
973
+ label: options.label,
974
+ schema: options.schema,
975
+ model: options.model
976
+ }
977
+ }),
978
+ controller.signal
979
+ );
980
+ return response.result ?? null;
981
+ },
982
+ step: async (key, producer, options = {}) => {
983
+ await progress.flush();
984
+ if (options.volatile) {
985
+ throwIfFactoryAborted(controller.signal);
986
+ return producer();
987
+ }
988
+ const cached = await awaitFactoryOperation(
989
+ () => self.rpc.factory.journal.get({
990
+ runId: params.runId,
991
+ executionToken: params.executionToken,
992
+ key
993
+ }),
994
+ controller.signal
995
+ );
996
+ if (cached.hit) {
997
+ if (cached.resultJson === void 0) {
998
+ throw new Error(
999
+ `step("${key}") journal returned a hit without a result`
1000
+ );
1001
+ }
1002
+ assertFactoryStepResult(cached.resultJson, key);
1003
+ return cached.resultJson;
1004
+ }
1005
+ const result2 = await producer();
1006
+ assertFactoryStepResult(result2, key);
1007
+ await awaitFactoryOperation(
1008
+ () => self.rpc.factory.journal.put({
1009
+ runId: params.runId,
1010
+ executionToken: params.executionToken,
1011
+ key,
1012
+ resultJson: result2
1013
+ }),
1014
+ controller.signal
1015
+ );
1016
+ return result2;
1017
+ },
1018
+ parallel: runFactoryParallel,
1019
+ pipeline: runFactoryPipeline,
1020
+ factory: async () => {
1021
+ throw new Error("nested factories are not supported");
1022
+ }
1023
+ };
1024
+ const result = await definition.run(context);
1025
+ if (result === void 0) {
1026
+ return {};
1027
+ }
1028
+ assertFactoryResult(result);
1029
+ return { result };
1030
+ } finally {
1031
+ try {
1032
+ await progress.close();
1033
+ } finally {
1034
+ const controllersForRun2 = self.factoryAbortControllers.get(params.runId);
1035
+ if (controllersForRun2?.get(params.executionToken) === controller) {
1036
+ controllersForRun2.delete(params.executionToken);
1037
+ if (controllersForRun2.size === 0) {
1038
+ self.factoryAbortControllers.delete(params.runId);
1039
+ }
1040
+ }
1041
+ }
1042
+ }
1043
+ },
1044
+ async abort(params) {
1045
+ const controllersForRun = self.factoryAbortControllers.get(params.runId);
1046
+ if (controllersForRun !== void 0) {
1047
+ const reason = new DOMException("Factory run was aborted", "AbortError");
1048
+ for (const controller of controllersForRun.values()) {
1049
+ controller.abort(reason);
1050
+ }
1051
+ }
1052
+ return {};
1053
+ }
1054
+ };
1055
+ }
574
1056
  /**
575
1057
  * Registers per-provider {@link BearerTokenProvider} callbacks for BYOK providers
576
1058
  * configured with managed-identity / on-demand bearer-token auth.
@@ -901,9 +1383,11 @@ class CopilotSession {
901
1383
  postToolUse: this.hooks.onPostToolUse,
902
1384
  postToolUseFailure: this.hooks.onPostToolUseFailure,
903
1385
  userPromptSubmitted: this.hooks.onUserPromptSubmitted,
1386
+ userPromptTransformed: this.hooks.onUserPromptTransformed,
904
1387
  sessionStart: this.hooks.onSessionStart,
905
1388
  sessionEnd: this.hooks.onSessionEnd,
906
- errorOccurred: this.hooks.onErrorOccurred
1389
+ errorOccurred: this.hooks.onErrorOccurred,
1390
+ agentStop: this.hooks.onAgentStop
907
1391
  };
908
1392
  const handler = handlerMap[hookType];
909
1393
  if (!handler) {
@@ -1009,7 +1493,7 @@ class CopilotSession {
1009
1493
  *
1010
1494
  * @example
1011
1495
  * ```typescript
1012
- * await session.setModel("gpt-4.1");
1496
+ * await session.setModel("gpt-5.4");
1013
1497
  * await session.setModel("claude-sonnet-4.6", { reasoningEffort: "high" });
1014
1498
  * ```
1015
1499
  */
@@ -1061,6 +1545,151 @@ function toCanvasRpcError(error) {
1061
1545
  const message = error instanceof Error ? error.message : String(error);
1062
1546
  return new import_node.ResponseError(import_node.ErrorCodes.InternalError, message, { code, message });
1063
1547
  }
1548
+ function strictJsonValidationError(context, category, message, path) {
1549
+ return new import_node.ResponseError(import_node.ErrorCodes.InternalError, message, {
1550
+ code: context.code,
1551
+ category,
1552
+ path
1553
+ });
1554
+ }
1555
+ function assertStrictJson(value, context) {
1556
+ const ancestors = /* @__PURE__ */ new Set();
1557
+ const visit = (current, path, allowUndefined) => {
1558
+ if (current === void 0) {
1559
+ if (allowUndefined) {
1560
+ return;
1561
+ }
1562
+ throw strictJsonValidationError(
1563
+ context,
1564
+ "nested_undefined",
1565
+ `${context.label} contains nested undefined at ${path}`,
1566
+ path
1567
+ );
1568
+ }
1569
+ if (current === null || typeof current === "boolean" || typeof current === "string") {
1570
+ return;
1571
+ }
1572
+ if (typeof current === "number") {
1573
+ if (!Number.isFinite(current)) {
1574
+ throw strictJsonValidationError(
1575
+ context,
1576
+ "non_finite_number",
1577
+ `${context.label} contains a non-finite number at ${path}`,
1578
+ path
1579
+ );
1580
+ }
1581
+ if (Object.is(current, -0)) {
1582
+ throw strictJsonValidationError(
1583
+ context,
1584
+ "negative_zero",
1585
+ `${context.label} contains negative zero at ${path}; normalize it to 0`,
1586
+ path
1587
+ );
1588
+ }
1589
+ return;
1590
+ }
1591
+ if (typeof current === "function" || typeof current === "symbol" || typeof current === "bigint") {
1592
+ throw strictJsonValidationError(
1593
+ context,
1594
+ "unsupported_type",
1595
+ `${context.label} contains a function, symbol, or BigInt at ${path}`,
1596
+ path
1597
+ );
1598
+ }
1599
+ if (typeof current !== "object") {
1600
+ throw strictJsonValidationError(
1601
+ context,
1602
+ "unsupported_type",
1603
+ `${context.label} contains a function, symbol, or BigInt at ${path}`,
1604
+ path
1605
+ );
1606
+ }
1607
+ if (ancestors.has(current)) {
1608
+ throw strictJsonValidationError(
1609
+ context,
1610
+ "cyclic_value",
1611
+ `${context.label} contains a cyclic reference at ${path}`,
1612
+ path
1613
+ );
1614
+ }
1615
+ ancestors.add(current);
1616
+ try {
1617
+ if (Array.isArray(current)) {
1618
+ const keys = Reflect.ownKeys(current);
1619
+ if (keys.length !== current.length + 1 || keys.some(
1620
+ (key) => key !== "length" && (typeof key !== "string" || !/^(0|[1-9]\d*)$/.test(key) || Number(key) >= current.length)
1621
+ )) {
1622
+ throw strictJsonValidationError(
1623
+ context,
1624
+ "unsupported_object",
1625
+ `${context.label} contains a non-JSON array property at ${path}`,
1626
+ path
1627
+ );
1628
+ }
1629
+ for (let index = 0; index < current.length; index++) {
1630
+ const descriptor = Object.getOwnPropertyDescriptor(current, String(index));
1631
+ if (descriptor === void 0 || !descriptor.enumerable || !("value" in descriptor)) {
1632
+ throw strictJsonValidationError(
1633
+ context,
1634
+ "unsupported_object",
1635
+ `${context.label} contains a non-JSON array property at ${path}[${index}]`,
1636
+ `${path}[${index}]`
1637
+ );
1638
+ }
1639
+ visit(descriptor.value, `${path}[${index}]`, false);
1640
+ }
1641
+ return;
1642
+ }
1643
+ const prototype = Object.getPrototypeOf(current);
1644
+ if (prototype !== Object.prototype && prototype !== null) {
1645
+ throw strictJsonValidationError(
1646
+ context,
1647
+ "unsupported_object",
1648
+ `${context.label} contains a non-JSON object at ${path}`,
1649
+ path
1650
+ );
1651
+ }
1652
+ for (const key of Reflect.ownKeys(current)) {
1653
+ if (typeof key === "symbol") {
1654
+ throw strictJsonValidationError(
1655
+ context,
1656
+ "unsupported_type",
1657
+ `${context.label} contains a function, symbol, or BigInt at ${path}`,
1658
+ path
1659
+ );
1660
+ }
1661
+ const propertyPath = /^[A-Za-z_$][\w$]*$/.test(key) ? `${path}.${key}` : `${path}[${JSON.stringify(key)}]`;
1662
+ const descriptor = Object.getOwnPropertyDescriptor(current, key);
1663
+ if (descriptor === void 0 || !descriptor.enumerable || !("value" in descriptor)) {
1664
+ throw strictJsonValidationError(
1665
+ context,
1666
+ "unsupported_object",
1667
+ `${context.label} contains a non-JSON property at ${propertyPath}`,
1668
+ propertyPath
1669
+ );
1670
+ }
1671
+ visit(descriptor.value, propertyPath, false);
1672
+ }
1673
+ } finally {
1674
+ ancestors.delete(current);
1675
+ }
1676
+ };
1677
+ visit(value, "$", context.allowTopLevelUndefined);
1678
+ }
1679
+ function assertFactoryResult(value) {
1680
+ assertStrictJson(value, {
1681
+ code: "factory_result_not_json",
1682
+ label: "Factory result",
1683
+ allowTopLevelUndefined: true
1684
+ });
1685
+ }
1686
+ function assertFactoryStepResult(value, key) {
1687
+ assertStrictJson(value, {
1688
+ code: "factory_step_not_json",
1689
+ label: `Factory step "${key}" result`,
1690
+ allowTopLevelUndefined: false
1691
+ });
1692
+ }
1064
1693
  // Annotate the CommonJS export names for ESM import in node:
1065
1694
  0 && (module.exports = {
1066
1695
  CopilotSession