@happyvertical/comfyui 0.80.0 → 0.80.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,478 +1,478 @@
1
1
  import { createLogger } from "@happyvertical/logger";
2
2
  import { ValidationError } from "@happyvertical/utils";
3
- class ComfyUIClient {
4
- options;
5
- ws = null;
6
- logger;
7
- clientId;
8
- messageHandlers = /* @__PURE__ */ new Map();
9
- reconnectAttempts = 0;
10
- isConnecting = false;
11
- username;
12
- password;
13
- constructor(options = {}) {
14
- this.options = {
15
- url: options.url ?? "http://localhost:8188",
16
- timeout: options.timeout ?? 6e5,
17
- headers: options.headers ?? {},
18
- reconnect: {
19
- enabled: options.reconnect?.enabled ?? true,
20
- maxAttempts: options.reconnect?.maxAttempts ?? 5,
21
- delay: options.reconnect?.delay ?? 1e3
22
- }
23
- };
24
- if (options.username && options.password) {
25
- this.username = options.username;
26
- this.password = options.password;
27
- const credentials = btoa(`${options.username}:${options.password}`);
28
- this.options.headers.Authorization = `Basic ${credentials}`;
29
- }
30
- this.logger = createLogger({ level: "info" });
31
- this.clientId = crypto.randomUUID();
32
- }
33
- /**
34
- * Get the HTTP base URL
35
- */
36
- get httpUrl() {
37
- return this.options.url;
38
- }
39
- /**
40
- * Get the WebSocket URL
41
- */
42
- get wsUrl() {
43
- const url = new URL(this.options.url);
44
- url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
45
- url.pathname = "/ws";
46
- url.searchParams.set("clientId", this.clientId);
47
- if (this.username && this.password) {
48
- url.username = this.username;
49
- url.password = this.password;
50
- }
51
- return url.toString();
52
- }
53
- /**
54
- * Connect to the ComfyUI WebSocket
55
- */
56
- async connect() {
57
- if (this.ws?.readyState === WebSocket.OPEN) {
58
- return;
59
- }
60
- if (this.isConnecting) {
61
- await new Promise((resolve, reject) => {
62
- const checkConnection = setInterval(() => {
63
- if (this.ws?.readyState === WebSocket.OPEN) {
64
- clearInterval(checkConnection);
65
- resolve();
66
- } else if (!this.isConnecting) {
67
- clearInterval(checkConnection);
68
- reject(new Error("Connection failed"));
69
- }
70
- }, 100);
71
- });
72
- return;
73
- }
74
- this.isConnecting = true;
75
- try {
76
- await this.establishConnection();
77
- this.reconnectAttempts = 0;
78
- } finally {
79
- this.isConnecting = false;
80
- }
81
- }
82
- /**
83
- * Establish WebSocket connection
84
- */
85
- async establishConnection() {
86
- return new Promise((resolve, reject) => {
87
- try {
88
- this.ws = new WebSocket(this.wsUrl);
89
- const timeout = setTimeout(() => {
90
- if (this.ws?.readyState !== WebSocket.OPEN) {
91
- this.ws?.close();
92
- reject(new Error("WebSocket connection timeout"));
93
- }
94
- }, 1e4);
95
- this.ws.onopen = () => {
96
- clearTimeout(timeout);
97
- this.logger.info("Connected to ComfyUI");
98
- resolve();
99
- };
100
- this.ws.onerror = (error) => {
101
- clearTimeout(timeout);
102
- this.logger.error("WebSocket error", { error });
103
- reject(new Error("WebSocket connection error"));
104
- };
105
- this.ws.onclose = () => {
106
- this.logger.info("Disconnected from ComfyUI");
107
- this.handleDisconnect();
108
- };
109
- this.ws.onmessage = (event) => {
110
- this.handleMessage(event.data);
111
- };
112
- } catch (error) {
113
- reject(error);
114
- }
115
- });
116
- }
117
- /**
118
- * Handle WebSocket disconnection
119
- */
120
- async handleDisconnect() {
121
- const reconnect = this.options.reconnect;
122
- if (!reconnect.enabled) return;
123
- if (this.reconnectAttempts < reconnect.maxAttempts) {
124
- this.reconnectAttempts++;
125
- this.logger.info(
126
- `Attempting reconnection ${this.reconnectAttempts}/${reconnect.maxAttempts}`
127
- );
128
- await new Promise((resolve) => setTimeout(resolve, reconnect.delay));
129
- try {
130
- await this.connect();
131
- } catch (error) {
132
- this.logger.error("Reconnection failed", { error });
133
- }
134
- }
135
- }
136
- /**
137
- * Handle incoming WebSocket messages
138
- */
139
- handleMessage(data) {
140
- try {
141
- const message = JSON.parse(data);
142
- const event = this.parseProgressEvent(message);
143
- if (event?.promptId) {
144
- const handlers = this.messageHandlers.get(event.promptId);
145
- if (handlers) {
146
- for (const handler of handlers) {
147
- handler(event);
148
- }
149
- }
150
- }
151
- } catch (error) {
152
- this.logger.warn("Failed to parse WebSocket message", { error, data });
153
- }
154
- }
155
- /**
156
- * Parse a WebSocket message into a ProgressEvent
157
- */
158
- parseProgressEvent(message) {
159
- switch (message.type) {
160
- case "executing":
161
- return {
162
- type: "executing",
163
- nodeId: message.data?.node,
164
- promptId: message.data?.prompt_id
165
- };
166
- case "progress":
167
- return {
168
- type: "progress",
169
- value: message.data?.value,
170
- max: message.data?.max,
171
- promptId: message.data?.prompt_id
172
- };
173
- case "execution_cached":
174
- return {
175
- type: "execution_cached",
176
- promptId: message.data?.prompt_id
177
- };
178
- case "executed":
179
- return {
180
- type: "executed",
181
- nodeId: message.data?.node,
182
- promptId: message.data?.prompt_id,
183
- output: message.data?.output
184
- };
185
- case "execution_error":
186
- return {
187
- type: "execution_error",
188
- promptId: message.data?.prompt_id,
189
- error: message.data?.exception_message
190
- };
191
- default:
192
- return null;
193
- }
194
- }
195
- /**
196
- * Disconnect from ComfyUI
197
- */
198
- async disconnect() {
199
- this.options.reconnect.enabled = false;
200
- this.messageHandlers.clear();
201
- if (this.ws) {
202
- this.ws.close();
203
- this.ws = null;
204
- }
205
- this.logger.info("Disconnected from ComfyUI");
206
- }
207
- /**
208
- * Make an HTTP request to ComfyUI
209
- */
210
- async request(path, options = {}) {
211
- const url = `${this.httpUrl}${path}`;
212
- const headers = {
213
- ...this.options.headers
214
- };
215
- if (options.body && !options.formData) {
216
- headers["Content-Type"] = "application/json";
217
- }
218
- const response = await fetch(url, {
219
- method: options.method ?? "GET",
220
- headers,
221
- body: options.formData ?? (options.body ? JSON.stringify(options.body) : void 0),
222
- signal: AbortSignal.timeout(this.options.timeout)
223
- });
224
- if (!response.ok) {
225
- const errorText = await response.text();
226
- throw new Error(
227
- `ComfyUI request failed: ${response.status} ${errorText}`
228
- );
229
- }
230
- return response.json();
231
- }
232
- /**
233
- * Queue a workflow for execution
234
- *
235
- * @param workflow - The ComfyUI workflow to execute
236
- * @param clientId - Optional client ID (uses default if not provided)
237
- * @returns Promise resolving to prompt result with ID
238
- */
239
- async queuePrompt(workflow, clientId) {
240
- const response = await this.request("/prompt", {
241
- method: "POST",
242
- body: {
243
- prompt: workflow,
244
- client_id: clientId ?? this.clientId
245
- }
246
- });
247
- return {
248
- promptId: response.prompt_id,
249
- number: response.number,
250
- nodeErrors: response.node_errors
251
- };
252
- }
253
- /**
254
- * Subscribe to progress events for a prompt
255
- *
256
- * @param promptId - The prompt ID to monitor
257
- * @param handler - Callback for progress events
258
- * @returns Unsubscribe function
259
- */
260
- onProgress(promptId, handler) {
261
- if (!this.messageHandlers.has(promptId)) {
262
- this.messageHandlers.set(promptId, /* @__PURE__ */ new Set());
263
- }
264
- this.messageHandlers.get(promptId)?.add(handler);
265
- return () => {
266
- this.messageHandlers.get(promptId)?.delete(handler);
267
- if (this.messageHandlers.get(promptId)?.size === 0) {
268
- this.messageHandlers.delete(promptId);
269
- }
270
- };
271
- }
272
- /**
273
- * Wait for a prompt to complete
274
- *
275
- * @param promptId - The prompt ID to wait for
276
- * @param options - Wait options including progress callback
277
- * @returns Promise resolving to the history entry
278
- */
279
- async waitForCompletion(promptId, options = {}) {
280
- const timeout = options.timeout ?? this.options.timeout;
281
- const startTime = Date.now();
282
- return new Promise((resolve, reject) => {
283
- let resolved = false;
284
- const timeoutId = timeout > 0 ? setTimeout(() => {
285
- if (!resolved) {
286
- resolved = true;
287
- unsubscribe();
288
- reject(new Error(`Timeout waiting for prompt ${promptId}`));
289
- }
290
- }, timeout) : null;
291
- const unsubscribe = this.onProgress(promptId, async (event) => {
292
- options.onProgress?.(event);
293
- if (event.type === "executed" && event.nodeId === null) {
294
- if (!resolved) {
295
- resolved = true;
296
- if (timeoutId) clearTimeout(timeoutId);
297
- unsubscribe();
298
- try {
299
- const history = await this.getHistory(promptId);
300
- resolve(history);
301
- } catch (error) {
302
- reject(error);
303
- }
304
- }
305
- } else if (event.type === "execution_error") {
306
- if (!resolved) {
307
- resolved = true;
308
- if (timeoutId) clearTimeout(timeoutId);
309
- unsubscribe();
310
- reject(new Error(`Workflow execution error: ${event.error}`));
311
- }
312
- }
313
- });
314
- const pollHistory = async () => {
315
- while (!resolved && (timeout === 0 || Date.now() - startTime < timeout)) {
316
- await new Promise((r) => setTimeout(r, options.pollInterval ?? 1e3));
317
- if (resolved) break;
318
- try {
319
- const history = await this.getHistory(promptId);
320
- if (history.status.completed) {
321
- if (!resolved) {
322
- resolved = true;
323
- if (timeoutId) clearTimeout(timeoutId);
324
- unsubscribe();
325
- resolve(history);
326
- }
327
- break;
328
- }
329
- } catch {
330
- }
331
- }
332
- };
333
- pollHistory().catch(() => {
334
- });
335
- });
336
- }
337
- /**
338
- * Get history for a specific prompt
339
- *
340
- * @param promptId - The prompt ID to get history for
341
- * @returns Promise resolving to the history entry
342
- */
343
- async getHistory(promptId) {
344
- const response = await this.request(
345
- `/history/${promptId}`
346
- );
347
- const entry = response[promptId];
348
- if (!entry) {
349
- throw new ValidationError(`History not found for prompt ${promptId}`);
350
- }
351
- return {
352
- promptId,
353
- status: entry.status,
354
- outputs: entry.outputs,
355
- prompt: entry.prompt
356
- };
357
- }
358
- /**
359
- * Download an output file from ComfyUI
360
- *
361
- * @param filename - Name of the file to download
362
- * @param subfolder - Subfolder where file is located
363
- * @param type - Type of file location ('output', 'temp', or 'input')
364
- * @returns Promise resolving to the file buffer
365
- */
366
- async downloadOutput(filename, subfolder = "", type = "output") {
367
- const params = new URLSearchParams({
368
- filename,
369
- subfolder,
370
- type
371
- });
372
- const url = `${this.httpUrl}/view?${params}`;
373
- const response = await fetch(url, {
374
- headers: this.options.headers,
375
- signal: AbortSignal.timeout(this.options.timeout)
376
- });
377
- if (!response.ok) {
378
- throw new Error(`Failed to download file: ${response.status}`);
379
- }
380
- const arrayBuffer = await response.arrayBuffer();
381
- return Buffer.from(arrayBuffer);
382
- }
383
- /**
384
- * Upload a file to ComfyUI
385
- *
386
- * @param file - File buffer to upload
387
- * @param filename - Name for the file
388
- * @param type - Where to upload ('input' or 'temp')
389
- * @param overwrite - Whether to overwrite existing file
390
- * @returns Promise resolving to upload result
391
- */
392
- async uploadFile(file, filename, type = "input", overwrite = false) {
393
- const formData = new FormData();
394
- formData.append("image", new Blob([new Uint8Array(file)]), filename);
395
- formData.append("type", type);
396
- formData.append("overwrite", String(overwrite));
397
- const response = await this.request("/upload/image", {
398
- method: "POST",
399
- formData
400
- });
401
- return {
402
- name: response.name,
403
- subfolder: response.subfolder,
404
- type: response.type
405
- };
406
- }
407
- /**
408
- * Get system statistics from ComfyUI
409
- *
410
- * @returns Promise resolving to system stats
411
- */
412
- async getSystemStats() {
413
- return this.request("/system_stats");
414
- }
415
- /**
416
- * Get the current queue status
417
- *
418
- * @returns Promise resolving to queue status
419
- */
420
- async getQueue() {
421
- const response = await this.request("/queue");
422
- return {
423
- running: response.queue_running,
424
- pending: response.queue_pending
425
- };
426
- }
427
- /**
428
- * Interrupt the current execution
429
- */
430
- async interrupt() {
431
- await this.request("/interrupt", { method: "POST" });
432
- }
433
- /**
434
- * Clear the execution queue
435
- */
436
- async clearQueue() {
437
- await this.request("/queue", {
438
- method: "POST",
439
- body: { clear: true }
440
- });
441
- }
442
- /**
443
- * Delete a specific item from the queue
444
- *
445
- * @param promptId - The prompt ID to delete
446
- */
447
- async deleteFromQueue(promptId) {
448
- await this.request("/queue", {
449
- method: "POST",
450
- body: { delete: [promptId] }
451
- });
452
- }
453
- }
3
+ //#region src/client.ts
4
+ /**
5
+ * ComfyUI Client
6
+ *
7
+ * WebSocket and REST client for interacting with ComfyUI servers.
8
+ * Supports workflow execution, progress monitoring, and file management.
9
+ */
10
+ /**
11
+ * ComfyUI client for workflow execution and management
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * import { ComfyUIClient } from '@happyvertical/comfyui';
16
+ *
17
+ * const client = new ComfyUIClient({
18
+ * url: 'http://localhost:8188',
19
+ * });
20
+ *
21
+ * await client.connect();
22
+ *
23
+ * const result = await client.queuePrompt(workflow);
24
+ * await client.waitForCompletion(result.promptId, {
25
+ * onProgress: (event) => console.log(`Progress: ${event.value}%`),
26
+ * });
27
+ *
28
+ * const output = await client.downloadOutput('video.mp4');
29
+ * await client.disconnect();
30
+ * ```
31
+ */
32
+ var ComfyUIClient = class {
33
+ options;
34
+ ws = null;
35
+ logger;
36
+ clientId;
37
+ messageHandlers = /* @__PURE__ */ new Map();
38
+ reconnectAttempts = 0;
39
+ isConnecting = false;
40
+ username;
41
+ password;
42
+ constructor(options = {}) {
43
+ this.options = {
44
+ url: options.url ?? "http://localhost:8188",
45
+ timeout: options.timeout ?? 6e5,
46
+ headers: options.headers ?? {},
47
+ reconnect: {
48
+ enabled: options.reconnect?.enabled ?? true,
49
+ maxAttempts: options.reconnect?.maxAttempts ?? 5,
50
+ delay: options.reconnect?.delay ?? 1e3
51
+ }
52
+ };
53
+ if (options.username && options.password) {
54
+ this.username = options.username;
55
+ this.password = options.password;
56
+ const credentials = btoa(`${options.username}:${options.password}`);
57
+ this.options.headers.Authorization = `Basic ${credentials}`;
58
+ }
59
+ this.logger = createLogger({ level: "info" });
60
+ this.clientId = crypto.randomUUID();
61
+ }
62
+ /**
63
+ * Get the HTTP base URL
64
+ */
65
+ get httpUrl() {
66
+ return this.options.url;
67
+ }
68
+ /**
69
+ * Get the WebSocket URL
70
+ */
71
+ get wsUrl() {
72
+ const url = new URL(this.options.url);
73
+ url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
74
+ url.pathname = "/ws";
75
+ url.searchParams.set("clientId", this.clientId);
76
+ if (this.username && this.password) {
77
+ url.username = this.username;
78
+ url.password = this.password;
79
+ }
80
+ return url.toString();
81
+ }
82
+ /**
83
+ * Connect to the ComfyUI WebSocket
84
+ */
85
+ async connect() {
86
+ if (this.ws?.readyState === WebSocket.OPEN) return;
87
+ if (this.isConnecting) {
88
+ await new Promise((resolve, reject) => {
89
+ const checkConnection = setInterval(() => {
90
+ if (this.ws?.readyState === WebSocket.OPEN) {
91
+ clearInterval(checkConnection);
92
+ resolve();
93
+ } else if (!this.isConnecting) {
94
+ clearInterval(checkConnection);
95
+ reject(/* @__PURE__ */ new Error("Connection failed"));
96
+ }
97
+ }, 100);
98
+ });
99
+ return;
100
+ }
101
+ this.isConnecting = true;
102
+ try {
103
+ await this.establishConnection();
104
+ this.reconnectAttempts = 0;
105
+ } finally {
106
+ this.isConnecting = false;
107
+ }
108
+ }
109
+ /**
110
+ * Establish WebSocket connection
111
+ */
112
+ async establishConnection() {
113
+ return new Promise((resolve, reject) => {
114
+ try {
115
+ this.ws = new WebSocket(this.wsUrl);
116
+ const timeout = setTimeout(() => {
117
+ if (this.ws?.readyState !== WebSocket.OPEN) {
118
+ this.ws?.close();
119
+ reject(/* @__PURE__ */ new Error("WebSocket connection timeout"));
120
+ }
121
+ }, 1e4);
122
+ this.ws.onopen = () => {
123
+ clearTimeout(timeout);
124
+ this.logger.info("Connected to ComfyUI");
125
+ resolve();
126
+ };
127
+ this.ws.onerror = (error) => {
128
+ clearTimeout(timeout);
129
+ this.logger.error("WebSocket error", { error });
130
+ reject(/* @__PURE__ */ new Error("WebSocket connection error"));
131
+ };
132
+ this.ws.onclose = () => {
133
+ this.logger.info("Disconnected from ComfyUI");
134
+ this.handleDisconnect();
135
+ };
136
+ this.ws.onmessage = (event) => {
137
+ this.handleMessage(event.data);
138
+ };
139
+ } catch (error) {
140
+ reject(error);
141
+ }
142
+ });
143
+ }
144
+ /**
145
+ * Handle WebSocket disconnection
146
+ */
147
+ async handleDisconnect() {
148
+ const reconnect = this.options.reconnect;
149
+ if (!reconnect.enabled) return;
150
+ if (this.reconnectAttempts < reconnect.maxAttempts) {
151
+ this.reconnectAttempts++;
152
+ this.logger.info(`Attempting reconnection ${this.reconnectAttempts}/${reconnect.maxAttempts}`);
153
+ await new Promise((resolve) => setTimeout(resolve, reconnect.delay));
154
+ try {
155
+ await this.connect();
156
+ } catch (error) {
157
+ this.logger.error("Reconnection failed", { error });
158
+ }
159
+ }
160
+ }
161
+ /**
162
+ * Handle incoming WebSocket messages
163
+ */
164
+ handleMessage(data) {
165
+ try {
166
+ const message = JSON.parse(data);
167
+ const event = this.parseProgressEvent(message);
168
+ if (event?.promptId) {
169
+ const handlers = this.messageHandlers.get(event.promptId);
170
+ if (handlers) for (const handler of handlers) handler(event);
171
+ }
172
+ } catch (error) {
173
+ this.logger.warn("Failed to parse WebSocket message", {
174
+ error,
175
+ data
176
+ });
177
+ }
178
+ }
179
+ /**
180
+ * Parse a WebSocket message into a ProgressEvent
181
+ */
182
+ parseProgressEvent(message) {
183
+ switch (message.type) {
184
+ case "executing": return {
185
+ type: "executing",
186
+ nodeId: message.data?.node,
187
+ promptId: message.data?.prompt_id
188
+ };
189
+ case "progress": return {
190
+ type: "progress",
191
+ value: message.data?.value,
192
+ max: message.data?.max,
193
+ promptId: message.data?.prompt_id
194
+ };
195
+ case "execution_cached": return {
196
+ type: "execution_cached",
197
+ promptId: message.data?.prompt_id
198
+ };
199
+ case "executed": return {
200
+ type: "executed",
201
+ nodeId: message.data?.node,
202
+ promptId: message.data?.prompt_id,
203
+ output: message.data?.output
204
+ };
205
+ case "execution_error": return {
206
+ type: "execution_error",
207
+ promptId: message.data?.prompt_id,
208
+ error: message.data?.exception_message
209
+ };
210
+ default: return null;
211
+ }
212
+ }
213
+ /**
214
+ * Disconnect from ComfyUI
215
+ */
216
+ async disconnect() {
217
+ this.options.reconnect.enabled = false;
218
+ this.messageHandlers.clear();
219
+ if (this.ws) {
220
+ this.ws.close();
221
+ this.ws = null;
222
+ }
223
+ this.logger.info("Disconnected from ComfyUI");
224
+ }
225
+ /**
226
+ * Make an HTTP request to ComfyUI
227
+ */
228
+ async request(path, options = {}) {
229
+ const url = `${this.httpUrl}${path}`;
230
+ const headers = { ...this.options.headers };
231
+ if (options.body && !options.formData) headers["Content-Type"] = "application/json";
232
+ const response = await fetch(url, {
233
+ method: options.method ?? "GET",
234
+ headers,
235
+ body: options.formData ?? (options.body ? JSON.stringify(options.body) : void 0),
236
+ signal: AbortSignal.timeout(this.options.timeout)
237
+ });
238
+ if (!response.ok) {
239
+ const errorText = await response.text();
240
+ throw new Error(`ComfyUI request failed: ${response.status} ${errorText}`);
241
+ }
242
+ return response.json();
243
+ }
244
+ /**
245
+ * Queue a workflow for execution
246
+ *
247
+ * @param workflow - The ComfyUI workflow to execute
248
+ * @param clientId - Optional client ID (uses default if not provided)
249
+ * @returns Promise resolving to prompt result with ID
250
+ */
251
+ async queuePrompt(workflow, clientId) {
252
+ const response = await this.request("/prompt", {
253
+ method: "POST",
254
+ body: {
255
+ prompt: workflow,
256
+ client_id: clientId ?? this.clientId
257
+ }
258
+ });
259
+ return {
260
+ promptId: response.prompt_id,
261
+ number: response.number,
262
+ nodeErrors: response.node_errors
263
+ };
264
+ }
265
+ /**
266
+ * Subscribe to progress events for a prompt
267
+ *
268
+ * @param promptId - The prompt ID to monitor
269
+ * @param handler - Callback for progress events
270
+ * @returns Unsubscribe function
271
+ */
272
+ onProgress(promptId, handler) {
273
+ if (!this.messageHandlers.has(promptId)) this.messageHandlers.set(promptId, /* @__PURE__ */ new Set());
274
+ this.messageHandlers.get(promptId)?.add(handler);
275
+ return () => {
276
+ this.messageHandlers.get(promptId)?.delete(handler);
277
+ if (this.messageHandlers.get(promptId)?.size === 0) this.messageHandlers.delete(promptId);
278
+ };
279
+ }
280
+ /**
281
+ * Wait for a prompt to complete
282
+ *
283
+ * @param promptId - The prompt ID to wait for
284
+ * @param options - Wait options including progress callback
285
+ * @returns Promise resolving to the history entry
286
+ */
287
+ async waitForCompletion(promptId, options = {}) {
288
+ const timeout = options.timeout ?? this.options.timeout;
289
+ const startTime = Date.now();
290
+ return new Promise((resolve, reject) => {
291
+ let resolved = false;
292
+ const timeoutId = timeout > 0 ? setTimeout(() => {
293
+ if (!resolved) {
294
+ resolved = true;
295
+ unsubscribe();
296
+ reject(/* @__PURE__ */ new Error(`Timeout waiting for prompt ${promptId}`));
297
+ }
298
+ }, timeout) : null;
299
+ const unsubscribe = this.onProgress(promptId, async (event) => {
300
+ options.onProgress?.(event);
301
+ if (event.type === "executed" && event.nodeId === null) {
302
+ if (!resolved) {
303
+ resolved = true;
304
+ if (timeoutId) clearTimeout(timeoutId);
305
+ unsubscribe();
306
+ try {
307
+ resolve(await this.getHistory(promptId));
308
+ } catch (error) {
309
+ reject(error);
310
+ }
311
+ }
312
+ } else if (event.type === "execution_error") {
313
+ if (!resolved) {
314
+ resolved = true;
315
+ if (timeoutId) clearTimeout(timeoutId);
316
+ unsubscribe();
317
+ reject(/* @__PURE__ */ new Error(`Workflow execution error: ${event.error}`));
318
+ }
319
+ }
320
+ });
321
+ const pollHistory = async () => {
322
+ while (!resolved && (timeout === 0 || Date.now() - startTime < timeout)) {
323
+ await new Promise((r) => setTimeout(r, options.pollInterval ?? 1e3));
324
+ if (resolved) break;
325
+ try {
326
+ const history = await this.getHistory(promptId);
327
+ if (history.status.completed) {
328
+ if (!resolved) {
329
+ resolved = true;
330
+ if (timeoutId) clearTimeout(timeoutId);
331
+ unsubscribe();
332
+ resolve(history);
333
+ }
334
+ break;
335
+ }
336
+ } catch {}
337
+ }
338
+ };
339
+ pollHistory().catch(() => {});
340
+ });
341
+ }
342
+ /**
343
+ * Get history for a specific prompt
344
+ *
345
+ * @param promptId - The prompt ID to get history for
346
+ * @returns Promise resolving to the history entry
347
+ */
348
+ async getHistory(promptId) {
349
+ const entry = (await this.request(`/history/${promptId}`))[promptId];
350
+ if (!entry) throw new ValidationError(`History not found for prompt ${promptId}`);
351
+ return {
352
+ promptId,
353
+ status: entry.status,
354
+ outputs: entry.outputs,
355
+ prompt: entry.prompt
356
+ };
357
+ }
358
+ /**
359
+ * Download an output file from ComfyUI
360
+ *
361
+ * @param filename - Name of the file to download
362
+ * @param subfolder - Subfolder where file is located
363
+ * @param type - Type of file location ('output', 'temp', or 'input')
364
+ * @returns Promise resolving to the file buffer
365
+ */
366
+ async downloadOutput(filename, subfolder = "", type = "output") {
367
+ const params = new URLSearchParams({
368
+ filename,
369
+ subfolder,
370
+ type
371
+ });
372
+ const url = `${this.httpUrl}/view?${params}`;
373
+ const response = await fetch(url, {
374
+ headers: this.options.headers,
375
+ signal: AbortSignal.timeout(this.options.timeout)
376
+ });
377
+ if (!response.ok) throw new Error(`Failed to download file: ${response.status}`);
378
+ const arrayBuffer = await response.arrayBuffer();
379
+ return Buffer.from(arrayBuffer);
380
+ }
381
+ /**
382
+ * Upload a file to ComfyUI
383
+ *
384
+ * @param file - File buffer to upload
385
+ * @param filename - Name for the file
386
+ * @param type - Where to upload ('input' or 'temp')
387
+ * @param overwrite - Whether to overwrite existing file
388
+ * @returns Promise resolving to upload result
389
+ */
390
+ async uploadFile(file, filename, type = "input", overwrite = false) {
391
+ const formData = new FormData();
392
+ formData.append("image", new Blob([new Uint8Array(file)]), filename);
393
+ formData.append("type", type);
394
+ formData.append("overwrite", String(overwrite));
395
+ const response = await this.request("/upload/image", {
396
+ method: "POST",
397
+ formData
398
+ });
399
+ return {
400
+ name: response.name,
401
+ subfolder: response.subfolder,
402
+ type: response.type
403
+ };
404
+ }
405
+ /**
406
+ * Get system statistics from ComfyUI
407
+ *
408
+ * @returns Promise resolving to system stats
409
+ */
410
+ async getSystemStats() {
411
+ return this.request("/system_stats");
412
+ }
413
+ /**
414
+ * Get the current queue status
415
+ *
416
+ * @returns Promise resolving to queue status
417
+ */
418
+ async getQueue() {
419
+ const response = await this.request("/queue");
420
+ return {
421
+ running: response.queue_running,
422
+ pending: response.queue_pending
423
+ };
424
+ }
425
+ /**
426
+ * Interrupt the current execution
427
+ */
428
+ async interrupt() {
429
+ await this.request("/interrupt", { method: "POST" });
430
+ }
431
+ /**
432
+ * Clear the execution queue
433
+ */
434
+ async clearQueue() {
435
+ await this.request("/queue", {
436
+ method: "POST",
437
+ body: { clear: true }
438
+ });
439
+ }
440
+ /**
441
+ * Delete a specific item from the queue
442
+ *
443
+ * @param promptId - The prompt ID to delete
444
+ */
445
+ async deleteFromQueue(promptId) {
446
+ await this.request("/queue", {
447
+ method: "POST",
448
+ body: { delete: [promptId] }
449
+ });
450
+ }
451
+ };
452
+ /**
453
+ * Helper function to inject parameters into a workflow
454
+ *
455
+ * @param workflow - The base workflow
456
+ * @param mapping - Node mapping defining where to inject
457
+ * @param values - Values to inject
458
+ * @returns Modified workflow with injected values
459
+ */
454
460
  function injectWorkflowParams(workflow, mapping, values) {
455
- const result = JSON.parse(JSON.stringify(workflow));
456
- for (const [key, nodeId] of Object.entries(mapping)) {
457
- if (nodeId && values[key] !== void 0 && result[nodeId]) {
458
- const node = result[nodeId];
459
- const inputFieldMap = {
460
- seedImage: "image",
461
- audioFile: "audio",
462
- baseVideo: "video",
463
- prompt: "text",
464
- negativePrompt: "text"
465
- };
466
- const inputField = inputFieldMap[key] ?? key;
467
- if (node.inputs && inputField in node.inputs) {
468
- node.inputs[inputField] = values[key];
469
- }
470
- }
471
- }
472
- return result;
461
+ const result = JSON.parse(JSON.stringify(workflow));
462
+ for (const [key, nodeId] of Object.entries(mapping)) if (nodeId && values[key] !== void 0 && result[nodeId]) {
463
+ const node = result[nodeId];
464
+ const inputField = {
465
+ seedImage: "image",
466
+ audioFile: "audio",
467
+ baseVideo: "video",
468
+ prompt: "text",
469
+ negativePrompt: "text"
470
+ }[key] ?? key;
471
+ if (node.inputs && inputField in node.inputs) node.inputs[inputField] = values[key];
472
+ }
473
+ return result;
473
474
  }
474
- export {
475
- ComfyUIClient,
476
- injectWorkflowParams
477
- };
478
- //# sourceMappingURL=index.js.map
475
+ //#endregion
476
+ export { ComfyUIClient, injectWorkflowParams };
477
+
478
+ //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/client.ts"],"sourcesContent":["/**\n * ComfyUI Client\n *\n * WebSocket and REST client for interacting with ComfyUI servers.\n * Supports workflow execution, progress monitoring, and file management.\n */\n\nimport { createLogger, type Logger } from '@happyvertical/logger';\nimport { ValidationError } from '@happyvertical/utils';\n\nimport type {\n ComfyUIClientOptions,\n ComfyWorkflow,\n HistoryEntry,\n NodeMapping,\n ProgressEvent,\n PromptResult,\n QueueStatus,\n ResolvedClientOptions,\n SystemStats,\n UploadResult,\n WaitOptions,\n} from './types.js';\n\n/**\n * WebSocket message types from ComfyUI.\n *\n * `data` carries the union of fields the various message types\n * use, discriminated by `type` at runtime in `parseProgressEvent`.\n * Previously typed as `unknown`, which made every\n * `message.data?.<field>` access in that switch fail TS2339 — the\n * narrowing would have required parser-side type guards, much\n * heavier than just listing the actual fields here.\n */\ninterface WSMessage {\n type: string;\n data?: {\n // `node` carries a real `null` value from the ComfyUI protocol —\n // `parseProgressEvent` writes it through to `ProgressEvent.nodeId`,\n // and `waitForCompletion` uses `event.nodeId === null` as the\n // \"no more nodes to execute\" completion sentinel. The previous\n // `data?: unknown` type accepted null implicitly; this narrowed\n // type must keep it. (See `packages/comfyui/src/client.ts:436`\n // and the comment about completion above it.)\n node?: string | null;\n prompt_id?: string;\n output?: Record<string, unknown>;\n value?: number;\n max?: number;\n exception_message?: string;\n };\n}\n\n/**\n * ComfyUI client for workflow execution and management\n *\n * @example\n * ```typescript\n * import { ComfyUIClient } from '@happyvertical/comfyui';\n *\n * const client = new ComfyUIClient({\n * url: 'http://localhost:8188',\n * });\n *\n * await client.connect();\n *\n * const result = await client.queuePrompt(workflow);\n * await client.waitForCompletion(result.promptId, {\n * onProgress: (event) => console.log(`Progress: ${event.value}%`),\n * });\n *\n * const output = await client.downloadOutput('video.mp4');\n * await client.disconnect();\n * ```\n */\nexport class ComfyUIClient {\n private options: ResolvedClientOptions;\n private ws: WebSocket | null = null;\n private logger: Logger;\n private clientId: string;\n private messageHandlers: Map<string, Set<(event: ProgressEvent) => void>> =\n new Map();\n private reconnectAttempts = 0;\n private isConnecting = false;\n private username?: string;\n private password?: string;\n\n constructor(options: ComfyUIClientOptions = {}) {\n this.options = {\n url: options.url ?? 'http://localhost:8188',\n timeout: options.timeout ?? 600000,\n headers: options.headers ?? {},\n reconnect: {\n enabled: options.reconnect?.enabled ?? true,\n maxAttempts: options.reconnect?.maxAttempts ?? 5,\n delay: options.reconnect?.delay ?? 1000,\n },\n };\n\n if (options.username && options.password) {\n this.username = options.username;\n this.password = options.password;\n const credentials = btoa(`${options.username}:${options.password}`);\n this.options.headers.Authorization = `Basic ${credentials}`;\n }\n\n this.logger = createLogger({ level: 'info' });\n this.clientId = crypto.randomUUID();\n }\n\n /**\n * Get the HTTP base URL\n */\n private get httpUrl(): string {\n return this.options.url;\n }\n\n /**\n * Get the WebSocket URL\n */\n private get wsUrl(): string {\n const url = new URL(this.options.url);\n url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';\n url.pathname = '/ws';\n url.searchParams.set('clientId', this.clientId);\n if (this.username && this.password) {\n url.username = this.username;\n url.password = this.password;\n }\n return url.toString();\n }\n\n /**\n * Connect to the ComfyUI WebSocket\n */\n async connect(): Promise<void> {\n if (this.ws?.readyState === WebSocket.OPEN) {\n return;\n }\n\n if (this.isConnecting) {\n // Wait for existing connection attempt\n await new Promise<void>((resolve, reject) => {\n const checkConnection = setInterval(() => {\n if (this.ws?.readyState === WebSocket.OPEN) {\n clearInterval(checkConnection);\n resolve();\n } else if (!this.isConnecting) {\n clearInterval(checkConnection);\n reject(new Error('Connection failed'));\n }\n }, 100);\n });\n return;\n }\n\n this.isConnecting = true;\n\n try {\n await this.establishConnection();\n this.reconnectAttempts = 0;\n } finally {\n this.isConnecting = false;\n }\n }\n\n /**\n * Establish WebSocket connection\n */\n private async establishConnection(): Promise<void> {\n return new Promise((resolve, reject) => {\n try {\n this.ws = new WebSocket(this.wsUrl);\n\n const timeout = setTimeout(() => {\n if (this.ws?.readyState !== WebSocket.OPEN) {\n this.ws?.close();\n reject(new Error('WebSocket connection timeout'));\n }\n }, 10000);\n\n this.ws.onopen = () => {\n clearTimeout(timeout);\n this.logger.info('Connected to ComfyUI');\n resolve();\n };\n\n this.ws.onerror = (error) => {\n clearTimeout(timeout);\n this.logger.error('WebSocket error', { error });\n reject(new Error('WebSocket connection error'));\n };\n\n this.ws.onclose = () => {\n this.logger.info('Disconnected from ComfyUI');\n this.handleDisconnect();\n };\n\n this.ws.onmessage = (event) => {\n this.handleMessage(event.data);\n };\n } catch (error) {\n reject(error);\n }\n });\n }\n\n /**\n * Handle WebSocket disconnection\n */\n private async handleDisconnect(): Promise<void> {\n const reconnect = this.options.reconnect;\n if (!reconnect.enabled) return;\n\n if (this.reconnectAttempts < reconnect.maxAttempts) {\n this.reconnectAttempts++;\n this.logger.info(\n `Attempting reconnection ${this.reconnectAttempts}/${reconnect.maxAttempts}`,\n );\n\n await new Promise((resolve) => setTimeout(resolve, reconnect.delay));\n\n try {\n await this.connect();\n } catch (error) {\n this.logger.error('Reconnection failed', { error });\n }\n }\n }\n\n /**\n * Handle incoming WebSocket messages\n */\n private handleMessage(data: string): void {\n try {\n const message: WSMessage = JSON.parse(data);\n const event = this.parseProgressEvent(message);\n\n if (event?.promptId) {\n const handlers = this.messageHandlers.get(event.promptId);\n if (handlers) {\n for (const handler of handlers) {\n handler(event);\n }\n }\n }\n } catch (error) {\n this.logger.warn('Failed to parse WebSocket message', { error, data });\n }\n }\n\n /**\n * Parse a WebSocket message into a ProgressEvent\n */\n private parseProgressEvent(message: WSMessage): ProgressEvent | null {\n switch (message.type) {\n case 'executing':\n return {\n type: 'executing',\n nodeId: message.data?.node,\n promptId: message.data?.prompt_id,\n };\n\n case 'progress':\n return {\n type: 'progress',\n value: message.data?.value,\n max: message.data?.max,\n promptId: message.data?.prompt_id,\n };\n\n case 'execution_cached':\n return {\n type: 'execution_cached',\n promptId: message.data?.prompt_id,\n };\n\n case 'executed':\n return {\n type: 'executed',\n nodeId: message.data?.node,\n promptId: message.data?.prompt_id,\n output: message.data?.output,\n };\n\n case 'execution_error':\n return {\n type: 'execution_error',\n promptId: message.data?.prompt_id,\n error: message.data?.exception_message,\n };\n\n default:\n return null;\n }\n }\n\n /**\n * Disconnect from ComfyUI\n */\n async disconnect(): Promise<void> {\n this.options.reconnect.enabled = false; // Prevent auto-reconnect\n this.messageHandlers.clear();\n\n if (this.ws) {\n this.ws.close();\n this.ws = null;\n }\n\n this.logger.info('Disconnected from ComfyUI');\n }\n\n /**\n * Make an HTTP request to ComfyUI\n */\n private async request<T>(\n path: string,\n options: {\n method?: 'GET' | 'POST' | 'DELETE';\n body?: unknown;\n formData?: FormData;\n } = {},\n ): Promise<T> {\n const url = `${this.httpUrl}${path}`;\n\n const headers: Record<string, string> = {\n ...this.options.headers,\n };\n\n if (options.body && !options.formData) {\n headers['Content-Type'] = 'application/json';\n }\n\n const response = await fetch(url, {\n method: options.method ?? 'GET',\n headers,\n body:\n options.formData ??\n (options.body ? JSON.stringify(options.body) : undefined),\n signal: AbortSignal.timeout(this.options.timeout),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(\n `ComfyUI request failed: ${response.status} ${errorText}`,\n );\n }\n\n return response.json();\n }\n\n /**\n * Queue a workflow for execution\n *\n * @param workflow - The ComfyUI workflow to execute\n * @param clientId - Optional client ID (uses default if not provided)\n * @returns Promise resolving to prompt result with ID\n */\n async queuePrompt(\n workflow: ComfyWorkflow,\n clientId?: string,\n ): Promise<PromptResult> {\n const response = await this.request<{\n prompt_id: string;\n number: number;\n node_errors?: Record<string, string[]>;\n }>('/prompt', {\n method: 'POST',\n body: {\n prompt: workflow,\n client_id: clientId ?? this.clientId,\n },\n });\n\n return {\n promptId: response.prompt_id,\n number: response.number,\n nodeErrors: response.node_errors,\n };\n }\n\n /**\n * Subscribe to progress events for a prompt\n *\n * @param promptId - The prompt ID to monitor\n * @param handler - Callback for progress events\n * @returns Unsubscribe function\n */\n onProgress(\n promptId: string,\n handler: (event: ProgressEvent) => void,\n ): () => void {\n if (!this.messageHandlers.has(promptId)) {\n this.messageHandlers.set(promptId, new Set());\n }\n\n this.messageHandlers.get(promptId)?.add(handler);\n\n return () => {\n this.messageHandlers.get(promptId)?.delete(handler);\n if (this.messageHandlers.get(promptId)?.size === 0) {\n this.messageHandlers.delete(promptId);\n }\n };\n }\n\n /**\n * Wait for a prompt to complete\n *\n * @param promptId - The prompt ID to wait for\n * @param options - Wait options including progress callback\n * @returns Promise resolving to the history entry\n */\n async waitForCompletion(\n promptId: string,\n options: WaitOptions = {},\n ): Promise<HistoryEntry> {\n const timeout = options.timeout ?? this.options.timeout;\n const startTime = Date.now();\n\n return new Promise((resolve, reject) => {\n let resolved = false;\n\n // Set up timeout\n const timeoutId =\n timeout > 0\n ? setTimeout(() => {\n if (!resolved) {\n resolved = true;\n unsubscribe();\n reject(new Error(`Timeout waiting for prompt ${promptId}`));\n }\n }, timeout)\n : null;\n\n // Subscribe to progress events\n const unsubscribe = this.onProgress(promptId, async (event) => {\n // Forward progress events\n options.onProgress?.(event);\n\n // Check for completion or error\n if (event.type === 'executed' && event.nodeId === null) {\n // Workflow completed (null nodeId means no more nodes to execute)\n if (!resolved) {\n resolved = true;\n if (timeoutId) clearTimeout(timeoutId);\n unsubscribe();\n\n try {\n const history = await this.getHistory(promptId);\n resolve(history);\n } catch (error) {\n reject(error);\n }\n }\n } else if (event.type === 'execution_error') {\n if (!resolved) {\n resolved = true;\n if (timeoutId) clearTimeout(timeoutId);\n unsubscribe();\n reject(new Error(`Workflow execution error: ${event.error}`));\n }\n }\n });\n\n // Fallback: poll history if WebSocket doesn't give completion\n const pollHistory = async () => {\n while (\n !resolved &&\n (timeout === 0 || Date.now() - startTime < timeout)\n ) {\n await new Promise((r) => setTimeout(r, options.pollInterval ?? 1000));\n\n if (resolved) break;\n\n try {\n const history = await this.getHistory(promptId);\n if (history.status.completed) {\n if (!resolved) {\n resolved = true;\n if (timeoutId) clearTimeout(timeoutId);\n unsubscribe();\n resolve(history);\n }\n break;\n }\n } catch {\n // History not available yet, continue polling\n }\n }\n };\n\n // Start polling as fallback\n pollHistory().catch(() => {});\n });\n }\n\n /**\n * Get history for a specific prompt\n *\n * @param promptId - The prompt ID to get history for\n * @returns Promise resolving to the history entry\n */\n async getHistory(promptId: string): Promise<HistoryEntry> {\n const response = await this.request<Record<string, any>>(\n `/history/${promptId}`,\n );\n\n const entry = response[promptId];\n if (!entry) {\n throw new ValidationError(`History not found for prompt ${promptId}`);\n }\n\n return {\n promptId,\n status: entry.status,\n outputs: entry.outputs,\n prompt: entry.prompt,\n };\n }\n\n /**\n * Download an output file from ComfyUI\n *\n * @param filename - Name of the file to download\n * @param subfolder - Subfolder where file is located\n * @param type - Type of file location ('output', 'temp', or 'input')\n * @returns Promise resolving to the file buffer\n */\n async downloadOutput(\n filename: string,\n subfolder: string = '',\n type: 'output' | 'temp' | 'input' = 'output',\n ): Promise<Buffer> {\n const params = new URLSearchParams({\n filename,\n subfolder,\n type,\n });\n\n const url = `${this.httpUrl}/view?${params}`;\n const response = await fetch(url, {\n headers: this.options.headers,\n signal: AbortSignal.timeout(this.options.timeout),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to download file: ${response.status}`);\n }\n\n const arrayBuffer = await response.arrayBuffer();\n return Buffer.from(arrayBuffer);\n }\n\n /**\n * Upload a file to ComfyUI\n *\n * @param file - File buffer to upload\n * @param filename - Name for the file\n * @param type - Where to upload ('input' or 'temp')\n * @param overwrite - Whether to overwrite existing file\n * @returns Promise resolving to upload result\n */\n async uploadFile(\n file: Buffer,\n filename: string,\n type: 'input' | 'temp' = 'input',\n overwrite = false,\n ): Promise<UploadResult> {\n const formData = new FormData();\n formData.append('image', new Blob([new Uint8Array(file)]), filename);\n formData.append('type', type);\n formData.append('overwrite', String(overwrite));\n\n const response = await this.request<{\n name: string;\n subfolder: string;\n type: string;\n }>('/upload/image', {\n method: 'POST',\n formData,\n });\n\n return {\n name: response.name,\n subfolder: response.subfolder,\n type: response.type as 'input' | 'temp' | 'output',\n };\n }\n\n /**\n * Get system statistics from ComfyUI\n *\n * @returns Promise resolving to system stats\n */\n async getSystemStats(): Promise<SystemStats> {\n return this.request<SystemStats>('/system_stats');\n }\n\n /**\n * Get the current queue status\n *\n * @returns Promise resolving to queue status\n */\n async getQueue(): Promise<QueueStatus> {\n const response = await this.request<{\n queue_running: any[];\n queue_pending: any[];\n }>('/queue');\n\n return {\n running: response.queue_running,\n pending: response.queue_pending,\n };\n }\n\n /**\n * Interrupt the current execution\n */\n async interrupt(): Promise<void> {\n await this.request('/interrupt', { method: 'POST' });\n }\n\n /**\n * Clear the execution queue\n */\n async clearQueue(): Promise<void> {\n await this.request('/queue', {\n method: 'POST',\n body: { clear: true },\n });\n }\n\n /**\n * Delete a specific item from the queue\n *\n * @param promptId - The prompt ID to delete\n */\n async deleteFromQueue(promptId: string): Promise<void> {\n await this.request('/queue', {\n method: 'POST',\n body: { delete: [promptId] },\n });\n }\n}\n\n/**\n * Helper function to inject parameters into a workflow\n *\n * @param workflow - The base workflow\n * @param mapping - Node mapping defining where to inject\n * @param values - Values to inject\n * @returns Modified workflow with injected values\n */\nexport function injectWorkflowParams(\n workflow: ComfyWorkflow,\n mapping: NodeMapping,\n values: Record<string, any>,\n): ComfyWorkflow {\n const result = JSON.parse(JSON.stringify(workflow));\n\n for (const [key, nodeId] of Object.entries(mapping)) {\n if (nodeId && values[key] !== undefined && result[nodeId]) {\n // Find the appropriate input field and update it\n const node = result[nodeId];\n\n // Common input field names for different node types\n const inputFieldMap: Record<string, string> = {\n seedImage: 'image',\n audioFile: 'audio',\n baseVideo: 'video',\n prompt: 'text',\n negativePrompt: 'text',\n };\n\n const inputField = inputFieldMap[key] ?? key;\n if (node.inputs && inputField in node.inputs) {\n node.inputs[inputField] = values[key];\n }\n }\n }\n\n return result;\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/client.ts"],"sourcesContent":["/**\n * ComfyUI Client\n *\n * WebSocket and REST client for interacting with ComfyUI servers.\n * Supports workflow execution, progress monitoring, and file management.\n */\n\nimport { createLogger, type Logger } from '@happyvertical/logger';\nimport { ValidationError } from '@happyvertical/utils';\n\nimport type {\n ComfyUIClientOptions,\n ComfyWorkflow,\n HistoryEntry,\n NodeMapping,\n ProgressEvent,\n PromptResult,\n QueueStatus,\n ResolvedClientOptions,\n SystemStats,\n UploadResult,\n WaitOptions,\n} from './types.js';\n\n/**\n * WebSocket message types from ComfyUI.\n *\n * `data` carries the union of fields the various message types\n * use, discriminated by `type` at runtime in `parseProgressEvent`.\n * Previously typed as `unknown`, which made every\n * `message.data?.<field>` access in that switch fail TS2339 — the\n * narrowing would have required parser-side type guards, much\n * heavier than just listing the actual fields here.\n */\ninterface WSMessage {\n type: string;\n data?: {\n // `node` carries a real `null` value from the ComfyUI protocol —\n // `parseProgressEvent` writes it through to `ProgressEvent.nodeId`,\n // and `waitForCompletion` uses `event.nodeId === null` as the\n // \"no more nodes to execute\" completion sentinel. The previous\n // `data?: unknown` type accepted null implicitly; this narrowed\n // type must keep it. (See `packages/comfyui/src/client.ts:436`\n // and the comment about completion above it.)\n node?: string | null;\n prompt_id?: string;\n output?: Record<string, unknown>;\n value?: number;\n max?: number;\n exception_message?: string;\n };\n}\n\n/**\n * ComfyUI client for workflow execution and management\n *\n * @example\n * ```typescript\n * import { ComfyUIClient } from '@happyvertical/comfyui';\n *\n * const client = new ComfyUIClient({\n * url: 'http://localhost:8188',\n * });\n *\n * await client.connect();\n *\n * const result = await client.queuePrompt(workflow);\n * await client.waitForCompletion(result.promptId, {\n * onProgress: (event) => console.log(`Progress: ${event.value}%`),\n * });\n *\n * const output = await client.downloadOutput('video.mp4');\n * await client.disconnect();\n * ```\n */\nexport class ComfyUIClient {\n private options: ResolvedClientOptions;\n private ws: WebSocket | null = null;\n private logger: Logger;\n private clientId: string;\n private messageHandlers: Map<string, Set<(event: ProgressEvent) => void>> =\n new Map();\n private reconnectAttempts = 0;\n private isConnecting = false;\n private username?: string;\n private password?: string;\n\n constructor(options: ComfyUIClientOptions = {}) {\n this.options = {\n url: options.url ?? 'http://localhost:8188',\n timeout: options.timeout ?? 600000,\n headers: options.headers ?? {},\n reconnect: {\n enabled: options.reconnect?.enabled ?? true,\n maxAttempts: options.reconnect?.maxAttempts ?? 5,\n delay: options.reconnect?.delay ?? 1000,\n },\n };\n\n if (options.username && options.password) {\n this.username = options.username;\n this.password = options.password;\n const credentials = btoa(`${options.username}:${options.password}`);\n this.options.headers.Authorization = `Basic ${credentials}`;\n }\n\n this.logger = createLogger({ level: 'info' });\n this.clientId = crypto.randomUUID();\n }\n\n /**\n * Get the HTTP base URL\n */\n private get httpUrl(): string {\n return this.options.url;\n }\n\n /**\n * Get the WebSocket URL\n */\n private get wsUrl(): string {\n const url = new URL(this.options.url);\n url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';\n url.pathname = '/ws';\n url.searchParams.set('clientId', this.clientId);\n if (this.username && this.password) {\n url.username = this.username;\n url.password = this.password;\n }\n return url.toString();\n }\n\n /**\n * Connect to the ComfyUI WebSocket\n */\n async connect(): Promise<void> {\n if (this.ws?.readyState === WebSocket.OPEN) {\n return;\n }\n\n if (this.isConnecting) {\n // Wait for existing connection attempt\n await new Promise<void>((resolve, reject) => {\n const checkConnection = setInterval(() => {\n if (this.ws?.readyState === WebSocket.OPEN) {\n clearInterval(checkConnection);\n resolve();\n } else if (!this.isConnecting) {\n clearInterval(checkConnection);\n reject(new Error('Connection failed'));\n }\n }, 100);\n });\n return;\n }\n\n this.isConnecting = true;\n\n try {\n await this.establishConnection();\n this.reconnectAttempts = 0;\n } finally {\n this.isConnecting = false;\n }\n }\n\n /**\n * Establish WebSocket connection\n */\n private async establishConnection(): Promise<void> {\n return new Promise((resolve, reject) => {\n try {\n this.ws = new WebSocket(this.wsUrl);\n\n const timeout = setTimeout(() => {\n if (this.ws?.readyState !== WebSocket.OPEN) {\n this.ws?.close();\n reject(new Error('WebSocket connection timeout'));\n }\n }, 10000);\n\n this.ws.onopen = () => {\n clearTimeout(timeout);\n this.logger.info('Connected to ComfyUI');\n resolve();\n };\n\n this.ws.onerror = (error) => {\n clearTimeout(timeout);\n this.logger.error('WebSocket error', { error });\n reject(new Error('WebSocket connection error'));\n };\n\n this.ws.onclose = () => {\n this.logger.info('Disconnected from ComfyUI');\n this.handleDisconnect();\n };\n\n this.ws.onmessage = (event) => {\n this.handleMessage(event.data);\n };\n } catch (error) {\n reject(error);\n }\n });\n }\n\n /**\n * Handle WebSocket disconnection\n */\n private async handleDisconnect(): Promise<void> {\n const reconnect = this.options.reconnect;\n if (!reconnect.enabled) return;\n\n if (this.reconnectAttempts < reconnect.maxAttempts) {\n this.reconnectAttempts++;\n this.logger.info(\n `Attempting reconnection ${this.reconnectAttempts}/${reconnect.maxAttempts}`,\n );\n\n await new Promise((resolve) => setTimeout(resolve, reconnect.delay));\n\n try {\n await this.connect();\n } catch (error) {\n this.logger.error('Reconnection failed', { error });\n }\n }\n }\n\n /**\n * Handle incoming WebSocket messages\n */\n private handleMessage(data: string): void {\n try {\n const message: WSMessage = JSON.parse(data);\n const event = this.parseProgressEvent(message);\n\n if (event?.promptId) {\n const handlers = this.messageHandlers.get(event.promptId);\n if (handlers) {\n for (const handler of handlers) {\n handler(event);\n }\n }\n }\n } catch (error) {\n this.logger.warn('Failed to parse WebSocket message', { error, data });\n }\n }\n\n /**\n * Parse a WebSocket message into a ProgressEvent\n */\n private parseProgressEvent(message: WSMessage): ProgressEvent | null {\n switch (message.type) {\n case 'executing':\n return {\n type: 'executing',\n nodeId: message.data?.node,\n promptId: message.data?.prompt_id,\n };\n\n case 'progress':\n return {\n type: 'progress',\n value: message.data?.value,\n max: message.data?.max,\n promptId: message.data?.prompt_id,\n };\n\n case 'execution_cached':\n return {\n type: 'execution_cached',\n promptId: message.data?.prompt_id,\n };\n\n case 'executed':\n return {\n type: 'executed',\n nodeId: message.data?.node,\n promptId: message.data?.prompt_id,\n output: message.data?.output,\n };\n\n case 'execution_error':\n return {\n type: 'execution_error',\n promptId: message.data?.prompt_id,\n error: message.data?.exception_message,\n };\n\n default:\n return null;\n }\n }\n\n /**\n * Disconnect from ComfyUI\n */\n async disconnect(): Promise<void> {\n this.options.reconnect.enabled = false; // Prevent auto-reconnect\n this.messageHandlers.clear();\n\n if (this.ws) {\n this.ws.close();\n this.ws = null;\n }\n\n this.logger.info('Disconnected from ComfyUI');\n }\n\n /**\n * Make an HTTP request to ComfyUI\n */\n private async request<T>(\n path: string,\n options: {\n method?: 'GET' | 'POST' | 'DELETE';\n body?: unknown;\n formData?: FormData;\n } = {},\n ): Promise<T> {\n const url = `${this.httpUrl}${path}`;\n\n const headers: Record<string, string> = {\n ...this.options.headers,\n };\n\n if (options.body && !options.formData) {\n headers['Content-Type'] = 'application/json';\n }\n\n const response = await fetch(url, {\n method: options.method ?? 'GET',\n headers,\n body:\n options.formData ??\n (options.body ? JSON.stringify(options.body) : undefined),\n signal: AbortSignal.timeout(this.options.timeout),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(\n `ComfyUI request failed: ${response.status} ${errorText}`,\n );\n }\n\n return response.json();\n }\n\n /**\n * Queue a workflow for execution\n *\n * @param workflow - The ComfyUI workflow to execute\n * @param clientId - Optional client ID (uses default if not provided)\n * @returns Promise resolving to prompt result with ID\n */\n async queuePrompt(\n workflow: ComfyWorkflow,\n clientId?: string,\n ): Promise<PromptResult> {\n const response = await this.request<{\n prompt_id: string;\n number: number;\n node_errors?: Record<string, string[]>;\n }>('/prompt', {\n method: 'POST',\n body: {\n prompt: workflow,\n client_id: clientId ?? this.clientId,\n },\n });\n\n return {\n promptId: response.prompt_id,\n number: response.number,\n nodeErrors: response.node_errors,\n };\n }\n\n /**\n * Subscribe to progress events for a prompt\n *\n * @param promptId - The prompt ID to monitor\n * @param handler - Callback for progress events\n * @returns Unsubscribe function\n */\n onProgress(\n promptId: string,\n handler: (event: ProgressEvent) => void,\n ): () => void {\n if (!this.messageHandlers.has(promptId)) {\n this.messageHandlers.set(promptId, new Set());\n }\n\n this.messageHandlers.get(promptId)?.add(handler);\n\n return () => {\n this.messageHandlers.get(promptId)?.delete(handler);\n if (this.messageHandlers.get(promptId)?.size === 0) {\n this.messageHandlers.delete(promptId);\n }\n };\n }\n\n /**\n * Wait for a prompt to complete\n *\n * @param promptId - The prompt ID to wait for\n * @param options - Wait options including progress callback\n * @returns Promise resolving to the history entry\n */\n async waitForCompletion(\n promptId: string,\n options: WaitOptions = {},\n ): Promise<HistoryEntry> {\n const timeout = options.timeout ?? this.options.timeout;\n const startTime = Date.now();\n\n return new Promise((resolve, reject) => {\n let resolved = false;\n\n // Set up timeout\n const timeoutId =\n timeout > 0\n ? setTimeout(() => {\n if (!resolved) {\n resolved = true;\n unsubscribe();\n reject(new Error(`Timeout waiting for prompt ${promptId}`));\n }\n }, timeout)\n : null;\n\n // Subscribe to progress events\n const unsubscribe = this.onProgress(promptId, async (event) => {\n // Forward progress events\n options.onProgress?.(event);\n\n // Check for completion or error\n if (event.type === 'executed' && event.nodeId === null) {\n // Workflow completed (null nodeId means no more nodes to execute)\n if (!resolved) {\n resolved = true;\n if (timeoutId) clearTimeout(timeoutId);\n unsubscribe();\n\n try {\n const history = await this.getHistory(promptId);\n resolve(history);\n } catch (error) {\n reject(error);\n }\n }\n } else if (event.type === 'execution_error') {\n if (!resolved) {\n resolved = true;\n if (timeoutId) clearTimeout(timeoutId);\n unsubscribe();\n reject(new Error(`Workflow execution error: ${event.error}`));\n }\n }\n });\n\n // Fallback: poll history if WebSocket doesn't give completion\n const pollHistory = async () => {\n while (\n !resolved &&\n (timeout === 0 || Date.now() - startTime < timeout)\n ) {\n await new Promise((r) => setTimeout(r, options.pollInterval ?? 1000));\n\n if (resolved) break;\n\n try {\n const history = await this.getHistory(promptId);\n if (history.status.completed) {\n if (!resolved) {\n resolved = true;\n if (timeoutId) clearTimeout(timeoutId);\n unsubscribe();\n resolve(history);\n }\n break;\n }\n } catch {\n // History not available yet, continue polling\n }\n }\n };\n\n // Start polling as fallback\n pollHistory().catch(() => {});\n });\n }\n\n /**\n * Get history for a specific prompt\n *\n * @param promptId - The prompt ID to get history for\n * @returns Promise resolving to the history entry\n */\n async getHistory(promptId: string): Promise<HistoryEntry> {\n const response = await this.request<Record<string, any>>(\n `/history/${promptId}`,\n );\n\n const entry = response[promptId];\n if (!entry) {\n throw new ValidationError(`History not found for prompt ${promptId}`);\n }\n\n return {\n promptId,\n status: entry.status,\n outputs: entry.outputs,\n prompt: entry.prompt,\n };\n }\n\n /**\n * Download an output file from ComfyUI\n *\n * @param filename - Name of the file to download\n * @param subfolder - Subfolder where file is located\n * @param type - Type of file location ('output', 'temp', or 'input')\n * @returns Promise resolving to the file buffer\n */\n async downloadOutput(\n filename: string,\n subfolder: string = '',\n type: 'output' | 'temp' | 'input' = 'output',\n ): Promise<Buffer> {\n const params = new URLSearchParams({\n filename,\n subfolder,\n type,\n });\n\n const url = `${this.httpUrl}/view?${params}`;\n const response = await fetch(url, {\n headers: this.options.headers,\n signal: AbortSignal.timeout(this.options.timeout),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to download file: ${response.status}`);\n }\n\n const arrayBuffer = await response.arrayBuffer();\n return Buffer.from(arrayBuffer);\n }\n\n /**\n * Upload a file to ComfyUI\n *\n * @param file - File buffer to upload\n * @param filename - Name for the file\n * @param type - Where to upload ('input' or 'temp')\n * @param overwrite - Whether to overwrite existing file\n * @returns Promise resolving to upload result\n */\n async uploadFile(\n file: Buffer,\n filename: string,\n type: 'input' | 'temp' = 'input',\n overwrite = false,\n ): Promise<UploadResult> {\n const formData = new FormData();\n formData.append('image', new Blob([new Uint8Array(file)]), filename);\n formData.append('type', type);\n formData.append('overwrite', String(overwrite));\n\n const response = await this.request<{\n name: string;\n subfolder: string;\n type: string;\n }>('/upload/image', {\n method: 'POST',\n formData,\n });\n\n return {\n name: response.name,\n subfolder: response.subfolder,\n type: response.type as 'input' | 'temp' | 'output',\n };\n }\n\n /**\n * Get system statistics from ComfyUI\n *\n * @returns Promise resolving to system stats\n */\n async getSystemStats(): Promise<SystemStats> {\n return this.request<SystemStats>('/system_stats');\n }\n\n /**\n * Get the current queue status\n *\n * @returns Promise resolving to queue status\n */\n async getQueue(): Promise<QueueStatus> {\n const response = await this.request<{\n queue_running: any[];\n queue_pending: any[];\n }>('/queue');\n\n return {\n running: response.queue_running,\n pending: response.queue_pending,\n };\n }\n\n /**\n * Interrupt the current execution\n */\n async interrupt(): Promise<void> {\n await this.request('/interrupt', { method: 'POST' });\n }\n\n /**\n * Clear the execution queue\n */\n async clearQueue(): Promise<void> {\n await this.request('/queue', {\n method: 'POST',\n body: { clear: true },\n });\n }\n\n /**\n * Delete a specific item from the queue\n *\n * @param promptId - The prompt ID to delete\n */\n async deleteFromQueue(promptId: string): Promise<void> {\n await this.request('/queue', {\n method: 'POST',\n body: { delete: [promptId] },\n });\n }\n}\n\n/**\n * Helper function to inject parameters into a workflow\n *\n * @param workflow - The base workflow\n * @param mapping - Node mapping defining where to inject\n * @param values - Values to inject\n * @returns Modified workflow with injected values\n */\nexport function injectWorkflowParams(\n workflow: ComfyWorkflow,\n mapping: NodeMapping,\n values: Record<string, any>,\n): ComfyWorkflow {\n const result = JSON.parse(JSON.stringify(workflow));\n\n for (const [key, nodeId] of Object.entries(mapping)) {\n if (nodeId && values[key] !== undefined && result[nodeId]) {\n // Find the appropriate input field and update it\n const node = result[nodeId];\n\n // Common input field names for different node types\n const inputFieldMap: Record<string, string> = {\n seedImage: 'image',\n audioFile: 'audio',\n baseVideo: 'video',\n prompt: 'text',\n negativePrompt: 'text',\n };\n\n const inputField = inputFieldMap[key] ?? key;\n if (node.inputs && inputField in node.inputs) {\n node.inputs[inputField] = values[key];\n }\n }\n }\n\n return result;\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happyvertical/comfyui",
3
- "version": "0.80.0",
3
+ "version": "0.80.1",
4
4
  "description": "ComfyUI API client for workflow orchestration and video generation",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -12,15 +12,15 @@
12
12
  }
13
13
  },
14
14
  "dependencies": {
15
- "@happyvertical/logger": "0.80.0",
16
- "@happyvertical/utils": "0.80.0"
15
+ "@happyvertical/logger": "0.80.1",
16
+ "@happyvertical/utils": "0.80.1"
17
17
  },
18
18
  "devDependencies": {
19
19
  "@types/node": "25.0.10",
20
- "typescript": "^5.9.3",
21
- "vite": "7.3.2",
20
+ "typescript": "5.9.3",
21
+ "vite": "8.1.4",
22
22
  "vite-plugin-dts": "4.5.4",
23
- "vitest": "^4.1.5"
23
+ "vitest": "4.1.10"
24
24
  },
25
25
  "files": [
26
26
  "dist",