@github/copilot-sdk 1.0.14 → 1.0.15-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/README.md +116 -11
- package/dist/cjs/cliVersion.js +1 -1
- package/dist/cjs/client.js +24 -17
- package/dist/cjs/copilotRequestHandler.js +156 -20
- package/dist/cjs/extension.js +12 -1
- package/dist/cjs/generated/rpc.js +349 -8
- package/dist/cjs/index.js +9 -2
- package/dist/cjs/schema.js +40 -0
- package/dist/cjs/session.js +625 -3
- package/dist/cjs/workflow.js +134 -0
- package/dist/cliVersion.d.ts +1 -1
- package/dist/cliVersion.js +1 -1
- package/dist/client.d.ts +2 -0
- package/dist/client.js +22 -15
- package/dist/copilotRequestHandler.js +156 -20
- package/dist/extension.d.ts +22 -8
- package/dist/extension.js +13 -1
- package/dist/generated/rpc.d.ts +2824 -315
- package/dist/generated/rpc.js +349 -8
- package/dist/generated/session-events.d.ts +565 -2
- package/dist/index.d.ts +3 -1
- package/dist/index.js +5 -1
- package/dist/schema.d.ts +4 -0
- package/dist/schema.js +14 -0
- package/dist/session.d.ts +26 -3
- package/dist/session.js +630 -3
- package/dist/types.d.ts +37 -5
- package/dist/workflow.d.ts +364 -0
- package/dist/workflow.js +106 -0
- package/docs/extensions.md +1 -0
- package/docs/workflows.md +257 -0
- package/package.json +17 -14
package/dist/cjs/session.js
CHANGED
|
@@ -26,8 +26,10 @@ var import_node = require("vscode-jsonrpc/node.js");
|
|
|
26
26
|
var import_rpc = require("./generated/rpc.js");
|
|
27
27
|
var import_canvas = require("./canvas.js");
|
|
28
28
|
var import_telemetry = require("./telemetry.js");
|
|
29
|
+
var import_schema = require("./schema.js");
|
|
29
30
|
var import_types = require("./types.js");
|
|
30
31
|
var import_factory = require("./factory.js");
|
|
32
|
+
var import_workflow = require("./workflow.js");
|
|
31
33
|
function isFactoryResumeErrorCode(value) {
|
|
32
34
|
return value === "not_found" || value === "non_resumable" || value === "already_active" || value === "factory_already_running" || value === "factory_limits_invalid" || value === "factory_session_disposed" || value === "factory_storage_unavailable" || value === "factory_storage_corrupt";
|
|
33
35
|
}
|
|
@@ -37,6 +39,15 @@ function copyDefinedFactoryAgentOption(source, target, key) {
|
|
|
37
39
|
target[key] = value;
|
|
38
40
|
}
|
|
39
41
|
}
|
|
42
|
+
function isWorkflowResumeErrorCode(value) {
|
|
43
|
+
return value === "not_found" || value === "non_resumable" || value === "workflow_run_not_resumable" || value === "already_active" || value === "workflow_already_running" || value === "workflow_limits_invalid" || value === "workflow_session_disposed" || value === "workflow_storage_unavailable" || value === "workflow_storage_corrupt";
|
|
44
|
+
}
|
|
45
|
+
function copyDefinedWorkflowAgentOption(source, target, key) {
|
|
46
|
+
const value = source[key];
|
|
47
|
+
if (value !== void 0) {
|
|
48
|
+
target[key] = value;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
40
51
|
const factoryExecutionStore = new import_node_async_hooks.AsyncLocalStorage();
|
|
41
52
|
function throwIfFactoryExecutionIsActive() {
|
|
42
53
|
if (factoryExecutionStore.getStore()?.active) {
|
|
@@ -45,6 +56,18 @@ function throwIfFactoryExecutionIsActive() {
|
|
|
45
56
|
);
|
|
46
57
|
}
|
|
47
58
|
}
|
|
59
|
+
const workflowExecutionStore = new import_node_async_hooks.AsyncLocalStorage();
|
|
60
|
+
function throwIfWorkflowExecutionIsActive() {
|
|
61
|
+
if (workflowExecutionStore.getStore()?.active) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
"workflow.run, workflow.resume, and workflow.pause are not allowed while a workflow body is running on this call path."
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function runInWorkflowHelperScope(helperScope, callback) {
|
|
68
|
+
const current = workflowExecutionStore.getStore();
|
|
69
|
+
return workflowExecutionStore.run({ active: current?.active ?? false, helperScope }, callback);
|
|
70
|
+
}
|
|
48
71
|
function runInFactoryHelperScope(helperScope, callback) {
|
|
49
72
|
const current = factoryExecutionStore.getStore();
|
|
50
73
|
return factoryExecutionStore.run({ active: current?.active ?? false, helperScope }, callback);
|
|
@@ -126,6 +149,54 @@ async function runFactoryPipeline(items, ...stages) {
|
|
|
126
149
|
})
|
|
127
150
|
);
|
|
128
151
|
}
|
|
152
|
+
async function runWorkflowParallel(thunks) {
|
|
153
|
+
if (!Array.isArray(thunks)) {
|
|
154
|
+
throw new Error(
|
|
155
|
+
"parallel() expects an array of functions, not promises. Wrap each call: () => agent(...)"
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
assertFactoryFanoutSize("parallel", thunks.length);
|
|
159
|
+
if (thunks.some((thunk) => typeof thunk !== "function")) {
|
|
160
|
+
throw new Error(
|
|
161
|
+
"parallel() expects an array of functions, not promises. Wrap each call: () => agent(...)"
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
return Promise.all(
|
|
165
|
+
thunks.map(
|
|
166
|
+
(thunk) => Promise.resolve().then(() => runInWorkflowHelperScope("parallel", thunk)).catch((error) => {
|
|
167
|
+
if (isFactoryFatalError(error)) {
|
|
168
|
+
throw error;
|
|
169
|
+
}
|
|
170
|
+
return null;
|
|
171
|
+
})
|
|
172
|
+
)
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
async function runWorkflowPipeline(items, ...stages) {
|
|
176
|
+
if (!Array.isArray(items)) {
|
|
177
|
+
throw new Error("pipeline(items, ...stages): items must be an array");
|
|
178
|
+
}
|
|
179
|
+
assertFactoryFanoutSize("pipeline", items.length);
|
|
180
|
+
return Promise.all(
|
|
181
|
+
items.map(async (item, index) => {
|
|
182
|
+
let previous = item;
|
|
183
|
+
for (const stage of stages) {
|
|
184
|
+
try {
|
|
185
|
+
previous = await runInWorkflowHelperScope(
|
|
186
|
+
"pipeline",
|
|
187
|
+
() => stage(previous, item, index)
|
|
188
|
+
);
|
|
189
|
+
} catch (error) {
|
|
190
|
+
if (isFactoryFatalError(error)) {
|
|
191
|
+
throw error;
|
|
192
|
+
}
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return previous;
|
|
197
|
+
})
|
|
198
|
+
);
|
|
199
|
+
}
|
|
129
200
|
class FactoryProgressBuffer {
|
|
130
201
|
constructor(send) {
|
|
131
202
|
this.send = send;
|
|
@@ -205,6 +276,85 @@ class FactoryProgressBuffer {
|
|
|
205
276
|
}
|
|
206
277
|
}
|
|
207
278
|
}
|
|
279
|
+
class WorkflowProgressBuffer {
|
|
280
|
+
constructor(send) {
|
|
281
|
+
this.send = send;
|
|
282
|
+
}
|
|
283
|
+
send;
|
|
284
|
+
nextSeq = 0;
|
|
285
|
+
pending = [];
|
|
286
|
+
flushTimer;
|
|
287
|
+
flushTail = Promise.resolve();
|
|
288
|
+
flushError;
|
|
289
|
+
flushFailed = false;
|
|
290
|
+
closed = false;
|
|
291
|
+
enqueue(kind, text) {
|
|
292
|
+
if (this.closed) {
|
|
293
|
+
throw new Error("Cannot log after the workflow run has settled");
|
|
294
|
+
}
|
|
295
|
+
this.pending.push({ seq: this.nextSeq++, kind, text });
|
|
296
|
+
this.scheduleFlush();
|
|
297
|
+
}
|
|
298
|
+
async flush() {
|
|
299
|
+
this.clearFlushTimer();
|
|
300
|
+
const lines = this.pending.splice(0);
|
|
301
|
+
if (lines.length > 0) {
|
|
302
|
+
this.flushTail = this.flushTail.then(async () => {
|
|
303
|
+
try {
|
|
304
|
+
await this.send(lines);
|
|
305
|
+
} catch (error) {
|
|
306
|
+
if (!this.flushFailed) {
|
|
307
|
+
this.flushFailed = true;
|
|
308
|
+
this.flushError = error;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
await this.flushTail;
|
|
314
|
+
if (this.flushFailed) {
|
|
315
|
+
throw this.flushError;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
async close() {
|
|
319
|
+
this.closed = true;
|
|
320
|
+
this.clearFlushTimer();
|
|
321
|
+
const lines = this.pending.splice(0);
|
|
322
|
+
await this.flushTail;
|
|
323
|
+
if (this.flushFailed) {
|
|
324
|
+
console.warn(
|
|
325
|
+
"Ignoring a background workflow progress flush failure after the workflow body settled",
|
|
326
|
+
this.flushError
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
if (lines.length > 0) {
|
|
330
|
+
try {
|
|
331
|
+
await this.send(lines);
|
|
332
|
+
} catch (error) {
|
|
333
|
+
console.warn(
|
|
334
|
+
"Failed to flush final workflow progress after the workflow body settled",
|
|
335
|
+
error
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
scheduleFlush() {
|
|
341
|
+
if (this.flushTimer !== void 0) {
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
this.flushTimer = setTimeout(() => {
|
|
345
|
+
this.flushTimer = void 0;
|
|
346
|
+
void this.flush().catch(() => {
|
|
347
|
+
});
|
|
348
|
+
}, FACTORY_LOG_FLUSH_DELAY_MS);
|
|
349
|
+
this.flushTimer.unref?.();
|
|
350
|
+
}
|
|
351
|
+
clearFlushTimer() {
|
|
352
|
+
if (this.flushTimer !== void 0) {
|
|
353
|
+
clearTimeout(this.flushTimer);
|
|
354
|
+
this.flushTimer = void 0;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
208
358
|
async function awaitFactoryOperation(operation, signal) {
|
|
209
359
|
let rejectAbort;
|
|
210
360
|
const abortPromise = new Promise((_resolve, reject) => {
|
|
@@ -219,11 +369,19 @@ async function awaitFactoryOperation(operation, signal) {
|
|
|
219
369
|
signal.removeEventListener("abort", onAbort);
|
|
220
370
|
}
|
|
221
371
|
}
|
|
372
|
+
async function awaitWorkflowOperation(operation, signal) {
|
|
373
|
+
return awaitFactoryOperation(operation, signal);
|
|
374
|
+
}
|
|
222
375
|
function throwIfFactoryAborted(signal) {
|
|
223
376
|
if (signal.aborted) {
|
|
224
377
|
throw signal.reason ?? new DOMException("Factory run was aborted", "AbortError");
|
|
225
378
|
}
|
|
226
379
|
}
|
|
380
|
+
function throwIfWorkflowAborted(signal) {
|
|
381
|
+
if (signal.aborted) {
|
|
382
|
+
throw signal.reason ?? new DOMException("Workflow run was aborted", "AbortError");
|
|
383
|
+
}
|
|
384
|
+
}
|
|
227
385
|
function isFactoryAbortError(error) {
|
|
228
386
|
return typeof error === "object" && error !== null && "name" in error && error.name === "AbortError";
|
|
229
387
|
}
|
|
@@ -262,6 +420,8 @@ class CopilotSession {
|
|
|
262
420
|
commandHandlers = /* @__PURE__ */ new Map();
|
|
263
421
|
factories = /* @__PURE__ */ new Map();
|
|
264
422
|
factoryAbortControllers = /* @__PURE__ */ new Map();
|
|
423
|
+
workflows = /* @__PURE__ */ new Map();
|
|
424
|
+
workflowAbortControllers = /* @__PURE__ */ new Map();
|
|
265
425
|
permissionHandler;
|
|
266
426
|
mcpAuthHandler;
|
|
267
427
|
userInputHandler;
|
|
@@ -277,6 +437,7 @@ class CopilotSession {
|
|
|
277
437
|
_capabilities = {};
|
|
278
438
|
openCanvasInstances = [];
|
|
279
439
|
disconnected = false;
|
|
440
|
+
pendingStructuredWaits = /* @__PURE__ */ new Set();
|
|
280
441
|
disconnecting = false;
|
|
281
442
|
onDisconnected;
|
|
282
443
|
/** @internal Client session API handlers, populated by CopilotClient during create/resume. */
|
|
@@ -344,6 +505,56 @@ class CopilotSession {
|
|
|
344
505
|
},
|
|
345
506
|
cancel: async (runId) => this.rpc.factory.cancel({ runId })
|
|
346
507
|
};
|
|
508
|
+
workflow = {
|
|
509
|
+
run: (async (nameOrHandle, options) => {
|
|
510
|
+
throwIfWorkflowExecutionIsActive();
|
|
511
|
+
const name = typeof nameOrHandle === "string" ? nameOrHandle : (0, import_workflow.getWorkflowDefinition)(nameOrHandle).meta.name;
|
|
512
|
+
const envelope = await this.rpc.workflow.run({
|
|
513
|
+
name,
|
|
514
|
+
args: options?.args === void 0 ? {} : options.args,
|
|
515
|
+
options: {
|
|
516
|
+
limits: options?.limits,
|
|
517
|
+
notifyOnComplete: options?.notifyOnComplete,
|
|
518
|
+
logPhaseNames: options?.logPhaseNames
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
return this.settleWorkflowRun(envelope);
|
|
522
|
+
}),
|
|
523
|
+
resume: (async (runId, options) => {
|
|
524
|
+
throwIfWorkflowExecutionIsActive();
|
|
525
|
+
let response;
|
|
526
|
+
try {
|
|
527
|
+
response = await this.rpc.workflow.resume({
|
|
528
|
+
runId,
|
|
529
|
+
limits: options?.limits,
|
|
530
|
+
notifyOnComplete: options?.notifyOnComplete,
|
|
531
|
+
logPhaseNames: options?.logPhaseNames
|
|
532
|
+
});
|
|
533
|
+
} catch (error) {
|
|
534
|
+
if (error instanceof import_node.ResponseError && typeof error.data === "object" && error.data !== null) {
|
|
535
|
+
const code = error.data.code;
|
|
536
|
+
if (isWorkflowResumeErrorCode(code)) {
|
|
537
|
+
throw new import_workflow.WorkflowResumeError(code, error.message);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
throw error;
|
|
541
|
+
}
|
|
542
|
+
return this.settleWorkflowRun(response.run);
|
|
543
|
+
}),
|
|
544
|
+
getRun: async (runId) => this.rpc.workflow.getRun({ runId }),
|
|
545
|
+
waitForRun: (runId, options) => this.waitForWorkflowRun(runId, options?.signal),
|
|
546
|
+
listRuns: (async (options) => {
|
|
547
|
+
const page = await this.rpc.workflow.listRuns(options ?? {});
|
|
548
|
+
return options === void 0 ? page.runs : page;
|
|
549
|
+
}),
|
|
550
|
+
getRunDetail: (runId) => this.rpc.workflow.getRunDetail({ runId }),
|
|
551
|
+
getRunProgress: (runId, options = {}) => this.rpc.workflow.getRunProgress({ runId, ...options }),
|
|
552
|
+
pause: async (runId) => {
|
|
553
|
+
throwIfWorkflowExecutionIsActive();
|
|
554
|
+
return this.rpc.workflow.pause({ runId });
|
|
555
|
+
},
|
|
556
|
+
cancel: async (runId) => this.rpc.workflow.cancel({ runId })
|
|
557
|
+
};
|
|
347
558
|
/**
|
|
348
559
|
* Resolve a start/resume envelope into the terminal envelope callers expect.
|
|
349
560
|
*
|
|
@@ -431,6 +642,86 @@ class CopilotSession {
|
|
|
431
642
|
void read();
|
|
432
643
|
});
|
|
433
644
|
}
|
|
645
|
+
settleWorkflowRun(envelope) {
|
|
646
|
+
if ((0, import_workflow.isWorkflowRunTerminal)(envelope.status)) {
|
|
647
|
+
return Promise.resolve(envelope);
|
|
648
|
+
}
|
|
649
|
+
return this.waitForWorkflowRun(envelope.runId);
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Resolve when a workflow run reaches a terminal status.
|
|
653
|
+
*
|
|
654
|
+
* The subscription is installed *before* the first read so a transition
|
|
655
|
+
* landing between the two cannot be missed, and re-reads are serialized so
|
|
656
|
+
* overlapping invalidation events cannot interleave — the run's revision
|
|
657
|
+
* advances once per operation, so a burst of events is common and must
|
|
658
|
+
* collapse into a single in-flight read. A bounded periodic re-read keeps a
|
|
659
|
+
* dropped invalidation from leaving the wait pending forever.
|
|
660
|
+
*/
|
|
661
|
+
waitForWorkflowRun(runId, signal) {
|
|
662
|
+
const abortError = () => signal?.reason ?? new DOMException("Workflow run wait was aborted", "AbortError");
|
|
663
|
+
if (signal?.aborted === true) {
|
|
664
|
+
return Promise.reject(abortError());
|
|
665
|
+
}
|
|
666
|
+
return new Promise((resolve, reject) => {
|
|
667
|
+
let settled = false;
|
|
668
|
+
let reading = false;
|
|
669
|
+
let rereadRequested = false;
|
|
670
|
+
let pollHandle;
|
|
671
|
+
let unsubscribe;
|
|
672
|
+
let onAbort;
|
|
673
|
+
const finish = (complete) => {
|
|
674
|
+
if (settled) {
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
settled = true;
|
|
678
|
+
if (pollHandle !== void 0) {
|
|
679
|
+
clearInterval(pollHandle);
|
|
680
|
+
}
|
|
681
|
+
unsubscribe?.();
|
|
682
|
+
if (onAbort !== void 0) {
|
|
683
|
+
signal?.removeEventListener("abort", onAbort);
|
|
684
|
+
}
|
|
685
|
+
complete();
|
|
686
|
+
};
|
|
687
|
+
const read = async () => {
|
|
688
|
+
if (settled) {
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
if (reading) {
|
|
692
|
+
rereadRequested = true;
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
reading = true;
|
|
696
|
+
try {
|
|
697
|
+
do {
|
|
698
|
+
rereadRequested = false;
|
|
699
|
+
const envelope = await this.rpc.workflow.getRun({ runId });
|
|
700
|
+
if ((0, import_workflow.isWorkflowRunTerminal)(envelope.status)) {
|
|
701
|
+
finish(() => resolve(envelope));
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
} while (rereadRequested && !settled);
|
|
705
|
+
} catch (error) {
|
|
706
|
+
finish(() => reject(error));
|
|
707
|
+
} finally {
|
|
708
|
+
reading = false;
|
|
709
|
+
}
|
|
710
|
+
};
|
|
711
|
+
if (signal !== void 0) {
|
|
712
|
+
onAbort = () => finish(() => reject(abortError()));
|
|
713
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
714
|
+
}
|
|
715
|
+
unsubscribe = this.on("factory.run_updated", (event) => {
|
|
716
|
+
if (event.data.runId === runId) {
|
|
717
|
+
void read();
|
|
718
|
+
}
|
|
719
|
+
});
|
|
720
|
+
pollHandle = setInterval(() => void read(), 5e3);
|
|
721
|
+
pollHandle.unref?.();
|
|
722
|
+
void read();
|
|
723
|
+
});
|
|
724
|
+
}
|
|
434
725
|
/**
|
|
435
726
|
* Typed session-scoped RPC methods.
|
|
436
727
|
*/
|
|
@@ -493,13 +784,54 @@ class CopilotSession {
|
|
|
493
784
|
attachments: options.attachments,
|
|
494
785
|
mode: options.mode,
|
|
495
786
|
agentMode: options.agentMode,
|
|
496
|
-
requestHeaders: options.requestHeaders
|
|
787
|
+
requestHeaders: options.requestHeaders,
|
|
788
|
+
...options.responseSchema ? {
|
|
789
|
+
responseFormat: {
|
|
790
|
+
type: "json_schema",
|
|
791
|
+
jsonSchema: {
|
|
792
|
+
name: "response",
|
|
793
|
+
strict: true,
|
|
794
|
+
schema: (0, import_schema.toJsonSchema)(options.responseSchema)
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
} : {}
|
|
497
798
|
});
|
|
498
799
|
return response.messageId;
|
|
499
800
|
}
|
|
500
|
-
async sendAndWait(optionsOrPrompt, timeout) {
|
|
801
|
+
async sendAndWait(optionsOrPrompt, schemaOrTimeout, timeout) {
|
|
501
802
|
const options = typeof optionsOrPrompt === "string" ? { prompt: optionsOrPrompt } : optionsOrPrompt;
|
|
502
|
-
const
|
|
803
|
+
const typedSchema = (0, import_schema.isResponseSchema)(schemaOrTimeout) ? schemaOrTimeout : void 0;
|
|
804
|
+
if (schemaOrTimeout !== void 0 && typeof schemaOrTimeout !== "number" && !typedSchema) {
|
|
805
|
+
throw new TypeError(
|
|
806
|
+
"The second argument must be a timeout or a schema with toJSONSchema() and parse(). Pass raw JSON Schema in options.responseSchema instead."
|
|
807
|
+
);
|
|
808
|
+
}
|
|
809
|
+
const effectiveTimeout = (typeof schemaOrTimeout === "number" ? schemaOrTimeout : timeout) ?? 6e4;
|
|
810
|
+
if (typedSchema && options.responseSchema) {
|
|
811
|
+
throw new Error(
|
|
812
|
+
"Do not specify responseSchema in options when requesting a typed response."
|
|
813
|
+
);
|
|
814
|
+
}
|
|
815
|
+
if (typedSchema && options.mode === "immediate") {
|
|
816
|
+
throw new Error(
|
|
817
|
+
"Structured output cannot be requested on an immediate steering message."
|
|
818
|
+
);
|
|
819
|
+
}
|
|
820
|
+
if (typedSchema || options.responseSchema) {
|
|
821
|
+
const message = await this.sendAndWaitForStructuredMessage(
|
|
822
|
+
typedSchema ? { ...options, responseSchema: typedSchema } : options,
|
|
823
|
+
effectiveTimeout
|
|
824
|
+
);
|
|
825
|
+
if (typedSchema) {
|
|
826
|
+
if (!message) {
|
|
827
|
+
throw new Error(
|
|
828
|
+
"The requested run completed without a structured assistant response."
|
|
829
|
+
);
|
|
830
|
+
}
|
|
831
|
+
return typedSchema.parse(JSON.parse(message.data.content));
|
|
832
|
+
}
|
|
833
|
+
return message;
|
|
834
|
+
}
|
|
503
835
|
let resolveOutcome;
|
|
504
836
|
const outcomePromise = new Promise((resolve) => {
|
|
505
837
|
resolveOutcome = resolve;
|
|
@@ -541,12 +873,95 @@ class CopilotSession {
|
|
|
541
873
|
unsubscribe();
|
|
542
874
|
}
|
|
543
875
|
}
|
|
876
|
+
async sendAndWaitForStructuredMessage(options, timeout) {
|
|
877
|
+
if (this.disconnected) {
|
|
878
|
+
throw new Error("Session is disconnected");
|
|
879
|
+
}
|
|
880
|
+
let resolveOutcome;
|
|
881
|
+
const outcomePromise = new Promise((resolve) => {
|
|
882
|
+
resolveOutcome = resolve;
|
|
883
|
+
});
|
|
884
|
+
const fail = (error) => resolveOutcome({ kind: "error", error });
|
|
885
|
+
let messageId;
|
|
886
|
+
let consumed = false;
|
|
887
|
+
let lastMessage;
|
|
888
|
+
const buffered = [];
|
|
889
|
+
const observe = (event) => {
|
|
890
|
+
if (event.agentId) return;
|
|
891
|
+
if (event.type === "user.message" && event.data.messageId === messageId) {
|
|
892
|
+
consumed = true;
|
|
893
|
+
} else if (event.type === "assistant.message" && event.data.originatingMessageId === messageId) {
|
|
894
|
+
consumed = true;
|
|
895
|
+
lastMessage = event.data.toolRequests?.length ? void 0 : event;
|
|
896
|
+
} else if (consumed && event.type === "session.idle" && event.data.mode !== "autopilot") {
|
|
897
|
+
if (event.data.aborted) {
|
|
898
|
+
fail(
|
|
899
|
+
new Error(
|
|
900
|
+
"The requested run was aborted before a structured result was completed."
|
|
901
|
+
)
|
|
902
|
+
);
|
|
903
|
+
} else {
|
|
904
|
+
resolveOutcome({ kind: "idle", message: lastMessage });
|
|
905
|
+
}
|
|
906
|
+
} else if (consumed && event.type === "session.error") {
|
|
907
|
+
const error = new Error(event.data.message);
|
|
908
|
+
error.stack = event.data.stack;
|
|
909
|
+
fail(error);
|
|
910
|
+
}
|
|
911
|
+
};
|
|
912
|
+
const unsubscribe = this.on((event) => {
|
|
913
|
+
if (event.type !== "user.message" && event.type !== "assistant.message" && event.type !== "session.idle" && event.type !== "session.error") {
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
if (messageId === void 0) {
|
|
917
|
+
buffered.push(event);
|
|
918
|
+
} else {
|
|
919
|
+
observe(event);
|
|
920
|
+
}
|
|
921
|
+
});
|
|
922
|
+
this.pendingStructuredWaits.add(fail);
|
|
923
|
+
const timer = setTimeout(
|
|
924
|
+
() => fail(new Error(`Timeout after ${timeout}ms waiting for the structured response`)),
|
|
925
|
+
timeout
|
|
926
|
+
);
|
|
927
|
+
try {
|
|
928
|
+
const sendOutcome = this.send(options).then(
|
|
929
|
+
(id) => {
|
|
930
|
+
if (!id) {
|
|
931
|
+
throw new Error(
|
|
932
|
+
"The runtime did not return a message ID for the structured send."
|
|
933
|
+
);
|
|
934
|
+
}
|
|
935
|
+
messageId = id;
|
|
936
|
+
for (const event of buffered) observe(event);
|
|
937
|
+
buffered.length = 0;
|
|
938
|
+
return outcomePromise;
|
|
939
|
+
},
|
|
940
|
+
(error) => ({
|
|
941
|
+
kind: "error",
|
|
942
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
943
|
+
})
|
|
944
|
+
);
|
|
945
|
+
const outcome = await Promise.race([sendOutcome, outcomePromise]);
|
|
946
|
+
if (outcome.kind === "error") throw outcome.error;
|
|
947
|
+
return outcome.message;
|
|
948
|
+
} finally {
|
|
949
|
+
clearTimeout(timer);
|
|
950
|
+
buffered.length = 0;
|
|
951
|
+
unsubscribe();
|
|
952
|
+
this.pendingStructuredWaits.delete(fail);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
544
955
|
/** @internal */
|
|
545
956
|
_markDisconnected() {
|
|
546
957
|
if (this.disconnected) {
|
|
547
958
|
return;
|
|
548
959
|
}
|
|
549
960
|
this.disconnected = true;
|
|
961
|
+
for (const fail of this.pendingStructuredWaits) {
|
|
962
|
+
fail(new Error("Session disconnected while waiting for a structured response"));
|
|
963
|
+
}
|
|
964
|
+
this.pendingStructuredWaits.clear();
|
|
550
965
|
for (const controller of this.pendingExternalTools.values()) {
|
|
551
966
|
controller.abort();
|
|
552
967
|
}
|
|
@@ -569,6 +984,13 @@ class CopilotSession {
|
|
|
569
984
|
}
|
|
570
985
|
}
|
|
571
986
|
this.factoryAbortControllers.clear();
|
|
987
|
+
this.workflows.clear();
|
|
988
|
+
for (const controllersForRun of this.workflowAbortControllers.values()) {
|
|
989
|
+
for (const controller of controllersForRun.values()) {
|
|
990
|
+
controller.abort();
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
this.workflowAbortControllers.clear();
|
|
572
994
|
this.transformCallbacks?.clear();
|
|
573
995
|
}
|
|
574
996
|
/** @internal */
|
|
@@ -1202,6 +1624,192 @@ class CopilotSession {
|
|
|
1202
1624
|
}
|
|
1203
1625
|
};
|
|
1204
1626
|
}
|
|
1627
|
+
/**
|
|
1628
|
+
* Registers workflow closures and reverse-RPC handlers for this session.
|
|
1629
|
+
*
|
|
1630
|
+
* @param workflows - Workflow handles declared by the joining extension.
|
|
1631
|
+
* @internal Called by the SDK when an extension joins a session.
|
|
1632
|
+
*/
|
|
1633
|
+
registerWorkflows(workflows) {
|
|
1634
|
+
this.workflows.clear();
|
|
1635
|
+
if (!workflows || workflows.length === 0) {
|
|
1636
|
+
delete this.clientSessionApis.workflow;
|
|
1637
|
+
return;
|
|
1638
|
+
}
|
|
1639
|
+
for (const handle of workflows) {
|
|
1640
|
+
const definition = (0, import_workflow.getWorkflowDefinition)(handle);
|
|
1641
|
+
if (this.workflows.has(definition.meta.name)) {
|
|
1642
|
+
throw new Error(
|
|
1643
|
+
`Duplicate workflow name "${definition.meta.name}". Workflow names must be unique within a joinSession call.`
|
|
1644
|
+
);
|
|
1645
|
+
}
|
|
1646
|
+
this.workflows.set(definition.meta.name, definition);
|
|
1647
|
+
}
|
|
1648
|
+
const self = this;
|
|
1649
|
+
this.clientSessionApis.workflow = {
|
|
1650
|
+
async execute(params) {
|
|
1651
|
+
const definition = self.workflows.get(params.name);
|
|
1652
|
+
if (!definition) {
|
|
1653
|
+
const message = `No workflow registered with name "${params.name}"`;
|
|
1654
|
+
throw new import_node.ResponseError(import_node.ErrorCodes.InvalidParams, message, {
|
|
1655
|
+
code: "workflow_not_found",
|
|
1656
|
+
name: params.name
|
|
1657
|
+
});
|
|
1658
|
+
}
|
|
1659
|
+
const controller = new AbortController();
|
|
1660
|
+
let controllersForRun = self.workflowAbortControllers.get(params.runId);
|
|
1661
|
+
if (controllersForRun === void 0) {
|
|
1662
|
+
controllersForRun = /* @__PURE__ */ new Map();
|
|
1663
|
+
self.workflowAbortControllers.set(params.runId, controllersForRun);
|
|
1664
|
+
}
|
|
1665
|
+
controllersForRun.set(params.executionToken, controller);
|
|
1666
|
+
const progress = new WorkflowProgressBuffer(async (lines) => {
|
|
1667
|
+
await self.rpc.workflow.log({
|
|
1668
|
+
runId: params.runId,
|
|
1669
|
+
executionToken: params.executionToken,
|
|
1670
|
+
lines
|
|
1671
|
+
});
|
|
1672
|
+
});
|
|
1673
|
+
try {
|
|
1674
|
+
const context = {
|
|
1675
|
+
runId: params.runId,
|
|
1676
|
+
args: params.args,
|
|
1677
|
+
session: self,
|
|
1678
|
+
signal: controller.signal,
|
|
1679
|
+
phase: (title) => {
|
|
1680
|
+
throwIfWorkflowAborted(controller.signal);
|
|
1681
|
+
progress.enqueue("phase", title);
|
|
1682
|
+
},
|
|
1683
|
+
log: (message) => {
|
|
1684
|
+
throwIfWorkflowAborted(controller.signal);
|
|
1685
|
+
progress.enqueue("log", message);
|
|
1686
|
+
},
|
|
1687
|
+
agent: async (prompt, options = {}) => {
|
|
1688
|
+
await progress.flush();
|
|
1689
|
+
const opts = {};
|
|
1690
|
+
for (const key of import_workflow.WORKFLOW_AGENT_OPTION_KEYS) {
|
|
1691
|
+
copyDefinedWorkflowAgentOption(options, opts, key);
|
|
1692
|
+
}
|
|
1693
|
+
const response = await awaitWorkflowOperation(
|
|
1694
|
+
() => self.rpc.workflow.agent({
|
|
1695
|
+
workflowRunId: params.runId,
|
|
1696
|
+
executionToken: params.executionToken,
|
|
1697
|
+
prompt,
|
|
1698
|
+
opts
|
|
1699
|
+
}),
|
|
1700
|
+
controller.signal
|
|
1701
|
+
);
|
|
1702
|
+
return response.result ?? null;
|
|
1703
|
+
},
|
|
1704
|
+
step: async (key, producer, options = {}) => {
|
|
1705
|
+
await progress.flush();
|
|
1706
|
+
if (options.volatile) {
|
|
1707
|
+
throwIfWorkflowAborted(controller.signal);
|
|
1708
|
+
return producer();
|
|
1709
|
+
}
|
|
1710
|
+
const cached = await awaitWorkflowOperation(
|
|
1711
|
+
() => self.rpc.workflow.journal.get({
|
|
1712
|
+
runId: params.runId,
|
|
1713
|
+
executionToken: params.executionToken,
|
|
1714
|
+
key
|
|
1715
|
+
}),
|
|
1716
|
+
controller.signal
|
|
1717
|
+
);
|
|
1718
|
+
if (cached.hit) {
|
|
1719
|
+
if (cached.resultJson === void 0) {
|
|
1720
|
+
throw new Error(
|
|
1721
|
+
`step("${key}") journal returned a hit without a result`
|
|
1722
|
+
);
|
|
1723
|
+
}
|
|
1724
|
+
assertWorkflowStepResult(cached.resultJson, key);
|
|
1725
|
+
return cached.resultJson;
|
|
1726
|
+
}
|
|
1727
|
+
const result2 = await producer();
|
|
1728
|
+
assertWorkflowStepResult(result2, key);
|
|
1729
|
+
await awaitWorkflowOperation(
|
|
1730
|
+
() => self.rpc.workflow.journal.put({
|
|
1731
|
+
runId: params.runId,
|
|
1732
|
+
executionToken: params.executionToken,
|
|
1733
|
+
key,
|
|
1734
|
+
resultJson: result2
|
|
1735
|
+
}),
|
|
1736
|
+
controller.signal
|
|
1737
|
+
);
|
|
1738
|
+
return result2;
|
|
1739
|
+
},
|
|
1740
|
+
pause: async (key) => {
|
|
1741
|
+
if (typeof key !== "string" || key.length === 0) {
|
|
1742
|
+
throw new Error("Workflow pause checkpoint key must not be empty");
|
|
1743
|
+
}
|
|
1744
|
+
const helperScope = workflowExecutionStore.getStore()?.helperScope;
|
|
1745
|
+
if (helperScope !== void 0) {
|
|
1746
|
+
throw new Error(
|
|
1747
|
+
`Workflow pause checkpoints are not allowed inside ${helperScope}() branches`
|
|
1748
|
+
);
|
|
1749
|
+
}
|
|
1750
|
+
await progress.flush();
|
|
1751
|
+
const response = await awaitWorkflowOperation(
|
|
1752
|
+
() => self.internalRpc.workflow.pauseAtCheckpoint({
|
|
1753
|
+
runId: params.runId,
|
|
1754
|
+
executionToken: params.executionToken,
|
|
1755
|
+
key
|
|
1756
|
+
}),
|
|
1757
|
+
controller.signal
|
|
1758
|
+
);
|
|
1759
|
+
switch (response.action) {
|
|
1760
|
+
case "continue":
|
|
1761
|
+
return;
|
|
1762
|
+
case "pause":
|
|
1763
|
+
await awaitWorkflowOperation(
|
|
1764
|
+
() => new Promise(() => {
|
|
1765
|
+
}),
|
|
1766
|
+
controller.signal
|
|
1767
|
+
);
|
|
1768
|
+
}
|
|
1769
|
+
},
|
|
1770
|
+
parallel: runWorkflowParallel,
|
|
1771
|
+
pipeline: runWorkflowPipeline,
|
|
1772
|
+
workflow: async () => {
|
|
1773
|
+
throw new Error("nested workflows are not supported");
|
|
1774
|
+
}
|
|
1775
|
+
};
|
|
1776
|
+
const execution = { active: true };
|
|
1777
|
+
const result = await workflowExecutionStore.run(execution, async () => {
|
|
1778
|
+
try {
|
|
1779
|
+
return await definition.run(context);
|
|
1780
|
+
} finally {
|
|
1781
|
+
execution.active = false;
|
|
1782
|
+
}
|
|
1783
|
+
});
|
|
1784
|
+
if (result === void 0) {
|
|
1785
|
+
return {};
|
|
1786
|
+
}
|
|
1787
|
+
assertWorkflowResult(result);
|
|
1788
|
+
return { result };
|
|
1789
|
+
} finally {
|
|
1790
|
+
try {
|
|
1791
|
+
await progress.close();
|
|
1792
|
+
} finally {
|
|
1793
|
+
const controllersForRun2 = self.workflowAbortControllers.get(params.runId);
|
|
1794
|
+
if (controllersForRun2?.get(params.executionToken) === controller) {
|
|
1795
|
+
controllersForRun2.delete(params.executionToken);
|
|
1796
|
+
if (controllersForRun2.size === 0) {
|
|
1797
|
+
self.workflowAbortControllers.delete(params.runId);
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
},
|
|
1803
|
+
async abort(params) {
|
|
1804
|
+
const controllersForRun = self.workflowAbortControllers.get(params.runId);
|
|
1805
|
+
const controller = controllersForRun?.get(params.executionToken);
|
|
1806
|
+
if (controller !== void 0) {
|
|
1807
|
+
controller.abort(new DOMException("Workflow run was aborted", "AbortError"));
|
|
1808
|
+
}
|
|
1809
|
+
return {};
|
|
1810
|
+
}
|
|
1811
|
+
};
|
|
1812
|
+
}
|
|
1205
1813
|
/**
|
|
1206
1814
|
* Registers per-provider {@link BearerTokenProvider} callbacks for BYOK providers
|
|
1207
1815
|
* configured with managed-identity / on-demand bearer-token auth.
|
|
@@ -1896,6 +2504,20 @@ function assertFactoryStepResult(value, key) {
|
|
|
1896
2504
|
allowTopLevelUndefined: false
|
|
1897
2505
|
});
|
|
1898
2506
|
}
|
|
2507
|
+
function assertWorkflowResult(value) {
|
|
2508
|
+
assertStrictJson(value, {
|
|
2509
|
+
code: "workflow_result_not_json",
|
|
2510
|
+
label: "Workflow result",
|
|
2511
|
+
allowTopLevelUndefined: true
|
|
2512
|
+
});
|
|
2513
|
+
}
|
|
2514
|
+
function assertWorkflowStepResult(value, key) {
|
|
2515
|
+
assertStrictJson(value, {
|
|
2516
|
+
code: "workflow_step_not_json",
|
|
2517
|
+
label: `Workflow step "${key}" result`,
|
|
2518
|
+
allowTopLevelUndefined: false
|
|
2519
|
+
});
|
|
2520
|
+
}
|
|
1899
2521
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1900
2522
|
0 && (module.exports = {
|
|
1901
2523
|
CopilotSession
|