@openclaw/comfy-provider 2026.10.10-beta.5 → 2026.11.5-beta.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/.source-module-urls/af112485932ee2451e1e8378d37b2058d6bb057dc6d0fd0f3b04da8b7510d1be.json +1 -0
- package/dist/index.js +139 -8
- package/dist/workflow-http.js +37 -0
- package/dist/workflow-runtime.js +146 -164
- package/package.json +4 -4
- package/dist/.source-module-urls/4b1ba36595977164ac2d6a9d158e4c0c41b121101fe672ec3bebfedb8d939616.json +0 -1
- package/dist/image-generation-provider.js +0 -51
- package/dist/music-generation-provider.js +0 -59
- package/dist/video-generation-provider.js +0 -66
- package/dist/workflow-history.js +0 -14
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"entries":[{"output":"index.js","source":"extensions/comfy/index.ts"},{"output":"workflow-http.js","source":"extensions/comfy/workflow-http.ts"},{"output":"workflow-runtime.js","source":"extensions/comfy/workflow-runtime.ts"}],"chunks":[{"output":"index.js","sha256":"3c002a65c3746de9ad698833d7c10864b442c347a8243f313601c4ab2a99b312","facadeSource":"extensions/comfy/index.ts","sources":["extensions/comfy/index.ts"]},{"output":"workflow-http.js","sha256":"015e242a2a3104e250bd8d5886d125e1e3687025c24ca52f08fb0d27be0d89f3","facadeSource":"extensions/comfy/workflow-http.ts","sources":["extensions/comfy/workflow-http.ts"]},{"output":"workflow-runtime.js","sha256":"ef0378f760c826913c71d6eb0c98ed970f65dace640acc5f55fbcf8315fa95aa","facadeSource":"extensions/comfy/workflow-runtime.ts","sources":["extensions/comfy/workflow-runtime.ts"]}],"unrenderedSources":[]}
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,45 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { buildComfyMusicGenerationProvider } from "./music-generation-provider.js";
|
|
3
|
-
import { buildComfyVideoGenerationProvider } from "./video-generation-provider.js";
|
|
1
|
+
import { DEFAULT_COMFY_MODEL, isComfyCapabilityConfigured, isComfyCapabilityConfiguredAsync, runComfyWorkflow } from "./workflow-runtime.js";
|
|
4
2
|
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
|
5
3
|
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth-api-key";
|
|
6
|
-
|
|
4
|
+
// extensions/comfy/index.ts
|
|
7
5
|
const PROVIDER_ID = "comfy";
|
|
6
|
+
const COMFY_MAX_INPUT_IMAGES = 1;
|
|
7
|
+
function comfyProviderDefaults(capability) {
|
|
8
|
+
return {
|
|
9
|
+
id: PROVIDER_ID,
|
|
10
|
+
label: "ComfyUI",
|
|
11
|
+
defaultModel: DEFAULT_COMFY_MODEL,
|
|
12
|
+
models: [DEFAULT_COMFY_MODEL],
|
|
13
|
+
isConfigured: ({ cfg, agentDir }) => isComfyCapabilityConfigured({
|
|
14
|
+
cfg,
|
|
15
|
+
agentDir,
|
|
16
|
+
capability
|
|
17
|
+
}),
|
|
18
|
+
isConfiguredAsync: ({ cfg, agentDir }) => isComfyCapabilityConfiguredAsync({
|
|
19
|
+
cfg,
|
|
20
|
+
agentDir,
|
|
21
|
+
capability
|
|
22
|
+
})
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function resolveInputImage(inputImage) {
|
|
26
|
+
if (!inputImage) return;
|
|
27
|
+
if (!inputImage.buffer) throw new Error("Comfy music generation requires loaded reference image bytes.");
|
|
28
|
+
return {
|
|
29
|
+
buffer: inputImage.buffer,
|
|
30
|
+
mimeType: inputImage.mimeType ?? "image/png",
|
|
31
|
+
fileName: inputImage.fileName
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function toComfyInputImage(inputImage) {
|
|
35
|
+
if (!inputImage) return;
|
|
36
|
+
if (!inputImage.buffer || !inputImage.mimeType) throw new Error("Comfy video generation requires a local reference image file");
|
|
37
|
+
return {
|
|
38
|
+
buffer: inputImage.buffer,
|
|
39
|
+
mimeType: inputImage.mimeType,
|
|
40
|
+
fileName: inputImage.fileName
|
|
41
|
+
};
|
|
42
|
+
}
|
|
8
43
|
var comfy_default = definePluginEntry({
|
|
9
44
|
id: PROVIDER_ID,
|
|
10
45
|
name: "ComfyUI Provider",
|
|
@@ -35,10 +70,106 @@ var comfy_default = definePluginEntry({
|
|
|
35
70
|
}
|
|
36
71
|
})]
|
|
37
72
|
});
|
|
38
|
-
api.registerImageGenerationProvider(
|
|
39
|
-
|
|
40
|
-
|
|
73
|
+
api.registerImageGenerationProvider({
|
|
74
|
+
...comfyProviderDefaults("image"),
|
|
75
|
+
capabilities: {
|
|
76
|
+
generate: {
|
|
77
|
+
maxCount: 1,
|
|
78
|
+
supportsSize: false,
|
|
79
|
+
supportsAspectRatio: false,
|
|
80
|
+
supportsResolution: false
|
|
81
|
+
},
|
|
82
|
+
edit: {
|
|
83
|
+
enabled: true,
|
|
84
|
+
maxCount: 1,
|
|
85
|
+
maxInputImages: 1,
|
|
86
|
+
supportsSize: false,
|
|
87
|
+
supportsAspectRatio: false,
|
|
88
|
+
supportsResolution: false
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
async generateImage(req) {
|
|
92
|
+
if ((req.inputImages?.length ?? 0) > 1) throw new Error("Comfy image generation currently supports at most one reference image");
|
|
93
|
+
const result = await runComfyWorkflow({
|
|
94
|
+
...req,
|
|
95
|
+
capability: "image",
|
|
96
|
+
inputImage: req.inputImages?.[0]
|
|
97
|
+
});
|
|
98
|
+
return {
|
|
99
|
+
images: result.assets,
|
|
100
|
+
model: result.model,
|
|
101
|
+
metadata: result.metadata
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
api.registerMusicGenerationProvider({
|
|
106
|
+
...comfyProviderDefaults("music"),
|
|
107
|
+
capabilities: {
|
|
108
|
+
generate: {},
|
|
109
|
+
edit: {
|
|
110
|
+
enabled: true,
|
|
111
|
+
maxInputImages: COMFY_MAX_INPUT_IMAGES
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
async generateMusic(req) {
|
|
115
|
+
if ((req.inputImages?.length ?? 0) > COMFY_MAX_INPUT_IMAGES) throw new Error(`Comfy music generation supports at most ${COMFY_MAX_INPUT_IMAGES} reference image.`);
|
|
116
|
+
const result = await runComfyWorkflow({
|
|
117
|
+
...req,
|
|
118
|
+
capability: "music",
|
|
119
|
+
inputImage: resolveInputImage(req.inputImages?.[0])
|
|
120
|
+
});
|
|
121
|
+
return {
|
|
122
|
+
tracks: result.assets.map(({ buffer, mimeType, fileName }) => ({
|
|
123
|
+
buffer,
|
|
124
|
+
mimeType,
|
|
125
|
+
fileName
|
|
126
|
+
})),
|
|
127
|
+
model: result.model,
|
|
128
|
+
metadata: {
|
|
129
|
+
...result.metadata,
|
|
130
|
+
inputImageCount: req.inputImages?.length ?? 0
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
api.registerVideoGenerationProvider({
|
|
136
|
+
...comfyProviderDefaults("video"),
|
|
137
|
+
capabilities: {
|
|
138
|
+
generate: {
|
|
139
|
+
maxVideos: 1,
|
|
140
|
+
supportsSize: false,
|
|
141
|
+
supportsAspectRatio: false,
|
|
142
|
+
supportsResolution: false,
|
|
143
|
+
supportsAudio: false,
|
|
144
|
+
supportsWatermark: false
|
|
145
|
+
},
|
|
146
|
+
imageToVideo: {
|
|
147
|
+
enabled: true,
|
|
148
|
+
maxVideos: 1,
|
|
149
|
+
maxInputImages: 1,
|
|
150
|
+
supportsSize: false,
|
|
151
|
+
supportsAspectRatio: false,
|
|
152
|
+
supportsResolution: false,
|
|
153
|
+
supportsAudio: false,
|
|
154
|
+
supportsWatermark: false
|
|
155
|
+
},
|
|
156
|
+
videoToVideo: { enabled: false }
|
|
157
|
+
},
|
|
158
|
+
async generateVideo(req) {
|
|
159
|
+
if ((req.inputImages?.length ?? 0) > 1) throw new Error("Comfy video generation currently supports at most one reference image");
|
|
160
|
+
if ((req.inputVideos?.length ?? 0) > 0) throw new Error("Comfy video generation does not support input videos");
|
|
161
|
+
const result = await runComfyWorkflow({
|
|
162
|
+
...req,
|
|
163
|
+
capability: "video",
|
|
164
|
+
inputImage: toComfyInputImage(req.inputImages?.[0])
|
|
165
|
+
});
|
|
166
|
+
return {
|
|
167
|
+
videos: result.assets,
|
|
168
|
+
model: result.model,
|
|
169
|
+
metadata: result.metadata
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
});
|
|
41
173
|
}
|
|
42
174
|
});
|
|
43
|
-
//#endregion
|
|
44
175
|
export { comfy_default as default };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { resolvePositiveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
|
|
2
|
+
import { assertOkOrThrowHttpError, readProviderJsonResponse } from "openclaw/plugin-sdk/provider-http";
|
|
3
|
+
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
|
|
4
|
+
// extensions/comfy/workflow-http.ts
|
|
5
|
+
async function readJsonResponse(params) {
|
|
6
|
+
const { errorPrefix, deadline, timeoutMs, ...request } = params;
|
|
7
|
+
const remainingMs = () => resolveComfyRemainingMs(deadline, timeoutMs);
|
|
8
|
+
const { response, release } = await fetchWithSsrFGuard({
|
|
9
|
+
...request,
|
|
10
|
+
timeoutMs: remainingMs()
|
|
11
|
+
});
|
|
12
|
+
try {
|
|
13
|
+
const requestHeaders = params.init?.headers;
|
|
14
|
+
await assertOkOrThrowHttpError(response, errorPrefix, {
|
|
15
|
+
requestHeaders,
|
|
16
|
+
bodyTimeoutMs: remainingMs,
|
|
17
|
+
onBodyTimeout: () => createComfyTimeoutError(timeoutMs)
|
|
18
|
+
});
|
|
19
|
+
return await readProviderJsonResponse(response, errorPrefix, {
|
|
20
|
+
requestHeaders,
|
|
21
|
+
timeoutMs: remainingMs,
|
|
22
|
+
onTimeout: () => createComfyTimeoutError(timeoutMs)
|
|
23
|
+
});
|
|
24
|
+
} finally {
|
|
25
|
+
await release();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function createComfyTimeoutError(timeoutMs) {
|
|
29
|
+
return /* @__PURE__ */ new Error(`Comfy workflow did not finish within ${Math.ceil(timeoutMs / 1e3)}s`);
|
|
30
|
+
}
|
|
31
|
+
function resolveComfyRemainingMs(deadline, timeoutMs, defaultTimeoutMs = timeoutMs) {
|
|
32
|
+
const defaultMs = resolvePositiveTimerTimeoutMs(defaultTimeoutMs, 1);
|
|
33
|
+
const remainingMs = deadline - performance.now();
|
|
34
|
+
if (remainingMs <= 0) throw createComfyTimeoutError(timeoutMs);
|
|
35
|
+
return Math.max(1, Math.min(defaultMs, remainingMs));
|
|
36
|
+
}
|
|
37
|
+
export { createComfyTimeoutError, readJsonResponse, resolveComfyRemainingMs };
|
package/dist/workflow-runtime.js
CHANGED
|
@@ -1,19 +1,28 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createComfyTimeoutError, readJsonResponse, resolveComfyRemainingMs } from "./workflow-http.js";
|
|
2
2
|
import { randomInt } from "node:crypto";
|
|
3
3
|
import fs from "node:fs/promises";
|
|
4
4
|
import { bufferToBlobPart } from "openclaw/plugin-sdk/blob-runtime";
|
|
5
|
-
import { resolveGeneratedMediaMaxBytes } from "openclaw/plugin-sdk/media-generation-runtime";
|
|
5
|
+
import { resolveGeneratedImageDownloadMaxBytes, resolveGeneratedMediaMaxBytes } from "openclaw/plugin-sdk/media-generation-runtime";
|
|
6
6
|
import { extensionForMime } from "openclaw/plugin-sdk/media-mime";
|
|
7
7
|
import { resolvePositiveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
|
|
8
8
|
import { isProviderApiKeyConfigured, isProviderApiKeyConfiguredAsync } from "openclaw/plugin-sdk/provider-auth";
|
|
9
9
|
import { resolveApiKeyForProvider } from "openclaw/plugin-sdk/provider-auth-runtime";
|
|
10
|
-
import { assertOkOrThrowHttpError, readProviderBinaryResponse,
|
|
10
|
+
import { assertOkOrThrowHttpError, readProviderBinaryResponse, redactProviderResponseErrorText, resolveProviderHttpRequestConfig } from "openclaw/plugin-sdk/provider-http";
|
|
11
11
|
import { normalizeSecretInputString, resolveConfiguredSecretInputString, resolveSecretInputString } from "openclaw/plugin-sdk/secret-input-runtime";
|
|
12
12
|
import { canResolveEnvSecretRefInReadOnlyPath } from "openclaw/plugin-sdk/secret-ref-readonly";
|
|
13
13
|
import { fetchWithSsrFGuard, isPrivateOrLoopbackHost, mergeSsrFPolicies, ssrfPolicyFromHttpBaseUrlAllowedOrigin } from "openclaw/plugin-sdk/ssrf-runtime";
|
|
14
14
|
import { asBoolean, isRecord, normalizeOptionalLowercaseString, normalizeOptionalString, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
15
15
|
import { resolveUserPath, sleep } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
16
|
-
|
|
16
|
+
// extensions/comfy/workflow-runtime.ts
|
|
17
|
+
function isTerminalComfyHistory(entry, headers) {
|
|
18
|
+
if (entry.status?.status_str === "error") {
|
|
19
|
+
const messages = entry.status.messages;
|
|
20
|
+
const errorDetails = (Array.isArray(messages) ? messages.findLast((message) => Array.isArray(message) && message[0] === "execution_error") : void 0)?.[1];
|
|
21
|
+
const detail = isRecord(errorDetails) ? normalizeOptionalString(errorDetails.exception_message) : void 0;
|
|
22
|
+
throw new Error(`Comfy workflow failed${detail ? `: ${redactProviderResponseErrorText(detail, headers)}` : ""}`);
|
|
23
|
+
}
|
|
24
|
+
return entry.status?.completed === true || Boolean(entry.outputs && Object.keys(entry.outputs).length);
|
|
25
|
+
}
|
|
17
26
|
const DEFAULT_COMFY_LOCAL_BASE_URL = "http://127.0.0.1:8188";
|
|
18
27
|
const DEFAULT_COMFY_CLOUD_BASE_URL = "https://cloud.comfy.org";
|
|
19
28
|
const DEFAULT_POLL_INTERVAL_MS = 1500;
|
|
@@ -131,40 +140,6 @@ function resolveComfyNetworkPolicy(params) {
|
|
|
131
140
|
function isSingleLabelServiceHostname(hostname) {
|
|
132
141
|
return /^[a-z0-9_](?:[a-z0-9_-]{0,61}[a-z0-9_])?$/u.test(hostname);
|
|
133
142
|
}
|
|
134
|
-
async function readJsonResponse(params) {
|
|
135
|
-
const { errorPrefix, ...request } = params;
|
|
136
|
-
const { response, release } = await fetchWithSsrFGuard(request);
|
|
137
|
-
try {
|
|
138
|
-
const requestHeaders = params.init?.headers;
|
|
139
|
-
await assertOkOrThrowHttpError(response, errorPrefix, { requestHeaders });
|
|
140
|
-
return await readProviderJsonResponse(response, errorPrefix, { requestHeaders });
|
|
141
|
-
} finally {
|
|
142
|
-
await release();
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
async function uploadInputImage(connection, image, capability) {
|
|
146
|
-
const { baseUrl, mode, headers: requestHeaders, ...request } = connection;
|
|
147
|
-
const form = new FormData();
|
|
148
|
-
form.set("image", new Blob([bufferToBlobPart(image.buffer)], { type: image.mimeType }), normalizeOptionalString(image.fileName) || `input.${extensionForMime(image.mimeType)?.slice(1) || "bin"}`);
|
|
149
|
-
form.set("type", "input");
|
|
150
|
-
form.set("overwrite", "true");
|
|
151
|
-
const headers = new Headers(requestHeaders);
|
|
152
|
-
headers.delete("Content-Type");
|
|
153
|
-
const payload = await readJsonResponse({
|
|
154
|
-
...request,
|
|
155
|
-
url: `${baseUrl}${mode === "cloud" ? "/api/upload/image" : "/upload/image"}`,
|
|
156
|
-
init: {
|
|
157
|
-
method: "POST",
|
|
158
|
-
headers,
|
|
159
|
-
body: form
|
|
160
|
-
},
|
|
161
|
-
auditContext: `comfy-${capability}-upload`,
|
|
162
|
-
errorPrefix: "Comfy image upload failed"
|
|
163
|
-
});
|
|
164
|
-
const uploadedName = normalizeOptionalString(payload.filename) || normalizeOptionalString(payload.name);
|
|
165
|
-
if (!uploadedName) throw new Error("Comfy image upload response missing filename");
|
|
166
|
-
return uploadedName;
|
|
167
|
-
}
|
|
168
143
|
function extractHistoryEntry(history, promptId) {
|
|
169
144
|
if (!isRecord(history)) return null;
|
|
170
145
|
const directOutputs = history.outputs;
|
|
@@ -173,108 +148,6 @@ function extractHistoryEntry(history, promptId) {
|
|
|
173
148
|
if (isRecord(nested)) return nested;
|
|
174
149
|
return null;
|
|
175
150
|
}
|
|
176
|
-
async function waitForComfyHistory(params, promptId, pollIntervalMs) {
|
|
177
|
-
const { baseUrl, headers: requestHeaders, mode, ...request } = params;
|
|
178
|
-
const headers = new Headers(requestHeaders);
|
|
179
|
-
const deadline = performance.now() + params.timeoutMs;
|
|
180
|
-
const read = (path, kind, timeoutMs) => readJsonResponse({
|
|
181
|
-
...request,
|
|
182
|
-
url: `${baseUrl}${path}`,
|
|
183
|
-
init: {
|
|
184
|
-
method: "GET",
|
|
185
|
-
headers
|
|
186
|
-
},
|
|
187
|
-
timeoutMs,
|
|
188
|
-
auditContext: `comfy-${kind}`,
|
|
189
|
-
errorPrefix: `Comfy ${kind} lookup failed`
|
|
190
|
-
});
|
|
191
|
-
for (;;) {
|
|
192
|
-
const requestTimeoutMs = resolveComfyRemainingMs(deadline, params.timeoutMs);
|
|
193
|
-
if (mode === "cloud") {
|
|
194
|
-
const status = await read(`/api/job/${promptId}/status`, "status", requestTimeoutMs);
|
|
195
|
-
if (status.status === "completed") return await read(`/api/history_v2/${promptId}`, "history", params.timeoutMs);
|
|
196
|
-
if (status.status === "failed" || status.status === "cancelled") {
|
|
197
|
-
const detail = redactProviderResponseErrorText(status.error ?? status.message ?? promptId, headers);
|
|
198
|
-
throw new Error(`Comfy workflow ${status.status}: ${detail}`);
|
|
199
|
-
}
|
|
200
|
-
} else {
|
|
201
|
-
const entry = extractHistoryEntry(await read(`/history/${promptId}`, "history", requestTimeoutMs), promptId);
|
|
202
|
-
if (entry && isTerminalComfyHistory(entry, params.headers)) return entry;
|
|
203
|
-
}
|
|
204
|
-
const pollDelayMs = resolveComfyRemainingMs(deadline, params.timeoutMs, pollIntervalMs);
|
|
205
|
-
await sleep(pollDelayMs);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
function resolveComfyRemainingMs(deadline, timeoutMs, defaultTimeoutMs = timeoutMs) {
|
|
209
|
-
const defaultMs = resolvePositiveTimerTimeoutMs(defaultTimeoutMs, 1);
|
|
210
|
-
const remainingMs = deadline - performance.now();
|
|
211
|
-
if (remainingMs <= 0) throw new Error(`Comfy workflow did not finish within ${Math.ceil(timeoutMs / 1e3)}s`);
|
|
212
|
-
return Math.max(1, Math.min(defaultMs, remainingMs));
|
|
213
|
-
}
|
|
214
|
-
function collectOutputFiles(params) {
|
|
215
|
-
const outputs = params.history.outputs;
|
|
216
|
-
if (!outputs) return [];
|
|
217
|
-
const nodeIds = params.outputNodeId ? [params.outputNodeId] : Object.keys(outputs);
|
|
218
|
-
const files = [];
|
|
219
|
-
for (const nodeId of nodeIds) {
|
|
220
|
-
const entry = outputs[nodeId];
|
|
221
|
-
if (!entry) continue;
|
|
222
|
-
for (const kind of params.outputKinds) {
|
|
223
|
-
const bucket = entry[kind];
|
|
224
|
-
if (!Array.isArray(bucket)) continue;
|
|
225
|
-
for (const file of bucket) {
|
|
226
|
-
if (params.capability === "video" && kind === "images") {
|
|
227
|
-
const fileName = normalizeOptionalString(file.filename) || normalizeOptionalString(file.name);
|
|
228
|
-
if (!fileName || !/\.(?:mp4|webm)$/i.test(fileName)) continue;
|
|
229
|
-
}
|
|
230
|
-
files.push({
|
|
231
|
-
nodeId,
|
|
232
|
-
file
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
return files;
|
|
238
|
-
}
|
|
239
|
-
async function downloadOutputFile(params, file, capability, maxBytes) {
|
|
240
|
-
const { baseUrl, headers: requestHeaders, mode, ...request } = params;
|
|
241
|
-
const headers = new Headers(requestHeaders);
|
|
242
|
-
const fileName = normalizeOptionalString(file.filename) || normalizeOptionalString(file.name);
|
|
243
|
-
if (!fileName) throw new Error("Comfy output entry missing filename");
|
|
244
|
-
const query = new URLSearchParams({
|
|
245
|
-
filename: fileName,
|
|
246
|
-
subfolder: normalizeOptionalString(file.subfolder) ?? "",
|
|
247
|
-
type: normalizeOptionalString(file.type) ?? "output"
|
|
248
|
-
});
|
|
249
|
-
const viewPath = mode === "cloud" ? "/api/view" : "/view";
|
|
250
|
-
const auditContext = `comfy-${capability}-download`;
|
|
251
|
-
const firstResponse = await fetchWithSsrFGuard({
|
|
252
|
-
...request,
|
|
253
|
-
url: `${baseUrl}${viewPath}?${query.toString()}`,
|
|
254
|
-
init: {
|
|
255
|
-
method: "GET",
|
|
256
|
-
headers
|
|
257
|
-
},
|
|
258
|
-
auditContext
|
|
259
|
-
});
|
|
260
|
-
try {
|
|
261
|
-
await assertOkOrThrowHttpError(firstResponse.response, "Comfy output download failed", { requestHeaders: headers });
|
|
262
|
-
const mimeType = normalizeOptionalString(firstResponse.response.headers.get("content-type")) || "application/octet-stream";
|
|
263
|
-
const downloadLabel = `Comfy ${capability} output download`;
|
|
264
|
-
return {
|
|
265
|
-
buffer: await readProviderBinaryResponse(firstResponse.response, downloadLabel, capability, {
|
|
266
|
-
maxBytes,
|
|
267
|
-
chunkTimeoutMs: params.timeoutMs,
|
|
268
|
-
onOverflow: ({ maxBytes: limit }) => /* @__PURE__ */ new Error(`${downloadLabel} exceeds ${limit} bytes`),
|
|
269
|
-
onIdleTimeout: ({ chunkTimeoutMs }) => /* @__PURE__ */ new Error(`${downloadLabel} stalled after ${chunkTimeoutMs}ms`)
|
|
270
|
-
}),
|
|
271
|
-
mimeType,
|
|
272
|
-
fileName
|
|
273
|
-
};
|
|
274
|
-
} finally {
|
|
275
|
-
await firstResponse.release();
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
151
|
function hasUnavailableComfyHeaderSecret(value, cfg) {
|
|
279
152
|
if (!isRecord(value)) return false;
|
|
280
153
|
return Object.entries(value).some(([name, headerValue]) => {
|
|
@@ -334,7 +207,7 @@ async function runComfyWorkflow(params) {
|
|
|
334
207
|
const seedInputName = normalizeOptionalString(capabilityConfig.seedInputName) ?? "seed";
|
|
335
208
|
const outputNodeId = normalizeOptionalString(capabilityConfig.outputNodeId);
|
|
336
209
|
const pollIntervalMs = resolvePositiveTimerTimeoutMs(readConfigInteger(capabilityConfig, "pollIntervalMs"), DEFAULT_POLL_INTERVAL_MS);
|
|
337
|
-
const timeoutMs = resolvePositiveTimerTimeoutMs(readConfigInteger(capabilityConfig, "timeoutMs")
|
|
210
|
+
const timeoutMs = resolvePositiveTimerTimeoutMs(params.timeoutMs ?? readConfigInteger(capabilityConfig, "timeoutMs"), DEFAULT_TIMEOUT_MS);
|
|
338
211
|
const providerModel = normalizeOptionalString(params.model) || "workflow";
|
|
339
212
|
setWorkflowInput(workflow, promptNodeId, promptInputName, params.prompt);
|
|
340
213
|
if (seedNodeId) setWorkflowInput(workflow, seedNodeId, seedInputName, randomInt(RANDOM_SEED_EXCLUSIVE_MAX));
|
|
@@ -366,17 +239,33 @@ async function runComfyWorkflow(params) {
|
|
|
366
239
|
explicitAllowPrivateNetwork,
|
|
367
240
|
mode
|
|
368
241
|
});
|
|
369
|
-
const
|
|
370
|
-
|
|
371
|
-
headers,
|
|
372
|
-
timeoutMs,
|
|
373
|
-
policy: networkPolicy,
|
|
374
|
-
dispatcherPolicy,
|
|
375
|
-
mode
|
|
376
|
-
};
|
|
242
|
+
const deadline = performance.now() + timeoutMs;
|
|
243
|
+
const remainingMs = () => resolveComfyRemainingMs(deadline, timeoutMs);
|
|
377
244
|
if (params.inputImage) {
|
|
378
245
|
if (!inputImageNodeId) throw new Error("Comfy edit requests require plugins.entries.comfy.config.<capability>.inputImageNodeId to be configured");
|
|
379
|
-
|
|
246
|
+
const form = new FormData();
|
|
247
|
+
form.set("image", new Blob([bufferToBlobPart(params.inputImage.buffer)], { type: params.inputImage.mimeType }), normalizeOptionalString(params.inputImage.fileName) || `input.${extensionForMime(params.inputImage.mimeType)?.slice(1) || "bin"}`);
|
|
248
|
+
form.set("type", "input");
|
|
249
|
+
form.set("overwrite", "true");
|
|
250
|
+
const uploadHeaders = new Headers(headers);
|
|
251
|
+
uploadHeaders.delete("Content-Type");
|
|
252
|
+
const payload = await readJsonResponse({
|
|
253
|
+
deadline,
|
|
254
|
+
timeoutMs,
|
|
255
|
+
policy: networkPolicy,
|
|
256
|
+
dispatcherPolicy,
|
|
257
|
+
url: `${baseUrl}${mode === "cloud" ? "/api/upload/image" : "/upload/image"}`,
|
|
258
|
+
init: {
|
|
259
|
+
method: "POST",
|
|
260
|
+
headers: uploadHeaders,
|
|
261
|
+
body: form
|
|
262
|
+
},
|
|
263
|
+
auditContext: `comfy-${params.capability}-upload`,
|
|
264
|
+
errorPrefix: "Comfy image upload failed"
|
|
265
|
+
});
|
|
266
|
+
const uploadedName = normalizeOptionalString(payload.filename) || normalizeOptionalString(payload.name);
|
|
267
|
+
if (!uploadedName) throw new Error("Comfy image upload response missing filename");
|
|
268
|
+
setWorkflowInput(workflow, inputImageNodeId, inputImageInputName, uploadedName);
|
|
380
269
|
}
|
|
381
270
|
const submitPayload = {
|
|
382
271
|
prompt: workflow,
|
|
@@ -389,6 +278,7 @@ async function runComfyWorkflow(params) {
|
|
|
389
278
|
headers,
|
|
390
279
|
body: JSON.stringify(submitPayload)
|
|
391
280
|
},
|
|
281
|
+
deadline,
|
|
392
282
|
timeoutMs,
|
|
393
283
|
policy: networkPolicy,
|
|
394
284
|
dispatcherPolicy,
|
|
@@ -397,24 +287,117 @@ async function runComfyWorkflow(params) {
|
|
|
397
287
|
});
|
|
398
288
|
const promptId = normalizeOptionalString(promptResponse.prompt_id);
|
|
399
289
|
if (!promptId) throw new Error("Comfy workflow submit response missing prompt_id");
|
|
400
|
-
const
|
|
290
|
+
const waitForHistory = async () => {
|
|
291
|
+
const read = (path, kind) => readJsonResponse({
|
|
292
|
+
policy: networkPolicy,
|
|
293
|
+
dispatcherPolicy,
|
|
294
|
+
url: `${baseUrl}${path}`,
|
|
295
|
+
init: {
|
|
296
|
+
method: "GET",
|
|
297
|
+
headers
|
|
298
|
+
},
|
|
299
|
+
deadline,
|
|
300
|
+
timeoutMs,
|
|
301
|
+
auditContext: `comfy-${kind}`,
|
|
302
|
+
errorPrefix: `Comfy ${kind} lookup failed`
|
|
303
|
+
});
|
|
304
|
+
for (;;) {
|
|
305
|
+
if (mode === "cloud") {
|
|
306
|
+
const status = await read(`/api/job/${promptId}/status`, "status");
|
|
307
|
+
if (status.status === "completed") return await read(`/api/history_v2/${promptId}`, "history");
|
|
308
|
+
if (status.status === "failed" || status.status === "cancelled") {
|
|
309
|
+
const detail = redactProviderResponseErrorText(status.error ?? status.message ?? promptId, headers);
|
|
310
|
+
throw new Error(`Comfy workflow ${status.status}: ${detail}`);
|
|
311
|
+
}
|
|
312
|
+
} else {
|
|
313
|
+
const entry = extractHistoryEntry(await read(`/history/${promptId}`, "history"), promptId);
|
|
314
|
+
if (entry && isTerminalComfyHistory(entry, headers)) return entry;
|
|
315
|
+
}
|
|
316
|
+
const pollDelayMs = resolveComfyRemainingMs(deadline, timeoutMs, pollIntervalMs);
|
|
317
|
+
await sleep(pollDelayMs);
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
const historyEntry = extractHistoryEntry(await waitForHistory(), promptId);
|
|
401
321
|
if (!historyEntry) throw new Error(`Comfy history response missing outputs for prompt ${promptId}`);
|
|
402
|
-
const
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
322
|
+
const outputKinds = params.capability === "music" ? ["audio"] : params.capability === "video" ? [
|
|
323
|
+
"images",
|
|
324
|
+
"gifs",
|
|
325
|
+
"videos"
|
|
326
|
+
] : ["images"];
|
|
327
|
+
const outputFiles = [];
|
|
328
|
+
const outputs = historyEntry.outputs;
|
|
329
|
+
if (outputs) {
|
|
330
|
+
const nodeIds = outputNodeId ? [outputNodeId] : Object.keys(outputs);
|
|
331
|
+
for (const nodeId of nodeIds) {
|
|
332
|
+
const entry = outputs[nodeId];
|
|
333
|
+
if (!entry) continue;
|
|
334
|
+
for (const kind of outputKinds) {
|
|
335
|
+
const bucket = entry[kind];
|
|
336
|
+
if (!Array.isArray(bucket)) continue;
|
|
337
|
+
for (const file of bucket) {
|
|
338
|
+
if (params.capability === "video" && kind === "images") {
|
|
339
|
+
const fileName = normalizeOptionalString(file.filename) || normalizeOptionalString(file.name);
|
|
340
|
+
if (!fileName || !/\.(?:mp4|webm)$/i.test(fileName)) continue;
|
|
341
|
+
}
|
|
342
|
+
outputFiles.push({
|
|
343
|
+
nodeId,
|
|
344
|
+
file
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
412
350
|
if (outputFiles.length === 0) throw new Error(`Comfy workflow ${promptId} completed without ${params.capability} outputs`);
|
|
351
|
+
const downloadOutputFile = async (file, maxBytes) => {
|
|
352
|
+
const fileName = normalizeOptionalString(file.filename) || normalizeOptionalString(file.name);
|
|
353
|
+
if (!fileName) throw new Error("Comfy output entry missing filename");
|
|
354
|
+
const query = new URLSearchParams({
|
|
355
|
+
filename: fileName,
|
|
356
|
+
subfolder: normalizeOptionalString(file.subfolder) ?? "",
|
|
357
|
+
type: normalizeOptionalString(file.type) ?? "output"
|
|
358
|
+
});
|
|
359
|
+
const viewPath = mode === "cloud" ? "/api/view" : "/view";
|
|
360
|
+
const auditContext = `comfy-${params.capability}-download`;
|
|
361
|
+
const firstResponse = await fetchWithSsrFGuard({
|
|
362
|
+
timeoutMs: remainingMs(),
|
|
363
|
+
policy: networkPolicy,
|
|
364
|
+
dispatcherPolicy,
|
|
365
|
+
url: `${baseUrl}${viewPath}?${query.toString()}`,
|
|
366
|
+
init: {
|
|
367
|
+
method: "GET",
|
|
368
|
+
headers
|
|
369
|
+
},
|
|
370
|
+
auditContext
|
|
371
|
+
});
|
|
372
|
+
try {
|
|
373
|
+
await assertOkOrThrowHttpError(firstResponse.response, "Comfy output download failed", {
|
|
374
|
+
requestHeaders: headers,
|
|
375
|
+
bodyTimeoutMs: remainingMs,
|
|
376
|
+
onBodyTimeout: () => createComfyTimeoutError(timeoutMs)
|
|
377
|
+
});
|
|
378
|
+
const mimeType = normalizeOptionalString(firstResponse.response.headers.get("content-type")) || "application/octet-stream";
|
|
379
|
+
const downloadLabel = `Comfy ${params.capability} output download`;
|
|
380
|
+
return {
|
|
381
|
+
buffer: await readProviderBinaryResponse(firstResponse.response, downloadLabel, params.capability, {
|
|
382
|
+
maxBytes,
|
|
383
|
+
chunkTimeoutMs: remainingMs(),
|
|
384
|
+
timeoutMs: remainingMs,
|
|
385
|
+
onTimeout: () => createComfyTimeoutError(timeoutMs),
|
|
386
|
+
onOverflow: ({ maxBytes: limit }) => /* @__PURE__ */ new Error(`${downloadLabel} exceeds ${limit} bytes`),
|
|
387
|
+
onIdleTimeout: ({ chunkTimeoutMs }) => /* @__PURE__ */ new Error(`${downloadLabel} stalled after ${chunkTimeoutMs}ms`)
|
|
388
|
+
}),
|
|
389
|
+
mimeType,
|
|
390
|
+
fileName
|
|
391
|
+
};
|
|
392
|
+
} finally {
|
|
393
|
+
await firstResponse.release();
|
|
394
|
+
}
|
|
395
|
+
};
|
|
413
396
|
const assets = [];
|
|
414
397
|
const outputKind = params.capability === "music" ? "audio" : params.capability;
|
|
415
|
-
const maxOutputBytes = resolveGeneratedMediaMaxBytes(params.cfg, outputKind);
|
|
398
|
+
const maxOutputBytes = outputKind === "image" ? resolveGeneratedImageDownloadMaxBytes(params.cfg) : resolveGeneratedMediaMaxBytes(params.cfg, outputKind);
|
|
416
399
|
for (const output of outputFiles) {
|
|
417
|
-
const downloaded = await downloadOutputFile(
|
|
400
|
+
const downloaded = await downloadOutputFile(output.file, maxOutputBytes);
|
|
418
401
|
assets.push({
|
|
419
402
|
...downloaded,
|
|
420
403
|
metadata: {
|
|
@@ -432,5 +415,4 @@ async function runComfyWorkflow(params) {
|
|
|
432
415
|
}
|
|
433
416
|
};
|
|
434
417
|
}
|
|
435
|
-
//#endregion
|
|
436
418
|
export { DEFAULT_COMFY_MODEL, isComfyCapabilityConfigured, isComfyCapabilityConfiguredAsync, runComfyWorkflow };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/comfy-provider",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.11.5-beta.1",
|
|
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.11.5-beta.1"
|
|
22
22
|
},
|
|
23
23
|
"build": {
|
|
24
|
-
"openclawVersion": "2026.
|
|
24
|
+
"openclawVersion": "2026.11.5-beta.1",
|
|
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.11.5-beta.1"
|
|
44
44
|
},
|
|
45
45
|
"peerDependenciesMeta": {
|
|
46
46
|
"openclaw": {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"entries":[{"output":"image-generation-provider.js","source":"extensions/comfy/image-generation-provider.ts"},{"output":"index.js","source":"extensions/comfy/index.ts"},{"output":"music-generation-provider.js","source":"extensions/comfy/music-generation-provider.ts"},{"output":"video-generation-provider.js","source":"extensions/comfy/video-generation-provider.ts"},{"output":"workflow-history.js","source":"extensions/comfy/workflow-history.ts"},{"output":"workflow-runtime.js","source":"extensions/comfy/workflow-runtime.ts"}],"chunks":[{"output":"image-generation-provider.js","sha256":"3da76f38919c27354d0ff06ab115f714c89d7513511678e9b5fbff286f80db38","facadeSource":"extensions/comfy/image-generation-provider.ts","sources":["extensions/comfy/image-generation-provider.ts"]},{"output":"index.js","sha256":"d418fc284f3dd877b213a2b23d07a3a7a828d32bb0a3e1e8ab60681188ddb425","facadeSource":"extensions/comfy/index.ts","sources":["extensions/comfy/index.ts"]},{"output":"music-generation-provider.js","sha256":"2a6e898dd7f5dfb86566dee5ce5c42f5a74d74229b2b143e04a4c8a809d82fe4","facadeSource":"extensions/comfy/music-generation-provider.ts","sources":["extensions/comfy/music-generation-provider.ts"]},{"output":"video-generation-provider.js","sha256":"886e48db912d349c7d74ee94d3d042e49c8efbfea50c54c5a4d3d872d30f2aa1","facadeSource":"extensions/comfy/video-generation-provider.ts","sources":["extensions/comfy/video-generation-provider.ts"]},{"output":"workflow-history.js","sha256":"2578f80a7235d612ad2482c48070e5276126bdec7dbb3d59585539bb4b295d75","facadeSource":"extensions/comfy/workflow-history.ts","sources":["extensions/comfy/workflow-history.ts"]},{"output":"workflow-runtime.js","sha256":"3a2444506934e5458f45d6342fc387397750f0500dad8ec09f693a574da7a92a","facadeSource":"extensions/comfy/workflow-runtime.ts","sources":["extensions/comfy/workflow-runtime.ts"]}],"unrenderedSources":[]}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { DEFAULT_COMFY_MODEL, isComfyCapabilityConfigured, isComfyCapabilityConfiguredAsync, runComfyWorkflow } from "./workflow-runtime.js";
|
|
2
|
-
//#region extensions/comfy/image-generation-provider.ts
|
|
3
|
-
function buildComfyImageGenerationProvider() {
|
|
4
|
-
return {
|
|
5
|
-
id: "comfy",
|
|
6
|
-
label: "ComfyUI",
|
|
7
|
-
defaultModel: DEFAULT_COMFY_MODEL,
|
|
8
|
-
models: [DEFAULT_COMFY_MODEL],
|
|
9
|
-
isConfigured: ({ cfg, agentDir }) => isComfyCapabilityConfigured({
|
|
10
|
-
cfg,
|
|
11
|
-
agentDir,
|
|
12
|
-
capability: "image"
|
|
13
|
-
}),
|
|
14
|
-
isConfiguredAsync: ({ cfg, agentDir }) => isComfyCapabilityConfiguredAsync({
|
|
15
|
-
cfg,
|
|
16
|
-
agentDir,
|
|
17
|
-
capability: "image"
|
|
18
|
-
}),
|
|
19
|
-
capabilities: {
|
|
20
|
-
generate: {
|
|
21
|
-
maxCount: 1,
|
|
22
|
-
supportsSize: false,
|
|
23
|
-
supportsAspectRatio: false,
|
|
24
|
-
supportsResolution: false
|
|
25
|
-
},
|
|
26
|
-
edit: {
|
|
27
|
-
enabled: true,
|
|
28
|
-
maxCount: 1,
|
|
29
|
-
maxInputImages: 1,
|
|
30
|
-
supportsSize: false,
|
|
31
|
-
supportsAspectRatio: false,
|
|
32
|
-
supportsResolution: false
|
|
33
|
-
}
|
|
34
|
-
},
|
|
35
|
-
async generateImage(req) {
|
|
36
|
-
if ((req.inputImages?.length ?? 0) > 1) throw new Error("Comfy image generation currently supports at most one reference image");
|
|
37
|
-
const result = await runComfyWorkflow({
|
|
38
|
-
...req,
|
|
39
|
-
capability: "image",
|
|
40
|
-
inputImage: req.inputImages?.[0]
|
|
41
|
-
});
|
|
42
|
-
return {
|
|
43
|
-
images: result.assets,
|
|
44
|
-
model: result.model,
|
|
45
|
-
metadata: result.metadata
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
//#endregion
|
|
51
|
-
export { buildComfyImageGenerationProvider };
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { DEFAULT_COMFY_MODEL, isComfyCapabilityConfigured, isComfyCapabilityConfiguredAsync, runComfyWorkflow } from "./workflow-runtime.js";
|
|
2
|
-
//#region extensions/comfy/music-generation-provider.ts
|
|
3
|
-
const COMFY_MAX_INPUT_IMAGES = 1;
|
|
4
|
-
function resolveInputImage(inputImage) {
|
|
5
|
-
if (!inputImage) return;
|
|
6
|
-
if (!inputImage.buffer) throw new Error("Comfy music generation requires loaded reference image bytes.");
|
|
7
|
-
return {
|
|
8
|
-
buffer: inputImage.buffer,
|
|
9
|
-
mimeType: inputImage.mimeType ?? "image/png",
|
|
10
|
-
fileName: inputImage.fileName
|
|
11
|
-
};
|
|
12
|
-
}
|
|
13
|
-
function buildComfyMusicGenerationProvider() {
|
|
14
|
-
return {
|
|
15
|
-
id: "comfy",
|
|
16
|
-
label: "ComfyUI",
|
|
17
|
-
defaultModel: DEFAULT_COMFY_MODEL,
|
|
18
|
-
models: [DEFAULT_COMFY_MODEL],
|
|
19
|
-
isConfigured: ({ cfg, agentDir }) => isComfyCapabilityConfigured({
|
|
20
|
-
cfg,
|
|
21
|
-
agentDir,
|
|
22
|
-
capability: "music"
|
|
23
|
-
}),
|
|
24
|
-
isConfiguredAsync: ({ cfg, agentDir }) => isComfyCapabilityConfiguredAsync({
|
|
25
|
-
cfg,
|
|
26
|
-
agentDir,
|
|
27
|
-
capability: "music"
|
|
28
|
-
}),
|
|
29
|
-
capabilities: {
|
|
30
|
-
generate: {},
|
|
31
|
-
edit: {
|
|
32
|
-
enabled: true,
|
|
33
|
-
maxInputImages: COMFY_MAX_INPUT_IMAGES
|
|
34
|
-
}
|
|
35
|
-
},
|
|
36
|
-
async generateMusic(req) {
|
|
37
|
-
if ((req.inputImages?.length ?? 0) > COMFY_MAX_INPUT_IMAGES) throw new Error(`Comfy music generation supports at most ${COMFY_MAX_INPUT_IMAGES} reference image.`);
|
|
38
|
-
const result = await runComfyWorkflow({
|
|
39
|
-
...req,
|
|
40
|
-
capability: "music",
|
|
41
|
-
inputImage: resolveInputImage(req.inputImages?.[0])
|
|
42
|
-
});
|
|
43
|
-
return {
|
|
44
|
-
tracks: result.assets.map(({ buffer, mimeType, fileName }) => ({
|
|
45
|
-
buffer,
|
|
46
|
-
mimeType,
|
|
47
|
-
fileName
|
|
48
|
-
})),
|
|
49
|
-
model: result.model,
|
|
50
|
-
metadata: {
|
|
51
|
-
...result.metadata,
|
|
52
|
-
inputImageCount: req.inputImages?.length ?? 0
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
//#endregion
|
|
59
|
-
export { buildComfyMusicGenerationProvider };
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { DEFAULT_COMFY_MODEL, isComfyCapabilityConfigured, isComfyCapabilityConfiguredAsync, runComfyWorkflow } from "./workflow-runtime.js";
|
|
2
|
-
//#region extensions/comfy/video-generation-provider.ts
|
|
3
|
-
function toComfyInputImage(inputImage) {
|
|
4
|
-
if (!inputImage) return;
|
|
5
|
-
if (!inputImage.buffer || !inputImage.mimeType) throw new Error("Comfy video generation requires a local reference image file");
|
|
6
|
-
return {
|
|
7
|
-
buffer: inputImage.buffer,
|
|
8
|
-
mimeType: inputImage.mimeType,
|
|
9
|
-
fileName: inputImage.fileName
|
|
10
|
-
};
|
|
11
|
-
}
|
|
12
|
-
function buildComfyVideoGenerationProvider() {
|
|
13
|
-
return {
|
|
14
|
-
id: "comfy",
|
|
15
|
-
label: "ComfyUI",
|
|
16
|
-
defaultModel: DEFAULT_COMFY_MODEL,
|
|
17
|
-
models: [DEFAULT_COMFY_MODEL],
|
|
18
|
-
isConfigured: ({ cfg, agentDir }) => isComfyCapabilityConfigured({
|
|
19
|
-
cfg,
|
|
20
|
-
agentDir,
|
|
21
|
-
capability: "video"
|
|
22
|
-
}),
|
|
23
|
-
isConfiguredAsync: ({ cfg, agentDir }) => isComfyCapabilityConfiguredAsync({
|
|
24
|
-
cfg,
|
|
25
|
-
agentDir,
|
|
26
|
-
capability: "video"
|
|
27
|
-
}),
|
|
28
|
-
capabilities: {
|
|
29
|
-
generate: {
|
|
30
|
-
maxVideos: 1,
|
|
31
|
-
supportsSize: false,
|
|
32
|
-
supportsAspectRatio: false,
|
|
33
|
-
supportsResolution: false,
|
|
34
|
-
supportsAudio: false,
|
|
35
|
-
supportsWatermark: false
|
|
36
|
-
},
|
|
37
|
-
imageToVideo: {
|
|
38
|
-
enabled: true,
|
|
39
|
-
maxVideos: 1,
|
|
40
|
-
maxInputImages: 1,
|
|
41
|
-
supportsSize: false,
|
|
42
|
-
supportsAspectRatio: false,
|
|
43
|
-
supportsResolution: false,
|
|
44
|
-
supportsAudio: false,
|
|
45
|
-
supportsWatermark: false
|
|
46
|
-
},
|
|
47
|
-
videoToVideo: { enabled: false }
|
|
48
|
-
},
|
|
49
|
-
async generateVideo(req) {
|
|
50
|
-
if ((req.inputImages?.length ?? 0) > 1) throw new Error("Comfy video generation currently supports at most one reference image");
|
|
51
|
-
if ((req.inputVideos?.length ?? 0) > 0) throw new Error("Comfy video generation does not support input videos");
|
|
52
|
-
const result = await runComfyWorkflow({
|
|
53
|
-
...req,
|
|
54
|
-
capability: "video",
|
|
55
|
-
inputImage: toComfyInputImage(req.inputImages?.[0])
|
|
56
|
-
});
|
|
57
|
-
return {
|
|
58
|
-
videos: result.assets,
|
|
59
|
-
model: result.model,
|
|
60
|
-
metadata: result.metadata
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
//#endregion
|
|
66
|
-
export { buildComfyVideoGenerationProvider };
|
package/dist/workflow-history.js
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { redactProviderResponseErrorText } from "openclaw/plugin-sdk/provider-http";
|
|
2
|
-
import { isRecord, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
3
|
-
//#region extensions/comfy/workflow-history.ts
|
|
4
|
-
function isTerminalComfyHistory(entry, headers) {
|
|
5
|
-
if (entry.status?.status_str === "error") {
|
|
6
|
-
const messages = entry.status.messages;
|
|
7
|
-
const executionError = Array.isArray(messages) ? messages.findLast((message) => Array.isArray(message) && message[0] === "execution_error") : void 0;
|
|
8
|
-
const detail = Array.isArray(executionError) && isRecord(executionError[1]) ? normalizeOptionalString(executionError[1].exception_message) : void 0;
|
|
9
|
-
throw new Error(`Comfy workflow failed${detail ? `: ${redactProviderResponseErrorText(detail, headers)}` : ""}`);
|
|
10
|
-
}
|
|
11
|
-
return entry.status?.completed === true || Boolean(entry.outputs && Object.keys(entry.outputs).length);
|
|
12
|
-
}
|
|
13
|
-
//#endregion
|
|
14
|
-
export { isTerminalComfyHistory };
|