@zerowidth/workbench-sdk 2.0.0-alpha.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.
- package/LICENSE +201 -0
- package/README.md +799 -0
- package/nodes/absolute/absolute.config.json +24 -0
- package/nodes/absolute/absolute.process.js +7 -0
- package/nodes/absolute/absolute.tests.json +38 -0
- package/nodes/absolute-value/absolute-value.config.json +24 -0
- package/nodes/absolute-value/absolute-value.process.js +5 -0
- package/nodes/absolute-value/absolute-value.tests.json +17 -0
- package/nodes/add/add.config.json +31 -0
- package/nodes/add/add.process.js +9 -0
- package/nodes/add/add.tests.json +41 -0
- package/nodes/airtable-create-record/airtable-create-record.config.json +64 -0
- package/nodes/airtable-create-record/airtable-create-record.process.js +44 -0
- package/nodes/airtable-create-record/airtable-create-record.tests.json +50 -0
- package/nodes/airtable-delete-record/airtable-delete-record.config.json +44 -0
- package/nodes/airtable-delete-record/airtable-delete-record.process.js +34 -0
- package/nodes/airtable-delete-record/airtable-delete-record.tests.json +23 -0
- package/nodes/airtable-get-record/airtable-get-record.config.json +56 -0
- package/nodes/airtable-get-record/airtable-get-record.process.js +36 -0
- package/nodes/airtable-get-record/airtable-get-record.tests.json +25 -0
- package/nodes/airtable-list-records/airtable-list-records.config.json +87 -0
- package/nodes/airtable-list-records/airtable-list-records.process.js +52 -0
- package/nodes/airtable-list-records/airtable-list-records.tests.json +46 -0
- package/nodes/airtable-update-record/airtable-update-record.config.json +71 -0
- package/nodes/airtable-update-record/airtable-update-record.process.js +48 -0
- package/nodes/airtable-update-record/airtable-update-record.tests.json +44 -0
- package/nodes/and-gate/and-gate.config.json +30 -0
- package/nodes/and-gate/and-gate.process.js +10 -0
- package/nodes/and-gate/and-gate.tests.json +22 -0
- package/nodes/anthropic-claude-3-5-haiku/anthropic-claude-3-5-haiku.config.json +155 -0
- package/nodes/anthropic-claude-3-5-haiku/anthropic-claude-3-5-haiku.process.js +136 -0
- package/nodes/anthropic-claude-3-5-haiku/anthropic-claude-3-5-haiku.tests.json +80 -0
- package/nodes/anthropic-claude-3-haiku/anthropic-claude-3-haiku.config.json +155 -0
- package/nodes/anthropic-claude-3-haiku/anthropic-claude-3-haiku.process.js +136 -0
- package/nodes/anthropic-claude-3-haiku/anthropic-claude-3-haiku.tests.json +80 -0
- package/nodes/anthropic-claude-haiku-4-5/anthropic-claude-haiku-4-5.config.json +186 -0
- package/nodes/anthropic-claude-haiku-4-5/anthropic-claude-haiku-4-5.process.js +138 -0
- package/nodes/anthropic-claude-haiku-4-5/anthropic-claude-haiku-4-5.tests.json +80 -0
- package/nodes/anthropic-claude-opus-4/anthropic-claude-opus-4.config.json +176 -0
- package/nodes/anthropic-claude-opus-4/anthropic-claude-opus-4.process.js +138 -0
- package/nodes/anthropic-claude-opus-4/anthropic-claude-opus-4.tests.json +80 -0
- package/nodes/anthropic-claude-opus-4-1/anthropic-claude-opus-4-1.config.json +186 -0
- package/nodes/anthropic-claude-opus-4-1/anthropic-claude-opus-4-1.process.js +138 -0
- package/nodes/anthropic-claude-opus-4-1/anthropic-claude-opus-4-1.tests.json +80 -0
- package/nodes/anthropic-claude-opus-4-5/anthropic-claude-opus-4-5.config.json +179 -0
- package/nodes/anthropic-claude-opus-4-5/anthropic-claude-opus-4-5.process.js +138 -0
- package/nodes/anthropic-claude-opus-4-5/anthropic-claude-opus-4-5.tests.json +80 -0
- package/nodes/anthropic-claude-opus-4-6/anthropic-claude-opus-4-6.config.json +188 -0
- package/nodes/anthropic-claude-opus-4-6/anthropic-claude-opus-4-6.process.js +138 -0
- package/nodes/anthropic-claude-opus-4-6/anthropic-claude-opus-4-6.tests.json +80 -0
- package/nodes/anthropic-claude-opus-4-6-fast/anthropic-claude-opus-4-6-fast.config.json +186 -0
- package/nodes/anthropic-claude-opus-4-6-fast/anthropic-claude-opus-4-6-fast.process.js +138 -0
- package/nodes/anthropic-claude-opus-4-6-fast/anthropic-claude-opus-4-6-fast.tests.json +80 -0
- package/nodes/anthropic-claude-opus-4-7/anthropic-claude-opus-4-7.config.json +170 -0
- package/nodes/anthropic-claude-opus-4-7/anthropic-claude-opus-4-7.process.js +138 -0
- package/nodes/anthropic-claude-opus-4-7/anthropic-claude-opus-4-7.tests.json +57 -0
- package/nodes/anthropic-claude-opus-4-7-fast/anthropic-claude-opus-4-7-fast.config.json +170 -0
- package/nodes/anthropic-claude-opus-4-7-fast/anthropic-claude-opus-4-7-fast.process.js +138 -0
- package/nodes/anthropic-claude-opus-4-7-fast/anthropic-claude-opus-4-7-fast.tests.json +57 -0
- package/nodes/anthropic-claude-opus-4-8/anthropic-claude-opus-4-8.config.json +170 -0
- package/nodes/anthropic-claude-opus-4-8/anthropic-claude-opus-4-8.process.js +138 -0
- package/nodes/anthropic-claude-opus-4-8/anthropic-claude-opus-4-8.tests.json +57 -0
- package/nodes/anthropic-claude-opus-4-8-fast/anthropic-claude-opus-4-8-fast.config.json +170 -0
- package/nodes/anthropic-claude-opus-4-8-fast/anthropic-claude-opus-4-8-fast.process.js +138 -0
- package/nodes/anthropic-claude-opus-4-8-fast/anthropic-claude-opus-4-8-fast.tests.json +57 -0
- package/nodes/anthropic-claude-sonnet-4/anthropic-claude-sonnet-4.config.json +177 -0
- package/nodes/anthropic-claude-sonnet-4/anthropic-claude-sonnet-4.process.js +138 -0
- package/nodes/anthropic-claude-sonnet-4/anthropic-claude-sonnet-4.tests.json +80 -0
- package/nodes/anthropic-claude-sonnet-4-5/anthropic-claude-sonnet-4-5.config.json +186 -0
- package/nodes/anthropic-claude-sonnet-4-5/anthropic-claude-sonnet-4-5.process.js +138 -0
- package/nodes/anthropic-claude-sonnet-4-5/anthropic-claude-sonnet-4-5.tests.json +80 -0
- package/nodes/anthropic-claude-sonnet-4-6/anthropic-claude-sonnet-4-6.config.json +188 -0
- package/nodes/anthropic-claude-sonnet-4-6/anthropic-claude-sonnet-4-6.process.js +138 -0
- package/nodes/anthropic-claude-sonnet-4-6/anthropic-claude-sonnet-4-6.tests.json +80 -0
- package/nodes/array-builder/array-builder.config.json +27 -0
- package/nodes/array-builder/array-builder.process.js +12 -0
- package/nodes/array-builder/array-builder.tests.json +20 -0
- package/nodes/array-filter/array-filter.config.json +47 -0
- package/nodes/array-filter/array-filter.process.js +58 -0
- package/nodes/array-filter/array-filter.tests.json +67 -0
- package/nodes/array-find/array-find.config.json +66 -0
- package/nodes/array-find/array-find.process.js +61 -0
- package/nodes/array-find/array-find.tests.json +83 -0
- package/nodes/array-flatten/array-flatten.config.json +39 -0
- package/nodes/array-flatten/array-flatten.process.js +67 -0
- package/nodes/array-flatten/array-flatten.tests.json +79 -0
- package/nodes/array-index-selector/array-index-selector.config.json +36 -0
- package/nodes/array-index-selector/array-index-selector.process.js +20 -0
- package/nodes/array-index-selector/array-index-selector.tests.json +37 -0
- package/nodes/array-join/array-join.config.json +30 -0
- package/nodes/array-join/array-join.process.js +9 -0
- package/nodes/array-join/array-join.tests.json +41 -0
- package/nodes/array-length/array-length.config.json +28 -0
- package/nodes/array-length/array-length.process.js +14 -0
- package/nodes/array-length/array-length.tests.json +32 -0
- package/nodes/array-map/array-map.config.json +30 -0
- package/nodes/array-map/array-map.process.js +16 -0
- package/nodes/array-map/array-map.tests.json +44 -0
- package/nodes/array-reverse/array-reverse.config.json +29 -0
- package/nodes/array-reverse/array-reverse.process.js +12 -0
- package/nodes/array-reverse/array-reverse.tests.json +60 -0
- package/nodes/array-shuffle/array-shuffle.config.json +38 -0
- package/nodes/array-shuffle/array-shuffle.process.js +37 -0
- package/nodes/array-shuffle/array-shuffle.tests.json +82 -0
- package/nodes/array-slice/array-slice.config.json +37 -0
- package/nodes/array-slice/array-slice.process.js +18 -0
- package/nodes/array-slice/array-slice.tests.json +43 -0
- package/nodes/array-sort/array-sort.config.json +55 -0
- package/nodes/array-sort/array-sort.process.js +45 -0
- package/nodes/array-sort/array-sort.tests.json +95 -0
- package/nodes/array-split/array-split.config.json +50 -0
- package/nodes/array-split/array-split.process.js +32 -0
- package/nodes/array-split/array-split.tests.json +77 -0
- package/nodes/array-unique/array-unique.config.json +65 -0
- package/nodes/array-unique/array-unique.process.js +55 -0
- package/nodes/array-unique/array-unique.tests.json +96 -0
- package/nodes/baai-bge-base-en-v1-5/baai-bge-base-en-v1-5.config.json +90 -0
- package/nodes/baai-bge-base-en-v1-5/baai-bge-base-en-v1-5.process.js +36 -0
- package/nodes/baai-bge-base-en-v1-5/baai-bge-base-en-v1-5.tests.json +48 -0
- package/nodes/baai-bge-large-en-v1-5/baai-bge-large-en-v1-5.config.json +90 -0
- package/nodes/baai-bge-large-en-v1-5/baai-bge-large-en-v1-5.process.js +36 -0
- package/nodes/baai-bge-large-en-v1-5/baai-bge-large-en-v1-5.tests.json +48 -0
- package/nodes/baai-bge-m3/baai-bge-m3.config.json +90 -0
- package/nodes/baai-bge-m3/baai-bge-m3.process.js +36 -0
- package/nodes/baai-bge-m3/baai-bge-m3.tests.json +48 -0
- package/nodes/base64-decode/base64-decode.config.json +36 -0
- package/nodes/base64-decode/base64-decode.process.js +18 -0
- package/nodes/base64-decode/base64-decode.tests.json +46 -0
- package/nodes/base64-encode/base64-encode.config.json +24 -0
- package/nodes/base64-encode/base64-encode.process.js +11 -0
- package/nodes/base64-encode/base64-encode.tests.json +38 -0
- package/nodes/boolean/boolean.config.json +37 -0
- package/nodes/boolean/boolean.process.js +9 -0
- package/nodes/boolean/boolean.tests.json +47 -0
- package/nodes/boolean-inverter/boolean-inverter.config.json +37 -0
- package/nodes/boolean-inverter/boolean-inverter.process.js +12 -0
- package/nodes/boolean-inverter/boolean-inverter.tests.json +22 -0
- package/nodes/ceil/ceil.config.json +23 -0
- package/nodes/ceil/ceil.process.js +8 -0
- package/nodes/ceil/ceil.tests.json +38 -0
- package/nodes/clean-text/clean-text.config.json +126 -0
- package/nodes/cohere-rerank-4-fast/cohere-rerank-4-fast.config.json +95 -0
- package/nodes/cohere-rerank-4-fast/cohere-rerank-4-fast.process.js +60 -0
- package/nodes/cohere-rerank-4-fast/cohere-rerank-4-fast.tests.json +47 -0
- package/nodes/cohere-rerank-4-pro/cohere-rerank-4-pro.config.json +95 -0
- package/nodes/cohere-rerank-4-pro/cohere-rerank-4-pro.process.js +60 -0
- package/nodes/cohere-rerank-4-pro/cohere-rerank-4-pro.tests.json +47 -0
- package/nodes/cohere-rerank-v3-5/cohere-rerank-v3-5.config.json +95 -0
- package/nodes/cohere-rerank-v3-5/cohere-rerank-v3-5.process.js +60 -0
- package/nodes/cohere-rerank-v3-5/cohere-rerank-v3-5.tests.json +47 -0
- package/nodes/combine-document-chunks/combine-document-chunks.config.json +58 -0
- package/nodes/combine-document-chunks/combine-document-chunks.process.js +119 -0
- package/nodes/combine-document-chunks/combine-document-chunks.tests.json +51 -0
- package/nodes/condition-check/condition-check.config.json +38 -0
- package/nodes/condition-check/condition-check.process.js +16 -0
- package/nodes/condition-check/condition-check.tests.json +112 -0
- package/nodes/confluence-get-page/confluence-get-page.config.json +45 -0
- package/nodes/confluence-get-page/confluence-get-page.process.js +22 -0
- package/nodes/confluence-get-page/confluence-get-page.tests.json +12 -0
- package/nodes/confluence-search/confluence-search.config.json +53 -0
- package/nodes/confluence-search/confluence-search.process.js +21 -0
- package/nodes/confluence-search/confluence-search.tests.json +14 -0
- package/nodes/conversation-to-text/conversation-to-text.config.json +55 -0
- package/nodes/conversation-to-text/conversation-to-text.process.js +58 -0
- package/nodes/conversation-to-text/conversation-to-text.tests.json +134 -0
- package/nodes/csv-parser/csv-parser.config.json +74 -0
- package/nodes/csv-parser/csv-parser.process.js +114 -0
- package/nodes/csv-parser/csv-parser.tests.json +93 -0
- package/nodes/csv-stringifier/csv-stringifier.config.json +76 -0
- package/nodes/csv-stringifier/csv-stringifier.process.js +101 -0
- package/nodes/csv-stringifier/csv-stringifier.tests.json +63 -0
- package/nodes/date-formatter/date-formatter.config.json +66 -0
- package/nodes/date-formatter/date-formatter.process.js +113 -0
- package/nodes/date-formatter/date-formatter.tests.json +50 -0
- package/nodes/dedupe-messages/dedupe-messages.config.json +30 -0
- package/nodes/dedupe-messages/dedupe-messages.process.js +44 -0
- package/nodes/dedupe-messages/dedupe-messages.tests.json +149 -0
- package/nodes/deepseek-deepseek-chat/deepseek-deepseek-chat.config.json +191 -0
- package/nodes/deepseek-deepseek-chat/deepseek-deepseek-chat.process.js +136 -0
- package/nodes/deepseek-deepseek-chat/deepseek-deepseek-chat.tests.json +80 -0
- package/nodes/deepseek-deepseek-chat-v3-0324/deepseek-deepseek-chat-v3-0324.config.json +191 -0
- package/nodes/deepseek-deepseek-chat-v3-0324/deepseek-deepseek-chat-v3-0324.process.js +136 -0
- package/nodes/deepseek-deepseek-chat-v3-0324/deepseek-deepseek-chat-v3-0324.tests.json +80 -0
- package/nodes/deepseek-deepseek-chat-v3-1/deepseek-deepseek-chat-v3-1.config.json +213 -0
- package/nodes/deepseek-deepseek-chat-v3-1/deepseek-deepseek-chat-v3-1.process.js +138 -0
- package/nodes/deepseek-deepseek-chat-v3-1/deepseek-deepseek-chat-v3-1.tests.json +80 -0
- package/nodes/deepseek-deepseek-r1/deepseek-deepseek-r1.config.json +212 -0
- package/nodes/deepseek-deepseek-r1/deepseek-deepseek-r1.process.js +138 -0
- package/nodes/deepseek-deepseek-r1/deepseek-deepseek-r1.tests.json +80 -0
- package/nodes/deepseek-deepseek-r1-0528/deepseek-deepseek-r1-0528.config.json +213 -0
- package/nodes/deepseek-deepseek-r1-0528/deepseek-deepseek-r1-0528.process.js +138 -0
- package/nodes/deepseek-deepseek-r1-0528/deepseek-deepseek-r1-0528.tests.json +80 -0
- package/nodes/deepseek-deepseek-r1-distill-llama-70b/deepseek-deepseek-r1-distill-llama-70b.config.json +189 -0
- package/nodes/deepseek-deepseek-r1-distill-llama-70b/deepseek-deepseek-r1-distill-llama-70b.process.js +138 -0
- package/nodes/deepseek-deepseek-r1-distill-llama-70b/deepseek-deepseek-r1-distill-llama-70b.tests.json +80 -0
- package/nodes/deepseek-deepseek-r1-distill-qwen-32b/deepseek-deepseek-r1-distill-qwen-32b.config.json +209 -0
- package/nodes/deepseek-deepseek-r1-distill-qwen-32b/deepseek-deepseek-r1-distill-qwen-32b.process.js +139 -0
- package/nodes/deepseek-deepseek-r1-distill-qwen-32b/deepseek-deepseek-r1-distill-qwen-32b.tests.json +80 -0
- package/nodes/deepseek-deepseek-v3-1-terminus/deepseek-deepseek-v3-1-terminus.config.json +213 -0
- package/nodes/deepseek-deepseek-v3-1-terminus/deepseek-deepseek-v3-1-terminus.process.js +138 -0
- package/nodes/deepseek-deepseek-v3-1-terminus/deepseek-deepseek-v3-1-terminus.tests.json +80 -0
- package/nodes/deepseek-deepseek-v3-2/deepseek-deepseek-v3-2.config.json +213 -0
- package/nodes/deepseek-deepseek-v3-2/deepseek-deepseek-v3-2.process.js +138 -0
- package/nodes/deepseek-deepseek-v3-2/deepseek-deepseek-v3-2.tests.json +80 -0
- package/nodes/deepseek-deepseek-v3-2-exp/deepseek-deepseek-v3-2-exp.config.json +213 -0
- package/nodes/deepseek-deepseek-v3-2-exp/deepseek-deepseek-v3-2-exp.process.js +138 -0
- package/nodes/deepseek-deepseek-v3-2-exp/deepseek-deepseek-v3-2-exp.tests.json +80 -0
- package/nodes/deepseek-deepseek-v4-flash/deepseek-deepseek-v4-flash.config.json +235 -0
- package/nodes/deepseek-deepseek-v4-flash/deepseek-deepseek-v4-flash.process.js +139 -0
- package/nodes/deepseek-deepseek-v4-flash/deepseek-deepseek-v4-flash.tests.json +80 -0
- package/nodes/deepseek-deepseek-v4-pro/deepseek-deepseek-v4-pro.config.json +235 -0
- package/nodes/deepseek-deepseek-v4-pro/deepseek-deepseek-v4-pro.process.js +139 -0
- package/nodes/deepseek-deepseek-v4-pro/deepseek-deepseek-v4-pro.tests.json +80 -0
- package/nodes/delay/delay.config.json +43 -0
- package/nodes/delay/delay.process.js +16 -0
- package/nodes/delay/delay.tests.json +59 -0
- package/nodes/delete-object-property/delete-object-property.config.json +53 -0
- package/nodes/delete-object-property/delete-object-property.process.js +42 -0
- package/nodes/delete-object-property/delete-object-property.tests.json +173 -0
- package/nodes/divide/divide.config.json +31 -0
- package/nodes/divide/divide.process.js +18 -0
- package/nodes/divide/divide.tests.json +42 -0
- package/nodes/duration-calculator/duration-calculator.config.json +77 -0
- package/nodes/duration-calculator/duration-calculator.process.js +96 -0
- package/nodes/duration-calculator/duration-calculator.tests.json +124 -0
- package/nodes/embedding/embedding.config.json +81 -0
- package/nodes/embedding/embedding.process.js +44 -0
- package/nodes/embedding/embedding.tests.json +26 -0
- package/nodes/equals/equals.config.json +32 -0
- package/nodes/equals/equals.process.js +29 -0
- package/nodes/equals/equals.tests.json +111 -0
- package/nodes/extract-code/extract-code.config.json +57 -0
- package/nodes/extract-code/extract-code.process.js +53 -0
- package/nodes/extract-code/extract-code.tests.json +162 -0
- package/nodes/extract-emails/extract-emails.config.json +85 -0
- package/nodes/extract-hashtags/extract-hashtags.config.json +85 -0
- package/nodes/extract-json/extract-json.config.json +44 -0
- package/nodes/extract-json/extract-json.process.js +102 -0
- package/nodes/extract-json/extract-json.tests.json +146 -0
- package/nodes/extract-mentions/extract-mentions.config.json +85 -0
- package/nodes/extract-phone-numbers/extract-phone-numbers.config.json +85 -0
- package/nodes/extract-text-content/extract-text-content.config.json +56 -0
- package/nodes/extract-text-content/extract-text-content.process.js +85 -0
- package/nodes/extract-text-content/extract-text-content.tests.json +144 -0
- package/nodes/extract-urls/extract-urls.config.json +85 -0
- package/nodes/filter-messages/filter-messages.config.json +53 -0
- package/nodes/filter-messages/filter-messages.process.js +88 -0
- package/nodes/filter-messages/filter-messages.tests.json +125 -0
- package/nodes/filter-tools/filter-tools.config.json +29 -0
- package/nodes/filter-tools/filter-tools.process.js +13 -0
- package/nodes/filter-tools/filter-tools.tests.json +89 -0
- package/nodes/firecrawl-scrape/firecrawl-scrape.config.json +171 -0
- package/nodes/firecrawl-scrape/firecrawl-scrape.process.js +69 -0
- package/nodes/firecrawl-scrape/firecrawl-scrape.tests.json +57 -0
- package/nodes/floor/floor.config.json +23 -0
- package/nodes/floor/floor.process.js +8 -0
- package/nodes/floor/floor.tests.json +38 -0
- package/nodes/generate-uuid/generate-uuid.config.json +31 -0
- package/nodes/generate-uuid/generate-uuid.process.js +15 -0
- package/nodes/generate-uuid/generate-uuid.tests.json +7 -0
- package/nodes/get-chunk-by-index/get-chunk-by-index.config.json +45 -0
- package/nodes/get-chunk-by-index/get-chunk-by-index.process.js +80 -0
- package/nodes/get-chunk-by-index/get-chunk-by-index.tests.json +51 -0
- package/nodes/get-first-n-messages/get-first-n-messages.config.json +37 -0
- package/nodes/get-first-n-messages/get-first-n-messages.process.js +27 -0
- package/nodes/get-first-n-messages/get-first-n-messages.tests.json +66 -0
- package/nodes/get-last-n-messages/get-last-n-messages.config.json +37 -0
- package/nodes/get-last-n-messages/get-last-n-messages.process.js +27 -0
- package/nodes/get-last-n-messages/get-last-n-messages.tests.json +66 -0
- package/nodes/get-messages-by-role/get-messages-by-role.config.json +37 -0
- package/nodes/get-messages-by-role/get-messages-by-role.process.js +28 -0
- package/nodes/get-messages-by-role/get-messages-by-role.tests.json +87 -0
- package/nodes/get-messages-range/get-messages-range.config.json +44 -0
- package/nodes/get-messages-range/get-messages-range.process.js +44 -0
- package/nodes/get-messages-range/get-messages-range.tests.json +104 -0
- package/nodes/get-object-property/get-object-property.config.json +54 -0
- package/nodes/get-object-property/get-object-property.process.js +35 -0
- package/nodes/get-object-property/get-object-property.tests.json +101 -0
- package/nodes/github-create-issue/github-create-issue.config.json +72 -0
- package/nodes/github-create-issue/github-create-issue.process.js +22 -0
- package/nodes/github-create-issue/github-create-issue.tests.json +15 -0
- package/nodes/google-custom-search/google-custom-search.config.json +229 -0
- package/nodes/google-custom-search/google-custom-search.process.js +96 -0
- package/nodes/google-custom-search/google-custom-search.tests.json +69 -0
- package/nodes/google-gemini-2-5-flash/google-gemini-2-5-flash.config.json +193 -0
- package/nodes/google-gemini-2-5-flash/google-gemini-2-5-flash.process.js +138 -0
- package/nodes/google-gemini-2-5-flash/google-gemini-2-5-flash.tests.json +80 -0
- package/nodes/google-gemini-2-5-flash-image/google-gemini-2-5-flash-image.config.json +129 -0
- package/nodes/google-gemini-2-5-flash-image/google-gemini-2-5-flash-image.process.js +55 -0
- package/nodes/google-gemini-2-5-flash-image/google-gemini-2-5-flash-image.tests.json +44 -0
- package/nodes/google-gemini-2-5-flash-lite/google-gemini-2-5-flash-lite.config.json +193 -0
- package/nodes/google-gemini-2-5-flash-lite/google-gemini-2-5-flash-lite.process.js +138 -0
- package/nodes/google-gemini-2-5-flash-lite/google-gemini-2-5-flash-lite.tests.json +80 -0
- package/nodes/google-gemini-2-5-flash-lite-preview-09-2025/google-gemini-2-5-flash-lite-preview-09-2025.config.json +193 -0
- package/nodes/google-gemini-2-5-flash-lite-preview-09-2025/google-gemini-2-5-flash-lite-preview-09-2025.process.js +138 -0
- package/nodes/google-gemini-2-5-flash-lite-preview-09-2025/google-gemini-2-5-flash-lite-preview-09-2025.tests.json +80 -0
- package/nodes/google-gemini-2-5-pro/google-gemini-2-5-pro.config.json +193 -0
- package/nodes/google-gemini-2-5-pro/google-gemini-2-5-pro.process.js +138 -0
- package/nodes/google-gemini-2-5-pro/google-gemini-2-5-pro.tests.json +80 -0
- package/nodes/google-gemini-2-5-pro-preview/google-gemini-2-5-pro-preview.config.json +193 -0
- package/nodes/google-gemini-2-5-pro-preview/google-gemini-2-5-pro-preview.process.js +138 -0
- package/nodes/google-gemini-2-5-pro-preview/google-gemini-2-5-pro-preview.tests.json +80 -0
- package/nodes/google-gemini-2-5-pro-preview-05-06/google-gemini-2-5-pro-preview-05-06.config.json +193 -0
- package/nodes/google-gemini-2-5-pro-preview-05-06/google-gemini-2-5-pro-preview-05-06.process.js +138 -0
- package/nodes/google-gemini-2-5-pro-preview-05-06/google-gemini-2-5-pro-preview-05-06.tests.json +80 -0
- package/nodes/google-gemini-3-1-flash-image-preview/google-gemini-3-1-flash-image-preview.config.json +151 -0
- package/nodes/google-gemini-3-1-flash-image-preview/google-gemini-3-1-flash-image-preview.process.js +57 -0
- package/nodes/google-gemini-3-1-flash-image-preview/google-gemini-3-1-flash-image-preview.tests.json +44 -0
- package/nodes/google-gemini-3-1-flash-lite/google-gemini-3-1-flash-lite.config.json +193 -0
- package/nodes/google-gemini-3-1-flash-lite/google-gemini-3-1-flash-lite.process.js +138 -0
- package/nodes/google-gemini-3-1-flash-lite/google-gemini-3-1-flash-lite.tests.json +80 -0
- package/nodes/google-gemini-3-1-flash-lite-preview/google-gemini-3-1-flash-lite-preview.config.json +193 -0
- package/nodes/google-gemini-3-1-flash-lite-preview/google-gemini-3-1-flash-lite-preview.process.js +138 -0
- package/nodes/google-gemini-3-1-flash-lite-preview/google-gemini-3-1-flash-lite-preview.tests.json +80 -0
- package/nodes/google-gemini-3-1-pro-preview/google-gemini-3-1-pro-preview.config.json +193 -0
- package/nodes/google-gemini-3-1-pro-preview/google-gemini-3-1-pro-preview.process.js +138 -0
- package/nodes/google-gemini-3-1-pro-preview/google-gemini-3-1-pro-preview.tests.json +80 -0
- package/nodes/google-gemini-3-1-pro-preview-customtools/google-gemini-3-1-pro-preview-customtools.config.json +193 -0
- package/nodes/google-gemini-3-1-pro-preview-customtools/google-gemini-3-1-pro-preview-customtools.process.js +138 -0
- package/nodes/google-gemini-3-1-pro-preview-customtools/google-gemini-3-1-pro-preview-customtools.tests.json +80 -0
- package/nodes/google-gemini-3-5-flash/google-gemini-3-5-flash.config.json +193 -0
- package/nodes/google-gemini-3-5-flash/google-gemini-3-5-flash.process.js +138 -0
- package/nodes/google-gemini-3-5-flash/google-gemini-3-5-flash.tests.json +80 -0
- package/nodes/google-gemini-3-flash-preview/google-gemini-3-flash-preview.config.json +193 -0
- package/nodes/google-gemini-3-flash-preview/google-gemini-3-flash-preview.process.js +138 -0
- package/nodes/google-gemini-3-flash-preview/google-gemini-3-flash-preview.tests.json +80 -0
- package/nodes/google-gemini-3-pro-image-preview/google-gemini-3-pro-image-preview.config.json +151 -0
- package/nodes/google-gemini-3-pro-image-preview/google-gemini-3-pro-image-preview.process.js +57 -0
- package/nodes/google-gemini-3-pro-image-preview/google-gemini-3-pro-image-preview.tests.json +44 -0
- package/nodes/google-gemini-embedding-001/google-gemini-embedding-001.config.json +90 -0
- package/nodes/google-gemini-embedding-001/google-gemini-embedding-001.process.js +36 -0
- package/nodes/google-gemini-embedding-001/google-gemini-embedding-001.tests.json +48 -0
- package/nodes/google-gemini-embedding-2/google-gemini-embedding-2.config.json +94 -0
- package/nodes/google-gemini-embedding-2/google-gemini-embedding-2.process.js +36 -0
- package/nodes/google-gemini-embedding-2/google-gemini-embedding-2.tests.json +48 -0
- package/nodes/google-gemini-embedding-2-preview/google-gemini-embedding-2-preview.config.json +94 -0
- package/nodes/google-gemini-embedding-2-preview/google-gemini-embedding-2-preview.process.js +36 -0
- package/nodes/google-gemini-embedding-2-preview/google-gemini-embedding-2-preview.tests.json +48 -0
- package/nodes/google-gemma-2-27b-it/google-gemma-2-27b-it.config.json +165 -0
- package/nodes/google-gemma-2-27b-it/google-gemma-2-27b-it.process.js +136 -0
- package/nodes/google-gemma-2-27b-it/google-gemma-2-27b-it.tests.json +80 -0
- package/nodes/google-gemma-3-12b-it/google-gemma-3-12b-it.config.json +191 -0
- package/nodes/google-gemma-3-12b-it/google-gemma-3-12b-it.process.js +136 -0
- package/nodes/google-gemma-3-12b-it/google-gemma-3-12b-it.tests.json +80 -0
- package/nodes/google-gemma-3-27b-it/google-gemma-3-27b-it.config.json +191 -0
- package/nodes/google-gemma-3-27b-it/google-gemma-3-27b-it.process.js +136 -0
- package/nodes/google-gemma-3-27b-it/google-gemma-3-27b-it.tests.json +80 -0
- package/nodes/google-gemma-3-4b-it/google-gemma-3-4b-it.config.json +168 -0
- package/nodes/google-gemma-3-4b-it/google-gemma-3-4b-it.process.js +136 -0
- package/nodes/google-gemma-3-4b-it/google-gemma-3-4b-it.tests.json +80 -0
- package/nodes/google-gemma-3n-e4b-it/google-gemma-3n-e4b-it.config.json +123 -0
- package/nodes/google-gemma-3n-e4b-it/google-gemma-3n-e4b-it.process.js +51 -0
- package/nodes/google-gemma-3n-e4b-it/google-gemma-3n-e4b-it.tests.json +44 -0
- package/nodes/google-gemma-4-26b-a4b-it/google-gemma-4-26b-a4b-it.config.json +235 -0
- package/nodes/google-gemma-4-26b-a4b-it/google-gemma-4-26b-a4b-it.process.js +139 -0
- package/nodes/google-gemma-4-26b-a4b-it/google-gemma-4-26b-a4b-it.tests.json +80 -0
- package/nodes/google-gemma-4-26b-a4b-it-free/google-gemma-4-26b-a4b-it-free.config.json +184 -0
- package/nodes/google-gemma-4-26b-a4b-it-free/google-gemma-4-26b-a4b-it-free.process.js +138 -0
- package/nodes/google-gemma-4-26b-a4b-it-free/google-gemma-4-26b-a4b-it-free.tests.json +80 -0
- package/nodes/google-gemma-4-31b-it/google-gemma-4-31b-it.config.json +235 -0
- package/nodes/google-gemma-4-31b-it/google-gemma-4-31b-it.process.js +139 -0
- package/nodes/google-gemma-4-31b-it/google-gemma-4-31b-it.tests.json +80 -0
- package/nodes/google-gemma-4-31b-it-free/google-gemma-4-31b-it-free.config.json +192 -0
- package/nodes/google-gemma-4-31b-it-free/google-gemma-4-31b-it-free.process.js +138 -0
- package/nodes/google-gemma-4-31b-it-free/google-gemma-4-31b-it-free.tests.json +80 -0
- package/nodes/google-lyria-3-clip-preview/google-lyria-3-clip-preview.config.json +111 -0
- package/nodes/google-lyria-3-clip-preview/google-lyria-3-clip-preview.process.js +51 -0
- package/nodes/google-lyria-3-clip-preview/google-lyria-3-clip-preview.tests.json +44 -0
- package/nodes/google-lyria-3-pro-preview/google-lyria-3-pro-preview.config.json +111 -0
- package/nodes/google-lyria-3-pro-preview/google-lyria-3-pro-preview.process.js +51 -0
- package/nodes/google-lyria-3-pro-preview/google-lyria-3-pro-preview.tests.json +44 -0
- package/nodes/greater-than/greater-than.config.json +33 -0
- package/nodes/greater-than/greater-than.process.js +11 -0
- package/nodes/greater-than/greater-than.tests.json +47 -0
- package/nodes/greater-than-equal/greater-than-equal.config.json +33 -0
- package/nodes/greater-than-equal/greater-than-equal.process.js +11 -0
- package/nodes/greater-than-equal/greater-than-equal.tests.json +52 -0
- package/nodes/has-object-property/has-object-property.config.json +55 -0
- package/nodes/has-object-property/has-object-property.process.js +42 -0
- package/nodes/has-object-property/has-object-property.tests.json +148 -0
- package/nodes/hash-content/hash-content.config.json +33 -0
- package/nodes/hash-content/hash-content.process.js +19 -0
- package/nodes/hash-content/hash-content.tests.json +51 -0
- package/nodes/histogram/histogram.config.json +36 -0
- package/nodes/histogram/histogram.process.js +73 -0
- package/nodes/histogram/histogram.tests.json +57 -0
- package/nodes/histogram-fixed/histogram-fixed.config.json +62 -0
- package/nodes/histogram-fixed/histogram-fixed.process.js +42 -0
- package/nodes/histogram-fixed/histogram-fixed.tests.json +62 -0
- package/nodes/http-request/http-request.config.json +90 -0
- package/nodes/http-request/http-request.process.js +89 -0
- package/nodes/http-request/http-request.tests.json +115 -0
- package/nodes/hubspot-read-contacts/hubspot-read-contacts.config.json +71 -0
- package/nodes/hubspot-read-contacts/hubspot-read-contacts.process.js +56 -0
- package/nodes/hubspot-search-contacts/hubspot-search-contacts.config.json +89 -0
- package/nodes/hubspot-search-contacts/hubspot-search-contacts.process.js +71 -0
- package/nodes/if-else/if-else.config.json +50 -0
- package/nodes/if-else/if-else.process.js +39 -0
- package/nodes/if-else/if-else.tests.json +94 -0
- package/nodes/input-chat/input-chat.config.json +45 -0
- package/nodes/input-chat/input-chat.process.js +29 -0
- package/nodes/input-data/input-data.config.json +59 -0
- package/nodes/input-data/input-data.process.js +25 -0
- package/nodes/input-data/input-data.tests.json +53 -0
- package/nodes/input-plugins/input-plugins.config.json +17 -0
- package/nodes/input-plugins/input-plugins.process.js +11 -0
- package/nodes/input-prompt/input-prompt.config.json +39 -0
- package/nodes/input-prompt/input-prompt.process.js +9 -0
- package/nodes/intfloat-e5-base-v2/intfloat-e5-base-v2.config.json +90 -0
- package/nodes/intfloat-e5-base-v2/intfloat-e5-base-v2.process.js +36 -0
- package/nodes/intfloat-e5-base-v2/intfloat-e5-base-v2.tests.json +48 -0
- package/nodes/intfloat-e5-large-v2/intfloat-e5-large-v2.config.json +90 -0
- package/nodes/intfloat-e5-large-v2/intfloat-e5-large-v2.process.js +36 -0
- package/nodes/intfloat-e5-large-v2/intfloat-e5-large-v2.tests.json +48 -0
- package/nodes/intfloat-multilingual-e5-large/intfloat-multilingual-e5-large.config.json +90 -0
- package/nodes/intfloat-multilingual-e5-large/intfloat-multilingual-e5-large.process.js +36 -0
- package/nodes/intfloat-multilingual-e5-large/intfloat-multilingual-e5-large.tests.json +48 -0
- package/nodes/jira-create-issue/jira-create-issue.config.json +87 -0
- package/nodes/jira-create-issue/jira-create-issue.process.js +24 -0
- package/nodes/jira-create-issue/jira-create-issue.tests.json +14 -0
- package/nodes/jira-list-issues/jira-list-issues.config.json +53 -0
- package/nodes/jira-list-issues/jira-list-issues.process.js +19 -0
- package/nodes/jira-list-issues/jira-list-issues.tests.json +14 -0
- package/nodes/jira-update-issue/jira-update-issue.config.json +60 -0
- package/nodes/jira-update-issue/jira-update-issue.process.js +30 -0
- package/nodes/jira-update-issue/jira-update-issue.tests.json +12 -0
- package/nodes/json-parser/json-parser.config.json +47 -0
- package/nodes/json-parser/json-parser.process.js +23 -0
- package/nodes/json-parser/json-parser.tests.json +57 -0
- package/nodes/json-stringifier/json-stringifier.config.json +54 -0
- package/nodes/json-stringifier/json-stringifier.process.js +20 -0
- package/nodes/json-stringifier/json-stringifier.tests.json +58 -0
- package/nodes/json-to-xml/json-to-xml.config.json +48 -0
- package/nodes/json-to-xml/json-to-xml.process.js +60 -0
- package/nodes/json-to-xml/json-to-xml.tests.json +103 -0
- package/nodes/json-to-yaml/json-to-yaml.config.json +32 -0
- package/nodes/json-to-yaml/json-to-yaml.process.js +89 -0
- package/nodes/json-to-yaml/json-to-yaml.tests.json +101 -0
- package/nodes/keep-bookends/keep-bookends.config.json +44 -0
- package/nodes/keep-bookends/keep-bookends.process.js +44 -0
- package/nodes/keep-bookends/keep-bookends.tests.json +185 -0
- package/nodes/less-than/less-than.config.json +33 -0
- package/nodes/less-than/less-than.process.js +11 -0
- package/nodes/less-than/less-than.tests.json +47 -0
- package/nodes/less-than-equal/less-than-equal.config.json +33 -0
- package/nodes/less-than-equal/less-than-equal.process.js +11 -0
- package/nodes/less-than-equal/less-than-equal.tests.json +52 -0
- package/nodes/linear-create-issue/linear-create-issue.config.json +72 -0
- package/nodes/linear-create-issue/linear-create-issue.process.js +24 -0
- package/nodes/linear-create-issue/linear-create-issue.tests.json +14 -0
- package/nodes/linear-list-issues/linear-list-issues.config.json +58 -0
- package/nodes/linear-list-issues/linear-list-issues.process.js +22 -0
- package/nodes/linear-list-issues/linear-list-issues.tests.json +13 -0
- package/nodes/linear-update-issue/linear-update-issue.config.json +72 -0
- package/nodes/linear-update-issue/linear-update-issue.process.js +24 -0
- package/nodes/linear-update-issue/linear-update-issue.tests.json +13 -0
- package/nodes/liquid-lfm-2-24b-a2b/liquid-lfm-2-24b-a2b.config.json +123 -0
- package/nodes/liquid-lfm-2-24b-a2b/liquid-lfm-2-24b-a2b.process.js +51 -0
- package/nodes/liquid-lfm-2-24b-a2b/liquid-lfm-2-24b-a2b.tests.json +44 -0
- package/nodes/liquid-lfm-2-5-1-2b-instruct-free/liquid-lfm-2-5-1-2b-instruct-free.config.json +130 -0
- package/nodes/liquid-lfm-2-5-1-2b-instruct-free/liquid-lfm-2-5-1-2b-instruct-free.process.js +51 -0
- package/nodes/liquid-lfm-2-5-1-2b-instruct-free/liquid-lfm-2-5-1-2b-instruct-free.tests.json +44 -0
- package/nodes/liquid-lfm-2-5-1-2b-thinking-free/liquid-lfm-2-5-1-2b-thinking-free.config.json +152 -0
- package/nodes/liquid-lfm-2-5-1-2b-thinking-free/liquid-lfm-2-5-1-2b-thinking-free.process.js +53 -0
- package/nodes/liquid-lfm-2-5-1-2b-thinking-free/liquid-lfm-2-5-1-2b-thinking-free.tests.json +44 -0
- package/nodes/loop-end/loop-end.config.json +74 -0
- package/nodes/loop-end/loop-end.process.js +69 -0
- package/nodes/loop-end/loop-end.tests.json +139 -0
- package/nodes/loop-start/loop-start.config.json +28 -0
- package/nodes/loop-start/loop-start.process.js +18 -0
- package/nodes/loop-start/loop-start.tests.json +65 -0
- package/nodes/mean/mean.config.json +23 -0
- package/nodes/mean/mean.process.js +23 -0
- package/nodes/mean/mean.tests.json +45 -0
- package/nodes/median/median.config.json +23 -0
- package/nodes/median/median.process.js +29 -0
- package/nodes/median/median.tests.json +54 -0
- package/nodes/merge-consecutive-roles/merge-consecutive-roles.config.json +38 -0
- package/nodes/merge-consecutive-roles/merge-consecutive-roles.process.js +75 -0
- package/nodes/merge-consecutive-roles/merge-consecutive-roles.tests.json +199 -0
- package/nodes/merge-messages/merge-messages.config.json +60 -0
- package/nodes/merge-messages/merge-messages.process.js +99 -0
- package/nodes/merge-messages/merge-messages.tests.json +174 -0
- package/nodes/message/message.config.json +57 -0
- package/nodes/message/message.process.js +40 -0
- package/nodes/message-bus/message-bus.config.json +103 -0
- package/nodes/message-bus/message-bus.process.js +69 -0
- package/nodes/message-bus/message-bus.tests.json +150 -0
- package/nodes/message-role-swap/message-role-swap.config.json +26 -0
- package/nodes/message-role-swap/message-role-swap.process.js +39 -0
- package/nodes/message-role-swap/message-role-swap.tests.json +197 -0
- package/nodes/mistralai-codestral-2508/mistralai-codestral-2508.config.json +187 -0
- package/nodes/mistralai-codestral-2508/mistralai-codestral-2508.process.js +136 -0
- package/nodes/mistralai-codestral-2508/mistralai-codestral-2508.tests.json +80 -0
- package/nodes/mistralai-codestral-embed-2505/mistralai-codestral-embed-2505.config.json +90 -0
- package/nodes/mistralai-codestral-embed-2505/mistralai-codestral-embed-2505.process.js +36 -0
- package/nodes/mistralai-codestral-embed-2505/mistralai-codestral-embed-2505.tests.json +48 -0
- package/nodes/mistralai-devstral-2512/mistralai-devstral-2512.config.json +187 -0
- package/nodes/mistralai-devstral-2512/mistralai-devstral-2512.process.js +136 -0
- package/nodes/mistralai-devstral-2512/mistralai-devstral-2512.tests.json +80 -0
- package/nodes/mistralai-ministral-14b-2512/mistralai-ministral-14b-2512.config.json +210 -0
- package/nodes/mistralai-ministral-14b-2512/mistralai-ministral-14b-2512.process.js +137 -0
- package/nodes/mistralai-ministral-14b-2512/mistralai-ministral-14b-2512.tests.json +80 -0
- package/nodes/mistralai-ministral-3b-2512/mistralai-ministral-3b-2512.config.json +210 -0
- package/nodes/mistralai-ministral-3b-2512/mistralai-ministral-3b-2512.process.js +137 -0
- package/nodes/mistralai-ministral-3b-2512/mistralai-ministral-3b-2512.tests.json +80 -0
- package/nodes/mistralai-ministral-8b-2512/mistralai-ministral-8b-2512.config.json +210 -0
- package/nodes/mistralai-ministral-8b-2512/mistralai-ministral-8b-2512.process.js +137 -0
- package/nodes/mistralai-ministral-8b-2512/mistralai-ministral-8b-2512.tests.json +80 -0
- package/nodes/mistralai-mistral-embed-2312/mistralai-mistral-embed-2312.config.json +90 -0
- package/nodes/mistralai-mistral-embed-2312/mistralai-mistral-embed-2312.process.js +36 -0
- package/nodes/mistralai-mistral-embed-2312/mistralai-mistral-embed-2312.tests.json +48 -0
- package/nodes/mistralai-mistral-large/mistralai-mistral-large.config.json +187 -0
- package/nodes/mistralai-mistral-large/mistralai-mistral-large.process.js +136 -0
- package/nodes/mistralai-mistral-large/mistralai-mistral-large.tests.json +80 -0
- package/nodes/mistralai-mistral-large-2407/mistralai-mistral-large-2407.config.json +187 -0
- package/nodes/mistralai-mistral-large-2407/mistralai-mistral-large-2407.process.js +136 -0
- package/nodes/mistralai-mistral-large-2407/mistralai-mistral-large-2407.tests.json +80 -0
- package/nodes/mistralai-mistral-large-2512/mistralai-mistral-large-2512.config.json +187 -0
- package/nodes/mistralai-mistral-large-2512/mistralai-mistral-large-2512.process.js +136 -0
- package/nodes/mistralai-mistral-large-2512/mistralai-mistral-large-2512.tests.json +80 -0
- package/nodes/mistralai-mistral-medium-3/mistralai-mistral-medium-3.config.json +187 -0
- package/nodes/mistralai-mistral-medium-3/mistralai-mistral-medium-3.process.js +136 -0
- package/nodes/mistralai-mistral-medium-3/mistralai-mistral-medium-3.tests.json +80 -0
- package/nodes/mistralai-mistral-medium-3-1/mistralai-mistral-medium-3-1.config.json +187 -0
- package/nodes/mistralai-mistral-medium-3-1/mistralai-mistral-medium-3-1.process.js +136 -0
- package/nodes/mistralai-mistral-medium-3-1/mistralai-mistral-medium-3-1.tests.json +80 -0
- package/nodes/mistralai-mistral-medium-3-5/mistralai-mistral-medium-3-5.config.json +209 -0
- package/nodes/mistralai-mistral-medium-3-5/mistralai-mistral-medium-3-5.process.js +138 -0
- package/nodes/mistralai-mistral-medium-3-5/mistralai-mistral-medium-3-5.tests.json +80 -0
- package/nodes/mistralai-mistral-nemo/mistralai-mistral-nemo.config.json +191 -0
- package/nodes/mistralai-mistral-nemo/mistralai-mistral-nemo.process.js +136 -0
- package/nodes/mistralai-mistral-nemo/mistralai-mistral-nemo.tests.json +80 -0
- package/nodes/mistralai-mistral-saba/mistralai-mistral-saba.config.json +187 -0
- package/nodes/mistralai-mistral-saba/mistralai-mistral-saba.process.js +136 -0
- package/nodes/mistralai-mistral-saba/mistralai-mistral-saba.tests.json +80 -0
- package/nodes/mistralai-mistral-small-24b-instruct-2501/mistralai-mistral-small-24b-instruct-2501.config.json +140 -0
- package/nodes/mistralai-mistral-small-24b-instruct-2501/mistralai-mistral-small-24b-instruct-2501.process.js +51 -0
- package/nodes/mistralai-mistral-small-24b-instruct-2501/mistralai-mistral-small-24b-instruct-2501.tests.json +44 -0
- package/nodes/mistralai-mistral-small-2603/mistralai-mistral-small-2603.config.json +210 -0
- package/nodes/mistralai-mistral-small-2603/mistralai-mistral-small-2603.process.js +138 -0
- package/nodes/mistralai-mistral-small-2603/mistralai-mistral-small-2603.tests.json +80 -0
- package/nodes/mistralai-mistral-small-3-1-24b-instruct/mistralai-mistral-small-3-1-24b-instruct.config.json +131 -0
- package/nodes/mistralai-mistral-small-3-1-24b-instruct/mistralai-mistral-small-3-1-24b-instruct.process.js +51 -0
- package/nodes/mistralai-mistral-small-3-1-24b-instruct/mistralai-mistral-small-3-1-24b-instruct.tests.json +44 -0
- package/nodes/mistralai-mistral-small-3-2-24b-instruct/mistralai-mistral-small-3-2-24b-instruct.config.json +191 -0
- package/nodes/mistralai-mistral-small-3-2-24b-instruct/mistralai-mistral-small-3-2-24b-instruct.process.js +136 -0
- package/nodes/mistralai-mistral-small-3-2-24b-instruct/mistralai-mistral-small-3-2-24b-instruct.tests.json +80 -0
- package/nodes/mistralai-mixtral-8x22b-instruct/mistralai-mixtral-8x22b-instruct.config.json +187 -0
- package/nodes/mistralai-mixtral-8x22b-instruct/mistralai-mixtral-8x22b-instruct.process.js +136 -0
- package/nodes/mistralai-mixtral-8x22b-instruct/mistralai-mixtral-8x22b-instruct.tests.json +80 -0
- package/nodes/mistralai-voxtral-small-24b-2507/mistralai-voxtral-small-24b-2507.config.json +187 -0
- package/nodes/mistralai-voxtral-small-24b-2507/mistralai-voxtral-small-24b-2507.process.js +136 -0
- package/nodes/mistralai-voxtral-small-24b-2507/mistralai-voxtral-small-24b-2507.tests.json +80 -0
- package/nodes/mode/mode.config.json +29 -0
- package/nodes/mode/mode.process.js +45 -0
- package/nodes/mode/mode.tests.json +53 -0
- package/nodes/modulo/modulo.config.json +30 -0
- package/nodes/modulo/modulo.process.js +31 -0
- package/nodes/modulo/modulo.tests.json +66 -0
- package/nodes/multiply/multiply.config.json +30 -0
- package/nodes/multiply/multiply.process.js +9 -0
- package/nodes/multiply/multiply.tests.json +42 -0
- package/nodes/nand-gate/nand-gate.config.json +30 -0
- package/nodes/nand-gate/nand-gate.process.js +10 -0
- package/nodes/nand-gate/nand-gate.tests.json +22 -0
- package/nodes/newsdata-io-archive/newsdata-io-archive.config.json +144 -0
- package/nodes/newsdata-io-archive/newsdata-io-archive.process.js +60 -0
- package/nodes/newsdata-io-archive/newsdata-io-archive.tests.json +68 -0
- package/nodes/newsdata-io-latest/newsdata-io-latest.config.json +142 -0
- package/nodes/newsdata-io-latest/newsdata-io-latest.process.js +60 -0
- package/nodes/newsdata-io-latest/newsdata-io-latest.tests.json +74 -0
- package/nodes/newsdata-io-sources/newsdata-io-sources.config.json +51 -0
- package/nodes/newsdata-io-sources/newsdata-io-sources.process.js +41 -0
- package/nodes/newsdata-io-sources/newsdata-io-sources.tests.json +53 -0
- package/nodes/nor-gate/nor-gate.config.json +30 -0
- package/nodes/nor-gate/nor-gate.process.js +10 -0
- package/nodes/nor-gate/nor-gate.tests.json +22 -0
- package/nodes/normalize-whitespace/normalize-whitespace.config.json +56 -0
- package/nodes/normalize-whitespace/normalize-whitespace.process.js +36 -0
- package/nodes/normalize-whitespace/normalize-whitespace.tests.json +76 -0
- package/nodes/not-equals/not-equals.config.json +32 -0
- package/nodes/not-equals/not-equals.process.js +29 -0
- package/nodes/not-equals/not-equals.tests.json +77 -0
- package/nodes/notion-append-blocks/notion-append-blocks.config.json +38 -0
- package/nodes/notion-append-blocks/notion-append-blocks.process.js +34 -0
- package/nodes/notion-append-blocks/notion-append-blocks.tests.json +80 -0
- package/nodes/notion-archive-page/notion-archive-page.config.json +44 -0
- package/nodes/notion-archive-page/notion-archive-page.process.js +29 -0
- package/nodes/notion-archive-page/notion-archive-page.tests.json +26 -0
- package/nodes/notion-create-page/notion-create-page.config.json +71 -0
- package/nodes/notion-create-page/notion-create-page.process.js +45 -0
- package/nodes/notion-create-page/notion-create-page.tests.json +92 -0
- package/nodes/notion-get-blocks/notion-get-blocks.config.json +51 -0
- package/nodes/notion-get-blocks/notion-get-blocks.process.js +29 -0
- package/nodes/notion-get-blocks/notion-get-blocks.tests.json +22 -0
- package/nodes/notion-get-page/notion-get-page.config.json +54 -0
- package/nodes/notion-get-page/notion-get-page.process.js +29 -0
- package/nodes/notion-get-page/notion-get-page.tests.json +14 -0
- package/nodes/notion-query-database/notion-query-database.config.json +65 -0
- package/nodes/notion-query-database/notion-query-database.process.js +31 -0
- package/nodes/notion-query-database/notion-query-database.tests.json +52 -0
- package/nodes/notion-search/notion-search.config.json +74 -0
- package/nodes/notion-search/notion-search.process.js +41 -0
- package/nodes/notion-search/notion-search.tests.json +46 -0
- package/nodes/notion-update-page/notion-update-page.config.json +57 -0
- package/nodes/notion-update-page/notion-update-page.process.js +34 -0
- package/nodes/notion-update-page/notion-update-page.tests.json +44 -0
- package/nodes/null-bomb/null-bomb.config.json +36 -0
- package/nodes/null-bomb/null-bomb.process.js +21 -0
- package/nodes/null-bomb/null-bomb.tests.json +83 -0
- package/nodes/number/number.config.json +59 -0
- package/nodes/number/number.process.js +16 -0
- package/nodes/number/number.tests.json +46 -0
- package/nodes/nvidia-llama-nemotron-embed-vl-1b-v2-free/nvidia-llama-nemotron-embed-vl-1b-v2-free.config.json +91 -0
- package/nodes/nvidia-llama-nemotron-embed-vl-1b-v2-free/nvidia-llama-nemotron-embed-vl-1b-v2-free.process.js +36 -0
- package/nodes/nvidia-llama-nemotron-embed-vl-1b-v2-free/nvidia-llama-nemotron-embed-vl-1b-v2-free.tests.json +48 -0
- package/nodes/nvidia-llama-nemotron-rerank-vl-1b-v2-free/nvidia-llama-nemotron-rerank-vl-1b-v2-free.config.json +95 -0
- package/nodes/nvidia-llama-nemotron-rerank-vl-1b-v2-free/nvidia-llama-nemotron-rerank-vl-1b-v2-free.process.js +60 -0
- package/nodes/nvidia-llama-nemotron-rerank-vl-1b-v2-free/nvidia-llama-nemotron-rerank-vl-1b-v2-free.tests.json +47 -0
- package/nodes/object-merge/object-merge.config.json +34 -0
- package/nodes/object-merge/object-merge.process.js +53 -0
- package/nodes/object-merge/object-merge.tests.json +146 -0
- package/nodes/openai-gpt-3-5-turbo/openai-gpt-3-5-turbo.config.json +210 -0
- package/nodes/openai-gpt-3-5-turbo/openai-gpt-3-5-turbo.process.js +137 -0
- package/nodes/openai-gpt-3-5-turbo/openai-gpt-3-5-turbo.tests.json +80 -0
- package/nodes/openai-gpt-3-5-turbo-0613/openai-gpt-3-5-turbo-0613.config.json +203 -0
- package/nodes/openai-gpt-3-5-turbo-0613/openai-gpt-3-5-turbo-0613.process.js +137 -0
- package/nodes/openai-gpt-3-5-turbo-0613/openai-gpt-3-5-turbo-0613.tests.json +80 -0
- package/nodes/openai-gpt-3-5-turbo-16k/openai-gpt-3-5-turbo-16k.config.json +211 -0
- package/nodes/openai-gpt-3-5-turbo-16k/openai-gpt-3-5-turbo-16k.process.js +137 -0
- package/nodes/openai-gpt-3-5-turbo-16k/openai-gpt-3-5-turbo-16k.tests.json +80 -0
- package/nodes/openai-gpt-3-5-turbo-instruct/openai-gpt-3-5-turbo-instruct.config.json +187 -0
- package/nodes/openai-gpt-3-5-turbo-instruct/openai-gpt-3-5-turbo-instruct.process.js +137 -0
- package/nodes/openai-gpt-3-5-turbo-instruct/openai-gpt-3-5-turbo-instruct.tests.json +80 -0
- package/nodes/openai-gpt-4/openai-gpt-4.config.json +211 -0
- package/nodes/openai-gpt-4/openai-gpt-4.process.js +137 -0
- package/nodes/openai-gpt-4/openai-gpt-4.tests.json +80 -0
- package/nodes/openai-gpt-4-1/openai-gpt-4-1.config.json +164 -0
- package/nodes/openai-gpt-4-1/openai-gpt-4-1.process.js +136 -0
- package/nodes/openai-gpt-4-1/openai-gpt-4-1.tests.json +80 -0
- package/nodes/openai-gpt-4-1-mini/openai-gpt-4-1-mini.config.json +164 -0
- package/nodes/openai-gpt-4-1-mini/openai-gpt-4-1-mini.process.js +136 -0
- package/nodes/openai-gpt-4-1-mini/openai-gpt-4-1-mini.tests.json +80 -0
- package/nodes/openai-gpt-4-1-nano/openai-gpt-4-1-nano.config.json +164 -0
- package/nodes/openai-gpt-4-1-nano/openai-gpt-4-1-nano.process.js +136 -0
- package/nodes/openai-gpt-4-1-nano/openai-gpt-4-1-nano.tests.json +80 -0
- package/nodes/openai-gpt-4-1106-preview/openai-gpt-4-1106-preview.config.json +210 -0
- package/nodes/openai-gpt-4-1106-preview/openai-gpt-4-1106-preview.process.js +137 -0
- package/nodes/openai-gpt-4-1106-preview/openai-gpt-4-1106-preview.tests.json +80 -0
- package/nodes/openai-gpt-4-turbo/openai-gpt-4-turbo.config.json +210 -0
- package/nodes/openai-gpt-4-turbo/openai-gpt-4-turbo.process.js +137 -0
- package/nodes/openai-gpt-4-turbo/openai-gpt-4-turbo.tests.json +80 -0
- package/nodes/openai-gpt-4-turbo-preview/openai-gpt-4-turbo-preview.config.json +210 -0
- package/nodes/openai-gpt-4-turbo-preview/openai-gpt-4-turbo-preview.process.js +137 -0
- package/nodes/openai-gpt-4-turbo-preview/openai-gpt-4-turbo-preview.tests.json +80 -0
- package/nodes/openai-gpt-4o/openai-gpt-4o.config.json +224 -0
- package/nodes/openai-gpt-4o/openai-gpt-4o.process.js +139 -0
- package/nodes/openai-gpt-4o/openai-gpt-4o.tests.json +80 -0
- package/nodes/openai-gpt-4o-2024-05-13/openai-gpt-4o-2024-05-13.config.json +224 -0
- package/nodes/openai-gpt-4o-2024-05-13/openai-gpt-4o-2024-05-13.process.js +139 -0
- package/nodes/openai-gpt-4o-2024-05-13/openai-gpt-4o-2024-05-13.tests.json +80 -0
- package/nodes/openai-gpt-4o-2024-08-06/openai-gpt-4o-2024-08-06.config.json +224 -0
- package/nodes/openai-gpt-4o-2024-08-06/openai-gpt-4o-2024-08-06.process.js +139 -0
- package/nodes/openai-gpt-4o-2024-08-06/openai-gpt-4o-2024-08-06.tests.json +80 -0
- package/nodes/openai-gpt-4o-2024-11-20/openai-gpt-4o-2024-11-20.config.json +223 -0
- package/nodes/openai-gpt-4o-2024-11-20/openai-gpt-4o-2024-11-20.process.js +139 -0
- package/nodes/openai-gpt-4o-2024-11-20/openai-gpt-4o-2024-11-20.tests.json +80 -0
- package/nodes/openai-gpt-4o-mini/openai-gpt-4o-mini.config.json +224 -0
- package/nodes/openai-gpt-4o-mini/openai-gpt-4o-mini.process.js +139 -0
- package/nodes/openai-gpt-4o-mini/openai-gpt-4o-mini.tests.json +80 -0
- package/nodes/openai-gpt-4o-mini-2024-07-18/openai-gpt-4o-mini-2024-07-18.config.json +223 -0
- package/nodes/openai-gpt-4o-mini-2024-07-18/openai-gpt-4o-mini-2024-07-18.process.js +139 -0
- package/nodes/openai-gpt-4o-mini-2024-07-18/openai-gpt-4o-mini-2024-07-18.tests.json +80 -0
- package/nodes/openai-gpt-4o-mini-search-preview/openai-gpt-4o-mini-search-preview.config.json +101 -0
- package/nodes/openai-gpt-4o-mini-search-preview/openai-gpt-4o-mini-search-preview.process.js +53 -0
- package/nodes/openai-gpt-4o-mini-search-preview/openai-gpt-4o-mini-search-preview.tests.json +26 -0
- package/nodes/openai-gpt-4o-search-preview/openai-gpt-4o-search-preview.config.json +101 -0
- package/nodes/openai-gpt-4o-search-preview/openai-gpt-4o-search-preview.process.js +53 -0
- package/nodes/openai-gpt-4o-search-preview/openai-gpt-4o-search-preview.tests.json +26 -0
- package/nodes/openai-gpt-5/openai-gpt-5.config.json +170 -0
- package/nodes/openai-gpt-5/openai-gpt-5.process.js +138 -0
- package/nodes/openai-gpt-5/openai-gpt-5.tests.json +57 -0
- package/nodes/openai-gpt-5-1/openai-gpt-5-1.config.json +170 -0
- package/nodes/openai-gpt-5-1/openai-gpt-5-1.process.js +138 -0
- package/nodes/openai-gpt-5-1/openai-gpt-5-1.tests.json +57 -0
- package/nodes/openai-gpt-5-1-chat/openai-gpt-5-1-chat.config.json +148 -0
- package/nodes/openai-gpt-5-1-chat/openai-gpt-5-1-chat.process.js +136 -0
- package/nodes/openai-gpt-5-1-chat/openai-gpt-5-1-chat.tests.json +57 -0
- package/nodes/openai-gpt-5-1-codex/openai-gpt-5-1-codex.config.json +170 -0
- package/nodes/openai-gpt-5-1-codex/openai-gpt-5-1-codex.process.js +138 -0
- package/nodes/openai-gpt-5-1-codex/openai-gpt-5-1-codex.tests.json +57 -0
- package/nodes/openai-gpt-5-1-codex-max/openai-gpt-5-1-codex-max.config.json +170 -0
- package/nodes/openai-gpt-5-1-codex-max/openai-gpt-5-1-codex-max.process.js +138 -0
- package/nodes/openai-gpt-5-1-codex-max/openai-gpt-5-1-codex-max.tests.json +57 -0
- package/nodes/openai-gpt-5-1-codex-mini/openai-gpt-5-1-codex-mini.config.json +170 -0
- package/nodes/openai-gpt-5-1-codex-mini/openai-gpt-5-1-codex-mini.process.js +138 -0
- package/nodes/openai-gpt-5-1-codex-mini/openai-gpt-5-1-codex-mini.tests.json +57 -0
- package/nodes/openai-gpt-5-2/openai-gpt-5-2.config.json +170 -0
- package/nodes/openai-gpt-5-2/openai-gpt-5-2.process.js +138 -0
- package/nodes/openai-gpt-5-2/openai-gpt-5-2.tests.json +57 -0
- package/nodes/openai-gpt-5-2-chat/openai-gpt-5-2-chat.config.json +148 -0
- package/nodes/openai-gpt-5-2-chat/openai-gpt-5-2-chat.process.js +136 -0
- package/nodes/openai-gpt-5-2-chat/openai-gpt-5-2-chat.tests.json +57 -0
- package/nodes/openai-gpt-5-2-codex/openai-gpt-5-2-codex.config.json +170 -0
- package/nodes/openai-gpt-5-2-codex/openai-gpt-5-2-codex.process.js +138 -0
- package/nodes/openai-gpt-5-2-codex/openai-gpt-5-2-codex.tests.json +57 -0
- package/nodes/openai-gpt-5-2-pro/openai-gpt-5-2-pro.config.json +169 -0
- package/nodes/openai-gpt-5-2-pro/openai-gpt-5-2-pro.process.js +138 -0
- package/nodes/openai-gpt-5-2-pro/openai-gpt-5-2-pro.tests.json +57 -0
- package/nodes/openai-gpt-5-3-chat/openai-gpt-5-3-chat.config.json +148 -0
- package/nodes/openai-gpt-5-3-chat/openai-gpt-5-3-chat.process.js +136 -0
- package/nodes/openai-gpt-5-3-chat/openai-gpt-5-3-chat.tests.json +57 -0
- package/nodes/openai-gpt-5-3-codex/openai-gpt-5-3-codex.config.json +170 -0
- package/nodes/openai-gpt-5-3-codex/openai-gpt-5-3-codex.process.js +138 -0
- package/nodes/openai-gpt-5-3-codex/openai-gpt-5-3-codex.tests.json +57 -0
- package/nodes/openai-gpt-5-4/openai-gpt-5-4.config.json +170 -0
- package/nodes/openai-gpt-5-4/openai-gpt-5-4.process.js +138 -0
- package/nodes/openai-gpt-5-4/openai-gpt-5-4.tests.json +57 -0
- package/nodes/openai-gpt-5-4-image-2/openai-gpt-5-4-image-2.config.json +153 -0
- package/nodes/openai-gpt-5-4-image-2/openai-gpt-5-4-image-2.process.js +58 -0
- package/nodes/openai-gpt-5-4-image-2/openai-gpt-5-4-image-2.tests.json +26 -0
- package/nodes/openai-gpt-5-4-mini/openai-gpt-5-4-mini.config.json +170 -0
- package/nodes/openai-gpt-5-4-mini/openai-gpt-5-4-mini.process.js +138 -0
- package/nodes/openai-gpt-5-4-mini/openai-gpt-5-4-mini.tests.json +57 -0
- package/nodes/openai-gpt-5-4-nano/openai-gpt-5-4-nano.config.json +170 -0
- package/nodes/openai-gpt-5-4-nano/openai-gpt-5-4-nano.process.js +138 -0
- package/nodes/openai-gpt-5-4-nano/openai-gpt-5-4-nano.tests.json +57 -0
- package/nodes/openai-gpt-5-4-pro/openai-gpt-5-4-pro.config.json +170 -0
- package/nodes/openai-gpt-5-4-pro/openai-gpt-5-4-pro.process.js +138 -0
- package/nodes/openai-gpt-5-4-pro/openai-gpt-5-4-pro.tests.json +57 -0
- package/nodes/openai-gpt-5-5/openai-gpt-5-5.config.json +170 -0
- package/nodes/openai-gpt-5-5/openai-gpt-5-5.process.js +138 -0
- package/nodes/openai-gpt-5-5/openai-gpt-5-5.tests.json +57 -0
- package/nodes/openai-gpt-5-5-pro/openai-gpt-5-5-pro.config.json +169 -0
- package/nodes/openai-gpt-5-5-pro/openai-gpt-5-5-pro.process.js +138 -0
- package/nodes/openai-gpt-5-5-pro/openai-gpt-5-5-pro.tests.json +57 -0
- package/nodes/openai-gpt-5-chat/openai-gpt-5-chat.config.json +96 -0
- package/nodes/openai-gpt-5-chat/openai-gpt-5-chat.process.js +51 -0
- package/nodes/openai-gpt-5-chat/openai-gpt-5-chat.tests.json +26 -0
- package/nodes/openai-gpt-5-codex/openai-gpt-5-codex.config.json +169 -0
- package/nodes/openai-gpt-5-codex/openai-gpt-5-codex.process.js +138 -0
- package/nodes/openai-gpt-5-codex/openai-gpt-5-codex.tests.json +57 -0
- package/nodes/openai-gpt-5-image/openai-gpt-5-image.config.json +169 -0
- package/nodes/openai-gpt-5-image/openai-gpt-5-image.process.js +58 -0
- package/nodes/openai-gpt-5-image/openai-gpt-5-image.tests.json +44 -0
- package/nodes/openai-gpt-5-image-mini/openai-gpt-5-image-mini.config.json +169 -0
- package/nodes/openai-gpt-5-image-mini/openai-gpt-5-image-mini.process.js +58 -0
- package/nodes/openai-gpt-5-image-mini/openai-gpt-5-image-mini.tests.json +44 -0
- package/nodes/openai-gpt-5-mini/openai-gpt-5-mini.config.json +170 -0
- package/nodes/openai-gpt-5-mini/openai-gpt-5-mini.process.js +138 -0
- package/nodes/openai-gpt-5-mini/openai-gpt-5-mini.tests.json +57 -0
- package/nodes/openai-gpt-5-nano/openai-gpt-5-nano.config.json +170 -0
- package/nodes/openai-gpt-5-nano/openai-gpt-5-nano.process.js +138 -0
- package/nodes/openai-gpt-5-nano/openai-gpt-5-nano.tests.json +57 -0
- package/nodes/openai-gpt-5-pro/openai-gpt-5-pro.config.json +169 -0
- package/nodes/openai-gpt-5-pro/openai-gpt-5-pro.process.js +138 -0
- package/nodes/openai-gpt-5-pro/openai-gpt-5-pro.tests.json +57 -0
- package/nodes/openai-gpt-audio/openai-gpt-audio.config.json +210 -0
- package/nodes/openai-gpt-audio/openai-gpt-audio.process.js +137 -0
- package/nodes/openai-gpt-audio/openai-gpt-audio.tests.json +80 -0
- package/nodes/openai-gpt-audio-mini/openai-gpt-audio-mini.config.json +210 -0
- package/nodes/openai-gpt-audio-mini/openai-gpt-audio-mini.process.js +137 -0
- package/nodes/openai-gpt-audio-mini/openai-gpt-audio-mini.tests.json +80 -0
- package/nodes/openai-gpt-chat-latest/openai-gpt-chat-latest.config.json +194 -0
- package/nodes/openai-gpt-chat-latest/openai-gpt-chat-latest.process.js +137 -0
- package/nodes/openai-gpt-chat-latest/openai-gpt-chat-latest.tests.json +57 -0
- package/nodes/openai-gpt-oss-120b/openai-gpt-oss-120b.config.json +235 -0
- package/nodes/openai-gpt-oss-120b/openai-gpt-oss-120b.process.js +139 -0
- package/nodes/openai-gpt-oss-120b/openai-gpt-oss-120b.tests.json +80 -0
- package/nodes/openai-gpt-oss-120b-free/openai-gpt-oss-120b-free.config.json +176 -0
- package/nodes/openai-gpt-oss-120b-free/openai-gpt-oss-120b-free.process.js +138 -0
- package/nodes/openai-gpt-oss-120b-free/openai-gpt-oss-120b-free.tests.json +80 -0
- package/nodes/openai-gpt-oss-20b/openai-gpt-oss-20b.config.json +235 -0
- package/nodes/openai-gpt-oss-20b/openai-gpt-oss-20b.process.js +139 -0
- package/nodes/openai-gpt-oss-20b/openai-gpt-oss-20b.tests.json +80 -0
- package/nodes/openai-gpt-oss-20b-free/openai-gpt-oss-20b-free.config.json +176 -0
- package/nodes/openai-gpt-oss-20b-free/openai-gpt-oss-20b-free.process.js +138 -0
- package/nodes/openai-gpt-oss-20b-free/openai-gpt-oss-20b-free.tests.json +80 -0
- package/nodes/openai-gpt-oss-safeguard-20b/openai-gpt-oss-safeguard-20b.config.json +192 -0
- package/nodes/openai-gpt-oss-safeguard-20b/openai-gpt-oss-safeguard-20b.process.js +138 -0
- package/nodes/openai-gpt-oss-safeguard-20b/openai-gpt-oss-safeguard-20b.tests.json +80 -0
- package/nodes/openai-o1/openai-o1.config.json +169 -0
- package/nodes/openai-o1/openai-o1.process.js +138 -0
- package/nodes/openai-o1/openai-o1.tests.json +57 -0
- package/nodes/openai-o1-pro/openai-o1-pro.config.json +118 -0
- package/nodes/openai-o1-pro/openai-o1-pro.process.js +53 -0
- package/nodes/openai-o1-pro/openai-o1-pro.tests.json +26 -0
- package/nodes/openai-o3/openai-o3.config.json +169 -0
- package/nodes/openai-o3/openai-o3.process.js +138 -0
- package/nodes/openai-o3/openai-o3.tests.json +57 -0
- package/nodes/openai-o3-deep-research/openai-o3-deep-research.config.json +244 -0
- package/nodes/openai-o3-deep-research/openai-o3-deep-research.process.js +141 -0
- package/nodes/openai-o3-deep-research/openai-o3-deep-research.tests.json +80 -0
- package/nodes/openai-o3-mini/openai-o3-mini.config.json +169 -0
- package/nodes/openai-o3-mini/openai-o3-mini.process.js +138 -0
- package/nodes/openai-o3-mini/openai-o3-mini.tests.json +57 -0
- package/nodes/openai-o3-mini-high/openai-o3-mini-high.config.json +169 -0
- package/nodes/openai-o3-mini-high/openai-o3-mini-high.process.js +138 -0
- package/nodes/openai-o3-mini-high/openai-o3-mini-high.tests.json +57 -0
- package/nodes/openai-o3-pro/openai-o3-pro.config.json +169 -0
- package/nodes/openai-o3-pro/openai-o3-pro.process.js +138 -0
- package/nodes/openai-o3-pro/openai-o3-pro.tests.json +57 -0
- package/nodes/openai-o4-mini/openai-o4-mini.config.json +169 -0
- package/nodes/openai-o4-mini/openai-o4-mini.process.js +138 -0
- package/nodes/openai-o4-mini/openai-o4-mini.tests.json +57 -0
- package/nodes/openai-o4-mini-deep-research/openai-o4-mini-deep-research.config.json +244 -0
- package/nodes/openai-o4-mini-deep-research/openai-o4-mini-deep-research.process.js +141 -0
- package/nodes/openai-o4-mini-deep-research/openai-o4-mini-deep-research.tests.json +80 -0
- package/nodes/openai-o4-mini-high/openai-o4-mini-high.config.json +169 -0
- package/nodes/openai-o4-mini-high/openai-o4-mini-high.process.js +138 -0
- package/nodes/openai-o4-mini-high/openai-o4-mini-high.tests.json +57 -0
- package/nodes/openai-omni-moderation-latest/openai-omni-moderation-latest.config.json +183 -0
- package/nodes/openai-omni-moderation-latest/openai-omni-moderation-latest.process.js +93 -0
- package/nodes/openai-omni-moderation-latest/openai-omni-moderation-latest.tests.json +103 -0
- package/nodes/openai-text-embedding-3-large/openai-text-embedding-3-large.config.json +90 -0
- package/nodes/openai-text-embedding-3-large/openai-text-embedding-3-large.process.js +36 -0
- package/nodes/openai-text-embedding-3-large/openai-text-embedding-3-large.tests.json +48 -0
- package/nodes/openai-text-embedding-3-small/openai-text-embedding-3-small.config.json +90 -0
- package/nodes/openai-text-embedding-3-small/openai-text-embedding-3-small.process.js +36 -0
- package/nodes/openai-text-embedding-3-small/openai-text-embedding-3-small.tests.json +48 -0
- package/nodes/openai-text-embedding-ada-002/openai-text-embedding-ada-002.config.json +90 -0
- package/nodes/openai-text-embedding-ada-002/openai-text-embedding-ada-002.process.js +36 -0
- package/nodes/openai-text-embedding-ada-002/openai-text-embedding-ada-002.tests.json +48 -0
- package/nodes/or-gate/or-gate.config.json +30 -0
- package/nodes/or-gate/or-gate.process.js +10 -0
- package/nodes/or-gate/or-gate.tests.json +22 -0
- package/nodes/output-chat/output-chat.config.json +49 -0
- package/nodes/output-chat/output-chat.process.js +20 -0
- package/nodes/output-data/output-data.config.json +59 -0
- package/nodes/output-data/output-data.process.js +10 -0
- package/nodes/parse-url/parse-url.config.json +72 -0
- package/nodes/parse-url/parse-url.process.js +61 -0
- package/nodes/parse-url/parse-url.tests.json +121 -0
- package/nodes/perplexity-pplx-embed-v1-0-6b/perplexity-pplx-embed-v1-0-6b.config.json +90 -0
- package/nodes/perplexity-pplx-embed-v1-0-6b/perplexity-pplx-embed-v1-0-6b.process.js +36 -0
- package/nodes/perplexity-pplx-embed-v1-0-6b/perplexity-pplx-embed-v1-0-6b.tests.json +48 -0
- package/nodes/perplexity-pplx-embed-v1-4b/perplexity-pplx-embed-v1-4b.config.json +90 -0
- package/nodes/perplexity-pplx-embed-v1-4b/perplexity-pplx-embed-v1-4b.process.js +36 -0
- package/nodes/perplexity-pplx-embed-v1-4b/perplexity-pplx-embed-v1-4b.tests.json +48 -0
- package/nodes/perplexity-sonar/perplexity-sonar.config.json +125 -0
- package/nodes/perplexity-sonar/perplexity-sonar.process.js +53 -0
- package/nodes/perplexity-sonar/perplexity-sonar.tests.json +44 -0
- package/nodes/perplexity-sonar-deep-research/perplexity-sonar-deep-research.config.json +175 -0
- package/nodes/perplexity-sonar-deep-research/perplexity-sonar-deep-research.process.js +140 -0
- package/nodes/perplexity-sonar-deep-research/perplexity-sonar-deep-research.tests.json +80 -0
- package/nodes/perplexity-sonar-pro/perplexity-sonar-pro.config.json +125 -0
- package/nodes/perplexity-sonar-pro/perplexity-sonar-pro.process.js +53 -0
- package/nodes/perplexity-sonar-pro/perplexity-sonar-pro.tests.json +44 -0
- package/nodes/perplexity-sonar-pro-search/perplexity-sonar-pro-search.config.json +148 -0
- package/nodes/perplexity-sonar-pro-search/perplexity-sonar-pro-search.process.js +55 -0
- package/nodes/perplexity-sonar-pro-search/perplexity-sonar-pro-search.tests.json +44 -0
- package/nodes/perplexity-sonar-reasoning-pro/perplexity-sonar-reasoning-pro.config.json +175 -0
- package/nodes/perplexity-sonar-reasoning-pro/perplexity-sonar-reasoning-pro.process.js +140 -0
- package/nodes/perplexity-sonar-reasoning-pro/perplexity-sonar-reasoning-pro.tests.json +80 -0
- package/nodes/pinecone-query/pinecone-query.config.json +69 -0
- package/nodes/pinecone-query/pinecone-query.process.js +22 -0
- package/nodes/pinecone-query/pinecone-query.tests.json +12 -0
- package/nodes/pinecone-upsert/pinecone-upsert.config.json +32 -0
- package/nodes/pinecone-upsert/pinecone-upsert.process.js +15 -0
- package/nodes/pinecone-upsert/pinecone-upsert.tests.json +13 -0
- package/nodes/power/power.config.json +30 -0
- package/nodes/power/power.process.js +9 -0
- package/nodes/power/power.tests.json +42 -0
- package/nodes/quartiles/quartiles.config.json +41 -0
- package/nodes/quartiles/quartiles.process.js +62 -0
- package/nodes/quartiles/quartiles.tests.json +69 -0
- package/nodes/query-knowledge-base/query-knowledge-base.config.json +81 -0
- package/nodes/query-knowledge-base/query-knowledge-base.process.js +35 -0
- package/nodes/qwen-qwen-2-5-72b-instruct/qwen-qwen-2-5-72b-instruct.config.json +191 -0
- package/nodes/qwen-qwen-2-5-72b-instruct/qwen-qwen-2-5-72b-instruct.process.js +136 -0
- package/nodes/qwen-qwen-2-5-72b-instruct/qwen-qwen-2-5-72b-instruct.tests.json +80 -0
- package/nodes/qwen-qwen-2-5-7b-instruct/qwen-qwen-2-5-7b-instruct.config.json +159 -0
- package/nodes/qwen-qwen-2-5-7b-instruct/qwen-qwen-2-5-7b-instruct.process.js +136 -0
- package/nodes/qwen-qwen-2-5-7b-instruct/qwen-qwen-2-5-7b-instruct.tests.json +80 -0
- package/nodes/qwen-qwen-2-5-coder-32b-instruct/qwen-qwen-2-5-coder-32b-instruct.config.json +159 -0
- package/nodes/qwen-qwen-2-5-coder-32b-instruct/qwen-qwen-2-5-coder-32b-instruct.process.js +136 -0
- package/nodes/qwen-qwen-2-5-coder-32b-instruct/qwen-qwen-2-5-coder-32b-instruct.tests.json +80 -0
- package/nodes/qwen-qwen-plus/qwen-qwen-plus.config.json +170 -0
- package/nodes/qwen-qwen-plus/qwen-qwen-plus.process.js +136 -0
- package/nodes/qwen-qwen-plus/qwen-qwen-plus.tests.json +80 -0
- package/nodes/qwen-qwen-plus-2025-07-28/qwen-qwen-plus-2025-07-28.config.json +171 -0
- package/nodes/qwen-qwen-plus-2025-07-28/qwen-qwen-plus-2025-07-28.process.js +136 -0
- package/nodes/qwen-qwen-plus-2025-07-28/qwen-qwen-plus-2025-07-28.tests.json +80 -0
- package/nodes/qwen-qwen-plus-2025-07-28-thinking/qwen-qwen-plus-2025-07-28-thinking.config.json +193 -0
- package/nodes/qwen-qwen-plus-2025-07-28-thinking/qwen-qwen-plus-2025-07-28-thinking.process.js +138 -0
- package/nodes/qwen-qwen-plus-2025-07-28-thinking/qwen-qwen-plus-2025-07-28-thinking.tests.json +80 -0
- package/nodes/qwen-qwen2-5-vl-72b-instruct/qwen-qwen2-5-vl-72b-instruct.config.json +139 -0
- package/nodes/qwen-qwen2-5-vl-72b-instruct/qwen-qwen2-5-vl-72b-instruct.process.js +51 -0
- package/nodes/qwen-qwen2-5-vl-72b-instruct/qwen-qwen2-5-vl-72b-instruct.tests.json +44 -0
- package/nodes/qwen-qwen3-14b/qwen-qwen3-14b.config.json +235 -0
- package/nodes/qwen-qwen3-14b/qwen-qwen3-14b.process.js +139 -0
- package/nodes/qwen-qwen3-14b/qwen-qwen3-14b.tests.json +80 -0
- package/nodes/qwen-qwen3-235b-a22b/qwen-qwen3-235b-a22b.config.json +192 -0
- package/nodes/qwen-qwen3-235b-a22b/qwen-qwen3-235b-a22b.process.js +138 -0
- package/nodes/qwen-qwen3-235b-a22b/qwen-qwen3-235b-a22b.tests.json +80 -0
- package/nodes/qwen-qwen3-235b-a22b-2507/qwen-qwen3-235b-a22b-2507.config.json +191 -0
- package/nodes/qwen-qwen3-235b-a22b-2507/qwen-qwen3-235b-a22b-2507.process.js +136 -0
- package/nodes/qwen-qwen3-235b-a22b-2507/qwen-qwen3-235b-a22b-2507.tests.json +80 -0
- package/nodes/qwen-qwen3-235b-a22b-thinking-2507/qwen-qwen3-235b-a22b-thinking-2507.config.json +213 -0
- package/nodes/qwen-qwen3-235b-a22b-thinking-2507/qwen-qwen3-235b-a22b-thinking-2507.process.js +138 -0
- package/nodes/qwen-qwen3-235b-a22b-thinking-2507/qwen-qwen3-235b-a22b-thinking-2507.tests.json +80 -0
- package/nodes/qwen-qwen3-30b-a3b/qwen-qwen3-30b-a3b.config.json +235 -0
- package/nodes/qwen-qwen3-30b-a3b/qwen-qwen3-30b-a3b.process.js +139 -0
- package/nodes/qwen-qwen3-30b-a3b/qwen-qwen3-30b-a3b.tests.json +80 -0
- package/nodes/qwen-qwen3-30b-a3b-instruct-2507/qwen-qwen3-30b-a3b-instruct-2507.config.json +191 -0
- package/nodes/qwen-qwen3-30b-a3b-instruct-2507/qwen-qwen3-30b-a3b-instruct-2507.process.js +136 -0
- package/nodes/qwen-qwen3-30b-a3b-instruct-2507/qwen-qwen3-30b-a3b-instruct-2507.tests.json +80 -0
- package/nodes/qwen-qwen3-30b-a3b-thinking-2507/qwen-qwen3-30b-a3b-thinking-2507.config.json +213 -0
- package/nodes/qwen-qwen3-30b-a3b-thinking-2507/qwen-qwen3-30b-a3b-thinking-2507.process.js +138 -0
- package/nodes/qwen-qwen3-30b-a3b-thinking-2507/qwen-qwen3-30b-a3b-thinking-2507.tests.json +80 -0
- package/nodes/qwen-qwen3-32b/qwen-qwen3-32b.config.json +213 -0
- package/nodes/qwen-qwen3-32b/qwen-qwen3-32b.process.js +138 -0
- package/nodes/qwen-qwen3-32b/qwen-qwen3-32b.tests.json +80 -0
- package/nodes/qwen-qwen3-5-122b-a10b/qwen-qwen3-5-122b-a10b.config.json +235 -0
- package/nodes/qwen-qwen3-5-122b-a10b/qwen-qwen3-5-122b-a10b.process.js +139 -0
- package/nodes/qwen-qwen3-5-122b-a10b/qwen-qwen3-5-122b-a10b.tests.json +80 -0
- package/nodes/qwen-qwen3-5-27b/qwen-qwen3-5-27b.config.json +235 -0
- package/nodes/qwen-qwen3-5-27b/qwen-qwen3-5-27b.process.js +139 -0
- package/nodes/qwen-qwen3-5-27b/qwen-qwen3-5-27b.tests.json +80 -0
- package/nodes/qwen-qwen3-5-35b-a3b/qwen-qwen3-5-35b-a3b.config.json +235 -0
- package/nodes/qwen-qwen3-5-35b-a3b/qwen-qwen3-5-35b-a3b.process.js +139 -0
- package/nodes/qwen-qwen3-5-35b-a3b/qwen-qwen3-5-35b-a3b.tests.json +80 -0
- package/nodes/qwen-qwen3-5-397b-a17b/qwen-qwen3-5-397b-a17b.config.json +235 -0
- package/nodes/qwen-qwen3-5-397b-a17b/qwen-qwen3-5-397b-a17b.process.js +139 -0
- package/nodes/qwen-qwen3-5-397b-a17b/qwen-qwen3-5-397b-a17b.tests.json +80 -0
- package/nodes/qwen-qwen3-5-9b/qwen-qwen3-5-9b.config.json +235 -0
- package/nodes/qwen-qwen3-5-9b/qwen-qwen3-5-9b.process.js +139 -0
- package/nodes/qwen-qwen3-5-9b/qwen-qwen3-5-9b.tests.json +80 -0
- package/nodes/qwen-qwen3-5-flash-02-23/qwen-qwen3-5-flash-02-23.config.json +193 -0
- package/nodes/qwen-qwen3-5-flash-02-23/qwen-qwen3-5-flash-02-23.process.js +138 -0
- package/nodes/qwen-qwen3-5-flash-02-23/qwen-qwen3-5-flash-02-23.tests.json +80 -0
- package/nodes/qwen-qwen3-5-plus-02-15/qwen-qwen3-5-plus-02-15.config.json +193 -0
- package/nodes/qwen-qwen3-5-plus-02-15/qwen-qwen3-5-plus-02-15.process.js +138 -0
- package/nodes/qwen-qwen3-5-plus-02-15/qwen-qwen3-5-plus-02-15.tests.json +80 -0
- package/nodes/qwen-qwen3-5-plus-20260420/qwen-qwen3-5-plus-20260420.config.json +193 -0
- package/nodes/qwen-qwen3-5-plus-20260420/qwen-qwen3-5-plus-20260420.process.js +138 -0
- package/nodes/qwen-qwen3-5-plus-20260420/qwen-qwen3-5-plus-20260420.tests.json +80 -0
- package/nodes/qwen-qwen3-6-27b/qwen-qwen3-6-27b.config.json +235 -0
- package/nodes/qwen-qwen3-6-27b/qwen-qwen3-6-27b.process.js +139 -0
- package/nodes/qwen-qwen3-6-27b/qwen-qwen3-6-27b.tests.json +80 -0
- package/nodes/qwen-qwen3-6-35b-a3b/qwen-qwen3-6-35b-a3b.config.json +235 -0
- package/nodes/qwen-qwen3-6-35b-a3b/qwen-qwen3-6-35b-a3b.process.js +139 -0
- package/nodes/qwen-qwen3-6-35b-a3b/qwen-qwen3-6-35b-a3b.tests.json +80 -0
- package/nodes/qwen-qwen3-6-flash/qwen-qwen3-6-flash.config.json +193 -0
- package/nodes/qwen-qwen3-6-flash/qwen-qwen3-6-flash.process.js +138 -0
- package/nodes/qwen-qwen3-6-flash/qwen-qwen3-6-flash.tests.json +80 -0
- package/nodes/qwen-qwen3-6-max-preview/qwen-qwen3-6-max-preview.config.json +215 -0
- package/nodes/qwen-qwen3-6-max-preview/qwen-qwen3-6-max-preview.process.js +139 -0
- package/nodes/qwen-qwen3-6-max-preview/qwen-qwen3-6-max-preview.tests.json +80 -0
- package/nodes/qwen-qwen3-6-plus/qwen-qwen3-6-plus.config.json +193 -0
- package/nodes/qwen-qwen3-6-plus/qwen-qwen3-6-plus.process.js +138 -0
- package/nodes/qwen-qwen3-6-plus/qwen-qwen3-6-plus.tests.json +80 -0
- package/nodes/qwen-qwen3-7-max/qwen-qwen3-7-max.config.json +215 -0
- package/nodes/qwen-qwen3-7-max/qwen-qwen3-7-max.process.js +139 -0
- package/nodes/qwen-qwen3-7-max/qwen-qwen3-7-max.tests.json +80 -0
- package/nodes/qwen-qwen3-7-plus/qwen-qwen3-7-plus.config.json +215 -0
- package/nodes/qwen-qwen3-7-plus/qwen-qwen3-7-plus.process.js +139 -0
- package/nodes/qwen-qwen3-7-plus/qwen-qwen3-7-plus.tests.json +80 -0
- package/nodes/qwen-qwen3-8b/qwen-qwen3-8b.config.json +213 -0
- package/nodes/qwen-qwen3-8b/qwen-qwen3-8b.process.js +138 -0
- package/nodes/qwen-qwen3-8b/qwen-qwen3-8b.tests.json +80 -0
- package/nodes/qwen-qwen3-coder/qwen-qwen3-coder.config.json +191 -0
- package/nodes/qwen-qwen3-coder/qwen-qwen3-coder.process.js +136 -0
- package/nodes/qwen-qwen3-coder/qwen-qwen3-coder.tests.json +80 -0
- package/nodes/qwen-qwen3-coder-30b-a3b-instruct/qwen-qwen3-coder-30b-a3b-instruct.config.json +189 -0
- package/nodes/qwen-qwen3-coder-30b-a3b-instruct/qwen-qwen3-coder-30b-a3b-instruct.process.js +136 -0
- package/nodes/qwen-qwen3-coder-30b-a3b-instruct/qwen-qwen3-coder-30b-a3b-instruct.tests.json +80 -0
- package/nodes/qwen-qwen3-coder-flash/qwen-qwen3-coder-flash.config.json +170 -0
- package/nodes/qwen-qwen3-coder-flash/qwen-qwen3-coder-flash.process.js +136 -0
- package/nodes/qwen-qwen3-coder-flash/qwen-qwen3-coder-flash.tests.json +80 -0
- package/nodes/qwen-qwen3-coder-free/qwen-qwen3-coder-free.config.json +171 -0
- package/nodes/qwen-qwen3-coder-free/qwen-qwen3-coder-free.process.js +136 -0
- package/nodes/qwen-qwen3-coder-free/qwen-qwen3-coder-free.tests.json +80 -0
- package/nodes/qwen-qwen3-coder-next/qwen-qwen3-coder-next.config.json +191 -0
- package/nodes/qwen-qwen3-coder-next/qwen-qwen3-coder-next.process.js +136 -0
- package/nodes/qwen-qwen3-coder-next/qwen-qwen3-coder-next.tests.json +80 -0
- package/nodes/qwen-qwen3-coder-plus/qwen-qwen3-coder-plus.config.json +171 -0
- package/nodes/qwen-qwen3-coder-plus/qwen-qwen3-coder-plus.process.js +136 -0
- package/nodes/qwen-qwen3-coder-plus/qwen-qwen3-coder-plus.tests.json +80 -0
- package/nodes/qwen-qwen3-embedding-4b/qwen-qwen3-embedding-4b.config.json +90 -0
- package/nodes/qwen-qwen3-embedding-4b/qwen-qwen3-embedding-4b.process.js +36 -0
- package/nodes/qwen-qwen3-embedding-4b/qwen-qwen3-embedding-4b.tests.json +48 -0
- package/nodes/qwen-qwen3-embedding-8b/qwen-qwen3-embedding-8b.config.json +90 -0
- package/nodes/qwen-qwen3-embedding-8b/qwen-qwen3-embedding-8b.process.js +36 -0
- package/nodes/qwen-qwen3-embedding-8b/qwen-qwen3-embedding-8b.tests.json +48 -0
- package/nodes/qwen-qwen3-max/qwen-qwen3-max.config.json +170 -0
- package/nodes/qwen-qwen3-max/qwen-qwen3-max.process.js +136 -0
- package/nodes/qwen-qwen3-max/qwen-qwen3-max.tests.json +80 -0
- package/nodes/qwen-qwen3-max-thinking/qwen-qwen3-max-thinking.config.json +193 -0
- package/nodes/qwen-qwen3-max-thinking/qwen-qwen3-max-thinking.process.js +138 -0
- package/nodes/qwen-qwen3-max-thinking/qwen-qwen3-max-thinking.tests.json +80 -0
- package/nodes/qwen-qwen3-next-80b-a3b-instruct/qwen-qwen3-next-80b-a3b-instruct.config.json +191 -0
- package/nodes/qwen-qwen3-next-80b-a3b-instruct/qwen-qwen3-next-80b-a3b-instruct.process.js +136 -0
- package/nodes/qwen-qwen3-next-80b-a3b-instruct/qwen-qwen3-next-80b-a3b-instruct.tests.json +80 -0
- package/nodes/qwen-qwen3-next-80b-a3b-instruct-free/qwen-qwen3-next-80b-a3b-instruct-free.config.json +180 -0
- package/nodes/qwen-qwen3-next-80b-a3b-instruct-free/qwen-qwen3-next-80b-a3b-instruct-free.process.js +136 -0
- package/nodes/qwen-qwen3-next-80b-a3b-instruct-free/qwen-qwen3-next-80b-a3b-instruct-free.tests.json +80 -0
- package/nodes/qwen-qwen3-next-80b-a3b-thinking/qwen-qwen3-next-80b-a3b-thinking.config.json +213 -0
- package/nodes/qwen-qwen3-next-80b-a3b-thinking/qwen-qwen3-next-80b-a3b-thinking.process.js +138 -0
- package/nodes/qwen-qwen3-next-80b-a3b-thinking/qwen-qwen3-next-80b-a3b-thinking.tests.json +80 -0
- package/nodes/qwen-qwen3-vl-235b-a22b-instruct/qwen-qwen3-vl-235b-a22b-instruct.config.json +191 -0
- package/nodes/qwen-qwen3-vl-235b-a22b-instruct/qwen-qwen3-vl-235b-a22b-instruct.process.js +136 -0
- package/nodes/qwen-qwen3-vl-235b-a22b-instruct/qwen-qwen3-vl-235b-a22b-instruct.tests.json +80 -0
- package/nodes/qwen-qwen3-vl-235b-a22b-thinking/qwen-qwen3-vl-235b-a22b-thinking.config.json +210 -0
- package/nodes/qwen-qwen3-vl-235b-a22b-thinking/qwen-qwen3-vl-235b-a22b-thinking.process.js +138 -0
- package/nodes/qwen-qwen3-vl-235b-a22b-thinking/qwen-qwen3-vl-235b-a22b-thinking.tests.json +80 -0
- package/nodes/qwen-qwen3-vl-30b-a3b-instruct/qwen-qwen3-vl-30b-a3b-instruct.config.json +191 -0
- package/nodes/qwen-qwen3-vl-30b-a3b-instruct/qwen-qwen3-vl-30b-a3b-instruct.process.js +136 -0
- package/nodes/qwen-qwen3-vl-30b-a3b-instruct/qwen-qwen3-vl-30b-a3b-instruct.tests.json +80 -0
- package/nodes/qwen-qwen3-vl-30b-a3b-thinking/qwen-qwen3-vl-30b-a3b-thinking.config.json +211 -0
- package/nodes/qwen-qwen3-vl-30b-a3b-thinking/qwen-qwen3-vl-30b-a3b-thinking.process.js +138 -0
- package/nodes/qwen-qwen3-vl-30b-a3b-thinking/qwen-qwen3-vl-30b-a3b-thinking.tests.json +80 -0
- package/nodes/qwen-qwen3-vl-32b-instruct/qwen-qwen3-vl-32b-instruct.config.json +170 -0
- package/nodes/qwen-qwen3-vl-32b-instruct/qwen-qwen3-vl-32b-instruct.process.js +136 -0
- package/nodes/qwen-qwen3-vl-32b-instruct/qwen-qwen3-vl-32b-instruct.tests.json +80 -0
- package/nodes/qwen-qwen3-vl-8b-instruct/qwen-qwen3-vl-8b-instruct.config.json +191 -0
- package/nodes/qwen-qwen3-vl-8b-instruct/qwen-qwen3-vl-8b-instruct.process.js +136 -0
- package/nodes/qwen-qwen3-vl-8b-instruct/qwen-qwen3-vl-8b-instruct.tests.json +80 -0
- package/nodes/qwen-qwen3-vl-8b-thinking/qwen-qwen3-vl-8b-thinking.config.json +193 -0
- package/nodes/qwen-qwen3-vl-8b-thinking/qwen-qwen3-vl-8b-thinking.process.js +138 -0
- package/nodes/qwen-qwen3-vl-8b-thinking/qwen-qwen3-vl-8b-thinking.tests.json +80 -0
- package/nodes/random-error/random-error.config.json +50 -0
- package/nodes/random-error/random-error.process.js +25 -0
- package/nodes/random-error/random-error.tests.json +38 -0
- package/nodes/random-number/random-number.config.json +66 -0
- package/nodes/random-number/random-number.process.js +67 -0
- package/nodes/random-number/random-number.tests.json +59 -0
- package/nodes/range/range.config.json +35 -0
- package/nodes/range/range.process.js +22 -0
- package/nodes/range/range.tests.json +68 -0
- package/nodes/read-website/read-website.config.json +71 -0
- package/nodes/remote-mcp-tool/remote-mcp-tool.config.json +21 -0
- package/nodes/resend-send-email/resend-send-email.config.json +74 -0
- package/nodes/resend-send-email/resend-send-email.process.js +26 -0
- package/nodes/resend-send-email/resend-send-email.tests.json +14 -0
- package/nodes/response-format/response-format.config.json +62 -0
- package/nodes/response-format/response-format.process.js +17 -0
- package/nodes/response-format/response-format.tests.json +233 -0
- package/nodes/round/round.config.json +23 -0
- package/nodes/round/round.process.js +8 -0
- package/nodes/round/round.tests.json +38 -0
- package/nodes/route/route.config.json +38 -0
- package/nodes/route/route.process.js +16 -0
- package/nodes/route/route.tests.json +90 -0
- package/nodes/search-internet/search-internet.config.json +71 -0
- package/nodes/self-healing-error/self-healing-error.config.json +43 -0
- package/nodes/self-healing-error/self-healing-error.process.js +31 -0
- package/nodes/self-healing-error/self-healing-error.tests.json +34 -0
- package/nodes/semantic-search/semantic-search.config.json +75 -0
- package/nodes/semantic-search/semantic-search.process.js +70 -0
- package/nodes/sendgrid-send-email/sendgrid-send-email.config.json +81 -0
- package/nodes/sendgrid-send-email/sendgrid-send-email.process.js +43 -0
- package/nodes/sendgrid-send-email/sendgrid-send-email.tests.json +41 -0
- package/nodes/sentence-transformers-all-minilm-l12-v2/sentence-transformers-all-minilm-l12-v2.config.json +90 -0
- package/nodes/sentence-transformers-all-minilm-l12-v2/sentence-transformers-all-minilm-l12-v2.process.js +36 -0
- package/nodes/sentence-transformers-all-minilm-l12-v2/sentence-transformers-all-minilm-l12-v2.tests.json +48 -0
- package/nodes/sentence-transformers-all-minilm-l6-v2/sentence-transformers-all-minilm-l6-v2.config.json +90 -0
- package/nodes/sentence-transformers-all-minilm-l6-v2/sentence-transformers-all-minilm-l6-v2.process.js +36 -0
- package/nodes/sentence-transformers-all-minilm-l6-v2/sentence-transformers-all-minilm-l6-v2.tests.json +48 -0
- package/nodes/sentence-transformers-all-mpnet-base-v2/sentence-transformers-all-mpnet-base-v2.config.json +90 -0
- package/nodes/sentence-transformers-all-mpnet-base-v2/sentence-transformers-all-mpnet-base-v2.process.js +36 -0
- package/nodes/sentence-transformers-all-mpnet-base-v2/sentence-transformers-all-mpnet-base-v2.tests.json +48 -0
- package/nodes/sentence-transformers-multi-qa-mpnet-base-dot-v1/sentence-transformers-multi-qa-mpnet-base-dot-v1.config.json +90 -0
- package/nodes/sentence-transformers-multi-qa-mpnet-base-dot-v1/sentence-transformers-multi-qa-mpnet-base-dot-v1.process.js +36 -0
- package/nodes/sentence-transformers-multi-qa-mpnet-base-dot-v1/sentence-transformers-multi-qa-mpnet-base-dot-v1.tests.json +48 -0
- package/nodes/sentence-transformers-paraphrase-minilm-l6-v2/sentence-transformers-paraphrase-minilm-l6-v2.config.json +90 -0
- package/nodes/sentence-transformers-paraphrase-minilm-l6-v2/sentence-transformers-paraphrase-minilm-l6-v2.process.js +36 -0
- package/nodes/sentence-transformers-paraphrase-minilm-l6-v2/sentence-transformers-paraphrase-minilm-l6-v2.tests.json +48 -0
- package/nodes/set-object-property/set-object-property.config.json +70 -0
- package/nodes/set-object-property/set-object-property.process.js +55 -0
- package/nodes/set-object-property/set-object-property.tests.json +173 -0
- package/nodes/simple-agent/simple-agent.config.json +121 -0
- package/nodes/simple-agent/simple-agent.process.js +121 -0
- package/nodes/simple-agent/simple-agent.tests.json +28 -0
- package/nodes/slack-send-message/slack-send-message.config.json +51 -0
- package/nodes/slack-send-message/slack-send-message.process.js +19 -0
- package/nodes/slack-send-message/slack-send-message.tests.json +13 -0
- package/nodes/split-into-chunks/split-into-chunks.config.json +54 -0
- package/nodes/split-into-chunks/split-into-chunks.process.js +42 -0
- package/nodes/split-into-chunks/split-into-chunks.tests.json +74 -0
- package/nodes/standard-deviation/standard-deviation.config.json +29 -0
- package/nodes/standard-deviation/standard-deviation.process.js +35 -0
- package/nodes/standard-deviation/standard-deviation.tests.json +49 -0
- package/nodes/string/string.config.json +39 -0
- package/nodes/string/string.process.js +14 -0
- package/nodes/string/string.tests.json +56 -0
- package/nodes/string-case/string-case.config.json +33 -0
- package/nodes/string-case/string-case.process.js +46 -0
- package/nodes/string-case/string-case.tests.json +44 -0
- package/nodes/string-concat/string-concat.config.json +30 -0
- package/nodes/string-concat/string-concat.process.js +9 -0
- package/nodes/string-concat/string-concat.tests.json +12 -0
- package/nodes/string-contains/string-contains.config.json +45 -0
- package/nodes/string-contains/string-contains.process.js +19 -0
- package/nodes/string-contains/string-contains.tests.json +58 -0
- package/nodes/string-length/string-length.config.json +23 -0
- package/nodes/string-length/string-length.process.js +5 -0
- package/nodes/string-length/string-length.tests.json +22 -0
- package/nodes/string-match/string-match.config.json +58 -0
- package/nodes/string-match/string-match.process.js +32 -0
- package/nodes/string-match/string-match.tests.json +82 -0
- package/nodes/string-replace/string-replace.config.json +53 -0
- package/nodes/string-replace/string-replace.process.js +19 -0
- package/nodes/string-replace/string-replace.tests.json +62 -0
- package/nodes/string-split/string-split.config.json +39 -0
- package/nodes/string-split/string-split.process.js +12 -0
- package/nodes/string-split/string-split.tests.json +26 -0
- package/nodes/string-substring/string-substring.config.json +37 -0
- package/nodes/string-substring/string-substring.process.js +10 -0
- package/nodes/string-substring/string-substring.tests.json +44 -0
- package/nodes/string-template/string-template.config.json +39 -0
- package/nodes/string-template/string-template.process.js +15 -0
- package/nodes/string-template/string-template.tests.json +63 -0
- package/nodes/string-trim/string-trim.config.json +40 -0
- package/nodes/string-trim/string-trim.process.js +22 -0
- package/nodes/string-trim/string-trim.tests.json +38 -0
- package/nodes/strip-html/strip-html.config.json +40 -0
- package/nodes/strip-html/strip-html.process.js +61 -0
- package/nodes/strip-html/strip-html.tests.json +94 -0
- package/nodes/stripe-list-customers/stripe-list-customers.config.json +52 -0
- package/nodes/stripe-list-customers/stripe-list-customers.process.js +18 -0
- package/nodes/stripe-list-customers/stripe-list-customers.tests.json +24 -0
- package/nodes/subtract/subtract.config.json +31 -0
- package/nodes/subtract/subtract.process.js +15 -0
- package/nodes/subtract/subtract.tests.json +41 -0
- package/nodes/supabase-query/supabase-query.config.json +67 -0
- package/nodes/supabase-query/supabase-query.process.js +21 -0
- package/nodes/supabase-query/supabase-query.tests.json +14 -0
- package/nodes/switch/switch.config.json +59 -0
- package/nodes/switch/switch.process.js +40 -0
- package/nodes/switch/switch.tests.json +85 -0
- package/nodes/system-prompt/system-prompt.config.json +54 -0
- package/nodes/system-prompt/system-prompt.process.js +62 -0
- package/nodes/temperature/temperature.config.json +46 -0
- package/nodes/temperature/temperature.process.js +16 -0
- package/nodes/temperature/temperature.tests.json +47 -0
- package/nodes/text-scratch-pad/text-scratch-pad.config.json +63 -0
- package/nodes/text-scratch-pad/text-scratch-pad.process.js +40 -0
- package/nodes/text-scratch-pad/text-scratch-pad.tests.json +131 -0
- package/nodes/text-to-conversation/text-to-conversation.config.json +46 -0
- package/nodes/text-to-conversation/text-to-conversation.process.js +50 -0
- package/nodes/text-to-conversation/text-to-conversation.tests.json +132 -0
- package/nodes/thenlper-gte-base/thenlper-gte-base.config.json +90 -0
- package/nodes/thenlper-gte-base/thenlper-gte-base.process.js +36 -0
- package/nodes/thenlper-gte-base/thenlper-gte-base.tests.json +48 -0
- package/nodes/thenlper-gte-large/thenlper-gte-large.config.json +90 -0
- package/nodes/thenlper-gte-large/thenlper-gte-large.process.js +36 -0
- package/nodes/thenlper-gte-large/thenlper-gte-large.tests.json +48 -0
- package/nodes/throw-error/throw-error.config.json +36 -0
- package/nodes/throw-error/throw-error.process.js +24 -0
- package/nodes/throw-error/throw-error.tests.json +44 -0
- package/nodes/time-adder/time-adder.config.json +81 -0
- package/nodes/time-adder/time-adder.process.js +111 -0
- package/nodes/time-adder/time-adder.tests.json +158 -0
- package/nodes/timestamp/timestamp.config.json +45 -0
- package/nodes/timestamp/timestamp.process.js +71 -0
- package/nodes/timestamp/timestamp.tests.json +90 -0
- package/nodes/token-count/token-count.config.json +24 -0
- package/nodes/token-count/token-count.process.js +74 -0
- package/nodes/token-count/token-count.tests.json +163 -0
- package/nodes/tokenizer/tokenizer.config.json +38 -0
- package/nodes/tokenizer/tokenizer.process.js +74 -0
- package/nodes/tokenizer/tokenizer.tests.json +129 -0
- package/nodes/tool/tool.config.json +60 -0
- package/nodes/tool/tool.process.js +26 -0
- package/nodes/tool-cleanup/tool-cleanup.config.json +30 -0
- package/nodes/tool-cleanup/tool-cleanup.process.js +99 -0
- package/nodes/tool-cleanup/tool-cleanup.tests.json +160 -0
- package/nodes/truncate-by-tokens/truncate-by-tokens.config.json +57 -0
- package/nodes/truncate-by-tokens/truncate-by-tokens.process.js +72 -0
- package/nodes/truncate-by-tokens/truncate-by-tokens.tests.json +84 -0
- package/nodes/truncate-by-tokens-from-start/truncate-by-tokens-from-start.config.json +57 -0
- package/nodes/truncate-by-tokens-from-start/truncate-by-tokens-from-start.process.js +72 -0
- package/nodes/truncate-by-tokens-from-start/truncate-by-tokens-from-start.tests.json +84 -0
- package/nodes/truncate-by-tokens-preserve-system/truncate-by-tokens-preserve-system.config.json +71 -0
- package/nodes/truncate-by-tokens-preserve-system/truncate-by-tokens-preserve-system.process.js +130 -0
- package/nodes/truncate-by-tokens-preserve-system/truncate-by-tokens-preserve-system.tests.json +144 -0
- package/nodes/truncate-old-tool-responses/truncate-old-tool-responses.config.json +46 -0
- package/nodes/truncate-old-tool-responses/truncate-old-tool-responses.process.js +40 -0
- package/nodes/truncate-old-tool-responses/truncate-old-tool-responses.tests.json +207 -0
- package/nodes/truncate-text/truncate-text.config.json +68 -0
- package/nodes/truncate-text/truncate-text.process.js +77 -0
- package/nodes/truncate-text/truncate-text.tests.json +115 -0
- package/nodes/twilio-send-sms/twilio-send-sms.config.json +45 -0
- package/nodes/twilio-send-sms/twilio-send-sms.process.js +17 -0
- package/nodes/twilio-send-sms/twilio-send-sms.tests.json +14 -0
- package/nodes/untokenizer/untokenizer.config.json +38 -0
- package/nodes/untokenizer/untokenizer.process.js +46 -0
- package/nodes/untokenizer/untokenizer.tests.json +83 -0
- package/nodes/word-count/word-count.config.json +54 -0
- package/nodes/word-count/word-count.process.js +40 -0
- package/nodes/word-count/word-count.tests.json +100 -0
- package/nodes/x-ai-grok-4-20/x-ai-grok-4-20.config.json +207 -0
- package/nodes/x-ai-grok-4-20/x-ai-grok-4-20.process.js +139 -0
- package/nodes/x-ai-grok-4-20/x-ai-grok-4-20.tests.json +80 -0
- package/nodes/x-ai-grok-4-20-multi-agent/x-ai-grok-4-20-multi-agent.config.json +156 -0
- package/nodes/x-ai-grok-4-20-multi-agent/x-ai-grok-4-20-multi-agent.process.js +54 -0
- package/nodes/x-ai-grok-4-20-multi-agent/x-ai-grok-4-20-multi-agent.tests.json +44 -0
- package/nodes/x-ai-grok-4-3/x-ai-grok-4-3.config.json +231 -0
- package/nodes/x-ai-grok-4-3/x-ai-grok-4-3.process.js +139 -0
- package/nodes/x-ai-grok-4-3/x-ai-grok-4-3.tests.json +80 -0
- package/nodes/x-ai-grok-build-0-1/x-ai-grok-build-0-1.config.json +231 -0
- package/nodes/x-ai-grok-build-0-1/x-ai-grok-build-0-1.process.js +139 -0
- package/nodes/x-ai-grok-build-0-1/x-ai-grok-build-0-1.tests.json +80 -0
- package/nodes/xml-to-json/xml-to-json.config.json +44 -0
- package/nodes/xml-to-json/xml-to-json.process.js +163 -0
- package/nodes/xml-to-json/xml-to-json.tests.json +113 -0
- package/nodes/xnor-gate/xnor-gate.config.json +30 -0
- package/nodes/xnor-gate/xnor-gate.process.js +10 -0
- package/nodes/xnor-gate/xnor-gate.tests.json +22 -0
- package/nodes/xor-gate/xor-gate.config.json +30 -0
- package/nodes/xor-gate/xor-gate.process.js +10 -0
- package/nodes/xor-gate/xor-gate.tests.json +22 -0
- package/nodes/yaml-to-json/yaml-to-json.config.json +36 -0
- package/nodes/yaml-to-json/yaml-to-json.process.js +178 -0
- package/nodes/yaml-to-json/yaml-to-json.tests.json +112 -0
- package/package.json +37 -0
- package/src/classes/ErrorManager.js +234 -0
- package/src/index.js +2998 -0
- package/src/integrations/airtable.js +192 -0
- package/src/integrations/confluence.js +87 -0
- package/src/integrations/firecrawl.js +157 -0
- package/src/integrations/github.js +80 -0
- package/src/integrations/google_custom_search.js +136 -0
- package/src/integrations/hubspot.js +282 -0
- package/src/integrations/jira.js +115 -0
- package/src/integrations/knowledge-base-interface.js +119 -0
- package/src/integrations/linear.js +134 -0
- package/src/integrations/newsdata_io.js +201 -0
- package/src/integrations/notion.js +208 -0
- package/src/integrations/openai.js +185 -0
- package/src/integrations/openrouter.js +751 -0
- package/src/integrations/pinecone.js +80 -0
- package/src/integrations/resend.js +76 -0
- package/src/integrations/sendgrid.js +118 -0
- package/src/integrations/slack.js +70 -0
- package/src/integrations/sqlite.js +477 -0
- package/src/integrations/stripe.js +78 -0
- package/src/integrations/supabase.js +104 -0
- package/src/integrations/twilio.js +66 -0
- package/src/types/README.md +181 -0
- package/src/types/content.converters.js +21 -0
- package/src/types/content.json +31 -0
- package/src/types/conversation.converters.js +148 -0
- package/src/types/conversation.json +51 -0
- package/src/types/message.converters.js +45 -0
- package/src/types/message.json +47 -0
- package/src/types/plugin.converters.js +28 -0
- package/src/types/plugin.json +10 -0
- package/src/types/tool.converters.js +28 -0
- package/src/types/tool.json +18 -0
- package/src/utilities/cache.js +381 -0
- package/src/utilities/helpers.js +87 -0
- package/src/utilities/loaders.js +900 -0
- package/src/utilities/mcp.js +165 -0
- package/src/utilities/oauth.js +280 -0
- package/src/utilities/sanitizeAPICall.js +63 -0
- package/src/utilities/typeConverters.js +34 -0
- package/src/utilities/typers.js +484 -0
- package/src/utilities/validators.js +124 -0
- package/types/content.json +31 -0
- package/types/conversation.json +51 -0
- package/types/message.json +47 -0
- package/types/plugin.json +10 -0
- package/types/tool.json +18 -0
package/src/index.js
ADDED
|
@@ -0,0 +1,2998 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import Ajv from "ajv";
|
|
4
|
+
import axios from "axios";
|
|
5
|
+
import { v4 as uuidv4 } from "uuid";
|
|
6
|
+
import crypto from "crypto";
|
|
7
|
+
|
|
8
|
+
import ErrorManager from "./classes/ErrorManager.js";
|
|
9
|
+
import CacheManager from "./utilities/cache.js";
|
|
10
|
+
|
|
11
|
+
import { callMCPTool, fetchMCPTools } from "./utilities/mcp.js";
|
|
12
|
+
import { loadNodes, loadIntegrations, detectAndLoadFlow } from "./utilities/loaders.js";
|
|
13
|
+
import { validateKeys, validateFlow, validateInputs } from "./utilities/validators.js";
|
|
14
|
+
import { loadCustomTypes, typeCheck, convertType } from "./utilities/typers.js";
|
|
15
|
+
import { createSafeToolName, isRemoteMCPTool, isManualToolNode, mapTypeToJSONSchema, getDirname } from "./utilities/helpers.js";
|
|
16
|
+
import { sanitizeAPICallEvent } from "./utilities/sanitizeAPICall.js";
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Workbench - Core class for executing node-based flows
|
|
21
|
+
* Handles node loading, input/output validation, and flow execution
|
|
22
|
+
*/
|
|
23
|
+
export default class Workbench {
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Create a new Workbench instance
|
|
29
|
+
* @param {Object} flow - The flow definition containing nodes and links
|
|
30
|
+
* @param {Object} config - Configuration options and context for the engine
|
|
31
|
+
*/
|
|
32
|
+
constructor(flow, config = {}) {
|
|
33
|
+
this.flow = flow;
|
|
34
|
+
this.debug = config.debug || false;
|
|
35
|
+
this.keys = config.keys || {};
|
|
36
|
+
this.executionQueue = [];
|
|
37
|
+
this.runningNodes = new Set();
|
|
38
|
+
this.completedNodes = new Set();
|
|
39
|
+
this.maxConcurrency = config.maxConcurrency || 5;
|
|
40
|
+
this.lastError = null;
|
|
41
|
+
|
|
42
|
+
this.maxPluginCalls = config.maxPluginCalls || 10;
|
|
43
|
+
|
|
44
|
+
this.config = {
|
|
45
|
+
...config
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// The abort signal inherited from an ancestor engine (set once, at
|
|
49
|
+
// construction — sub-engines receive the parent's via {...config}). Captured
|
|
50
|
+
// here so a later run() can't mistake a *prior run's own* signal (which run()
|
|
51
|
+
// writes back into config.signal) for an ancestor and abort instantly.
|
|
52
|
+
this._inheritedSignal = this.config.signal || null;
|
|
53
|
+
|
|
54
|
+
// Track knowledge base files that need cleanup
|
|
55
|
+
this.knowledgeFilesToCleanup = new Set();
|
|
56
|
+
|
|
57
|
+
// Initialize timeout flag
|
|
58
|
+
this.hasTimedOut = false;
|
|
59
|
+
|
|
60
|
+
// Track executed node states to prevent duplicate processing
|
|
61
|
+
this.executedNodeStates = new Set();
|
|
62
|
+
|
|
63
|
+
// Track node hashes that are currently in the queue or running
|
|
64
|
+
// This prevents adding the same node multiple times to the queue
|
|
65
|
+
this.queuedNodeHashes = new Set();
|
|
66
|
+
|
|
67
|
+
// Resolvers for waitForNodeCompletion - event-based signaling
|
|
68
|
+
this.nodeCompletionResolvers = [];
|
|
69
|
+
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async initialize() {
|
|
73
|
+
|
|
74
|
+
this.loadNodes = loadNodes.bind(this);
|
|
75
|
+
this.loadIntegrations = loadIntegrations.bind(this);
|
|
76
|
+
this.loadCustomTypes = loadCustomTypes.bind(this);
|
|
77
|
+
|
|
78
|
+
this.typeCheck = typeCheck.bind(this);
|
|
79
|
+
this.convertType = convertType.bind(this);
|
|
80
|
+
// MCP tools are fetched via fetchMCPTools utility (no binding needed)
|
|
81
|
+
this.validateKeys = validateKeys.bind(this);
|
|
82
|
+
this.validateFlow = validateFlow.bind(this);
|
|
83
|
+
this.validateInputs = validateInputs.bind(this);
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
const nodes = await this.loadNodes(this.flow);
|
|
87
|
+
const compiledCustomTypes = await this.loadCustomTypes();
|
|
88
|
+
|
|
89
|
+
if(!this.config.integrations) {
|
|
90
|
+
this.config.integrations = await this.loadIntegrations(this.config, this.flow);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Bind API call emit helper for node process functions (e.g. http-request)
|
|
94
|
+
this.config._emitAPICall = async (rawEvent) => {
|
|
95
|
+
if (!this.config.onAPICall) return;
|
|
96
|
+
const event = sanitizeAPICallEvent(rawEvent);
|
|
97
|
+
try {
|
|
98
|
+
const result = this.config.onAPICall(event);
|
|
99
|
+
if (result && typeof result.then === 'function') await result;
|
|
100
|
+
} catch (e) {
|
|
101
|
+
// Don't let callback errors break node execution
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
this.nodes = nodes;
|
|
106
|
+
this.compiledCustomTypes = compiledCustomTypes;
|
|
107
|
+
this.nodesDir = path.join(getDirname(import.meta.url), "../nodes");
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
this.cache = new CacheManager();
|
|
111
|
+
this.flowTimeout = null;
|
|
112
|
+
this.timeline = [];
|
|
113
|
+
|
|
114
|
+
// Initialize ErrorManager for centralized error handling
|
|
115
|
+
this.errorManager = new ErrorManager({
|
|
116
|
+
onError: this.config.onError || null,
|
|
117
|
+
executionId: this.config.executionId || uuidv4(),
|
|
118
|
+
executionContext: {
|
|
119
|
+
timeline: this.timeline,
|
|
120
|
+
nodeCount: this.flow.nodes.length
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// this.logDebug("zv1 initialized with config:", JSON.stringify(config, null, 2));
|
|
125
|
+
this.logDebug(`Loaded ${Object.keys(nodes).length} node types`);
|
|
126
|
+
this.logDebug(`Loaded ${Object.keys(compiledCustomTypes).length} custom types`);
|
|
127
|
+
|
|
128
|
+
// Clean up the flow
|
|
129
|
+
this.sanitizeFlow();
|
|
130
|
+
|
|
131
|
+
// Validate keys for nodes
|
|
132
|
+
this.validateKeys();
|
|
133
|
+
|
|
134
|
+
// Ensure the flow can even be run (use expanded flow)
|
|
135
|
+
this.validateFlow(this.flow);
|
|
136
|
+
this.initializePluginMappings();
|
|
137
|
+
|
|
138
|
+
// Track knowledge base files for cleanup
|
|
139
|
+
this._trackKnowledgeFiles();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Create a new zv1 instance
|
|
144
|
+
* Supports both legacy JSON files and new .zv1 files with hierarchical imports
|
|
145
|
+
*
|
|
146
|
+
* @param {string|Object} flow - File path (string) or flow definition object
|
|
147
|
+
* @param {Object} config - Configuration options and context for the engine
|
|
148
|
+
* @returns {Promise<zv1>} Fully initialized zv1 instance
|
|
149
|
+
* @throws {Error} If flow cannot be loaded or is invalid
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* // Load from .zv1 file
|
|
153
|
+
* const engine = await Workbench.create('./myflow.zv1', { keys: { openrouter: 'sk-...' } });
|
|
154
|
+
*
|
|
155
|
+
* // Load from legacy JSON file
|
|
156
|
+
* const engine = await Workbench.create('./legacy.json', { keys: { openrouter: 'sk-...' } });
|
|
157
|
+
*
|
|
158
|
+
* // Load from flow object
|
|
159
|
+
* const engine = await Workbench.create(flowObject, { keys: { openrouter: 'sk-...' } });
|
|
160
|
+
*/
|
|
161
|
+
static async create(flow, config = {}) {
|
|
162
|
+
try {
|
|
163
|
+
// Use the new unified loader to detect and load the flow
|
|
164
|
+
// This handles both legacy JSON files and new .zv1 files
|
|
165
|
+
const loadedFlow = await detectAndLoadFlow(flow);
|
|
166
|
+
|
|
167
|
+
// Create engine instance with the loaded flow
|
|
168
|
+
const engine = new Workbench(loadedFlow, config);
|
|
169
|
+
|
|
170
|
+
// Initialize the engine (loads nodes, validates flow, etc.)
|
|
171
|
+
await engine.initialize();
|
|
172
|
+
|
|
173
|
+
return engine;
|
|
174
|
+
} catch (error) {
|
|
175
|
+
// Provide more context for common errors
|
|
176
|
+
if (error.message.includes('not found')) {
|
|
177
|
+
throw new Error(`Flow file not found: ${error.message}`);
|
|
178
|
+
} else if (error.message.includes('Invalid JSON')) {
|
|
179
|
+
throw new Error(`Invalid JSON in flow file: ${error.message}`);
|
|
180
|
+
} else if (error.message.includes('Missing required')) {
|
|
181
|
+
throw new Error(`Invalid flow structure: ${error.message}`);
|
|
182
|
+
} else {
|
|
183
|
+
throw new Error(`Failed to create zv1 instance: ${error.message}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Helper function to log debug information
|
|
190
|
+
* @param {...any} args - Arguments to log
|
|
191
|
+
*/
|
|
192
|
+
logDebug(...args) {
|
|
193
|
+
if (this.debug) {
|
|
194
|
+
console.log("[DEBUG]", ...args);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Helper to check if node has any refiring inputs
|
|
200
|
+
* @private
|
|
201
|
+
* @param {string} nodeId - The node ID to check
|
|
202
|
+
* @returns {boolean} True if the node has any refiring inputs
|
|
203
|
+
*/
|
|
204
|
+
_hasRefiringInput(nodeId) {
|
|
205
|
+
const node = this.flow.nodes.find(n => n.id === nodeId);
|
|
206
|
+
if (!node) return false;
|
|
207
|
+
|
|
208
|
+
const nodeDefinition = this.nodes[node.type];
|
|
209
|
+
if (!nodeDefinition) return false;
|
|
210
|
+
|
|
211
|
+
return nodeDefinition.config.inputs.some(input =>
|
|
212
|
+
input.allow_multiple && input.refires
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Create a hash of node execution state (id + inputs + settings)
|
|
218
|
+
* Used to prevent duplicate processing of nodes with identical contexts
|
|
219
|
+
* @private
|
|
220
|
+
* @param {string} nodeId - The node ID
|
|
221
|
+
* @param {Object} inputs - The inputs object
|
|
222
|
+
* @param {Object} settings - The settings object
|
|
223
|
+
* @returns {string} SHA-256 hash of the execution state
|
|
224
|
+
*/
|
|
225
|
+
_createExecutionHash(nodeId, inputs, settings) {
|
|
226
|
+
const state = {
|
|
227
|
+
id: nodeId,
|
|
228
|
+
inputs: inputs || {},
|
|
229
|
+
settings: settings || {}
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
// For refiring nodes, add a timestamp based on the input values
|
|
233
|
+
if (this._hasRefiringInput(nodeId)) {
|
|
234
|
+
// Find the latest timestamp from any refiring input
|
|
235
|
+
let latestInputTimestamp = 0;
|
|
236
|
+
const node = this.flow.nodes.find(n => n.id === nodeId);
|
|
237
|
+
const nodeDefinition = this.nodes[node.type];
|
|
238
|
+
|
|
239
|
+
if (nodeDefinition?.config?.inputs) {
|
|
240
|
+
nodeDefinition.config.inputs.forEach(inputDef => {
|
|
241
|
+
if (inputDef.allow_multiple && inputDef.refires) {
|
|
242
|
+
const inputLinks = this.flow.links.filter(
|
|
243
|
+
link => link.to.node_id === nodeId && link.to.port_name === inputDef.name
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
inputLinks.forEach(link => {
|
|
247
|
+
const timestamp = this.cache.getLatestTimestamp({
|
|
248
|
+
node_id: link.from.node_id,
|
|
249
|
+
port_name: link.from.port_name
|
|
250
|
+
});
|
|
251
|
+
if (timestamp && timestamp > latestInputTimestamp) {
|
|
252
|
+
latestInputTimestamp = timestamp;
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
state.timestamp = latestInputTimestamp;
|
|
260
|
+
} else {
|
|
261
|
+
// For non-refiring nodes, include input values AND current timestamp
|
|
262
|
+
// This ensures nodes re-execute when inputs change OR when called again
|
|
263
|
+
state.inputHash = JSON.stringify(inputs || {});
|
|
264
|
+
// state.timestamp = Number(process.hrtime.bigint()); // Current execution timestamp
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Create deterministic JSON string (sort keys for consistency)
|
|
268
|
+
const stateString = JSON.stringify(state, Object.keys(state).sort());
|
|
269
|
+
|
|
270
|
+
// Use SHA-256 hash
|
|
271
|
+
return crypto.createHash('sha256')
|
|
272
|
+
.update(stateString)
|
|
273
|
+
.digest('hex')
|
|
274
|
+
.substring(0, 16); // Use first 16 chars for shorter hash
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Update execution context for ErrorManager
|
|
279
|
+
* @param {Object} context - Additional execution context
|
|
280
|
+
*/
|
|
281
|
+
updateErrorContext(context) {
|
|
282
|
+
if (this.errorManager) {
|
|
283
|
+
this.errorManager.updateExecutionContext(context);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Core execution logic shared between processNode and processNodeWithArgs
|
|
289
|
+
* @private
|
|
290
|
+
*/
|
|
291
|
+
/**
|
|
292
|
+
* Race a node's execution promise against the flow's abort signal so a hung
|
|
293
|
+
* node (e.g. a stalled LLM/HTTP call) can't park the execution loop past the
|
|
294
|
+
* flow timeout. The signal is also handed to integrations so the underlying
|
|
295
|
+
* request is actually cancelled. No-op when there's no abort controller, so
|
|
296
|
+
* callers outside run() behave exactly as before.
|
|
297
|
+
*/
|
|
298
|
+
_raceAbort(promise) {
|
|
299
|
+
const signal = this.abortController?.signal;
|
|
300
|
+
if (!signal) return promise;
|
|
301
|
+
const reason = () => (signal.reason instanceof Error ? signal.reason : new Error('Flow aborted'));
|
|
302
|
+
if (signal.aborted) {
|
|
303
|
+
// Still attach a handler so the original promise's eventual rejection
|
|
304
|
+
// isn't unhandled.
|
|
305
|
+
Promise.resolve(promise).catch(() => {});
|
|
306
|
+
return Promise.reject(reason());
|
|
307
|
+
}
|
|
308
|
+
return new Promise((resolve, reject) => {
|
|
309
|
+
const onAbort = () => { cleanup(); reject(reason()); };
|
|
310
|
+
const cleanup = () => signal.removeEventListener('abort', onAbort);
|
|
311
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
312
|
+
promise.then(
|
|
313
|
+
(v) => { cleanup(); resolve(v); },
|
|
314
|
+
(e) => { cleanup(); reject(e); }
|
|
315
|
+
);
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
async _executeNodeCore(node, inputs, settings, nodeDefinition) {
|
|
320
|
+
// Warn once per deprecated node type. These still execute (so existing flows
|
|
321
|
+
// keep working), but the underlying model may be gone or expiring — see
|
|
322
|
+
// config.deprecation. Tombstoned nodes are kept rather than deleted precisely
|
|
323
|
+
// so flows referencing them still load.
|
|
324
|
+
if (nodeDefinition?.config?.deprecated) {
|
|
325
|
+
this._deprecationWarned = this._deprecationWarned || new Set();
|
|
326
|
+
if (!this._deprecationWarned.has(node.type)) {
|
|
327
|
+
this._deprecationWarned.add(node.type);
|
|
328
|
+
const dep = nodeDefinition.config.deprecation || {};
|
|
329
|
+
const detail = [
|
|
330
|
+
dep.reason,
|
|
331
|
+
dep.expiration_date ? `expires ${dep.expiration_date}` : null,
|
|
332
|
+
dep.replacement ? `use ${dep.replacement} instead` : null
|
|
333
|
+
].filter(Boolean).join('; ');
|
|
334
|
+
console.warn(`[zv1] Node type "${node.type}" is deprecated${detail ? ': ' + detail : ''}.`);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const timelineEntry = {
|
|
339
|
+
nodeId: node.id,
|
|
340
|
+
nodeType: node.type,
|
|
341
|
+
inputs: JSON.parse(JSON.stringify(inputs)),
|
|
342
|
+
settings: JSON.parse(JSON.stringify(settings || {})),
|
|
343
|
+
startTime: new Date().toISOString()
|
|
344
|
+
};
|
|
345
|
+
const startDate = new Date();
|
|
346
|
+
|
|
347
|
+
try {
|
|
348
|
+
if(this.config.onNodeStart) {
|
|
349
|
+
await this.config.onNodeStart({
|
|
350
|
+
nodeId: node.id,
|
|
351
|
+
nodeType: node.type,
|
|
352
|
+
timestamp: Date.now(),
|
|
353
|
+
inputs: inputs,
|
|
354
|
+
settings: settings || {}
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Create a copy of nodeConfig with instance-specific type and id
|
|
359
|
+
// This avoids mutating the shared nodeDefinition.config which would cause
|
|
360
|
+
// issues when multiple nodes of the same type run concurrently
|
|
361
|
+
const nodeConfig = {
|
|
362
|
+
...nodeDefinition.config,
|
|
363
|
+
type: node.type,
|
|
364
|
+
id: node.id
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
const outputs = await this._raceAbort(
|
|
368
|
+
nodeDefinition.process({inputs, settings, config: this.config, nodeConfig})
|
|
369
|
+
);
|
|
370
|
+
const endDate = new Date();
|
|
371
|
+
timelineEntry.outputs = JSON.parse(JSON.stringify(outputs));
|
|
372
|
+
timelineEntry.endTime = endDate.toISOString();
|
|
373
|
+
timelineEntry.durationMs = endDate - startDate;
|
|
374
|
+
timelineEntry.status = 'success';
|
|
375
|
+
this.timeline.push(timelineEntry);
|
|
376
|
+
|
|
377
|
+
if(this.config.onNodeComplete) {
|
|
378
|
+
await this.config.onNodeComplete({
|
|
379
|
+
nodeId: node.id,
|
|
380
|
+
nodeType: node.type,
|
|
381
|
+
timestamp: Date.now(),
|
|
382
|
+
inputs: inputs,
|
|
383
|
+
outputs: outputs,
|
|
384
|
+
settings: settings || {}
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return outputs;
|
|
389
|
+
} catch (error) {
|
|
390
|
+
const endDate = new Date();
|
|
391
|
+
timelineEntry.endTime = endDate.toISOString();
|
|
392
|
+
timelineEntry.durationMs = endDate - startDate;
|
|
393
|
+
timelineEntry.status = 'error';
|
|
394
|
+
timelineEntry.errorMessage = error.message;
|
|
395
|
+
this.timeline.push(timelineEntry);
|
|
396
|
+
|
|
397
|
+
// Update execution context for ErrorManager
|
|
398
|
+
this.errorManager.updateExecutionContext({
|
|
399
|
+
timeline: this.timeline,
|
|
400
|
+
nodeCount: this.flow.nodes.length,
|
|
401
|
+
nodesExecuted: this.timeline.length,
|
|
402
|
+
cost_summary: this._getCostSummaryFromTimeline()
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
// Use ErrorManager to handle the error
|
|
406
|
+
this.errorManager.throwNodeError(
|
|
407
|
+
node.id,
|
|
408
|
+
node.type,
|
|
409
|
+
`Node execution failed: ${error.message}`,
|
|
410
|
+
error
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Process a single node
|
|
417
|
+
* @param {Object} node - The node to process
|
|
418
|
+
* @returns {Object} The outputs from the node
|
|
419
|
+
*/
|
|
420
|
+
async processNode(node) {
|
|
421
|
+
|
|
422
|
+
this.logDebug(`Processing node [${node.id}] of type [${node.type}]`);
|
|
423
|
+
|
|
424
|
+
const nodeDefinition = this.nodes[node.type];
|
|
425
|
+
if (!nodeDefinition) {
|
|
426
|
+
this.logDebug(`Error: Node type "${node.type}" not found`);
|
|
427
|
+
throw new Error(`Node type "${node.type}" not found. It may have been removed or renamed (e.g. a model retired from the provider). Check for an updated node type or regenerate the node catalog.`);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Apply default settings from node configuration
|
|
431
|
+
if (!node.settings) {
|
|
432
|
+
node.settings = {};
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Inherit default values for settings
|
|
436
|
+
if (nodeDefinition.config.settings) {
|
|
437
|
+
nodeDefinition.config.settings.forEach(settingDef => {
|
|
438
|
+
if (settingDef.default !== undefined && (node.settings[settingDef.name] === undefined || node.settings[settingDef.name] === null || node.settings[settingDef.name] === '')) {
|
|
439
|
+
this.logDebug(`Applying default value for setting [${settingDef.name}]: ${JSON.stringify(settingDef.default)}`);
|
|
440
|
+
node.settings[settingDef.name] = settingDef.default;
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Collect inputs from the cache
|
|
446
|
+
const inputs = {};
|
|
447
|
+
this.logDebug(`Collecting inputs for node [${node.id}]`);
|
|
448
|
+
|
|
449
|
+
// Get all links that connect to this node
|
|
450
|
+
const nodeInputLinks = this.flow.links.filter((link) => link.to.node_id === node.id);
|
|
451
|
+
|
|
452
|
+
// First, identify which input ports have connections
|
|
453
|
+
const connectedInputs = new Set();
|
|
454
|
+
nodeInputLinks.forEach(link => {
|
|
455
|
+
connectedInputs.add(link.to.port_name);
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
// Process all connected inputs
|
|
459
|
+
nodeInputLinks.forEach((link) => {
|
|
460
|
+
this.logDebug(`Processing link from [${link.from.node_id}:${link.from.port_name}] to [${node.id}:${link.to.port_name}]`);
|
|
461
|
+
|
|
462
|
+
const key = `${link.from.node_id}:${link.from.port_name}`;
|
|
463
|
+
const inputName = link.to.port_name;
|
|
464
|
+
|
|
465
|
+
const inputDef = nodeDefinition.config.inputs.find((i) => i.name === inputName);
|
|
466
|
+
if (!inputDef) {
|
|
467
|
+
this.logDebug(`Warning: No input definition found for port ${inputName}`);
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
this.logDebug(`Input definition for ${inputName}:`, inputDef);
|
|
472
|
+
|
|
473
|
+
if (inputDef.allow_multiple && inputDef.refires) {
|
|
474
|
+
// Refiring input: Only collect NEW values (not yet consumed)
|
|
475
|
+
const lastConsumed = CacheManager.getLastConsumed(node.settings, inputName);
|
|
476
|
+
|
|
477
|
+
// If never consumed before, get the latest value (for initial execution)
|
|
478
|
+
if (lastConsumed === 0) {
|
|
479
|
+
const latestValue = this.cache.get({ node_id: link.from.node_id, port_name: link.from.port_name });
|
|
480
|
+
if (latestValue !== undefined) {
|
|
481
|
+
inputs[inputName] = latestValue;
|
|
482
|
+
this.logDebug(`Refiring input [${inputName}]: using initial value (never consumed):`, latestValue);
|
|
483
|
+
}
|
|
484
|
+
} else {
|
|
485
|
+
// Get only new values since last consumption
|
|
486
|
+
const newValues = this.cache.getNew({
|
|
487
|
+
node_id: link.from.node_id,
|
|
488
|
+
port_name: link.from.port_name,
|
|
489
|
+
afterTimestamp: lastConsumed
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
this.logDebug(`Refiring input [${inputName}]: found ${newValues.length} new values since ${lastConsumed}`);
|
|
493
|
+
|
|
494
|
+
if (newValues.length > 0) {
|
|
495
|
+
// For refiring, use the first new value that triggered this execution
|
|
496
|
+
inputs[inputName] = newValues[0];
|
|
497
|
+
this.logDebug(`Setting refiring input [${inputName}] to new value:`, newValues[0]);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
} else if (inputDef.allow_multiple && !inputDef.refires) {
|
|
501
|
+
// Non-refiring multiple input: Collect all current values
|
|
502
|
+
const cachedValue = this.cache.get({ node_id: link.from.node_id, port_name: link.from.port_name });
|
|
503
|
+
|
|
504
|
+
if (this.cache.has({ node_id: link.from.node_id, port_name: link.from.port_name })) {
|
|
505
|
+
if(!inputs[inputName]) {
|
|
506
|
+
inputs[inputName] = [];
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// Get the value and validate its type
|
|
510
|
+
const value = cachedValue;
|
|
511
|
+
const itemType = inputDef.type || "any";
|
|
512
|
+
|
|
513
|
+
// If the value is an array and doesn't pass type check as-is,
|
|
514
|
+
// check if it's an array of the expected type and spread it in
|
|
515
|
+
if (Array.isArray(value) && !this.typeCheck(value, itemType)) {
|
|
516
|
+
let allValid = value.length > 0;
|
|
517
|
+
for (const item of value) {
|
|
518
|
+
if (!this.typeCheck(item, itemType)) {
|
|
519
|
+
allValid = false;
|
|
520
|
+
break;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
if (allValid) {
|
|
524
|
+
inputs[inputName].push(...value);
|
|
525
|
+
this.logDebug(`Spread array of ${value.length} items into multiple-input [${inputName}]`);
|
|
526
|
+
} else {
|
|
527
|
+
this.logDebug(`Type mismatch for item in multiple-input [${inputName}]: Expected ${itemType}`);
|
|
528
|
+
}
|
|
529
|
+
} else if (this.typeCheck(value, itemType)) {
|
|
530
|
+
inputs[inputName].push(value);
|
|
531
|
+
} else {
|
|
532
|
+
this.logDebug(`Type mismatch for item in multiple-input [${inputName}]: Expected ${itemType}`);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
} else {
|
|
536
|
+
// Single value input: Always use latest value
|
|
537
|
+
const cachedValue = this.cache.get({ node_id: link.from.node_id, port_name: link.from.port_name });
|
|
538
|
+
|
|
539
|
+
if (this.cache.has({ node_id: link.from.node_id, port_name: link.from.port_name })) {
|
|
540
|
+
this.logDebug(`Setting value for input [${inputName}]`);
|
|
541
|
+
inputs[inputName] = cachedValue;
|
|
542
|
+
} else if (!inputDef.required && inputDef.default !== undefined) {
|
|
543
|
+
// If optional and no link value, use the default
|
|
544
|
+
inputs[inputName] = inputDef.default;
|
|
545
|
+
this.logDebug(`Input [${inputName}] for node [${node.id}] using default value:`, inputDef.default);
|
|
546
|
+
} else {
|
|
547
|
+
this.logDebug(`Warning: No value found for input [${inputName}] and no default available`);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
// Now handle unconnected inputs with default values
|
|
553
|
+
nodeDefinition.config.inputs.forEach((inputDef) => {
|
|
554
|
+
const inputName = inputDef.name;
|
|
555
|
+
|
|
556
|
+
// Skip inputs that already have values from connections
|
|
557
|
+
if (connectedInputs.has(inputName) || inputs[inputName] !== undefined) {
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// Apply default value for unconnected inputs if available
|
|
562
|
+
if (inputDef.default !== undefined) {
|
|
563
|
+
inputs[inputName] = inputDef.default;
|
|
564
|
+
this.logDebug(`Applied default value for unconnected input [${inputName}]:`, inputDef.default);
|
|
565
|
+
} else if (inputDef.required) {
|
|
566
|
+
this.logDebug(`Warning: Required input [${inputName}] has no connection and no default value`);
|
|
567
|
+
}
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
// Debug: Log inputs before processing
|
|
573
|
+
this.logDebug(`Node [${node.id}] of type [${node.type}] inputs:`, JSON.stringify(inputs, null, 2));
|
|
574
|
+
|
|
575
|
+
// Validate inputs against node configuration
|
|
576
|
+
this.validateInputs(nodeDefinition.config, inputs);
|
|
577
|
+
|
|
578
|
+
// If this node is a macro, execute its internal flow (check this FIRST before accepts_plugins)
|
|
579
|
+
if (nodeDefinition.config.is_macro) {
|
|
580
|
+
this.logDebug(`Node [${node.id}] is a macro. Executing internal flow.`);
|
|
581
|
+
return await this.processMacroNode(node);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// If this node is an LLM that accepts plugins, use the special handler
|
|
585
|
+
if (nodeDefinition.config.accepts_plugins) {
|
|
586
|
+
this.logDebug(`Node [${node.id}] is an LLM with plugin support. Using processLLMNode.`);
|
|
587
|
+
return await this.processLLMNode(node);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// Execute the process function using the shared core logic
|
|
591
|
+
this.logDebug(`Executing process function for node [${node.id}]`);
|
|
592
|
+
|
|
593
|
+
const outputs = await this._executeNodeCore(node, inputs, node.settings || {}, nodeDefinition);
|
|
594
|
+
|
|
595
|
+
// Track this execution state to prevent future duplicates
|
|
596
|
+
const executionHash = this._createExecutionHash(node.id, inputs, node.settings || {});
|
|
597
|
+
this.executedNodeStates.add(executionHash);
|
|
598
|
+
|
|
599
|
+
// Debug: Log outputs after processing
|
|
600
|
+
this.logDebug(`Node [${node.id}] outputs:`, JSON.stringify(outputs, null, 2));
|
|
601
|
+
|
|
602
|
+
// Update consumption tracking for refiring inputs
|
|
603
|
+
const consumptionUpdates = {};
|
|
604
|
+
const refiringInputs = nodeDefinition.config.inputs.filter(i => i.allow_multiple && i.refires);
|
|
605
|
+
|
|
606
|
+
if (refiringInputs.length > 0) {
|
|
607
|
+
this.logDebug(`Updating consumption tracking for ${refiringInputs.length} refiring inputs`);
|
|
608
|
+
|
|
609
|
+
refiringInputs.forEach(inputDef => {
|
|
610
|
+
const inputName = inputDef.name;
|
|
611
|
+
|
|
612
|
+
// Find all connected links for this refiring input
|
|
613
|
+
const inputLinks = this.flow.links.filter(
|
|
614
|
+
link => link.to.node_id === node.id && link.to.port_name === inputName
|
|
615
|
+
);
|
|
616
|
+
|
|
617
|
+
// Get the latest timestamp from all connected sources
|
|
618
|
+
let maxTimestamp = 0;
|
|
619
|
+
inputLinks.forEach(link => {
|
|
620
|
+
const timestamp = this.cache.getLatestTimestamp({
|
|
621
|
+
node_id: link.from.node_id,
|
|
622
|
+
port_name: link.from.port_name
|
|
623
|
+
});
|
|
624
|
+
if (timestamp && timestamp > maxTimestamp) {
|
|
625
|
+
maxTimestamp = timestamp;
|
|
626
|
+
}
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
if (maxTimestamp > 0) {
|
|
630
|
+
consumptionUpdates[inputName] = maxTimestamp;
|
|
631
|
+
this.logDebug(`Marking input [${inputName}] as consumed up to timestamp ${maxTimestamp}`);
|
|
632
|
+
}
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// Handle updated settings
|
|
637
|
+
if (outputs.__updated_settings) {
|
|
638
|
+
node.settings = {
|
|
639
|
+
...node.settings,
|
|
640
|
+
...outputs.__updated_settings,
|
|
641
|
+
};
|
|
642
|
+
this.logDebug(`Node [${node.id}] updated settings:`, JSON.stringify(node.settings, null, 2));
|
|
643
|
+
delete outputs.__updated_settings; // Remove from regular outputs
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// Apply consumption tracking updates if any
|
|
647
|
+
if (Object.keys(consumptionUpdates).length > 0) {
|
|
648
|
+
node.settings = {
|
|
649
|
+
...node.settings,
|
|
650
|
+
_consumption_tracking: {
|
|
651
|
+
...node.settings._consumption_tracking,
|
|
652
|
+
...consumptionUpdates
|
|
653
|
+
}
|
|
654
|
+
};
|
|
655
|
+
this.logDebug(`Node [${node.id}] updated consumption tracking:`, node.settings._consumption_tracking);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// Store outputs in the cache
|
|
659
|
+
for (const [key, value] of Object.entries(outputs)) {
|
|
660
|
+
this.logDebug(`Storing output in cache: ${node.id}:${key}`, value);
|
|
661
|
+
this.cache.set({ node_id: node.id, port_name: key, value });
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
this.logDebug(`Node [${node.id}] processing completed successfully`);
|
|
665
|
+
return outputs;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/**
|
|
669
|
+
* Propagate values through the graph
|
|
670
|
+
* @param {string} nodeId - The ID of the node to start propagation from
|
|
671
|
+
*/
|
|
672
|
+
async propagate(currentNodeId) {
|
|
673
|
+
this.logDebug(`Starting propagation from node [${currentNodeId}]`);
|
|
674
|
+
const visitedNodes = new Map(); // Map to track visit count per node
|
|
675
|
+
|
|
676
|
+
// Check for timeout
|
|
677
|
+
if (this.hasTimedOut) {
|
|
678
|
+
this.errorManager.throwTimeoutError("Flow execution timed out.");
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// Get current node definition
|
|
682
|
+
const currentNode = this.flow.nodes.find((node) => node.id === currentNodeId);
|
|
683
|
+
if (!currentNode) {
|
|
684
|
+
this.logDebug(`Error: Node with ID "${currentNodeId}" not found`);
|
|
685
|
+
throw new Error(`Node with ID "${currentNodeId}" not found.`);
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
const nodeDefinition = this.nodes[currentNode.type];
|
|
689
|
+
|
|
690
|
+
// Get current visit count (for debugging only)
|
|
691
|
+
const visitCount = visitedNodes.get(currentNodeId) || 0;
|
|
692
|
+
|
|
693
|
+
// Increment visit count
|
|
694
|
+
visitedNodes.set(currentNodeId, visitCount + 1);
|
|
695
|
+
|
|
696
|
+
this.logDebug(`Processing node [${currentNodeId}] (visit #${visitCount + 1})`);
|
|
697
|
+
|
|
698
|
+
this.logDebug(`Processing downstream propagation for node [${currentNodeId}]`);
|
|
699
|
+
|
|
700
|
+
// Find all downstream nodes connected to this node
|
|
701
|
+
const downstreamLinks = this.flow.links.filter((link) => {
|
|
702
|
+
|
|
703
|
+
if(link.from.node_id === currentNodeId) {
|
|
704
|
+
|
|
705
|
+
// did this output from nodeId actually send a value that wasn't undefined or null?
|
|
706
|
+
const value = this.cache.get({ node_id: currentNodeId, port_name: link.from.port_name });
|
|
707
|
+
if(value === undefined || value === null) {
|
|
708
|
+
return false;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
return true;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
return false;
|
|
715
|
+
});
|
|
716
|
+
const downstreamNodes = downstreamLinks.map((link) => {
|
|
717
|
+
const node = this.flow.nodes.find((node) => node.id === link.to.node_id);
|
|
718
|
+
|
|
719
|
+
|
|
720
|
+
return node;
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
this.logDebug(`Downstream nodes: ${downstreamNodes.map(n => n?.id).join(', ')}`);
|
|
724
|
+
|
|
725
|
+
for (const downstreamNode of downstreamNodes) {
|
|
726
|
+
if (!downstreamNode) continue;
|
|
727
|
+
|
|
728
|
+
// Skip plugin nodes that are actually connected as plugins - they should only run when called by LLM
|
|
729
|
+
const isPluginNode = this.nodes[downstreamNode.type]?.config?.is_plugin;
|
|
730
|
+
const isConnectedAsPlugin = this.flow.links.some(link =>
|
|
731
|
+
link.from.node_id === downstreamNode.id && link.type === "plugin"
|
|
732
|
+
);
|
|
733
|
+
|
|
734
|
+
if (isPluginNode && isConnectedAsPlugin) {
|
|
735
|
+
this.logDebug(`Skipping plugin node [${downstreamNode.id}] during propagation - will only run when called by LLM`);
|
|
736
|
+
continue;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
// Check if the downstream node is ready
|
|
740
|
+
const nodeDefinition = this.nodes[downstreamNode.type];
|
|
741
|
+
const nodeLinks = this.flow.links.filter((link) => link.to.node_id === downstreamNode.id);
|
|
742
|
+
|
|
743
|
+
// Group links by input name
|
|
744
|
+
const linksByInput = {};
|
|
745
|
+
nodeLinks.forEach(link => {
|
|
746
|
+
if (!linksByInput[link.to.port_name]) {
|
|
747
|
+
linksByInput[link.to.port_name] = [];
|
|
748
|
+
}
|
|
749
|
+
linksByInput[link.to.port_name].push(link);
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
// Check if all required inputs are ready
|
|
753
|
+
const isReady = Object.entries(linksByInput).every(([inputName, links]) => {
|
|
754
|
+
const inputDef = nodeDefinition.config.inputs.find(i => i.name === inputName);
|
|
755
|
+
if (!inputDef) return true; // defensive; shouldn't normally happen
|
|
756
|
+
|
|
757
|
+
// --- 1) Refiring multiple inputs (loop-start etc.) ---
|
|
758
|
+
if (inputDef.allow_multiple && inputDef.refires) {
|
|
759
|
+
const lastConsumed = CacheManager.getLastConsumed(downstreamNode.settings, inputName);
|
|
760
|
+
|
|
761
|
+
return links.some(link => {
|
|
762
|
+
if (link.type === "plugin") return false; // shouldn't be refiring anyway
|
|
763
|
+
|
|
764
|
+
const { node_id, port_name } = link.from;
|
|
765
|
+
|
|
766
|
+
if (lastConsumed === 0) {
|
|
767
|
+
// First time: we only consider this "ready"
|
|
768
|
+
// if upstream has actually produced a *non-null* value.
|
|
769
|
+
const hasEntry = this.cache.has({ node_id, port_name });
|
|
770
|
+
const value = this.cache.get({ node_id, port_name });
|
|
771
|
+
|
|
772
|
+
const ready = hasEntry && value !== null && value !== undefined;
|
|
773
|
+
this.logDebug(
|
|
774
|
+
`Refiring input [${inputName}] first run from ${node_id}:${port_name}, ready: ${ready}`
|
|
775
|
+
);
|
|
776
|
+
return ready;
|
|
777
|
+
} else {
|
|
778
|
+
// Subsequent runs: only fire when there is something newer
|
|
779
|
+
const hasNew = this.cache.hasNew({
|
|
780
|
+
node_id,
|
|
781
|
+
port_name,
|
|
782
|
+
afterTimestamp: lastConsumed
|
|
783
|
+
});
|
|
784
|
+
this.logDebug(
|
|
785
|
+
`Refiring input [${inputName}] from ${node_id}:${port_name}, hasNew: ${hasNew} (after ${lastConsumed})`
|
|
786
|
+
);
|
|
787
|
+
return hasNew;
|
|
788
|
+
}
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// --- 2) Non-refiring inputs (your existing behavior, just made explicit) ---
|
|
793
|
+
|
|
794
|
+
return links.every(link => {
|
|
795
|
+
if (link.type === "plugin") return true;
|
|
796
|
+
|
|
797
|
+
const { node_id, port_name } = link.from;
|
|
798
|
+
const hasEntry = this.cache.has({ node_id, port_name });
|
|
799
|
+
const value = this.cache.get({ node_id, port_name });
|
|
800
|
+
|
|
801
|
+
this.logDebug(
|
|
802
|
+
`Checking regular input [${inputName}] from ${node_id}:${port_name}, hasEntry: ${hasEntry}, value:`,
|
|
803
|
+
value
|
|
804
|
+
);
|
|
805
|
+
|
|
806
|
+
if (inputDef.required) {
|
|
807
|
+
// Required: as soon as the upstream has *any* value in history
|
|
808
|
+
// (including null), we consider this satisfied.
|
|
809
|
+
// This matches your previous behavior.
|
|
810
|
+
return hasEntry;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// Optional:
|
|
814
|
+
// - If no link existed in history -> false (upstream hasn't run yet).
|
|
815
|
+
// - If upstream ran with either a real value or null -> true.
|
|
816
|
+
// (null meaning "ran, but intentionally no data", which is your convention.)
|
|
817
|
+
return hasEntry || value === null;
|
|
818
|
+
});
|
|
819
|
+
});
|
|
820
|
+
|
|
821
|
+
|
|
822
|
+
if (isReady) {
|
|
823
|
+
this.logDebug(`Node [${downstreamNode.id}] is ready. Processing...`);
|
|
824
|
+
|
|
825
|
+
// Use centralized method to add to queue with duplicate checking
|
|
826
|
+
this.addToExecutionQueue(downstreamNode);
|
|
827
|
+
} else {
|
|
828
|
+
this.logDebug(`Node [${downstreamNode.id}] is not ready.`);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
this.logDebug(`Propagation from node [${currentNodeId}] completed`);
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
/**
|
|
835
|
+
* Add a node to the execution queue with duplicate checking
|
|
836
|
+
* This centralizes all execution queue additions and ensures nodes
|
|
837
|
+
* with identical execution states (same inputs/settings) are not executed twice
|
|
838
|
+
* @param {Object} node - The node to add to the execution queue
|
|
839
|
+
* @returns {boolean} True if the node was added to the queue, false if it was skipped (duplicate)
|
|
840
|
+
* @private
|
|
841
|
+
*/
|
|
842
|
+
addToExecutionQueue(node) {
|
|
843
|
+
try {
|
|
844
|
+
const nodeDefinition = this.nodes[node.type];
|
|
845
|
+
if (!nodeDefinition) {
|
|
846
|
+
this.logDebug(`Warning: Cannot add node [${node.id}] to queue - node type "${node.type}" not found`);
|
|
847
|
+
return false;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// Collect inputs for this node to create execution hash
|
|
851
|
+
const inputs = this._collectNodeInputs(node, nodeDefinition);
|
|
852
|
+
const executionHash = this._createExecutionHash(node.id, inputs, node.settings || {});
|
|
853
|
+
|
|
854
|
+
// Check if this exact execution state has already been processed
|
|
855
|
+
if (this.executedNodeStates.has(executionHash)) {
|
|
856
|
+
this.logDebug(`Node [${node.id}] already executed with identical inputs/settings (hash: ${executionHash}), skipping queue addition`);
|
|
857
|
+
return false;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
// Check if this node is already in the queue or currently running
|
|
861
|
+
if (this.queuedNodeHashes.has(executionHash)) {
|
|
862
|
+
this.logDebug(`Node [${node.id}] already in queue or running with hash ${executionHash}, skipping duplicate queue addition`);
|
|
863
|
+
return false;
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// Mark as queued and add to execution queue
|
|
867
|
+
this.queuedNodeHashes.add(executionHash);
|
|
868
|
+
this.executionQueue.push(node);
|
|
869
|
+
this.logDebug(`Added node [${node.id}] of type [${node.type}] to execution queue (hash: ${executionHash})`);
|
|
870
|
+
return true;
|
|
871
|
+
} catch (error) {
|
|
872
|
+
this.logDebug(`Error adding node [${node.id}] to execution queue:`, error.message);
|
|
873
|
+
throw error;
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
/**
|
|
878
|
+
* Collect inputs for a node (helper method for duplicate checking)
|
|
879
|
+
* @param {Object} node - The node to collect inputs for
|
|
880
|
+
* @param {Object} nodeDefinition - The node definition
|
|
881
|
+
* @returns {Object} The collected inputs
|
|
882
|
+
* @private
|
|
883
|
+
*/
|
|
884
|
+
_collectNodeInputs(node, nodeDefinition) {
|
|
885
|
+
const inputs = {};
|
|
886
|
+
|
|
887
|
+
// Get all links that connect to this node
|
|
888
|
+
const nodeInputLinks = this.flow.links.filter((link) => link.to.node_id === node.id);
|
|
889
|
+
|
|
890
|
+
// First, identify which input ports have connections
|
|
891
|
+
const connectedInputs = new Set();
|
|
892
|
+
nodeInputLinks.forEach(link => {
|
|
893
|
+
connectedInputs.add(link.to.port_name);
|
|
894
|
+
});
|
|
895
|
+
|
|
896
|
+
// Process all connected inputs
|
|
897
|
+
nodeInputLinks.forEach((link) => {
|
|
898
|
+
const inputName = link.to.port_name;
|
|
899
|
+
const inputDef = nodeDefinition.config.inputs.find((i) => i.name === inputName);
|
|
900
|
+
if (!inputDef) return;
|
|
901
|
+
|
|
902
|
+
if (inputDef.allow_multiple && inputDef.refires) {
|
|
903
|
+
// Refiring input: Only collect NEW values (not yet consumed)
|
|
904
|
+
const lastConsumed = CacheManager.getLastConsumed(node.settings, inputName);
|
|
905
|
+
|
|
906
|
+
if (lastConsumed === 0) {
|
|
907
|
+
const latestValue = this.cache.get({ node_id: link.from.node_id, port_name: link.from.port_name });
|
|
908
|
+
if (latestValue !== undefined) {
|
|
909
|
+
inputs[inputName] = latestValue;
|
|
910
|
+
}
|
|
911
|
+
} else {
|
|
912
|
+
const newValues = this.cache.getNew({
|
|
913
|
+
node_id: link.from.node_id,
|
|
914
|
+
port_name: link.from.port_name,
|
|
915
|
+
afterTimestamp: lastConsumed
|
|
916
|
+
});
|
|
917
|
+
|
|
918
|
+
if (newValues.length > 0) {
|
|
919
|
+
inputs[inputName] = newValues[0];
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
} else if (inputDef.allow_multiple && !inputDef.refires) {
|
|
923
|
+
// Non-refiring multiple input: Collect all current values
|
|
924
|
+
const cachedValue = this.cache.get({ node_id: link.from.node_id, port_name: link.from.port_name });
|
|
925
|
+
|
|
926
|
+
if (this.cache.has({ node_id: link.from.node_id, port_name: link.from.port_name })) {
|
|
927
|
+
if(!inputs[inputName]) {
|
|
928
|
+
inputs[inputName] = [];
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
const value = cachedValue;
|
|
932
|
+
const itemType = inputDef.type || "any";
|
|
933
|
+
|
|
934
|
+
// If the value is an array and doesn't pass type check as-is,
|
|
935
|
+
// check if it's an array of the expected type and spread it in
|
|
936
|
+
if (Array.isArray(value) && !this.typeCheck(value, itemType)) {
|
|
937
|
+
let allValid = value.length > 0;
|
|
938
|
+
for (const item of value) {
|
|
939
|
+
if (!this.typeCheck(item, itemType)) {
|
|
940
|
+
allValid = false;
|
|
941
|
+
break;
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
if (allValid) {
|
|
945
|
+
inputs[inputName].push(...value);
|
|
946
|
+
}
|
|
947
|
+
} else if (this.typeCheck(value, itemType)) {
|
|
948
|
+
inputs[inputName].push(value);
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
} else {
|
|
952
|
+
// Single value input: Always use latest value
|
|
953
|
+
const cachedValue = this.cache.get({ node_id: link.from.node_id, port_name: link.from.port_name });
|
|
954
|
+
|
|
955
|
+
if (this.cache.has({ node_id: link.from.node_id, port_name: link.from.port_name })) {
|
|
956
|
+
inputs[inputName] = cachedValue;
|
|
957
|
+
} else if (!inputDef.required && inputDef.default !== undefined) {
|
|
958
|
+
inputs[inputName] = inputDef.default;
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
});
|
|
962
|
+
|
|
963
|
+
// Handle unconnected inputs with default values
|
|
964
|
+
nodeDefinition.config.inputs.forEach((inputDef) => {
|
|
965
|
+
const inputName = inputDef.name;
|
|
966
|
+
|
|
967
|
+
if (connectedInputs.has(inputName) || inputs[inputName] !== undefined) {
|
|
968
|
+
return;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
if (inputDef.default !== undefined) {
|
|
972
|
+
inputs[inputName] = inputDef.default;
|
|
973
|
+
}
|
|
974
|
+
});
|
|
975
|
+
|
|
976
|
+
return inputs;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
/**
|
|
980
|
+
* Launch a node for parallel execution
|
|
981
|
+
* @param {Object} node - The node to launch
|
|
982
|
+
*/
|
|
983
|
+
launchNode(node) {
|
|
984
|
+
this.runningNodes.add(node.id);
|
|
985
|
+
this.logDebug(`Node [${node.id}] added to running set`);
|
|
986
|
+
|
|
987
|
+
// Remove from queued hashes since it's now running
|
|
988
|
+
// Recalculate hash to remove it from tracking
|
|
989
|
+
try {
|
|
990
|
+
const nodeDefinition = this.nodes[node.type];
|
|
991
|
+
if (nodeDefinition) {
|
|
992
|
+
const inputs = this._collectNodeInputs(node, nodeDefinition);
|
|
993
|
+
const executionHash = this._createExecutionHash(node.id, inputs, node.settings || {});
|
|
994
|
+
this.queuedNodeHashes.delete(executionHash);
|
|
995
|
+
this.logDebug(`Removed node [${node.id}] hash ${executionHash} from queued tracking (now running)`);
|
|
996
|
+
}
|
|
997
|
+
} catch (error) {
|
|
998
|
+
// If hash calculation fails, continue anyway - not critical
|
|
999
|
+
this.logDebug(`Warning: Could not remove hash for node [${node.id}] from queued tracking:`, error.message);
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// Execute node asynchronously
|
|
1003
|
+
this.processNode(node)
|
|
1004
|
+
.then(() => {
|
|
1005
|
+
// Node completed successfully
|
|
1006
|
+
this.runningNodes.delete(node.id);
|
|
1007
|
+
this.completedNodes.add(node.id);
|
|
1008
|
+
this.logDebug(`Node [${node.id}] completed, removed from running set`);
|
|
1009
|
+
|
|
1010
|
+
// Propagate downstream nodes to queue
|
|
1011
|
+
this.propagate(node.id);
|
|
1012
|
+
|
|
1013
|
+
// Signal completion for any waiting promises
|
|
1014
|
+
this._signalNodeCompletion();
|
|
1015
|
+
})
|
|
1016
|
+
.catch((error) => {
|
|
1017
|
+
// Node failed
|
|
1018
|
+
this.runningNodes.delete(node.id);
|
|
1019
|
+
this.logDebug(`Node [${node.id}] failed, removed from running set:`, error.message);
|
|
1020
|
+
|
|
1021
|
+
// Store the error for the main execution loop to handle
|
|
1022
|
+
this.lastError = error;
|
|
1023
|
+
|
|
1024
|
+
// Signal completion for any waiting promises (even on failure)
|
|
1025
|
+
this._signalNodeCompletion();
|
|
1026
|
+
});
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
/**
|
|
1030
|
+
* Wait for at least one node to complete
|
|
1031
|
+
* Uses event-based signaling instead of busy-waiting
|
|
1032
|
+
* @returns {Promise<void>}
|
|
1033
|
+
*/
|
|
1034
|
+
async waitForNodeCompletion() {
|
|
1035
|
+
// If no nodes are running, return immediately
|
|
1036
|
+
if (this.runningNodes.size === 0) {
|
|
1037
|
+
return;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
// Wait for a node to complete via event-based signaling
|
|
1041
|
+
return new Promise((resolve) => {
|
|
1042
|
+
this.nodeCompletionResolvers.push(resolve);
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
/**
|
|
1047
|
+
* Signal that a node has completed (success or failure)
|
|
1048
|
+
* This triggers any waiting promises from waitForNodeCompletion
|
|
1049
|
+
*/
|
|
1050
|
+
_signalNodeCompletion() {
|
|
1051
|
+
if (this.nodeCompletionResolvers.length > 0) {
|
|
1052
|
+
const resolver = this.nodeCompletionResolvers.shift();
|
|
1053
|
+
resolver();
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
|
|
1058
|
+
/**
|
|
1059
|
+
* Clean up resources including knowledge databases and temporary files
|
|
1060
|
+
* This should be called when the engine is no longer needed to free up memory
|
|
1061
|
+
* @returns {Promise<void>}
|
|
1062
|
+
*/
|
|
1063
|
+
async cleanup() {
|
|
1064
|
+
this.logDebug('Starting cleanup process...');
|
|
1065
|
+
|
|
1066
|
+
try {
|
|
1067
|
+
// Clean up main orchestration knowledge base integration
|
|
1068
|
+
if (this.config.integrations?.knowledgeBase) {
|
|
1069
|
+
this.logDebug('Cleaning up main knowledge base integration...');
|
|
1070
|
+
await this.config.integrations.knowledgeBase.disconnect();
|
|
1071
|
+
delete this.config.integrations.knowledgeBase;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
// Also clean up legacy sqlite integration for backward compatibility
|
|
1075
|
+
if (this.config.integrations?.sqlite) {
|
|
1076
|
+
this.logDebug('Cleaning up legacy SQLite integration...');
|
|
1077
|
+
await this.config.integrations.sqlite.disconnect();
|
|
1078
|
+
delete this.config.integrations.sqlite;
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
// Clean up any imported engines that were created
|
|
1082
|
+
// These are stored in the cache when import nodes are processed
|
|
1083
|
+
const rawStore = this.cache.getRawStore();
|
|
1084
|
+
const importEngines = Object.values(rawStore).filter(item =>
|
|
1085
|
+
item && typeof item === 'object' && item.constructor?.name === 'zv1'
|
|
1086
|
+
);
|
|
1087
|
+
|
|
1088
|
+
for (const importEngine of importEngines) {
|
|
1089
|
+
if (importEngine.cleanup) {
|
|
1090
|
+
this.logDebug('Cleaning up imported engine...');
|
|
1091
|
+
await importEngine.cleanup();
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
// Clear the cache
|
|
1096
|
+
this.cache.clear();
|
|
1097
|
+
|
|
1098
|
+
// Clear timeline
|
|
1099
|
+
this.timeline = [];
|
|
1100
|
+
|
|
1101
|
+
// Clean up any remaining temporary knowledge base files
|
|
1102
|
+
await this._cleanupTempKnowledgeFiles();
|
|
1103
|
+
|
|
1104
|
+
this.logDebug('Cleanup completed successfully');
|
|
1105
|
+
|
|
1106
|
+
} catch (error) {
|
|
1107
|
+
console.warn('[WARN] Error during cleanup:', error.message);
|
|
1108
|
+
// Don't throw - cleanup should be best effort
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
/**
|
|
1113
|
+
* Run the flow and return the final output of the flow
|
|
1114
|
+
* @param {Object} inputData - Data to inject into input nodes
|
|
1115
|
+
* @param {number} timeout - Maximum execution time in milliseconds
|
|
1116
|
+
* @returns {Object} The final output from output nodes
|
|
1117
|
+
*/
|
|
1118
|
+
async run(inputData, timeout = 60000) {
|
|
1119
|
+
this.logDebug(`Starting flow execution with timeout: ${timeout}ms`);
|
|
1120
|
+
this.logDebug(`Input data:`, JSON.stringify(inputData, null, 2));
|
|
1121
|
+
|
|
1122
|
+
// Clear executed states for this run
|
|
1123
|
+
this.executedNodeStates.clear();
|
|
1124
|
+
this.queuedNodeHashes.clear();
|
|
1125
|
+
|
|
1126
|
+
// clear the execution queue
|
|
1127
|
+
this.executionQueue = [];
|
|
1128
|
+
this.runningNodes = new Set();
|
|
1129
|
+
this.completedNodes = new Set();
|
|
1130
|
+
|
|
1131
|
+
// Update error manager with timeout context
|
|
1132
|
+
this.errorManager.updateExecutionContext({
|
|
1133
|
+
timeout: timeout,
|
|
1134
|
+
startTime: Date.now()
|
|
1135
|
+
});
|
|
1136
|
+
|
|
1137
|
+
// Set a timeout flag to prevent infinite execution
|
|
1138
|
+
this.hasTimedOut = false;
|
|
1139
|
+
|
|
1140
|
+
// Preemptive cancellation: an AbortController whose signal is exposed to
|
|
1141
|
+
// node processes/integrations (via config.signal) and aborted when the flow
|
|
1142
|
+
// times out. This lets in-flight LLM/HTTP calls and sub-engines actually
|
|
1143
|
+
// stop, instead of the loop parking on a hung node until it resolves.
|
|
1144
|
+
this.abortController = new AbortController();
|
|
1145
|
+
this._abortTeardown = () => {};
|
|
1146
|
+
// Cascade: if an ancestor engine passed its signal in (macro / imported-flow
|
|
1147
|
+
// sub-engine), abort when it aborts — so a parent timeout reaches descendants.
|
|
1148
|
+
// Use the construction-time inherited signal, NOT this.config.signal (which a
|
|
1149
|
+
// prior run() of a reused engine may have overwritten with its own signal).
|
|
1150
|
+
const inheritedSignal = this._inheritedSignal;
|
|
1151
|
+
if (inheritedSignal) {
|
|
1152
|
+
if (inheritedSignal.aborted) {
|
|
1153
|
+
this.abortController.abort(inheritedSignal.reason);
|
|
1154
|
+
} else {
|
|
1155
|
+
const onAncestorAbort = () => this.abortController.abort(inheritedSignal.reason);
|
|
1156
|
+
inheritedSignal.addEventListener('abort', onAncestorAbort, { once: true });
|
|
1157
|
+
this._abortTeardown = () => inheritedSignal.removeEventListener('abort', onAncestorAbort);
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
// Expose our signal to node processes and integrations.
|
|
1161
|
+
this.config.signal = this.abortController.signal;
|
|
1162
|
+
|
|
1163
|
+
this.flowTimeout = setTimeout(() => {
|
|
1164
|
+
this.logDebug("Flow execution timed out");
|
|
1165
|
+
this.hasTimedOut = true;
|
|
1166
|
+
if (!this.abortController.signal.aborted) {
|
|
1167
|
+
this.abortController.abort(new Error(`Flow execution timed out after ${timeout}ms`));
|
|
1168
|
+
}
|
|
1169
|
+
}, timeout);
|
|
1170
|
+
|
|
1171
|
+
let inputsMissingValues = [];
|
|
1172
|
+
|
|
1173
|
+
try {
|
|
1174
|
+
this.logDebug("Starting flow execution...");
|
|
1175
|
+
|
|
1176
|
+
// Step 1: Add all entry nodes (constant nodes without inputs) to the execution queue
|
|
1177
|
+
for (const node of this.entryNodes) {
|
|
1178
|
+
this.logDebug(`Processing entry node [${node.id}] of type [${node.type}]`);
|
|
1179
|
+
// await this.processNode(node);
|
|
1180
|
+
// await this.propagate(node.id);
|
|
1181
|
+
|
|
1182
|
+
this.addToExecutionQueue(node);
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
|
|
1186
|
+
// Step 2: Setup and then add input nodes (if any) to the execution queue
|
|
1187
|
+
if (this.inputNodes.length > 0) {
|
|
1188
|
+
|
|
1189
|
+
const numberOfInputDataNodes = this.inputNodes.filter(node => node.type === "input-data").length;
|
|
1190
|
+
const numberOfInputChatNodes = this.inputNodes.filter(node => node.type === "input-chat").length;
|
|
1191
|
+
const numberOfInputPromptNodes = this.inputNodes.filter(node => node.type === "input-prompt").length;
|
|
1192
|
+
|
|
1193
|
+
this.logDebug(`Found ${numberOfInputDataNodes} input-data nodes, ${numberOfInputChatNodes} input-chat nodes, ${numberOfInputPromptNodes} input-prompt nodes`);
|
|
1194
|
+
|
|
1195
|
+
for(const inputNode of this.inputNodes) {
|
|
1196
|
+
this.logDebug(`Injecting inputData into input node [${inputNode.id}]:`, inputData);
|
|
1197
|
+
if(!inputNode.settings) {
|
|
1198
|
+
inputNode.settings = {};
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
if(inputNode.type === "input-data") {
|
|
1202
|
+
const inputKey = inputNode.settings?.key || 'data';
|
|
1203
|
+
let variable_value = inputData[inputKey];
|
|
1204
|
+
|
|
1205
|
+
// If the specific key is not found, try to map from the main 'data' key
|
|
1206
|
+
// This handles cases where .zv1 files have input-data nodes with specific keys
|
|
1207
|
+
// but the main flow is passing input with key 'data'
|
|
1208
|
+
if(variable_value === undefined && inputData.data !== undefined && inputKey !== 'data') {
|
|
1209
|
+
this.logDebug(`Mapping input 'data' to input-data node key '${inputKey}'`);
|
|
1210
|
+
variable_value = inputData.data;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
if(variable_value === undefined) {
|
|
1214
|
+
variable_value = inputNode.settings.default_value;
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
if(variable_value !== undefined) {
|
|
1218
|
+
// await this.processNode({ ...inputNode, settings: { ...inputNode.settings, ...{value: variable_value} } });
|
|
1219
|
+
// await this.propagate(inputNode.id);
|
|
1220
|
+
this.addToExecutionQueue({ ...inputNode, settings: { ...inputNode.settings, ...{value: variable_value} } });
|
|
1221
|
+
} else {
|
|
1222
|
+
inputsMissingValues.push({
|
|
1223
|
+
id: inputNode.id,
|
|
1224
|
+
type: inputNode.type,
|
|
1225
|
+
key: inputNode.settings?.key || 'data'
|
|
1226
|
+
});
|
|
1227
|
+
}
|
|
1228
|
+
} else if(inputNode.type === "input-chat") {
|
|
1229
|
+
|
|
1230
|
+
let variable_value = inputData[inputNode.settings?.key || 'chat'];
|
|
1231
|
+
|
|
1232
|
+
if(variable_value !== undefined) {
|
|
1233
|
+
// Normalize string/object to message array
|
|
1234
|
+
if (typeof variable_value === 'string') {
|
|
1235
|
+
variable_value = [{ role: 'user', content: variable_value }];
|
|
1236
|
+
} else if (variable_value && typeof variable_value === 'object' && !Array.isArray(variable_value)) {
|
|
1237
|
+
variable_value = [variable_value];
|
|
1238
|
+
}
|
|
1239
|
+
this.addToExecutionQueue({ ...inputNode, settings: { ...inputNode.settings, ...{messages: variable_value} } });
|
|
1240
|
+
} else {
|
|
1241
|
+
inputsMissingValues.push({
|
|
1242
|
+
id: inputNode.id,
|
|
1243
|
+
type: inputNode.type,
|
|
1244
|
+
key: inputNode.settings?.key || 'chat'
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
} else if(inputNode.type === "input-prompt") {
|
|
1249
|
+
let variable_value = inputData[inputNode.settings?.key || 'prompt'];
|
|
1250
|
+
|
|
1251
|
+
if(variable_value !== undefined) {
|
|
1252
|
+
// await this.processNode({ ...inputNode, settings: { ...inputNode.settings, ...{prompt: variable_value} } });
|
|
1253
|
+
// await this.propagate(inputNode.id);
|
|
1254
|
+
this.addToExecutionQueue({ ...inputNode, settings: { ...inputNode.settings, ...{prompt: variable_value} } });
|
|
1255
|
+
} else {
|
|
1256
|
+
inputsMissingValues.push({
|
|
1257
|
+
id: inputNode.id,
|
|
1258
|
+
type: inputNode.type,
|
|
1259
|
+
key: inputNode.settings?.key || 'prompt'
|
|
1260
|
+
});
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
} else {
|
|
1266
|
+
this.logDebug("No input nodes found - flow will start from constant nodes");
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
// Step 3: Process the execution queue with parallel execution
|
|
1270
|
+
while(this.executionQueue.length > 0 || this.runningNodes.size > 0) {
|
|
1271
|
+
|
|
1272
|
+
// timeout check
|
|
1273
|
+
if(this.hasTimedOut) {
|
|
1274
|
+
this.errorManager.throwTimeoutError("Flow execution timed out.");
|
|
1275
|
+
break;
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
// Launch nodes up to concurrency limit
|
|
1279
|
+
while(this.executionQueue.length > 0 && this.runningNodes.size < this.maxConcurrency) {
|
|
1280
|
+
const node = this.executionQueue.shift();
|
|
1281
|
+
this.logDebug('Launching node:', node.id, 'type:', node.type);
|
|
1282
|
+
// Launch node asynchronously
|
|
1283
|
+
this.launchNode(node);
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
// Wait for at least one node to complete
|
|
1287
|
+
if(this.runningNodes.size > 0) {
|
|
1288
|
+
await this.waitForNodeCompletion();
|
|
1289
|
+
|
|
1290
|
+
// Check for errors from parallel execution
|
|
1291
|
+
if(this.lastError) {
|
|
1292
|
+
const error = this.lastError;
|
|
1293
|
+
this.lastError = null; // Clear the error
|
|
1294
|
+
throw error; // Re-throw to be handled by the main try/catch
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
this.logDebug("Execution queue is empty and no nodes running");
|
|
1300
|
+
|
|
1301
|
+
// Step 4: Capture and return all output nodes' results
|
|
1302
|
+
const outputNodes = this.flow.nodes.filter(
|
|
1303
|
+
(node) => this.nodes[node.type]?.config?.is_output
|
|
1304
|
+
);
|
|
1305
|
+
|
|
1306
|
+
this.logDebug("outputNodes", outputNodes);
|
|
1307
|
+
|
|
1308
|
+
// If no output nodes found, return partial completion from terminal nodes
|
|
1309
|
+
if (outputNodes.length === 0) {
|
|
1310
|
+
this.logDebug("No output nodes found, returning partial completion from terminal nodes");
|
|
1311
|
+
|
|
1312
|
+
// Find all nodes that don't have outgoing connections
|
|
1313
|
+
const nodesWithOutgoingLinks = new Set(
|
|
1314
|
+
this.flow.links.map(link => link.from.node_id)
|
|
1315
|
+
);
|
|
1316
|
+
|
|
1317
|
+
const terminalNodes = this.flow.nodes.filter(
|
|
1318
|
+
node => !nodesWithOutgoingLinks.has(node.id)
|
|
1319
|
+
);
|
|
1320
|
+
|
|
1321
|
+
this.logDebug(`Found ${terminalNodes.length} terminal nodes`);
|
|
1322
|
+
|
|
1323
|
+
// Collect outputs from all terminal nodes
|
|
1324
|
+
const terminalOutputs = terminalNodes
|
|
1325
|
+
.map(node => {
|
|
1326
|
+
const nodeConfig = this.nodes[node.type]?.config;
|
|
1327
|
+
if (!nodeConfig) return null;
|
|
1328
|
+
const outputs = {};
|
|
1329
|
+
// For all outputs defined in config
|
|
1330
|
+
for (const output of nodeConfig.outputs) {
|
|
1331
|
+
const value = this.cache.get({ node_id: node.id, port_name: output.name });
|
|
1332
|
+
if (value === null || value === undefined) continue;
|
|
1333
|
+
outputs[output.name] = value;
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
// --- NEW: For import nodes, also include all cache keys that start with node.id + ':'
|
|
1337
|
+
if (node.type.startsWith('imported-')) {
|
|
1338
|
+
const nodeOutputs = this.cache.getNodeOutputs(node.id);
|
|
1339
|
+
for (const [outputName, value] of Object.entries(nodeOutputs)) {
|
|
1340
|
+
if (!(outputName in outputs)) {
|
|
1341
|
+
outputs[outputName] = value;
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
if (Object.keys(outputs).length === 0) return null;
|
|
1346
|
+
return {
|
|
1347
|
+
node_id: node.id,
|
|
1348
|
+
type: node.type,
|
|
1349
|
+
outputs
|
|
1350
|
+
};
|
|
1351
|
+
})
|
|
1352
|
+
.filter(Boolean); // Remove null entries
|
|
1353
|
+
|
|
1354
|
+
return {
|
|
1355
|
+
partial: true,
|
|
1356
|
+
message: inputsMissingValues.length > 0 ? "Completed with missing input values and output nodes, results may be partial." : "Completed without output nodes.",
|
|
1357
|
+
terminalNodes: terminalOutputs,
|
|
1358
|
+
timeline: this.timeline,
|
|
1359
|
+
inputsMissingValues: inputsMissingValues,
|
|
1360
|
+
cost_summary: this._getCostSummaryFromTimeline()
|
|
1361
|
+
};
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
// Collect all outputs
|
|
1365
|
+
const finalOutputs = {};
|
|
1366
|
+
|
|
1367
|
+
for (const node of outputNodes) {
|
|
1368
|
+
// Get the node's output values
|
|
1369
|
+
const nodeConfig = this.nodes[node.type].config;
|
|
1370
|
+
const nodeOutputs = nodeConfig.outputs;
|
|
1371
|
+
|
|
1372
|
+
// check cache to see if this node returned a value for output_key
|
|
1373
|
+
|
|
1374
|
+
const outputKey = node.settings?.key;
|
|
1375
|
+
|
|
1376
|
+
let doesThisNodeHaveAKey = false;
|
|
1377
|
+
|
|
1378
|
+
if(outputKey !== undefined && outputKey !== null && outputKey !== '') {
|
|
1379
|
+
doesThisNodeHaveAKey = true;
|
|
1380
|
+
} else {
|
|
1381
|
+
doesThisNodeHaveAKey = false;
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
this.logDebug("nodeOutputs", nodeOutputs);
|
|
1385
|
+
|
|
1386
|
+
const numberOfOutputDataNodes = nodeOutputs.filter(output => output.name === "value").length;
|
|
1387
|
+
const numberOfOutputChatNodes = nodeOutputs.filter(output => output.name === "chat").length;
|
|
1388
|
+
const numberOfOutputPromptNodes = nodeOutputs.filter(output => output.name === "prompt").length;
|
|
1389
|
+
|
|
1390
|
+
this.logDebug(`Found ${numberOfOutputDataNodes} output-data nodes, ${numberOfOutputChatNodes} output-chat nodes, ${numberOfOutputPromptNodes} output-prompt nodes`);
|
|
1391
|
+
|
|
1392
|
+
let dataIndex = 0;
|
|
1393
|
+
let chatIndex = 0;
|
|
1394
|
+
|
|
1395
|
+
for (const output of nodeOutputs) {
|
|
1396
|
+
|
|
1397
|
+
if (this.cache.has({ node_id: node.id, port_name: output.name })) {
|
|
1398
|
+
const value = this.cache.get({ node_id: node.id, port_name: output.name });
|
|
1399
|
+
|
|
1400
|
+
// Skip null/undefined values
|
|
1401
|
+
if (value === null || value === undefined) continue;
|
|
1402
|
+
|
|
1403
|
+
// if this node.type is a data output, add the value to .data if no key is provided - otherwise add the value to the outputs with the key
|
|
1404
|
+
if(node.type === "output-data") {
|
|
1405
|
+
if(doesThisNodeHaveAKey ) {
|
|
1406
|
+
// if the key is provided, add the value to the outputs with the key
|
|
1407
|
+
finalOutputs[outputKey] = value;
|
|
1408
|
+
} else if(numberOfOutputDataNodes > 1) {
|
|
1409
|
+
finalOutputs['data_' + dataIndex] = value;
|
|
1410
|
+
dataIndex++;
|
|
1411
|
+
} else {
|
|
1412
|
+
finalOutputs['data'] = value;
|
|
1413
|
+
}
|
|
1414
|
+
} else if(node.type === "output-chat") {
|
|
1415
|
+
// if this node.type is a chat output, add the value to the conversation object
|
|
1416
|
+
|
|
1417
|
+
if(doesThisNodeHaveAKey) {
|
|
1418
|
+
finalOutputs[outputKey] = value;
|
|
1419
|
+
} else if(numberOfOutputChatNodes > 1) {
|
|
1420
|
+
finalOutputs['chat_' + chatIndex] = value;
|
|
1421
|
+
chatIndex++;
|
|
1422
|
+
} else {
|
|
1423
|
+
finalOutputs['chat'] = value;
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
this.logDebug("Flow execution complete. Final outputs:", finalOutputs);
|
|
1431
|
+
clearTimeout(this.flowTimeout);
|
|
1432
|
+
|
|
1433
|
+
return {
|
|
1434
|
+
outputs: finalOutputs,
|
|
1435
|
+
timeline: this.timeline,
|
|
1436
|
+
cost_summary: this._getCostSummaryFromTimeline(),
|
|
1437
|
+
inputsMissingValues: inputsMissingValues,
|
|
1438
|
+
message: inputsMissingValues.length > 0 ? "Completed with missing input values, results may be partial." : "Completed."
|
|
1439
|
+
};
|
|
1440
|
+
} catch (error) {
|
|
1441
|
+
// Clear the timeout on error
|
|
1442
|
+
clearTimeout(this.flowTimeout);
|
|
1443
|
+
|
|
1444
|
+
// If this is a timeout error, add it to the timeline
|
|
1445
|
+
if (error.errorType === 'timeout') {
|
|
1446
|
+
// Create a timeline entry for the timeout
|
|
1447
|
+
const timeoutEntry = {
|
|
1448
|
+
nodeId: 'system',
|
|
1449
|
+
nodeType: 'timeout',
|
|
1450
|
+
inputs: {},
|
|
1451
|
+
settings: {},
|
|
1452
|
+
startTime: new Date(this.errorManager.executionContext.startTime).toISOString(),
|
|
1453
|
+
endTime: new Date().toISOString(),
|
|
1454
|
+
durationMs: Date.now() - this.errorManager.executionContext.startTime,
|
|
1455
|
+
status: 'error',
|
|
1456
|
+
errorMessage: error.message
|
|
1457
|
+
};
|
|
1458
|
+
this.timeline.push(timeoutEntry);
|
|
1459
|
+
|
|
1460
|
+
// Update error manager context with final timeline
|
|
1461
|
+
this.errorManager.updateExecutionContext({
|
|
1462
|
+
timeline: this.timeline,
|
|
1463
|
+
nodesExecuted: this.timeline.length
|
|
1464
|
+
});
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
// Re-throw to propagate to the caller
|
|
1468
|
+
throw error;
|
|
1469
|
+
} finally {
|
|
1470
|
+
clearTimeout(this.flowTimeout);
|
|
1471
|
+
this._abortTeardown?.();
|
|
1472
|
+
// Restore the inherited signal so a reused engine doesn't treat this run's
|
|
1473
|
+
// (possibly aborted) signal as an ancestor on the next run().
|
|
1474
|
+
this.config.signal = this._inheritedSignal;
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
/**
|
|
1479
|
+
* Process a macro node by executing its internal flow
|
|
1480
|
+
* @param {Object} node - The macro node to process
|
|
1481
|
+
* @returns {Object} The outputs from the macro
|
|
1482
|
+
*/
|
|
1483
|
+
async processMacroNode(node) {
|
|
1484
|
+
this.logDebug(`Processing macro node [${node.id}] of type [${node.type}]`);
|
|
1485
|
+
|
|
1486
|
+
const nodeDefinition = this.nodes[node.type];
|
|
1487
|
+
const macroConfig = nodeDefinition.config;
|
|
1488
|
+
|
|
1489
|
+
// Create timeline entry for macro execution
|
|
1490
|
+
const startDate = new Date();
|
|
1491
|
+
const timelineEntry = {
|
|
1492
|
+
node_id: node.id,
|
|
1493
|
+
type: node.type,
|
|
1494
|
+
startTime: startDate.toISOString(),
|
|
1495
|
+
status: 'running'
|
|
1496
|
+
};
|
|
1497
|
+
this.timeline.push(timelineEntry);
|
|
1498
|
+
|
|
1499
|
+
|
|
1500
|
+
try {
|
|
1501
|
+
// Create internal flow from macro_flow
|
|
1502
|
+
const internalFlow = {
|
|
1503
|
+
nodes: [...macroConfig.macro_flow.nodes],
|
|
1504
|
+
links: [...macroConfig.macro_flow.links]
|
|
1505
|
+
};
|
|
1506
|
+
|
|
1507
|
+
// Prepare tools object for internal engine if this macro accepts plugins
|
|
1508
|
+
let tools = {};
|
|
1509
|
+
|
|
1510
|
+
if (macroConfig.accepts_plugins && macroConfig.plugins && macroConfig.plugins.length > 0) {
|
|
1511
|
+
this.logDebug(`Macro [${node.id}] accepts plugins, creating tool runners for parent context execution`);
|
|
1512
|
+
|
|
1513
|
+
// Find all plugin nodes connected to this macro in the parent flow
|
|
1514
|
+
const externalPluginNodeIds = this.flow.links
|
|
1515
|
+
.filter(link => link.type === "plugin" && link.to.node_id === node.id)
|
|
1516
|
+
.map(link => link.from.node_id);
|
|
1517
|
+
|
|
1518
|
+
this.logDebug(`Found ${externalPluginNodeIds.length} external plugins connected to macro [${node.id}]`);
|
|
1519
|
+
|
|
1520
|
+
// Create tool definitions for each external plugin
|
|
1521
|
+
for (const externalPluginNodeId of externalPluginNodeIds) {
|
|
1522
|
+
const externalPluginNode = this.flow.nodes.find(n => n.id === externalPluginNodeId);
|
|
1523
|
+
if (externalPluginNode) {
|
|
1524
|
+
// Generate the tool schema from the parent context
|
|
1525
|
+
const schema = this.generateToolSchema(externalPluginNode);
|
|
1526
|
+
|
|
1527
|
+
// Create a tool runner that executes in PARENT context
|
|
1528
|
+
const toolRunner = async (args) => {
|
|
1529
|
+
this.logDebug(`Tool runner called for [${schema.name}] from internal macro engine`);
|
|
1530
|
+
return await this.executePluginInParentContext(externalPluginNode, args);
|
|
1531
|
+
};
|
|
1532
|
+
|
|
1533
|
+
tools[schema.name] = {
|
|
1534
|
+
schema: schema,
|
|
1535
|
+
process: toolRunner
|
|
1536
|
+
};
|
|
1537
|
+
|
|
1538
|
+
this.logDebug(`Created tool runner for plugin [${externalPluginNode.id}] -> [${schema.name}]`);
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
// Create internal zv1 instance
|
|
1544
|
+
const internalEngine = new Workbench(internalFlow, {
|
|
1545
|
+
...this.config,
|
|
1546
|
+
// Pass through integrations and other config
|
|
1547
|
+
integrations: this.config.integrations,
|
|
1548
|
+
keys: this.config.keys,
|
|
1549
|
+
debug: this.config.debug,
|
|
1550
|
+
// Pass tools from parent context
|
|
1551
|
+
tools: Object.keys(tools).length > 0 ? tools : undefined,
|
|
1552
|
+
// Conditionally disable event firing for internal engine
|
|
1553
|
+
// If includeInternalEvents is true, pass through the event handlers
|
|
1554
|
+
onNodeStart: this.config.includeInternalEvents ? this.config.onNodeStart : null,
|
|
1555
|
+
onNodeComplete: this.config.includeInternalEvents ? this.config.onNodeComplete : null,
|
|
1556
|
+
onNodeError: this.config.includeInternalEvents ? this.config.onNodeError : null
|
|
1557
|
+
});
|
|
1558
|
+
|
|
1559
|
+
// Share executed states with internal engine to prevent duplicate execution
|
|
1560
|
+
internalEngine.executedNodeStates = this.executedNodeStates;
|
|
1561
|
+
|
|
1562
|
+
// Initialize the internal engine
|
|
1563
|
+
await internalEngine.initialize();
|
|
1564
|
+
|
|
1565
|
+
// Map macro inputs to internal flow inputs
|
|
1566
|
+
const internalInputs = {};
|
|
1567
|
+
macroConfig.inputs.forEach(inputDef => {
|
|
1568
|
+
const inputValue = this.getNodeInputValue(node, inputDef.name);
|
|
1569
|
+
if (inputValue !== undefined) {
|
|
1570
|
+
internalInputs[inputDef.name] = inputValue;
|
|
1571
|
+
}
|
|
1572
|
+
});
|
|
1573
|
+
|
|
1574
|
+
|
|
1575
|
+
// Fire onNodeStart event (matching the convention used in _executeNodeCore)
|
|
1576
|
+
if (this.config.onNodeStart) {
|
|
1577
|
+
await this.config.onNodeStart({
|
|
1578
|
+
nodeId: node.id,
|
|
1579
|
+
nodeType: node.type,
|
|
1580
|
+
timestamp: Date.now(),
|
|
1581
|
+
inputs: internalInputs,
|
|
1582
|
+
settings: node.settings || {}
|
|
1583
|
+
});
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
this.logDebug(`Macro [${node.id}] internal inputs:`, JSON.stringify(internalInputs, null, 2));
|
|
1587
|
+
|
|
1588
|
+
// Create execution hash to check for duplicate processing
|
|
1589
|
+
const executionHash = this._createExecutionHash(node.id, internalInputs, node.settings || {});
|
|
1590
|
+
|
|
1591
|
+
// Skip if this exact execution state has already been processed
|
|
1592
|
+
if (this.executedNodeStates.has(executionHash)) {
|
|
1593
|
+
|
|
1594
|
+
this.logDebug(`Macro [${node.id}] already executed with identical inputs/settings (hash: ${executionHash}), skipping`);
|
|
1595
|
+
|
|
1596
|
+
// Complete timeline entry for skipped execution
|
|
1597
|
+
const endDate = new Date();
|
|
1598
|
+
timelineEntry.endTime = endDate.toISOString();
|
|
1599
|
+
timelineEntry.durationMs = endDate - startDate;
|
|
1600
|
+
timelineEntry.status = 'skipped';
|
|
1601
|
+
timelineEntry.outputs = {};
|
|
1602
|
+
|
|
1603
|
+
// Fire onNodeComplete event for skipped execution
|
|
1604
|
+
if (this.config.onNodeComplete) {
|
|
1605
|
+
await this.config.onNodeComplete({
|
|
1606
|
+
nodeId: node.id,
|
|
1607
|
+
nodeType: node.type,
|
|
1608
|
+
timestamp: Date.now(),
|
|
1609
|
+
outputs: {},
|
|
1610
|
+
durationMs: endDate - startDate
|
|
1611
|
+
});
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
return {}; // Return empty outputs to avoid downstream processing
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
// Execute the internal flow
|
|
1618
|
+
const internalResult = await internalEngine.run(internalInputs);
|
|
1619
|
+
|
|
1620
|
+
// Map internal outputs back to macro outputs
|
|
1621
|
+
const macroOutputs = {};
|
|
1622
|
+
macroConfig.outputs.forEach(outputDef => {
|
|
1623
|
+
// Look for the output in the internal engine's cache
|
|
1624
|
+
// The output-data node stores its value in the cache with key "output_node_id:value"
|
|
1625
|
+
const outputNodeId = `output_${outputDef.name}`;
|
|
1626
|
+
const outputValue = internalEngine.cache.get({ node_id: outputNodeId, port_name: 'value' });
|
|
1627
|
+
|
|
1628
|
+
if (outputValue !== undefined) {
|
|
1629
|
+
macroOutputs[outputDef.name] = outputValue;
|
|
1630
|
+
}
|
|
1631
|
+
});
|
|
1632
|
+
|
|
1633
|
+
this.logDebug(`Macro [${node.id}] outputs:`, JSON.stringify(macroOutputs, null, 2));
|
|
1634
|
+
|
|
1635
|
+
// Store macro outputs in the main engine's cache for downstream propagation
|
|
1636
|
+
for (const [outputName, outputValue] of Object.entries(macroOutputs)) {
|
|
1637
|
+
this.cache.set({ node_id: node.id, port_name: outputName, value: outputValue });
|
|
1638
|
+
this.logDebug(`Stored macro output in cache: ${node.id}:${outputName}`, outputValue);
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
// Complete timeline entry
|
|
1642
|
+
const endDate = new Date();
|
|
1643
|
+
timelineEntry.endTime = endDate.toISOString();
|
|
1644
|
+
timelineEntry.durationMs = endDate - startDate;
|
|
1645
|
+
timelineEntry.status = 'completed';
|
|
1646
|
+
timelineEntry.outputs = JSON.parse(JSON.stringify(macroOutputs));
|
|
1647
|
+
|
|
1648
|
+
// Fire onNodeComplete event (matching the convention used in _executeNodeCore)
|
|
1649
|
+
if (this.config.onNodeComplete) {
|
|
1650
|
+
await this.config.onNodeComplete({
|
|
1651
|
+
nodeId: node.id,
|
|
1652
|
+
nodeType: node.type,
|
|
1653
|
+
timestamp: Date.now(),
|
|
1654
|
+
outputs: JSON.parse(JSON.stringify(macroOutputs)),
|
|
1655
|
+
durationMs: endDate - startDate
|
|
1656
|
+
});
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
// Track this execution state
|
|
1660
|
+
this.executedNodeStates.add(executionHash);
|
|
1661
|
+
|
|
1662
|
+
return macroOutputs;
|
|
1663
|
+
} catch (error) {
|
|
1664
|
+
// Update timeline entry with error
|
|
1665
|
+
const endDate = new Date();
|
|
1666
|
+
timelineEntry.endTime = endDate.toISOString();
|
|
1667
|
+
timelineEntry.durationMs = endDate - startDate;
|
|
1668
|
+
timelineEntry.status = 'error';
|
|
1669
|
+
timelineEntry.errorMessage = error.message;
|
|
1670
|
+
|
|
1671
|
+
// Fire onNodeError event (matching the convention used in _executeNodeCore)
|
|
1672
|
+
if (this.config.onNodeError) {
|
|
1673
|
+
await this.config.onNodeError({
|
|
1674
|
+
nodeId: node.id,
|
|
1675
|
+
nodeType: node.type,
|
|
1676
|
+
timestamp: Date.now(),
|
|
1677
|
+
error: error.message,
|
|
1678
|
+
durationMs: endDate - startDate
|
|
1679
|
+
});
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
throw error;
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
/**
|
|
1687
|
+
* Get input value for a specific node and input name
|
|
1688
|
+
* @param {Object} node - The node to get input for
|
|
1689
|
+
* @param {string} inputName - The name of the input
|
|
1690
|
+
* @returns {any} The input value or undefined
|
|
1691
|
+
*/
|
|
1692
|
+
getNodeInputValue(node, inputName) {
|
|
1693
|
+
// Find the link that connects to this input
|
|
1694
|
+
const link = this.flow.links.find(link =>
|
|
1695
|
+
link.to.node_id === node.id && link.to.port_name === inputName
|
|
1696
|
+
);
|
|
1697
|
+
|
|
1698
|
+
if (!link) {
|
|
1699
|
+
this.logDebug(`No link found for input ${inputName} on node ${node.id}`);
|
|
1700
|
+
return undefined;
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
const value = this.cache.get({ node_id: link.from.node_id, port_name: link.from.port_name });
|
|
1704
|
+
|
|
1705
|
+
this.logDebug(`Getting input value for ${node.id}.${inputName} from ${link.from.node_id}:${link.from.port_name}:`, value);
|
|
1706
|
+
|
|
1707
|
+
return value;
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
/**
|
|
1711
|
+
* Clean up the flow by removing invalid links
|
|
1712
|
+
* @private
|
|
1713
|
+
*/
|
|
1714
|
+
sanitizeFlow() {
|
|
1715
|
+
const nodeIds = new Set(this.flow.nodes.map(node => node.id));
|
|
1716
|
+
|
|
1717
|
+
// Filter out links that reference non-existent nodes
|
|
1718
|
+
const originalLength = this.flow.links.length;
|
|
1719
|
+
this.flow.links = this.flow.links.filter(link =>
|
|
1720
|
+
nodeIds.has(link.from.node_id) && nodeIds.has(link.to.node_id)
|
|
1721
|
+
);
|
|
1722
|
+
|
|
1723
|
+
const removedCount = originalLength - this.flow.links.length;
|
|
1724
|
+
if (removedCount > 0) {
|
|
1725
|
+
this.logDebug(`Removed ${removedCount} invalid link(s) referencing non-existent nodes`);
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
/**
|
|
1730
|
+
* Scan for plugin links and map LLM nodes to their plugin/tool nodes
|
|
1731
|
+
*/
|
|
1732
|
+
initializePluginMappings() {
|
|
1733
|
+
this.llmPlugins = {};
|
|
1734
|
+
for (const node of this.flow.nodes) {
|
|
1735
|
+
if (this.nodes[node.type]?.config?.accepts_plugins) {
|
|
1736
|
+
this.llmPlugins[node.id] = this.flow.links
|
|
1737
|
+
.filter(link => link.type === "plugin" && link.to.node_id === node.id)
|
|
1738
|
+
.map(link => link.from.node_id);
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
/**
|
|
1744
|
+
* Main entry for processing LLM nodes with plugins/tools
|
|
1745
|
+
*/
|
|
1746
|
+
async processLLMNode(node) {
|
|
1747
|
+
this.logDebug(`Processing LLM node [${node.id}] of type [${node.type}]`);
|
|
1748
|
+
|
|
1749
|
+
const nodeDefinition = this.nodes[node.type];
|
|
1750
|
+
if (!nodeDefinition) {
|
|
1751
|
+
this.logDebug(`Error: Node type "${node.type}" not found`);
|
|
1752
|
+
throw new Error(`Node type "${node.type}" not found. It may have been removed or renamed (e.g. a model retired from the provider). Check for an updated node type or regenerate the node catalog.`);
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
// Apply default settings from node configuration (same as processNode)
|
|
1756
|
+
if (!node.settings) {
|
|
1757
|
+
node.settings = {};
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
// Inherit default values for settings
|
|
1761
|
+
if (nodeDefinition.config.settings) {
|
|
1762
|
+
nodeDefinition.config.settings.forEach(settingDef => {
|
|
1763
|
+
if (settingDef.default !== undefined && (node.settings[settingDef.name] === undefined || node.settings[settingDef.name] === null || node.settings[settingDef.name] === '')) {
|
|
1764
|
+
this.logDebug(`Applying default value for setting [${settingDef.name}]: ${JSON.stringify(settingDef.default)}`);
|
|
1765
|
+
node.settings[settingDef.name] = settingDef.default;
|
|
1766
|
+
}
|
|
1767
|
+
});
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
// Collect inputs using the same logic as processNode (same as processNode)
|
|
1771
|
+
const inputs = {};
|
|
1772
|
+
this.logDebug(`Collecting inputs for LLM node [${node.id}]`);
|
|
1773
|
+
|
|
1774
|
+
// Get all links that connect to this node
|
|
1775
|
+
const nodeInputLinks = this.flow.links.filter((link) => link.to.node_id === node.id);
|
|
1776
|
+
|
|
1777
|
+
// First, identify which input ports have connections
|
|
1778
|
+
const connectedInputs = new Set();
|
|
1779
|
+
nodeInputLinks.forEach(link => {
|
|
1780
|
+
connectedInputs.add(link.to.port_name);
|
|
1781
|
+
});
|
|
1782
|
+
|
|
1783
|
+
// Process all connected inputs
|
|
1784
|
+
nodeInputLinks.forEach((link) => {
|
|
1785
|
+
this.logDebug(`Processing link from [${link.from.node_id}:${link.from.port_name}] to [${node.id}:${link.to.port_name}]`);
|
|
1786
|
+
|
|
1787
|
+
const key = `${link.from.node_id}:${link.from.port_name}`;
|
|
1788
|
+
const inputName = link.to.port_name;
|
|
1789
|
+
|
|
1790
|
+
const inputDef = nodeDefinition.config.inputs.find((i) => i.name === inputName);
|
|
1791
|
+
if (!inputDef) {
|
|
1792
|
+
this.logDebug(`Warning: No input definition found for port ${inputName}`);
|
|
1793
|
+
return;
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
this.logDebug(`Input definition for ${inputName}:`, inputDef);
|
|
1797
|
+
|
|
1798
|
+
if (inputDef.allow_multiple && inputDef.refires) {
|
|
1799
|
+
// Refiring input: Only collect NEW values (not yet consumed)
|
|
1800
|
+
const lastConsumed = CacheManager.getLastConsumed(node.settings, inputName);
|
|
1801
|
+
|
|
1802
|
+
// If never consumed before, get the latest value (for initial execution)
|
|
1803
|
+
if (lastConsumed === 0) {
|
|
1804
|
+
const latestValue = this.cache.get({ node_id: link.from.node_id, port_name: link.from.port_name });
|
|
1805
|
+
if (latestValue !== undefined) {
|
|
1806
|
+
inputs[inputName] = latestValue;
|
|
1807
|
+
this.logDebug(`LLM refiring input [${inputName}]: using initial value (never consumed):`, latestValue);
|
|
1808
|
+
}
|
|
1809
|
+
} else {
|
|
1810
|
+
// Get only new values since last consumption
|
|
1811
|
+
const newValues = this.cache.getNew({
|
|
1812
|
+
node_id: link.from.node_id,
|
|
1813
|
+
port_name: link.from.port_name,
|
|
1814
|
+
afterTimestamp: lastConsumed
|
|
1815
|
+
});
|
|
1816
|
+
|
|
1817
|
+
this.logDebug(`LLM refiring input [${inputName}]: found ${newValues.length} new values since ${lastConsumed}`);
|
|
1818
|
+
|
|
1819
|
+
if (newValues.length > 0) {
|
|
1820
|
+
// For refiring, use the first new value that triggered this execution
|
|
1821
|
+
inputs[inputName] = newValues[0];
|
|
1822
|
+
this.logDebug(`Setting LLM refiring input [${inputName}] to new value:`, newValues[0]);
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
} else if (inputDef.allow_multiple && !inputDef.refires) {
|
|
1826
|
+
// Non-refiring multiple input: Collect all current values
|
|
1827
|
+
const cachedValue = this.cache.get({ node_id: link.from.node_id, port_name: link.from.port_name });
|
|
1828
|
+
|
|
1829
|
+
if (this.cache.has({ node_id: link.from.node_id, port_name: link.from.port_name })) {
|
|
1830
|
+
if(!inputs[inputName]) {
|
|
1831
|
+
inputs[inputName] = [];
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
// Get the value and validate its type
|
|
1835
|
+
const value = cachedValue;
|
|
1836
|
+
const itemType = inputDef.type || "any";
|
|
1837
|
+
|
|
1838
|
+
// If the value is an array and doesn't pass type check as-is,
|
|
1839
|
+
// check if it's an array of the expected type and spread it in
|
|
1840
|
+
if (Array.isArray(value) && !this.typeCheck(value, itemType)) {
|
|
1841
|
+
let allValid = value.length > 0;
|
|
1842
|
+
for (const item of value) {
|
|
1843
|
+
if (!this.typeCheck(item, itemType)) {
|
|
1844
|
+
allValid = false;
|
|
1845
|
+
break;
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
if (allValid) {
|
|
1849
|
+
inputs[inputName].push(...value);
|
|
1850
|
+
this.logDebug(`Spread array of ${value.length} items into multiple-input [${inputName}]`);
|
|
1851
|
+
} else {
|
|
1852
|
+
this.logDebug(`Type mismatch for item in multiple-input [${inputName}]: Expected ${itemType}`);
|
|
1853
|
+
}
|
|
1854
|
+
} else if (this.typeCheck(value, itemType)) {
|
|
1855
|
+
inputs[inputName].push(value);
|
|
1856
|
+
} else {
|
|
1857
|
+
this.logDebug(`Type mismatch for item in multiple-input [${inputName}]: Expected ${itemType}`);
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
} else {
|
|
1861
|
+
// Single value input: Always use latest value
|
|
1862
|
+
const cachedValue = this.cache.get({ node_id: link.from.node_id, port_name: link.from.port_name });
|
|
1863
|
+
|
|
1864
|
+
if (this.cache.has({ node_id: link.from.node_id, port_name: link.from.port_name })) {
|
|
1865
|
+
this.logDebug(`Setting value for LLM input [${inputName}]`);
|
|
1866
|
+
inputs[inputName] = cachedValue;
|
|
1867
|
+
} else if (!inputDef.required && inputDef.default !== undefined) {
|
|
1868
|
+
// If optional and no link value, use the default
|
|
1869
|
+
inputs[inputName] = inputDef.default;
|
|
1870
|
+
this.logDebug(`Input [${inputName}] for LLM node [${node.id}] using default value:`, inputDef.default);
|
|
1871
|
+
} else {
|
|
1872
|
+
this.logDebug(`Warning: No value found for LLM input [${inputName}] and no default available`);
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
});
|
|
1876
|
+
|
|
1877
|
+
// Now handle unconnected inputs with default values
|
|
1878
|
+
nodeDefinition.config.inputs.forEach((inputDef) => {
|
|
1879
|
+
const inputName = inputDef.name;
|
|
1880
|
+
|
|
1881
|
+
// Skip inputs that already have values from connections
|
|
1882
|
+
if (connectedInputs.has(inputName) || inputs[inputName] !== undefined) {
|
|
1883
|
+
return;
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
// Apply default value for unconnected inputs if available
|
|
1887
|
+
if (inputDef.default !== undefined) {
|
|
1888
|
+
inputs[inputName] = inputDef.default;
|
|
1889
|
+
this.logDebug(`Applied default value for unconnected input [${inputName}]:`, inputDef.default);
|
|
1890
|
+
} else if (inputDef.required) {
|
|
1891
|
+
this.logDebug(`Warning: Required input [${inputName}] has no connection and no default value`);
|
|
1892
|
+
}
|
|
1893
|
+
});
|
|
1894
|
+
|
|
1895
|
+
// Debug: Log inputs before processing
|
|
1896
|
+
this.logDebug(`LLM Node [${node.id}] of type [${node.type}] inputs:`, JSON.stringify(inputs, null, 2));
|
|
1897
|
+
|
|
1898
|
+
// Validate inputs against node configuration (same as processNode)
|
|
1899
|
+
this.validateInputs(nodeDefinition.config, inputs);
|
|
1900
|
+
|
|
1901
|
+
// Create execution hash to check for duplicate processing
|
|
1902
|
+
const executionHash = this._createExecutionHash(node.id, inputs, node.settings || {});
|
|
1903
|
+
|
|
1904
|
+
// Skip if this exact execution state has already been processed
|
|
1905
|
+
if (this.executedNodeStates.has(executionHash)) {
|
|
1906
|
+
this.logDebug(`LLM Node [${node.id}] already executed with identical inputs/settings (hash: ${executionHash}), skipping`);
|
|
1907
|
+
return {}; // Return empty outputs to avoid downstream processing
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
// 1. Gather plugin/tool schemas and runners
|
|
1911
|
+
const toolSchemas = [];
|
|
1912
|
+
const toolRunners = {}; // toolName -> handler
|
|
1913
|
+
|
|
1914
|
+
// First, check if there are tools provided from config (parent context or developer-provided)
|
|
1915
|
+
if (this.config.tools && typeof this.config.tools === 'object' && !Array.isArray(this.config.tools)) {
|
|
1916
|
+
this.logDebug(`Found ${Object.keys(this.config.tools).length} tools from config`);
|
|
1917
|
+
for (const [toolName, toolDef] of Object.entries(this.config.tools)) {
|
|
1918
|
+
// If schema is provided, add it to the list
|
|
1919
|
+
if (toolDef.schema) {
|
|
1920
|
+
toolSchemas.push(toolDef.schema);
|
|
1921
|
+
this.logDebug(`Loaded tool schema from config: ${toolName}`);
|
|
1922
|
+
}
|
|
1923
|
+
// Always register the process function if provided
|
|
1924
|
+
if (toolDef.process) {
|
|
1925
|
+
toolRunners[toolName] = toolDef.process;
|
|
1926
|
+
this.logDebug(`Loaded tool runner from config: ${toolName}`);
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
// Then, discover local plugins (these can override or supplement parent tools)
|
|
1932
|
+
const pluginNodeIds = this.llmPlugins[node.id] || [];
|
|
1933
|
+
const toolNodeMap = {}; // Map tool name -> plugin node info for timeline/error tracking
|
|
1934
|
+
const manualToolNames = new Set(); // Track manual tools that have no runner
|
|
1935
|
+
|
|
1936
|
+
for (const pluginNodeId of pluginNodeIds) {
|
|
1937
|
+
const pluginNode = this.flow.nodes.find(n => n.id === pluginNodeId);
|
|
1938
|
+
if (!pluginNode) continue;
|
|
1939
|
+
|
|
1940
|
+
if (this.isLocalNodePlugin(pluginNode)) {
|
|
1941
|
+
const schema = this.generateToolSchema(pluginNode);
|
|
1942
|
+
toolSchemas.push(schema);
|
|
1943
|
+
|
|
1944
|
+
// Track the mapping from tool name to plugin node
|
|
1945
|
+
toolNodeMap[schema.name] = { node: pluginNode, type: 'plugin' };
|
|
1946
|
+
|
|
1947
|
+
const nodeDefinition = this.nodes[pluginNode.type];
|
|
1948
|
+
|
|
1949
|
+
// Special handling for macro nodes
|
|
1950
|
+
if (nodeDefinition?.config?.is_macro) {
|
|
1951
|
+
this.logDebug(`Node [${pluginNode.id}] is a macro plugin. Using processMacroNode.`);
|
|
1952
|
+
toolRunners[schema.name] = async (args) => {
|
|
1953
|
+
// Merge LLM args with static inputs
|
|
1954
|
+
const staticInputs = this.collectStaticInputs(pluginNode);
|
|
1955
|
+
const mergedInputs = { ...staticInputs, ...args };
|
|
1956
|
+
this.logDebug(`Merged inputs for macro plugin [${pluginNode.id}]:`, mergedInputs);
|
|
1957
|
+
|
|
1958
|
+
// For macros, we need to temporarily add the args to the cache
|
|
1959
|
+
const tempCacheKeys = [];
|
|
1960
|
+
|
|
1961
|
+
// Store args in cache as if they came from upstream nodes
|
|
1962
|
+
for (const [inputName, value] of Object.entries(mergedInputs)) {
|
|
1963
|
+
const tempNodeId = `__plugin_arg_${pluginNode.id}`;
|
|
1964
|
+
this.cache.set({ node_id: tempNodeId, port_name: inputName, value });
|
|
1965
|
+
tempCacheKeys.push({ node_id: tempNodeId, port_name: inputName });
|
|
1966
|
+
|
|
1967
|
+
// Add a temporary link so getNodeInputValue can find it
|
|
1968
|
+
const tempLink = {
|
|
1969
|
+
from: { node_id: tempNodeId, port_name: inputName },
|
|
1970
|
+
to: { node_id: pluginNode.id, port_name: inputName }
|
|
1971
|
+
};
|
|
1972
|
+
this.flow.links.push(tempLink);
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
try {
|
|
1976
|
+
// Execute the macro
|
|
1977
|
+
const outputs = await this.processMacroNode(pluginNode);
|
|
1978
|
+
|
|
1979
|
+
// Store outputs in cache for downstream propagation
|
|
1980
|
+
this.logDebug(`Storing macro plugin outputs in cache:`, outputs);
|
|
1981
|
+
for (const [key, value] of Object.entries(outputs)) {
|
|
1982
|
+
this.cache.set({ node_id: pluginNode.id, port_name: key, value });
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
// Propagate outputs downstream
|
|
1986
|
+
await this.propagate(pluginNode.id);
|
|
1987
|
+
|
|
1988
|
+
return outputs;
|
|
1989
|
+
} finally {
|
|
1990
|
+
// Clean up temporary cache entries and links
|
|
1991
|
+
tempCacheKeys.forEach(({ node_id, port_name }) =>
|
|
1992
|
+
this.cache.delete({ node_id, port_name })
|
|
1993
|
+
);
|
|
1994
|
+
this.flow.links = this.flow.links.filter(link =>
|
|
1995
|
+
!link.from.node_id.startsWith('__plugin_arg_')
|
|
1996
|
+
);
|
|
1997
|
+
}
|
|
1998
|
+
};
|
|
1999
|
+
}
|
|
2000
|
+
// Check if this is an imported node with chat inputs
|
|
2001
|
+
else if (nodeDefinition?.config?.is_import &&
|
|
2002
|
+
nodeDefinition.config.inputs?.some(input => input.is_chat_input)) {
|
|
2003
|
+
toolRunners[schema.name] = async (args) => {
|
|
2004
|
+
// Merge LLM args with static inputs
|
|
2005
|
+
const staticInputs = this.collectStaticInputs(pluginNode);
|
|
2006
|
+
const mergedInputs = { ...staticInputs, ...args };
|
|
2007
|
+
this.logDebug(`Merged inputs for chat plugin [${pluginNode.id}]:`, mergedInputs);
|
|
2008
|
+
|
|
2009
|
+
// Execute the plugin node
|
|
2010
|
+
const outputs = await this.processImportedChatNode(pluginNode, mergedInputs);
|
|
2011
|
+
|
|
2012
|
+
// Store outputs in cache for downstream propagation
|
|
2013
|
+
this.logDebug(`Storing plugin outputs in cache for downstream propagation:`, outputs);
|
|
2014
|
+
for (const [key, value] of Object.entries(outputs)) {
|
|
2015
|
+
this.cache.set({ node_id: pluginNode.id, port_name: key, value });
|
|
2016
|
+
}
|
|
2017
|
+
|
|
2018
|
+
// Propagate outputs downstream (if any downstream connections exist)
|
|
2019
|
+
await this.propagate(pluginNode.id);
|
|
2020
|
+
|
|
2021
|
+
return outputs;
|
|
2022
|
+
};
|
|
2023
|
+
} else {
|
|
2024
|
+
toolRunners[schema.name] = async (args) => {
|
|
2025
|
+
// Merge LLM args with static inputs
|
|
2026
|
+
const staticInputs = this.collectStaticInputs(pluginNode);
|
|
2027
|
+
const mergedInputs = { ...staticInputs, ...args };
|
|
2028
|
+
this.logDebug(`Merged inputs for plugin [${pluginNode.id}]:`, mergedInputs);
|
|
2029
|
+
|
|
2030
|
+
// Execute the plugin node
|
|
2031
|
+
const outputs = await this.processNodeWithArgs(pluginNode, mergedInputs);
|
|
2032
|
+
|
|
2033
|
+
// Store outputs in cache for downstream propagation
|
|
2034
|
+
this.logDebug(`Storing plugin outputs in cache for downstream propagation:`, outputs);
|
|
2035
|
+
for (const [key, value] of Object.entries(outputs)) {
|
|
2036
|
+
this.cache.set({ node_id: pluginNode.id, port_name: key, value });
|
|
2037
|
+
}
|
|
2038
|
+
|
|
2039
|
+
// Propagate outputs downstream (if any downstream connections exist)
|
|
2040
|
+
await this.propagate(pluginNode.id);
|
|
2041
|
+
|
|
2042
|
+
return outputs;
|
|
2043
|
+
};
|
|
2044
|
+
}
|
|
2045
|
+
} else if (isRemoteMCPTool(pluginNode)) {
|
|
2046
|
+
// Resolve named MCP integration from config.keys.mcp
|
|
2047
|
+
const integrationName = pluginNode.settings?.mcp_integration;
|
|
2048
|
+
if (!integrationName) {
|
|
2049
|
+
this.logDebug(`MCP plugin node [${pluginNode.id}] has no mcp_integration setting`);
|
|
2050
|
+
continue;
|
|
2051
|
+
}
|
|
2052
|
+
const mcpConfig = this.config.keys?.mcp?.[integrationName];
|
|
2053
|
+
if (!mcpConfig?.url) {
|
|
2054
|
+
this.logDebug(`MCP integration "${integrationName}" not found in config.keys.mcp`);
|
|
2055
|
+
continue;
|
|
2056
|
+
}
|
|
2057
|
+
const { url, token } = mcpConfig;
|
|
2058
|
+
try {
|
|
2059
|
+
const tools = await fetchMCPTools(url, token);
|
|
2060
|
+
for (const tool of tools) {
|
|
2061
|
+
toolSchemas.push(tool);
|
|
2062
|
+
toolNodeMap[tool.name] = { node: pluginNode, type: 'mcp', mcpToolName: tool.name };
|
|
2063
|
+
toolRunners[tool.name] = async (args) => {
|
|
2064
|
+
return await callMCPTool({ ...args, name: tool.name }, { url, token });
|
|
2065
|
+
};
|
|
2066
|
+
}
|
|
2067
|
+
this.logDebug(`Loaded ${tools.length} MCP tools from "${integrationName}"`);
|
|
2068
|
+
} catch (err) {
|
|
2069
|
+
this.logDebug(`Failed to fetch MCP tools from "${integrationName}" (${url}): ${err.message}`);
|
|
2070
|
+
}
|
|
2071
|
+
} else if (isManualToolNode(pluginNode)) {
|
|
2072
|
+
const schema = this.generateToolSchema(pluginNode);
|
|
2073
|
+
toolSchemas.push(schema);
|
|
2074
|
+
// Manual tools: no runner, just pass through - track them so we don't error
|
|
2075
|
+
manualToolNames.add(schema.name);
|
|
2076
|
+
toolNodeMap[schema.name] = { node: pluginNode, type: 'manual' };
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2080
|
+
// --- Also gather tool schemas connected to the LLM's 'tools' input port ---
|
|
2081
|
+
const toolInputLinks = this.flow.links.filter(
|
|
2082
|
+
link => link.to.node_id === node.id && link.to.port_name === "tools"
|
|
2083
|
+
);
|
|
2084
|
+
for (const link of toolInputLinks) {
|
|
2085
|
+
const cachedValue = this.cache.get({ node_id: link.from.node_id, port_name: link.from.port_name });
|
|
2086
|
+
if (!cachedValue) continue;
|
|
2087
|
+
|
|
2088
|
+
// Normalize to an array — could be a single tool or an array of tools
|
|
2089
|
+
const schemas = Array.isArray(cachedValue) ? cachedValue : [cachedValue];
|
|
2090
|
+
for (const toolSchema of schemas) {
|
|
2091
|
+
if (!toolSchema || typeof toolSchema !== 'object' || !toolSchema.name) continue;
|
|
2092
|
+
// Only add schema if developer hasn't already provided one via config
|
|
2093
|
+
const configTool = this.config.tools?.[toolSchema.name];
|
|
2094
|
+
if (!configTool || !configTool.schema) {
|
|
2095
|
+
toolSchemas.push(toolSchema);
|
|
2096
|
+
this.logDebug(`Loaded tool schema from flow: ${toolSchema.name}`);
|
|
2097
|
+
// If there's no runner for this tool, track it as a manual tool
|
|
2098
|
+
if (!configTool?.process && !toolRunners[toolSchema.name]) {
|
|
2099
|
+
manualToolNames.add(toolSchema.name);
|
|
2100
|
+
const sourceNode = this.flow.nodes.find(n => n.id === link.from.node_id);
|
|
2101
|
+
toolNodeMap[toolSchema.name] = { node: sourceNode, type: 'manual' };
|
|
2102
|
+
}
|
|
2103
|
+
} else {
|
|
2104
|
+
this.logDebug(`Skipping flow schema for ${toolSchema.name} - using config schema instead`);
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
|
|
2109
|
+
// Extract internal tool names (tools handled by engine plugins, not manual/external tools)
|
|
2110
|
+
// This is used by LLM nodes to filter the conversation output to only include
|
|
2111
|
+
// messages related to internally-handled tools
|
|
2112
|
+
const internalToolNames = Object.entries(toolNodeMap)
|
|
2113
|
+
.filter(([_, info]) => info.type === 'plugin' || info.type === 'mcp')
|
|
2114
|
+
.map(([name, _]) => name);
|
|
2115
|
+
this.config.internal_tool_names = internalToolNames;
|
|
2116
|
+
this.logDebug(`Internal tool names for LLM node [${node.id}]:`, internalToolNames);
|
|
2117
|
+
|
|
2118
|
+
// 2. Inject toolSchemas into LLM call
|
|
2119
|
+
let llmResult;
|
|
2120
|
+
let tool_results = [];
|
|
2121
|
+
let toolCallMessage = null;
|
|
2122
|
+
let toolCallCount = 0;
|
|
2123
|
+
|
|
2124
|
+
// Normalize string/object messages to array format before accumulation
|
|
2125
|
+
if (typeof inputs.messages === 'string') {
|
|
2126
|
+
inputs.messages = [{ role: 'user', content: inputs.messages }];
|
|
2127
|
+
} else if (inputs.messages && typeof inputs.messages === 'object' && !Array.isArray(inputs.messages)) {
|
|
2128
|
+
inputs.messages = [inputs.messages];
|
|
2129
|
+
}
|
|
2130
|
+
|
|
2131
|
+
// Track accumulated messages across tool call rounds
|
|
2132
|
+
// Start with the original messages, then append tool calls/results each round
|
|
2133
|
+
let accumulatedMessages = Array.isArray(inputs.messages) ? [...inputs.messages] : [];
|
|
2134
|
+
|
|
2135
|
+
do {
|
|
2136
|
+
// Create inputs with accumulated messages for this round
|
|
2137
|
+
const roundInputs = { ...inputs, messages: accumulatedMessages };
|
|
2138
|
+
llmResult = await this.callLLMWithTools(node, roundInputs, toolSchemas, toolCallMessage, tool_results);
|
|
2139
|
+
|
|
2140
|
+
// After the LLM call, if there were tool calls/results, they've been appended
|
|
2141
|
+
// Update accumulatedMessages to include what was sent this round
|
|
2142
|
+
if (toolCallMessage && tool_results.length > 0) {
|
|
2143
|
+
accumulatedMessages = [...accumulatedMessages, toolCallMessage];
|
|
2144
|
+
for (const toolResult of tool_results) {
|
|
2145
|
+
accumulatedMessages.push({
|
|
2146
|
+
role: "tool",
|
|
2147
|
+
tool_call_id: toolResult.tool_call_id,
|
|
2148
|
+
name: toolResult.name,
|
|
2149
|
+
content: typeof toolResult.result === "string"
|
|
2150
|
+
? toolResult.result
|
|
2151
|
+
: JSON.stringify(toolResult.result)
|
|
2152
|
+
});
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
|
|
2156
|
+
tool_results = [];
|
|
2157
|
+
toolCallMessage = null;
|
|
2158
|
+
|
|
2159
|
+
if (llmResult.tool_calls && llmResult.tool_calls.length > 0) {
|
|
2160
|
+
|
|
2161
|
+
for (const tool_call of llmResult.tool_calls) {
|
|
2162
|
+
|
|
2163
|
+
if (tool_call.type === 'function' && tool_call.function) {
|
|
2164
|
+
const toolName = tool_call.function.name;
|
|
2165
|
+
|
|
2166
|
+
// Check if this is a manual tool (no runner, pass through)
|
|
2167
|
+
if (manualToolNames.has(toolName)) {
|
|
2168
|
+
this.logDebug(`Manual tool called: ${toolName} - passing through without execution`);
|
|
2169
|
+
// Don't push any result - the tool_calls will pass through on the assistant message
|
|
2170
|
+
continue;
|
|
2171
|
+
}
|
|
2172
|
+
|
|
2173
|
+
if (toolRunners[toolName]) {
|
|
2174
|
+
const toolNodeInfo = toolNodeMap[toolName];
|
|
2175
|
+
const startDate = new Date();
|
|
2176
|
+
|
|
2177
|
+
// Try to parse the arguments as a JSON object
|
|
2178
|
+
let toolArguments = tool_call.function.arguments;
|
|
2179
|
+
try {
|
|
2180
|
+
toolArguments = JSON.parse(tool_call.function.arguments);
|
|
2181
|
+
} catch (parseError) {
|
|
2182
|
+
// JSON parse failed - create error result
|
|
2183
|
+
this.logDebug(`Failed to parse tool arguments for ${toolName}:`, parseError.message);
|
|
2184
|
+
|
|
2185
|
+
// Create timeline entry for the parse error
|
|
2186
|
+
if (toolNodeInfo?.node) {
|
|
2187
|
+
const endDate = new Date();
|
|
2188
|
+
const timelineEntry = {
|
|
2189
|
+
nodeId: toolNodeInfo.node.id,
|
|
2190
|
+
nodeType: toolNodeInfo.node.type,
|
|
2191
|
+
inputs: { raw_arguments: tool_call.function.arguments },
|
|
2192
|
+
startTime: startDate.toISOString(),
|
|
2193
|
+
endTime: endDate.toISOString(),
|
|
2194
|
+
durationMs: endDate - startDate,
|
|
2195
|
+
status: 'error',
|
|
2196
|
+
errorMessage: `Invalid JSON arguments: ${parseError.message}`
|
|
2197
|
+
};
|
|
2198
|
+
this.timeline.push(timelineEntry);
|
|
2199
|
+
|
|
2200
|
+
// Fire onNodeError event
|
|
2201
|
+
if (this.config.onNodeError) {
|
|
2202
|
+
await this.config.onNodeError({
|
|
2203
|
+
nodeId: toolNodeInfo.node.id,
|
|
2204
|
+
nodeType: toolNodeInfo.node.type,
|
|
2205
|
+
error: parseError
|
|
2206
|
+
});
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
|
|
2210
|
+
// Push error result to LLM
|
|
2211
|
+
tool_results.push({
|
|
2212
|
+
original_tool_call: tool_call,
|
|
2213
|
+
tool_call_id: tool_call.id,
|
|
2214
|
+
name: toolName,
|
|
2215
|
+
result: {
|
|
2216
|
+
error: true,
|
|
2217
|
+
message: `Invalid arguments provided: ${parseError.message}`
|
|
2218
|
+
}
|
|
2219
|
+
});
|
|
2220
|
+
continue;
|
|
2221
|
+
}
|
|
2222
|
+
|
|
2223
|
+
// Fire onNodeStart so the consumer's live-trace sink
|
|
2224
|
+
// sees this tool call as it dispatches. Without this,
|
|
2225
|
+
// multi-round tool-calling LLMs look like a single
|
|
2226
|
+
// long "LLM node running…" entry — the waterfall has
|
|
2227
|
+
// no visibility into the tool-call cycles inside.
|
|
2228
|
+
if (toolNodeInfo?.node && this.config.onNodeStart) {
|
|
2229
|
+
try {
|
|
2230
|
+
await this.config.onNodeStart({
|
|
2231
|
+
nodeId: toolNodeInfo.node.id,
|
|
2232
|
+
nodeType: toolNodeInfo.node.type,
|
|
2233
|
+
inputs: toolArguments,
|
|
2234
|
+
});
|
|
2235
|
+
} catch (_hookErr) {
|
|
2236
|
+
// Hook failures must not break the flow — swallow.
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2239
|
+
|
|
2240
|
+
// Execute the tool runner with error handling
|
|
2241
|
+
try {
|
|
2242
|
+
const toolResult = await toolRunners[toolName](toolArguments);
|
|
2243
|
+
|
|
2244
|
+
// Success - push result
|
|
2245
|
+
tool_results.push({
|
|
2246
|
+
original_tool_call: tool_call,
|
|
2247
|
+
tool_call_id: tool_call.id,
|
|
2248
|
+
name: toolName,
|
|
2249
|
+
result: toolResult
|
|
2250
|
+
});
|
|
2251
|
+
|
|
2252
|
+
// Mirror the error-path instrumentation: record a
|
|
2253
|
+
// timeline entry + onNodeComplete event for the
|
|
2254
|
+
// successful tool call. Consumers' post-run timeline
|
|
2255
|
+
// walk + live trace stream both see the call now.
|
|
2256
|
+
if (toolNodeInfo?.node) {
|
|
2257
|
+
const endDate = new Date();
|
|
2258
|
+
const timelineEntry = {
|
|
2259
|
+
nodeId: toolNodeInfo.node.id,
|
|
2260
|
+
nodeType: toolNodeInfo.node.type,
|
|
2261
|
+
inputs: toolArguments,
|
|
2262
|
+
outputs: { result: toolResult },
|
|
2263
|
+
startTime: startDate.toISOString(),
|
|
2264
|
+
endTime: endDate.toISOString(),
|
|
2265
|
+
durationMs: endDate - startDate,
|
|
2266
|
+
status: 'success',
|
|
2267
|
+
};
|
|
2268
|
+
this.timeline.push(timelineEntry);
|
|
2269
|
+
|
|
2270
|
+
if (this.config.onNodeComplete) {
|
|
2271
|
+
try {
|
|
2272
|
+
await this.config.onNodeComplete({
|
|
2273
|
+
nodeId: toolNodeInfo.node.id,
|
|
2274
|
+
nodeType: toolNodeInfo.node.type,
|
|
2275
|
+
inputs: toolArguments,
|
|
2276
|
+
outputs: { result: toolResult },
|
|
2277
|
+
});
|
|
2278
|
+
} catch (_hookErr) {
|
|
2279
|
+
// Swallow — hook errors must not break the flow.
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
|
|
2284
|
+
} catch (executionError) {
|
|
2285
|
+
// Tool execution failed - create error result
|
|
2286
|
+
this.logDebug(`Tool execution failed for ${toolName}:`, executionError.message);
|
|
2287
|
+
|
|
2288
|
+
// Create timeline entry for the execution error
|
|
2289
|
+
if (toolNodeInfo?.node) {
|
|
2290
|
+
const endDate = new Date();
|
|
2291
|
+
const timelineEntry = {
|
|
2292
|
+
nodeId: toolNodeInfo.node.id,
|
|
2293
|
+
nodeType: toolNodeInfo.node.type,
|
|
2294
|
+
inputs: toolArguments,
|
|
2295
|
+
startTime: startDate.toISOString(),
|
|
2296
|
+
endTime: endDate.toISOString(),
|
|
2297
|
+
durationMs: endDate - startDate,
|
|
2298
|
+
status: 'error',
|
|
2299
|
+
errorMessage: executionError.message
|
|
2300
|
+
};
|
|
2301
|
+
this.timeline.push(timelineEntry);
|
|
2302
|
+
|
|
2303
|
+
// Fire onNodeError event
|
|
2304
|
+
if (this.config.onNodeError) {
|
|
2305
|
+
await this.config.onNodeError({
|
|
2306
|
+
nodeId: toolNodeInfo.node.id,
|
|
2307
|
+
nodeType: toolNodeInfo.node.type,
|
|
2308
|
+
error: executionError
|
|
2309
|
+
});
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
|
|
2313
|
+
// Push error result to LLM
|
|
2314
|
+
tool_results.push({
|
|
2315
|
+
original_tool_call: tool_call,
|
|
2316
|
+
tool_call_id: tool_call.id,
|
|
2317
|
+
name: toolName,
|
|
2318
|
+
result: {
|
|
2319
|
+
error: true,
|
|
2320
|
+
message: executionError.message
|
|
2321
|
+
}
|
|
2322
|
+
});
|
|
2323
|
+
}
|
|
2324
|
+
} else {
|
|
2325
|
+
// No runner found and not a manual tool - this is an error
|
|
2326
|
+
this.logDebug(`No runner found for tool: ${toolName}`);
|
|
2327
|
+
|
|
2328
|
+
tool_results.push({
|
|
2329
|
+
original_tool_call: tool_call,
|
|
2330
|
+
tool_call_id: tool_call.id,
|
|
2331
|
+
name: toolName,
|
|
2332
|
+
result: {
|
|
2333
|
+
error: true,
|
|
2334
|
+
message: `Tool "${toolName}" is not available`
|
|
2335
|
+
}
|
|
2336
|
+
});
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
// Prepare the tool call message for the next LLM call
|
|
2341
|
+
toolCallMessage = {
|
|
2342
|
+
role: "assistant",
|
|
2343
|
+
content: null,
|
|
2344
|
+
tool_calls: llmResult.tool_calls
|
|
2345
|
+
};
|
|
2346
|
+
toolCallCount++;
|
|
2347
|
+
} else {
|
|
2348
|
+
break; // No more tool calls, exit loop
|
|
2349
|
+
}
|
|
2350
|
+
} while (tool_results.length > 0 && toolCallCount < this.maxPluginCalls);
|
|
2351
|
+
|
|
2352
|
+
// 4. Continue with normal LLM output processing
|
|
2353
|
+
// Store outputs in the cache and propagate downstream
|
|
2354
|
+
for (const output of nodeDefinition.config.outputs || []) {
|
|
2355
|
+
if (llmResult[output.name] !== undefined) {
|
|
2356
|
+
this.cache.set({ node_id: node.id, port_name: output.name, value: llmResult[output.name] });
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2359
|
+
|
|
2360
|
+
// Debug: Log outputs after processing
|
|
2361
|
+
this.logDebug(`LLM Node [${node.id}] outputs:`, JSON.stringify(llmResult, null, 2));
|
|
2362
|
+
|
|
2363
|
+
// Handle updated settings
|
|
2364
|
+
if (llmResult.__updated_settings) {
|
|
2365
|
+
node.settings = {
|
|
2366
|
+
...node.settings,
|
|
2367
|
+
...llmResult.__updated_settings,
|
|
2368
|
+
};
|
|
2369
|
+
this.logDebug(`LLM Node [${node.id}] updated settings:`, JSON.stringify(node.settings, null, 2));
|
|
2370
|
+
delete llmResult.__updated_settings; // Remove from regular outputs
|
|
2371
|
+
}
|
|
2372
|
+
|
|
2373
|
+
// Track this execution state
|
|
2374
|
+
this.executedNodeStates.add(executionHash);
|
|
2375
|
+
|
|
2376
|
+
// Clean up internal_tool_names from config
|
|
2377
|
+
delete this.config.internal_tool_names;
|
|
2378
|
+
|
|
2379
|
+
this.logDebug(`LLM Node [${node.id}] processing completed successfully`);
|
|
2380
|
+
return llmResult;
|
|
2381
|
+
}
|
|
2382
|
+
|
|
2383
|
+
/**
|
|
2384
|
+
* Execute a plugin node in the parent context with all its dependencies
|
|
2385
|
+
* This allows plugins to be called from internal engines (macros/imports)
|
|
2386
|
+
* while maintaining their connections in the parent flow
|
|
2387
|
+
* @param {Object} pluginNode - The plugin node to execute
|
|
2388
|
+
* @param {Object} args - Arguments from the tool call
|
|
2389
|
+
* @returns {Object} The outputs from the plugin
|
|
2390
|
+
*/
|
|
2391
|
+
async executePluginInParentContext(pluginNode, args) {
|
|
2392
|
+
this.logDebug(`Executing plugin [${pluginNode.id}] in parent context with args:`, args);
|
|
2393
|
+
|
|
2394
|
+
const nodeDefinition = this.nodes[pluginNode.type];
|
|
2395
|
+
if (!nodeDefinition) {
|
|
2396
|
+
this.logDebug(`Error: Node type "${pluginNode.type}" not found`);
|
|
2397
|
+
throw new Error(`Node type "${pluginNode.type}" not found.`);
|
|
2398
|
+
}
|
|
2399
|
+
|
|
2400
|
+
// Check if this is a macro or import node (needs special handling)
|
|
2401
|
+
if (nodeDefinition.config.is_macro) {
|
|
2402
|
+
this.logDebug(`Plugin [${pluginNode.id}] is a macro, using processMacroNode`);
|
|
2403
|
+
|
|
2404
|
+
// For macros called as plugins, we need to temporarily add the args to the cache
|
|
2405
|
+
// so the macro can access them via getNodeInputValue
|
|
2406
|
+
const tempCacheKeys = [];
|
|
2407
|
+
|
|
2408
|
+
// Store args in cache as if they came from upstream nodes
|
|
2409
|
+
for (const [inputName, value] of Object.entries(args)) {
|
|
2410
|
+
const tempNodeId = `__plugin_arg_${pluginNode.id}`;
|
|
2411
|
+
this.cache.set({ node_id: tempNodeId, port_name: inputName, value });
|
|
2412
|
+
tempCacheKeys.push({ node_id: tempNodeId, port_name: inputName });
|
|
2413
|
+
|
|
2414
|
+
// Also add a temporary link so getNodeInputValue can find it
|
|
2415
|
+
const tempLink = {
|
|
2416
|
+
from: { node_id: tempNodeId, port_name: inputName },
|
|
2417
|
+
to: { node_id: pluginNode.id, port_name: inputName }
|
|
2418
|
+
};
|
|
2419
|
+
this.flow.links.push(tempLink);
|
|
2420
|
+
}
|
|
2421
|
+
|
|
2422
|
+
try {
|
|
2423
|
+
// Execute the macro
|
|
2424
|
+
const outputs = await this.processMacroNode(pluginNode);
|
|
2425
|
+
return outputs;
|
|
2426
|
+
} finally {
|
|
2427
|
+
// Always clean up temporary cache entries and links, even on error
|
|
2428
|
+
tempCacheKeys.forEach(({ node_id, port_name }) =>
|
|
2429
|
+
this.cache.delete({ node_id, port_name })
|
|
2430
|
+
);
|
|
2431
|
+
this.flow.links = this.flow.links.filter(link =>
|
|
2432
|
+
!link.from.node_id.startsWith('__plugin_arg_')
|
|
2433
|
+
);
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
2436
|
+
|
|
2437
|
+
// For regular nodes and imports
|
|
2438
|
+
// Collect static inputs (from parent flow connections)
|
|
2439
|
+
const staticInputs = this.collectStaticInputs(pluginNode);
|
|
2440
|
+
|
|
2441
|
+
// Merge static inputs with LLM-provided args
|
|
2442
|
+
const mergedInputs = { ...staticInputs, ...args };
|
|
2443
|
+
|
|
2444
|
+
this.logDebug(`Merged inputs for plugin [${pluginNode.id}]:`, mergedInputs);
|
|
2445
|
+
|
|
2446
|
+
// Execute the plugin node with merged inputs
|
|
2447
|
+
const outputs = await this.processNodeWithArgs(pluginNode, mergedInputs);
|
|
2448
|
+
|
|
2449
|
+
// Store outputs in parent cache for downstream propagation
|
|
2450
|
+
this.logDebug(`Storing plugin outputs in parent cache:`, outputs);
|
|
2451
|
+
for (const [key, value] of Object.entries(outputs)) {
|
|
2452
|
+
this.cache.set({ node_id: pluginNode.id, port_name: key, value });
|
|
2453
|
+
}
|
|
2454
|
+
|
|
2455
|
+
// Propagate outputs downstream in parent context
|
|
2456
|
+
await this.propagate(pluginNode.id);
|
|
2457
|
+
|
|
2458
|
+
return outputs;
|
|
2459
|
+
}
|
|
2460
|
+
|
|
2461
|
+
/**
|
|
2462
|
+
* Check if a node is a local plugin (is_plugin or is_macro)
|
|
2463
|
+
* @param {Object} node - The node to check
|
|
2464
|
+
* @returns {boolean} True if the node is a plugin or macro
|
|
2465
|
+
*/
|
|
2466
|
+
isLocalNodePlugin(node) {
|
|
2467
|
+
const thisNodeConfig = this.nodes[node.type]?.config || {};
|
|
2468
|
+
return thisNodeConfig.is_plugin || thisNodeConfig.is_macro;
|
|
2469
|
+
}
|
|
2470
|
+
|
|
2471
|
+
/**
|
|
2472
|
+
* Check if a node has all required inputs available in the cache
|
|
2473
|
+
* @param {Object} node - The node to check
|
|
2474
|
+
* @returns {boolean} True if all required inputs are available
|
|
2475
|
+
* @throws {Error} If the node type is not found
|
|
2476
|
+
*/
|
|
2477
|
+
isNodeReady(node) {
|
|
2478
|
+
const nodeDefinition = this.nodes[node.type];
|
|
2479
|
+
if (!nodeDefinition) {
|
|
2480
|
+
this.logDebug(`Error: Node type "${node.type}" not found`);
|
|
2481
|
+
throw new Error(`Node type "${node.type}" not found. It may have been removed or renamed (e.g. a model retired from the provider). Check for an updated node type or regenerate the node catalog.`);
|
|
2482
|
+
}
|
|
2483
|
+
for (const inputDef of nodeDefinition.config.inputs || []) {
|
|
2484
|
+
if (!inputDef.required) continue;
|
|
2485
|
+
const inputName = inputDef.name;
|
|
2486
|
+
const incomingLink = this.flow.links.find(
|
|
2487
|
+
link => link.to.node_id === node.id && link.to.port_name === inputName
|
|
2488
|
+
);
|
|
2489
|
+
if (incomingLink) {
|
|
2490
|
+
if (!this.cache.has({ node_id: incomingLink.from.node_id, port_name: incomingLink.from.port_name })) {
|
|
2491
|
+
return false;
|
|
2492
|
+
}
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
2495
|
+
return true;
|
|
2496
|
+
}
|
|
2497
|
+
|
|
2498
|
+
/**
|
|
2499
|
+
* Collect statically connected inputs for a plugin node
|
|
2500
|
+
* @param {Object} node - The plugin node to collect inputs for
|
|
2501
|
+
* @returns {Object} The statically connected inputs
|
|
2502
|
+
*/
|
|
2503
|
+
collectStaticInputs(node) {
|
|
2504
|
+
const config = this.nodes[node.type]?.config || {};
|
|
2505
|
+
const staticInputs = {};
|
|
2506
|
+
|
|
2507
|
+
// Find all statically connected inputs (non-plugin links)
|
|
2508
|
+
const staticLinks = this.flow.links.filter(link =>
|
|
2509
|
+
link.to.node_id === node.id && link.type !== "plugin"
|
|
2510
|
+
);
|
|
2511
|
+
|
|
2512
|
+
this.logDebug(`Collecting static inputs for plugin node [${node.id}]:`, staticLinks.map(l => `${l.from.node_id}:${l.from.port_name} -> ${l.to.port_name}`));
|
|
2513
|
+
|
|
2514
|
+
staticLinks.forEach(link => {
|
|
2515
|
+
const inputName = link.to.port_name;
|
|
2516
|
+
const inputDef = config.inputs?.find(input => input.name === inputName);
|
|
2517
|
+
|
|
2518
|
+
if (!inputDef) {
|
|
2519
|
+
this.logDebug(`Warning: No input definition found for static input ${inputName}`);
|
|
2520
|
+
return;
|
|
2521
|
+
}
|
|
2522
|
+
|
|
2523
|
+
const value = this.cache.get({ node_id: link.from.node_id, port_name: link.from.port_name });
|
|
2524
|
+
|
|
2525
|
+
if (value !== undefined) {
|
|
2526
|
+
if (inputDef.allow_multiple) {
|
|
2527
|
+
// Initialize or reset the array for this run
|
|
2528
|
+
if (!staticInputs[inputName]) {
|
|
2529
|
+
staticInputs[inputName] = [];
|
|
2530
|
+
}
|
|
2531
|
+
|
|
2532
|
+
// Get the value and validate its type
|
|
2533
|
+
const itemType = inputDef.type || "any";
|
|
2534
|
+
|
|
2535
|
+
// Validate individual item type
|
|
2536
|
+
if (this.typeCheck(value, itemType)) {
|
|
2537
|
+
staticInputs[inputName].push(value);
|
|
2538
|
+
} else {
|
|
2539
|
+
this.logDebug(`Type mismatch for static input [${inputName}]: Expected ${itemType}`);
|
|
2540
|
+
}
|
|
2541
|
+
} else {
|
|
2542
|
+
staticInputs[inputName] = value;
|
|
2543
|
+
}
|
|
2544
|
+
|
|
2545
|
+
this.logDebug(`Collected static input [${inputName}]:`, value);
|
|
2546
|
+
} else {
|
|
2547
|
+
this.logDebug(`Warning: No value found for static input [${inputName}]`);
|
|
2548
|
+
}
|
|
2549
|
+
});
|
|
2550
|
+
|
|
2551
|
+
// Handle unconnected inputs with default values
|
|
2552
|
+
config.inputs?.forEach(inputDef => {
|
|
2553
|
+
const inputName = inputDef.name;
|
|
2554
|
+
|
|
2555
|
+
// Skip if already has a value from static connections
|
|
2556
|
+
if (staticInputs[inputName] !== undefined) {
|
|
2557
|
+
return;
|
|
2558
|
+
}
|
|
2559
|
+
|
|
2560
|
+
// Apply default value for unconnected inputs if available
|
|
2561
|
+
if (inputDef.default !== undefined) {
|
|
2562
|
+
staticInputs[inputName] = inputDef.default;
|
|
2563
|
+
this.logDebug(`Applied default value for static input [${inputName}]:`, inputDef.default);
|
|
2564
|
+
}
|
|
2565
|
+
});
|
|
2566
|
+
|
|
2567
|
+
return staticInputs;
|
|
2568
|
+
}
|
|
2569
|
+
|
|
2570
|
+
generateToolSchema(node) {
|
|
2571
|
+
// Get the node config and settings
|
|
2572
|
+
const config = this.nodes[node.type]?.config || {};
|
|
2573
|
+
const settings = node.settings || {};
|
|
2574
|
+
|
|
2575
|
+
// Use the node's custom display name name/description if present, otherwise fall back to config
|
|
2576
|
+
let name = node.display_name || config.display_name || node.type;
|
|
2577
|
+
name = createSafeToolName(name);
|
|
2578
|
+
|
|
2579
|
+
const description = node.description || config.description || "";
|
|
2580
|
+
|
|
2581
|
+
// Find which inputs are statically connected (not available to LLM)
|
|
2582
|
+
const staticallyConnectedInputs = new Set();
|
|
2583
|
+
this.flow.links.forEach(link => {
|
|
2584
|
+
if (link.to.node_id === node.id && link.type !== "plugin") {
|
|
2585
|
+
staticallyConnectedInputs.add(link.to.port_name);
|
|
2586
|
+
}
|
|
2587
|
+
});
|
|
2588
|
+
|
|
2589
|
+
// Build JSON Schema properties for each input that is NOT statically connected
|
|
2590
|
+
const properties = {};
|
|
2591
|
+
const required = [];
|
|
2592
|
+
(config.inputs || []).forEach(input => {
|
|
2593
|
+
// Skip inputs that are statically connected - LLM shouldn't see these
|
|
2594
|
+
if (staticallyConnectedInputs.has(input.name)) {
|
|
2595
|
+
return;
|
|
2596
|
+
}
|
|
2597
|
+
|
|
2598
|
+
if(config.is_import){
|
|
2599
|
+
if(input.is_data_input){
|
|
2600
|
+
properties[input.name] = {
|
|
2601
|
+
type: input.type || "object",
|
|
2602
|
+
description: input.description || "",
|
|
2603
|
+
};
|
|
2604
|
+
} else if(input.is_chat_input){
|
|
2605
|
+
properties[input.name] = {
|
|
2606
|
+
type: "string",
|
|
2607
|
+
description: "A conversational chat message to send to this agent."
|
|
2608
|
+
};
|
|
2609
|
+
} else if(input.is_prompt_input){
|
|
2610
|
+
properties[input.name] = {
|
|
2611
|
+
type: "string",
|
|
2612
|
+
description: input.description || "",
|
|
2613
|
+
};
|
|
2614
|
+
}
|
|
2615
|
+
if (input.required) {
|
|
2616
|
+
required.push(input.name);
|
|
2617
|
+
}
|
|
2618
|
+
} else {
|
|
2619
|
+
properties[input.name] = {
|
|
2620
|
+
type: mapTypeToJSONSchema(input.type),
|
|
2621
|
+
description: input.description || "",
|
|
2622
|
+
};
|
|
2623
|
+
if (input.default !== undefined) {
|
|
2624
|
+
properties[input.name].default = input.default;
|
|
2625
|
+
}
|
|
2626
|
+
if (input.required) {
|
|
2627
|
+
required.push(input.name);
|
|
2628
|
+
}
|
|
2629
|
+
}
|
|
2630
|
+
});
|
|
2631
|
+
|
|
2632
|
+
|
|
2633
|
+
// Return the tool schema object
|
|
2634
|
+
return {
|
|
2635
|
+
name,
|
|
2636
|
+
description,
|
|
2637
|
+
parameters: {
|
|
2638
|
+
type: "object",
|
|
2639
|
+
properties,
|
|
2640
|
+
required,
|
|
2641
|
+
}
|
|
2642
|
+
};
|
|
2643
|
+
}
|
|
2644
|
+
|
|
2645
|
+
|
|
2646
|
+
/**
|
|
2647
|
+
* Process imported nodes with chat inputs, maintaining conversation state per chat key
|
|
2648
|
+
* @param {Object} node - The imported node to process
|
|
2649
|
+
* @param {Object} args - Arguments from the LLM tool call
|
|
2650
|
+
* @returns {Object} The outputs from the node
|
|
2651
|
+
*/
|
|
2652
|
+
async processImportedChatNode(node, args) {
|
|
2653
|
+
this.logDebug('processImportedChatNode', node.id, args);
|
|
2654
|
+
|
|
2655
|
+
const nodeDefinition = this.nodes[node.type];
|
|
2656
|
+
if (!nodeDefinition || !nodeDefinition.config.is_import) {
|
|
2657
|
+
this.logDebug(`Error: Node ${node.id} is not an imported node`);
|
|
2658
|
+
throw new Error(`Node ${node.id} is not an imported node`);
|
|
2659
|
+
}
|
|
2660
|
+
|
|
2661
|
+
// Initialize conversation state if it doesn't exist
|
|
2662
|
+
if (!this._conversationState) {
|
|
2663
|
+
this._conversationState = {};
|
|
2664
|
+
}
|
|
2665
|
+
|
|
2666
|
+
// Find the imported flow definition to get the actual input-chat nodes
|
|
2667
|
+
const importDef = nodeDefinition.config.importDefinition;
|
|
2668
|
+
|
|
2669
|
+
// Transform string arguments to message arrays for each chat input
|
|
2670
|
+
const transformedArgs = { ...args };
|
|
2671
|
+
|
|
2672
|
+
for (const chatInput of nodeDefinition.config.inputs.filter(input => input.is_chat_input)) {
|
|
2673
|
+
const inputValue = args[chatInput.name];
|
|
2674
|
+
if (typeof inputValue === 'string') {
|
|
2675
|
+
|
|
2676
|
+
// The input's name IS the input-chat node's settings.key (set in
|
|
2677
|
+
// convertImportToNodeType), so use it directly. The previous lookup
|
|
2678
|
+
// compared the key against node ids, always missed, and fell back to
|
|
2679
|
+
// 'chat' — collapsing every chat stream into one and ignoring custom keys.
|
|
2680
|
+
const chatKey = chatInput.name;
|
|
2681
|
+
|
|
2682
|
+
// Create conversation key: nodeId + chatKey for this specific chat stream
|
|
2683
|
+
const conversationKey = `${node.id}_${chatKey}`;
|
|
2684
|
+
|
|
2685
|
+
if (!this._conversationState[conversationKey]) {
|
|
2686
|
+
this._conversationState[conversationKey] = [];
|
|
2687
|
+
}
|
|
2688
|
+
|
|
2689
|
+
// Add the new user message to this chat stream's history
|
|
2690
|
+
this._conversationState[conversationKey].push({
|
|
2691
|
+
role: 'user',
|
|
2692
|
+
content: inputValue
|
|
2693
|
+
});
|
|
2694
|
+
|
|
2695
|
+
// Pass the full conversation history for this chat stream
|
|
2696
|
+
transformedArgs[chatInput.name] = [...this._conversationState[conversationKey]];
|
|
2697
|
+
|
|
2698
|
+
this.logDebug(`Updated conversation for ${conversationKey}:`, this._conversationState[conversationKey]);
|
|
2699
|
+
}
|
|
2700
|
+
}
|
|
2701
|
+
|
|
2702
|
+
// Process the imported node with the full conversation context
|
|
2703
|
+
const outputs = await this.processNodeWithArgs(node, transformedArgs);
|
|
2704
|
+
|
|
2705
|
+
// Handle responses from output-chat nodes and append to appropriate conversation streams
|
|
2706
|
+
const outputChatNodes = importDef.nodes.filter(n => n.type === 'output-chat');
|
|
2707
|
+
|
|
2708
|
+
for (const outputChatNode of outputChatNodes) {
|
|
2709
|
+
const chatKey = outputChatNode.settings?.key || 'chat';
|
|
2710
|
+
const conversationKey = `${node.id}_${chatKey}`;
|
|
2711
|
+
|
|
2712
|
+
// The import's process emits the response under the output-chat's key,
|
|
2713
|
+
// not the bare node id. Read by key (fall back to node id for safety) so
|
|
2714
|
+
// the assistant's replies actually get appended to the conversation.
|
|
2715
|
+
const chatOutput = outputs[chatKey] ?? outputs[outputChatNode.id];
|
|
2716
|
+
|
|
2717
|
+
this.logDebug(`Looking for output from node ${outputChatNode.id} with chat key ${chatKey}:`, chatOutput);
|
|
2718
|
+
|
|
2719
|
+
if (chatOutput && Array.isArray(chatOutput)) {
|
|
2720
|
+
if (!this._conversationState[conversationKey]) {
|
|
2721
|
+
this._conversationState[conversationKey] = [];
|
|
2722
|
+
}
|
|
2723
|
+
|
|
2724
|
+
// The chat output contains new response messages to append
|
|
2725
|
+
// (not the full conversation history)
|
|
2726
|
+
if (chatOutput.length > 0) {
|
|
2727
|
+
this._conversationState[conversationKey].push(...chatOutput);
|
|
2728
|
+
this.logDebug(`Appended ${chatOutput.length} new messages to conversation ${conversationKey}:`, chatOutput);
|
|
2729
|
+
}
|
|
2730
|
+
} else {
|
|
2731
|
+
this.logDebug(`No chat output found for node ${outputChatNode.id}, available outputs:`, Object.keys(outputs));
|
|
2732
|
+
}
|
|
2733
|
+
}
|
|
2734
|
+
|
|
2735
|
+
return outputs;
|
|
2736
|
+
}
|
|
2737
|
+
|
|
2738
|
+
/**
|
|
2739
|
+
* Reset conversation state for specific chat streams or all conversations
|
|
2740
|
+
* @param {string} nodeId - Optional node ID to reset conversations for
|
|
2741
|
+
* @param {string} chatKey - Optional chat key to reset specific chat stream
|
|
2742
|
+
*/
|
|
2743
|
+
resetConversationState(nodeId = null, chatKey = null) {
|
|
2744
|
+
if (!this._conversationState) return;
|
|
2745
|
+
|
|
2746
|
+
if (nodeId && chatKey) {
|
|
2747
|
+
const conversationKey = `${nodeId}_${chatKey}`;
|
|
2748
|
+
delete this._conversationState[conversationKey];
|
|
2749
|
+
this.logDebug(`Reset conversation state for node ${nodeId}, chat key ${chatKey}`);
|
|
2750
|
+
} else if (nodeId) {
|
|
2751
|
+
// Reset all chat streams for this node
|
|
2752
|
+
const keysToDelete = Object.keys(this._conversationState).filter(key =>
|
|
2753
|
+
key.startsWith(`${nodeId}_`)
|
|
2754
|
+
);
|
|
2755
|
+
keysToDelete.forEach(key => delete this._conversationState[key]);
|
|
2756
|
+
this.logDebug(`Reset all conversation states for node ${nodeId}`);
|
|
2757
|
+
} else {
|
|
2758
|
+
this._conversationState = {};
|
|
2759
|
+
this.logDebug('Reset all conversation states');
|
|
2760
|
+
}
|
|
2761
|
+
}
|
|
2762
|
+
|
|
2763
|
+
async processNodeWithArgs(node, args) {
|
|
2764
|
+
this.logDebug('processNodeWithArgs', node, args);
|
|
2765
|
+
|
|
2766
|
+
const nodeDefinition = this.nodes[node.type];
|
|
2767
|
+
if (!nodeDefinition) {
|
|
2768
|
+
this.logDebug(`Error: Node type "${node.type}" not found`);
|
|
2769
|
+
throw new Error(`Node type "${node.type}" not found. It may have been removed or renamed (e.g. a model retired from the provider). Check for an updated node type or regenerate the node catalog.`);
|
|
2770
|
+
}
|
|
2771
|
+
|
|
2772
|
+
// For import nodes, their process function expects (inputs, settings, config)
|
|
2773
|
+
// For regular nodes, same signature
|
|
2774
|
+
// We'll use node.settings or an empty object
|
|
2775
|
+
const settings = node.settings || {};
|
|
2776
|
+
|
|
2777
|
+
// Validate the merged inputs against the node configuration
|
|
2778
|
+
this.validateInputs(nodeDefinition.config, args);
|
|
2779
|
+
|
|
2780
|
+
// Execute using the shared core logic
|
|
2781
|
+
const outputs = await this._executeNodeCore(node, args, settings, nodeDefinition);
|
|
2782
|
+
|
|
2783
|
+
this.logDebug('processNodeWithArgs outputs', outputs);
|
|
2784
|
+
return outputs;
|
|
2785
|
+
}
|
|
2786
|
+
|
|
2787
|
+
|
|
2788
|
+
async callLLMWithTools(node, inputs, toolSchemas, toolCallMessage, toolResults) {
|
|
2789
|
+
const nodeDefinition = this.nodes[node.type];
|
|
2790
|
+
if (!nodeDefinition) {
|
|
2791
|
+
this.logDebug(`Error: Node type "${node.type}" not found`);
|
|
2792
|
+
throw new Error(`Node type "${node.type}" not found. It may have been removed or renamed (e.g. a model retired from the provider). Check for an updated node type or regenerate the node catalog.`);
|
|
2793
|
+
}
|
|
2794
|
+
|
|
2795
|
+
// Use the inputs passed from processLLMNode instead of re-collecting them
|
|
2796
|
+
const llmInputs = { ...inputs };
|
|
2797
|
+
|
|
2798
|
+
// Merge plugin/manual toolSchemas with any tools already in inputs (e.g. from filter-tools)
|
|
2799
|
+
// Deduplicate by tool name since tools from the "tools" input port are collected
|
|
2800
|
+
// both during normal input collection and during tool schema gathering
|
|
2801
|
+
const inputTools = Array.isArray(llmInputs.tools) ? llmInputs.tools : [];
|
|
2802
|
+
const seenToolNames = new Set();
|
|
2803
|
+
llmInputs.tools = [];
|
|
2804
|
+
for (const tool of [...toolSchemas, ...inputTools]) {
|
|
2805
|
+
if (tool?.name && !seenToolNames.has(tool.name)) {
|
|
2806
|
+
seenToolNames.add(tool.name);
|
|
2807
|
+
llmInputs.tools.push(tool);
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2810
|
+
|
|
2811
|
+
// If this is a tool call response, append it to the messages array (OpenAI style)
|
|
2812
|
+
if (toolCallMessage && toolResults && Array.isArray(llmInputs.messages)) {
|
|
2813
|
+
|
|
2814
|
+
//
|
|
2815
|
+
llmInputs.messages = [
|
|
2816
|
+
...llmInputs.messages,
|
|
2817
|
+
toolCallMessage
|
|
2818
|
+
];
|
|
2819
|
+
|
|
2820
|
+
for(const toolResult of toolResults) {
|
|
2821
|
+
// You may need to adapt this for other LLMs
|
|
2822
|
+
llmInputs.messages = [
|
|
2823
|
+
...llmInputs.messages,
|
|
2824
|
+
{
|
|
2825
|
+
role: "tool",
|
|
2826
|
+
tool_call_id: toolResult.tool_call_id,
|
|
2827
|
+
name: toolResult.name,
|
|
2828
|
+
content: typeof toolResult.result === "string"
|
|
2829
|
+
? toolResult.result
|
|
2830
|
+
: JSON.stringify(toolResult.result)
|
|
2831
|
+
}
|
|
2832
|
+
];
|
|
2833
|
+
}
|
|
2834
|
+
}
|
|
2835
|
+
|
|
2836
|
+
// Execute using the shared core logic
|
|
2837
|
+
const outputs = await this._executeNodeCore(node, llmInputs, node.settings || {}, nodeDefinition);
|
|
2838
|
+
|
|
2839
|
+
return outputs;
|
|
2840
|
+
}
|
|
2841
|
+
|
|
2842
|
+
_getCostSummaryFromTimeline() {
|
|
2843
|
+
let total = 0;
|
|
2844
|
+
let itemized = [];
|
|
2845
|
+
for (const entry of this.timeline) {
|
|
2846
|
+
const outputs = entry.outputs || {};
|
|
2847
|
+
if (typeof outputs.cost_total === 'number') {
|
|
2848
|
+
total += outputs.cost_total;
|
|
2849
|
+
|
|
2850
|
+
let item = {
|
|
2851
|
+
node_id: entry.nodeId,
|
|
2852
|
+
node_type: entry.nodeType,
|
|
2853
|
+
total: outputs.cost_total,
|
|
2854
|
+
itemized: outputs.cost_itemized
|
|
2855
|
+
}
|
|
2856
|
+
itemized.push(item);
|
|
2857
|
+
}
|
|
2858
|
+
}
|
|
2859
|
+
|
|
2860
|
+
return {
|
|
2861
|
+
total,
|
|
2862
|
+
itemized
|
|
2863
|
+
};
|
|
2864
|
+
}
|
|
2865
|
+
|
|
2866
|
+
/**
|
|
2867
|
+
* Track a knowledge base file for cleanup
|
|
2868
|
+
* @param {string} filePath - Path to the knowledge base file
|
|
2869
|
+
*/
|
|
2870
|
+
trackKnowledgeFile(filePath) {
|
|
2871
|
+
if (filePath && filePath.includes('knowledge_')) {
|
|
2872
|
+
this.knowledgeFilesToCleanup.add(filePath);
|
|
2873
|
+
this.logDebug(`Tracking knowledge file for cleanup: ${filePath}`);
|
|
2874
|
+
}
|
|
2875
|
+
}
|
|
2876
|
+
|
|
2877
|
+
/**
|
|
2878
|
+
* Track all knowledge base files that need cleanup
|
|
2879
|
+
* @private
|
|
2880
|
+
*/
|
|
2881
|
+
_trackKnowledgeFiles() {
|
|
2882
|
+
// Track main flow's knowledge base file
|
|
2883
|
+
if (this.flow.knowledgeDbPath) {
|
|
2884
|
+
this.trackKnowledgeFile(this.flow.knowledgeDbPath);
|
|
2885
|
+
}
|
|
2886
|
+
|
|
2887
|
+
// Track import knowledge base files
|
|
2888
|
+
if (this.flow.imports && Array.isArray(this.flow.imports)) {
|
|
2889
|
+
for (const importDef of this.flow.imports) {
|
|
2890
|
+
if (importDef.knowledgeDbPath) {
|
|
2891
|
+
this.trackKnowledgeFile(importDef.knowledgeDbPath);
|
|
2892
|
+
}
|
|
2893
|
+
}
|
|
2894
|
+
}
|
|
2895
|
+
}
|
|
2896
|
+
|
|
2897
|
+
/**
|
|
2898
|
+
* Clean up temporary knowledge base files for this specific engine instance
|
|
2899
|
+
* @private
|
|
2900
|
+
*/
|
|
2901
|
+
async _cleanupTempKnowledgeFiles() {
|
|
2902
|
+
|
|
2903
|
+
try {
|
|
2904
|
+
const fs = await import('fs');
|
|
2905
|
+
const path = await import('path');
|
|
2906
|
+
|
|
2907
|
+
const tempDir = path.join(process.cwd(), '.temp');
|
|
2908
|
+
if (!fs.existsSync(tempDir)) {
|
|
2909
|
+
return; // No temp directory exists
|
|
2910
|
+
}
|
|
2911
|
+
|
|
2912
|
+
// Clean up tracked knowledge files first
|
|
2913
|
+
for (const filePath of this.knowledgeFilesToCleanup) {
|
|
2914
|
+
try {
|
|
2915
|
+
if (fs.existsSync(filePath)) {
|
|
2916
|
+
fs.unlinkSync(filePath);
|
|
2917
|
+
this.logDebug(`Cleaned up tracked knowledge file: ${filePath}`);
|
|
2918
|
+
}
|
|
2919
|
+
|
|
2920
|
+
// Also clean up any lock files
|
|
2921
|
+
const lockPath = `${filePath}.lock`;
|
|
2922
|
+
if (fs.existsSync(lockPath)) {
|
|
2923
|
+
fs.unlinkSync(lockPath);
|
|
2924
|
+
this.logDebug(`Cleaned up lock file: ${lockPath}`);
|
|
2925
|
+
}
|
|
2926
|
+
} catch (error) {
|
|
2927
|
+
console.warn(`[WARN] Failed to cleanup tracked knowledge file ${filePath}:`, error.message);
|
|
2928
|
+
}
|
|
2929
|
+
}
|
|
2930
|
+
|
|
2931
|
+
// Get the flow ID to identify our specific temporary files
|
|
2932
|
+
const flowId = this.flow.id || this.flow.metadata?.id;
|
|
2933
|
+
if (!flowId) {
|
|
2934
|
+
this.logDebug('No flow ID found, skipping additional knowledge file cleanup');
|
|
2935
|
+
return;
|
|
2936
|
+
}
|
|
2937
|
+
|
|
2938
|
+
// Look for any remaining knowledge files that match our flow ID pattern
|
|
2939
|
+
const files = fs.readdirSync(tempDir);
|
|
2940
|
+
const knowledgeFiles = files.filter(file => file === `knowledge_${flowId}.db`);
|
|
2941
|
+
|
|
2942
|
+
|
|
2943
|
+
for (const file of knowledgeFiles) {
|
|
2944
|
+
const filePath = path.join(tempDir, file);
|
|
2945
|
+
const lockPath = `${filePath}.lock`;
|
|
2946
|
+
|
|
2947
|
+
try {
|
|
2948
|
+
fs.unlinkSync(filePath);
|
|
2949
|
+
this.logDebug(`Cleaned up additional temporary knowledge file: ${filePath}`);
|
|
2950
|
+
} catch (error) {
|
|
2951
|
+
console.warn(`[WARN] Failed to cleanup additional temporary knowledge file ${filePath}:`, error.message);
|
|
2952
|
+
}
|
|
2953
|
+
|
|
2954
|
+
// Also clean up any lock files
|
|
2955
|
+
try {
|
|
2956
|
+
if (fs.existsSync(lockPath)) {
|
|
2957
|
+
fs.unlinkSync(lockPath);
|
|
2958
|
+
this.logDebug(`Cleaned up lock file: ${lockPath}`);
|
|
2959
|
+
}
|
|
2960
|
+
} catch (error) {
|
|
2961
|
+
// Ignore lock file cleanup errors
|
|
2962
|
+
}
|
|
2963
|
+
}
|
|
2964
|
+
|
|
2965
|
+
// Also clean up any knowledge files that are older than 1 hour (safety cleanup)
|
|
2966
|
+
const oneHourAgo = Date.now() - (60 * 60 * 1000);
|
|
2967
|
+
const allKnowledgeFiles = files.filter(file => file.startsWith('knowledge_') && file.endsWith('.db'));
|
|
2968
|
+
|
|
2969
|
+
for (const file of allKnowledgeFiles) {
|
|
2970
|
+
const filePath = path.join(tempDir, file);
|
|
2971
|
+
try {
|
|
2972
|
+
const stats = fs.statSync(filePath);
|
|
2973
|
+
if (stats.mtime.getTime() < oneHourAgo) {
|
|
2974
|
+
fs.unlinkSync(filePath);
|
|
2975
|
+
this.logDebug(`Cleaned up old temporary knowledge file: ${filePath}`);
|
|
2976
|
+
}
|
|
2977
|
+
} catch (error) {
|
|
2978
|
+
// Ignore errors for old file cleanup
|
|
2979
|
+
}
|
|
2980
|
+
}
|
|
2981
|
+
|
|
2982
|
+
// If temp directory is now empty, remove it
|
|
2983
|
+
try {
|
|
2984
|
+
const remainingFiles = fs.readdirSync(tempDir);
|
|
2985
|
+
if (remainingFiles.length === 0) {
|
|
2986
|
+
fs.rmdirSync(tempDir);
|
|
2987
|
+
this.logDebug('Removed empty temporary directory');
|
|
2988
|
+
}
|
|
2989
|
+
} catch (error) {
|
|
2990
|
+
// Ignore errors for directory cleanup
|
|
2991
|
+
}
|
|
2992
|
+
|
|
2993
|
+
} catch (error) {
|
|
2994
|
+
console.warn('[WARN] Error during temporary knowledge file cleanup:', error.message);
|
|
2995
|
+
// Don't throw - cleanup should be best effort
|
|
2996
|
+
}
|
|
2997
|
+
}
|
|
2998
|
+
}
|