@openclaw/comfy-provider 2026.8.1-beta.3 → 2026.8.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.
@@ -1,12 +1,13 @@
1
+ import { randomInt } from "node:crypto";
1
2
  import fs from "node:fs/promises";
2
3
  import { resolveGeneratedMediaMaxBytes } from "openclaw/plugin-sdk/media-generation-runtime";
3
4
  import { extensionForMime } from "openclaw/plugin-sdk/media-mime";
4
5
  import { resolvePositiveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
5
6
  import { isProviderApiKeyConfigured } from "openclaw/plugin-sdk/provider-auth";
6
7
  import { resolveApiKeyForProvider } from "openclaw/plugin-sdk/provider-auth-runtime";
7
- import { assertOkOrThrowHttpError, normalizeBaseUrl, readProviderJsonResponse, resolveProviderHttpRequestConfig } from "openclaw/plugin-sdk/provider-http";
8
+ import { assertOkOrThrowHttpError, normalizeBaseUrl, readProviderJsonResponse, redactProviderResponseErrorText, resolveProviderHttpRequestConfig } from "openclaw/plugin-sdk/provider-http";
8
9
  import { readResponseWithLimit } from "openclaw/plugin-sdk/response-limit-runtime";
9
- import { normalizeSecretInputString, resolveSecretInputString } from "openclaw/plugin-sdk/secret-input-runtime";
10
+ import { normalizeSecretInputString, resolveConfiguredSecretInputString, resolveSecretInputString } from "openclaw/plugin-sdk/secret-input-runtime";
10
11
  import { canResolveEnvSecretRefInReadOnlyPath } from "openclaw/plugin-sdk/secret-ref-readonly";
11
12
  import { fetchWithSsrFGuard, isPrivateOrLoopbackHost, mergeSsrFPolicies, ssrfPolicyFromHttpBaseUrlAllowedOrigin } from "openclaw/plugin-sdk/ssrf-runtime";
12
13
  import { asBoolean, isRecord, normalizeOptionalLowercaseString, normalizeOptionalString, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
@@ -16,8 +17,10 @@ const DEFAULT_COMFY_LOCAL_BASE_URL = "http://127.0.0.1:8188";
16
17
  const DEFAULT_COMFY_CLOUD_BASE_URL = "https://cloud.comfy.org";
17
18
  const DEFAULT_PROMPT_INPUT_NAME = "text";
18
19
  const DEFAULT_INPUT_IMAGE_INPUT_NAME = "image";
20
+ const DEFAULT_SEED_INPUT_NAME = "seed";
19
21
  const DEFAULT_POLL_INTERVAL_MS = 1500;
20
22
  const DEFAULT_TIMEOUT_MS = 5 * 6e4;
23
+ const RANDOM_SEED_EXCLUSIVE_MAX = 2 ** 48 - 1;
21
24
  const DEFAULT_COMFY_MODEL = "workflow";
22
25
  function readConfigInteger(config, key) {
23
26
  const value = config[key];
@@ -107,6 +110,34 @@ function setWorkflowInput(params) {
107
110
  if (!isRecord(inputs)) throw new Error(`Comfy workflow node "${params.nodeId}" is missing an inputs object`);
108
111
  inputs[params.inputName] = params.value;
109
112
  }
113
+ async function resolveComfyHeadersConfig(value, cfg) {
114
+ const headers = new Headers();
115
+ if (!isRecord(value)) return headers;
116
+ for (const [name, headerValue] of Object.entries(value)) {
117
+ const path = `plugins.entries.comfy.config.headers.${name}`;
118
+ const inspected = resolveSecretInputString({
119
+ value: headerValue,
120
+ path,
121
+ defaults: cfg.secrets?.defaults,
122
+ mode: "inspect"
123
+ });
124
+ if (inspected.status === "available") {
125
+ headers.set(name, inspected.value);
126
+ continue;
127
+ }
128
+ if (inspected.status === "missing") continue;
129
+ const resolved = await resolveConfiguredSecretInputString({
130
+ config: cfg,
131
+ env: process.env,
132
+ value: headerValue,
133
+ path,
134
+ unresolvedReasonStyle: "detailed"
135
+ });
136
+ if (resolved.unresolvedRefReason) throw new Error(`${path} references an unavailable secret: ${resolved.unresolvedRefReason}`);
137
+ if (resolved.value) headers.set(name, resolved.value);
138
+ }
139
+ return headers;
140
+ }
110
141
  function resolveComfyNetworkPolicy(params) {
111
142
  let parsed;
112
143
  try {
@@ -138,8 +169,9 @@ async function readJsonResponse(params) {
138
169
  auditContext: params.auditContext
139
170
  });
140
171
  try {
141
- await assertOkOrThrowHttpError(response, params.errorPrefix);
142
- return await readProviderJsonResponse(response, params.errorPrefix);
172
+ const requestHeaders = params.init?.headers;
173
+ await assertOkOrThrowHttpError(response, params.errorPrefix, { requestHeaders });
174
+ return await readProviderJsonResponse(response, params.errorPrefix, { requestHeaders });
143
175
  } finally {
144
176
  await release();
145
177
  }
@@ -230,7 +262,10 @@ async function waitForCloudCompletion(params) {
230
262
  errorPrefix: "Comfy status lookup failed"
231
263
  });
232
264
  if (status.status === "completed") return;
233
- if (status.status === "failed" || status.status === "cancelled") throw new Error(`Comfy workflow ${status.status}: ${status.error ?? status.message ?? params.promptId}`);
265
+ if (status.status === "failed" || status.status === "cancelled") {
266
+ const detail = redactProviderResponseErrorText(status.error ?? status.message ?? params.promptId, params.headers);
267
+ throw new Error(`Comfy workflow ${status.status}: ${detail}`);
268
+ }
234
269
  const pollDelayMs = resolveComfyRemainingMs(deadline, params.timeoutMs, params.pollIntervalMs);
235
270
  await new Promise((resolve) => {
236
271
  setTimeout(resolve, pollDelayMs);
@@ -290,7 +325,7 @@ async function downloadOutputFile(params) {
290
325
  auditContext
291
326
  });
292
327
  try {
293
- await assertOkOrThrowHttpError(firstResponse.response, "Comfy output download failed");
328
+ await assertOkOrThrowHttpError(firstResponse.response, "Comfy output download failed", { requestHeaders: params.headers });
294
329
  const mimeType = normalizeOptionalString(firstResponse.response.headers.get("content-type")) || "application/octet-stream";
295
330
  return {
296
331
  buffer: await readResponseWithLimit(firstResponse.response, params.maxBytes, {
@@ -304,11 +339,30 @@ async function downloadOutputFile(params) {
304
339
  await firstResponse.release();
305
340
  }
306
341
  }
342
+ function hasUnavailableComfyHeaderSecret(value, cfg) {
343
+ if (!isRecord(value)) return false;
344
+ return Object.entries(value).some(([name, headerValue]) => {
345
+ const inspected = resolveSecretInputString({
346
+ value: headerValue,
347
+ path: `plugins.entries.comfy.config.headers.${name}`,
348
+ defaults: cfg?.secrets?.defaults,
349
+ mode: "inspect"
350
+ });
351
+ if (inspected.status !== "configured_unavailable" || inspected.ref.source !== "env") return false;
352
+ const envVarName = inspected.ref.id.trim();
353
+ return !(canResolveEnvSecretRefInReadOnlyPath({
354
+ cfg,
355
+ provider: inspected.ref.provider,
356
+ id: envVarName
357
+ }) && Boolean(normalizeSecretInputString(process.env[envVarName])));
358
+ });
359
+ }
307
360
  function isComfyCapabilityConfigured(params) {
308
361
  const capabilityConfig = getComfyCapabilityConfig(getComfyConfig(params.cfg), params.capability);
309
362
  const hasWorkflow = Boolean(resolveComfyWorkflowSource(capabilityConfig).workflow || normalizeOptionalString(capabilityConfig.workflowPath));
310
363
  const hasPromptNode = Boolean(normalizeOptionalString(capabilityConfig.promptNodeId));
311
364
  if (!hasWorkflow || !hasPromptNode) return false;
365
+ if (hasUnavailableComfyHeaderSecret(capabilityConfig.headers, params.cfg)) return false;
312
366
  if (resolveComfyMode(capabilityConfig) === "local") return true;
313
367
  const configuredApiKey = resolveComfyApiKey(capabilityConfig, params.cfg);
314
368
  if (configuredApiKey.status === "available") return true;
@@ -327,6 +381,8 @@ async function runComfyWorkflow(params) {
327
381
  const promptInputName = normalizeOptionalString(capabilityConfig.promptInputName) ?? DEFAULT_PROMPT_INPUT_NAME;
328
382
  const inputImageNodeId = normalizeOptionalString(capabilityConfig.inputImageNodeId);
329
383
  const inputImageInputName = normalizeOptionalString(capabilityConfig.inputImageInputName) ?? DEFAULT_INPUT_IMAGE_INPUT_NAME;
384
+ const seedNodeId = normalizeOptionalString(capabilityConfig.seedNodeId);
385
+ const seedInputName = normalizeOptionalString(capabilityConfig.seedInputName) ?? DEFAULT_SEED_INPUT_NAME;
330
386
  const outputNodeId = normalizeOptionalString(capabilityConfig.outputNodeId);
331
387
  const pollIntervalMs = resolvePositiveTimerTimeoutMs(readConfigInteger(capabilityConfig, "pollIntervalMs"), DEFAULT_POLL_INTERVAL_MS);
332
388
  const timeoutMs = resolvePositiveTimerTimeoutMs(readConfigInteger(capabilityConfig, "timeoutMs") ?? params.timeoutMs, DEFAULT_TIMEOUT_MS);
@@ -337,6 +393,12 @@ async function runComfyWorkflow(params) {
337
393
  inputName: promptInputName,
338
394
  value: params.prompt
339
395
  });
396
+ if (seedNodeId) setWorkflowInput({
397
+ workflow,
398
+ nodeId: seedNodeId,
399
+ inputName: seedInputName,
400
+ value: randomInt(RANDOM_SEED_EXCLUSIVE_MAX)
401
+ });
340
402
  const pluginApiKey = resolveComfyApiKey(capabilityConfig, params.cfg);
341
403
  const resolvedAuth = mode === "cloud" ? pluginApiKey.status === "available" ? {
342
404
  apiKey: pluginApiKey.apiKey,
@@ -354,6 +416,7 @@ async function runComfyWorkflow(params) {
354
416
  baseUrl: normalizeOptionalString(capabilityConfig.baseUrl),
355
417
  defaultBaseUrl: mode === "cloud" ? DEFAULT_COMFY_CLOUD_BASE_URL : DEFAULT_COMFY_LOCAL_BASE_URL,
356
418
  allowPrivateNetwork: mode === "local" || explicitAllowPrivateNetwork,
419
+ headers: await resolveComfyHeadersConfig(capabilityConfig.headers, params.cfg),
357
420
  defaultHeaders: mode === "cloud" ? {
358
421
  "X-API-Key": resolvedAuth?.apiKey ?? "",
359
422
  "Content-Type": "application/json"
@@ -5,6 +5,11 @@
5
5
  },
6
6
  "enabledByDefault": true,
7
7
  "providers": ["comfy"],
8
+ "configContracts": {
9
+ "secretInputs": {
10
+ "paths": [{ "path": "headers.*", "expected": "string" }]
11
+ }
12
+ },
8
13
  "setup": {
9
14
  "providers": [
10
15
  {
@@ -196,6 +201,10 @@
196
201
  "allowPrivateNetwork": {
197
202
  "type": "boolean"
198
203
  },
204
+ "headers": {
205
+ "type": "object",
206
+ "additionalProperties": { "type": ["string", "object"] }
207
+ },
199
208
  "workflowPath": {
200
209
  "type": "string"
201
210
  },
@@ -208,6 +217,12 @@
208
217
  "promptInputName": {
209
218
  "type": "string"
210
219
  },
220
+ "seedNodeId": {
221
+ "type": "string"
222
+ },
223
+ "seedInputName": {
224
+ "type": "string"
225
+ },
211
226
  "inputImageNodeId": {
212
227
  "type": "string"
213
228
  },
@@ -233,6 +248,8 @@
233
248
  "workflow": { "type": "object" },
234
249
  "promptNodeId": { "type": "string" },
235
250
  "promptInputName": { "type": "string" },
251
+ "seedNodeId": { "type": "string" },
252
+ "seedInputName": { "type": "string" },
236
253
  "inputImageNodeId": { "type": "string" },
237
254
  "inputImageInputName": { "type": "string" },
238
255
  "outputNodeId": { "type": "string" },
@@ -248,6 +265,8 @@
248
265
  "workflow": { "type": "object" },
249
266
  "promptNodeId": { "type": "string" },
250
267
  "promptInputName": { "type": "string" },
268
+ "seedNodeId": { "type": "string" },
269
+ "seedInputName": { "type": "string" },
251
270
  "inputImageNodeId": { "type": "string" },
252
271
  "inputImageInputName": { "type": "string" },
253
272
  "outputNodeId": { "type": "string" },
@@ -263,6 +282,8 @@
263
282
  "workflow": { "type": "object" },
264
283
  "promptNodeId": { "type": "string" },
265
284
  "promptInputName": { "type": "string" },
285
+ "seedNodeId": { "type": "string" },
286
+ "seedInputName": { "type": "string" },
266
287
  "outputNodeId": { "type": "string" },
267
288
  "pollIntervalMs": { "type": "integer", "minimum": 100 },
268
289
  "timeoutMs": { "type": "integer", "minimum": 1000 }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/comfy-provider",
3
- "version": "2026.8.1-beta.3",
3
+ "version": "2026.8.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.8.1-beta.3"
21
+ "pluginApi": ">=2026.8.1"
22
22
  },
23
23
  "build": {
24
- "openclawVersion": "2026.8.1-beta.3",
24
+ "openclawVersion": "2026.8.1",
25
25
  "bundledDist": false
26
26
  },
27
27
  "release": {
@@ -38,7 +38,7 @@
38
38
  "README.md"
39
39
  ],
40
40
  "peerDependencies": {
41
- "openclaw": ">=2026.8.1-beta.3"
41
+ "openclaw": ">=2026.8.1"
42
42
  },
43
43
  "peerDependenciesMeta": {
44
44
  "openclaw": {