comfyui-node 1.0.0
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/LICENSE +21 -0
- package/README.md +601 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/call-wrapper.d.ts +115 -0
- package/dist/call-wrapper.d.ts.map +1 -0
- package/dist/call-wrapper.js +432 -0
- package/dist/call-wrapper.js.map +1 -0
- package/dist/client.d.ts +233 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +719 -0
- package/dist/client.js.map +1 -0
- package/dist/constants.d.ts +4 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +4 -0
- package/dist/constants.js.map +1 -0
- package/dist/features/abstract.d.ts +15 -0
- package/dist/features/abstract.d.ts.map +1 -0
- package/dist/features/abstract.js +19 -0
- package/dist/features/abstract.js.map +1 -0
- package/dist/features/base.d.ts +9 -0
- package/dist/features/base.d.ts.map +1 -0
- package/dist/features/base.js +15 -0
- package/dist/features/base.js.map +1 -0
- package/dist/features/feature-flags.d.ts +14 -0
- package/dist/features/feature-flags.d.ts.map +1 -0
- package/dist/features/feature-flags.js +27 -0
- package/dist/features/feature-flags.js.map +1 -0
- package/dist/features/file.d.ts +86 -0
- package/dist/features/file.d.ts.map +1 -0
- package/dist/features/file.js +160 -0
- package/dist/features/file.js.map +1 -0
- package/dist/features/history.d.ts +16 -0
- package/dist/features/history.d.ts.map +1 -0
- package/dist/features/history.js +23 -0
- package/dist/features/history.js.map +1 -0
- package/dist/features/index.d.ts +14 -0
- package/dist/features/index.d.ts.map +1 -0
- package/dist/features/index.js +14 -0
- package/dist/features/index.js.map +1 -0
- package/dist/features/manager.d.ts +154 -0
- package/dist/features/manager.d.ts.map +1 -0
- package/dist/features/manager.js +309 -0
- package/dist/features/manager.js.map +1 -0
- package/dist/features/misc.d.ts +17 -0
- package/dist/features/misc.d.ts.map +1 -0
- package/dist/features/misc.js +52 -0
- package/dist/features/misc.js.map +1 -0
- package/dist/features/model.d.ts +39 -0
- package/dist/features/model.d.ts.map +1 -0
- package/dist/features/model.js +79 -0
- package/dist/features/model.js.map +1 -0
- package/dist/features/monitoring.d.ts +90 -0
- package/dist/features/monitoring.d.ts.map +1 -0
- package/dist/features/monitoring.js +129 -0
- package/dist/features/monitoring.js.map +1 -0
- package/dist/features/node.d.ts +42 -0
- package/dist/features/node.d.ts.map +1 -0
- package/dist/features/node.js +68 -0
- package/dist/features/node.js.map +1 -0
- package/dist/features/queue.d.ts +23 -0
- package/dist/features/queue.d.ts.map +1 -0
- package/dist/features/queue.js +68 -0
- package/dist/features/queue.js.map +1 -0
- package/dist/features/system.d.ts +21 -0
- package/dist/features/system.d.ts.map +1 -0
- package/dist/features/system.js +45 -0
- package/dist/features/system.js.map +1 -0
- package/dist/features/terminal.d.ts +25 -0
- package/dist/features/terminal.d.ts.map +1 -0
- package/dist/features/terminal.js +32 -0
- package/dist/features/terminal.js.map +1 -0
- package/dist/features/user.d.ts +42 -0
- package/dist/features/user.d.ts.map +1 -0
- package/dist/features/user.js +76 -0
- package/dist/features/user.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/pool.d.ts +171 -0
- package/dist/pool.d.ts.map +1 -0
- package/dist/pool.js +467 -0
- package/dist/pool.js.map +1 -0
- package/dist/prompt-builder.d.ts +131 -0
- package/dist/prompt-builder.d.ts.map +1 -0
- package/dist/prompt-builder.js +266 -0
- package/dist/prompt-builder.js.map +1 -0
- package/dist/tools.d.ts +10 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +16 -0
- package/dist/tools.js.map +1 -0
- package/dist/typed-event-target.d.ts +7 -0
- package/dist/typed-event-target.d.ts.map +1 -0
- package/dist/typed-event-target.js +19 -0
- package/dist/typed-event-target.js.map +1 -0
- package/dist/types/api.d.ts +212 -0
- package/dist/types/api.d.ts.map +1 -0
- package/dist/types/api.js +16 -0
- package/dist/types/api.js.map +1 -0
- package/dist/types/error.d.ts +72 -0
- package/dist/types/error.d.ts.map +1 -0
- package/dist/types/error.js +75 -0
- package/dist/types/error.js.map +1 -0
- package/dist/types/event.d.ts +164 -0
- package/dist/types/event.d.ts.map +1 -0
- package/dist/types/event.js +2 -0
- package/dist/types/event.js.map +1 -0
- package/dist/types/manager.d.ts +157 -0
- package/dist/types/manager.d.ts.map +1 -0
- package/dist/types/manager.js +41 -0
- package/dist/types/manager.js.map +1 -0
- package/dist/types/sampler.d.ts +3 -0
- package/dist/types/sampler.d.ts.map +1 -0
- package/dist/types/sampler.js +2 -0
- package/dist/types/sampler.js.map +1 -0
- package/dist/types/tool.d.ts +10 -0
- package/dist/types/tool.d.ts.map +1 -0
- package/dist/types/tool.js +2 -0
- package/dist/types/tool.js.map +1 -0
- package/dist/utils/response-error.d.ts +4 -0
- package/dist/utils/response-error.d.ts.map +1 -0
- package/dist/utils/response-error.js +62 -0
- package/dist/utils/response-error.js.map +1 -0
- package/dist/utils/ws-reconnect.d.ts +29 -0
- package/dist/utils/ws-reconnect.d.ts.map +1 -0
- package/dist/utils/ws-reconnect.js +91 -0
- package/dist/utils/ws-reconnect.js.map +1 -0
- package/package.json +71 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Igor Lins e Silva
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,601 @@
|
|
|
1
|
+
# ComfyUI SDK
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/comfyui-node)
|
|
4
|
+
[](https://github.com/igorls/comfyui-node/blob/main/LICENSE)
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
TypeScript SDK for interacting with the [ComfyUI](https://github.com/comfyanonymous/ComfyUI) API – focused on workflow construction, prompt execution orchestration, multi‑instance scheduling and extension integration.
|
|
10
|
+
|
|
11
|
+
> 1.0 is a complete redesign around modular feature namespaces (`api.ext.*`) and stronger typing. All legacy instance methods have been removed – see Migration section if upgrading.
|
|
12
|
+
|
|
13
|
+
## Contents
|
|
14
|
+
|
|
15
|
+
- [Features](#features)
|
|
16
|
+
- [Installation](#installation)
|
|
17
|
+
- [Quick Start](#quick-start)
|
|
18
|
+
- [Multi-Instance Pool](#multi-instance-pool)
|
|
19
|
+
- [Authentication](#authentication)
|
|
20
|
+
- [Custom WebSocket](#custom-websocket)
|
|
21
|
+
- [Modular Features (`api.ext`)](#modular-features-apiext)
|
|
22
|
+
- [Events](#events)
|
|
23
|
+
- [1.0 Migration](#10-migration)
|
|
24
|
+
- [Reference Overview](#reference-overview)
|
|
25
|
+
- [Examples](#examples)
|
|
26
|
+
- [Errors & Diagnostics](#errors--diagnostics)
|
|
27
|
+
- [Contributing](#contributing)
|
|
28
|
+
- [License](#license)
|
|
29
|
+
|
|
30
|
+
## Features
|
|
31
|
+
|
|
32
|
+
- Fully typed TypeScript surface
|
|
33
|
+
- Fluent `PromptBuilder` for graph mutation & input/output mapping
|
|
34
|
+
- Fluent `PromptBuilder` for graph mutation & input/output mapping (with validation & JSON (de)serialization helpers)
|
|
35
|
+
- WebSocket events (progress, preview, output, completion) with reconnection & HTTP polling fallback
|
|
36
|
+
- Weighted multi‑instance job distribution (`ComfyPool`)
|
|
37
|
+
- Extension integration (Manager, Crystools monitor, feature flags)
|
|
38
|
+
- Modular feature namespaces (`api.ext.queue`, `api.ext.node`, etc.)
|
|
39
|
+
- Upload helpers (images, masks) & user data file operations
|
|
40
|
+
- Authentication strategies (basic, bearer token, custom headers)
|
|
41
|
+
- Structured errors & narrow fetch helper
|
|
42
|
+
- Validation utilities for prompt graphs (missing mappings, immediate cycles)
|
|
43
|
+
- JSON round‑trip support for builder state persistence
|
|
44
|
+
|
|
45
|
+
## Installation
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
bun add comfyui-node
|
|
49
|
+
# or
|
|
50
|
+
npm install comfyui-node
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Requires Node.js 22+ (Active / Current). Earlier Node versions are not supported in 1.0+.
|
|
54
|
+
|
|
55
|
+
## Quick Start
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
import { ComfyApi, CallWrapper, PromptBuilder, seed, TSamplerName, TSchedulerName } from "comfyui-node";
|
|
59
|
+
import ExampleTxt2ImgWorkflow from "./example-txt2img-workflow.json";
|
|
60
|
+
|
|
61
|
+
const api = new ComfyApi("http://localhost:8189").init();
|
|
62
|
+
const workflow = new PromptBuilder(
|
|
63
|
+
ExampleTxt2ImgWorkflow,
|
|
64
|
+
["positive","negative","checkpoint","seed","batch","step","cfg","sampler","sheduler","width","height"],
|
|
65
|
+
["images"]
|
|
66
|
+
)
|
|
67
|
+
.setInputNode("checkpoint","4.inputs.ckpt_name")
|
|
68
|
+
.setInputNode("seed","3.inputs.seed")
|
|
69
|
+
.setInputNode("batch","5.inputs.batch_size")
|
|
70
|
+
.setInputNode("negative","7.inputs.text")
|
|
71
|
+
.setInputNode("positive","6.inputs.text")
|
|
72
|
+
.setInputNode("cfg","3.inputs.cfg")
|
|
73
|
+
.setInputNode("sampler","3.inputs.sampler_name")
|
|
74
|
+
.setInputNode("sheduler","3.inputs.scheduler")
|
|
75
|
+
.setInputNode("step","3.inputs.steps")
|
|
76
|
+
.setInputNode("width","5.inputs.width")
|
|
77
|
+
.setInputNode("height","5.inputs.height")
|
|
78
|
+
.setOutputNode("images","9")
|
|
79
|
+
.input("checkpoint","SDXL/realvisxlV40_v40LightningBakedvae.safetensors", api.osType)
|
|
80
|
+
.input("seed", seed())
|
|
81
|
+
.input("step", 6)
|
|
82
|
+
.input("cfg", 1)
|
|
83
|
+
.input<TSamplerName>("sampler","dpmpp_2m_sde_gpu")
|
|
84
|
+
.input<TSchedulerName>("sheduler","sgm_uniform")
|
|
85
|
+
.input("width",1024)
|
|
86
|
+
.input("height",1024)
|
|
87
|
+
.input("batch",1)
|
|
88
|
+
.input("positive","A picture of a dog on the street");
|
|
89
|
+
|
|
90
|
+
new CallWrapper(api, workflow)
|
|
91
|
+
.onFinished(d => console.log(d.images?.images.map((img: any) => api.ext.file.getPathImage(img))))
|
|
92
|
+
.run();
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### PromptBuilder Validation & Serialization
|
|
96
|
+
|
|
97
|
+
`PromptBuilder` now includes optional robustness helpers you can invoke before submission:
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
builder
|
|
101
|
+
.validateOutputMappings() // Ensures every declared output key maps to an existing node id
|
|
102
|
+
.validateNoImmediateCycles(); // Guards against a node directly referencing itself in its input tuple
|
|
103
|
+
|
|
104
|
+
// Serialize to persist / send over IPC
|
|
105
|
+
const saved = builder.toJSON();
|
|
106
|
+
// Later restore (types must line up with original generic parameters when casting)
|
|
107
|
+
const restored = PromptBuilder.fromJSON(saved);
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
If validation fails an `Error` is thrown with a concise list of offending mappings, e.g.
|
|
111
|
+
|
|
112
|
+
```text
|
|
113
|
+
Error: Unmapped or missing output nodes: images:UNMAPPED
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Cycle detection currently targets immediate self‑cycles (a node whose input tuple references itself). Broader multi‑hop cycle detection can be layered later without breaking this API.
|
|
117
|
+
|
|
118
|
+
### Common Validation Patterns
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
function safeBuild(wf: any) {
|
|
122
|
+
return new PromptBuilder(wf,["positive","seed"],["images"])
|
|
123
|
+
.setInputNode("positive","6.inputs.text")
|
|
124
|
+
.setInputNode("seed","3.inputs.seed")
|
|
125
|
+
.setOutputNode("images","9")
|
|
126
|
+
.input("positive","Hello world")
|
|
127
|
+
.input("seed", seed())
|
|
128
|
+
.validateOutputMappings()
|
|
129
|
+
.validateNoImmediateCycles();
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Multi-Instance Pool
|
|
134
|
+
|
|
135
|
+
`ComfyPool` provides weighted job scheduling & automatic client selection across multiple ComfyUI instances. It is transport‑agnostic and only relies on the standard `ComfyApi` event surface.
|
|
136
|
+
|
|
137
|
+
### Modes
|
|
138
|
+
|
|
139
|
+
| Mode | Enum | Behavior | When to use |
|
|
140
|
+
| ---- | ---- | -------- | ----------- |
|
|
141
|
+
| Pick zero queue | `EQueueMode.PICK_ZERO` (default) | Choose any online client whose reported `queue_remaining` is 0 (prefers idle machines). Locks a client until it emits an execution event. | Co‑existence with the ComfyUI web UI where queue spikes are common. |
|
|
142
|
+
| Lowest queue | `EQueueMode.PICK_LOWEST` | Choose the online client with the smallest `queue_remaining` (may still be busy). | High throughput batch ingestion; keeps all nodes saturated. |
|
|
143
|
+
| Round‑robin | `EQueueMode.PICK_ROUTINE` | Simple rotation through available online clients irrespective of queue depth. | Latency balancing; predictable distribution. |
|
|
144
|
+
|
|
145
|
+
### Basic Example
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
import { ComfyApi, ComfyPool, EQueueMode, CallWrapper, PromptBuilder, seed } from "comfyui-node";
|
|
149
|
+
import ExampleTxt2ImgWorkflow from "./example-txt2img-workflow.json";
|
|
150
|
+
|
|
151
|
+
// Create two API clients (auth / headers etc still work as normal)
|
|
152
|
+
const pool = new ComfyPool([
|
|
153
|
+
new ComfyApi("http://localhost:8188"),
|
|
154
|
+
new ComfyApi("http://localhost:8189")
|
|
155
|
+
]); // defaults to PICK_ZERO
|
|
156
|
+
|
|
157
|
+
// A single generation task (returns file paths for convenience)
|
|
158
|
+
const generate = async (api: ComfyApi) => {
|
|
159
|
+
const wf = new PromptBuilder(ExampleTxt2ImgWorkflow,["positive","checkpoint","seed"],["images"])
|
|
160
|
+
.setInputNode("checkpoint","4.inputs.ckpt_name")
|
|
161
|
+
.setInputNode("seed","3.inputs.seed")
|
|
162
|
+
.setInputNode("positive","6.inputs.text")
|
|
163
|
+
.setOutputNode("images","9")
|
|
164
|
+
.input("checkpoint","SDXL/realvisxlV40_v40LightningBakedvae.safetensors", api.osType)
|
|
165
|
+
.input("seed", seed())
|
|
166
|
+
.input("positive","A close up picture of a cat");
|
|
167
|
+
return await new Promise<string[]>(resolve => {
|
|
168
|
+
new CallWrapper(api, wf)
|
|
169
|
+
.onFinished(data => resolve(data.images?.images.map((img: any) => api.ext.file.getPathImage(img)) || []))
|
|
170
|
+
.run();
|
|
171
|
+
});
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
// Dispatch 3 parallel generations across the pool
|
|
175
|
+
const results = await pool.batch(Array(3).fill(generate));
|
|
176
|
+
console.log(results.flat());
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Job Weighting
|
|
180
|
+
|
|
181
|
+
Jobs are inserted into an internal priority queue ordered by ascending weight. Lower weight runs earlier. By default the weight is set to the queue length at insertion (FIFO). You can override:
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
await Promise.all([
|
|
185
|
+
pool.run(doSomethingHeavy, 10), // runs later
|
|
186
|
+
pool.run(doSomethingQuick, 1), // runs first
|
|
187
|
+
pool.run(anotherTask, 5)
|
|
188
|
+
]);
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Include / Exclude Filters
|
|
192
|
+
|
|
193
|
+
Target or avoid specific client IDs:
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
await pool.run(taskA, undefined, { includeIds: ["gpu-a"] }); // only gpu-a
|
|
197
|
+
await pool.run(taskB, undefined, { excludeIds: ["gpu-b"] }); // any except gpu-b
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Failover & Retries
|
|
201
|
+
|
|
202
|
+
`run()` attempts transparent failover when a job throws. It excludes the failing client and retries another (up to `maxRetries`).
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
await pool.run(doGenerate, undefined, undefined, { maxRetries: 3, retryDelay: 1500 });
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Disable failover:
|
|
209
|
+
|
|
210
|
+
```ts
|
|
211
|
+
await pool.run(doGenerate, undefined, undefined, { enableFailover: false });
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Pool Events
|
|
215
|
+
|
|
216
|
+
`ComfyPool` is an `EventTarget` emitting high‑level orchestration signals:
|
|
217
|
+
|
|
218
|
+
| Event | Detail Payload | When |
|
|
219
|
+
| ----- | -------------- | ---- |
|
|
220
|
+
| `init` | – | All clients added & initial processing pass done |
|
|
221
|
+
| `added` / `removed` | `{ client, clientIdx }` | Client lifecycle changes |
|
|
222
|
+
| `ready` | `{ client, clientIdx }` | Individual client fully initialized |
|
|
223
|
+
| `executing` / `executed` | `{ client, clientIdx }` | A job starts / finishes on a client |
|
|
224
|
+
| `execution_error` | `{ client, clientIdx, error, willRetry, attempt, maxRetries }` | A job threw; may retry |
|
|
225
|
+
| `execution_interrupted` | `{ client, clientIdx }` | Underlying API emitted interruption |
|
|
226
|
+
| `connected` / `disconnected` / `reconnected` | `{ client, clientIdx }` | WebSocket state relayed from `ComfyApi` |
|
|
227
|
+
| `terminal` | `{ clientIdx, line }` | Terminal log pass‑through |
|
|
228
|
+
| `system_monitor` | `{ clientIdx, data }` | Crystools monitor snapshot (when supported) |
|
|
229
|
+
| `add_job` | `{ jobIdx, weight }` | Job inserted into internal queue |
|
|
230
|
+
| `change_mode` | `{ mode }` | Queue selection mode altered |
|
|
231
|
+
| `have_job` | `{ client, remain }` | A client reports pending queue > 0 |
|
|
232
|
+
| `idle` | `{ client }` | A previously busy client reports queue 0 |
|
|
233
|
+
|
|
234
|
+
### Cleaning Up
|
|
235
|
+
|
|
236
|
+
Always invoke `destroy()` when finished to clear intervals, event listeners & underlying client connections:
|
|
237
|
+
|
|
238
|
+
```ts
|
|
239
|
+
pool.destroy();
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Combined Orchestration Example (Auth + Pool + Validation + Retry)
|
|
243
|
+
|
|
244
|
+
```ts
|
|
245
|
+
import { ComfyApi, ComfyPool, EQueueMode, PromptBuilder, CallWrapper, seed } from "comfyui-node";
|
|
246
|
+
|
|
247
|
+
const pool = new ComfyPool([
|
|
248
|
+
new ComfyApi(process.env.C1!,"c1", { credentials: { type: "bearer_token", token: process.env.C1_TOKEN! } }),
|
|
249
|
+
new ComfyApi(process.env.C2!,"c2")
|
|
250
|
+
], { mode: EQueueMode.PICK_LOWEST });
|
|
251
|
+
|
|
252
|
+
async function generate(api: ComfyApi, text: string) {
|
|
253
|
+
const wf = /* load / clone a base workflow JSON */ {} as any;
|
|
254
|
+
const builder = new PromptBuilder(wf,["positive","seed"],["images"])
|
|
255
|
+
.setInputNode("positive","6.inputs.text")
|
|
256
|
+
.setInputNode("seed","3.inputs.seed")
|
|
257
|
+
.setOutputNode("images","9")
|
|
258
|
+
.input("positive", text)
|
|
259
|
+
.input("seed", seed())
|
|
260
|
+
.validateOutputMappings();
|
|
261
|
+
|
|
262
|
+
return await new Promise<string[]>((resolve, reject) => {
|
|
263
|
+
new CallWrapper(api, builder)
|
|
264
|
+
.onFinished(d => resolve((d.images?.images||[]).map((img:any)=> api.ext.file.getPathImage(img))))
|
|
265
|
+
.onFailed(err => reject(err))
|
|
266
|
+
.run();
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Weighted submission with retry semantics
|
|
271
|
+
const tasks = ["cat portrait","cyberpunk city","forest at dawn"].map(txt => (api: ComfyApi) => generate(api, txt));
|
|
272
|
+
const results = await Promise.all(tasks.map((fn,i)=> pool.run(fn, i))); // lower weight = earlier
|
|
273
|
+
console.log(results.flat());
|
|
274
|
+
pool.destroy();
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Choosing a Mode
|
|
278
|
+
|
|
279
|
+
| Goal | Suggested Mode |
|
|
280
|
+
| ---- | -------------- |
|
|
281
|
+
| Minimize latency spikes | `PICK_ZERO` |
|
|
282
|
+
| Maximize throughput | `PICK_LOWEST` |
|
|
283
|
+
| Deterministic striping | `PICK_ROUTINE` |
|
|
284
|
+
|
|
285
|
+
You can change dynamically:
|
|
286
|
+
|
|
287
|
+
```ts
|
|
288
|
+
pool.changeMode(EQueueMode.PICK_LOWEST);
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Observability Tips
|
|
292
|
+
|
|
293
|
+
Listen for `execution_error` with `willRetry=true` to surface transient node failures; attach Prometheus / metrics counters externally from these events if desired.
|
|
294
|
+
|
|
295
|
+
### Relation to `CallWrapper`
|
|
296
|
+
|
|
297
|
+
`ComfyPool` does not abstract prompt construction or execution detail; each job decides how to use `CallWrapper`, direct `api.ext.queue.*` calls or even file operations before enqueueing.
|
|
298
|
+
|
|
299
|
+
### Future Ideas (Contributions Welcome)
|
|
300
|
+
|
|
301
|
+
- Global circuit breaker (temporarily exclude flapping client)
|
|
302
|
+
- Adaptive weight assignment based on rolling execution duration
|
|
303
|
+
- Pluggable selection strategies via user callback
|
|
304
|
+
|
|
305
|
+
If you build one, open a PR – keep the core minimal & dependency‑free.
|
|
306
|
+
|
|
307
|
+
## Authentication
|
|
308
|
+
|
|
309
|
+
```ts
|
|
310
|
+
import { ComfyApi, BasicCredentials, BearerTokenCredentials, CustomCredentials } from "comfyui-node";
|
|
311
|
+
|
|
312
|
+
const basic = new ComfyApi("http://localhost:8189","id1", { credentials: { type: "basic", username: "u", password: "p" } as BasicCredentials }).init();
|
|
313
|
+
const bearer = new ComfyApi("http://localhost:8189","id2", { credentials: { type: "bearer_token", token: "token" } as BearerTokenCredentials }).init();
|
|
314
|
+
const custom = new ComfyApi("http://localhost:8189","id3", { credentials: { type: "custom", headers: { "X-Api-Key": "abc" } } as CustomCredentials }).init();
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
## Custom WebSocket
|
|
318
|
+
|
|
319
|
+
```ts
|
|
320
|
+
import { ComfyApi, WebSocketInterface } from "comfyui-node";
|
|
321
|
+
import CustomWebSocket from "your-custom-ws";
|
|
322
|
+
|
|
323
|
+
const api = new ComfyApi("http://localhost:8189", "node-id", { customWebSocketImpl: CustomWebSocket as WebSocketInterface }).init();
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
## Modular Features (`api.ext`)
|
|
327
|
+
|
|
328
|
+
```ts
|
|
329
|
+
await api.waitForReady();
|
|
330
|
+
await api.ext.queue.queuePrompt(null, workflow);
|
|
331
|
+
const stats = await api.ext.system.getSystemStats();
|
|
332
|
+
const checkpoints = await api.ext.node.getCheckpoints();
|
|
333
|
+
const embeddings = await api.ext.misc.getEmbeddings();
|
|
334
|
+
const flags = await api.ext.featureFlags.getServerFeatures();
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
| Namespace | Responsibility |
|
|
338
|
+
| --------- | -------------- |
|
|
339
|
+
| `queue` | Prompt submission, append & interrupt |
|
|
340
|
+
| `history` | Execution history retrieval |
|
|
341
|
+
| `system` | System stats & memory free |
|
|
342
|
+
| `node` | Node defs + sampler / checkpoint / lora helpers |
|
|
343
|
+
| `user` | User & settings CRUD |
|
|
344
|
+
| `file` | Uploads, image helpers, user data file ops |
|
|
345
|
+
| `model` | Experimental model browsing & previews |
|
|
346
|
+
| `terminal` | Terminal logs & subscription toggle |
|
|
347
|
+
| `misc` | Extensions list, embeddings (new + fallback) |
|
|
348
|
+
| `manager` | ComfyUI Manager extension integration |
|
|
349
|
+
| `monitor` | Crystools monitor events & snapshot |
|
|
350
|
+
| `featureFlags` | Server capabilities (`/features`) |
|
|
351
|
+
|
|
352
|
+
## Events
|
|
353
|
+
|
|
354
|
+
Both `ComfyApi` and `ComfyPool` expose strongly typed event maps. Import the key unions or event maps for generic helpers:
|
|
355
|
+
|
|
356
|
+
```ts
|
|
357
|
+
import { ComfyApi, ComfyApiEventKey, TComfyAPIEventMap } from 'comfyui-node';
|
|
358
|
+
|
|
359
|
+
const api = new ComfyApi('http://localhost:8188');
|
|
360
|
+
api.on('progress', (ev) => {
|
|
361
|
+
console.log(ev.detail.value, '/', ev.detail.max);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
function handleApiEvent<K extends ComfyApiEventKey>(k: K, e: TComfyAPIEventMap[K]) {
|
|
365
|
+
if (k === 'executed') {
|
|
366
|
+
console.log('Node executed:', e.detail.node);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
Pool usage:
|
|
372
|
+
|
|
373
|
+
```ts
|
|
374
|
+
import { ComfyPool, ComfyPoolEventKey } from 'comfyui-node';
|
|
375
|
+
|
|
376
|
+
pool.on('execution_error', (ev) => {
|
|
377
|
+
if (ev.detail.willRetry) console.warn('Transient failure, retrying...');
|
|
378
|
+
});
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
---
|
|
382
|
+
|
|
383
|
+
## 1.0 Migration
|
|
384
|
+
|
|
385
|
+
All legacy `ComfyApi` instance methods listed below were **removed in 1.0.0** after a deprecation window in 0.2.x. Migrate to the `api.ext.*` namespaces. If you're upgrading from <1.0, replace calls as shown. No runtime warnings remain (they were stripped with the removals).
|
|
386
|
+
|
|
387
|
+
| Deprecated | Replacement |
|
|
388
|
+
| ---------- | ----------- |
|
|
389
|
+
| `queuePrompt(...)` | `api.ext.queue.queuePrompt(...)` |
|
|
390
|
+
| `appendPrompt(...)` | `api.ext.queue.appendPrompt(...)` |
|
|
391
|
+
| `getHistories(...)` | `api.ext.history.getHistories(...)` |
|
|
392
|
+
| `getHistory(id)` | `api.ext.history.getHistory(id)` |
|
|
393
|
+
| `getSystemStats()` | `api.ext.system.getSystemStats()` |
|
|
394
|
+
| `getCheckpoints()` | `api.ext.node.getCheckpoints()` |
|
|
395
|
+
| `getLoras()` | `api.ext.node.getLoras()` |
|
|
396
|
+
| `getSamplerInfo()` | `api.ext.node.getSamplerInfo()` |
|
|
397
|
+
| `getNodeDefs(name?)` | `api.ext.node.getNodeDefs(name?)` |
|
|
398
|
+
| `getExtensions()` | `api.ext.misc.getExtensions()` |
|
|
399
|
+
| `getEmbeddings()` | `api.ext.misc.getEmbeddings()` |
|
|
400
|
+
| `uploadImage(...)` | `api.ext.file.uploadImage(...)` |
|
|
401
|
+
| `uploadMask(...)` | `api.ext.file.uploadMask(...)` |
|
|
402
|
+
| `getPathImage(info)` | `api.ext.file.getPathImage(info)` |
|
|
403
|
+
| `getImage(info)` | `api.ext.file.getImage(info)` |
|
|
404
|
+
| `getUserData(file)` | `api.ext.file.getUserData(file)` |
|
|
405
|
+
| `storeUserData(...)` | `api.ext.file.storeUserData(...)` |
|
|
406
|
+
| `deleteUserData(file)` | `api.ext.file.deleteUserData(file)` |
|
|
407
|
+
| `moveUserData(...)` | `api.ext.file.moveUserData(...)` |
|
|
408
|
+
| `listUserData(...)` | `api.ext.file.listUserData(...)` |
|
|
409
|
+
| `getUserConfig()` | `api.ext.user.getUserConfig()` |
|
|
410
|
+
| `createUser(name)` | `api.ext.user.createUser(name)` |
|
|
411
|
+
| `getSettings()` | `api.ext.user.getSettings()` |
|
|
412
|
+
| `getSetting(id)` | `api.ext.user.getSetting(id)` |
|
|
413
|
+
| `storeSettings(map)` | `api.ext.user.storeSettings(map)` |
|
|
414
|
+
| `storeSetting(id,val)` | `api.ext.user.storeSetting(id,val)` |
|
|
415
|
+
| `getTerminalLogs()` | `api.ext.terminal.getTerminalLogs()` |
|
|
416
|
+
| `setTerminalSubscription()` | `api.ext.terminal.setTerminalSubscription()` |
|
|
417
|
+
| `interrupt()` | `api.ext.queue.interrupt()` |
|
|
418
|
+
|
|
419
|
+
Quick grep-based migration (bash):
|
|
420
|
+
|
|
421
|
+
```bash
|
|
422
|
+
grep -R "api\.getSystemStats" -n src | cut -d: -f1 | xargs sed -i '' 's/api\.getSystemStats()/api.ext.system.getSystemStats()/g'
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
PowerShell example:
|
|
426
|
+
|
|
427
|
+
```powershell
|
|
428
|
+
Get-ChildItem -Recurse -Include *.ts | ForEach-Object {
|
|
429
|
+
(Get-Content $_.FullName) -replace 'api.getSystemStats\(\)', 'api.ext.system.getSystemStats()' | Set-Content $_.FullName
|
|
430
|
+
}
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
(Adjust the pattern per method; or use a codemod tool if you have many occurrences.)
|
|
434
|
+
|
|
435
|
+
Diff example:
|
|
436
|
+
|
|
437
|
+
Example migration:
|
|
438
|
+
|
|
439
|
+
```diff
|
|
440
|
+
- const stats = await api.getSystemStats();
|
|
441
|
+
+ const stats = await api.ext.system.getSystemStats();
|
|
442
|
+
- await api.uploadImage(buf, 'a.png');
|
|
443
|
+
+ await api.ext.file.uploadImage(buf, 'a.png');
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
## Reference Overview
|
|
447
|
+
|
|
448
|
+
Core (non‑deprecated) `ComfyApi` methods: `init`, `waitForReady`, event registration (`on`/`off`/`removeAllListeners`), `fetchApi`, `pollStatus`, `ping`, `reconnectWs`, `destroy`, and modular surface via `ext`.
|
|
449
|
+
|
|
450
|
+
Supporting classes:
|
|
451
|
+
|
|
452
|
+
- `PromptBuilder` – graph construction & value injection
|
|
453
|
+
- `CallWrapper` – prompt execution lifecycle helpers
|
|
454
|
+
- `ComfyPool` – multi‑instance scheduler
|
|
455
|
+
|
|
456
|
+
Enums & Types: `EQueueMode`, sampler / scheduler unions, `OSType`, plus exported response types found under `types/*`.
|
|
457
|
+
|
|
458
|
+
## Examples
|
|
459
|
+
|
|
460
|
+
See the `examples` directory for text‑to‑image, image‑to‑image, upscaling and pool orchestration patterns.
|
|
461
|
+
|
|
462
|
+
## Errors & Diagnostics
|
|
463
|
+
|
|
464
|
+
The SDK raises specialized subclasses of `Error` to improve debuggability during workflow submission and execution:
|
|
465
|
+
|
|
466
|
+
| Error | When | Key Extras |
|
|
467
|
+
| ----- | ---- | ---------- |
|
|
468
|
+
| `EnqueueFailedError` | HTTP `/prompt` (append/queue) failed | `status`, `statusText`, `url`, `method`, `bodyJSON`, `bodyTextSnippet`, `reason` |
|
|
469
|
+
| `ExecutionFailedError` | Execution finished but not all mapped outputs arrived | missing outputs context |
|
|
470
|
+
| `ExecutionInterruptedError` | Server emitted an interruption mid run | cause carries interruption detail |
|
|
471
|
+
| `MissingNodeError` | A declared bypass or output node is absent | `cause` (optional) |
|
|
472
|
+
| `WentMissingError` | Job disappeared from queue and no cached output | – |
|
|
473
|
+
| `FailedCacheError` | Cached output retrieval failed | – |
|
|
474
|
+
| `CustomEventError` | Server emitted execution error event | event payload in `cause` |
|
|
475
|
+
| `DisconnectedError` | WebSocket disconnected mid‑execution | – |
|
|
476
|
+
|
|
477
|
+
### Error Codes
|
|
478
|
+
|
|
479
|
+
Every custom error exposes a stable `code` (enum) to enable branch logic without string matching message text:
|
|
480
|
+
|
|
481
|
+
```ts
|
|
482
|
+
import { ErrorCode, EnqueueFailedError } from "comfyui-node";
|
|
483
|
+
|
|
484
|
+
try { /* run call wrapper */ } catch (e) {
|
|
485
|
+
if ((e as any).code === ErrorCode.ENQUEUE_FAILED) {
|
|
486
|
+
// inspect structured diagnostics
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
### EnqueueFailedError Details
|
|
492
|
+
|
|
493
|
+
When the server rejects a workflow submission the SDK now attempts to surface the underlying cause:
|
|
494
|
+
|
|
495
|
+
```ts
|
|
496
|
+
try {
|
|
497
|
+
await new CallWrapper(api, workflow).run();
|
|
498
|
+
} catch (e) {
|
|
499
|
+
if (e instanceof EnqueueFailedError) {
|
|
500
|
+
console.error('Status:', e.status, e.statusText);
|
|
501
|
+
console.error('Reason:', e.reason);
|
|
502
|
+
console.error('Body JSON:', e.bodyJSON);
|
|
503
|
+
console.error('Snippet:', e.bodyTextSnippet);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
`reason` is resolved using (in order): `bodyJSON.error`, `bodyJSON.message`, falling back to a truncated textual body (first 500 chars). Raw JSON (if parseable) and a short text snippet are both retained to help rapidly identify mis‑shaped prompts, missing extensions, permission issues or model path problems.
|
|
509
|
+
|
|
510
|
+
If the response body is not JSON, `bodyTextSnippet` contains the first 500 characters of the returned text, which is also copied into `reason`.
|
|
511
|
+
|
|
512
|
+
These enriched diagnostics are only attached for the enqueue phase; downstream execution issues still rely on event‑level errors.
|
|
513
|
+
|
|
514
|
+
### Execution Failure vs Interruption
|
|
515
|
+
|
|
516
|
+
- `ExecutionFailedError`: The workflow ran but one or more declared output nodes never produced data (often due to an upstream node error not surfaced as a global event). Revisit your output mappings or inspect per‑node errors.
|
|
517
|
+
- `ExecutionInterruptedError`: The server (or user action) actively interrupted execution; retrying may succeed if the interruption cause was transient.
|
|
518
|
+
|
|
519
|
+
### Persisting & Replaying Builder State
|
|
520
|
+
|
|
521
|
+
You can store builder state in a database / job queue:
|
|
522
|
+
|
|
523
|
+
```ts
|
|
524
|
+
const snapshot = builder.toJSON();
|
|
525
|
+
// later
|
|
526
|
+
const restored = PromptBuilder.fromJSON(snapshot)
|
|
527
|
+
.validateOutputMappings();
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
This is useful for deferred execution, cross‑process scheduling, or audit logging of the exact prompt graph sent to the server.
|
|
531
|
+
|
|
532
|
+
## Contributing
|
|
533
|
+
|
|
534
|
+
Issues and PRs welcome. Please include focused changes and tests where sensible. Adhere to existing coding style and keep feature surfaces minimal & cohesive.
|
|
535
|
+
|
|
536
|
+
## Testing & Coverage
|
|
537
|
+
|
|
538
|
+
This repository uses Bun's built-in test runner. Common scripts:
|
|
539
|
+
|
|
540
|
+
```bash
|
|
541
|
+
bun test # unit + lightweight integration tests
|
|
542
|
+
bun run test:real # real server tests (COMFY_REAL=1)
|
|
543
|
+
bun run test:full # comprehensive real server tests (COMFY_REAL=1 COMFY_FULL=1)
|
|
544
|
+
bun run coverage # text coverage summary (lines/functions per file)
|
|
545
|
+
bun run coverage:lcov # generate coverage/lcov.info (for badges or external services)
|
|
546
|
+
bun run coverage:enforce # generate LCOV then enforce thresholds
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
Environment flags:
|
|
550
|
+
|
|
551
|
+
- `COMFY_REAL=1` enables `test/real.integration.spec.ts` (expects a running ComfyUI at `http://localhost:8188` unless overridden via `COMFY_HOST`).
|
|
552
|
+
- `COMFY_FULL=1` additionally enables the extended `test/real.full.integration.spec.ts` suite.
|
|
553
|
+
- `COMFY_HOST=http://host:port` to point at a non-default instance.
|
|
554
|
+
|
|
555
|
+
Coverage thresholds are enforced by `scripts/coverage-check.ts` (baseline intentionally modest to allow incremental improvement):
|
|
556
|
+
|
|
557
|
+
Default thresholds:
|
|
558
|
+
|
|
559
|
+
- Lines: `>= 25%`
|
|
560
|
+
- Functions: `>= 60%`
|
|
561
|
+
|
|
562
|
+
Override thresholds ad hoc (CI example):
|
|
563
|
+
|
|
564
|
+
```bash
|
|
565
|
+
COVERAGE_MIN_LINES=30 COVERAGE_MIN_FUNCTIONS=65 bun run coverage:enforce
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
or in PowerShell:
|
|
569
|
+
|
|
570
|
+
```powershell
|
|
571
|
+
$env:COVERAGE_MIN_LINES=30; $env:COVERAGE_MIN_FUNCTIONS=65; bun run coverage:enforce
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
### Improving Coverage
|
|
575
|
+
|
|
576
|
+
Current low-coverage areas (see `bun test --coverage` output):
|
|
577
|
+
|
|
578
|
+
- `src/client.ts` – large surface; break out helpers & add unit tests for fetch error branches and WebSocket reconnect logic.
|
|
579
|
+
- `src/call-wrapper.ts` – test error paths (enqueue failure, execution interruption, missing outputs) with mocked `fetch` & event streams.
|
|
580
|
+
- Feature modules with toleration logic (`monitoring`, `manager`, `terminal`) – add mocks to simulate absent endpoints & successful responses.
|
|
581
|
+
|
|
582
|
+
Incremental strategy:
|
|
583
|
+
|
|
584
|
+
1. Extract pure helper functions from monolithic classes (e.g., parsing, polling backoff) into modules you can unit test in isolation.
|
|
585
|
+
2. Add fine-grained tests for error branches (simulate non-200 responses & malformed JSON bodies) to raise line coverage quickly.
|
|
586
|
+
3. Introduce deterministic mock WebSocket that replays scripted events (connection drop, progress, output) to cover reconnect & event translation.
|
|
587
|
+
4. Gradually raise `COVERAGE_MIN_LINES` by 5% after each meaningful set of additions.
|
|
588
|
+
|
|
589
|
+
Skipping heavy real-image generation: full suite internally tolerates missing models & will skip or soften assertions rather than fail—use it sparingly in CI (nightly job) if runtime is a concern.
|
|
590
|
+
|
|
591
|
+
If contributing, please run at least:
|
|
592
|
+
|
|
593
|
+
```bash
|
|
594
|
+
bun test && bun run coverage
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
before opening a PR, and prefer adding tests alongside new feature code.
|
|
598
|
+
|
|
599
|
+
## License
|
|
600
|
+
|
|
601
|
+
MIT – see `LICENSE`.
|