n8n-nodes-comfyui-all 2.2.9 → 2.2.11

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.
Files changed (57) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +126 -11
  3. package/dist/agent-tools/ComfyUIAgentTool.d.ts +62 -0
  4. package/dist/agent-tools/ComfyUIAgentTool.d.ts.map +1 -0
  5. package/dist/agent-tools/ComfyUIAgentTool.js +440 -0
  6. package/dist/agent-tools/ComfyUIAgentTool.js.map +1 -0
  7. package/dist/nodes/AxiosAdapter.d.ts +5 -0
  8. package/dist/nodes/AxiosAdapter.d.ts.map +1 -0
  9. package/dist/nodes/AxiosAdapter.js +31 -0
  10. package/dist/nodes/AxiosAdapter.js.map +1 -0
  11. package/dist/nodes/ComfyUi/ComfyUi.node.d.ts +30 -0
  12. package/dist/nodes/ComfyUi/ComfyUi.node.d.ts.map +1 -1
  13. package/dist/nodes/ComfyUi/ComfyUi.node.js +47 -251
  14. package/dist/nodes/ComfyUi/ComfyUi.node.js.map +1 -1
  15. package/dist/nodes/ComfyUiClient.d.ts +116 -3
  16. package/dist/nodes/ComfyUiClient.d.ts.map +1 -1
  17. package/dist/nodes/ComfyUiClient.js +374 -98
  18. package/dist/nodes/ComfyUiClient.js.map +1 -1
  19. package/dist/nodes/HttpClient.d.ts +34 -0
  20. package/dist/nodes/HttpClient.d.ts.map +1 -0
  21. package/dist/nodes/HttpClient.js +85 -0
  22. package/dist/nodes/HttpClient.js.map +1 -0
  23. package/dist/nodes/N8nHelpersAdapter.d.ts +8 -0
  24. package/dist/nodes/N8nHelpersAdapter.d.ts.map +1 -0
  25. package/dist/nodes/N8nHelpersAdapter.js +23 -0
  26. package/dist/nodes/N8nHelpersAdapter.js.map +1 -0
  27. package/dist/nodes/constants.d.ts +14 -3
  28. package/dist/nodes/constants.d.ts.map +1 -1
  29. package/dist/nodes/constants.js +20 -14
  30. package/dist/nodes/constants.js.map +1 -1
  31. package/dist/nodes/errors.d.ts +35 -0
  32. package/dist/nodes/errors.d.ts.map +1 -0
  33. package/dist/nodes/errors.js +111 -0
  34. package/dist/nodes/errors.js.map +1 -0
  35. package/dist/nodes/logger.d.ts +9 -17
  36. package/dist/nodes/logger.d.ts.map +1 -1
  37. package/dist/nodes/logger.js +72 -23
  38. package/dist/nodes/logger.js.map +1 -1
  39. package/dist/nodes/parameterProcessor.d.ts +22 -0
  40. package/dist/nodes/parameterProcessor.d.ts.map +1 -0
  41. package/dist/nodes/parameterProcessor.js +268 -0
  42. package/dist/nodes/parameterProcessor.js.map +1 -0
  43. package/dist/nodes/types.d.ts +129 -0
  44. package/dist/nodes/types.d.ts.map +1 -1
  45. package/dist/nodes/utils.d.ts +65 -0
  46. package/dist/nodes/utils.d.ts.map +1 -0
  47. package/dist/nodes/utils.js +172 -0
  48. package/dist/nodes/utils.js.map +1 -0
  49. package/dist/nodes/validation.d.ts +8 -0
  50. package/dist/nodes/validation.d.ts.map +1 -1
  51. package/dist/nodes/validation.js +129 -7
  52. package/dist/nodes/validation.js.map +1 -1
  53. package/dist/nodes/workflowConfig.d.ts +13 -0
  54. package/dist/nodes/workflowConfig.d.ts.map +1 -0
  55. package/dist/nodes/workflowConfig.js +91 -0
  56. package/dist/nodes/workflowConfig.js.map +1 -0
  57. package/package.json +18 -2
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 wwrs
3
+ Copyright (c) 2025 wwrs
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -131,22 +131,74 @@ By default, the first output image/video uses `data` as the binary property name
131
131
 
132
132
  ## 🤖 AI Agent Integration
133
133
 
134
- Use ComfyUI as a tool in AI Agent workflows:
134
+ Use ComfyUI as a tool in AI Agent workflows.
135
135
 
136
- 1. Add an **AI Agent** node (e.g., OpenAI Conversational Agent)
137
- 2. In the **Tools** section, add **ComfyUI**
138
- 3. Configure the ComfyUI node parameters
139
- 4. Start chatting!
136
+ ### Quick Setup
140
137
 
141
- **Example:**
138
+ **Step 1: Create AI Agent**
139
+ 1. Add **OpenAI Conversational Agent** node
140
+ 2. Configure Chat Model (GPT-4/GPT-3.5)
141
+ 3. Add Memory (optional but recommended)
142
+
143
+ **Step 2: Add ComfyUI Tool**
144
+ 1. Click AI Agent node
145
+ 2. In **Tools** section, click **+ Add Tool**
146
+ 3. Search and select **ComfyUI**
147
+ 4. Configure:
148
+ - **ComfyUI URL**: `http://127.0.0.1:8188`
149
+ - **Workflow JSON**: Your ComfyUI workflow (API format)
150
+ - **Node Parameters**: Configure text parameter override
151
+
152
+ **Step 3: Start Chatting**
153
+ Execute workflow and start conversing!
154
+
155
+ ### Example Conversations
156
+
157
+ **Basic Image Generation:**
158
+
159
+ **User:**
160
+ ```
161
+ Generate a picture of a cute cat sitting on a fence
162
+ ```
163
+
164
+ **AI:**
165
+ ```
166
+ I'll generate that for you.
167
+ [Executes ComfyUI with prompt: "a cute cat sitting on a fence"]
168
+ Done! Here's the image.
169
+ ```
170
+
171
+ **With Parameters:**
172
+
173
+ **User:**
174
+ ```
175
+ Create a cyberpunk city, size:1024x768, steps:30
142
176
  ```
143
- User: Generate a cute cat picture
144
177
 
145
- AI: I'll generate that for you using ComfyUI.
146
- [Calls ComfyUI tool]
147
- Done! Here's your cute cat picture.
178
+ **Supported Parameters:**
179
+ - `size:WIDTHxHEIGHT` - Image dimensions
180
+ - `steps:N` - Sampling steps
181
+ - `cfg:N` - CFG strength
182
+ - `seed:N` - Random seed
183
+ - `negative:TEXT` - Negative prompt
184
+
185
+ **Negative Prompts:**
186
+
187
+ **User:**
188
+ ```
189
+ Draw a beautiful sunset, negative: blurry, low quality
148
190
  ```
149
191
 
192
+ ### Configuring Node Parameters for AI Agent
193
+
194
+ In the ComfyUI node, configure parameter overrides:
195
+
196
+ - **Node ID**: `6` (your CLIP text node)
197
+ - **Parameter Mode**: Single Parameter
198
+ - **Parameter Name**: `text`
199
+ - **Type**: Text
200
+ - **Value**: [Leave empty - AI Agent will fill this]
201
+
150
202
  ### Tool Mode Output Format
151
203
 
152
204
  When used as an AI Agent tool, ComfyUI returns both binary data and URL information:
@@ -167,7 +219,70 @@ When used as an AI Agent tool, ComfyUI returns both binary data and URL informat
167
219
 
168
220
  This ensures compatibility with both workflow mode (binary data) and tool mode (URLs for AI Agent).
169
221
 
170
- For detailed AI Agent usage, see [AI-AGENT-USAGE.md](AI-AGENT-USAGE.md).
222
+ ### Best Practices
223
+
224
+ **1. Clear Prompts**
225
+
226
+ **Good:** "Generate a landscape painting of mountains at sunset"
227
+
228
+ **Bad:** "Make a picture"
229
+
230
+ **2. Use Specific Parameters**
231
+
232
+ **Good:** "Create a portrait, size:512x768, steps:25"
233
+
234
+ **Bad:** "Create a high-quality portrait"
235
+
236
+ **3. Negative Prompts**
237
+
238
+ Always use negative prompts for better quality:
239
+
240
+ ```
241
+ A beautiful landscape, negative: blurry, distorted, low quality
242
+ ```
243
+
244
+ ### Advanced Usage
245
+
246
+ **Style Transfer:**
247
+
248
+ **User:**
249
+ ```
250
+ Transform this image to oil painting style
251
+ ```
252
+
253
+ Setup:
254
+ - Upload input image as binary data
255
+ - Configure style parameters
256
+
257
+ **Multi-Modal Workflows:**
258
+
259
+ Combine ComfyUI with other tools:
260
+
261
+ **User:**
262
+ ```
263
+ Generate an image of a futuristic city, then write a poem about it
264
+ ```
265
+
266
+ AI Agent:
267
+ 1. Calls ComfyUI to generate image
268
+ 2. Calls Chat Model to write poem
269
+ 3. Returns both results
270
+
271
+ ### Troubleshooting
272
+
273
+ **AI Agent Doesn't Call ComfyUI**
274
+
275
+ **Check:**
276
+ 1. ComfyUI tool added to Tools list?
277
+ 2. Workflow JSON configured?
278
+ 3. ComfyUI server running?
279
+
280
+ **Images Don't Match Prompts**
281
+
282
+ **Check:**
283
+ 1. Correct node ID (e.g., `6` for CLIP text)
284
+ 2. Parameter name matches workflow (`text`)
285
+ 3. Workflow uses dynamic text input
171
286
 
172
287
  ## 🔧 Configuration Reference
173
288
 
@@ -0,0 +1,62 @@
1
+ /**
2
+ * ComfyUI Agent Tool - Custom Code Tool for n8n AI Agent
3
+ *
4
+ * This tool allows AI Agents to directly call ComfyUI API to generate images
5
+ *
6
+ * Usage:
7
+ * 1. Create a "Custom Code Tool" node in n8n
8
+ * 2. Copy this code into the JavaScript code box
9
+ * 3. Fill in the Description: Generates images using ComfyUI. Use this tool when the user asks to create, generate, or make images.
10
+ * 4. Add this tool to the tools list of the AI Agent node
11
+ */
12
+ import { Workflow, ImageInfo, ParsedParameters, ToolInputOptions, ToolResult } from '../nodes/types';
13
+ /**
14
+ * Cancel any ongoing ComfyUI workflow execution
15
+ * @returns {boolean} True if a request was cancelled, false if no request was in progress
16
+ */
17
+ export declare function cancelComfyUIExecution(): boolean;
18
+ /**
19
+ * Parse user input to extract image generation parameters
20
+ * @param query - User query text
21
+ * @returns Parsed parameters object
22
+ */
23
+ declare function parseInput(query: string): ParsedParameters;
24
+ /**
25
+ * Find node ID by node type
26
+ * @param workflow - Workflow object
27
+ * @param classType - Node type
28
+ * @returns Node ID or null
29
+ */
30
+ declare function findNodeByClassType(workflow: Workflow, classType: string): string | null;
31
+ /**
32
+ * Update workflow parameters
33
+ * @param workflow - ComfyUI workflow object
34
+ * @param params - Parameters object
35
+ * @returns Updated workflow
36
+ */
37
+ declare function updateWorkflow(workflow: Workflow, params: ParsedParameters): Workflow;
38
+ /**
39
+ * Extract image information from output nodes
40
+ * @param outputs - ComfyUI output object
41
+ * @param url - ComfyUI server URL
42
+ * @returns Array of image information
43
+ */
44
+ declare function extractImagesFromOutputs(outputs: Record<string, unknown>, url: string): ImageInfo[];
45
+ /**
46
+ * Process single image object
47
+ * @param image - Image object
48
+ * @param nodeId - Node ID
49
+ * @param url - ComfyUI server URL
50
+ * @returns Processed image information or null
51
+ */
52
+ declare function processImage(image: unknown, nodeId: string, url: string): ImageInfo | null;
53
+ /**
54
+ * Main function: Handle tool invocation
55
+ * @param query - User query text
56
+ * @param options - Optional configuration parameters
57
+ * @param options.comfyUiUrl - ComfyUI server URL
58
+ * @param options.workflowConfig - Workflow configuration options
59
+ */
60
+ export declare function handleToolInput(query: string, options?: ToolInputOptions): Promise<ToolResult>;
61
+ export { parseInput, updateWorkflow, findNodeByClassType, extractImagesFromOutputs, processImage };
62
+ //# sourceMappingURL=ComfyUIAgentTool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ComfyUIAgentTool.d.ts","sourceRoot":"","sources":["../../agent-tools/ComfyUIAgentTool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,EAGL,QAAQ,EACR,SAAS,EACT,gBAAgB,EAEhB,gBAAgB,EAChB,UAAU,EAGX,MAAM,gBAAgB,CAAC;AA0BxB;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,OAAO,CAQhD;AA2ED;;;;GAIG;AACH,iBAAS,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,gBAAgB,CA4CnD;AAED;;;;;GAKG;AACH,iBAAS,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOjF;AAED;;;;;GAKG;AACH,iBAAS,cAAc,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,gBAAgB,GAAG,QAAQ,CAyC9E;AAED;;;;;GAKG;AACH,iBAAS,wBAAwB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,CA2B5F;AAED;;;;;;GAMG;AACH,iBAAS,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAsBnF;AA0ID;;;;;;GAMG;AACH,wBAAsB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,UAAU,CAAC,CA4CxG;AAaD,OAAO,EACL,UAAU,EACV,cAAc,EACd,mBAAmB,EACnB,wBAAwB,EACxB,YAAY,EACb,CAAC"}
@@ -0,0 +1,440 @@
1
+ "use strict";
2
+ /**
3
+ * ComfyUI Agent Tool - Custom Code Tool for n8n AI Agent
4
+ *
5
+ * This tool allows AI Agents to directly call ComfyUI API to generate images
6
+ *
7
+ * Usage:
8
+ * 1. Create a "Custom Code Tool" node in n8n
9
+ * 2. Copy this code into the JavaScript code box
10
+ * 3. Fill in the Description: Generates images using ComfyUI. Use this tool when the user asks to create, generate, or make images.
11
+ * 4. Add this tool to the tools list of the AI Agent node
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.cancelComfyUIExecution = cancelComfyUIExecution;
15
+ exports.handleToolInput = handleToolInput;
16
+ exports.parseInput = parseInput;
17
+ exports.updateWorkflow = updateWorkflow;
18
+ exports.findNodeByClassType = findNodeByClassType;
19
+ exports.extractImagesFromOutputs = extractImagesFromOutputs;
20
+ exports.processImage = processImage;
21
+ const crypto_1 = require("crypto");
22
+ const workflowConfig_1 = require("../nodes/workflowConfig");
23
+ const HttpClient_1 = require("../nodes/HttpClient");
24
+ const AxiosAdapter_1 = require("../nodes/AxiosAdapter");
25
+ const logger_1 = require("../nodes/logger");
26
+ // Default configuration
27
+ const DEFAULT_COMFYUI_URL = 'http://127.0.0.1:8188';
28
+ // Request timeout configuration
29
+ const QUEUE_REQUEST_TIMEOUT = 30000; // Queue request timeout 30 seconds
30
+ const HISTORY_REQUEST_TIMEOUT = 10000; // History request timeout 10 seconds
31
+ // Create logger instance
32
+ const logger = new logger_1.Logger(undefined, ['ComfyUI Tool']);
33
+ // Create HTTP client instance
34
+ const httpClient = new HttpClient_1.HttpClient({
35
+ adapter: new AxiosAdapter_1.AxiosAdapter(),
36
+ logger,
37
+ defaultTimeout: QUEUE_REQUEST_TIMEOUT,
38
+ });
39
+ // Global AbortController for cancelling requests
40
+ let currentAbortController = null;
41
+ /**
42
+ * Cancel any ongoing ComfyUI workflow execution
43
+ * @returns {boolean} True if a request was cancelled, false if no request was in progress
44
+ */
45
+ function cancelComfyUIExecution() {
46
+ if (currentAbortController) {
47
+ currentAbortController.abort();
48
+ currentAbortController = null;
49
+ logger.info('ComfyUI workflow execution cancelled');
50
+ return true;
51
+ }
52
+ return false;
53
+ }
54
+ // Parameter default values
55
+ const DEFAULT_NEGATIVE_PROMPT = 'ugly, blurry, low quality, distorted';
56
+ const DEFAULT_WIDTH = 512;
57
+ const DEFAULT_HEIGHT = 512;
58
+ const DEFAULT_STEPS = 20;
59
+ const DEFAULT_CFG = 8;
60
+ // Maximum random seed value (32-bit signed integer max value)
61
+ const MAX_SEED_VALUE = 2147483647;
62
+ // Polling configuration
63
+ const POLLING_MAX_ATTEMPTS = 120; // Maximum wait time of 2 minutes (120 seconds)
64
+ const POLLING_INTERVAL_MS = 1000; // Polling interval of 1 second
65
+ /**
66
+ * Promise-based delay function
67
+ * @param ms - Delay time in milliseconds
68
+ * @returns Promise object
69
+ */
70
+ function delay(ms) {
71
+ return new Promise(resolve => setTimeout(resolve, ms));
72
+ }
73
+ // Parameter extraction rules configuration
74
+ const PARAM_PATTERNS = {
75
+ negative: {
76
+ regex: /(?<=\s|,|,|^)negative:\s*([^\n]+)/i,
77
+ paramKey: 'negative_prompt',
78
+ parser: (match) => match[1].trim()
79
+ },
80
+ size: {
81
+ regex: /(?<=\s|,|,|^)size:\s*(\d+)x(\d+)/i,
82
+ paramKeys: ['width', 'height'],
83
+ parser: (match) => ({
84
+ width: parseInt(match[1]),
85
+ height: parseInt(match[2])
86
+ })
87
+ },
88
+ steps: {
89
+ regex: /(?<=\s|,|,|^)steps:\s*(\d+)/i,
90
+ paramKey: 'steps',
91
+ parser: (match) => parseInt(match[1])
92
+ },
93
+ cfg: {
94
+ regex: /(?<=\s|,|,|^)cfg:\s*([\d.]+)/i,
95
+ paramKey: 'cfg',
96
+ parser: (match) => parseFloat(match[1])
97
+ },
98
+ seed: {
99
+ regex: /(?<=\s|,|,|^)seed:\s*(\d+)/i,
100
+ paramKey: 'seed',
101
+ parser: (match) => parseInt(match[1])
102
+ }
103
+ };
104
+ /**
105
+ * Extract parameter from query
106
+ * @param query - User query text
107
+ * @param pattern - Parameter pattern configuration
108
+ * @returns Extracted parameter and cleaned query
109
+ */
110
+ function extractParameter(query, pattern) {
111
+ const match = query.match(pattern.regex);
112
+ if (!match) {
113
+ return { value: null, cleanedQuery: query };
114
+ }
115
+ const value = pattern.parser(match);
116
+ const cleanedQuery = query.replace(pattern.regex, '').trim();
117
+ return { value, cleanedQuery };
118
+ }
119
+ /**
120
+ * Parse user input to extract image generation parameters
121
+ * @param query - User query text
122
+ * @returns Parsed parameters object
123
+ */
124
+ function parseInput(query) {
125
+ const params = {
126
+ prompt: '',
127
+ negative_prompt: DEFAULT_NEGATIVE_PROMPT,
128
+ width: DEFAULT_WIDTH,
129
+ height: DEFAULT_HEIGHT,
130
+ steps: DEFAULT_STEPS,
131
+ cfg: DEFAULT_CFG,
132
+ seed: (0, crypto_1.randomInt)(0, MAX_SEED_VALUE)
133
+ };
134
+ let currentQuery = query.trim();
135
+ for (const [, pattern] of Object.entries(PARAM_PATTERNS)) {
136
+ const { value, cleanedQuery } = extractParameter(currentQuery, pattern);
137
+ if (value !== null) {
138
+ if (pattern.paramKeys) {
139
+ // Type guard: check if value is an object with keys
140
+ if (typeof value === 'object' && value !== null) {
141
+ const valueRecord = value;
142
+ const valueKeys = Object.keys(valueRecord);
143
+ pattern.paramKeys.forEach((key, index) => {
144
+ params[key] = valueRecord[valueKeys[index]];
145
+ });
146
+ }
147
+ }
148
+ else if (pattern.paramKey) {
149
+ params[pattern.paramKey] = value;
150
+ }
151
+ currentQuery = cleanedQuery;
152
+ }
153
+ }
154
+ // Clean up remaining parameter markers and extra punctuation
155
+ currentQuery = currentQuery
156
+ .replace(/,\s*,/g, ',') // Remove consecutive Chinese commas
157
+ .replace(/,\s*,/g, ',') // Remove consecutive English commas
158
+ .replace(/[,,]\s*[,,]/g, ' ') // Remove extra spaces between commas
159
+ .replace(/^\s*[,,]+/g, '') // Remove all leading commas
160
+ .replace(/[,,]+\s*$/g, '') // Remove all trailing commas
161
+ .trim();
162
+ params.prompt = currentQuery;
163
+ return params;
164
+ }
165
+ /**
166
+ * Find node ID by node type
167
+ * @param workflow - Workflow object
168
+ * @param classType - Node type
169
+ * @returns Node ID or null
170
+ */
171
+ function findNodeByClassType(workflow, classType) {
172
+ for (const nodeId in workflow) {
173
+ if (workflow[nodeId] && workflow[nodeId].class_type === classType) {
174
+ return nodeId;
175
+ }
176
+ }
177
+ return null;
178
+ }
179
+ /**
180
+ * Update workflow parameters
181
+ * @param workflow - ComfyUI workflow object
182
+ * @param params - Parameters object
183
+ * @returns Updated workflow
184
+ */
185
+ function updateWorkflow(workflow, params) {
186
+ const updatedWorkflow = JSON.parse(JSON.stringify(workflow));
187
+ // Find and update positive and negative prompt nodes
188
+ const clipTextEncodeNodes = [];
189
+ for (const nodeId in updatedWorkflow) {
190
+ if (updatedWorkflow[nodeId] && updatedWorkflow[nodeId].class_type === 'CLIPTextEncode') {
191
+ clipTextEncodeNodes.push(nodeId);
192
+ }
193
+ }
194
+ if (clipTextEncodeNodes.length >= 1) {
195
+ updatedWorkflow[clipTextEncodeNodes[0]].inputs.text = params.prompt;
196
+ }
197
+ if (clipTextEncodeNodes.length >= 2) {
198
+ updatedWorkflow[clipTextEncodeNodes[1]].inputs.text = params.negative_prompt;
199
+ }
200
+ // Find and update image size node
201
+ const latentNodeId = findNodeByClassType(updatedWorkflow, 'EmptyLatentImage') ||
202
+ findNodeByClassType(updatedWorkflow, 'EmptySD3LatentImage');
203
+ if (latentNodeId && updatedWorkflow[latentNodeId].inputs) {
204
+ updatedWorkflow[latentNodeId].inputs.width = params.width;
205
+ updatedWorkflow[latentNodeId].inputs.height = params.height;
206
+ }
207
+ // Find and update sampling parameters node
208
+ const samplerNodeId = findNodeByClassType(updatedWorkflow, 'KSampler');
209
+ if (samplerNodeId && updatedWorkflow[samplerNodeId].inputs) {
210
+ updatedWorkflow[samplerNodeId].inputs.steps = params.steps;
211
+ updatedWorkflow[samplerNodeId].inputs.cfg = params.cfg;
212
+ updatedWorkflow[samplerNodeId].inputs.seed = params.seed;
213
+ }
214
+ // Find and update edit instruction node (for image editing workflows)
215
+ const primitiveNodeId = findNodeByClassType(updatedWorkflow, 'PrimitiveStringMultiline');
216
+ if (primitiveNodeId && updatedWorkflow[primitiveNodeId].inputs) {
217
+ updatedWorkflow[primitiveNodeId].inputs.value = params.prompt;
218
+ }
219
+ return updatedWorkflow;
220
+ }
221
+ /**
222
+ * Extract image information from output nodes
223
+ * @param outputs - ComfyUI output object
224
+ * @param url - ComfyUI server URL
225
+ * @returns Array of image information
226
+ */
227
+ function extractImagesFromOutputs(outputs, url) {
228
+ const images = [];
229
+ for (const nodeId in outputs) {
230
+ const nodeOutput = outputs[nodeId];
231
+ if (!nodeOutput || typeof nodeOutput !== 'object') {
232
+ continue;
233
+ }
234
+ const nodeImages = nodeOutput.images;
235
+ if (!nodeImages) {
236
+ continue;
237
+ }
238
+ if (!Array.isArray(nodeImages) || nodeImages.length === 0) {
239
+ continue;
240
+ }
241
+ for (const image of nodeImages) {
242
+ const imageInfo = processImage(image, nodeId, url);
243
+ if (imageInfo) {
244
+ images.push(imageInfo);
245
+ }
246
+ }
247
+ }
248
+ return images;
249
+ }
250
+ /**
251
+ * Process single image object
252
+ * @param image - Image object
253
+ * @param nodeId - Node ID
254
+ * @param url - ComfyUI server URL
255
+ * @returns Processed image information or null
256
+ */
257
+ function processImage(image, nodeId, url) {
258
+ if (!image || typeof image !== 'object') {
259
+ logger.warn(`Invalid image object in node ${nodeId}`);
260
+ return null;
261
+ }
262
+ const imageObj = image;
263
+ const filename = imageObj.filename;
264
+ const subfolder = imageObj.subfolder || '';
265
+ const type = imageObj.type || 'output';
266
+ if (!filename) {
267
+ logger.warn(`Image missing filename in node ${nodeId}`);
268
+ return null;
269
+ }
270
+ return {
271
+ filename: filename,
272
+ subfolder: subfolder,
273
+ type: type,
274
+ url: `${url}/view?filename=${filename}&subfolder=${subfolder}&type=${type}`
275
+ };
276
+ }
277
+ /**
278
+ * Execute ComfyUI workflow
279
+ * @param workflow - ComfyUI workflow object
280
+ * @param comfyUiUrl - ComfyUI server URL
281
+ * @param signal - Optional AbortSignal for cancelling the request
282
+ */
283
+ async function executeComfyUIWorkflow(workflow, comfyUiUrl, signal) {
284
+ const url = comfyUiUrl || DEFAULT_COMFYUI_URL;
285
+ // Create new AbortController for this execution if not provided
286
+ const abortController = signal ? null : new AbortController();
287
+ const effectiveSignal = signal || abortController?.signal;
288
+ if (abortController) {
289
+ currentAbortController = abortController;
290
+ }
291
+ try {
292
+ // Check if already aborted
293
+ if (effectiveSignal?.aborted) {
294
+ throw new Error('Workflow execution was cancelled before starting');
295
+ }
296
+ // 1. Queue prompt
297
+ let promptResponse;
298
+ try {
299
+ promptResponse = await httpClient.post(`${url}/prompt`, {
300
+ prompt: workflow
301
+ }, {
302
+ timeout: QUEUE_REQUEST_TIMEOUT,
303
+ abortSignal: effectiveSignal,
304
+ });
305
+ }
306
+ catch (error) {
307
+ if (error instanceof Error) {
308
+ if (error.name === 'CanceledError' || effectiveSignal?.aborted) {
309
+ throw new Error('Workflow execution was cancelled');
310
+ }
311
+ const httpError = error;
312
+ const errorCode = httpError.code;
313
+ if (errorCode === 'ECONNREFUSED') {
314
+ throw new Error(`Failed to connect to ComfyUI server at ${url}. Please check if the server is running.`);
315
+ }
316
+ else if (errorCode === 'ETIMEDOUT' || errorCode === 'ECONNABORTED') {
317
+ throw new Error(`Connection timeout while connecting to ComfyUI server at ${url}.`);
318
+ }
319
+ else if (httpError.response) {
320
+ throw new Error(`ComfyUI server returned error: ${httpError.response.statusCode} ${httpError.response.statusMessage}`);
321
+ }
322
+ else {
323
+ throw new Error(`Failed to queue workflow: ${error.message}`);
324
+ }
325
+ }
326
+ throw new Error(`Failed to queue workflow: ${String(error)}`);
327
+ }
328
+ if (!promptResponse || !promptResponse.prompt_id) {
329
+ throw new Error('Invalid response from ComfyUI server: missing prompt_id');
330
+ }
331
+ const promptId = promptResponse.prompt_id;
332
+ logger.info(`Workflow queued with ID: ${promptId}`);
333
+ // 2. Poll for results
334
+ let attempts = 0;
335
+ while (attempts < POLLING_MAX_ATTEMPTS) {
336
+ // Check if aborted before each polling attempt
337
+ if (effectiveSignal?.aborted) {
338
+ throw new Error('Workflow execution was cancelled');
339
+ }
340
+ await delay(POLLING_INTERVAL_MS);
341
+ // Check history
342
+ let historyResponse;
343
+ try {
344
+ historyResponse = await httpClient.get(`${url}/history/${promptId}`, {
345
+ timeout: HISTORY_REQUEST_TIMEOUT,
346
+ abortSignal: effectiveSignal,
347
+ });
348
+ }
349
+ catch (error) {
350
+ if (effectiveSignal?.aborted || (error instanceof Error && error.name === 'CanceledError')) {
351
+ throw new Error('Workflow execution was cancelled');
352
+ }
353
+ const httpError = error instanceof Error ? error : undefined;
354
+ const errorCode = httpError?.code;
355
+ const errorMessage = error instanceof Error ? error.message : String(error);
356
+ if (errorCode === 'ECONNREFUSED') {
357
+ throw new Error(`Lost connection to ComfyUI server at ${url}.`);
358
+ }
359
+ else if (errorCode === 'ETIMEDOUT' || errorCode === 'ECONNABORTED') {
360
+ logger.warn(`Timeout checking history (attempt ${attempts + 1}/${POLLING_MAX_ATTEMPTS})`);
361
+ attempts++;
362
+ continue;
363
+ }
364
+ else {
365
+ logger.warn(`Error checking history: ${errorMessage}`);
366
+ attempts++;
367
+ continue;
368
+ }
369
+ }
370
+ if (historyResponse && historyResponse[promptId]) {
371
+ const outputs = historyResponse[promptId].outputs;
372
+ if (outputs) {
373
+ const images = extractImagesFromOutputs(outputs, url);
374
+ if (images.length === 0) {
375
+ throw new Error('Workflow completed but no images were generated');
376
+ }
377
+ return {
378
+ success: true,
379
+ prompt_id: promptId,
380
+ images: images
381
+ };
382
+ }
383
+ }
384
+ attempts++;
385
+ }
386
+ throw new Error(`Workflow execution timeout after ${POLLING_MAX_ATTEMPTS} seconds (prompt_id: ${promptId}). The workflow may still be running on ComfyUI server.`);
387
+ }
388
+ finally {
389
+ // Clean up AbortController
390
+ if (abortController) {
391
+ currentAbortController = null;
392
+ }
393
+ }
394
+ }
395
+ /**
396
+ * Main function: Handle tool invocation
397
+ * @param query - User query text
398
+ * @param options - Optional configuration parameters
399
+ * @param options.comfyUiUrl - ComfyUI server URL
400
+ * @param options.workflowConfig - Workflow configuration options
401
+ */
402
+ async function handleToolInput(query, options = {}) {
403
+ try {
404
+ const { comfyUiUrl, workflowConfig } = options;
405
+ logger.info(`Received query: ${query}`);
406
+ if (comfyUiUrl) {
407
+ logger.info(`Using ComfyUI URL: ${comfyUiUrl}`);
408
+ }
409
+ // Parse input parameters
410
+ const params = parseInput(query);
411
+ logger.debug('Parsed parameters:', JSON.stringify(params, null, 2));
412
+ // Get workflow template from configuration
413
+ const workflowTemplate = (0, workflowConfig_1.getWorkflowTemplate)(workflowConfig);
414
+ // Update workflow
415
+ const workflow = updateWorkflow(workflowTemplate, params);
416
+ // Execute workflow
417
+ const result = await executeComfyUIWorkflow(workflow, comfyUiUrl);
418
+ logger.info(`Generated ${result.images.length} image(s)`);
419
+ // Return result
420
+ return {
421
+ success: true,
422
+ message: `Successfully generated ${result.images.length} image(s) with prompt: "${params.prompt}"`,
423
+ data: {
424
+ prompt: params.prompt,
425
+ images: result.images.map(img => img.url),
426
+ parameters: params
427
+ }
428
+ };
429
+ }
430
+ catch (error) {
431
+ const errorMessage = error instanceof Error ? error.message : String(error);
432
+ logger.error('Error:', errorMessage);
433
+ return {
434
+ success: false,
435
+ error: errorMessage,
436
+ message: `Failed to generate image: ${errorMessage}`
437
+ };
438
+ }
439
+ }
440
+ //# sourceMappingURL=ComfyUIAgentTool.js.map