@openclaw/comfy-provider 2026.9.8 → 2026.10.1-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -30,23 +30,14 @@ function buildComfyImageGenerationProvider() {
|
|
|
30
30
|
async generateImage(req) {
|
|
31
31
|
if ((req.inputImages?.length ?? 0) > 1) throw new Error("Comfy image generation currently supports at most one reference image");
|
|
32
32
|
const result = await runComfyWorkflow({
|
|
33
|
-
|
|
34
|
-
agentDir: req.agentDir,
|
|
35
|
-
authStore: req.authStore,
|
|
36
|
-
prompt: req.prompt,
|
|
37
|
-
model: req.model,
|
|
38
|
-
timeoutMs: req.timeoutMs,
|
|
33
|
+
...req,
|
|
39
34
|
capability: "image",
|
|
40
|
-
outputKinds: ["images"],
|
|
41
35
|
inputImage: req.inputImages?.[0]
|
|
42
36
|
});
|
|
43
37
|
return {
|
|
44
38
|
images: result.assets,
|
|
45
39
|
model: result.model,
|
|
46
|
-
metadata:
|
|
47
|
-
promptId: result.promptId,
|
|
48
|
-
outputNodeIds: result.outputNodeIds
|
|
49
|
-
}
|
|
40
|
+
metadata: result.metadata
|
|
50
41
|
};
|
|
51
42
|
}
|
|
52
43
|
};
|
|
@@ -31,14 +31,8 @@ function buildComfyMusicGenerationProvider() {
|
|
|
31
31
|
async generateMusic(req) {
|
|
32
32
|
if ((req.inputImages?.length ?? 0) > COMFY_MAX_INPUT_IMAGES) throw new Error(`Comfy music generation supports at most ${COMFY_MAX_INPUT_IMAGES} reference image.`);
|
|
33
33
|
const result = await runComfyWorkflow({
|
|
34
|
-
|
|
35
|
-
agentDir: req.agentDir,
|
|
36
|
-
authStore: req.authStore,
|
|
37
|
-
prompt: req.prompt,
|
|
38
|
-
model: req.model,
|
|
39
|
-
timeoutMs: req.timeoutMs,
|
|
34
|
+
...req,
|
|
40
35
|
capability: "music",
|
|
41
|
-
outputKinds: ["audio"],
|
|
42
36
|
inputImage: resolveInputImage(req.inputImages?.[0])
|
|
43
37
|
});
|
|
44
38
|
return {
|
|
@@ -49,8 +43,7 @@ function buildComfyMusicGenerationProvider() {
|
|
|
49
43
|
})),
|
|
50
44
|
model: result.model,
|
|
51
45
|
metadata: {
|
|
52
|
-
|
|
53
|
-
outputNodeIds: result.outputNodeIds,
|
|
46
|
+
...result.metadata,
|
|
54
47
|
inputImageCount: req.inputImages?.length ?? 0
|
|
55
48
|
}
|
|
56
49
|
};
|
|
@@ -45,27 +45,14 @@ function buildComfyVideoGenerationProvider() {
|
|
|
45
45
|
if ((req.inputImages?.length ?? 0) > 1) throw new Error("Comfy video generation currently supports at most one reference image");
|
|
46
46
|
if ((req.inputVideos?.length ?? 0) > 0) throw new Error("Comfy video generation does not support input videos");
|
|
47
47
|
const result = await runComfyWorkflow({
|
|
48
|
-
|
|
49
|
-
agentDir: req.agentDir,
|
|
50
|
-
authStore: req.authStore,
|
|
51
|
-
prompt: req.prompt,
|
|
52
|
-
model: req.model,
|
|
53
|
-
timeoutMs: req.timeoutMs,
|
|
48
|
+
...req,
|
|
54
49
|
capability: "video",
|
|
55
|
-
outputKinds: [
|
|
56
|
-
"images",
|
|
57
|
-
"gifs",
|
|
58
|
-
"videos"
|
|
59
|
-
],
|
|
60
50
|
inputImage: toComfyInputImage(req.inputImages?.[0])
|
|
61
51
|
});
|
|
62
52
|
return {
|
|
63
53
|
videos: result.assets,
|
|
64
54
|
model: result.model,
|
|
65
|
-
metadata:
|
|
66
|
-
promptId: result.promptId,
|
|
67
|
-
outputNodeIds: result.outputNodeIds
|
|
68
|
-
}
|
|
55
|
+
metadata: result.metadata
|
|
69
56
|
};
|
|
70
57
|
}
|
|
71
58
|
};
|
package/dist/workflow-runtime.js
CHANGED
|
@@ -6,7 +6,7 @@ import { extensionForMime } from "openclaw/plugin-sdk/media-mime";
|
|
|
6
6
|
import { resolvePositiveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
|
|
7
7
|
import { isProviderApiKeyConfigured } from "openclaw/plugin-sdk/provider-auth";
|
|
8
8
|
import { resolveApiKeyForProvider } from "openclaw/plugin-sdk/provider-auth-runtime";
|
|
9
|
-
import { assertOkOrThrowHttpError,
|
|
9
|
+
import { assertOkOrThrowHttpError, readProviderBinaryResponse, readProviderJsonResponse, redactProviderResponseErrorText, resolveProviderHttpRequestConfig } from "openclaw/plugin-sdk/provider-http";
|
|
10
10
|
import { normalizeSecretInputString, resolveConfiguredSecretInputString, resolveSecretInputString } from "openclaw/plugin-sdk/secret-input-runtime";
|
|
11
11
|
import { canResolveEnvSecretRefInReadOnlyPath } from "openclaw/plugin-sdk/secret-ref-readonly";
|
|
12
12
|
import { fetchWithSsrFGuard, isPrivateOrLoopbackHost, mergeSsrFPolicies, ssrfPolicyFromHttpBaseUrlAllowedOrigin } from "openclaw/plugin-sdk/ssrf-runtime";
|
|
@@ -15,9 +15,6 @@ import { resolveUserPath } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
|
15
15
|
//#region extensions/comfy/workflow-runtime.ts
|
|
16
16
|
const DEFAULT_COMFY_LOCAL_BASE_URL = "http://127.0.0.1:8188";
|
|
17
17
|
const DEFAULT_COMFY_CLOUD_BASE_URL = "https://cloud.comfy.org";
|
|
18
|
-
const DEFAULT_PROMPT_INPUT_NAME = "text";
|
|
19
|
-
const DEFAULT_INPUT_IMAGE_INPUT_NAME = "image";
|
|
20
|
-
const DEFAULT_SEED_INPUT_NAME = "seed";
|
|
21
18
|
const DEFAULT_POLL_INTERVAL_MS = 1500;
|
|
22
19
|
const DEFAULT_TIMEOUT_MS = 3e5;
|
|
23
20
|
const RANDOM_SEED_EXCLUSIVE_MAX = 2 ** 48 - 1;
|
|
@@ -63,8 +60,7 @@ function resolveComfyApiKey(config, cfg) {
|
|
|
63
60
|
const apiKey = normalizeSecretInputString(resolved.value);
|
|
64
61
|
return apiKey ? {
|
|
65
62
|
status: "available",
|
|
66
|
-
apiKey
|
|
67
|
-
source: "plugins.entries.comfy.config.apiKey"
|
|
63
|
+
apiKey
|
|
68
64
|
} : { status: "missing" };
|
|
69
65
|
}
|
|
70
66
|
if (resolved.status === "configured_unavailable") {
|
|
@@ -78,38 +74,28 @@ function resolveComfyApiKey(config, cfg) {
|
|
|
78
74
|
const apiKey = normalizeSecretInputString(process.env[envVarName]);
|
|
79
75
|
return apiKey ? {
|
|
80
76
|
status: "available",
|
|
81
|
-
apiKey
|
|
82
|
-
source: `plugins.entries.comfy.config.apiKey (${envVarName})`
|
|
77
|
+
apiKey
|
|
83
78
|
} : { status: "configured_unavailable" };
|
|
84
79
|
}
|
|
85
80
|
return { status: "missing" };
|
|
86
81
|
}
|
|
87
|
-
function getRequiredConfigString(config, key) {
|
|
88
|
-
const value = normalizeOptionalString(config[key]);
|
|
89
|
-
if (!value) throw new Error(`plugins.entries.comfy.config.${key} is required`);
|
|
90
|
-
return value;
|
|
91
|
-
}
|
|
92
|
-
function resolveComfyWorkflowSource(config) {
|
|
93
|
-
const workflow = config.workflow;
|
|
94
|
-
if (isRecord(workflow)) return { workflow: structuredClone(workflow) };
|
|
95
|
-
return { workflowPath: normalizeOptionalString(config.workflowPath) };
|
|
96
|
-
}
|
|
97
82
|
async function loadComfyWorkflow(config) {
|
|
98
|
-
const
|
|
99
|
-
if (
|
|
100
|
-
|
|
101
|
-
|
|
83
|
+
const workflow = config.workflow;
|
|
84
|
+
if (isRecord(workflow)) return structuredClone(workflow);
|
|
85
|
+
const workflowPath = normalizeOptionalString(config.workflowPath);
|
|
86
|
+
if (!workflowPath) throw new Error("plugins.entries.comfy.config.<capability>.workflow or workflowPath is required");
|
|
87
|
+
const resolvedPath = resolveUserPath(workflowPath);
|
|
102
88
|
const raw = await fs.readFile(resolvedPath, "utf8");
|
|
103
89
|
const parsed = JSON.parse(raw);
|
|
104
90
|
if (!isRecord(parsed)) throw new Error(`Comfy workflow at ${resolvedPath} must be a JSON object`);
|
|
105
91
|
return parsed;
|
|
106
92
|
}
|
|
107
|
-
function setWorkflowInput(
|
|
108
|
-
const node =
|
|
109
|
-
if (!isRecord(node)) throw new Error(`Comfy workflow missing node "${
|
|
93
|
+
function setWorkflowInput(workflow, nodeId, inputName, value) {
|
|
94
|
+
const node = workflow[nodeId];
|
|
95
|
+
if (!isRecord(node)) throw new Error(`Comfy workflow missing node "${nodeId}"`);
|
|
110
96
|
const inputs = node.inputs;
|
|
111
|
-
if (!isRecord(inputs)) throw new Error(`Comfy workflow node "${
|
|
112
|
-
inputs[
|
|
97
|
+
if (!isRecord(inputs)) throw new Error(`Comfy workflow node "${nodeId}" is missing an inputs object`);
|
|
98
|
+
inputs[inputName] = value;
|
|
113
99
|
}
|
|
114
100
|
async function resolveComfyHeadersConfig(value, cfg, configPath) {
|
|
115
101
|
const headers = new Headers();
|
|
@@ -129,55 +115,35 @@ async function resolveComfyHeadersConfig(value, cfg, configPath) {
|
|
|
129
115
|
return headers;
|
|
130
116
|
}
|
|
131
117
|
function resolveComfyNetworkPolicy(params) {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
parsed = new URL(params.baseUrl);
|
|
135
|
-
} catch {
|
|
136
|
-
return {};
|
|
137
|
-
}
|
|
118
|
+
const parsed = URL.parse(params.baseUrl);
|
|
119
|
+
if (!parsed) return;
|
|
138
120
|
const hostname = normalizeOptionalLowercaseString(parsed.hostname) ?? "";
|
|
139
|
-
if (!hostname) return
|
|
121
|
+
if (!hostname) return;
|
|
140
122
|
const localHostnamePolicy = params.mode === "local" ? { hostnameAllowlist: [hostname] } : void 0;
|
|
141
|
-
|
|
142
|
-
if (!params.
|
|
143
|
-
if (!params.explicitAllowPrivateNetwork && params.mode
|
|
144
|
-
if (!params.explicitAllowPrivateNetwork && params.mode === "local" && !isPrivateOrLoopbackHost(hostname) && !isSingleLabelServiceHostname(hostname)) return hostnameOnlyPolicy;
|
|
123
|
+
if (!params.allowPrivateNetwork) return localHostnamePolicy;
|
|
124
|
+
if (!params.explicitAllowPrivateNetwork && params.mode !== "local") return;
|
|
125
|
+
if (!params.explicitAllowPrivateNetwork && params.mode === "local" && !isPrivateOrLoopbackHost(hostname) && !isSingleLabelServiceHostname(hostname)) return localHostnamePolicy;
|
|
145
126
|
const originPolicy = ssrfPolicyFromHttpBaseUrlAllowedOrigin(params.baseUrl);
|
|
146
|
-
if (!originPolicy) return
|
|
147
|
-
return
|
|
127
|
+
if (!originPolicy) return localHostnamePolicy;
|
|
128
|
+
return params.mode === "local" ? mergeSsrFPolicies(originPolicy, localHostnamePolicy) : originPolicy;
|
|
148
129
|
}
|
|
149
130
|
function isSingleLabelServiceHostname(hostname) {
|
|
150
131
|
return /^[a-z0-9_](?:[a-z0-9_-]{0,61}[a-z0-9_])?$/u.test(hostname);
|
|
151
132
|
}
|
|
152
133
|
async function readJsonResponse(params) {
|
|
153
|
-
const {
|
|
154
|
-
|
|
155
|
-
init: params.init,
|
|
156
|
-
timeoutMs: params.timeoutMs,
|
|
157
|
-
policy: params.policy,
|
|
158
|
-
dispatcherPolicy: params.dispatcherPolicy,
|
|
159
|
-
auditContext: params.auditContext
|
|
160
|
-
});
|
|
134
|
+
const { errorPrefix, ...request } = params;
|
|
135
|
+
const { response, release } = await fetchWithSsrFGuard(request);
|
|
161
136
|
try {
|
|
162
137
|
const requestHeaders = params.init?.headers;
|
|
163
|
-
await assertOkOrThrowHttpError(response,
|
|
164
|
-
return await readProviderJsonResponse(response,
|
|
138
|
+
await assertOkOrThrowHttpError(response, errorPrefix, { requestHeaders });
|
|
139
|
+
return await readProviderJsonResponse(response, errorPrefix, { requestHeaders });
|
|
165
140
|
} finally {
|
|
166
141
|
await release();
|
|
167
142
|
}
|
|
168
143
|
}
|
|
169
|
-
function resolveFileExtension(params) {
|
|
170
|
-
const extension = extensionForMime(params.mimeType);
|
|
171
|
-
if (extension) return extension.slice(1);
|
|
172
|
-
const fileName = params.fileName?.trim();
|
|
173
|
-
if (!fileName) return "bin";
|
|
174
|
-
const dotIndex = fileName.lastIndexOf(".");
|
|
175
|
-
if (dotIndex < 0 || dotIndex === fileName.length - 1) return "bin";
|
|
176
|
-
return fileName.slice(dotIndex + 1);
|
|
177
|
-
}
|
|
178
144
|
async function uploadInputImage(params) {
|
|
179
145
|
const form = new FormData();
|
|
180
|
-
form.set("image", new Blob([bufferToBlobPart(params.image.buffer)], { type: params.image.mimeType }), normalizeOptionalString(params.image.fileName) || `input.${
|
|
146
|
+
form.set("image", new Blob([bufferToBlobPart(params.image.buffer)], { type: params.image.mimeType }), normalizeOptionalString(params.image.fileName) || `input.${extensionForMime(params.image.mimeType)?.slice(1) || "bin"}`);
|
|
181
147
|
form.set("type", "input");
|
|
182
148
|
form.set("overwrite", "true");
|
|
183
149
|
const headers = new Headers(params.headers);
|
|
@@ -303,7 +269,8 @@ async function downloadOutputFile(params) {
|
|
|
303
269
|
onOverflow: ({ maxBytes }) => /* @__PURE__ */ new Error(`${downloadLabel} exceeds ${maxBytes} bytes`),
|
|
304
270
|
onIdleTimeout: ({ chunkTimeoutMs }) => /* @__PURE__ */ new Error(`${downloadLabel} stalled after ${chunkTimeoutMs}ms`)
|
|
305
271
|
}),
|
|
306
|
-
mimeType
|
|
272
|
+
mimeType,
|
|
273
|
+
fileName
|
|
307
274
|
};
|
|
308
275
|
} finally {
|
|
309
276
|
await firstResponse.release();
|
|
@@ -330,7 +297,7 @@ function hasUnavailableComfyHeaderSecret(value, cfg) {
|
|
|
330
297
|
function isComfyCapabilityConfigured(params) {
|
|
331
298
|
const { config } = getComfyConfig(params.cfg);
|
|
332
299
|
const capabilityConfig = getComfyCapabilityConfig(config, params.capability);
|
|
333
|
-
const hasWorkflow = Boolean(
|
|
300
|
+
const hasWorkflow = Boolean(isRecord(capabilityConfig.workflow) || normalizeOptionalString(capabilityConfig.workflowPath));
|
|
334
301
|
const hasPromptNode = Boolean(normalizeOptionalString(capabilityConfig.promptNodeId));
|
|
335
302
|
if (!hasWorkflow || !hasPromptNode) return false;
|
|
336
303
|
if (hasUnavailableComfyHeaderSecret(capabilityConfig.headers, params.cfg)) return false;
|
|
@@ -349,40 +316,27 @@ async function runComfyWorkflow(params) {
|
|
|
349
316
|
const capabilityConfig = getComfyCapabilityConfig(config, params.capability);
|
|
350
317
|
const mode = resolveComfyMode(capabilityConfig);
|
|
351
318
|
const workflow = await loadComfyWorkflow(capabilityConfig);
|
|
352
|
-
const promptNodeId =
|
|
353
|
-
|
|
319
|
+
const promptNodeId = normalizeOptionalString(capabilityConfig.promptNodeId);
|
|
320
|
+
if (!promptNodeId) throw new Error("plugins.entries.comfy.config.promptNodeId is required");
|
|
321
|
+
const promptInputName = normalizeOptionalString(capabilityConfig.promptInputName) ?? "text";
|
|
354
322
|
const inputImageNodeId = normalizeOptionalString(capabilityConfig.inputImageNodeId);
|
|
355
|
-
const inputImageInputName = normalizeOptionalString(capabilityConfig.inputImageInputName) ??
|
|
323
|
+
const inputImageInputName = normalizeOptionalString(capabilityConfig.inputImageInputName) ?? "image";
|
|
356
324
|
const seedNodeId = normalizeOptionalString(capabilityConfig.seedNodeId);
|
|
357
|
-
const seedInputName = normalizeOptionalString(capabilityConfig.seedInputName) ??
|
|
325
|
+
const seedInputName = normalizeOptionalString(capabilityConfig.seedInputName) ?? "seed";
|
|
358
326
|
const outputNodeId = normalizeOptionalString(capabilityConfig.outputNodeId);
|
|
359
327
|
const pollIntervalMs = resolvePositiveTimerTimeoutMs(readConfigInteger(capabilityConfig, "pollIntervalMs"), DEFAULT_POLL_INTERVAL_MS);
|
|
360
328
|
const timeoutMs = resolvePositiveTimerTimeoutMs(readConfigInteger(capabilityConfig, "timeoutMs") ?? params.timeoutMs, DEFAULT_TIMEOUT_MS);
|
|
361
329
|
const providerModel = normalizeOptionalString(params.model) || "workflow";
|
|
362
|
-
setWorkflowInput(
|
|
363
|
-
|
|
364
|
-
nodeId: promptNodeId,
|
|
365
|
-
inputName: promptInputName,
|
|
366
|
-
value: params.prompt
|
|
367
|
-
});
|
|
368
|
-
if (seedNodeId) setWorkflowInput({
|
|
369
|
-
workflow,
|
|
370
|
-
nodeId: seedNodeId,
|
|
371
|
-
inputName: seedInputName,
|
|
372
|
-
value: randomInt(RANDOM_SEED_EXCLUSIVE_MAX)
|
|
373
|
-
});
|
|
330
|
+
setWorkflowInput(workflow, promptNodeId, promptInputName, params.prompt);
|
|
331
|
+
if (seedNodeId) setWorkflowInput(workflow, seedNodeId, seedInputName, randomInt(RANDOM_SEED_EXCLUSIVE_MAX));
|
|
374
332
|
const pluginApiKey = resolveComfyApiKey(capabilityConfig, params.cfg);
|
|
375
|
-
const
|
|
376
|
-
apiKey: pluginApiKey.apiKey,
|
|
377
|
-
source: pluginApiKey.source,
|
|
378
|
-
mode: "api-key"
|
|
379
|
-
} : pluginApiKey.status === "configured_unavailable" ? null : await resolveApiKeyForProvider({
|
|
333
|
+
const apiKey = mode === "cloud" ? pluginApiKey.status === "available" ? pluginApiKey.apiKey : pluginApiKey.status === "configured_unavailable" ? void 0 : (await resolveApiKeyForProvider({
|
|
380
334
|
provider: "comfy",
|
|
381
335
|
cfg: params.cfg,
|
|
382
336
|
agentDir: params.agentDir,
|
|
383
337
|
store: params.authStore
|
|
384
|
-
}) :
|
|
385
|
-
if (mode === "cloud" && !
|
|
338
|
+
})).apiKey : void 0;
|
|
339
|
+
if (mode === "cloud" && !apiKey) throw new Error("Comfy Cloud API key missing");
|
|
386
340
|
const explicitAllowPrivateNetwork = asBoolean(capabilityConfig.allowPrivateNetwork) === true;
|
|
387
341
|
const { baseUrl, allowPrivateNetwork, headers, dispatcherPolicy } = resolveProviderHttpRequestConfig({
|
|
388
342
|
baseUrl: normalizeOptionalString(capabilityConfig.baseUrl),
|
|
@@ -390,51 +344,45 @@ async function runComfyWorkflow(params) {
|
|
|
390
344
|
allowPrivateNetwork: mode === "local" || explicitAllowPrivateNetwork,
|
|
391
345
|
headers: await resolveComfyHeadersConfig(capabilityConfig.headers, params.cfg, configPath),
|
|
392
346
|
defaultHeaders: mode === "cloud" ? {
|
|
393
|
-
"X-API-Key":
|
|
347
|
+
"X-API-Key": apiKey ?? "",
|
|
394
348
|
"Content-Type": "application/json"
|
|
395
349
|
} : { "Content-Type": "application/json" },
|
|
396
350
|
provider: "comfy",
|
|
397
351
|
capability: params.capability === "music" ? "audio" : params.capability,
|
|
398
352
|
transport: "http"
|
|
399
353
|
});
|
|
400
|
-
const normalizedBaseUrl = normalizeBaseUrl(baseUrl) || (mode === "cloud" ? DEFAULT_COMFY_CLOUD_BASE_URL : DEFAULT_COMFY_LOCAL_BASE_URL);
|
|
401
354
|
const networkPolicy = resolveComfyNetworkPolicy({
|
|
402
|
-
baseUrl
|
|
355
|
+
baseUrl,
|
|
403
356
|
allowPrivateNetwork,
|
|
404
357
|
explicitAllowPrivateNetwork,
|
|
405
358
|
mode
|
|
406
359
|
});
|
|
407
360
|
if (params.inputImage) {
|
|
408
361
|
if (!inputImageNodeId) throw new Error("Comfy edit requests require plugins.entries.comfy.config.<capability>.inputImageNodeId to be configured");
|
|
409
|
-
setWorkflowInput({
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
image: params.inputImage,
|
|
420
|
-
mode,
|
|
421
|
-
capability: params.capability
|
|
422
|
-
})
|
|
423
|
-
});
|
|
362
|
+
setWorkflowInput(workflow, inputImageNodeId, inputImageInputName, await uploadInputImage({
|
|
363
|
+
baseUrl,
|
|
364
|
+
headers: new Headers(headers),
|
|
365
|
+
timeoutMs,
|
|
366
|
+
policy: networkPolicy,
|
|
367
|
+
dispatcherPolicy,
|
|
368
|
+
image: params.inputImage,
|
|
369
|
+
mode,
|
|
370
|
+
capability: params.capability
|
|
371
|
+
}));
|
|
424
372
|
}
|
|
425
373
|
const submitPayload = {
|
|
426
374
|
prompt: workflow,
|
|
427
|
-
...mode === "cloud" &&
|
|
375
|
+
...mode === "cloud" && apiKey ? { extra_data: { api_key_comfy_org: apiKey } } : {}
|
|
428
376
|
};
|
|
429
377
|
const promptResponse = await readJsonResponse({
|
|
430
|
-
url: `${
|
|
378
|
+
url: `${baseUrl}${mode === "cloud" ? "/api/prompt" : "/prompt"}`,
|
|
431
379
|
init: {
|
|
432
380
|
method: "POST",
|
|
433
381
|
headers,
|
|
434
382
|
body: JSON.stringify(submitPayload)
|
|
435
383
|
},
|
|
436
384
|
timeoutMs,
|
|
437
|
-
policy: networkPolicy
|
|
385
|
+
policy: networkPolicy,
|
|
438
386
|
dispatcherPolicy,
|
|
439
387
|
auditContext: `comfy-${params.capability}-generate`,
|
|
440
388
|
errorPrefix: "Comfy workflow submit failed"
|
|
@@ -442,12 +390,12 @@ async function runComfyWorkflow(params) {
|
|
|
442
390
|
const promptId = normalizeOptionalString(promptResponse.prompt_id);
|
|
443
391
|
if (!promptId) throw new Error("Comfy workflow submit response missing prompt_id");
|
|
444
392
|
const historyEntry = extractHistoryEntry(await waitForComfyHistory({
|
|
445
|
-
baseUrl
|
|
393
|
+
baseUrl,
|
|
446
394
|
promptId,
|
|
447
395
|
headers: new Headers(headers),
|
|
448
396
|
timeoutMs,
|
|
449
397
|
pollIntervalMs,
|
|
450
|
-
policy: networkPolicy
|
|
398
|
+
policy: networkPolicy,
|
|
451
399
|
dispatcherPolicy,
|
|
452
400
|
mode
|
|
453
401
|
}), promptId);
|
|
@@ -455,32 +403,31 @@ async function runComfyWorkflow(params) {
|
|
|
455
403
|
const outputFiles = collectOutputFiles({
|
|
456
404
|
history: historyEntry,
|
|
457
405
|
outputNodeId,
|
|
458
|
-
outputKinds: params.
|
|
406
|
+
outputKinds: params.capability === "music" ? ["audio"] : params.capability === "video" ? [
|
|
407
|
+
"images",
|
|
408
|
+
"gifs",
|
|
409
|
+
"videos"
|
|
410
|
+
] : ["images"],
|
|
459
411
|
capability: params.capability
|
|
460
412
|
});
|
|
461
413
|
if (outputFiles.length === 0) throw new Error(`Comfy workflow ${promptId} completed without ${params.capability} outputs`);
|
|
462
414
|
const assets = [];
|
|
463
415
|
const outputKind = params.capability === "music" ? "audio" : params.capability;
|
|
464
416
|
const maxOutputBytes = resolveGeneratedMediaMaxBytes(params.cfg, outputKind);
|
|
465
|
-
let assetIndex = 0;
|
|
466
417
|
for (const output of outputFiles) {
|
|
467
418
|
const downloaded = await downloadOutputFile({
|
|
468
|
-
baseUrl
|
|
419
|
+
baseUrl,
|
|
469
420
|
headers: new Headers(headers),
|
|
470
421
|
timeoutMs,
|
|
471
|
-
policy: networkPolicy
|
|
422
|
+
policy: networkPolicy,
|
|
472
423
|
dispatcherPolicy,
|
|
473
424
|
file: output.file,
|
|
474
425
|
mode,
|
|
475
426
|
capability: params.capability,
|
|
476
427
|
maxBytes: maxOutputBytes
|
|
477
428
|
});
|
|
478
|
-
assetIndex += 1;
|
|
479
|
-
const originalName = normalizeOptionalString(output.file.filename) || normalizeOptionalString(output.file.name);
|
|
480
429
|
assets.push({
|
|
481
|
-
|
|
482
|
-
mimeType: downloaded.mimeType,
|
|
483
|
-
fileName: originalName || `${params.capability}-${assetIndex}.${resolveFileExtension({ mimeType: downloaded.mimeType })}`,
|
|
430
|
+
...downloaded,
|
|
484
431
|
metadata: {
|
|
485
432
|
nodeId: output.nodeId,
|
|
486
433
|
promptId
|
|
@@ -490,8 +437,10 @@ async function runComfyWorkflow(params) {
|
|
|
490
437
|
return {
|
|
491
438
|
assets,
|
|
492
439
|
model: providerModel,
|
|
493
|
-
|
|
494
|
-
|
|
440
|
+
metadata: {
|
|
441
|
+
promptId,
|
|
442
|
+
outputNodeIds: uniqueStrings(outputFiles.map((entry) => entry.nodeId))
|
|
443
|
+
}
|
|
495
444
|
};
|
|
496
445
|
}
|
|
497
446
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/comfy-provider",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.10.1-beta.2",
|
|
4
4
|
"description": "OpenClaw ComfyUI provider plugin.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -18,10 +18,10 @@
|
|
|
18
18
|
"minHostVersion": ">=2026.7.2"
|
|
19
19
|
},
|
|
20
20
|
"compat": {
|
|
21
|
-
"pluginApi": ">=2026.
|
|
21
|
+
"pluginApi": ">=2026.10.1-beta.2"
|
|
22
22
|
},
|
|
23
23
|
"build": {
|
|
24
|
-
"openclawVersion": "2026.
|
|
24
|
+
"openclawVersion": "2026.10.1-beta.2",
|
|
25
25
|
"bundledDist": false
|
|
26
26
|
},
|
|
27
27
|
"release": {
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"assets/activity.svg"
|
|
41
41
|
],
|
|
42
42
|
"peerDependencies": {
|
|
43
|
-
"openclaw": ">=2026.
|
|
43
|
+
"openclaw": ">=2026.10.1-beta.2"
|
|
44
44
|
},
|
|
45
45
|
"peerDependenciesMeta": {
|
|
46
46
|
"openclaw": {
|