nodestorm-mcp 0.1.0

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.
@@ -0,0 +1,1655 @@
1
+ # Workflow Builder Agent Guide
2
+
3
+ **Purpose**: Enable an LLM agent to build executable NodeStorm workflow graphs from natural language prompts.
4
+
5
+ **Last Updated**: 2026-01-19
6
+
7
+ ---
8
+
9
+ ## 1. Quick Reference
10
+
11
+ ### Handle Naming Convention
12
+ Use these simple handle IDs (they are automatically normalized during import):
13
+
14
+ ```
15
+ sourceHandle: "output" # Standard output (sends data)
16
+ targetHandle: "input" # Standard input (receives data)
17
+ sourceHandle: "true" # Boolean true branch
18
+ sourceHandle: "false" # Boolean false branch
19
+ sourceHandle: "each" # Iteration (list/table)
20
+ ```
21
+
22
+ **Note**: Both simple (`"output"`) and prefixed (`"{nodeId}-output-handle"`) formats are accepted.
23
+
24
+ ### Edge Branch Types
25
+ | Branch Value | Used By | Purpose |
26
+ |-------------|---------|---------|
27
+ | `null` or omit | All nodes | Regular data flow |
28
+ | `"true"` | boolean | True condition path |
29
+ | `"false"` | boolean | False condition path |
30
+ | `"each"` | list, table | Iteration (run child for each item) |
31
+ | `"{case_name}"` | switch | Route to specific case |
32
+
33
+ ### Valid Node Types (EXACT VALUES)
34
+ **CRITICAL**: The `type` field must be one of these exact lowercase values:
35
+
36
+ ```
37
+ text, list, table, search, scraper, file, prompt, boolean, json, switch, wait, aggregate, http_request, email, google_drive, transcription, image_generation
38
+ ```
39
+
40
+ **DO NOT use category names** like "Data Sources" or "AI/LLM" - use the exact type values above.
41
+
42
+ | Category | Valid `type` Values |
43
+ |----------|---------------------|
44
+ | Data | `text`, `list`, `table`, `file` |
45
+ | Web | `search`, `scraper` |
46
+ | AI | `prompt`, `boolean`, `json` |
47
+ | Control | `switch`, `wait`, `aggregate` |
48
+ | Actions | `http_request`, `email` |
49
+ | Integrations | `google_drive` |
50
+ | Media | `transcription`, `image_generation` |
51
+
52
+ ### Minimal Valid Graph Template
53
+ ```json
54
+ {
55
+ "nodes": [
56
+ {
57
+ "id": "text-1",
58
+ "type": "text",
59
+ "position": { "x": 100, "y": 100 },
60
+ "data": { "label": "text", "input": "Hello world" }
61
+ }
62
+ ],
63
+ "edges": []
64
+ }
65
+ ```
66
+
67
+ ---
68
+
69
+ ## 2. Graph JSON Schema
70
+
71
+ ### 2.1 Top-Level Structure
72
+ ```json
73
+ {
74
+ "nodes": [ /* Array of Node objects */ ],
75
+ "edges": [ /* Array of Edge objects */ ]
76
+ }
77
+ ```
78
+
79
+ ### 2.2 Node Schema
80
+ ```typescript
81
+ interface Node {
82
+ id: string; // Unique identifier, format: "{type}-{uuid}" or custom
83
+ type: string; // One of the 19 supported node types
84
+ position: {
85
+ x: number; // Canvas X coordinate
86
+ y: number; // Canvas Y coordinate
87
+ };
88
+ data: {
89
+ label: string; // Display label (usually same as type)
90
+ // ... type-specific configuration fields
91
+ };
92
+ }
93
+ ```
94
+
95
+ ### 2.3 Edge Schema
96
+ ```typescript
97
+ interface Edge {
98
+ id: string; // Unique identifier, format: "edge-{uuid}"
99
+ source: string; // Source node ID
100
+ target: string; // Target node ID
101
+ sourceHandle: string; // Format: "{sourceNodeId}-{handleType}-handle"
102
+ targetHandle: string; // Format: "{targetNodeId}-input-handle"
103
+ branch?: string; // null | "true" | "false" | "each" | "{case_name}"
104
+ type: "custom"; // Required for ReactFlow rendering
105
+ markerEnd: { type: "arrowClosed" }; // Arrow at target end
106
+ style: { strokeWidth: 2 } | { strokeWidth: 2, strokeDasharray: "5,5" }; // Dashed for "each" edges
107
+ }
108
+ ```
109
+
110
+ ### 2.4 Position Guidelines
111
+ - **Horizontal spacing**: 300px between nodes
112
+ - **Vertical spacing**: 150px between parallel branches
113
+ - **Root nodes**: Start at x: 100, y: 100
114
+ - **Flow direction**: Left to right (increasing X)
115
+
116
+ ### 2.5 ID Generation
117
+ - Use format: `{type}-{random}` (e.g., `prompt-abc123`)
118
+ - IDs must be unique within the graph
119
+ - Edge IDs: `edge-{random}` or `e{index}`
120
+
121
+ ---
122
+
123
+ ## 3. Complete Node Type Catalog
124
+
125
+ ### 3.1 DATA NODES
126
+
127
+ #### `text` - Static Text Input
128
+ **Purpose**: Provide static text content to the workflow.
129
+
130
+ **Config Schema**:
131
+ ```json
132
+ {
133
+ "label": "text",
134
+ "input": "The static text content goes here"
135
+ }
136
+ ```
137
+
138
+ **Handles**:
139
+ - Input: `{id}-input-handle`
140
+ - Output: `{id}-output-handle`
141
+
142
+ **Output**: `{ text: "The static text content" }`
143
+
144
+ **Tip**: Most nodes (search, prompt, etc.) can work as root nodes with their own `input` field. Use `text` when you need a dedicated, editable text input at the start of a flow.
145
+
146
+ ---
147
+
148
+ #### `list` - List Extractor/Creator
149
+ **Purpose**: Extract list items using LLM OR enter items manually.
150
+
151
+ **Config Schema (Extract Mode)**:
152
+ ```json
153
+ {
154
+ "label": "list",
155
+ "mode": "extract",
156
+ "input": "Extract all company names mentioned"
157
+ }
158
+ ```
159
+
160
+ **Config Schema (Manual Mode)**:
161
+ ```json
162
+ {
163
+ "label": "list",
164
+ "mode": "manual",
165
+ "input": "item1\nitem2\nitem3"
166
+ }
167
+ ```
168
+
169
+ **Note**: Manual mode uses `mode: "manual"` and the `input` field (not `"manual_input"` or `"manual_list_input"`).
170
+
171
+ **Handles**:
172
+ - Input: `{id}-input-handle`
173
+ - Output: `{id}-output-handle`
174
+ - Each (iteration): `{id}-each-handle` (SOURCE)
175
+
176
+ **Output**: `{ items: [...], list: [...], count: N, text: "item1\nitem2..." }`
177
+
178
+ ---
179
+
180
+ #### `table` - Tabular Data
181
+ **Purpose**: Store and iterate over tabular data. Import CSV files, paste from clipboard, or build tables manually.
182
+
183
+ **Config Schema**:
184
+ ```json
185
+ {
186
+ "label": "table",
187
+ "columns": [
188
+ { "id": "col_1", "name": "Name", "type": "text" },
189
+ { "id": "col_2", "name": "Email", "type": "text" }
190
+ ],
191
+ "rows": [
192
+ { "col_1": "John Doe", "col_2": "john@example.com" },
193
+ { "col_1": "Jane Smith", "col_2": "jane@example.com" }
194
+ ],
195
+ "row_count": 2,
196
+ "storage_mode": "inline"
197
+ }
198
+ ```
199
+
200
+ **Config Options**:
201
+ | Option | Type | Default | Description |
202
+ |--------|------|---------|-------------|
203
+ | `columns` | array | `[]` | Column definitions with id, name, and type |
204
+ | `rows` | array | `[]` | Row data (inline storage) |
205
+ | `row_count` | number | `0` | Total row count (used for file storage display) |
206
+ | `storage_mode` | enum | `"inline"` | `"inline"` (in config) or `"file"` (UserFile) |
207
+ | `file_id` | string | `null` | UserFile ID when using file storage |
208
+
209
+ **Column Types**: `text`, `number`, `date`, `boolean`
210
+
211
+ **Handles**:
212
+ - Input: `{id}-input-handle`
213
+ - Output: `{id}-output-handle`
214
+ - Each (iteration): `{id}-each-handle` (SOURCE)
215
+
216
+ **Output**: `{ rows: [...], list: [...], columns: [...], count: N, text: "N rows" }`
217
+
218
+ **Iteration**: Connect `{id}-each-handle` to child with `branch: "each"` to iterate over rows. Each row is passed as an object with column values accessible by column ID.
219
+
220
+ **Storage**: Tables under 500KB are stored inline in node config. Larger tables automatically use UserFile storage.
221
+
222
+ **Limits**: Max 5000 rows, 50 columns, 500KB inline (then file storage)
223
+
224
+ ---
225
+
226
+ #### `search` - Web Search
227
+ **Purpose**: Search the web using Tavily AI Search (primary) or Brave Search (fallback).
228
+
229
+ **Config Schema**:
230
+ ```json
231
+ {
232
+ "label": "search",
233
+ "input": "Find information about...",
234
+ "useLlmCleaning": true,
235
+ "topic": "general",
236
+ "searchDepth": "basic",
237
+ "timeRange": "",
238
+ "includeDomains": [],
239
+ "excludeDomains": [],
240
+ "maxResults": 8,
241
+ "includeAnswer": false,
242
+ "modelConfig": {
243
+ "chat_model": "gpt-5.4-mini",
244
+ "temperature": 0.7,
245
+ "maxTokens": 100
246
+ }
247
+ }
248
+ ```
249
+
250
+ **Config Options**:
251
+ | Option | Type | Default | Description |
252
+ |--------|------|---------|-------------|
253
+ | `input` | string | "" | Search query or instructions |
254
+ | `useLlmCleaning` | boolean | `true` | AI optimizes query before search |
255
+ | `topic` | enum | `"general"` | `"general"`, `"news"`, `"finance"` |
256
+ | `searchDepth` | enum | `"basic"` | `"basic"` (1 credit), `"advanced"` (2 credits) |
257
+ | `timeRange` | enum | `""` | `""` (any), `"day"`, `"week"`, `"month"`, `"year"` |
258
+ | `includeDomains` | array | `[]` | Only include results from these domains |
259
+ | `excludeDomains` | array | `[]` | Exclude results from these domains |
260
+ | `maxResults` | number | `8` | Number of results (1-20) |
261
+ | `includeAnswer` | boolean | `false` | Include AI-generated answer summary |
262
+
263
+ **Handles**:
264
+ - Input: `{id}-input-handle`
265
+ - Output: `{id}-output-handle`
266
+
267
+ **Output**: `{ text: "formatted results", results: [...], query: "optimized query", provider: "tavily"|"brave" }`
268
+
269
+ **Notes**:
270
+ - Tavily is used as primary provider when `TAVILY_API_KEY` is configured
271
+ - Falls back to Brave Search if Tavily is unavailable or fails
272
+ - `useLlmCleaning: true` uses AI to optimize search query from natural language input
273
+ - `includeAnswer: true` adds an AI-generated summary at the top of results (Tavily only)
274
+
275
+ ---
276
+
277
+ #### `scraper` - Web Page Scraper
278
+ **Purpose**: Extract content from web pages.
279
+
280
+ **Config Schema**:
281
+ ```json
282
+ {
283
+ "label": "scraper",
284
+ "input": "https://example.com/page"
285
+ }
286
+ ```
287
+
288
+ **Handles**:
289
+ - Input: `{id}-input-handle`
290
+ - Output: `{id}-output-handle`
291
+
292
+ **Output**: `{ text: "cleaned HTML", html: "...", url: "..." }`
293
+
294
+ **Note**: URL must start with `http://` or `https://`. May take up to 1 minute.
295
+
296
+ ---
297
+
298
+ #### `file` - Persistent File Storage
299
+ **Purpose**: Read, update, or create persistent files (CSV, JSON, TXT, MD) that persist across workflows.
300
+
301
+ **Mode Inference** (automatic based on configuration):
302
+ | Has Parent | Has File | Mode |
303
+ |------------|----------|------|
304
+ | No | Yes | **Read** - Load file content, pass to children |
305
+ | Yes | Yes | **Update** - Update file with parent data |
306
+ | Yes | No | **Create** - Create new file from parent data |
307
+
308
+ **Config Schema (Read Mode)**:
309
+ ```json
310
+ {
311
+ "label": "file",
312
+ "file_id": "123",
313
+ "file_name": "contacts.csv",
314
+ "file_path": "/projects/contacts.csv"
315
+ }
316
+ ```
317
+
318
+ **Config Schema (Update Mode)**:
319
+ ```json
320
+ {
321
+ "label": "file",
322
+ "file_id": "123",
323
+ "file_name": "contacts.csv",
324
+ "file_path": "/projects/contacts.csv",
325
+ "update_instructions": "Append the new rows to the CSV"
326
+ }
327
+ ```
328
+
329
+ **Config Schema (Create Mode)**:
330
+ ```json
331
+ {
332
+ "label": "file",
333
+ "create_instructions": "Create a CSV with columns: name, email, status",
334
+ "folder_path": "/projects/"
335
+ }
336
+ ```
337
+
338
+ **Handles**:
339
+ - Input: `{id}-input-handle`
340
+ - Output: `{id}-output-handle`
341
+
342
+ **Output**:
343
+ ```json
344
+ {
345
+ "text": "File content or operation summary",
346
+ "file_path": "/projects/contacts.csv",
347
+ "file_id": "123",
348
+ "metadata": {
349
+ "name": "contacts.csv",
350
+ "content_type": "text/csv",
351
+ "size_bytes": 1024
352
+ }
353
+ }
354
+ ```
355
+
356
+ **Supported File Types**:
357
+ - `text/csv` - CSV files
358
+ - `application/json` - JSON files
359
+ - `text/plain` - Plain text files
360
+ - `text/markdown` - Markdown files
361
+
362
+ **Limits**: 10MB max file size, 1000 files per user
363
+
364
+ **Note**: File node differs from CSV node - file node persists data to storage, CSV node parses inline CSV text. Use file node when you need data to persist across workflow runs.
365
+
366
+ ---
367
+
368
+ ### 3.3 AI/LLM NODES
369
+
370
+ #### `prompt` - LLM Prompt
371
+ **Purpose**: Send prompts to language models and get responses.
372
+
373
+ **Config Schema**:
374
+ ```json
375
+ {
376
+ "label": "prompt",
377
+ "input": "Your prompt or instruction here",
378
+ "modelConfig": {
379
+ "provider": "openai",
380
+ "chat_model": "gpt-5.4-mini",
381
+ "systemMessage": "You are a helpful assistant.",
382
+ "temperature": 0.7,
383
+ "maxTokens": 400
384
+ }
385
+ }
386
+ ```
387
+
388
+ **Supported Models**:
389
+ | Provider | Models |
390
+ |----------|--------|
391
+ | `openai` | `gpt-5.4`, `gpt-5.4-mini`, `gpt-5.4-nano` |
392
+ | `anthropic` | `claude-3-5-sonnet-20241022`, `claude-3-5-haiku`, `claude-3-opus` |
393
+ | `google` | `gemini-1.5-pro`, `gemini-1.5-flash`, `gemini-2.0-flash` |
394
+
395
+ **Handles**:
396
+ - Input: `{id}-input-handle`
397
+ - Output: `{id}-output-handle`
398
+
399
+ **Output**: `{ messages: [{role, content}...], text: "LLM response" }`
400
+
401
+ **Auto-Chunking**: When input text exceeds 80,000 characters, the prompt node automatically:
402
+ 1. Splits input into chunks (500 char overlap for context continuity)
403
+ 2. Processes each chunk with the same prompt
404
+ 3. Concatenates results with double-newline separators
405
+
406
+ This eliminates the need for a separate `chunk` node.
407
+
408
+ ---
409
+
410
+ #### `boolean` - Conditional Branching
411
+ **Purpose**: Evaluate a yes/no question and route to true or false branch.
412
+
413
+ **Config Schema**:
414
+ ```json
415
+ {
416
+ "label": "boolean",
417
+ "input": "Is this a positive sentiment?",
418
+ "modelConfig": {
419
+ "chat_model": "gpt-5.4-mini"
420
+ }
421
+ }
422
+ ```
423
+
424
+ **Handles**:
425
+ - Input: `{id}-input-handle`
426
+ - True output: `{id}-true-handle` (SOURCE)
427
+ - False output: `{id}-false-handle` (SOURCE)
428
+
429
+ **Output**: `{ text: "true"|"false", boolean: true|false, messages: [...] }`
430
+
431
+ **Edge Usage**:
432
+ - True path: `sourceHandle: "{id}-true-handle", branch: "true"`
433
+ - False path: `sourceHandle: "{id}-false-handle", branch: "false"`
434
+
435
+ ---
436
+
437
+ #### `json` - JSON Extractor
438
+ **Purpose**: Extract structured JSON from text using LLM.
439
+
440
+ **Config Schema**:
441
+ ```json
442
+ {
443
+ "label": "json",
444
+ "input": "Extract name, email, and phone as JSON",
445
+ "modelConfig": {
446
+ "chat_model": "gpt-5.4-mini",
447
+ "temperature": 0.7,
448
+ "maxTokens": 1000
449
+ }
450
+ }
451
+ ```
452
+
453
+ **Handles**:
454
+ - Input: `{id}-input-handle`
455
+ - Output: `{id}-output-handle`
456
+
457
+ **Output**: `{ json: {...}, text: "pretty printed JSON" }`
458
+
459
+ ---
460
+
461
+ ### 3.4 LOGIC & CONTROL NODES
462
+
463
+ #### `switch` - Multi-Path Router
464
+ **Purpose**: Route execution to one of multiple paths based on conditions.
465
+
466
+ **Config Schema**:
467
+ ```json
468
+ {
469
+ "label": "switch",
470
+ "condition_type": "llm|regex|contains|equals",
471
+ "cases": [
472
+ {
473
+ "name": "positive",
474
+ "description": "Positive sentiment",
475
+ "value": "positive",
476
+ "pattern": "\\b(good|great|excellent)\\b"
477
+ },
478
+ {
479
+ "name": "negative",
480
+ "description": "Negative sentiment"
481
+ }
482
+ ],
483
+ "default_branch": "neutral",
484
+ "input_value": "",
485
+ "llm_prompt": "Classify the sentiment"
486
+ }
487
+ ```
488
+
489
+ **Condition Types**:
490
+ | Type | Description | Case Fields |
491
+ |------|-------------|-------------|
492
+ | `equals` | Exact match | `value` or `values` array |
493
+ | `contains` | Substring match | `value` or `values` array |
494
+ | `regex` | Pattern match | `pattern` |
495
+ | `llm` | AI classification | `name`, `description` |
496
+
497
+ **Handles**:
498
+ - Input: `{id}-input-handle`
499
+ - Case outputs: `{id}-output-handle` (one per case + default)
500
+
501
+ **Output**: `{ branch: "case_name", text: "Routed to: case_name", result: {...} }`
502
+
503
+ **Edge Usage**: `branch: "{case_name}"` routes to that case's path.
504
+
505
+ ---
506
+
507
+ #### `wait` - Execution Delay
508
+ **Purpose**: Pause workflow execution for a specified duration.
509
+
510
+ **Config Schema**:
511
+ ```json
512
+ {
513
+ "label": "wait",
514
+ "delay_type": "seconds|minutes|hours|until",
515
+ "delay_amount": 30,
516
+ "until_time": "2025-01-01T12:00:00Z"
517
+ }
518
+ ```
519
+
520
+ **Handles**:
521
+ - Input: `{id}-input-handle`
522
+ - Output: `{id}-output-handle`
523
+
524
+ **Output**: `{ success: true, text: "Waited 30 seconds", result: {...} }`
525
+
526
+ **Limits**: Maximum 7 days (168 hours)
527
+
528
+ ---
529
+
530
+ #### `aggregate` - Iteration Collector
531
+ **Purpose**: Collect all results from an iteration into a single array. **REQUIRED** to terminate iteration scope.
532
+
533
+ **Config Schema**:
534
+ ```json
535
+ {
536
+ "label": "aggregate"
537
+ }
538
+ ```
539
+
540
+ **Handles**:
541
+ - Input: `{id}-input-handle`
542
+ - Output: `{id}-output-handle`
543
+
544
+ **Output**: `{ result: [...], count: N }`
545
+
546
+ **CRITICAL**: Every iteration (list/table with `each` edge) MUST end with an aggregate node.
547
+
548
+ ---
549
+
550
+ ### 3.5 ACTION NODES
551
+
552
+ #### `http_request` - HTTP API Calls
553
+ **Purpose**: Make HTTP requests to external APIs.
554
+
555
+ **Config Schema**:
556
+ ```json
557
+ {
558
+ "label": "http_request",
559
+ "url": "https://api.example.com/endpoint",
560
+ "method": "GET|POST|PUT|PATCH|DELETE",
561
+ "headers": {
562
+ "Authorization": "Bearer token",
563
+ "Content-Type": "application/json"
564
+ },
565
+ "body": "{\"key\": \"value\"}",
566
+ "query_params": {
567
+ "param1": "value1"
568
+ },
569
+ "credential_id": null,
570
+ "timeout": 30
571
+ }
572
+ ```
573
+
574
+ **Handles**:
575
+ - Input: `{id}-input-handle`
576
+ - Output: `{id}-output-handle`
577
+
578
+ **Output**: `{ body: {...}, status: 200, headers: {...}, success: true, text: "..." }`
579
+
580
+ **Limits**: Max 30 second timeout, 1MB response size
581
+
582
+ ---
583
+
584
+ #### `email` - Send Email
585
+ **Purpose**: Send email notifications to the workflow user.
586
+
587
+ **Config Schema**:
588
+ ```json
589
+ {
590
+ "label": "email",
591
+ "subject": "Flow Results: {{parent.text}}",
592
+ "body": "Here are your results:\n\n{{parent.result}}",
593
+ "format": "plain|html"
594
+ }
595
+ ```
596
+
597
+ **Variable Interpolation**: Use `{{parent.key}}` to insert parent output values.
598
+
599
+ **Handles**:
600
+ - Input: `{id}-input-handle`
601
+ - Output: `{id}-output-handle`
602
+
603
+ **Output**: `{ sent: true, message_id: "...", recipient: "user@email.com", timestamp: "..." }`
604
+
605
+ **Security**: Email is ALWAYS sent to the authenticated user only.
606
+ **Limits**: 50 emails per day per user.
607
+
608
+ ---
609
+
610
+ ### 3.6 INTEGRATION NODES
611
+
612
+ #### `google_drive` - Google Drive
613
+ **Purpose**: Manage files and folders in Google Drive.
614
+
615
+ **Config Schema**:
616
+ ```json
617
+ {
618
+ "label": "google_drive",
619
+ "operation": "list_files|get_metadata|download_file|upload_file|update_file|create_folder|copy_file|move_file|delete_file|search_files",
620
+ "folder_id": "...",
621
+ "file_id": "...",
622
+ "file_name": "...",
623
+ "file_content": "...",
624
+ "query": "..."
625
+ }
626
+ ```
627
+
628
+ **Handles**:
629
+ - Input: `{id}-input-handle`
630
+ - Output: `{id}-output-handle`
631
+
632
+ ---
633
+
634
+ ### 3.7 MEDIA NODES
635
+
636
+ #### `transcription` - Audio to Text
637
+ **Purpose**: Convert audio to text using Whisper or Google STT.
638
+
639
+ **Config Schema**:
640
+ ```json
641
+ {
642
+ "label": "transcription",
643
+ "provider": "openai|google",
644
+ "audio_source": "url|content",
645
+ "audio_url": "https://example.com/audio.mp3",
646
+ "language": "auto|en|es|fr|de|...",
647
+ "output_format": "text|json|verbose_json|srt|vtt",
648
+ "prompt": "Optional context prompt"
649
+ }
650
+ ```
651
+
652
+ **Handles**:
653
+ - Input: `{id}-input-handle`
654
+ - Output: `{id}-output-handle`
655
+
656
+ **Output**: `{ text: "transcribed text", success: true, ... }`
657
+
658
+ **Supported Formats**: mp3, mp4, wav, m4a, webm, flac, ogg
659
+ **Limits**: 25MB max file size (OpenAI)
660
+
661
+ ---
662
+
663
+ #### `image_generation` - AI Image Creation
664
+ **Purpose**: Generate images using DALL-E 3 or Imagen 3.
665
+
666
+ **Config Schema**:
667
+ ```json
668
+ {
669
+ "label": "image_generation",
670
+ "provider": "openai|google",
671
+ "operation": "generate|edit|variation",
672
+ "prompt": "A serene mountain landscape at sunset",
673
+ "size": "1024x1024|1792x1024|1024x1792",
674
+ "aspect_ratio": "1:1|16:9|9:16|4:3|3:4",
675
+ "quality": "standard|hd",
676
+ "style": "vivid|natural",
677
+ "count": 1,
678
+ "negative_prompt": "blurry, low quality"
679
+ }
680
+ ```
681
+
682
+ **Provider-Specific Options**:
683
+ | Option | OpenAI (DALL-E 3) | Google (Imagen 3) |
684
+ |--------|-------------------|-------------------|
685
+ | Size | `size` field | `aspect_ratio` field |
686
+ | Quality | `quality`, `style` | N/A |
687
+ | Negative | N/A | `negative_prompt` |
688
+
689
+ **Handles**:
690
+ - Input: `{id}-input-handle`
691
+ - Output: `{id}-output-handle`
692
+
693
+ **Output**: `{ images: [{url, revised_prompt}], image_count: N, success: true }`
694
+
695
+ ---
696
+
697
+ ---
698
+
699
+ ## 4. Edge Connection Rules
700
+
701
+ ### 4.1 Handle Types
702
+ - **Source handles** (output): Can only connect TO target handles
703
+ - **Target handles** (input): Can only receive FROM source handles
704
+
705
+ ### 4.2 Handle Naming Convention
706
+ ```
707
+ Standard output: {nodeId}-output-handle
708
+ Standard input: {nodeId}-input-handle
709
+ Boolean true: {nodeId}-true-handle
710
+ Boolean false: {nodeId}-false-handle
711
+ Each (iteration): {nodeId}-each-handle
712
+ ```
713
+
714
+ ### 4.3 Branch Property Rules
715
+ | Source Node Type | Branch Value | Meaning |
716
+ |------------------|--------------|---------|
717
+ | Any (regular) | `null` or omit | Standard data flow |
718
+ | `boolean` | `"true"` | True condition path |
719
+ | `boolean` | `"false"` | False condition path |
720
+ | `list`, `table` | `"each"` | Iterate over items |
721
+ | `switch` | `"{case_name}"` | Route to named case |
722
+
723
+ ### 4.4 Multi-Input Nodes
724
+ - Multiple parent nodes CAN connect to the same target handle
725
+ - Messages from all parents are merged automatically
726
+ - Use for combining data from multiple sources
727
+
728
+ ### 4.5 Edge Example
729
+ ```json
730
+ {
731
+ "id": "edge-1",
732
+ "source": "prompt-1",
733
+ "target": "boolean-1",
734
+ "sourceHandle": "prompt-1-output-handle",
735
+ "targetHandle": "boolean-1-input-handle",
736
+ "type": "custom",
737
+ "markerEnd": { "type": "arrowClosed" },
738
+ "style": { "strokeWidth": 2 }
739
+ }
740
+ ```
741
+
742
+ For iteration edges (with `branch: "each"`), use dashed style:
743
+ ```json
744
+ {
745
+ "id": "edge-iter",
746
+ "source": "list-1",
747
+ "target": "prompt-1",
748
+ "sourceHandle": "list-1-each-handle",
749
+ "targetHandle": "prompt-1-input-handle",
750
+ "branch": "each",
751
+ "type": "custom",
752
+ "markerEnd": { "type": "arrowClosed" },
753
+ "style": { "strokeWidth": 2, "strokeDasharray": "5,5" }
754
+ }
755
+ ```
756
+
757
+ ---
758
+
759
+ ## 5. Iteration Scope Rules
760
+
761
+ ### 5.1 Iteration Sources
762
+ Only these node types can start an iteration:
763
+ - `list` (via `{id}-each-handle`)
764
+ - `table` (via `{id}-each-handle`)
765
+
766
+ ### 5.2 Starting Iteration
767
+ ```json
768
+ {
769
+ "id": "edge-iter",
770
+ "source": "list-1",
771
+ "target": "prompt-1",
772
+ "sourceHandle": "list-1-each-handle",
773
+ "targetHandle": "prompt-1-input-handle",
774
+ "branch": "each",
775
+ "type": "custom",
776
+ "markerEnd": { "type": "arrowClosed" },
777
+ "style": { "strokeWidth": 2, "strokeDasharray": "5,5" }
778
+ }
779
+ ```
780
+
781
+ ### 5.3 Iteration Scope
782
+ - **Starts**: When an edge with `branch: "each"` connects to a node
783
+ - **Continues**: All downstream nodes until an aggregate node
784
+ - **Ends**: At the `aggregate` node
785
+
786
+ ### 5.4 Aggregate Node Requirement
787
+ **CRITICAL**: Every iteration MUST terminate with an `aggregate` node.
788
+
789
+ ```
790
+ list --[each]--> prompt --> aggregate
791
+ ```
792
+
793
+ Without aggregate:
794
+ - Results are lost
795
+ - Downstream nodes never execute
796
+ - Workflow hangs
797
+
798
+ ### 5.5 What Happens During Iteration
799
+ 1. List/table has 10 items
800
+ 2. Child node runs 10 times (once per item)
801
+ 3. Each run gets the current item's data as context
802
+ 4. Aggregate collects all 10 results into an array
803
+ 5. Downstream nodes receive the aggregated array
804
+
805
+ ---
806
+
807
+ ## 6. Common Workflow Patterns
808
+
809
+ ### 6.1 Linear Chain
810
+ ```
811
+ text --> prompt
812
+ ```
813
+ Simple sequential processing. The last node's output is visible in execution results.
814
+
815
+ ### 6.2 Conditional Branching
816
+ ```
817
+ prompt --> boolean --[true]--> promptA
818
+ \--[false]--> promptB
819
+ ```
820
+ Route based on yes/no decision.
821
+
822
+ ### 6.3 Iteration Pattern
823
+ ```
824
+ list --[each]--> prompt --> aggregate
825
+ ```
826
+ Process each item and combine results.
827
+
828
+ ### 6.4 Multi-Path Switch
829
+ ```
830
+ prompt --> switch --[case1]--> action1
831
+ \--[case2]--> action2
832
+ \--[default]--> action3
833
+ ```
834
+ Route to one of many paths.
835
+
836
+ ### 6.5 Data Enrichment
837
+ ```
838
+ table --[each]--> scraper --> prompt --> aggregate
839
+ ```
840
+ Fetch data for each row, process, combine.
841
+
842
+ ### 6.6 Multi-Input Merge
843
+ ```
844
+ promptA ----\
845
+ --> prompt
846
+ promptB ----/
847
+ ```
848
+ Combine outputs from multiple nodes.
849
+
850
+ ---
851
+
852
+ ## 7. Example Workflows
853
+
854
+ ### 7.1 Simple Prompt Chain
855
+ **User Request**: "Create a workflow that takes a question and answers it"
856
+
857
+ ```json
858
+ {
859
+ "nodes": [
860
+ {
861
+ "id": "text-1",
862
+ "type": "text",
863
+ "position": { "x": 100, "y": 100 },
864
+ "data": { "label": "text", "input": "What is machine learning?" }
865
+ },
866
+ {
867
+ "id": "prompt-1",
868
+ "type": "prompt",
869
+ "position": { "x": 400, "y": 100 },
870
+ "data": {
871
+ "label": "prompt",
872
+ "input": "Answer this question in simple terms",
873
+ "modelConfig": {
874
+ "provider": "openai",
875
+ "chat_model": "gpt-5.4-mini",
876
+ "systemMessage": "You are a helpful teacher.",
877
+ "temperature": 0.7,
878
+ "maxTokens": 400
879
+ }
880
+ }
881
+ }
882
+ ],
883
+ "edges": [
884
+ {
885
+ "id": "e1",
886
+ "source": "text-1",
887
+ "target": "prompt-1",
888
+ "sourceHandle": "text-1-output-handle",
889
+ "targetHandle": "prompt-1-input-handle",
890
+ "type": "custom",
891
+ "markerEnd": { "type": "arrowClosed" },
892
+ "style": { "strokeWidth": 2 }
893
+ }
894
+ ]
895
+ }
896
+ ```
897
+
898
+ ---
899
+
900
+ ### 7.2 URL Scraping with Iteration
901
+ **User Request**: "Scrape 3 URLs and summarize each page"
902
+
903
+ ```json
904
+ {
905
+ "nodes": [
906
+ {
907
+ "id": "list-1",
908
+ "type": "list",
909
+ "position": { "x": 100, "y": 100 },
910
+ "data": {
911
+ "label": "list",
912
+ "mode": "manual",
913
+ "input": "https://example.com/page1\nhttps://example.com/page2\nhttps://example.com/page3"
914
+ }
915
+ },
916
+ {
917
+ "id": "scraper-1",
918
+ "type": "scraper",
919
+ "position": { "x": 400, "y": 100 },
920
+ "data": { "label": "scraper", "input": "" }
921
+ },
922
+ {
923
+ "id": "prompt-1",
924
+ "type": "prompt",
925
+ "position": { "x": 700, "y": 100 },
926
+ "data": {
927
+ "label": "prompt",
928
+ "input": "Summarize this web page content in 2-3 sentences",
929
+ "modelConfig": {
930
+ "provider": "openai",
931
+ "chat_model": "gpt-5.4-mini",
932
+ "temperature": 0.5,
933
+ "maxTokens": 200
934
+ }
935
+ }
936
+ },
937
+ {
938
+ "id": "aggregate-1",
939
+ "type": "aggregate",
940
+ "position": { "x": 1000, "y": 100 },
941
+ "data": { "label": "aggregate" }
942
+ }
943
+ ],
944
+ "edges": [
945
+ {
946
+ "id": "e1",
947
+ "source": "list-1",
948
+ "target": "scraper-1",
949
+ "sourceHandle": "list-1-each-handle",
950
+ "targetHandle": "scraper-1-input-handle",
951
+ "branch": "each",
952
+ "type": "custom",
953
+ "markerEnd": { "type": "arrowClosed" },
954
+ "style": { "strokeWidth": 2, "strokeDasharray": "5,5" }
955
+ },
956
+ {
957
+ "id": "e2",
958
+ "source": "scraper-1",
959
+ "target": "prompt-1",
960
+ "sourceHandle": "scraper-1-output-handle",
961
+ "targetHandle": "prompt-1-input-handle",
962
+ "type": "custom",
963
+ "markerEnd": { "type": "arrowClosed" },
964
+ "style": { "strokeWidth": 2 }
965
+ },
966
+ {
967
+ "id": "e3",
968
+ "source": "prompt-1",
969
+ "target": "aggregate-1",
970
+ "sourceHandle": "prompt-1-output-handle",
971
+ "targetHandle": "aggregate-1-input-handle",
972
+ "type": "custom",
973
+ "markerEnd": { "type": "arrowClosed" },
974
+ "style": { "strokeWidth": 2 }
975
+ }
976
+ ]
977
+ }
978
+ ```
979
+
980
+ ---
981
+
982
+ ### 7.3 Conditional Email Notification
983
+ **User Request**: "Analyze sentiment and send email only if positive"
984
+
985
+ ```json
986
+ {
987
+ "nodes": [
988
+ {
989
+ "id": "text-1",
990
+ "type": "text",
991
+ "position": { "x": 100, "y": 100 },
992
+ "data": { "label": "text", "input": "I absolutely love this product! It exceeded my expectations." }
993
+ },
994
+ {
995
+ "id": "boolean-1",
996
+ "type": "boolean",
997
+ "position": { "x": 400, "y": 100 },
998
+ "data": {
999
+ "label": "boolean",
1000
+ "input": "Is this feedback positive?",
1001
+ "modelConfig": { "chat_model": "gpt-5.4-mini" }
1002
+ }
1003
+ },
1004
+ {
1005
+ "id": "email-1",
1006
+ "type": "email",
1007
+ "position": { "x": 700, "y": 50 },
1008
+ "data": {
1009
+ "label": "email",
1010
+ "subject": "Positive Feedback Received!",
1011
+ "body": "Great news! We received positive feedback:\n\n{{parent.text}}",
1012
+ "format": "plain"
1013
+ }
1014
+ },
1015
+ {
1016
+ "id": "prompt-1",
1017
+ "type": "prompt",
1018
+ "position": { "x": 700, "y": 200 },
1019
+ "data": {
1020
+ "label": "prompt",
1021
+ "input": "Generate a response acknowledging this negative feedback",
1022
+ "modelConfig": { "chat_model": "gpt-5.4-mini", "maxTokens": 200 }
1023
+ }
1024
+ }
1025
+ ],
1026
+ "edges": [
1027
+ {
1028
+ "id": "e1",
1029
+ "source": "text-1",
1030
+ "target": "boolean-1",
1031
+ "sourceHandle": "text-1-output-handle",
1032
+ "targetHandle": "boolean-1-input-handle",
1033
+ "type": "custom",
1034
+ "markerEnd": { "type": "arrowClosed" },
1035
+ "style": { "strokeWidth": 2 }
1036
+ },
1037
+ {
1038
+ "id": "e2",
1039
+ "source": "boolean-1",
1040
+ "target": "email-1",
1041
+ "sourceHandle": "boolean-1-true-handle",
1042
+ "targetHandle": "email-1-input-handle",
1043
+ "branch": "true",
1044
+ "type": "custom",
1045
+ "markerEnd": { "type": "arrowClosed" },
1046
+ "style": { "strokeWidth": 2 }
1047
+ },
1048
+ {
1049
+ "id": "e3",
1050
+ "source": "boolean-1",
1051
+ "target": "prompt-1",
1052
+ "sourceHandle": "boolean-1-false-handle",
1053
+ "targetHandle": "prompt-1-input-handle",
1054
+ "branch": "false",
1055
+ "type": "custom",
1056
+ "markerEnd": { "type": "arrowClosed" },
1057
+ "style": { "strokeWidth": 2 }
1058
+ }
1059
+ ]
1060
+ }
1061
+ ```
1062
+
1063
+ ---
1064
+
1065
+ ### 7.4 API Integration with HTTP Request
1066
+ **User Request**: "Fetch data from an API and process it"
1067
+
1068
+ ```json
1069
+ {
1070
+ "nodes": [
1071
+ {
1072
+ "id": "http-1",
1073
+ "type": "http_request",
1074
+ "position": { "x": 100, "y": 100 },
1075
+ "data": {
1076
+ "label": "http_request",
1077
+ "url": "https://api.example.com/data",
1078
+ "method": "GET",
1079
+ "headers": { "Accept": "application/json" },
1080
+ "timeout": 30
1081
+ }
1082
+ },
1083
+ {
1084
+ "id": "json-1",
1085
+ "type": "json",
1086
+ "position": { "x": 400, "y": 100 },
1087
+ "data": {
1088
+ "label": "json",
1089
+ "input": "Extract the name and status fields",
1090
+ "modelConfig": { "chat_model": "gpt-5.4-mini" }
1091
+ }
1092
+ }
1093
+ ],
1094
+ "edges": [
1095
+ {
1096
+ "id": "e1",
1097
+ "source": "http-1",
1098
+ "target": "json-1",
1099
+ "sourceHandle": "http-1-output-handle",
1100
+ "targetHandle": "json-1-input-handle",
1101
+ "type": "custom",
1102
+ "markerEnd": { "type": "arrowClosed" },
1103
+ "style": { "strokeWidth": 2 }
1104
+ }
1105
+ ]
1106
+ }
1107
+ ```
1108
+
1109
+ ---
1110
+
1111
+ ### 7.5 Multi-Path Switch Routing
1112
+ **User Request**: "Classify customer inquiries into categories and handle each differently"
1113
+
1114
+ ```json
1115
+ {
1116
+ "nodes": [
1117
+ {
1118
+ "id": "text-1",
1119
+ "type": "text",
1120
+ "position": { "x": 100, "y": 150 },
1121
+ "data": { "label": "text", "input": "I want to return my order #12345" }
1122
+ },
1123
+ {
1124
+ "id": "switch-1",
1125
+ "type": "switch",
1126
+ "position": { "x": 400, "y": 150 },
1127
+ "data": {
1128
+ "label": "switch",
1129
+ "condition_type": "llm",
1130
+ "cases": [
1131
+ { "name": "refund", "description": "Request for refund or return" },
1132
+ { "name": "shipping", "description": "Shipping or delivery question" },
1133
+ { "name": "product", "description": "Product information inquiry" }
1134
+ ],
1135
+ "default_branch": "general",
1136
+ "llm_prompt": "Classify this customer inquiry"
1137
+ }
1138
+ },
1139
+ {
1140
+ "id": "prompt-refund",
1141
+ "type": "prompt",
1142
+ "position": { "x": 700, "y": 50 },
1143
+ "data": {
1144
+ "label": "prompt",
1145
+ "input": "Generate a helpful refund/return process response",
1146
+ "modelConfig": { "chat_model": "gpt-5.4-mini", "maxTokens": 200 }
1147
+ }
1148
+ },
1149
+ {
1150
+ "id": "prompt-shipping",
1151
+ "type": "prompt",
1152
+ "position": { "x": 700, "y": 150 },
1153
+ "data": {
1154
+ "label": "prompt",
1155
+ "input": "Generate a shipping status response",
1156
+ "modelConfig": { "chat_model": "gpt-5.4-mini", "maxTokens": 200 }
1157
+ }
1158
+ },
1159
+ {
1160
+ "id": "prompt-general",
1161
+ "type": "prompt",
1162
+ "position": { "x": 700, "y": 250 },
1163
+ "data": {
1164
+ "label": "prompt",
1165
+ "input": "Generate a general customer support response",
1166
+ "modelConfig": { "chat_model": "gpt-5.4-mini", "maxTokens": 200 }
1167
+ }
1168
+ }
1169
+ ],
1170
+ "edges": [
1171
+ {
1172
+ "id": "e1",
1173
+ "source": "text-1",
1174
+ "target": "switch-1",
1175
+ "sourceHandle": "text-1-output-handle",
1176
+ "targetHandle": "switch-1-input-handle",
1177
+ "type": "custom",
1178
+ "markerEnd": { "type": "arrowClosed" },
1179
+ "style": { "strokeWidth": 2 }
1180
+ },
1181
+ {
1182
+ "id": "e2",
1183
+ "source": "switch-1",
1184
+ "target": "prompt-refund",
1185
+ "sourceHandle": "switch-1-output-handle",
1186
+ "targetHandle": "prompt-refund-input-handle",
1187
+ "branch": "refund",
1188
+ "type": "custom",
1189
+ "markerEnd": { "type": "arrowClosed" },
1190
+ "style": { "strokeWidth": 2 }
1191
+ },
1192
+ {
1193
+ "id": "e3",
1194
+ "source": "switch-1",
1195
+ "target": "prompt-shipping",
1196
+ "sourceHandle": "switch-1-output-handle",
1197
+ "targetHandle": "prompt-shipping-input-handle",
1198
+ "branch": "shipping",
1199
+ "type": "custom",
1200
+ "markerEnd": { "type": "arrowClosed" },
1201
+ "style": { "strokeWidth": 2 }
1202
+ },
1203
+ {
1204
+ "id": "e4",
1205
+ "source": "switch-1",
1206
+ "target": "prompt-general",
1207
+ "sourceHandle": "switch-1-output-handle",
1208
+ "targetHandle": "prompt-general-input-handle",
1209
+ "branch": "general",
1210
+ "type": "custom",
1211
+ "markerEnd": { "type": "arrowClosed" },
1212
+ "style": { "strokeWidth": 2 }
1213
+ }
1214
+ ]
1215
+ }
1216
+ ```
1217
+
1218
+ ---
1219
+
1220
+ ### 7.6 Web Search and Summary
1221
+ **User Request**: "Search for information and summarize the results"
1222
+
1223
+ ```json
1224
+ {
1225
+ "nodes": [
1226
+ {
1227
+ "id": "search-1",
1228
+ "type": "search",
1229
+ "position": { "x": 100, "y": 100 },
1230
+ "data": {
1231
+ "label": "search",
1232
+ "input": "Latest developments in artificial intelligence 2024",
1233
+ "useLlmCleaning": true,
1234
+ "maxResults": 5
1235
+ }
1236
+ },
1237
+ {
1238
+ "id": "prompt-1",
1239
+ "type": "prompt",
1240
+ "position": { "x": 400, "y": 100 },
1241
+ "data": {
1242
+ "label": "prompt",
1243
+ "input": "Summarize the key points from these search results in a concise paragraph",
1244
+ "modelConfig": {
1245
+ "chat_model": "gpt-5.4-mini",
1246
+ "temperature": 0.5,
1247
+ "maxTokens": 300
1248
+ }
1249
+ }
1250
+ }
1251
+ ],
1252
+ "edges": [
1253
+ {
1254
+ "id": "e1",
1255
+ "source": "search-1",
1256
+ "target": "prompt-1",
1257
+ "sourceHandle": "search-1-output-handle",
1258
+ "targetHandle": "prompt-1-input-handle",
1259
+ "type": "custom",
1260
+ "markerEnd": { "type": "arrowClosed" },
1261
+ "style": { "strokeWidth": 2 }
1262
+ }
1263
+ ]
1264
+ }
1265
+ ```
1266
+
1267
+ ---
1268
+
1269
+ ### 7.7 Image Generation
1270
+ **User Request**: "Generate an image based on a prompt"
1271
+
1272
+ ```json
1273
+ {
1274
+ "nodes": [
1275
+ {
1276
+ "id": "text-1",
1277
+ "type": "text",
1278
+ "position": { "x": 100, "y": 100 },
1279
+ "data": { "label": "text", "input": "A serene mountain landscape at sunset with snow-capped peaks" }
1280
+ },
1281
+ {
1282
+ "id": "image-1",
1283
+ "type": "image_generation",
1284
+ "position": { "x": 400, "y": 100 },
1285
+ "data": {
1286
+ "label": "image_generation",
1287
+ "provider": "openai",
1288
+ "operation": "generate",
1289
+ "prompt": "",
1290
+ "size": "1024x1024",
1291
+ "quality": "standard",
1292
+ "style": "vivid",
1293
+ "count": 1
1294
+ }
1295
+ }
1296
+ ],
1297
+ "edges": [
1298
+ {
1299
+ "id": "e1",
1300
+ "source": "text-1",
1301
+ "target": "image-1",
1302
+ "sourceHandle": "text-1-output-handle",
1303
+ "targetHandle": "image-1-input-handle",
1304
+ "type": "custom",
1305
+ "markerEnd": { "type": "arrowClosed" },
1306
+ "style": { "strokeWidth": 2 }
1307
+ }
1308
+ ]
1309
+ }
1310
+ ```
1311
+
1312
+ ---
1313
+
1314
+ ### 7.8 Table-Based Data Pipeline
1315
+ **User Request**: "Process a table of data with AI and send results via email"
1316
+
1317
+ ```json
1318
+ {
1319
+ "nodes": [
1320
+ {
1321
+ "id": "table-1",
1322
+ "type": "table",
1323
+ "position": { "x": 100, "y": 100 },
1324
+ "data": {
1325
+ "label": "table",
1326
+ "columns": [
1327
+ { "id": "col_1", "name": "Company", "type": "text" },
1328
+ { "id": "col_2", "name": "Description", "type": "text" }
1329
+ ],
1330
+ "rows": [
1331
+ { "col_1": "Acme Corp", "col_2": "Software development company" },
1332
+ { "col_1": "TechStart", "col_2": "AI startup focused on automation" },
1333
+ { "col_1": "DataFlow", "col_2": "Data analytics platform" }
1334
+ ],
1335
+ "storage_mode": "inline"
1336
+ }
1337
+ },
1338
+ {
1339
+ "id": "prompt-1",
1340
+ "type": "prompt",
1341
+ "position": { "x": 400, "y": 100 },
1342
+ "data": {
1343
+ "label": "prompt",
1344
+ "input": "Analyze this company and provide a brief competitive analysis",
1345
+ "modelConfig": { "chat_model": "gpt-5.4-mini", "maxTokens": 300 }
1346
+ }
1347
+ },
1348
+ {
1349
+ "id": "aggregate-1",
1350
+ "type": "aggregate",
1351
+ "position": { "x": 700, "y": 100 },
1352
+ "data": { "label": "aggregate" }
1353
+ },
1354
+ {
1355
+ "id": "email-1",
1356
+ "type": "email",
1357
+ "position": { "x": 1000, "y": 100 },
1358
+ "data": {
1359
+ "label": "email",
1360
+ "subject": "Company Analysis Report",
1361
+ "body": "Here are the analysis results:\n\n{{parent.text}}",
1362
+ "format": "plain"
1363
+ }
1364
+ }
1365
+ ],
1366
+ "edges": [
1367
+ {
1368
+ "id": "e1",
1369
+ "source": "table-1",
1370
+ "target": "prompt-1",
1371
+ "sourceHandle": "table-1-each-handle",
1372
+ "targetHandle": "prompt-1-input-handle",
1373
+ "branch": "each",
1374
+ "type": "custom",
1375
+ "markerEnd": { "type": "arrowClosed" },
1376
+ "style": { "strokeWidth": 2, "strokeDasharray": "5,5" }
1377
+ },
1378
+ {
1379
+ "id": "e2",
1380
+ "source": "prompt-1",
1381
+ "target": "aggregate-1",
1382
+ "sourceHandle": "prompt-1-output-handle",
1383
+ "targetHandle": "aggregate-1-input-handle",
1384
+ "type": "custom",
1385
+ "markerEnd": { "type": "arrowClosed" },
1386
+ "style": { "strokeWidth": 2 }
1387
+ },
1388
+ {
1389
+ "id": "e3",
1390
+ "source": "aggregate-1",
1391
+ "target": "email-1",
1392
+ "sourceHandle": "aggregate-1-output-handle",
1393
+ "targetHandle": "email-1-input-handle",
1394
+ "type": "custom",
1395
+ "markerEnd": { "type": "arrowClosed" },
1396
+ "style": { "strokeWidth": 2 }
1397
+ }
1398
+ ]
1399
+ }
1400
+ ```
1401
+
1402
+ ---
1403
+
1404
+ ## 8. Validation Checklist
1405
+
1406
+ ### Server-Side Validation (FlowValidator)
1407
+
1408
+ **Important**: Validation is now enforced server-side before execution via `FlowValidator`. The validator runs automatically when a flow is executed and returns detailed error messages if validation fails.
1409
+
1410
+ **Location**: `backend/app/services/flow_validator.rb`
1411
+
1412
+ **Error Format**: Errors include node IDs for easy debugging:
1413
+ - `[node-abc123] Missing required config: url`
1414
+ - `[node-xyz789] Unknown node type: invalid_type`
1415
+ - `Flow has no nodes`
1416
+ - `Flow contains a cycle`
1417
+
1418
+ **Validation Categories**:
1419
+ 1. **Structure**: Non-empty nodes array, unique IDs, required fields (id, type, data)
1420
+ 2. **Node Types**: Must be in TASK_MAP (all 17 valid node types)
1421
+ 3. **Connections**: Valid edges, no self-loops, no isolated nodes, DAG (no cycles), entry point exists
1422
+ 4. **Iteration Scope**: Each edges must reach aggregate terminator
1423
+ 5. **Config**: Truly required fields only; backend workers provide defaults for optional fields:
1424
+ - `prompt/boolean/json`: No required fields (backend defaults to gpt-5.4-mini)
1425
+ - `switch`: `cases` required (non-empty); `condition_type` validated only if present
1426
+ - `http_request`: `url` required; `method` validated only if present (defaults to GET)
1427
+ - `wait`: All fields optional (validated only if present, backend has defaults)
1428
+ - `list` (manual mode): `input` field required when `mode == "manual"`
1429
+ - `transcription/image_generation`: All fields optional (workers have defaults)
1430
+ 6. **Credentials**: OAuth connections for google_drive, API credentials for http_request
1431
+
1432
+ ### Pre-Output Checklist
1433
+
1434
+ Before outputting a graph, verify:
1435
+
1436
+ ### Node Validation
1437
+ - [ ] All nodes have unique IDs
1438
+ - [ ] All node types are valid (from the 19 supported types)
1439
+ - [ ] All required config fields are present
1440
+ - [ ] Position coordinates are numbers
1441
+ - [ ] `data.label` matches node `type`
1442
+
1443
+ ### Edge Validation
1444
+ - [ ] All edges have unique IDs
1445
+ - [ ] Source node exists in nodes array
1446
+ - [ ] Target node exists in nodes array
1447
+ - [ ] `sourceHandle` format: `{sourceNodeId}-{output|true|false|each}-handle`
1448
+ - [ ] `targetHandle` format: `{targetNodeId}-input-handle`
1449
+ - [ ] `type` is set to `"custom"` (required for rendering)
1450
+ - [ ] `markerEnd` is set to `{ "type": "arrowClosed" }`
1451
+ - [ ] `style` is set to `{ "strokeWidth": 2 }` (or with `"strokeDasharray": "5,5"` for iteration edges)
1452
+ - [ ] Branch value matches source node type:
1453
+ - `boolean`: `"true"` or `"false"`
1454
+ - `list`/`table` each: `"each"`
1455
+ - `switch`: case name or default branch
1456
+ - Other: `null` or omit
1457
+
1458
+ ### Graph Structure
1459
+ - [ ] No orphan nodes (all nodes connected except starting nodes)
1460
+ - [ ] No cycles (DAG requirement - data flows one direction)
1461
+ - [ ] Every iteration scope has an aggregate terminator
1462
+ - [ ] At least one root node (no incoming edges)
1463
+ - [ ] Boolean nodes have both true and false paths handled
1464
+
1465
+ ### Handle Compatibility
1466
+ - [ ] Each edge connects source handle to target handle
1467
+ - [ ] Boolean handles (`true-handle`, `false-handle`) only on boolean nodes
1468
+ - [ ] Each handles (`each-handle`) only on list/table nodes
1469
+ - [ ] Case handles (`case-{name}-handle`, `case-default-handle`) only on switch nodes
1470
+
1471
+ ---
1472
+
1473
+ ## 9. Common Pitfalls & Anti-Patterns
1474
+
1475
+ ### 9.1 Handle Naming Errors
1476
+ ```
1477
+ WRONG: sourceHandle: "output"
1478
+ RIGHT: sourceHandle: "prompt-1-output-handle"
1479
+
1480
+ WRONG: targetHandle: "prompt-1-output-handle"
1481
+ RIGHT: targetHandle: "prompt-1-input-handle"
1482
+ ```
1483
+
1484
+ ### 9.2 Missing Aggregate Node
1485
+ ```
1486
+ WRONG: list --[each]--> prompt
1487
+ RIGHT: list --[each]--> prompt --> aggregate
1488
+ ```
1489
+ **Always terminate iteration scopes with aggregate.**
1490
+
1491
+ ### 9.3 Incorrect Branch Values
1492
+ ```
1493
+ WRONG: branch: "True" (capital T)
1494
+ RIGHT: branch: "true" (lowercase)
1495
+
1496
+ WRONG: branch: "yes"
1497
+ RIGHT: branch: "true"
1498
+ ```
1499
+
1500
+ ### 9.4 Orphan Branches
1501
+ ```
1502
+ WRONG: Boolean with only true path, no false path handling
1503
+ RIGHT: Both paths should connect somewhere (even to same result)
1504
+ ```
1505
+
1506
+ ### 9.5 Using `text` Nodes Unnecessarily
1507
+ ```
1508
+ WRONG: text --> search --> prompt
1509
+ RIGHT: search --> prompt (search can be root, prompt output is visible)
1510
+ ```
1511
+ Most workflows work better without `text` as a prefix:
1512
+ - Start with the first meaningful node (search, prompt, list, etc.) as root
1513
+ - The last node's output is automatically visible in execution results
1514
+ - Use `text` only when you need to provide static input that users can edit
1515
+
1516
+ ### 9.6 Circular Dependencies
1517
+ ```
1518
+ WRONG: A --> B --> C --> A
1519
+ RIGHT: Ensure unidirectional flow (DAG)
1520
+ ```
1521
+
1522
+ ### 9.7 Each Edge Without Aggregate
1523
+ ```
1524
+ WRONG: list --[each]--> prompt --[each]--> scraper (nested each)
1525
+ RIGHT: list --[each]--> prompt --> aggregate --> list --[each]--> ...
1526
+ ```
1527
+ **Never nest iteration scopes. Always aggregate before starting new iteration.**
1528
+
1529
+ ### 9.8 Wrong Handle for Iteration
1530
+ ```
1531
+ WRONG: sourceHandle: "list-1-output-handle", branch: "each"
1532
+ RIGHT: sourceHandle: "list-1-each-handle", branch: "each"
1533
+ ```
1534
+
1535
+ ### 9.9 Model Config (Optional)
1536
+ ```
1537
+ OPTIONAL: { "input": "..." } (backend uses default model: gpt-5.4-mini)
1538
+ EXPLICIT: { "input": "...", "modelConfig": { "chat_model": "gpt-4" } }
1539
+ ```
1540
+ **Note**: Model config is optional for prompt/boolean/json nodes. The backend uses `gpt-5.4-mini` by default when not specified. Only specify `modelConfig` when you need a different model.
1541
+
1542
+ ---
1543
+
1544
+ ## 10. Natural Language Translation Guide
1545
+
1546
+ ### 10.1 Intent Keywords to Node Types
1547
+
1548
+ #### Data
1549
+ | Keywords | Node Type |
1550
+ |----------|-----------|
1551
+ | "static text", "constant", "fixed value" | `text` |
1552
+ | "list of", "multiple items", "array", "extract items" | `list` |
1553
+ | "table", "tabular data", "rows and columns" | `table` |
1554
+ | "file", "persistent", "save to file", "load file", "update file" | `file` |
1555
+
1556
+ #### Web
1557
+ | Keywords | Node Type |
1558
+ |----------|-----------|
1559
+ | "scrape", "extract from website", "web page" | `scraper` |
1560
+ | "search", "look up", "find online" | `search` |
1561
+
1562
+ #### AI Processing
1563
+ | Keywords | Node Type |
1564
+ |----------|-----------|
1565
+ | "summarize", "explain", "write", "generate text" | `prompt` |
1566
+ | "true/false", "yes/no", "decide if", "check if" | `boolean` |
1567
+ | "JSON", "structured data", "extract fields" | `json` |
1568
+ | "classify", "route", "which category" | `switch` |
1569
+
1570
+ #### Logic & Control
1571
+ | Keywords | Node Type |
1572
+ |----------|-----------|
1573
+ | "wait", "delay", "pause", "schedule" | `wait` |
1574
+ | "combine", "collect all", "merge results" | `aggregate` |
1575
+
1576
+ #### Actions
1577
+ | Keywords | Node Type |
1578
+ |----------|-----------|
1579
+ | "send email", "notify", "email" | `email` |
1580
+ | "API call", "HTTP request", "webhook", "fetch" | `http_request` |
1581
+
1582
+ #### Integrations
1583
+ | Keywords | Node Type |
1584
+ |----------|-----------|
1585
+ | "Google Drive", "files", "folders" | `google_drive` |
1586
+
1587
+ #### Media
1588
+ | Keywords | Node Type |
1589
+ |----------|-----------|
1590
+ | "transcribe", "audio to text", "speech" | `transcription` |
1591
+ | "generate image", "create picture", "DALL-E" | `image_generation` |
1592
+
1593
+ ### 10.2 Pattern Recognition
1594
+
1595
+ #### "For each X, do Y"
1596
+ ```
1597
+ list/table --[each]--> Y --> aggregate
1598
+ ```
1599
+
1600
+ #### "If X then A, else B"
1601
+ ```
1602
+ boolean --[true]--> A
1603
+ \--[false]--> B
1604
+ ```
1605
+
1606
+ #### "Scrape and summarize"
1607
+ ```
1608
+ scraper --> prompt
1609
+ ```
1610
+
1611
+ #### "Search and process"
1612
+ ```
1613
+ search --> prompt
1614
+ ```
1615
+
1616
+ ### 10.3 Example Translations
1617
+
1618
+ | User Request | Workflow Pattern |
1619
+ |--------------|------------------|
1620
+ | "Scrape 10 URLs and summarize each" | list (URLs) --[each]--> scraper --> prompt --> aggregate |
1621
+ | "Send email if sentiment is positive" | text --> boolean --[true]--> email |
1622
+ | "Process each item in my list" | list --[each]--> prompt --> aggregate |
1623
+ | "Generate an image from a description" | text --> image_generation |
1624
+ | "Search web and extract key facts" | search --> json |
1625
+ | "Wait 5 minutes then send email" | text --> wait --> email |
1626
+ | "Analyze each row in my table" | table --[each]--> prompt --> aggregate |
1627
+
1628
+ ---
1629
+
1630
+ ## Appendix: Supported Models Reference
1631
+
1632
+ ### OpenAI Models
1633
+ | Model ID | Description | Best For |
1634
+ |----------|-------------|----------|
1635
+ | `gpt-5.4` | Most capable | Complex reasoning, multi-step tool orchestration |
1636
+ | `gpt-5.4-mini` | Fast & efficient | General tasks (default) |
1637
+ | `gpt-5.4-nano` | Cheapest, fastest | Simple/no-tool prompts |
1638
+
1639
+ ### Anthropic Models
1640
+ | Model ID | Description |
1641
+ |----------|-------------|
1642
+ | `claude-3-5-sonnet-20241022` | Balanced performance (default) |
1643
+ | `claude-3-5-haiku-20241022` | Fast responses |
1644
+ | `claude-3-opus-20240229` | Most capable |
1645
+
1646
+ ### Google Models
1647
+ | Model ID | Description |
1648
+ |----------|-------------|
1649
+ | `gemini-1.5-pro` | Most capable |
1650
+ | `gemini-1.5-flash` | Fast (default) |
1651
+ | `gemini-2.0-flash-exp` | Latest fast (experimental) |
1652
+
1653
+ ---
1654
+
1655
+ **End of Guide**