@openclaw/comfy-provider 2026.9.8 → 2026.10.1-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.
@@ -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
- cfg: req.cfg,
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
- cfg: req.cfg,
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
- promptId: result.promptId,
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
- cfg: req.cfg,
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
  };
@@ -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, normalizeBaseUrl, readProviderBinaryResponse, readProviderJsonResponse, redactProviderResponseErrorText, resolveProviderHttpRequestConfig } from "openclaw/plugin-sdk/provider-http";
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 source = resolveComfyWorkflowSource(config);
99
- if (source.workflow) return source.workflow;
100
- if (!source.workflowPath) throw new Error("plugins.entries.comfy.config.<capability>.workflow or workflowPath is required");
101
- const resolvedPath = resolveUserPath(source.workflowPath);
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(params) {
108
- const node = params.workflow[params.nodeId];
109
- if (!isRecord(node)) throw new Error(`Comfy workflow missing node "${params.nodeId}"`);
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 "${params.nodeId}" is missing an inputs object`);
112
- inputs[params.inputName] = params.value;
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
- let parsed;
133
- try {
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
- const hostnameOnlyPolicy = localHostnamePolicy ? { apiPolicy: localHostnamePolicy } : {};
142
- if (!params.allowPrivateNetwork) return hostnameOnlyPolicy;
143
- if (!params.explicitAllowPrivateNetwork && params.mode !== "local") return {};
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 hostnameOnlyPolicy;
147
- return { apiPolicy: params.mode === "local" ? mergeSsrFPolicies(originPolicy, localHostnamePolicy) : originPolicy };
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 { response, release } = await fetchWithSsrFGuard({
154
- url: params.url,
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, params.errorPrefix, { requestHeaders });
164
- return await readProviderJsonResponse(response, params.errorPrefix, { requestHeaders });
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.${resolveFileExtension({ mimeType: params.image.mimeType })}`);
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(resolveComfyWorkflowSource(capabilityConfig).workflow || normalizeOptionalString(capabilityConfig.workflowPath));
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 = getRequiredConfigString(capabilityConfig, "promptNodeId");
353
- const promptInputName = normalizeOptionalString(capabilityConfig.promptInputName) ?? DEFAULT_PROMPT_INPUT_NAME;
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) ?? DEFAULT_INPUT_IMAGE_INPUT_NAME;
323
+ const inputImageInputName = normalizeOptionalString(capabilityConfig.inputImageInputName) ?? "image";
356
324
  const seedNodeId = normalizeOptionalString(capabilityConfig.seedNodeId);
357
- const seedInputName = normalizeOptionalString(capabilityConfig.seedInputName) ?? DEFAULT_SEED_INPUT_NAME;
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
- workflow,
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 resolvedAuth = mode === "cloud" ? pluginApiKey.status === "available" ? {
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
- }) : null;
385
- if (mode === "cloud" && !resolvedAuth?.apiKey) throw new Error("Comfy Cloud API key missing");
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": resolvedAuth?.apiKey ?? "",
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: normalizedBaseUrl,
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
- workflow,
411
- nodeId: inputImageNodeId,
412
- inputName: inputImageInputName,
413
- value: await uploadInputImage({
414
- baseUrl: normalizedBaseUrl,
415
- headers: new Headers(headers),
416
- timeoutMs,
417
- policy: networkPolicy.apiPolicy,
418
- dispatcherPolicy,
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" && resolvedAuth?.apiKey ? { extra_data: { api_key_comfy_org: resolvedAuth.apiKey } } : {}
375
+ ...mode === "cloud" && apiKey ? { extra_data: { api_key_comfy_org: apiKey } } : {}
428
376
  };
429
377
  const promptResponse = await readJsonResponse({
430
- url: `${normalizedBaseUrl}${mode === "cloud" ? "/api/prompt" : "/prompt"}`,
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.apiPolicy,
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: normalizedBaseUrl,
393
+ baseUrl,
446
394
  promptId,
447
395
  headers: new Headers(headers),
448
396
  timeoutMs,
449
397
  pollIntervalMs,
450
- policy: networkPolicy.apiPolicy,
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.outputKinds,
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: normalizedBaseUrl,
419
+ baseUrl,
469
420
  headers: new Headers(headers),
470
421
  timeoutMs,
471
- policy: networkPolicy.apiPolicy,
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
- buffer: downloaded.buffer,
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
- promptId,
494
- outputNodeIds: uniqueStrings(outputFiles.map((entry) => entry.nodeId))
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.9.8",
3
+ "version": "2026.10.1-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.9.8"
21
+ "pluginApi": ">=2026.10.1-beta.1"
22
22
  },
23
23
  "build": {
24
- "openclawVersion": "2026.9.8",
24
+ "openclawVersion": "2026.10.1-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.9.8"
43
+ "openclaw": ">=2026.10.1-beta.1"
44
44
  },
45
45
  "peerDependenciesMeta": {
46
46
  "openclaw": {