@github/copilot-sdk 1.0.9-preview.0 → 1.0.9-preview.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/session.js CHANGED
@@ -2,6 +2,14 @@ import { ConnectionError, ErrorCodes, ResponseError } from "vscode-jsonrpc/node.
2
2
  import { createSessionRpc } from "./generated/rpc.js";
3
3
  import { CanvasError } from "./canvas.js";
4
4
  import { getTraceContext } from "./telemetry.js";
5
+ import {
6
+ getFactoryDefinition,
7
+ FactoryResumeError,
8
+ isFactoryRunTerminal
9
+ } from "./factory.js";
10
+ function isFactoryResumeErrorCode(value) {
11
+ return value === "not_found" || value === "non_resumable" || value === "already_active" || value === "reapproval_declined" || value === "no_approval_provider";
12
+ }
5
13
  function deserializeHookInput(raw) {
6
14
  if (!raw || typeof raw !== "object" || typeof raw.timestamp !== "number") {
7
15
  return raw;
@@ -22,6 +30,164 @@ function isOpenCanvasInstance(value) {
22
30
  const instance = value;
23
31
  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;
24
32
  }
33
+ const FACTORY_LOG_FLUSH_DELAY_MS = 10;
34
+ const MAX_FACTORY_FANOUT_ITEMS = 4096;
35
+ function assertFactoryFanoutSize(kind, size) {
36
+ if (size > MAX_FACTORY_FANOUT_ITEMS) {
37
+ throw new Error(
38
+ `${kind}() accepts at most ${MAX_FACTORY_FANOUT_ITEMS} items; got ${size}.`
39
+ );
40
+ }
41
+ }
42
+ async function runFactoryParallel(thunks) {
43
+ if (!Array.isArray(thunks)) {
44
+ throw new Error(
45
+ "parallel() expects an array of functions, not promises. Wrap each call: () => agent(...)"
46
+ );
47
+ }
48
+ assertFactoryFanoutSize("parallel", thunks.length);
49
+ if (thunks.some((thunk) => typeof thunk !== "function")) {
50
+ throw new Error(
51
+ "parallel() expects an array of functions, not promises. Wrap each call: () => agent(...)"
52
+ );
53
+ }
54
+ return Promise.all(
55
+ thunks.map(
56
+ (thunk) => Promise.resolve().then(() => thunk()).catch((error) => {
57
+ if (isFactoryFatalError(error)) {
58
+ throw error;
59
+ }
60
+ return null;
61
+ })
62
+ )
63
+ );
64
+ }
65
+ async function runFactoryPipeline(items, ...stages) {
66
+ if (!Array.isArray(items)) {
67
+ throw new Error("pipeline(items, ...stages): items must be an array");
68
+ }
69
+ assertFactoryFanoutSize("pipeline", items.length);
70
+ return Promise.all(
71
+ items.map(async (item, index) => {
72
+ let previous = item;
73
+ for (const stage of stages) {
74
+ try {
75
+ previous = await stage(previous, item, index);
76
+ } catch (error) {
77
+ if (isFactoryFatalError(error)) {
78
+ throw error;
79
+ }
80
+ return null;
81
+ }
82
+ }
83
+ return previous;
84
+ })
85
+ );
86
+ }
87
+ class FactoryProgressBuffer {
88
+ constructor(send) {
89
+ this.send = send;
90
+ }
91
+ send;
92
+ nextSeq = 0;
93
+ pending = [];
94
+ flushTimer;
95
+ flushTail = Promise.resolve();
96
+ flushError;
97
+ flushFailed = false;
98
+ closed = false;
99
+ enqueue(kind, text) {
100
+ if (this.closed) {
101
+ throw new Error("Cannot log after the factory run has settled");
102
+ }
103
+ this.pending.push({ seq: this.nextSeq++, kind, text });
104
+ this.scheduleFlush();
105
+ }
106
+ async flush() {
107
+ this.clearFlushTimer();
108
+ const lines = this.pending.splice(0);
109
+ if (lines.length > 0) {
110
+ this.flushTail = this.flushTail.then(async () => {
111
+ try {
112
+ await this.send(lines);
113
+ } catch (error) {
114
+ if (!this.flushFailed) {
115
+ this.flushFailed = true;
116
+ this.flushError = error;
117
+ }
118
+ }
119
+ });
120
+ }
121
+ await this.flushTail;
122
+ if (this.flushFailed) {
123
+ throw this.flushError;
124
+ }
125
+ }
126
+ async close() {
127
+ this.closed = true;
128
+ this.clearFlushTimer();
129
+ const lines = this.pending.splice(0);
130
+ await this.flushTail;
131
+ if (this.flushFailed) {
132
+ throw this.flushError;
133
+ }
134
+ if (lines.length > 0) {
135
+ try {
136
+ await this.send(lines);
137
+ } catch (error) {
138
+ console.warn(
139
+ "Failed to flush final factory progress after the factory body settled",
140
+ error
141
+ );
142
+ }
143
+ }
144
+ }
145
+ scheduleFlush() {
146
+ if (this.flushTimer !== void 0) {
147
+ return;
148
+ }
149
+ this.flushTimer = setTimeout(() => {
150
+ this.flushTimer = void 0;
151
+ void this.flush().catch(() => {
152
+ });
153
+ }, FACTORY_LOG_FLUSH_DELAY_MS);
154
+ this.flushTimer.unref?.();
155
+ }
156
+ clearFlushTimer() {
157
+ if (this.flushTimer !== void 0) {
158
+ clearTimeout(this.flushTimer);
159
+ this.flushTimer = void 0;
160
+ }
161
+ }
162
+ }
163
+ function toPublicFactoryRunResult(envelope) {
164
+ return envelope;
165
+ }
166
+ async function awaitFactoryOperation(operation, signal) {
167
+ let rejectAbort;
168
+ const abortPromise = new Promise((_resolve, reject) => {
169
+ rejectAbort = reject;
170
+ });
171
+ const onAbort = () => rejectAbort?.(signal.reason ?? new DOMException("Factory run was aborted", "AbortError"));
172
+ signal.addEventListener("abort", onAbort, { once: true });
173
+ try {
174
+ throwIfFactoryAborted(signal);
175
+ return await Promise.race([operation(), abortPromise]);
176
+ } finally {
177
+ signal.removeEventListener("abort", onAbort);
178
+ }
179
+ }
180
+ function throwIfFactoryAborted(signal) {
181
+ if (signal.aborted) {
182
+ throw signal.reason ?? new DOMException("Factory run was aborted", "AbortError");
183
+ }
184
+ }
185
+ function isFactoryAbortError(error) {
186
+ return typeof error === "object" && error !== null && "name" in error && error.name === "AbortError";
187
+ }
188
+ function isFactoryFatalError(error) {
189
+ return isFactoryAbortError(error) || error instanceof ResponseError || error instanceof ConnectionError;
190
+ }
25
191
  const TOOL_SEARCH_TOOL_NAME = "tool_search_tool";
26
192
  class CopilotSession {
27
193
  /**
@@ -49,6 +215,8 @@ class CopilotSession {
49
215
  canvases = /* @__PURE__ */ new Map();
50
216
  bearerTokenProviders = /* @__PURE__ */ new Map();
51
217
  commandHandlers = /* @__PURE__ */ new Map();
218
+ factories = /* @__PURE__ */ new Map();
219
+ factoryAbortControllers = /* @__PURE__ */ new Map();
52
220
  permissionHandler;
53
221
  mcpAuthHandler;
54
222
  userInputHandler;
@@ -64,6 +232,128 @@ class CopilotSession {
64
232
  disconnected = false;
65
233
  /** @internal Client session API handlers, populated by CopilotClient during create/resume. */
66
234
  clientSessionApis = {};
235
+ /**
236
+ * Friendly factory API for running registered factories by name or handle.
237
+ *
238
+ * @experimental Part of the experimental Agent Factories surface and may
239
+ * change or be removed in future SDK or CLI releases.
240
+ */
241
+ factory = {
242
+ run: (async (nameOrHandle, options) => {
243
+ const name = typeof nameOrHandle === "string" ? nameOrHandle : getFactoryDefinition(nameOrHandle).meta.name;
244
+ if (options?.resumeFromRunId !== void 0) {
245
+ return this.factory.resume(options.resumeFromRunId, {
246
+ limits: options.limits
247
+ });
248
+ }
249
+ const envelope = await this.rpc.factory.run({
250
+ name,
251
+ args: options?.args === void 0 ? {} : options.args,
252
+ options: {
253
+ limits: options?.limits
254
+ }
255
+ });
256
+ return toPublicFactoryRunResult(envelope);
257
+ }),
258
+ resume: (async (runId, options) => {
259
+ let response;
260
+ try {
261
+ response = await this.rpc.factory.resume({
262
+ runId,
263
+ limits: options?.limits
264
+ });
265
+ } catch (error) {
266
+ if (error instanceof ResponseError && typeof error.data === "object" && error.data !== null) {
267
+ const code = error.data.code;
268
+ if (isFactoryResumeErrorCode(code)) {
269
+ throw new FactoryResumeError(code, error.message);
270
+ }
271
+ }
272
+ throw error;
273
+ }
274
+ return toPublicFactoryRunResult(response.run);
275
+ }),
276
+ getRun: async (runId) => toPublicFactoryRunResult(await this.rpc.factory.getRun({ runId })),
277
+ waitForRun: (runId, options) => this.waitForFactoryRun(runId, options?.signal),
278
+ listRuns: async () => (await this.rpc.factory.listRuns()).runs,
279
+ getRunDetail: (runId) => this.rpc.factory.getRunDetail({ runId }),
280
+ getRunProgress: (runId, options = {}) => this.rpc.factory.getRunProgress({ runId, ...options }),
281
+ cancel: async (runId) => toPublicFactoryRunResult(await this.rpc.factory.cancel({ runId }))
282
+ };
283
+ /**
284
+ * Resolve when a factory run reaches a terminal status.
285
+ *
286
+ * The subscription is installed *before* the first read so a transition
287
+ * landing between the two cannot be missed, and re-reads are serialized so
288
+ * overlapping invalidation events cannot interleave — the run's revision
289
+ * advances once per operation, so a burst of events is common and must
290
+ * collapse into a single in-flight read. A bounded periodic re-read keeps a
291
+ * dropped invalidation from leaving the wait pending forever.
292
+ */
293
+ waitForFactoryRun(runId, signal) {
294
+ const abortError = () => signal?.reason ?? new DOMException("Factory run wait was aborted", "AbortError");
295
+ if (signal?.aborted === true) {
296
+ return Promise.reject(abortError());
297
+ }
298
+ return new Promise((resolve, reject) => {
299
+ let settled = false;
300
+ let reading = false;
301
+ let rereadRequested = false;
302
+ let pollHandle;
303
+ let unsubscribe;
304
+ let onAbort;
305
+ const finish = (complete) => {
306
+ if (settled) {
307
+ return;
308
+ }
309
+ settled = true;
310
+ if (pollHandle !== void 0) {
311
+ clearInterval(pollHandle);
312
+ }
313
+ unsubscribe?.();
314
+ if (onAbort !== void 0) {
315
+ signal?.removeEventListener("abort", onAbort);
316
+ }
317
+ complete();
318
+ };
319
+ const read = async () => {
320
+ if (settled) {
321
+ return;
322
+ }
323
+ if (reading) {
324
+ rereadRequested = true;
325
+ return;
326
+ }
327
+ reading = true;
328
+ try {
329
+ do {
330
+ rereadRequested = false;
331
+ const envelope = await this.rpc.factory.getRun({ runId });
332
+ if (isFactoryRunTerminal(envelope.status)) {
333
+ finish(() => resolve(toPublicFactoryRunResult(envelope)));
334
+ return;
335
+ }
336
+ } while (rereadRequested && !settled);
337
+ } catch (error) {
338
+ finish(() => reject(error));
339
+ } finally {
340
+ reading = false;
341
+ }
342
+ };
343
+ if (signal !== void 0) {
344
+ onAbort = () => finish(() => reject(abortError()));
345
+ signal.addEventListener("abort", onAbort, { once: true });
346
+ }
347
+ unsubscribe = this.on("factory.run_updated", (event) => {
348
+ if (event.data.runId === runId) {
349
+ void read();
350
+ }
351
+ });
352
+ pollHandle = setInterval(() => void read(), 5e3);
353
+ pollHandle.unref?.();
354
+ void read();
355
+ });
356
+ }
67
357
  /**
68
358
  * Typed session-scoped RPC methods.
69
359
  */
@@ -178,6 +468,13 @@ class CopilotSession {
178
468
  this.autoModeSwitchHandler = void 0;
179
469
  this.commandHandlers.clear();
180
470
  this.canvases.clear();
471
+ this.factories.clear();
472
+ for (const controllersForRun of this.factoryAbortControllers.values()) {
473
+ for (const controller of controllersForRun.values()) {
474
+ controller.abort();
475
+ }
476
+ }
477
+ this.factoryAbortControllers.clear();
181
478
  this.transformCallbacks?.clear();
182
479
  }
183
480
  on(eventTypeOrHandler, handler) {
@@ -564,6 +861,157 @@ class CopilotSession {
564
861
  }
565
862
  };
566
863
  }
864
+ /**
865
+ * Registers factory closures and reverse-RPC handlers for this session.
866
+ *
867
+ * @param factories - Factory handles declared by the joining extension.
868
+ * @internal Called by the SDK when an extension joins a session.
869
+ */
870
+ registerFactories(factories) {
871
+ this.factories.clear();
872
+ if (!factories || factories.length === 0) {
873
+ delete this.clientSessionApis.factory;
874
+ return;
875
+ }
876
+ for (const handle of factories) {
877
+ const definition = getFactoryDefinition(handle);
878
+ if (this.factories.has(definition.meta.name)) {
879
+ throw new Error(
880
+ `Duplicate factory name "${definition.meta.name}". Factory names must be unique within a joinSession call.`
881
+ );
882
+ }
883
+ this.factories.set(definition.meta.name, definition);
884
+ }
885
+ const self = this;
886
+ this.clientSessionApis.factory = {
887
+ async execute(params) {
888
+ const definition = self.factories.get(params.name);
889
+ if (!definition) {
890
+ const message = `No factory registered with name "${params.name}"`;
891
+ throw new ResponseError(ErrorCodes.InvalidParams, message, {
892
+ code: "factory_not_found",
893
+ name: params.name
894
+ });
895
+ }
896
+ const controller = new AbortController();
897
+ let controllersForRun = self.factoryAbortControllers.get(params.runId);
898
+ if (controllersForRun === void 0) {
899
+ controllersForRun = /* @__PURE__ */ new Map();
900
+ self.factoryAbortControllers.set(params.runId, controllersForRun);
901
+ }
902
+ controllersForRun.set(params.executionToken, controller);
903
+ const progress = new FactoryProgressBuffer(async (lines) => {
904
+ await self.rpc.factory.log({
905
+ runId: params.runId,
906
+ executionToken: params.executionToken,
907
+ lines
908
+ });
909
+ });
910
+ try {
911
+ const context = {
912
+ runId: params.runId,
913
+ args: params.args,
914
+ session: self,
915
+ signal: controller.signal,
916
+ phase: (title) => {
917
+ throwIfFactoryAborted(controller.signal);
918
+ progress.enqueue("phase", title);
919
+ },
920
+ log: (message) => {
921
+ throwIfFactoryAborted(controller.signal);
922
+ progress.enqueue("log", message);
923
+ },
924
+ agent: async (prompt, options = {}) => {
925
+ await progress.flush();
926
+ const response = await awaitFactoryOperation(
927
+ () => self.rpc.factory.agent({
928
+ factoryRunId: params.runId,
929
+ executionToken: params.executionToken,
930
+ prompt,
931
+ opts: {
932
+ label: options.label,
933
+ schema: options.schema,
934
+ model: options.model
935
+ }
936
+ }),
937
+ controller.signal
938
+ );
939
+ return response.result ?? null;
940
+ },
941
+ step: async (key, producer, options = {}) => {
942
+ await progress.flush();
943
+ if (options.volatile) {
944
+ throwIfFactoryAborted(controller.signal);
945
+ return producer();
946
+ }
947
+ const cached = await awaitFactoryOperation(
948
+ () => self.rpc.factory.journal.get({
949
+ runId: params.runId,
950
+ executionToken: params.executionToken,
951
+ key
952
+ }),
953
+ controller.signal
954
+ );
955
+ if (cached.hit) {
956
+ if (cached.resultJson === void 0) {
957
+ throw new Error(
958
+ `step("${key}") journal returned a hit without a result`
959
+ );
960
+ }
961
+ assertFactoryStepResult(cached.resultJson, key);
962
+ return cached.resultJson;
963
+ }
964
+ const result2 = await producer();
965
+ assertFactoryStepResult(result2, key);
966
+ await awaitFactoryOperation(
967
+ () => self.rpc.factory.journal.put({
968
+ runId: params.runId,
969
+ executionToken: params.executionToken,
970
+ key,
971
+ resultJson: result2
972
+ }),
973
+ controller.signal
974
+ );
975
+ return result2;
976
+ },
977
+ parallel: runFactoryParallel,
978
+ pipeline: runFactoryPipeline,
979
+ factory: async () => {
980
+ throw new Error("nested factories are not supported");
981
+ }
982
+ };
983
+ const result = await definition.run(context);
984
+ if (result === void 0) {
985
+ return {};
986
+ }
987
+ assertFactoryResult(result);
988
+ return { result };
989
+ } finally {
990
+ try {
991
+ await progress.close();
992
+ } finally {
993
+ const controllersForRun2 = self.factoryAbortControllers.get(params.runId);
994
+ if (controllersForRun2?.get(params.executionToken) === controller) {
995
+ controllersForRun2.delete(params.executionToken);
996
+ if (controllersForRun2.size === 0) {
997
+ self.factoryAbortControllers.delete(params.runId);
998
+ }
999
+ }
1000
+ }
1001
+ }
1002
+ },
1003
+ async abort(params) {
1004
+ const controllersForRun = self.factoryAbortControllers.get(params.runId);
1005
+ if (controllersForRun !== void 0) {
1006
+ const reason = new DOMException("Factory run was aborted", "AbortError");
1007
+ for (const controller of controllersForRun.values()) {
1008
+ controller.abort(reason);
1009
+ }
1010
+ }
1011
+ return {};
1012
+ }
1013
+ };
1014
+ }
567
1015
  /**
568
1016
  * Registers per-provider {@link BearerTokenProvider} callbacks for BYOK providers
569
1017
  * configured with managed-identity / on-demand bearer-token auth.
@@ -1055,6 +1503,151 @@ function toCanvasRpcError(error) {
1055
1503
  const message = error instanceof Error ? error.message : String(error);
1056
1504
  return new ResponseError(ErrorCodes.InternalError, message, { code, message });
1057
1505
  }
1506
+ function strictJsonValidationError(context, category, message, path) {
1507
+ return new ResponseError(ErrorCodes.InternalError, message, {
1508
+ code: context.code,
1509
+ category,
1510
+ path
1511
+ });
1512
+ }
1513
+ function assertStrictJson(value, context) {
1514
+ const ancestors = /* @__PURE__ */ new Set();
1515
+ const visit = (current, path, allowUndefined) => {
1516
+ if (current === void 0) {
1517
+ if (allowUndefined) {
1518
+ return;
1519
+ }
1520
+ throw strictJsonValidationError(
1521
+ context,
1522
+ "nested_undefined",
1523
+ `${context.label} contains nested undefined at ${path}`,
1524
+ path
1525
+ );
1526
+ }
1527
+ if (current === null || typeof current === "boolean" || typeof current === "string") {
1528
+ return;
1529
+ }
1530
+ if (typeof current === "number") {
1531
+ if (!Number.isFinite(current)) {
1532
+ throw strictJsonValidationError(
1533
+ context,
1534
+ "non_finite_number",
1535
+ `${context.label} contains a non-finite number at ${path}`,
1536
+ path
1537
+ );
1538
+ }
1539
+ if (Object.is(current, -0)) {
1540
+ throw strictJsonValidationError(
1541
+ context,
1542
+ "negative_zero",
1543
+ `${context.label} contains negative zero at ${path}; normalize it to 0`,
1544
+ path
1545
+ );
1546
+ }
1547
+ return;
1548
+ }
1549
+ if (typeof current === "function" || typeof current === "symbol" || typeof current === "bigint") {
1550
+ throw strictJsonValidationError(
1551
+ context,
1552
+ "unsupported_type",
1553
+ `${context.label} contains a function, symbol, or BigInt at ${path}`,
1554
+ path
1555
+ );
1556
+ }
1557
+ if (typeof current !== "object") {
1558
+ throw strictJsonValidationError(
1559
+ context,
1560
+ "unsupported_type",
1561
+ `${context.label} contains a function, symbol, or BigInt at ${path}`,
1562
+ path
1563
+ );
1564
+ }
1565
+ if (ancestors.has(current)) {
1566
+ throw strictJsonValidationError(
1567
+ context,
1568
+ "cyclic_value",
1569
+ `${context.label} contains a cyclic reference at ${path}`,
1570
+ path
1571
+ );
1572
+ }
1573
+ ancestors.add(current);
1574
+ try {
1575
+ if (Array.isArray(current)) {
1576
+ const keys = Reflect.ownKeys(current);
1577
+ if (keys.length !== current.length + 1 || keys.some(
1578
+ (key) => key !== "length" && (typeof key !== "string" || !/^(0|[1-9]\d*)$/.test(key) || Number(key) >= current.length)
1579
+ )) {
1580
+ throw strictJsonValidationError(
1581
+ context,
1582
+ "unsupported_object",
1583
+ `${context.label} contains a non-JSON array property at ${path}`,
1584
+ path
1585
+ );
1586
+ }
1587
+ for (let index = 0; index < current.length; index++) {
1588
+ const descriptor = Object.getOwnPropertyDescriptor(current, String(index));
1589
+ if (descriptor === void 0 || !descriptor.enumerable || !("value" in descriptor)) {
1590
+ throw strictJsonValidationError(
1591
+ context,
1592
+ "unsupported_object",
1593
+ `${context.label} contains a non-JSON array property at ${path}[${index}]`,
1594
+ `${path}[${index}]`
1595
+ );
1596
+ }
1597
+ visit(descriptor.value, `${path}[${index}]`, false);
1598
+ }
1599
+ return;
1600
+ }
1601
+ const prototype = Object.getPrototypeOf(current);
1602
+ if (prototype !== Object.prototype && prototype !== null) {
1603
+ throw strictJsonValidationError(
1604
+ context,
1605
+ "unsupported_object",
1606
+ `${context.label} contains a non-JSON object at ${path}`,
1607
+ path
1608
+ );
1609
+ }
1610
+ for (const key of Reflect.ownKeys(current)) {
1611
+ if (typeof key === "symbol") {
1612
+ throw strictJsonValidationError(
1613
+ context,
1614
+ "unsupported_type",
1615
+ `${context.label} contains a function, symbol, or BigInt at ${path}`,
1616
+ path
1617
+ );
1618
+ }
1619
+ const propertyPath = /^[A-Za-z_$][\w$]*$/.test(key) ? `${path}.${key}` : `${path}[${JSON.stringify(key)}]`;
1620
+ const descriptor = Object.getOwnPropertyDescriptor(current, key);
1621
+ if (descriptor === void 0 || !descriptor.enumerable || !("value" in descriptor)) {
1622
+ throw strictJsonValidationError(
1623
+ context,
1624
+ "unsupported_object",
1625
+ `${context.label} contains a non-JSON property at ${propertyPath}`,
1626
+ propertyPath
1627
+ );
1628
+ }
1629
+ visit(descriptor.value, propertyPath, false);
1630
+ }
1631
+ } finally {
1632
+ ancestors.delete(current);
1633
+ }
1634
+ };
1635
+ visit(value, "$", context.allowTopLevelUndefined);
1636
+ }
1637
+ function assertFactoryResult(value) {
1638
+ assertStrictJson(value, {
1639
+ code: "factory_result_not_json",
1640
+ label: "Factory result",
1641
+ allowTopLevelUndefined: true
1642
+ });
1643
+ }
1644
+ function assertFactoryStepResult(value, key) {
1645
+ assertStrictJson(value, {
1646
+ code: "factory_step_not_json",
1647
+ label: `Factory step "${key}" result`,
1648
+ allowTopLevelUndefined: false
1649
+ });
1650
+ }
1058
1651
  export {
1059
1652
  CopilotSession
1060
1653
  };
package/dist/types.d.ts CHANGED
@@ -1525,6 +1525,45 @@ export interface CanvasProviderIdentity {
1525
1525
  /** Optional display name surfaced as the canvas extension name. */
1526
1526
  name?: string;
1527
1527
  }
1528
+ /**
1529
+ * Static resource ceilings declared by a factory before it runs.
1530
+ *
1531
+ * @experimental Part of the experimental Agent Factories surface and may
1532
+ * change or be removed in future SDK or CLI releases.
1533
+ */
1534
+ export interface FactoryLimits {
1535
+ /** Maximum number of factory subagents that may run concurrently. Must be positive when present. */
1536
+ maxConcurrentSubagents?: number;
1537
+ /** Maximum total number of factory subagents that may be spawned. Must be positive when present. */
1538
+ maxTotalSubagents?: number;
1539
+ /** Maximum AI credits consumed by factory subagents and descendants. This post-paid ceiling is soft. */
1540
+ maxAiCredits?: number;
1541
+ /**
1542
+ * Maximum accumulated active-execution time, in seconds. Active execution includes the entire extension body,
1543
+ * subprocess waits, queued-agent waits, and sleeps. The limit is armed from the remaining headroom when a run
1544
+ * resumes; time between attempts is not counted. Must be finite and positive when present.
1545
+ */
1546
+ timeoutSeconds?: number;
1547
+ }
1548
+ /**
1549
+ * Registration metadata for an extension-authored factory.
1550
+ *
1551
+ * @experimental Part of the experimental Agent Factories surface and may
1552
+ * change or be removed in future SDK or CLI releases.
1553
+ */
1554
+ export interface FactoryMeta {
1555
+ /** Stable factory name used for invocation. */
1556
+ name: string;
1557
+ /** Human-readable factory description. */
1558
+ description: string;
1559
+ /** Display metadata for the progress phases the factory may report. */
1560
+ phases: Array<{
1561
+ title: string;
1562
+ detail?: string;
1563
+ }>;
1564
+ /** Optional resource ceilings presented to the user before execution. */
1565
+ limits?: FactoryLimits;
1566
+ }
1528
1567
  /**
1529
1568
  * Provider-scoped options for the Copilot API (CAPI).
1530
1569
  *
@@ -1631,13 +1670,8 @@ export interface SessionConfigBase {
1631
1670
  */
1632
1671
  configDirectory?: string;
1633
1672
  /**
1634
- * When true, automatically discovers MCP server configurations (e.g. `.mcp.json`,
1635
- * `.vscode/mcp.json`) and skill directories from the working directory and merges
1636
- * them with any explicitly provided `mcpServers` and `skillDirectories`, with
1637
- * explicit values taking precedence on name collision.
1638
- *
1639
- * Note: custom instruction files (`.github/copilot-instructions.md`, `AGENTS.md`, etc.)
1640
- * are always loaded from the working directory regardless of this setting.
1673
+ * Enables runtime discovery of supported configuration. Explicitly supplied
1674
+ * configuration takes precedence over discovered values.
1641
1675
  *
1642
1676
  * @default false
1643
1677
  */
@@ -56,4 +56,5 @@ The `session` object provides methods for sending messages, logging to the timel
56
56
  ## Further Reading
57
57
 
58
58
  - `examples.md` — Practical code examples for tools, hooks, events, and complete extensions
59
+ - `factories.md`: Authoring, running, resuming, and observing Agent Factories
59
60
  - `agent-author.md` — Step-by-step workflow for agents authoring extensions programmatically