@github/copilot-sdk 1.0.9-preview.0 → 1.0.9-preview.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/dist/cjs/client.js +25 -6
- package/dist/cjs/extension.js +21 -6
- package/dist/cjs/factory.js +123 -0
- package/dist/cjs/generated/rpc.js +0 -5
- package/dist/cjs/index.js +9 -2
- package/dist/cjs/session.js +589 -0
- package/dist/client.d.ts +1 -0
- package/dist/client.js +25 -6
- package/dist/extension.d.ts +11 -2
- package/dist/extension.js +22 -6
- package/dist/factory.d.ts +273 -0
- package/dist/factory.js +96 -0
- package/dist/generated/rpc.d.ts +0 -13
- package/dist/generated/rpc.js +0 -5
- package/dist/generated/session-events.d.ts +1 -31
- package/dist/index.d.ts +3 -1
- package/dist/index.js +5 -1
- package/dist/session.d.ts +21 -0
- package/dist/session.js +593 -0
- package/dist/types.d.ts +41 -7
- package/docs/extensions.md +1 -0
- package/docs/factories.md +240 -0
- package/docs/factory-patterns.md +194 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import { CopilotClient } from "./client.js";
|
|
|
2
2
|
import { RuntimeConnection } from "./types.js";
|
|
3
3
|
import { BuiltInTools, ToolSet } from "./toolSet.js";
|
|
4
4
|
import { CopilotSession } from "./session.js";
|
|
5
|
+
import { defineFactory, FactoryResumeError, isFactoryRunTerminal } from "./factory.js";
|
|
5
6
|
import {
|
|
6
7
|
Canvas,
|
|
7
8
|
CanvasError,
|
|
@@ -29,6 +30,7 @@ export {
|
|
|
29
30
|
CopilotWebSocketCloseStatus,
|
|
30
31
|
CopilotWebSocketForwarder,
|
|
31
32
|
CopilotWebSocketHandler,
|
|
33
|
+
FactoryResumeError,
|
|
32
34
|
RuntimeConnection,
|
|
33
35
|
SYSTEM_MESSAGE_SECTIONS,
|
|
34
36
|
SessionFsSqliteTransactionFailure,
|
|
@@ -37,5 +39,7 @@ export {
|
|
|
37
39
|
convertMcpCallToolResult,
|
|
38
40
|
createCanvas,
|
|
39
41
|
createSessionFsAdapter,
|
|
40
|
-
|
|
42
|
+
defineFactory,
|
|
43
|
+
defineTool,
|
|
44
|
+
isFactoryRunTerminal
|
|
41
45
|
};
|
package/dist/session.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createSessionRpc } from "./generated/rpc.js";
|
|
2
2
|
import type { OpenCanvasInstance } from "./generated/rpc.js";
|
|
3
3
|
import type { MessageOptions, ContextTier, ReasoningEffort, ReasoningSummary, ModelCapabilitiesOverride, SessionCapabilities, SessionEvent, SessionEventHandler, SessionEventType, SessionUiApi, TypedSessionEventHandler } from "./types.js";
|
|
4
|
+
import { type SessionFactoryApi } from "./factory.js";
|
|
4
5
|
/** Assistant message event - the final response from the assistant. */
|
|
5
6
|
export type AssistantMessageEvent = Extract<SessionEvent, {
|
|
6
7
|
type: "assistant.message";
|
|
@@ -45,6 +46,8 @@ export declare class CopilotSession {
|
|
|
45
46
|
private canvases;
|
|
46
47
|
private bearerTokenProviders;
|
|
47
48
|
private commandHandlers;
|
|
49
|
+
private factories;
|
|
50
|
+
private factoryAbortControllers;
|
|
48
51
|
private permissionHandler?;
|
|
49
52
|
private mcpAuthHandler?;
|
|
50
53
|
private userInputHandler?;
|
|
@@ -58,6 +61,24 @@ export declare class CopilotSession {
|
|
|
58
61
|
private _capabilities;
|
|
59
62
|
private openCanvasInstances;
|
|
60
63
|
private disconnected;
|
|
64
|
+
/**
|
|
65
|
+
* Friendly factory API for running registered factories by name or handle.
|
|
66
|
+
*
|
|
67
|
+
* @experimental Part of the experimental Agent Factories surface and may
|
|
68
|
+
* change or be removed in future SDK or CLI releases.
|
|
69
|
+
*/
|
|
70
|
+
readonly factory: SessionFactoryApi;
|
|
71
|
+
/**
|
|
72
|
+
* Resolve when a factory run reaches a terminal status.
|
|
73
|
+
*
|
|
74
|
+
* The subscription is installed *before* the first read so a transition
|
|
75
|
+
* landing between the two cannot be missed, and re-reads are serialized so
|
|
76
|
+
* overlapping invalidation events cannot interleave — the run's revision
|
|
77
|
+
* advances once per operation, so a burst of events is common and must
|
|
78
|
+
* collapse into a single in-flight read. A bounded periodic re-read keeps a
|
|
79
|
+
* dropped invalidation from leaving the wait pending forever.
|
|
80
|
+
*/
|
|
81
|
+
private waitForFactoryRun;
|
|
61
82
|
/**
|
|
62
83
|
* Typed session-scoped RPC methods.
|
|
63
84
|
*/
|
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
|
};
|