comfyui-node 1.6.4 → 1.6.5
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 +71 -9
- package/dist/.tsbuildinfo +1 -1
- package/dist/call-wrapper.d.ts.map +1 -1
- package/dist/call-wrapper.js +859 -856
- package/dist/call-wrapper.js.map +1 -1
- package/dist/client.d.ts +27 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +87 -6
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/multipool/multi-workflow-pool.d.ts.map +1 -1
- package/dist/multipool/multi-workflow-pool.js +61 -30
- package/dist/multipool/multi-workflow-pool.js.map +1 -1
- package/dist/pool/WorkflowPool.d.ts.map +1 -1
- package/dist/pool/WorkflowPool.js +855 -845
- package/dist/pool/WorkflowPool.js.map +1 -1
- package/dist/pool/client/ClientManager.js +215 -215
- package/dist/pool/index.js +3 -3
- package/dist/pool/types/job.d.ts +15 -0
- package/dist/pool/types/job.d.ts.map +1 -1
- package/dist/types/event.d.ts +17 -1
- package/dist/types/event.d.ts.map +1 -1
- package/dist/utils/model-loading.d.ts +158 -0
- package/dist/utils/model-loading.d.ts.map +1 -0
- package/dist/utils/model-loading.js +273 -0
- package/dist/utils/model-loading.js.map +1 -0
- package/package.json +9 -5
package/dist/call-wrapper.js
CHANGED
|
@@ -1,857 +1,860 @@
|
|
|
1
|
-
import { FailedCacheError, WentMissingError, EnqueueFailedError, DisconnectedError, CustomEventError, ExecutionFailedError, ExecutionInterruptedError, MissingNodeError } from "./types/error.js";
|
|
2
|
-
import { buildEnqueueFailedError } from "./utils/response-error.js";
|
|
3
|
-
const DISCONNECT_FAILURE_GRACE_MS = 5000;
|
|
4
|
-
const CALL_WRAPPER_DEBUG = process.env.WORKFLOW_POOL_DEBUG === "1";
|
|
5
|
-
/**
|
|
6
|
-
* Represents a wrapper class for making API calls using the ComfyApi client.
|
|
7
|
-
* Provides methods for setting callback functions and executing the job.
|
|
8
|
-
*/
|
|
9
|
-
export class CallWrapper {
|
|
10
|
-
client;
|
|
11
|
-
prompt;
|
|
12
|
-
started = false;
|
|
13
|
-
isCompletingSuccessfully = false;
|
|
14
|
-
promptId;
|
|
15
|
-
output = {};
|
|
16
|
-
onPreviewFn;
|
|
17
|
-
onPreviewMetaFn;
|
|
18
|
-
onPendingFn;
|
|
19
|
-
onStartFn;
|
|
20
|
-
onOutputFn;
|
|
21
|
-
onFinishedFn;
|
|
22
|
-
onFailedFn;
|
|
23
|
-
onProgressFn;
|
|
24
|
-
jobResolveFn;
|
|
25
|
-
jobDoneResolved = false;
|
|
26
|
-
pendingCompletion = null;
|
|
27
|
-
cancellationRequested = false;
|
|
28
|
-
promptLoadTrigger = null;
|
|
29
|
-
disconnectRecoveryActive = false;
|
|
30
|
-
disconnectFailureTimer = null;
|
|
31
|
-
onReconnectHandlerOffFn;
|
|
32
|
-
onReconnectFailedHandlerOffFn;
|
|
33
|
-
onDisconnectedHandlerOffFn;
|
|
34
|
-
checkExecutingOffFn;
|
|
35
|
-
checkExecutedOffFn;
|
|
36
|
-
progressHandlerOffFn;
|
|
37
|
-
previewHandlerOffFn;
|
|
38
|
-
executionHandlerOffFn;
|
|
39
|
-
errorHandlerOffFn;
|
|
40
|
-
executionEndSuccessOffFn;
|
|
41
|
-
statusHandlerOffFn;
|
|
42
|
-
interruptionHandlerOffFn;
|
|
43
|
-
/**
|
|
44
|
-
* Constructs a new CallWrapper instance.
|
|
45
|
-
* @param client The ComfyApi client.
|
|
46
|
-
* @param workflow The workflow object.
|
|
47
|
-
*/
|
|
48
|
-
constructor(client, workflow) {
|
|
49
|
-
this.client = client;
|
|
50
|
-
this.prompt = workflow;
|
|
51
|
-
return this;
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Set the callback function to be called when a preview event occurs.
|
|
55
|
-
*
|
|
56
|
-
* @param fn - The callback function to be called. It receives a Blob object representing the event and an optional promptId string.
|
|
57
|
-
* @returns The current instance of the CallWrapper.
|
|
58
|
-
*/
|
|
59
|
-
onPreview(fn) {
|
|
60
|
-
this.onPreviewFn = fn;
|
|
61
|
-
return this;
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Set the callback function to be called when a preview-with-metadata event occurs.
|
|
65
|
-
*/
|
|
66
|
-
onPreviewMeta(fn) {
|
|
67
|
-
this.onPreviewMetaFn = fn;
|
|
68
|
-
return this;
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Set a callback function to be executed when the job is queued.
|
|
72
|
-
* @param {Function} fn - The callback function to be executed.
|
|
73
|
-
* @returns The current instance of the CallWrapper.
|
|
74
|
-
*/
|
|
75
|
-
onPending(fn) {
|
|
76
|
-
this.onPendingFn = fn;
|
|
77
|
-
return this;
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* Set the callback function to be executed when the job start.
|
|
81
|
-
*
|
|
82
|
-
* @param fn - The callback function to be executed. It can optionally receive a `promptId` parameter.
|
|
83
|
-
* @returns The current instance of the CallWrapper.
|
|
84
|
-
*/
|
|
85
|
-
onStart(fn) {
|
|
86
|
-
this.onStartFn = fn;
|
|
87
|
-
return this;
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* Sets the callback function to handle the output node when the workflow is executing. This is
|
|
91
|
-
* useful when you want to handle the output of each nodes as they are being processed.
|
|
92
|
-
*
|
|
93
|
-
* All the nodes defined in the `mapOutputKeys` will be passed to this function when node is executed.
|
|
94
|
-
*
|
|
95
|
-
* @param fn - The callback function to handle the output.
|
|
96
|
-
* @returns The current instance of the class.
|
|
97
|
-
*/
|
|
98
|
-
onOutput(fn) {
|
|
99
|
-
this.onOutputFn = fn;
|
|
100
|
-
return this;
|
|
101
|
-
}
|
|
102
|
-
/**
|
|
103
|
-
* Set the callback function to be executed when the asynchronous operation is finished.
|
|
104
|
-
*
|
|
105
|
-
* @param fn - The callback function to be executed. It receives the data returned by the operation
|
|
106
|
-
* and an optional promptId parameter.
|
|
107
|
-
* @returns The current instance of the CallWrapper.
|
|
108
|
-
*/
|
|
109
|
-
onFinished(fn) {
|
|
110
|
-
this.onFinishedFn = fn;
|
|
111
|
-
return this;
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* Set the callback function to be executed when the API call fails.
|
|
115
|
-
*
|
|
116
|
-
* @param fn - The callback function to be executed when the API call fails.
|
|
117
|
-
* It receives an `Error` object as the first parameter and an optional `promptId` as the second parameter.
|
|
118
|
-
* @returns The current instance of the CallWrapper.
|
|
119
|
-
*/
|
|
120
|
-
onFailed(fn) {
|
|
121
|
-
this.onFailedFn = fn;
|
|
122
|
-
return this;
|
|
123
|
-
}
|
|
124
|
-
/**
|
|
125
|
-
* Set a callback function to be called when progress information is available.
|
|
126
|
-
* @param fn - The callback function to be called with the progress information.
|
|
127
|
-
* @returns The current instance of the CallWrapper.
|
|
128
|
-
*/
|
|
129
|
-
onProgress(fn) {
|
|
130
|
-
this.onProgressFn = fn;
|
|
131
|
-
return this;
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* Run the call wrapper and returns the output of the executed job.
|
|
135
|
-
* If the job is already cached, it returns the cached output.
|
|
136
|
-
* If the job is not cached, it executes the job and returns the output.
|
|
137
|
-
*
|
|
138
|
-
* @returns A promise that resolves to the output of the executed job,
|
|
139
|
-
* or `undefined` if the job is not found,
|
|
140
|
-
* or `false` if the job execution fails.
|
|
141
|
-
*/
|
|
142
|
-
async run() {
|
|
143
|
-
/**
|
|
144
|
-
* Start the job execution.
|
|
145
|
-
*/
|
|
146
|
-
this.emitLog("CallWrapper.run", "enqueue start");
|
|
147
|
-
this.pendingCompletion = null;
|
|
148
|
-
this.jobResolveFn = undefined;
|
|
149
|
-
this.jobDoneResolved = false;
|
|
150
|
-
this.cancellationRequested = false;
|
|
151
|
-
this.promptLoadTrigger = null;
|
|
152
|
-
const job = await this.enqueueJob();
|
|
153
|
-
if (!job) {
|
|
154
|
-
// enqueueJob already invoked onFailed with a rich error instance; just abort.
|
|
155
|
-
this.emitLog("CallWrapper.run", "enqueue failed -> abort");
|
|
156
|
-
return false;
|
|
157
|
-
}
|
|
158
|
-
const promptLoadCached = new Promise((resolve) => {
|
|
159
|
-
this.promptLoadTrigger = (value) => {
|
|
160
|
-
if (this.promptLoadTrigger) {
|
|
161
|
-
this.promptLoadTrigger = null;
|
|
162
|
-
}
|
|
163
|
-
resolve(value);
|
|
164
|
-
};
|
|
165
|
-
});
|
|
166
|
-
const jobDonePromise = new Promise((resolve) => {
|
|
167
|
-
this.jobDoneResolved = false;
|
|
168
|
-
this.jobResolveFn = (value) => {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
this.
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
this.
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
this.
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
if (output
|
|
288
|
-
cachedOutputDone = true;
|
|
289
|
-
this.cleanupListeners("cached output
|
|
290
|
-
this.
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
if (!
|
|
307
|
-
throw new MissingNodeError(`Node
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
}
|
|
411
|
-
this.
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
this.
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
this.
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
this.
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
this.disconnectRecoveryActive
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
this.disconnectRecoveryActive
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
this.
|
|
530
|
-
this.
|
|
531
|
-
this.
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
this.
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
this.
|
|
548
|
-
this.
|
|
549
|
-
this.
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
}
|
|
590
|
-
if (!hisData) {
|
|
591
|
-
this.emitLog("CallWrapper.handleCachedOutput", "history
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
output._raw
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
if
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
const
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
this.
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
//
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
this.
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
this.
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
this.
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
this.
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
this.
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
this.
|
|
770
|
-
this.
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
this.
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
this.
|
|
830
|
-
this.
|
|
831
|
-
this.
|
|
832
|
-
this.
|
|
833
|
-
this.
|
|
834
|
-
this.
|
|
835
|
-
this.
|
|
836
|
-
this.
|
|
837
|
-
this.
|
|
838
|
-
this.
|
|
839
|
-
this.
|
|
840
|
-
this.
|
|
841
|
-
this.
|
|
842
|
-
this.
|
|
843
|
-
this.
|
|
844
|
-
this.
|
|
845
|
-
this.
|
|
846
|
-
this.
|
|
847
|
-
this.
|
|
848
|
-
this.
|
|
849
|
-
this.
|
|
850
|
-
this.
|
|
851
|
-
this.
|
|
852
|
-
this.
|
|
853
|
-
this.
|
|
854
|
-
this.
|
|
855
|
-
|
|
856
|
-
|
|
1
|
+
import { FailedCacheError, WentMissingError, EnqueueFailedError, DisconnectedError, CustomEventError, ExecutionFailedError, ExecutionInterruptedError, MissingNodeError } from "./types/error.js";
|
|
2
|
+
import { buildEnqueueFailedError } from "./utils/response-error.js";
|
|
3
|
+
const DISCONNECT_FAILURE_GRACE_MS = 5000;
|
|
4
|
+
const CALL_WRAPPER_DEBUG = process.env.WORKFLOW_POOL_DEBUG === "1";
|
|
5
|
+
/**
|
|
6
|
+
* Represents a wrapper class for making API calls using the ComfyApi client.
|
|
7
|
+
* Provides methods for setting callback functions and executing the job.
|
|
8
|
+
*/
|
|
9
|
+
export class CallWrapper {
|
|
10
|
+
client;
|
|
11
|
+
prompt;
|
|
12
|
+
started = false;
|
|
13
|
+
isCompletingSuccessfully = false;
|
|
14
|
+
promptId;
|
|
15
|
+
output = {};
|
|
16
|
+
onPreviewFn;
|
|
17
|
+
onPreviewMetaFn;
|
|
18
|
+
onPendingFn;
|
|
19
|
+
onStartFn;
|
|
20
|
+
onOutputFn;
|
|
21
|
+
onFinishedFn;
|
|
22
|
+
onFailedFn;
|
|
23
|
+
onProgressFn;
|
|
24
|
+
jobResolveFn;
|
|
25
|
+
jobDoneResolved = false;
|
|
26
|
+
pendingCompletion = null;
|
|
27
|
+
cancellationRequested = false;
|
|
28
|
+
promptLoadTrigger = null;
|
|
29
|
+
disconnectRecoveryActive = false;
|
|
30
|
+
disconnectFailureTimer = null;
|
|
31
|
+
onReconnectHandlerOffFn;
|
|
32
|
+
onReconnectFailedHandlerOffFn;
|
|
33
|
+
onDisconnectedHandlerOffFn;
|
|
34
|
+
checkExecutingOffFn;
|
|
35
|
+
checkExecutedOffFn;
|
|
36
|
+
progressHandlerOffFn;
|
|
37
|
+
previewHandlerOffFn;
|
|
38
|
+
executionHandlerOffFn;
|
|
39
|
+
errorHandlerOffFn;
|
|
40
|
+
executionEndSuccessOffFn;
|
|
41
|
+
statusHandlerOffFn;
|
|
42
|
+
interruptionHandlerOffFn;
|
|
43
|
+
/**
|
|
44
|
+
* Constructs a new CallWrapper instance.
|
|
45
|
+
* @param client The ComfyApi client.
|
|
46
|
+
* @param workflow The workflow object.
|
|
47
|
+
*/
|
|
48
|
+
constructor(client, workflow) {
|
|
49
|
+
this.client = client;
|
|
50
|
+
this.prompt = workflow;
|
|
51
|
+
return this;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Set the callback function to be called when a preview event occurs.
|
|
55
|
+
*
|
|
56
|
+
* @param fn - The callback function to be called. It receives a Blob object representing the event and an optional promptId string.
|
|
57
|
+
* @returns The current instance of the CallWrapper.
|
|
58
|
+
*/
|
|
59
|
+
onPreview(fn) {
|
|
60
|
+
this.onPreviewFn = fn;
|
|
61
|
+
return this;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Set the callback function to be called when a preview-with-metadata event occurs.
|
|
65
|
+
*/
|
|
66
|
+
onPreviewMeta(fn) {
|
|
67
|
+
this.onPreviewMetaFn = fn;
|
|
68
|
+
return this;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Set a callback function to be executed when the job is queued.
|
|
72
|
+
* @param {Function} fn - The callback function to be executed.
|
|
73
|
+
* @returns The current instance of the CallWrapper.
|
|
74
|
+
*/
|
|
75
|
+
onPending(fn) {
|
|
76
|
+
this.onPendingFn = fn;
|
|
77
|
+
return this;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Set the callback function to be executed when the job start.
|
|
81
|
+
*
|
|
82
|
+
* @param fn - The callback function to be executed. It can optionally receive a `promptId` parameter.
|
|
83
|
+
* @returns The current instance of the CallWrapper.
|
|
84
|
+
*/
|
|
85
|
+
onStart(fn) {
|
|
86
|
+
this.onStartFn = fn;
|
|
87
|
+
return this;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Sets the callback function to handle the output node when the workflow is executing. This is
|
|
91
|
+
* useful when you want to handle the output of each nodes as they are being processed.
|
|
92
|
+
*
|
|
93
|
+
* All the nodes defined in the `mapOutputKeys` will be passed to this function when node is executed.
|
|
94
|
+
*
|
|
95
|
+
* @param fn - The callback function to handle the output.
|
|
96
|
+
* @returns The current instance of the class.
|
|
97
|
+
*/
|
|
98
|
+
onOutput(fn) {
|
|
99
|
+
this.onOutputFn = fn;
|
|
100
|
+
return this;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Set the callback function to be executed when the asynchronous operation is finished.
|
|
104
|
+
*
|
|
105
|
+
* @param fn - The callback function to be executed. It receives the data returned by the operation
|
|
106
|
+
* and an optional promptId parameter.
|
|
107
|
+
* @returns The current instance of the CallWrapper.
|
|
108
|
+
*/
|
|
109
|
+
onFinished(fn) {
|
|
110
|
+
this.onFinishedFn = fn;
|
|
111
|
+
return this;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Set the callback function to be executed when the API call fails.
|
|
115
|
+
*
|
|
116
|
+
* @param fn - The callback function to be executed when the API call fails.
|
|
117
|
+
* It receives an `Error` object as the first parameter and an optional `promptId` as the second parameter.
|
|
118
|
+
* @returns The current instance of the CallWrapper.
|
|
119
|
+
*/
|
|
120
|
+
onFailed(fn) {
|
|
121
|
+
this.onFailedFn = fn;
|
|
122
|
+
return this;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Set a callback function to be called when progress information is available.
|
|
126
|
+
* @param fn - The callback function to be called with the progress information.
|
|
127
|
+
* @returns The current instance of the CallWrapper.
|
|
128
|
+
*/
|
|
129
|
+
onProgress(fn) {
|
|
130
|
+
this.onProgressFn = fn;
|
|
131
|
+
return this;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Run the call wrapper and returns the output of the executed job.
|
|
135
|
+
* If the job is already cached, it returns the cached output.
|
|
136
|
+
* If the job is not cached, it executes the job and returns the output.
|
|
137
|
+
*
|
|
138
|
+
* @returns A promise that resolves to the output of the executed job,
|
|
139
|
+
* or `undefined` if the job is not found,
|
|
140
|
+
* or `false` if the job execution fails.
|
|
141
|
+
*/
|
|
142
|
+
async run() {
|
|
143
|
+
/**
|
|
144
|
+
* Start the job execution.
|
|
145
|
+
*/
|
|
146
|
+
this.emitLog("CallWrapper.run", "enqueue start");
|
|
147
|
+
this.pendingCompletion = null;
|
|
148
|
+
this.jobResolveFn = undefined;
|
|
149
|
+
this.jobDoneResolved = false;
|
|
150
|
+
this.cancellationRequested = false;
|
|
151
|
+
this.promptLoadTrigger = null;
|
|
152
|
+
const job = await this.enqueueJob();
|
|
153
|
+
if (!job) {
|
|
154
|
+
// enqueueJob already invoked onFailed with a rich error instance; just abort.
|
|
155
|
+
this.emitLog("CallWrapper.run", "enqueue failed -> abort");
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
const promptLoadCached = new Promise((resolve) => {
|
|
159
|
+
this.promptLoadTrigger = (value) => {
|
|
160
|
+
if (this.promptLoadTrigger) {
|
|
161
|
+
this.promptLoadTrigger = null;
|
|
162
|
+
}
|
|
163
|
+
resolve(value);
|
|
164
|
+
};
|
|
165
|
+
});
|
|
166
|
+
const jobDonePromise = new Promise((resolve) => {
|
|
167
|
+
this.jobDoneResolved = false;
|
|
168
|
+
this.jobResolveFn = (value) => {
|
|
169
|
+
resolve(value);
|
|
170
|
+
};
|
|
171
|
+
if (this.pendingCompletion !== null) {
|
|
172
|
+
const pending = this.pendingCompletion;
|
|
173
|
+
this.pendingCompletion = null;
|
|
174
|
+
this.jobResolveFn?.(pending);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
/**
|
|
178
|
+
* Declare the function to check if the job is executing.
|
|
179
|
+
*/
|
|
180
|
+
const checkExecutingFn = (event) => {
|
|
181
|
+
// Defensive null check for event detail
|
|
182
|
+
if (!event.detail) {
|
|
183
|
+
this.emitLog("CallWrapper.run", "executing event received with no detail");
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
if (event.detail.prompt_id === job.prompt_id) {
|
|
187
|
+
this.emitLog("CallWrapper.run", "executing observed", { node: event.detail.node });
|
|
188
|
+
this.resolvePromptLoad(false);
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
/**
|
|
192
|
+
* Declare the function to check if the job is cached.
|
|
193
|
+
*/
|
|
194
|
+
const checkExecutionCachedFn = (event) => {
|
|
195
|
+
// Defensive null checks for event detail
|
|
196
|
+
if (!event.detail || !event.detail.nodes) {
|
|
197
|
+
this.emitLog("CallWrapper.run", "execution_cached event received with invalid structure");
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
const outputNodes = Object.values(this.prompt.mapOutputKeys).filter((n) => !!n);
|
|
201
|
+
if (event.detail.nodes.length > 0 && event.detail.prompt_id === job.prompt_id) {
|
|
202
|
+
/**
|
|
203
|
+
* Cached is true if all output nodes are included in the cached nodes.
|
|
204
|
+
*/
|
|
205
|
+
const cached = outputNodes.every((node) => event.detail.nodes.includes(node));
|
|
206
|
+
this.emitLog("CallWrapper.run", "execution_cached observed", {
|
|
207
|
+
cached,
|
|
208
|
+
nodes: event.detail.nodes,
|
|
209
|
+
expected: outputNodes
|
|
210
|
+
});
|
|
211
|
+
this.resolvePromptLoad(cached);
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
/**
|
|
215
|
+
* Listen to the executing event.
|
|
216
|
+
*/
|
|
217
|
+
this.checkExecutingOffFn = this.client.on("executing", checkExecutingFn);
|
|
218
|
+
this.checkExecutedOffFn = this.client.on("execution_cached", checkExecutionCachedFn);
|
|
219
|
+
// race condition handling
|
|
220
|
+
let wentMissing = false;
|
|
221
|
+
let cachedOutputDone = false;
|
|
222
|
+
let cachedOutputPromise = Promise.resolve(null);
|
|
223
|
+
const statusHandler = async () => {
|
|
224
|
+
const queue = await this.client.getQueue();
|
|
225
|
+
const queueItems = [...queue.queue_pending, ...queue.queue_running];
|
|
226
|
+
this.emitLog("CallWrapper.status", "queue snapshot", {
|
|
227
|
+
running: queue.queue_running.length,
|
|
228
|
+
pending: queue.queue_pending.length
|
|
229
|
+
});
|
|
230
|
+
for (const queueItem of queueItems) {
|
|
231
|
+
if (queueItem[1] === job.prompt_id) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
await cachedOutputPromise;
|
|
236
|
+
if (cachedOutputDone) {
|
|
237
|
+
this.emitLog("CallWrapper.status", "cached output already handled");
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
if (this.cancellationRequested) {
|
|
241
|
+
this.emitLog("CallWrapper.status", "job missing after cancellation", {
|
|
242
|
+
prompt_id: job.prompt_id
|
|
243
|
+
});
|
|
244
|
+
this.resolvePromptLoad(false);
|
|
245
|
+
this.resolveJob(false);
|
|
246
|
+
this.cleanupListeners("status handler cancellation");
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
wentMissing = true;
|
|
250
|
+
const output = await this.handleCachedOutput(job.prompt_id);
|
|
251
|
+
if (output) {
|
|
252
|
+
cachedOutputDone = true;
|
|
253
|
+
this.emitLog("CallWrapper.status", "output from history after missing", {
|
|
254
|
+
prompt_id: job.prompt_id
|
|
255
|
+
});
|
|
256
|
+
this.resolvePromptLoad(false);
|
|
257
|
+
this.resolveJob(output);
|
|
258
|
+
this.cleanupListeners("status handler resolved from history");
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
if (this.disconnectRecoveryActive) {
|
|
262
|
+
this.emitLog("CallWrapper.status", "job missing but disconnect recovery active -> waiting", {
|
|
263
|
+
prompt_id: job.prompt_id
|
|
264
|
+
});
|
|
265
|
+
this.resolvePromptLoad(false);
|
|
266
|
+
void this.attemptHistoryCompletion("status_missing");
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
cachedOutputDone = true;
|
|
270
|
+
this.emitLog("CallWrapper.status", "job missing without cached output", {
|
|
271
|
+
prompt_id: job.prompt_id
|
|
272
|
+
});
|
|
273
|
+
this.resolvePromptLoad(false);
|
|
274
|
+
this.resolveJob(false);
|
|
275
|
+
this.cleanupListeners("status handler missing");
|
|
276
|
+
this.emitFailure(new WentMissingError("The job went missing!"), job.prompt_id);
|
|
277
|
+
};
|
|
278
|
+
this.statusHandlerOffFn = this.client.on("status", statusHandler);
|
|
279
|
+
// Attach execution listeners immediately so fast jobs cannot finish before we subscribe
|
|
280
|
+
this.handleJobExecution(job.prompt_id);
|
|
281
|
+
await promptLoadCached;
|
|
282
|
+
if (wentMissing) {
|
|
283
|
+
return jobDonePromise;
|
|
284
|
+
}
|
|
285
|
+
cachedOutputPromise = this.handleCachedOutput(job.prompt_id);
|
|
286
|
+
const output = await cachedOutputPromise;
|
|
287
|
+
if (output) {
|
|
288
|
+
cachedOutputDone = true;
|
|
289
|
+
this.cleanupListeners("no cached output values returned");
|
|
290
|
+
this.resolveJob(output);
|
|
291
|
+
return output;
|
|
292
|
+
}
|
|
293
|
+
if (output === false) {
|
|
294
|
+
cachedOutputDone = true;
|
|
295
|
+
this.cleanupListeners("cached output ready before execution listeners");
|
|
296
|
+
this.emitFailure(new FailedCacheError("Failed to get cached output"), this.promptId);
|
|
297
|
+
this.resolveJob(false);
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
this.emitLog("CallWrapper.run", "no cached output -> proceed with execution listeners");
|
|
301
|
+
return jobDonePromise;
|
|
302
|
+
}
|
|
303
|
+
async bypassWorkflowNodes(workflow) {
|
|
304
|
+
const nodeDefs = {}; // cache node definitions
|
|
305
|
+
for (const nodeId of this.prompt.bypassNodes) {
|
|
306
|
+
if (!workflow[nodeId]) {
|
|
307
|
+
throw new MissingNodeError(`Node ${nodeId.toString()} is missing from the workflow!`);
|
|
308
|
+
}
|
|
309
|
+
const classType = workflow[nodeId].class_type;
|
|
310
|
+
// Directly use feature namespace to avoid deprecated internal call
|
|
311
|
+
const def = nodeDefs[classType] || (await this.client.ext.node.getNodeDefs(classType))?.[classType];
|
|
312
|
+
if (!def) {
|
|
313
|
+
throw new MissingNodeError(`Node type ${workflow[nodeId].class_type} is missing from server!`);
|
|
314
|
+
}
|
|
315
|
+
nodeDefs[classType] = def;
|
|
316
|
+
const connections = new Map();
|
|
317
|
+
const connectedInputs = [];
|
|
318
|
+
// connect output nodes to matching input nodes
|
|
319
|
+
for (const [outputIdx, outputType] of Array.from(def.output.entries())) {
|
|
320
|
+
for (const [inputName, inputValue] of Object.entries(workflow[nodeId].inputs)) {
|
|
321
|
+
if (connectedInputs.includes(inputName)) {
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
if (def.input.required[inputName]?.[0] === outputType) {
|
|
325
|
+
connections.set(outputIdx, inputValue);
|
|
326
|
+
connectedInputs.push(inputName);
|
|
327
|
+
break;
|
|
328
|
+
}
|
|
329
|
+
if (def.input.optional?.[inputName]?.[0] === outputType) {
|
|
330
|
+
connections.set(outputIdx, inputValue);
|
|
331
|
+
connectedInputs.push(inputName);
|
|
332
|
+
break;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
// search and replace all nodes' inputs referencing this node based on matching output type, or remove reference
|
|
337
|
+
// if no matching output type was found
|
|
338
|
+
for (const [conNodeId, conNode] of Object.entries(workflow)) {
|
|
339
|
+
for (const [conInputName, conInputValue] of Object.entries(conNode.inputs)) {
|
|
340
|
+
if (!Array.isArray(conInputValue) || conInputValue[0] !== nodeId) {
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
if (connections.has(conInputValue[1])) {
|
|
344
|
+
workflow[conNodeId].inputs[conInputName] = connections.get(conInputValue[1]);
|
|
345
|
+
}
|
|
346
|
+
else {
|
|
347
|
+
delete workflow[conNodeId].inputs[conInputName];
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
delete workflow[nodeId];
|
|
352
|
+
}
|
|
353
|
+
return workflow;
|
|
354
|
+
}
|
|
355
|
+
async enqueueJob() {
|
|
356
|
+
let workflow = structuredClone(this.prompt.workflow);
|
|
357
|
+
if (this.prompt.bypassNodes.length > 0) {
|
|
358
|
+
try {
|
|
359
|
+
workflow = await this.bypassWorkflowNodes(workflow);
|
|
360
|
+
}
|
|
361
|
+
catch (e) {
|
|
362
|
+
if (e instanceof Response) {
|
|
363
|
+
this.emitFailure(new MissingNodeError("Failed to get workflow node definitions", { cause: await e.json() }), this.promptId);
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
this.emitFailure(new MissingNodeError("There was a missing node in the workflow bypass.", { cause: e }), this.promptId);
|
|
367
|
+
}
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
let job;
|
|
372
|
+
try {
|
|
373
|
+
job = await this.client.ext.queue.appendPrompt(workflow);
|
|
374
|
+
}
|
|
375
|
+
catch (e) {
|
|
376
|
+
try {
|
|
377
|
+
if (e instanceof EnqueueFailedError) {
|
|
378
|
+
this.emitFailure(e, this.promptId);
|
|
379
|
+
}
|
|
380
|
+
else if (e instanceof Response) {
|
|
381
|
+
const err = await buildEnqueueFailedError(e);
|
|
382
|
+
this.emitFailure(err, this.promptId);
|
|
383
|
+
}
|
|
384
|
+
else if (e && typeof e === "object" && "response" in e && e.response instanceof Response) {
|
|
385
|
+
const err = await buildEnqueueFailedError(e.response);
|
|
386
|
+
this.emitFailure(err, this.promptId);
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
this.emitFailure(new EnqueueFailedError("Failed to queue prompt", { cause: e, reason: e?.message }), this.promptId);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
catch (inner) {
|
|
393
|
+
this.emitFailure(new EnqueueFailedError("Failed to queue prompt", { cause: inner }), this.promptId);
|
|
394
|
+
}
|
|
395
|
+
job = null;
|
|
396
|
+
}
|
|
397
|
+
if (!job) {
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
this.promptId = job.prompt_id;
|
|
401
|
+
console.log(`[CallWrapper] Enqueued with promptId=${this.promptId?.substring(0, 8)}...`);
|
|
402
|
+
console.log(`[CallWrapper] Full job object:`, JSON.stringify({ promptId: job.prompt_id }, null, 2));
|
|
403
|
+
this.emitLog("CallWrapper.enqueueJob", "queued", { prompt_id: this.promptId });
|
|
404
|
+
this.onPendingFn?.(this.promptId);
|
|
405
|
+
this.onDisconnectedHandlerOffFn = this.client.on("disconnected", () => {
|
|
406
|
+
if (this.isCompletingSuccessfully) {
|
|
407
|
+
this.emitLog("CallWrapper.enqueueJob", "disconnected during success completion -> ignored");
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
this.emitLog("CallWrapper.enqueueJob", "socket disconnected -> enter recovery", { promptId: this.promptId });
|
|
411
|
+
this.startDisconnectRecovery();
|
|
412
|
+
});
|
|
413
|
+
this.onReconnectHandlerOffFn = this.client.on("reconnected", () => {
|
|
414
|
+
if (!this.disconnectRecoveryActive) {
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
this.emitLog("CallWrapper.enqueueJob", "socket reconnected", { promptId: this.promptId });
|
|
418
|
+
this.stopDisconnectRecovery();
|
|
419
|
+
void this.attemptHistoryCompletion("reconnected");
|
|
420
|
+
});
|
|
421
|
+
this.onReconnectFailedHandlerOffFn = this.client.on("reconnection_failed", () => {
|
|
422
|
+
if (!this.disconnectRecoveryActive) {
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
this.emitLog("CallWrapper.enqueueJob", "reconnection failed", { promptId: this.promptId });
|
|
426
|
+
this.failDisconnected("reconnection_failed");
|
|
427
|
+
});
|
|
428
|
+
return job;
|
|
429
|
+
}
|
|
430
|
+
resolvePromptLoad(value) {
|
|
431
|
+
const trigger = this.promptLoadTrigger;
|
|
432
|
+
if (!trigger) {
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
this.promptLoadTrigger = null;
|
|
436
|
+
try {
|
|
437
|
+
trigger(value);
|
|
438
|
+
}
|
|
439
|
+
catch (error) {
|
|
440
|
+
this.emitLog("CallWrapper.resolvePromptLoad", "prompt load trigger threw", {
|
|
441
|
+
error: error instanceof Error ? error.message : String(error),
|
|
442
|
+
promptId: this.promptId
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
resolveJob(value) {
|
|
447
|
+
if (CALL_WRAPPER_DEBUG) {
|
|
448
|
+
console.log("[debug] resolveJob", this.promptId, value, Boolean(this.jobResolveFn), this.jobDoneResolved);
|
|
449
|
+
}
|
|
450
|
+
if (this.jobResolveFn && !this.jobDoneResolved) {
|
|
451
|
+
this.jobDoneResolved = true;
|
|
452
|
+
this.jobResolveFn(value);
|
|
453
|
+
if (CALL_WRAPPER_DEBUG) {
|
|
454
|
+
console.log("[debug] jobResolveFn invoked", this.promptId);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
else if (!this.jobResolveFn) {
|
|
458
|
+
this.pendingCompletion = value;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
emitFailure(error, promptId) {
|
|
462
|
+
const fn = this.onFailedFn;
|
|
463
|
+
if (!fn) {
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
const targetPromptId = promptId ?? this.promptId;
|
|
467
|
+
try {
|
|
468
|
+
if (CALL_WRAPPER_DEBUG) {
|
|
469
|
+
console.log("[debug] emitFailure start", error.name);
|
|
470
|
+
}
|
|
471
|
+
fn(error, targetPromptId);
|
|
472
|
+
if (CALL_WRAPPER_DEBUG) {
|
|
473
|
+
console.log("[debug] emitFailure end", error.name);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
catch (callbackError) {
|
|
477
|
+
this.emitLog("CallWrapper.emitFailure", "onFailed callback threw", {
|
|
478
|
+
prompt_id: targetPromptId,
|
|
479
|
+
error: callbackError instanceof Error ? callbackError.message : String(callbackError)
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
cancel(reason = "cancelled") {
|
|
484
|
+
if (this.cancellationRequested) {
|
|
485
|
+
this.emitLog("CallWrapper.cancel", "cancel already requested", {
|
|
486
|
+
promptId: this.promptId,
|
|
487
|
+
reason
|
|
488
|
+
});
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
this.cancellationRequested = true;
|
|
492
|
+
this.emitLog("CallWrapper.cancel", "cancel requested", {
|
|
493
|
+
promptId: this.promptId,
|
|
494
|
+
reason
|
|
495
|
+
});
|
|
496
|
+
this.resolvePromptLoad(false);
|
|
497
|
+
this.emitFailure(new ExecutionInterruptedError("The execution was interrupted!", { cause: { reason } }), this.promptId);
|
|
498
|
+
this.cleanupListeners("cancel requested");
|
|
499
|
+
this.resolveJob(false);
|
|
500
|
+
}
|
|
501
|
+
startDisconnectRecovery() {
|
|
502
|
+
if (this.disconnectRecoveryActive || this.cancellationRequested) {
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
this.disconnectRecoveryActive = true;
|
|
506
|
+
if (this.disconnectFailureTimer) {
|
|
507
|
+
clearTimeout(this.disconnectFailureTimer);
|
|
508
|
+
}
|
|
509
|
+
this.disconnectFailureTimer = setTimeout(() => this.failDisconnected("timeout"), DISCONNECT_FAILURE_GRACE_MS);
|
|
510
|
+
void this.attemptHistoryCompletion("disconnect_start");
|
|
511
|
+
}
|
|
512
|
+
stopDisconnectRecovery() {
|
|
513
|
+
if (!this.disconnectRecoveryActive) {
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
this.disconnectRecoveryActive = false;
|
|
517
|
+
if (this.disconnectFailureTimer) {
|
|
518
|
+
clearTimeout(this.disconnectFailureTimer);
|
|
519
|
+
this.disconnectFailureTimer = null;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
async attemptHistoryCompletion(reason) {
|
|
523
|
+
if (!this.promptId || this.cancellationRequested) {
|
|
524
|
+
return false;
|
|
525
|
+
}
|
|
526
|
+
try {
|
|
527
|
+
const output = await this.handleCachedOutput(this.promptId);
|
|
528
|
+
if (output && output !== false) {
|
|
529
|
+
this.emitLog("CallWrapper.historyRecovery", "completed from history", { reason, promptId: this.promptId });
|
|
530
|
+
this.stopDisconnectRecovery();
|
|
531
|
+
this.isCompletingSuccessfully = true;
|
|
532
|
+
this.resolvePromptLoad(false);
|
|
533
|
+
this.resolveJob(output);
|
|
534
|
+
this.cleanupListeners(`history recovery (${reason})`);
|
|
535
|
+
return true;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
catch (error) {
|
|
539
|
+
this.emitLog("CallWrapper.historyRecovery", "history fetch failed", { reason, error: String(error) });
|
|
540
|
+
}
|
|
541
|
+
return false;
|
|
542
|
+
}
|
|
543
|
+
failDisconnected(reason) {
|
|
544
|
+
if (!this.disconnectRecoveryActive || this.isCompletingSuccessfully) {
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
this.stopDisconnectRecovery();
|
|
548
|
+
this.emitLog("CallWrapper.enqueueJob", "disconnect recovery failed", { reason, promptId: this.promptId });
|
|
549
|
+
this.resolvePromptLoad(false);
|
|
550
|
+
this.resolveJob(false);
|
|
551
|
+
this.cleanupListeners("disconnect failure");
|
|
552
|
+
this.emitFailure(new DisconnectedError("Disconnected"), this.promptId);
|
|
553
|
+
}
|
|
554
|
+
async handleCachedOutput(promptId) {
|
|
555
|
+
const hisData = await this.client.ext.history.getHistory(promptId);
|
|
556
|
+
this.emitLog("CallWrapper.handleCachedOutput", "history fetched", {
|
|
557
|
+
promptId,
|
|
558
|
+
status: hisData?.status?.status_str,
|
|
559
|
+
completed: hisData?.status?.completed,
|
|
560
|
+
outputKeys: hisData?.outputs ? Object.keys(hisData.outputs) : [],
|
|
561
|
+
hasOutputs: !!(hisData && hisData.outputs && Object.keys(hisData.outputs).length > 0)
|
|
562
|
+
});
|
|
563
|
+
// Only return outputs if execution is actually completed
|
|
564
|
+
if (hisData && hisData.status?.completed && hisData.outputs) {
|
|
565
|
+
const output = this.mapOutput(hisData.outputs);
|
|
566
|
+
const hasDefinedValue = Object.entries(output).some(([key, value]) => {
|
|
567
|
+
if (key === "_raw") {
|
|
568
|
+
return value !== undefined && value !== null && Object.keys(value).length > 0;
|
|
569
|
+
}
|
|
570
|
+
return value !== undefined;
|
|
571
|
+
});
|
|
572
|
+
if (hasDefinedValue) {
|
|
573
|
+
this.emitLog("CallWrapper.handleCachedOutput", "returning completed outputs");
|
|
574
|
+
this.onFinishedFn?.(output, this.promptId);
|
|
575
|
+
return output;
|
|
576
|
+
}
|
|
577
|
+
else {
|
|
578
|
+
this.emitLog("CallWrapper.handleCachedOutput", "cached output missing defined values", {
|
|
579
|
+
promptId,
|
|
580
|
+
outputKeys: Object.keys(hisData.outputs ?? {}),
|
|
581
|
+
mappedKeys: this.prompt.mapOutputKeys
|
|
582
|
+
});
|
|
583
|
+
return false;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
if (hisData && hisData.status?.completed && !hisData.outputs) {
|
|
587
|
+
this.emitLog("CallWrapper.handleCachedOutput", "history completed without outputs", { promptId });
|
|
588
|
+
return false;
|
|
589
|
+
}
|
|
590
|
+
if (hisData && !hisData.status?.completed) {
|
|
591
|
+
this.emitLog("CallWrapper.handleCachedOutput", "history not completed yet");
|
|
592
|
+
}
|
|
593
|
+
if (!hisData) {
|
|
594
|
+
this.emitLog("CallWrapper.handleCachedOutput", "history entry not available");
|
|
595
|
+
}
|
|
596
|
+
return null;
|
|
597
|
+
}
|
|
598
|
+
mapOutput(outputNodes) {
|
|
599
|
+
const outputMapped = this.prompt.mapOutputKeys;
|
|
600
|
+
const output = {};
|
|
601
|
+
for (const key in outputMapped) {
|
|
602
|
+
const node = outputMapped[key];
|
|
603
|
+
if (node) {
|
|
604
|
+
output[key] = outputNodes[node];
|
|
605
|
+
}
|
|
606
|
+
else {
|
|
607
|
+
if (!output._raw) {
|
|
608
|
+
output._raw = {};
|
|
609
|
+
}
|
|
610
|
+
output._raw[key] = outputNodes[key];
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
return output;
|
|
614
|
+
}
|
|
615
|
+
handleJobExecution(promptId) {
|
|
616
|
+
if (this.executionHandlerOffFn) {
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
const reverseOutputMapped = this.reverseMapOutputKeys();
|
|
620
|
+
const mapOutputKeys = this.prompt.mapOutputKeys;
|
|
621
|
+
console.log(`[CallWrapper] handleJobExecution for ${promptId.substring(0, 8)}... - mapOutputKeys:`, mapOutputKeys, "reverseOutputMapped:", reverseOutputMapped);
|
|
622
|
+
this.progressHandlerOffFn = this.client.on("progress", (ev) => this.handleProgress(ev, promptId));
|
|
623
|
+
this.previewHandlerOffFn = this.client.on("b_preview", (ev) => {
|
|
624
|
+
// Note: b_preview events don't include prompt_id. They're scoped per connection.
|
|
625
|
+
// If multiple jobs use the same connection, they will all receive preview events.
|
|
626
|
+
// This is a limitation of the ComfyUI protocol - previews are not separated by prompt_id.
|
|
627
|
+
this.onPreviewFn?.(ev.detail, this.promptId);
|
|
628
|
+
});
|
|
629
|
+
// Also forward preview with metadata if available
|
|
630
|
+
const offPreviewMeta = this.client.on("b_preview_meta", (ev) => {
|
|
631
|
+
// Validate prompt_id from metadata if available to prevent cross-user preview leakage
|
|
632
|
+
const metadata = ev.detail.metadata;
|
|
633
|
+
const metaPromptId = metadata?.prompt_id;
|
|
634
|
+
if (metaPromptId && metaPromptId !== promptId) {
|
|
635
|
+
console.log(`[CallWrapper] Ignoring b_preview_meta for wrong prompt. Expected ${promptId.substring(0, 8)}..., got ${metaPromptId.substring(0, 8)}...`);
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
this.onPreviewMetaFn?.(ev.detail, this.promptId);
|
|
639
|
+
});
|
|
640
|
+
const prevCleanup = this.previewHandlerOffFn;
|
|
641
|
+
this.previewHandlerOffFn = () => {
|
|
642
|
+
prevCleanup?.();
|
|
643
|
+
offPreviewMeta?.();
|
|
644
|
+
};
|
|
645
|
+
const totalOutput = Object.keys(reverseOutputMapped).length;
|
|
646
|
+
let remainingOutput = totalOutput;
|
|
647
|
+
console.log(`[CallWrapper] totalOutput=${totalOutput}, remainingOutput=${remainingOutput}`);
|
|
648
|
+
const executionHandler = (ev) => {
|
|
649
|
+
console.log(`[CallWrapper.executionHandler] received executed event for promptId=${ev.detail.prompt_id?.substring(0, 8)}..., node=${ev.detail.node}, waitingFor=${promptId.substring(0, 8)}...`);
|
|
650
|
+
const eventPromptId = ev.detail.prompt_id;
|
|
651
|
+
const isCorrectPrompt = eventPromptId === promptId;
|
|
652
|
+
// STRICT: Only accept events where prompt_id matches our expected promptId
|
|
653
|
+
if (!isCorrectPrompt) {
|
|
654
|
+
console.log(`[CallWrapper.executionHandler] REJECTED - prompt_id mismatch (expected ${promptId.substring(0, 8)}..., got ${eventPromptId?.substring(0, 8)}...)`);
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
const outputKey = reverseOutputMapped[ev.detail.node];
|
|
658
|
+
console.log(`[CallWrapper] executionHandler - promptId: ${promptId.substring(0, 8)}... (event says: ${ev.detail.prompt_id?.substring(0, 8)}...), node: ${ev.detail.node}, outputKey: ${outputKey}, output:`, JSON.stringify(ev.detail.output));
|
|
659
|
+
if (outputKey) {
|
|
660
|
+
this.output[outputKey] = ev.detail.output;
|
|
661
|
+
this.onOutputFn?.(outputKey, ev.detail.output, this.promptId);
|
|
662
|
+
remainingOutput--;
|
|
663
|
+
}
|
|
664
|
+
else {
|
|
665
|
+
this.output._raw = this.output._raw || {};
|
|
666
|
+
this.output._raw[ev.detail.node] = ev.detail.output;
|
|
667
|
+
this.onOutputFn?.(ev.detail.node, ev.detail.output, this.promptId);
|
|
668
|
+
}
|
|
669
|
+
console.log(`[CallWrapper] afterProcessing - remainingAfter: ${remainingOutput}, willTriggerCompletion: ${remainingOutput === 0}`);
|
|
670
|
+
if (remainingOutput === 0) {
|
|
671
|
+
console.log(`[CallWrapper] all outputs collected for ${promptId.substring(0, 8)}...`);
|
|
672
|
+
// Mark as successfully completing BEFORE cleanup to prevent race condition with disconnection handler
|
|
673
|
+
this.isCompletingSuccessfully = true;
|
|
674
|
+
this.cleanupListeners("all outputs collected");
|
|
675
|
+
this.onFinishedFn?.(this.output, this.promptId);
|
|
676
|
+
this.resolveJob(this.output);
|
|
677
|
+
}
|
|
678
|
+
};
|
|
679
|
+
const executedEnd = async () => {
|
|
680
|
+
console.log(`[CallWrapper] execution_success fired for ${promptId.substring(0, 8)}..., remainingOutput=${remainingOutput}, totalOutput=${totalOutput}`);
|
|
681
|
+
// If we've already marked this as successfully completing, don't fail it again
|
|
682
|
+
if (this.isCompletingSuccessfully) {
|
|
683
|
+
console.log(`[CallWrapper] Already marked as successfully completing, ignoring this execution_success`);
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
if (remainingOutput === 0) {
|
|
687
|
+
console.log(`[CallWrapper] all outputs already collected, nothing to do`);
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
// Wait briefly for outputs that might be arriving due to prompt ID mismatch
|
|
691
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
692
|
+
console.log(`[CallWrapper] After wait - remainingOutput=${remainingOutput}, this.output keys:`, Object.keys(this.output));
|
|
693
|
+
// Check if outputs arrived while we were waiting
|
|
694
|
+
if (remainingOutput === 0) {
|
|
695
|
+
console.log(`[CallWrapper] Outputs arrived during wait - marking as complete`);
|
|
696
|
+
this.isCompletingSuccessfully = true;
|
|
697
|
+
this.cleanupListeners("executedEnd - outputs complete after wait");
|
|
698
|
+
this.onFinishedFn?.(this.output, this.promptId);
|
|
699
|
+
this.resolveJob(this.output);
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
// Check if we have collected all outputs (even if prompt ID mismatch)
|
|
703
|
+
const hasAllOutputs = Object.keys(reverseOutputMapped).every((nodeId) => this.output[reverseOutputMapped[nodeId]] !== undefined);
|
|
704
|
+
if (hasAllOutputs) {
|
|
705
|
+
console.log(`[CallWrapper] Have all required outputs despite promptId mismatch - marking as complete`);
|
|
706
|
+
this.isCompletingSuccessfully = true;
|
|
707
|
+
this.cleanupListeners("executedEnd - outputs complete despite promptId mismatch");
|
|
708
|
+
this.onFinishedFn?.(this.output, this.promptId);
|
|
709
|
+
this.resolveJob(this.output);
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
// Try to fetch from history with retry logic
|
|
713
|
+
let hisData = null;
|
|
714
|
+
for (let retries = 0; retries < 5; retries++) {
|
|
715
|
+
hisData = await this.client.ext.history.getHistory(promptId);
|
|
716
|
+
console.log(`[CallWrapper] History query result for ${promptId.substring(0, 8)}... (attempt ${retries + 1}) - status:`, hisData?.status, "outputs:", Object.keys(hisData?.outputs ?? {}).length);
|
|
717
|
+
if (hisData?.status?.completed && hisData.outputs) {
|
|
718
|
+
console.log(`[CallWrapper] Found completed job in history with outputs - attempting to populate from history`);
|
|
719
|
+
break;
|
|
720
|
+
}
|
|
721
|
+
if (retries < 4) {
|
|
722
|
+
console.log(`[CallWrapper] History not ready yet, waiting 100ms before retry...`);
|
|
723
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
if (hisData?.status?.completed && hisData.outputs) {
|
|
727
|
+
// Try to extract outputs from history data
|
|
728
|
+
let populatedCount = 0;
|
|
729
|
+
for (const [nodeIdStr, nodeOutput] of Object.entries(hisData.outputs)) {
|
|
730
|
+
const nodeId = parseInt(nodeIdStr, 10);
|
|
731
|
+
const outputKey = reverseOutputMapped[nodeId];
|
|
732
|
+
if (outputKey && nodeOutput) {
|
|
733
|
+
// nodeOutput is typically { images: [...] } or similar - take the first property
|
|
734
|
+
const outputValue = Array.isArray(nodeOutput) ? nodeOutput[0] : Object.values(nodeOutput)[0];
|
|
735
|
+
if (outputValue !== undefined) {
|
|
736
|
+
this.output[outputKey] = outputValue;
|
|
737
|
+
this.onOutputFn?.(outputKey, outputValue, this.promptId);
|
|
738
|
+
populatedCount++;
|
|
739
|
+
remainingOutput--;
|
|
740
|
+
console.log(`[CallWrapper] Populated ${outputKey} from history`);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
if (remainingOutput === 0) {
|
|
745
|
+
console.log(`[CallWrapper] Successfully populated all outputs from history for ${promptId.substring(0, 8)}...`);
|
|
746
|
+
this.isCompletingSuccessfully = true;
|
|
747
|
+
this.cleanupListeners("executedEnd - populated from history");
|
|
748
|
+
this.onFinishedFn?.(this.output, this.promptId);
|
|
749
|
+
this.resolveJob(this.output);
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
if (populatedCount > 0) {
|
|
753
|
+
console.log(`[CallWrapper] Populated ${populatedCount} outputs from history (remainingOutput=${remainingOutput})`);
|
|
754
|
+
if (remainingOutput === 0) {
|
|
755
|
+
this.isCompletingSuccessfully = true;
|
|
756
|
+
this.cleanupListeners("executedEnd - all outputs from history");
|
|
757
|
+
this.onFinishedFn?.(this.output, this.promptId);
|
|
758
|
+
this.resolveJob(this.output);
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
console.log(`[CallWrapper] execution failed due to missing outputs - remainingOutput=${remainingOutput}, totalOutput=${totalOutput}`);
|
|
764
|
+
this.emitFailure(new ExecutionFailedError("Execution failed"), this.promptId);
|
|
765
|
+
this.resolvePromptLoad(false);
|
|
766
|
+
this.cleanupListeners("executedEnd missing outputs");
|
|
767
|
+
this.resolveJob(false);
|
|
768
|
+
};
|
|
769
|
+
this.executionEndSuccessOffFn = this.client.on("execution_success", executedEnd);
|
|
770
|
+
this.executionHandlerOffFn = this.client.on("executed", executionHandler);
|
|
771
|
+
console.log(`[CallWrapper] Registered listeners for ${promptId.substring(0, 8)}... - executionHandler and executedEnd`);
|
|
772
|
+
this.errorHandlerOffFn = this.client.on("execution_error", (ev) => this.handleError(ev, promptId));
|
|
773
|
+
this.interruptionHandlerOffFn = this.client.on("execution_interrupted", (ev) => {
|
|
774
|
+
if (ev.detail.prompt_id !== promptId)
|
|
775
|
+
return;
|
|
776
|
+
this.emitFailure(new ExecutionInterruptedError("The execution was interrupted!", { cause: ev.detail }), ev.detail.prompt_id);
|
|
777
|
+
this.resolvePromptLoad(false);
|
|
778
|
+
this.cleanupListeners("execution interrupted");
|
|
779
|
+
this.resolveJob(false);
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
reverseMapOutputKeys() {
|
|
783
|
+
const outputMapped = this.prompt.mapOutputKeys;
|
|
784
|
+
return Object.entries(outputMapped).reduce((acc, [k, v]) => {
|
|
785
|
+
if (v)
|
|
786
|
+
acc[v] = k;
|
|
787
|
+
return acc;
|
|
788
|
+
}, {});
|
|
789
|
+
}
|
|
790
|
+
handleProgress(ev, promptId) {
|
|
791
|
+
if (ev.detail.prompt_id === promptId && !this.started) {
|
|
792
|
+
this.started = true;
|
|
793
|
+
this.onStartFn?.(this.promptId);
|
|
794
|
+
}
|
|
795
|
+
this.onProgressFn?.(ev.detail, this.promptId);
|
|
796
|
+
}
|
|
797
|
+
handleError(ev, promptId) {
|
|
798
|
+
if (ev.detail.prompt_id !== promptId)
|
|
799
|
+
return;
|
|
800
|
+
this.emitLog("CallWrapper.handleError", ev.detail.exception_type, {
|
|
801
|
+
prompt_id: ev.detail.prompt_id,
|
|
802
|
+
node_id: ev.detail?.node_id
|
|
803
|
+
});
|
|
804
|
+
this.emitFailure(new CustomEventError(ev.detail.exception_type, { cause: ev.detail }), ev.detail.prompt_id);
|
|
805
|
+
if (CALL_WRAPPER_DEBUG) {
|
|
806
|
+
console.log("[debug] handleError after emitFailure");
|
|
807
|
+
}
|
|
808
|
+
this.resolvePromptLoad(false);
|
|
809
|
+
if (CALL_WRAPPER_DEBUG) {
|
|
810
|
+
console.log("[debug] handleError before cleanup");
|
|
811
|
+
}
|
|
812
|
+
this.cleanupListeners("execution_error received");
|
|
813
|
+
if (CALL_WRAPPER_DEBUG) {
|
|
814
|
+
console.log("[debug] handleError after cleanup");
|
|
815
|
+
}
|
|
816
|
+
this.resolveJob(false);
|
|
817
|
+
}
|
|
818
|
+
emitLog(fnName, message, data) {
|
|
819
|
+
const detail = { fnName, message, data };
|
|
820
|
+
const customEvent = new CustomEvent("log", { detail });
|
|
821
|
+
const clientAny = this.client;
|
|
822
|
+
if (typeof clientAny.emit === "function") {
|
|
823
|
+
clientAny.emit("log", customEvent);
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
clientAny.dispatchEvent?.(customEvent);
|
|
827
|
+
}
|
|
828
|
+
cleanupListeners(reason) {
|
|
829
|
+
const debugPayload = { reason, promptId: this.promptId };
|
|
830
|
+
this.emitLog("CallWrapper.cleanupListeners", "removing listeners", debugPayload);
|
|
831
|
+
this.resolvePromptLoad(false);
|
|
832
|
+
this.stopDisconnectRecovery();
|
|
833
|
+
this.onReconnectHandlerOffFn?.();
|
|
834
|
+
this.onReconnectHandlerOffFn = undefined;
|
|
835
|
+
this.onReconnectFailedHandlerOffFn?.();
|
|
836
|
+
this.onReconnectFailedHandlerOffFn = undefined;
|
|
837
|
+
this.disconnectFailureTimer = null;
|
|
838
|
+
this.onDisconnectedHandlerOffFn?.();
|
|
839
|
+
this.onDisconnectedHandlerOffFn = undefined;
|
|
840
|
+
this.checkExecutingOffFn?.();
|
|
841
|
+
this.checkExecutingOffFn = undefined;
|
|
842
|
+
this.checkExecutedOffFn?.();
|
|
843
|
+
this.checkExecutedOffFn = undefined;
|
|
844
|
+
this.progressHandlerOffFn?.();
|
|
845
|
+
this.progressHandlerOffFn = undefined;
|
|
846
|
+
this.previewHandlerOffFn?.();
|
|
847
|
+
this.previewHandlerOffFn = undefined;
|
|
848
|
+
this.executionHandlerOffFn?.();
|
|
849
|
+
this.executionHandlerOffFn = undefined;
|
|
850
|
+
this.errorHandlerOffFn?.();
|
|
851
|
+
this.errorHandlerOffFn = undefined;
|
|
852
|
+
this.executionEndSuccessOffFn?.();
|
|
853
|
+
this.executionEndSuccessOffFn = undefined;
|
|
854
|
+
this.interruptionHandlerOffFn?.();
|
|
855
|
+
this.interruptionHandlerOffFn = undefined;
|
|
856
|
+
this.statusHandlerOffFn?.();
|
|
857
|
+
this.statusHandlerOffFn = undefined;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
857
860
|
//# sourceMappingURL=call-wrapper.js.map
|