dsh-image-gen 0.2.1 → 0.2.3

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/lib/index.js CHANGED
@@ -16,13 +16,16 @@ const IMAGE_PROVIDERS = [
16
16
  "google",
17
17
  "openai",
18
18
  "seedream",
19
- "dashscope"
19
+ "dashscope",
20
+ "comfyui"
20
21
  ];
21
22
  /** Default endpoints and base URLs. */
22
23
  const DEFAULT_GOOGLE_ENDPOINT = "https://generativelanguage.googleapis.com/v1beta/interactions";
23
24
  const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1";
24
25
  const DEFAULT_SEEDREAM_BASE_URL = "https://ark.cn-beijing.volces.com/api/v3";
25
26
  const DEFAULT_DASHSCOPE_ENDPOINT = "https://dashscope.aliyuncs.com/api/v1";
27
+ const DEFAULT_COMFYUI_BASE_URL = "http://127.0.0.1:8188";
28
+ const DEFAULT_COMFYUI_TIMEOUT_MS = 3e5;
26
29
  /** Default model names. */
27
30
  const DEFAULT_GOOGLE_MODEL = "gemini-3.1-flash-image";
28
31
  const DEFAULT_OPENAI_MODEL = "gpt-image-2";
@@ -52,6 +55,10 @@ const Config = z.object({
52
55
  seedreamModel: z.string().default(DEFAULT_SEEDREAM_MODEL),
53
56
  dashscopeEndpoint: z.string().default(DEFAULT_DASHSCOPE_ENDPOINT),
54
57
  dashscopeModel: z.string().default(DEFAULT_DASHSCOPE_MODEL),
58
+ comfyuiBaseURL: z.string().default(DEFAULT_COMFYUI_BASE_URL),
59
+ comfyuiWorkflowJson: z.string().default(""),
60
+ comfyuiWorkflowName: z.string().default(""),
61
+ comfyuiTimeoutMs: z.number().min(1e3).max(36e5).default(DEFAULT_COMFYUI_TIMEOUT_MS),
55
62
  saveToWorkspace: z.boolean().default(true),
56
63
  workspaceFolder: z.string().default(DEFAULT_WORKSPACE_FOLDER)
57
64
  });
@@ -79,6 +86,13 @@ function resolveProvider(config) {
79
86
  endpoint: config.dashscopeEndpoint ?? "https://dashscope.aliyuncs.com/api/v1",
80
87
  imageSize: "1024*1024"
81
88
  };
89
+ case "comfyui": return {
90
+ provider: "comfyui",
91
+ baseURL: config.comfyuiBaseURL ?? "http://127.0.0.1:8188",
92
+ workflowJson: config.comfyuiWorkflowJson ?? "",
93
+ workflowName: config.comfyuiWorkflowName?.trim() || "API workflow",
94
+ timeoutMs: config.comfyuiTimeoutMs ?? 3e5
95
+ };
82
96
  case "google": return {
83
97
  provider: "google",
84
98
  apiKeyEnv: GOOGLE_API_KEY_ENV,
@@ -90,6 +104,256 @@ function resolveProvider(config) {
90
104
  }
91
105
  }
92
106
  //#endregion
107
+ //#region lib/types/comfyui-workflow.js
108
+ /** Pure ComfyUI API-workflow validation and placeholder injection. */
109
+ const COMFYUI_PROMPT_PLACEHOLDER = "{{prompt}}";
110
+ const COMFYUI_SEED_PLACEHOLDER = "{{seed}}";
111
+ /** Parse, clone, and inject one prompt plus an optional randomized seed. */
112
+ function prepareComfyUIWorkflow(workflowJson, prompt, seed = randomSeed()) {
113
+ const workflow = parseWorkflow(workflowJson);
114
+ let promptReplacements = 0;
115
+ const inject = (value) => {
116
+ if (typeof value === "string") {
117
+ if (value === "{{prompt}}") {
118
+ promptReplacements += 1;
119
+ return prompt;
120
+ }
121
+ if (value === "{{seed}}") return seed;
122
+ if (value.includes("{{prompt}}")) {
123
+ promptReplacements += 1;
124
+ return value.replaceAll(COMFYUI_PROMPT_PLACEHOLDER, prompt);
125
+ }
126
+ return value.includes("{{seed}}") ? value.replaceAll(COMFYUI_SEED_PLACEHOLDER, String(seed)) : value;
127
+ }
128
+ if (Array.isArray(value)) return value.map(inject);
129
+ if (!isRecord(value)) return value;
130
+ return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, inject(child)]));
131
+ };
132
+ const prepared = Object.fromEntries(Object.entries(workflow).map(([nodeId, value]) => {
133
+ const node = record$4(value);
134
+ const inputs = node === void 0 ? void 0 : record$4(node.inputs);
135
+ return inputs === void 0 ? [nodeId, value] : [nodeId, {
136
+ ...node,
137
+ inputs: inject(inputs)
138
+ }];
139
+ }));
140
+ if (promptReplacements === 0) throw new Error(`ComfyUI workflow must contain ${COMFYUI_PROMPT_PLACEHOLDER} in a text input`);
141
+ return prepared;
142
+ }
143
+ function parseWorkflow(workflowJson) {
144
+ if (workflowJson.trim().length === 0) throw new Error("Import a ComfyUI API workflow JSON file in Settings before generating");
145
+ if (new TextEncoder().encode(workflowJson).byteLength > 5242880) throw new Error("ComfyUI workflow file must be no larger than 5 MB");
146
+ let value;
147
+ try {
148
+ value = JSON.parse(workflowJson);
149
+ } catch {
150
+ throw new Error("ComfyUI workflow file is not valid JSON");
151
+ }
152
+ if (!isRecord(value) || Object.keys(value).length === 0) throw new Error("ComfyUI workflow must be a non-empty API-format JSON object");
153
+ if (!Object.values(value).some((node) => {
154
+ const inputs = record$4(node)?.inputs;
155
+ return inputs !== void 0 && containsPromptPlaceholder(inputs);
156
+ })) throw new Error(`ComfyUI workflow must contain ${COMFYUI_PROMPT_PLACEHOLDER} in a text input`);
157
+ return value;
158
+ }
159
+ function containsPromptPlaceholder(value) {
160
+ if (typeof value === "string") return value.includes(COMFYUI_PROMPT_PLACEHOLDER);
161
+ if (Array.isArray(value)) return value.some(containsPromptPlaceholder);
162
+ return isRecord(value) && Object.values(value).some(containsPromptPlaceholder);
163
+ }
164
+ function isRecord(value) {
165
+ return typeof value === "object" && value !== null && !Array.isArray(value);
166
+ }
167
+ function record$4(value) {
168
+ return isRecord(value) ? value : void 0;
169
+ }
170
+ function randomSeed() {
171
+ return Math.floor(Math.random() * 4294967296);
172
+ }
173
+ //#endregion
174
+ //#region lib/types/comfyui.js
175
+ const ERROR_LIMIT$3 = 4096;
176
+ const POLL_INTERVAL_MS = 500;
177
+ const MAX_HISTORY_BYTES = 16777216;
178
+ /** Run one ComfyUI workflow and return its first final image. */
179
+ async function generateComfyUIImage(input) {
180
+ input.signal.throwIfAborted();
181
+ const baseURL = comfyUIBaseURL(input.baseURL);
182
+ const workflow = prepareComfyUIWorkflow(input.workflowJson, input.prompt);
183
+ const controller = new AbortController();
184
+ const forwardAbort = () => {
185
+ controller.abort(input.signal.reason);
186
+ };
187
+ input.signal.addEventListener("abort", forwardAbort, { once: true });
188
+ const timeout = setTimeout(() => {
189
+ controller.abort(/* @__PURE__ */ new Error("ComfyUI generation timed out"));
190
+ }, input.timeoutMs);
191
+ try {
192
+ return await downloadOutput(baseURL, await waitForOutput(baseURL, await submitWorkflow(baseURL, workflow, controller.signal), controller.signal), input.maxBytes, controller.signal);
193
+ } catch (error) {
194
+ input.signal.throwIfAborted();
195
+ if (controller.signal.aborted) throw new Error(`ComfyUI generation timed out after ${String(input.timeoutMs)} ms`);
196
+ if (error instanceof TypeError) throw new Error(`Could not connect to ComfyUI at ${baseURL.origin}`);
197
+ throw error;
198
+ } finally {
199
+ clearTimeout(timeout);
200
+ input.signal.removeEventListener("abort", forwardAbort);
201
+ }
202
+ }
203
+ async function submitWorkflow(baseURL, workflow, signal) {
204
+ const response = await fetch(endpoint(baseURL, "prompt"), {
205
+ method: "POST",
206
+ redirect: "error",
207
+ signal,
208
+ headers: {
209
+ "content-type": "application/json",
210
+ accept: "application/json"
211
+ },
212
+ body: JSON.stringify({ prompt: workflow })
213
+ });
214
+ const text = await readBoundedText$3(response, ERROR_LIMIT$3);
215
+ if (!response.ok) throw new Error(`ComfyUI rejected the workflow (${response.status}): ${text}`);
216
+ const payload = parseJsonRecord(text, "ComfyUI /prompt returned invalid JSON");
217
+ if (typeof payload.prompt_id !== "string" || payload.prompt_id.length === 0) throw new Error(`ComfyUI /prompt returned no prompt_id: ${text}`);
218
+ return payload.prompt_id;
219
+ }
220
+ async function waitForOutput(baseURL, promptId, signal) {
221
+ for (;;) {
222
+ signal.throwIfAborted();
223
+ const response = await fetch(endpoint(baseURL, `history/${encodeURIComponent(promptId)}`), {
224
+ redirect: "error",
225
+ signal,
226
+ headers: { accept: "application/json" }
227
+ });
228
+ const text = await readBoundedText$3(response, MAX_HISTORY_BYTES);
229
+ if (!response.ok) throw new Error(`ComfyUI history request failed (${response.status}): ${text.slice(0, ERROR_LIMIT$3)}`);
230
+ const entry = record$3(parseJsonRecord(text, "ComfyUI history returned invalid JSON")[promptId]);
231
+ if (entry !== void 0) {
232
+ const status = record$3(entry.status);
233
+ if (status?.status_str === "error") throw new Error(`ComfyUI workflow failed: ${JSON.stringify(status.messages ?? status).slice(0, ERROR_LIMIT$3)}`);
234
+ const output = firstOutputImage(entry.outputs);
235
+ if (output !== void 0) return output;
236
+ if (status?.completed === true) throw new Error("ComfyUI workflow completed without an output image");
237
+ }
238
+ await delay(POLL_INTERVAL_MS, signal);
239
+ }
240
+ }
241
+ function firstOutputImage(value) {
242
+ const outputs = record$3(value);
243
+ if (outputs === void 0) return void 0;
244
+ for (const nodeOutput of Object.values(outputs)) {
245
+ const images = record$3(nodeOutput)?.images;
246
+ if (!Array.isArray(images)) continue;
247
+ for (const image of images) {
248
+ const item = record$3(image);
249
+ if (typeof item?.filename !== "string" || item.filename.length === 0) continue;
250
+ const output = {
251
+ filename: item.filename,
252
+ subfolder: typeof item.subfolder === "string" ? item.subfolder : "",
253
+ type: typeof item.type === "string" ? item.type : "output"
254
+ };
255
+ if (output.type === "output") return output;
256
+ }
257
+ }
258
+ }
259
+ async function downloadOutput(baseURL, output, maxBytes, signal) {
260
+ const url = endpoint(baseURL, "view");
261
+ url.searchParams.set("filename", output.filename);
262
+ url.searchParams.set("subfolder", output.subfolder);
263
+ url.searchParams.set("type", output.type);
264
+ const response = await fetch(url, {
265
+ redirect: "error",
266
+ signal
267
+ });
268
+ if (!response.ok) throw new Error(`ComfyUI image download failed (${response.status})`);
269
+ const mediaType = imageMediaType$3(response.headers.get("content-type")) ?? imageMediaTypeFromName(output.filename);
270
+ if (mediaType === void 0) throw new Error("ComfyUI image download returned an unsupported content type");
271
+ return {
272
+ data: await readBoundedBytes$2(response, maxBytes),
273
+ mediaType
274
+ };
275
+ }
276
+ function comfyUIBaseURL(value) {
277
+ let url;
278
+ try {
279
+ url = new URL(value.endsWith("/") ? value : `${value}/`);
280
+ } catch {
281
+ throw new Error("ComfyUI URL must be an absolute http:// or https:// URL");
282
+ }
283
+ if (url.protocol !== "http:" && url.protocol !== "https:") throw new Error("ComfyUI URL must use http:// or https://");
284
+ return url;
285
+ }
286
+ function endpoint(baseURL, path) {
287
+ return new URL(path, baseURL);
288
+ }
289
+ function record$3(value) {
290
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
291
+ }
292
+ function parseJsonRecord(text, message) {
293
+ let value;
294
+ try {
295
+ value = JSON.parse(text);
296
+ } catch {
297
+ throw new Error(message);
298
+ }
299
+ const parsed = record$3(value);
300
+ if (parsed === void 0) throw new Error(message);
301
+ return parsed;
302
+ }
303
+ function imageMediaType$3(value) {
304
+ const mediaType = value?.split(";", 1)[0]?.trim().toLowerCase();
305
+ return mediaType === "image/png" || mediaType === "image/jpeg" || mediaType === "image/webp" || mediaType === "image/gif" ? mediaType : void 0;
306
+ }
307
+ function imageMediaTypeFromName(filename) {
308
+ const lower = filename.toLowerCase();
309
+ if (lower.endsWith(".png")) return "image/png";
310
+ if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg";
311
+ if (lower.endsWith(".webp")) return "image/webp";
312
+ if (lower.endsWith(".gif")) return "image/gif";
313
+ }
314
+ async function delay(milliseconds, signal) {
315
+ await new Promise((resolve, reject) => {
316
+ const timer = setTimeout(done, milliseconds);
317
+ const abort = () => {
318
+ clearTimeout(timer);
319
+ signal.removeEventListener("abort", abort);
320
+ reject(signal.reason);
321
+ };
322
+ function done() {
323
+ signal.removeEventListener("abort", abort);
324
+ resolve();
325
+ }
326
+ signal.addEventListener("abort", abort, { once: true });
327
+ });
328
+ }
329
+ async function readBoundedText$3(response, maxBytes) {
330
+ return new TextDecoder().decode(await readBoundedBytes$2(response, maxBytes));
331
+ }
332
+ async function readBoundedBytes$2(response, maxBytes) {
333
+ if (response.body === null) return /* @__PURE__ */ new Uint8Array();
334
+ const reader = response.body.getReader();
335
+ const chunks = [];
336
+ let bytes = 0;
337
+ try {
338
+ for (;;) {
339
+ const next = await reader.read();
340
+ if (next.done) break;
341
+ bytes += next.value.byteLength;
342
+ if (bytes > maxBytes) throw new Error(`ComfyUI response exceeded the ${String(maxBytes)} byte limit`);
343
+ chunks.push(next.value);
344
+ }
345
+ } finally {
346
+ reader.releaseLock();
347
+ }
348
+ const joined = new Uint8Array(bytes);
349
+ let offset = 0;
350
+ for (const chunk of chunks) {
351
+ joined.set(chunk, offset);
352
+ offset += chunk.byteLength;
353
+ }
354
+ return joined;
355
+ }
356
+ //#endregion
93
357
  //#region lib/types/dashscope.js
94
358
  async function generateDashScopeImage(options) {
95
359
  assertQwenImageModel(options.model);
@@ -1028,6 +1292,14 @@ function apply(ctx, config = {}) {
1028
1292
  output: imageOutput("Generated"),
1029
1293
  async execute(args, exec) {
1030
1294
  const active = resolveProvider(current());
1295
+ if (active.provider === "comfyui") return saveGenerated(ctx, await generateComfyUIImage({
1296
+ baseURL: active.baseURL,
1297
+ workflowJson: active.workflowJson,
1298
+ prompt: args.prompt,
1299
+ timeoutMs: active.timeoutMs,
1300
+ maxBytes: ctx.attachments.imageLimits.maxImageBytes,
1301
+ signal: exec.signal
1302
+ }), active.provider, active.workflowName, "API workflow", current(), exec);
1031
1303
  const credential = await ctx.credentials.resolve(credentialRef(active.apiKeyEnv));
1032
1304
  if (credential === void 0 || credential.value.length === 0) throw new Error(`generate_image requires the ${active.apiKeyEnv} credential; configure it in Settings > Plugins > Image generation.`);
1033
1305
  if (active.provider === "google") {
@@ -1127,6 +1399,7 @@ function apply(ctx, config = {}) {
1127
1399
  output: imageOutput("Edited"),
1128
1400
  async execute(args, exec) {
1129
1401
  const active = resolveProvider(current());
1402
+ if (active.provider === "comfyui") throw new Error("edit_image is not yet supported by the ComfyUI provider; switch providers or use generate_image");
1130
1403
  const credential = await ctx.credentials.resolve(credentialRef(active.apiKeyEnv));
1131
1404
  if (credential === void 0 || credential.value.length === 0) throw new Error(`edit_image requires the ${active.apiKeyEnv} credential; configure it in Settings > Plugins > Image generation.`);
1132
1405
  const sourceImages = await resolveReferenceImages({
@@ -0,0 +1,8 @@
1
+ /** Dual-path image attachment resolution for legacy rc.2 and modern DSH runtime blocks. */
2
+ import type { ImageAttachmentRef } from '@deepseek-ai/dsh-attachment';
3
+ import type { ToolCallBlock } from '@deepseek-ai/dsh-client-runtime/client';
4
+ /**
5
+ * Extract the verified durable image attachment from a completed tool result block.
6
+ * Supports legacy rc.2 `resultView.content` and modern DSH `block.content`.
7
+ */
8
+ export declare function imageRef(block: ToolCallBlock): ImageAttachmentRef | undefined;
@@ -1,5 +1,4 @@
1
1
  import type { Context } from '@deepseek-ai/cordis';
2
- import type { ConnectionHandle } from '@deepseek-ai/dsh-client-connection/client';
3
2
  import type { SettingsScope } from '@deepseek-ai/dsh-client-runtime/client';
4
3
  import type { InjectFace, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
5
4
  import { type ImageProvider } from '../shared.js';
@@ -15,12 +14,33 @@ interface ImageSettings {
15
14
  seedreamModel?: string;
16
15
  dashscopeEndpoint?: string;
17
16
  dashscopeModel?: string;
17
+ comfyuiBaseURL?: string;
18
+ comfyuiWorkflowJson?: string;
19
+ comfyuiWorkflowName?: string;
20
+ comfyuiTimeoutMs?: number;
18
21
  saveToWorkspace?: boolean;
19
22
  workspaceFolder?: string;
20
23
  }
24
+ interface CredentialInfo {
25
+ configured?: boolean;
26
+ }
27
+ interface CredentialResult {
28
+ ok: boolean;
29
+ value?: Readonly<Record<string, CredentialInfo>>;
30
+ }
31
+ interface CredentialMutationResult {
32
+ ok: boolean;
33
+ error?: {
34
+ message?: string;
35
+ };
36
+ }
37
+ interface CredentialsRemote {
38
+ describe(refs: string[]): Promise<CredentialResult>;
39
+ set(ref: string, value: string): Promise<CredentialMutationResult>;
40
+ }
21
41
  interface SettingsFace {
22
42
  scope: SettingsScope<ImageSettings>;
23
- credentials: ConnectionHandle['api']['credentials'];
43
+ credentials: CredentialsRemote;
24
44
  locale?: LocaleService | undefined;
25
45
  }
26
46
  interface ImageCardFace {
@@ -0,0 +1,8 @@
1
+ export declare const COMFYUI_PROMPT_PLACEHOLDER = "{{prompt}}";
2
+ export declare const COMFYUI_SEED_PLACEHOLDER = "{{seed}}";
3
+ type JsonRecord = Record<string, unknown>;
4
+ /** Validate imported JSON without exposing workflow graph details to callers. */
5
+ export declare function validateComfyUIWorkflowJson(workflowJson: string): void;
6
+ /** Parse, clone, and inject one prompt plus an optional randomized seed. */
7
+ export declare function prepareComfyUIWorkflow(workflowJson: string, prompt: string, seed?: number): JsonRecord;
8
+ export {};
@@ -0,0 +1,15 @@
1
+ /** ComfyUI text-to-image adapter using an imported API-format workflow. */
2
+ import type { ImageMediaType } from '@deepseek-ai/dsh-attachment';
3
+ export interface GeneratedComfyUIImage {
4
+ data: Uint8Array;
5
+ mediaType: ImageMediaType;
6
+ }
7
+ /** Run one ComfyUI workflow and return its first final image. */
8
+ export declare function generateComfyUIImage(input: {
9
+ baseURL: string;
10
+ workflowJson: string;
11
+ prompt: string;
12
+ timeoutMs: number;
13
+ maxBytes: number;
14
+ signal: AbortSignal;
15
+ }): Promise<GeneratedComfyUIImage>;
@@ -1,7 +1,7 @@
1
1
  /** User-facing configuration for supported image providers. */
2
2
  import z from '@deepseek-ai/schemastery';
3
- import { DEFAULT_DASHSCOPE_ENDPOINT, DEFAULT_DASHSCOPE_MODEL, DEFAULT_GOOGLE_ENDPOINT, DEFAULT_GOOGLE_MODEL, DEFAULT_OPENAI_BASE_URL, DEFAULT_OPENAI_MODEL, DEFAULT_SEEDREAM_BASE_URL, DEFAULT_SEEDREAM_MODEL, IMAGE_PROVIDERS, type ImageProvider } from './shared.js';
4
- export { DEFAULT_DASHSCOPE_ENDPOINT, DEFAULT_DASHSCOPE_MODEL, DEFAULT_GOOGLE_ENDPOINT, DEFAULT_GOOGLE_MODEL, DEFAULT_OPENAI_BASE_URL, DEFAULT_OPENAI_MODEL, DEFAULT_SEEDREAM_BASE_URL, DEFAULT_SEEDREAM_MODEL, IMAGE_PROVIDERS, type ImageProvider, };
3
+ import { DEFAULT_DASHSCOPE_ENDPOINT, DEFAULT_DASHSCOPE_MODEL, DEFAULT_COMFYUI_BASE_URL, DEFAULT_COMFYUI_TIMEOUT_MS, DEFAULT_COMFYUI_WORKFLOW_LABEL, DEFAULT_GOOGLE_ENDPOINT, DEFAULT_GOOGLE_MODEL, DEFAULT_OPENAI_BASE_URL, DEFAULT_OPENAI_MODEL, DEFAULT_SEEDREAM_BASE_URL, DEFAULT_SEEDREAM_MODEL, IMAGE_PROVIDERS, type ImageProvider } from './shared.js';
4
+ export { DEFAULT_DASHSCOPE_ENDPOINT, DEFAULT_DASHSCOPE_MODEL, DEFAULT_COMFYUI_BASE_URL, DEFAULT_COMFYUI_TIMEOUT_MS, DEFAULT_COMFYUI_WORKFLOW_LABEL, DEFAULT_GOOGLE_ENDPOINT, DEFAULT_GOOGLE_MODEL, DEFAULT_OPENAI_BASE_URL, DEFAULT_OPENAI_MODEL, DEFAULT_SEEDREAM_BASE_URL, DEFAULT_SEEDREAM_MODEL, IMAGE_PROVIDERS, type ImageProvider, };
5
5
  /** Default workspace subfolder that receives generated image files. */
6
6
  export declare const DEFAULT_WORKSPACE_FOLDER = "dsh-image-gen";
7
7
  /** Google API credential reference. */
@@ -28,6 +28,12 @@ export interface Config {
28
28
  seedreamModel?: string;
29
29
  dashscopeEndpoint?: string;
30
30
  dashscopeModel?: string;
31
+ comfyuiBaseURL?: string;
32
+ /** ComfyUI API-format workflow JSON imported through the Web settings page. */
33
+ comfyuiWorkflowJson?: string;
34
+ /** Original imported file name, used as a human-readable result label. */
35
+ comfyuiWorkflowName?: string;
36
+ comfyuiTimeoutMs?: number;
31
37
  /** Also write every generated image as a file under the session workspace. */
32
38
  saveToWorkspace?: boolean;
33
39
  /** Workspace subfolder for generated images; empty means the workspace root. */
@@ -61,4 +67,10 @@ export declare function resolveProvider(config: Config): {
61
67
  model: string;
62
68
  endpoint: string;
63
69
  imageSize: string;
70
+ } | {
71
+ provider: 'comfyui';
72
+ baseURL: string;
73
+ workflowJson: string;
74
+ workflowName: string;
75
+ timeoutMs: number;
64
76
  };
@@ -4,13 +4,17 @@ export declare const IMAGE_ROUTE = "/plugins/dsh-image-gen/image";
4
4
  /** Namespace persisted through DSH Settings. */
5
5
  export declare const IMAGE_GENERATION_NAMESPACE = "image-generation";
6
6
  /** Supported providers. */
7
- export declare const IMAGE_PROVIDERS: readonly ["google", "openai", "seedream", "dashscope"];
7
+ export declare const IMAGE_PROVIDERS: readonly ["google", "openai", "seedream", "dashscope", "comfyui"];
8
8
  export type ImageProvider = typeof IMAGE_PROVIDERS[number];
9
9
  /** Default endpoints and base URLs. */
10
10
  export declare const DEFAULT_GOOGLE_ENDPOINT = "https://generativelanguage.googleapis.com/v1beta/interactions";
11
11
  export declare const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1";
12
12
  export declare const DEFAULT_SEEDREAM_BASE_URL = "https://ark.cn-beijing.volces.com/api/v3";
13
13
  export declare const DEFAULT_DASHSCOPE_ENDPOINT = "https://dashscope.aliyuncs.com/api/v1";
14
+ export declare const DEFAULT_COMFYUI_BASE_URL = "http://127.0.0.1:8188";
15
+ export declare const DEFAULT_COMFYUI_TIMEOUT_MS = 300000;
16
+ export declare const DEFAULT_COMFYUI_WORKFLOW_LABEL = "API workflow";
17
+ export declare const MAX_COMFYUI_WORKFLOW_BYTES: number;
14
18
  /** Default model names. */
15
19
  export declare const DEFAULT_GOOGLE_MODEL = "gemini-3.1-flash-image";
16
20
  export declare const DEFAULT_OPENAI_MODEL = "gpt-image-2";