@alpic80/rivet-core 1.19.1-aidon.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +7 -0
- package/README.md +176 -0
- package/dist/cjs/bundle.cjs +18915 -0
- package/dist/cjs/bundle.cjs.map +7 -0
- package/dist/esm/api/createProcessor.js +131 -0
- package/dist/esm/api/streaming.js +116 -0
- package/dist/esm/exports.js +32 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/integrations/AudioProvider.js +1 -0
- package/dist/esm/integrations/DatasetProvider.js +92 -0
- package/dist/esm/integrations/EmbeddingGenerator.js +1 -0
- package/dist/esm/integrations/GptTokenizerTokenizer.js +65 -0
- package/dist/esm/integrations/LLMProvider.js +1 -0
- package/dist/esm/integrations/Tokenizer.js +1 -0
- package/dist/esm/integrations/VectorDatabase.js +1 -0
- package/dist/esm/integrations/enableIntegrations.js +3 -0
- package/dist/esm/integrations/integrations.js +19 -0
- package/dist/esm/integrations/openai/OpenAIEmbeddingGenerator.js +23 -0
- package/dist/esm/model/DataValue.js +176 -0
- package/dist/esm/model/Dataset.js +1 -0
- package/dist/esm/model/EditorDefinition.js +1 -0
- package/dist/esm/model/GraphProcessor.js +1198 -0
- package/dist/esm/model/NodeBase.js +1 -0
- package/dist/esm/model/NodeBodySpec.js +1 -0
- package/dist/esm/model/NodeDefinition.js +12 -0
- package/dist/esm/model/NodeGraph.js +14 -0
- package/dist/esm/model/NodeImpl.js +49 -0
- package/dist/esm/model/NodeRegistration.js +144 -0
- package/dist/esm/model/Nodes.js +227 -0
- package/dist/esm/model/PluginLoadSpec.js +1 -0
- package/dist/esm/model/ProcessContext.js +1 -0
- package/dist/esm/model/Project.js +2 -0
- package/dist/esm/model/RivetPlugin.js +1 -0
- package/dist/esm/model/RivetUIContext.js +1 -0
- package/dist/esm/model/Settings.js +1 -0
- package/dist/esm/model/nodes/AbortGraphNode.js +100 -0
- package/dist/esm/model/nodes/AppendToDatasetNode.js +115 -0
- package/dist/esm/model/nodes/ArrayNode.js +144 -0
- package/dist/esm/model/nodes/AssembleMessageNode.js +199 -0
- package/dist/esm/model/nodes/AssemblePromptNode.js +129 -0
- package/dist/esm/model/nodes/AudioNode.js +101 -0
- package/dist/esm/model/nodes/BooleanNode.js +74 -0
- package/dist/esm/model/nodes/CallGraphNode.js +136 -0
- package/dist/esm/model/nodes/ChatNode.js +964 -0
- package/dist/esm/model/nodes/ChunkNode.js +166 -0
- package/dist/esm/model/nodes/CoalesceNode.js +104 -0
- package/dist/esm/model/nodes/CodeNode.js +136 -0
- package/dist/esm/model/nodes/CommentNode.js +69 -0
- package/dist/esm/model/nodes/CompareNode.js +138 -0
- package/dist/esm/model/nodes/ContextNode.js +99 -0
- package/dist/esm/model/nodes/CreateDatasetNode.js +71 -0
- package/dist/esm/model/nodes/DatasetNearestNeigborsNode.js +97 -0
- package/dist/esm/model/nodes/DelayNode.js +105 -0
- package/dist/esm/model/nodes/DelegateFunctionCallNode.js +136 -0
- package/dist/esm/model/nodes/DestructureNode.js +86 -0
- package/dist/esm/model/nodes/EvaluateNode.js +141 -0
- package/dist/esm/model/nodes/ExternalCallNode.js +162 -0
- package/dist/esm/model/nodes/ExtractJsonNode.js +122 -0
- package/dist/esm/model/nodes/ExtractMarkdownCodeBlocksNode.js +100 -0
- package/dist/esm/model/nodes/ExtractObjectPathNode.js +128 -0
- package/dist/esm/model/nodes/ExtractRegexNode.js +201 -0
- package/dist/esm/model/nodes/ExtractYamlNode.js +214 -0
- package/dist/esm/model/nodes/FilterNode.js +73 -0
- package/dist/esm/model/nodes/GetAllDatasetsNode.js +53 -0
- package/dist/esm/model/nodes/GetDatasetRowNode.js +99 -0
- package/dist/esm/model/nodes/GetEmbeddingNode.js +130 -0
- package/dist/esm/model/nodes/GetGlobalNode.js +139 -0
- package/dist/esm/model/nodes/GptFunctionNode.js +169 -0
- package/dist/esm/model/nodes/GraphInputNode.js +130 -0
- package/dist/esm/model/nodes/GraphOutputNode.js +104 -0
- package/dist/esm/model/nodes/GraphReferenceNode.js +128 -0
- package/dist/esm/model/nodes/HashNode.js +97 -0
- package/dist/esm/model/nodes/HttpCallNode.js +257 -0
- package/dist/esm/model/nodes/IfElseNode.js +138 -0
- package/dist/esm/model/nodes/IfNode.js +124 -0
- package/dist/esm/model/nodes/ImageNode.js +107 -0
- package/dist/esm/model/nodes/JoinNode.js +135 -0
- package/dist/esm/model/nodes/ListGraphsNode.js +61 -0
- package/dist/esm/model/nodes/LoadDatasetNode.js +83 -0
- package/dist/esm/model/nodes/LoopControllerNode.js +206 -0
- package/dist/esm/model/nodes/MatchNode.js +137 -0
- package/dist/esm/model/nodes/NumberNode.js +86 -0
- package/dist/esm/model/nodes/ObjectNode.js +121 -0
- package/dist/esm/model/nodes/PassthroughNode.js +78 -0
- package/dist/esm/model/nodes/PlayAudioNode.js +61 -0
- package/dist/esm/model/nodes/PopNode.js +89 -0
- package/dist/esm/model/nodes/PromptNode.js +227 -0
- package/dist/esm/model/nodes/RaceInputsNode.js +86 -0
- package/dist/esm/model/nodes/RaiseEventNode.js +84 -0
- package/dist/esm/model/nodes/RandomNumberNode.js +106 -0
- package/dist/esm/model/nodes/ReadDirectoryNode.js +165 -0
- package/dist/esm/model/nodes/ReadFileNode.js +114 -0
- package/dist/esm/model/nodes/ReplaceDatasetNode.js +118 -0
- package/dist/esm/model/nodes/SetGlobalNode.js +124 -0
- package/dist/esm/model/nodes/ShuffleNode.js +64 -0
- package/dist/esm/model/nodes/SliceNode.js +100 -0
- package/dist/esm/model/nodes/SplitNode.js +101 -0
- package/dist/esm/model/nodes/SubGraphNode.js +181 -0
- package/dist/esm/model/nodes/TextNode.js +97 -0
- package/dist/esm/model/nodes/ToJsonNode.js +78 -0
- package/dist/esm/model/nodes/ToYamlNode.js +68 -0
- package/dist/esm/model/nodes/TrimChatMessagesNode.js +120 -0
- package/dist/esm/model/nodes/URLReferenceNode.js +79 -0
- package/dist/esm/model/nodes/UserInputNode.js +111 -0
- package/dist/esm/model/nodes/VectorNearestNeighborsNode.js +127 -0
- package/dist/esm/model/nodes/VectorStoreNode.js +124 -0
- package/dist/esm/model/nodes/WaitForEventNode.js +88 -0
- package/dist/esm/native/BaseDir.js +32 -0
- package/dist/esm/native/BrowserNativeApi.js +19 -0
- package/dist/esm/native/NativeApi.js +1 -0
- package/dist/esm/plugins/aidon/index.js +2 -0
- package/dist/esm/plugins/aidon/nodes/ChatAidonNode.js +215 -0
- package/dist/esm/plugins/aidon/plugin.js +9 -0
- package/dist/esm/plugins/anthropic/anthropic.js +187 -0
- package/dist/esm/plugins/anthropic/fetchEventSource.js +106 -0
- package/dist/esm/plugins/anthropic/index.js +2 -0
- package/dist/esm/plugins/anthropic/nodes/ChatAnthropicNode.js +652 -0
- package/dist/esm/plugins/anthropic/plugin.js +18 -0
- package/dist/esm/plugins/assemblyAi/LemurActionItemsNode.js +75 -0
- package/dist/esm/plugins/assemblyAi/LemurQaNode.js +155 -0
- package/dist/esm/plugins/assemblyAi/LemurSummaryNode.js +79 -0
- package/dist/esm/plugins/assemblyAi/LemurTaskNode.js +82 -0
- package/dist/esm/plugins/assemblyAi/TranscribeAudioNode.js +125 -0
- package/dist/esm/plugins/assemblyAi/index.js +2 -0
- package/dist/esm/plugins/assemblyAi/lemurHelpers.js +114 -0
- package/dist/esm/plugins/assemblyAi/plugin.js +32 -0
- package/dist/esm/plugins/autoevals/AutoEvalsNode.js +223 -0
- package/dist/esm/plugins/autoevals/index.js +2 -0
- package/dist/esm/plugins/autoevals/plugin.js +8 -0
- package/dist/esm/plugins/gentrace/index.js +2 -0
- package/dist/esm/plugins/gentrace/plugin.js +192 -0
- package/dist/esm/plugins/google/google.js +60 -0
- package/dist/esm/plugins/google/index.js +2 -0
- package/dist/esm/plugins/google/nodes/ChatGoogleNode.js +364 -0
- package/dist/esm/plugins/google/plugin.js +32 -0
- package/dist/esm/plugins/huggingface/index.js +2 -0
- package/dist/esm/plugins/huggingface/nodes/ChatHuggingFace.js +243 -0
- package/dist/esm/plugins/huggingface/nodes/TextToImageHuggingFace.js +189 -0
- package/dist/esm/plugins/huggingface/plugin.js +26 -0
- package/dist/esm/plugins/openai/handleOpenaiError.js +17 -0
- package/dist/esm/plugins/openai/index.js +2 -0
- package/dist/esm/plugins/openai/nodes/AttachAssistantFileNode.js +123 -0
- package/dist/esm/plugins/openai/nodes/CreateAssistantNode.js +289 -0
- package/dist/esm/plugins/openai/nodes/CreateThreadMessageNode.js +176 -0
- package/dist/esm/plugins/openai/nodes/CreateThreadNode.js +157 -0
- package/dist/esm/plugins/openai/nodes/DeleteAssistantNode.js +104 -0
- package/dist/esm/plugins/openai/nodes/DeleteThreadNode.js +97 -0
- package/dist/esm/plugins/openai/nodes/GetAssistantNode.js +118 -0
- package/dist/esm/plugins/openai/nodes/GetOpenAIFileNode.js +100 -0
- package/dist/esm/plugins/openai/nodes/GetThreadNode.js +108 -0
- package/dist/esm/plugins/openai/nodes/ListAssistantsNode.js +202 -0
- package/dist/esm/plugins/openai/nodes/ListOpenAIFilesNode.js +94 -0
- package/dist/esm/plugins/openai/nodes/ListThreadMessagesNode.js +224 -0
- package/dist/esm/plugins/openai/nodes/RunThreadNode.js +630 -0
- package/dist/esm/plugins/openai/nodes/ThreadMessageNode.js +145 -0
- package/dist/esm/plugins/openai/nodes/UploadFileNode.js +121 -0
- package/dist/esm/plugins/openai/plugin.js +44 -0
- package/dist/esm/plugins/pinecone/PineconeVectorDatabase.js +88 -0
- package/dist/esm/plugins/pinecone/index.js +2 -0
- package/dist/esm/plugins/pinecone/plugin.js +19 -0
- package/dist/esm/plugins.js +21 -0
- package/dist/esm/recording/ExecutionRecorder.js +177 -0
- package/dist/esm/recording/RecordedEvents.js +1 -0
- package/dist/esm/utils/assertNever.js +3 -0
- package/dist/esm/utils/base64.js +25 -0
- package/dist/esm/utils/chatMessageToOpenAIChatCompletionMessage.js +60 -0
- package/dist/esm/utils/coerceType.js +322 -0
- package/dist/esm/utils/compatibility.js +27 -0
- package/dist/esm/utils/copyToClipboard.js +10 -0
- package/dist/esm/utils/defaults.js +2 -0
- package/dist/esm/utils/errors.js +7 -0
- package/dist/esm/utils/expectType.js +34 -0
- package/dist/esm/utils/fetchEventSource.js +120 -0
- package/dist/esm/utils/genericUtilFunctions.js +25 -0
- package/dist/esm/utils/getPluginConfig.js +23 -0
- package/dist/esm/utils/handleEscapeCharacters.js +11 -0
- package/dist/esm/utils/index.js +14 -0
- package/dist/esm/utils/inputs.js +16 -0
- package/dist/esm/utils/interpolation.js +6 -0
- package/dist/esm/utils/misc.js +1 -0
- package/dist/esm/utils/newId.js +4 -0
- package/dist/esm/utils/openai.js +219 -0
- package/dist/esm/utils/outputs.js +14 -0
- package/dist/esm/utils/serialization/serialization.js +86 -0
- package/dist/esm/utils/serialization/serializationUtils.js +13 -0
- package/dist/esm/utils/serialization/serialization_v1.js +19 -0
- package/dist/esm/utils/serialization/serialization_v2.js +24 -0
- package/dist/esm/utils/serialization/serialization_v3.js +145 -0
- package/dist/esm/utils/serialization/serialization_v4.js +200 -0
- package/dist/esm/utils/symbols.js +2 -0
- package/dist/esm/utils/time.js +14 -0
- package/dist/esm/utils/typeSafety.js +42 -0
- package/dist/types/api/createProcessor.d.ts +37 -0
- package/dist/types/api/streaming.d.ts +56 -0
- package/dist/types/exports.d.ts +33 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/integrations/AudioProvider.d.ts +4 -0
- package/dist/types/integrations/DatasetProvider.d.ts +33 -0
- package/dist/types/integrations/EmbeddingGenerator.d.ts +3 -0
- package/dist/types/integrations/GptTokenizerTokenizer.d.ts +20 -0
- package/dist/types/integrations/LLMProvider.d.ts +7 -0
- package/dist/types/integrations/Tokenizer.d.ts +11 -0
- package/dist/types/integrations/VectorDatabase.d.ts +7 -0
- package/dist/types/integrations/enableIntegrations.d.ts +1 -0
- package/dist/types/integrations/integrations.d.ts +12 -0
- package/dist/types/integrations/openai/OpenAIEmbeddingGenerator.d.ts +10 -0
- package/dist/types/model/DataValue.d.ts +138 -0
- package/dist/types/model/Dataset.d.ts +19 -0
- package/dist/types/model/EditorDefinition.d.ts +142 -0
- package/dist/types/model/GraphProcessor.d.ts +192 -0
- package/dist/types/model/NodeBase.d.ts +110 -0
- package/dist/types/model/NodeBodySpec.d.ts +19 -0
- package/dist/types/model/NodeDefinition.d.ts +13 -0
- package/dist/types/model/NodeGraph.d.ts +15 -0
- package/dist/types/model/NodeImpl.d.ts +55 -0
- package/dist/types/model/NodeRegistration.d.ts +24 -0
- package/dist/types/model/Nodes.d.ts +84 -0
- package/dist/types/model/PluginLoadSpec.d.ts +17 -0
- package/dist/types/model/ProcessContext.d.ts +69 -0
- package/dist/types/model/Project.d.ts +17 -0
- package/dist/types/model/RivetPlugin.d.ts +45 -0
- package/dist/types/model/RivetUIContext.d.ts +18 -0
- package/dist/types/model/Settings.d.ts +15 -0
- package/dist/types/model/nodes/AbortGraphNode.d.ts +22 -0
- package/dist/types/model/nodes/AppendToDatasetNode.d.ts +21 -0
- package/dist/types/model/nodes/ArrayNode.d.ts +20 -0
- package/dist/types/model/nodes/AssembleMessageNode.d.ts +23 -0
- package/dist/types/model/nodes/AssemblePromptNode.d.ts +20 -0
- package/dist/types/model/nodes/AudioNode.d.ts +20 -0
- package/dist/types/model/nodes/BooleanNode.d.ts +19 -0
- package/dist/types/model/nodes/CallGraphNode.d.ts +16 -0
- package/dist/types/model/nodes/ChatNode.d.ts +77 -0
- package/dist/types/model/nodes/ChunkNode.d.ts +22 -0
- package/dist/types/model/nodes/CoalesceNode.d.ts +14 -0
- package/dist/types/model/nodes/CodeNode.d.ts +21 -0
- package/dist/types/model/nodes/CommentNode.d.ts +20 -0
- package/dist/types/model/nodes/CompareNode.d.ts +19 -0
- package/dist/types/model/nodes/ContextNode.d.ts +24 -0
- package/dist/types/model/nodes/CreateDatasetNode.d.ts +13 -0
- package/dist/types/model/nodes/DatasetNearestNeigborsNode.d.ts +19 -0
- package/dist/types/model/nodes/DelayNode.d.ts +20 -0
- package/dist/types/model/nodes/DelegateFunctionCallNode.d.ts +25 -0
- package/dist/types/model/nodes/DestructureNode.d.ts +18 -0
- package/dist/types/model/nodes/EvaluateNode.d.ts +19 -0
- package/dist/types/model/nodes/ExternalCallNode.d.ts +22 -0
- package/dist/types/model/nodes/ExtractJsonNode.d.ts +13 -0
- package/dist/types/model/nodes/ExtractMarkdownCodeBlocksNode.d.ts +12 -0
- package/dist/types/model/nodes/ExtractObjectPathNode.d.ts +19 -0
- package/dist/types/model/nodes/ExtractRegexNode.d.ts +22 -0
- package/dist/types/model/nodes/ExtractYamlNode.d.ts +21 -0
- package/dist/types/model/nodes/FilterNode.d.ts +13 -0
- package/dist/types/model/nodes/GetAllDatasetsNode.d.ts +14 -0
- package/dist/types/model/nodes/GetDatasetRowNode.d.ts +19 -0
- package/dist/types/model/nodes/GetEmbeddingNode.d.ts +24 -0
- package/dist/types/model/nodes/GetGlobalNode.d.ts +29 -0
- package/dist/types/model/nodes/GptFunctionNode.d.ts +25 -0
- package/dist/types/model/nodes/GraphInputNode.d.ts +24 -0
- package/dist/types/model/nodes/GraphOutputNode.d.ts +22 -0
- package/dist/types/model/nodes/GraphReferenceNode.d.ts +22 -0
- package/dist/types/model/nodes/HashNode.d.ts +17 -0
- package/dist/types/model/nodes/HttpCallNode.d.ts +27 -0
- package/dist/types/model/nodes/IfElseNode.d.ts +18 -0
- package/dist/types/model/nodes/IfNode.d.ts +17 -0
- package/dist/types/model/nodes/ImageNode.d.ts +20 -0
- package/dist/types/model/nodes/JoinNode.d.ts +21 -0
- package/dist/types/model/nodes/ListGraphsNode.d.ts +14 -0
- package/dist/types/model/nodes/LoadDatasetNode.d.ts +17 -0
- package/dist/types/model/nodes/LoopControllerNode.d.ts +20 -0
- package/dist/types/model/nodes/MatchNode.d.ts +19 -0
- package/dist/types/model/nodes/NumberNode.d.ts +21 -0
- package/dist/types/model/nodes/ObjectNode.d.ts +18 -0
- package/dist/types/model/nodes/PassthroughNode.d.ts +14 -0
- package/dist/types/model/nodes/PlayAudioNode.d.ts +15 -0
- package/dist/types/model/nodes/PopNode.d.ts +19 -0
- package/dist/types/model/nodes/PromptNode.d.ts +23 -0
- package/dist/types/model/nodes/RaceInputsNode.d.ts +17 -0
- package/dist/types/model/nodes/RaiseEventNode.d.ts +22 -0
- package/dist/types/model/nodes/RandomNumberNode.d.ts +23 -0
- package/dist/types/model/nodes/ReadDirectoryNode.d.ts +30 -0
- package/dist/types/model/nodes/ReadFileNode.d.ts +23 -0
- package/dist/types/model/nodes/ReplaceDatasetNode.d.ts +21 -0
- package/dist/types/model/nodes/SetGlobalNode.d.ts +23 -0
- package/dist/types/model/nodes/ShuffleNode.d.ts +12 -0
- package/dist/types/model/nodes/SliceNode.d.ts +22 -0
- package/dist/types/model/nodes/SplitNode.d.ts +19 -0
- package/dist/types/model/nodes/SubGraphNode.d.ts +29 -0
- package/dist/types/model/nodes/TextNode.d.ts +18 -0
- package/dist/types/model/nodes/ToJsonNode.d.ts +18 -0
- package/dist/types/model/nodes/ToYamlNode.d.ts +13 -0
- package/dist/types/model/nodes/TrimChatMessagesNode.d.ts +20 -0
- package/dist/types/model/nodes/URLReferenceNode.d.ts +19 -0
- package/dist/types/model/nodes/UserInputNode.d.ts +21 -0
- package/dist/types/model/nodes/VectorNearestNeighborsNode.d.ts +24 -0
- package/dist/types/model/nodes/VectorStoreNode.d.ts +22 -0
- package/dist/types/model/nodes/WaitForEventNode.d.ts +21 -0
- package/dist/types/native/BaseDir.d.ts +29 -0
- package/dist/types/native/BrowserNativeApi.d.ts +11 -0
- package/dist/types/native/NativeApi.d.ts +17 -0
- package/dist/types/plugins/aidon/index.d.ts +2 -0
- package/dist/types/plugins/aidon/nodes/ChatAidonNode.d.ts +3 -0
- package/dist/types/plugins/aidon/plugin.d.ts +2 -0
- package/dist/types/plugins/anthropic/anthropic.d.ts +216 -0
- package/dist/types/plugins/anthropic/fetchEventSource.d.ts +11 -0
- package/dist/types/plugins/anthropic/index.d.ts +2 -0
- package/dist/types/plugins/anthropic/nodes/ChatAnthropicNode.d.ts +30 -0
- package/dist/types/plugins/anthropic/plugin.d.ts +2 -0
- package/dist/types/plugins/assemblyAi/LemurActionItemsNode.d.ts +6 -0
- package/dist/types/plugins/assemblyAi/LemurQaNode.d.ts +22 -0
- package/dist/types/plugins/assemblyAi/LemurSummaryNode.d.ts +8 -0
- package/dist/types/plugins/assemblyAi/LemurTaskNode.d.ts +8 -0
- package/dist/types/plugins/assemblyAi/TranscribeAudioNode.d.ts +7 -0
- package/dist/types/plugins/assemblyAi/index.d.ts +2 -0
- package/dist/types/plugins/assemblyAi/lemurHelpers.d.ts +67 -0
- package/dist/types/plugins/assemblyAi/plugin.d.ts +2 -0
- package/dist/types/plugins/autoevals/AutoEvalsNode.d.ts +8 -0
- package/dist/types/plugins/autoevals/index.d.ts +2 -0
- package/dist/types/plugins/autoevals/plugin.d.ts +2 -0
- package/dist/types/plugins/gentrace/index.d.ts +2 -0
- package/dist/types/plugins/gentrace/plugin.d.ts +5 -0
- package/dist/types/plugins/google/google.d.ts +60 -0
- package/dist/types/plugins/google/index.d.ts +2 -0
- package/dist/types/plugins/google/nodes/ChatGoogleNode.d.ts +27 -0
- package/dist/types/plugins/google/plugin.d.ts +2 -0
- package/dist/types/plugins/huggingface/index.d.ts +2 -0
- package/dist/types/plugins/huggingface/nodes/ChatHuggingFace.d.ts +24 -0
- package/dist/types/plugins/huggingface/nodes/TextToImageHuggingFace.d.ts +20 -0
- package/dist/types/plugins/huggingface/plugin.d.ts +2 -0
- package/dist/types/plugins/openai/handleOpenaiError.d.ts +1 -0
- package/dist/types/plugins/openai/index.d.ts +2 -0
- package/dist/types/plugins/openai/nodes/AttachAssistantFileNode.d.ts +10 -0
- package/dist/types/plugins/openai/nodes/CreateAssistantNode.d.ts +26 -0
- package/dist/types/plugins/openai/nodes/CreateThreadMessageNode.d.ts +15 -0
- package/dist/types/plugins/openai/nodes/CreateThreadNode.d.ts +13 -0
- package/dist/types/plugins/openai/nodes/DeleteAssistantNode.d.ts +8 -0
- package/dist/types/plugins/openai/nodes/DeleteThreadNode.d.ts +8 -0
- package/dist/types/plugins/openai/nodes/GetAssistantNode.d.ts +8 -0
- package/dist/types/plugins/openai/nodes/GetOpenAIFileNode.d.ts +8 -0
- package/dist/types/plugins/openai/nodes/GetThreadNode.d.ts +8 -0
- package/dist/types/plugins/openai/nodes/ListAssistantsNode.d.ts +14 -0
- package/dist/types/plugins/openai/nodes/ListOpenAIFilesNode.d.ts +8 -0
- package/dist/types/plugins/openai/nodes/ListThreadMessagesNode.d.ts +16 -0
- package/dist/types/plugins/openai/nodes/RunThreadNode.d.ts +28 -0
- package/dist/types/plugins/openai/nodes/ThreadMessageNode.d.ts +14 -0
- package/dist/types/plugins/openai/nodes/UploadFileNode.d.ts +7 -0
- package/dist/types/plugins/openai/plugin.d.ts +2 -0
- package/dist/types/plugins/pinecone/PineconeVectorDatabase.d.ts +9 -0
- package/dist/types/plugins/pinecone/index.d.ts +2 -0
- package/dist/types/plugins/pinecone/plugin.d.ts +2 -0
- package/dist/types/plugins.d.ts +20 -0
- package/dist/types/recording/ExecutionRecorder.d.ts +25 -0
- package/dist/types/recording/RecordedEvents.d.ts +100 -0
- package/dist/types/utils/assertNever.d.ts +1 -0
- package/dist/types/utils/base64.d.ts +2 -0
- package/dist/types/utils/chatMessageToOpenAIChatCompletionMessage.d.ts +3 -0
- package/dist/types/utils/coerceType.d.ts +6 -0
- package/dist/types/utils/compatibility.d.ts +3 -0
- package/dist/types/utils/copyToClipboard.d.ts +1 -0
- package/dist/types/utils/defaults.d.ts +2 -0
- package/dist/types/utils/errors.d.ts +2 -0
- package/dist/types/utils/expectType.d.ts +3 -0
- package/dist/types/utils/fetchEventSource.d.ts +12 -0
- package/dist/types/utils/genericUtilFunctions.d.ts +21 -0
- package/dist/types/utils/getPluginConfig.d.ts +2 -0
- package/dist/types/utils/handleEscapeCharacters.d.ts +2 -0
- package/dist/types/utils/index.d.ts +14 -0
- package/dist/types/utils/inputs.d.ts +3 -0
- package/dist/types/utils/interpolation.d.ts +1 -0
- package/dist/types/utils/misc.d.ts +1 -0
- package/dist/types/utils/newId.d.ts +1 -0
- package/dist/types/utils/openai.d.ts +739 -0
- package/dist/types/utils/outputs.d.ts +3 -0
- package/dist/types/utils/serialization/serialization.d.ts +12 -0
- package/dist/types/utils/serialization/serializationUtils.d.ts +6 -0
- package/dist/types/utils/serialization/serialization_v1.d.ts +3 -0
- package/dist/types/utils/serialization/serialization_v2.d.ts +3 -0
- package/dist/types/utils/serialization/serialization_v3.d.ts +19 -0
- package/dist/types/utils/serialization/serialization_v4.d.ts +9 -0
- package/dist/types/utils/symbols.d.ts +3 -0
- package/dist/types/utils/time.d.ts +22 -0
- package/dist/types/utils/typeSafety.d.ts +37 -0
- package/package.json +97 -0
|
@@ -0,0 +1,1198 @@
|
|
|
1
|
+
import { max, range, sum, uniqBy } from 'lodash-es';
|
|
2
|
+
import { isArrayDataValue, arrayizeDataValue, getScalarTypeOf, } from './DataValue.js';
|
|
3
|
+
import PQueueImport from 'p-queue';
|
|
4
|
+
import { getError } from '../utils/errors.js';
|
|
5
|
+
import Emittery from 'emittery';
|
|
6
|
+
import { entries, fromEntries, values } from '../utils/typeSafety.js';
|
|
7
|
+
import { isNotNull } from '../utils/genericUtilFunctions.js';
|
|
8
|
+
import { nanoid } from 'nanoid/non-secure';
|
|
9
|
+
import { P, match } from 'ts-pattern';
|
|
10
|
+
import { coerceTypeOptional } from '../utils/coerceType.js';
|
|
11
|
+
import { globalRivetNodeRegistry } from './Nodes.js';
|
|
12
|
+
import { getPluginConfig } from '../utils/index.js';
|
|
13
|
+
import { GptTokenizerTokenizer } from '../integrations/GptTokenizerTokenizer.js';
|
|
14
|
+
// CJS compatibility, gets default.default for whatever reason
|
|
15
|
+
let PQueue = PQueueImport;
|
|
16
|
+
if (typeof PQueue !== 'function') {
|
|
17
|
+
PQueue = PQueueImport.default;
|
|
18
|
+
}
|
|
19
|
+
export class GraphProcessor {
|
|
20
|
+
// Per-instance state
|
|
21
|
+
#graph;
|
|
22
|
+
#project;
|
|
23
|
+
#nodesById;
|
|
24
|
+
#nodeInstances;
|
|
25
|
+
#connections;
|
|
26
|
+
#definitions;
|
|
27
|
+
#emitter = new Emittery();
|
|
28
|
+
#running = false;
|
|
29
|
+
#isSubProcessor = false;
|
|
30
|
+
#scc;
|
|
31
|
+
#nodesNotInCycle;
|
|
32
|
+
#externalFunctions = {};
|
|
33
|
+
slowMode = false;
|
|
34
|
+
#isPaused = false;
|
|
35
|
+
#parent;
|
|
36
|
+
#registry;
|
|
37
|
+
id = nanoid();
|
|
38
|
+
executor;
|
|
39
|
+
/** If set, specifies the node(s) that the graph will run TO, instead of the nodes without any dependents. */
|
|
40
|
+
runToNodeIds;
|
|
41
|
+
/** The node that is executing this graph, almost always a subgraph node. Undefined for root. */
|
|
42
|
+
#executor;
|
|
43
|
+
/** The interval between nodeFinish events when playing back a recording. I.e. how fast the playback is. */
|
|
44
|
+
recordingPlaybackChatLatency = 1000;
|
|
45
|
+
warnOnInvalidGraph = false;
|
|
46
|
+
// Per-process state
|
|
47
|
+
#erroredNodes = undefined;
|
|
48
|
+
#remainingNodes = undefined;
|
|
49
|
+
#visitedNodes = undefined;
|
|
50
|
+
#currentlyProcessing = undefined;
|
|
51
|
+
#context = undefined;
|
|
52
|
+
#nodeResults = undefined;
|
|
53
|
+
#abortController = undefined;
|
|
54
|
+
#processingQueue = undefined;
|
|
55
|
+
#graphInputs = undefined;
|
|
56
|
+
#graphOutputs = undefined;
|
|
57
|
+
#executionCache = undefined;
|
|
58
|
+
#queuedNodes = undefined;
|
|
59
|
+
#loopControllersSeen = undefined;
|
|
60
|
+
#subprocessors = undefined;
|
|
61
|
+
#contextValues = undefined;
|
|
62
|
+
#globals = undefined;
|
|
63
|
+
#attachedNodeData = undefined;
|
|
64
|
+
#aborted = false;
|
|
65
|
+
#abortSuccessfully = false;
|
|
66
|
+
#abortError = undefined;
|
|
67
|
+
#totalCost = 0;
|
|
68
|
+
#ignoreNodes = undefined;
|
|
69
|
+
#nodeAbortControllers = new Map();
|
|
70
|
+
/** User input nodes that are pending user input. */
|
|
71
|
+
#pendingUserInputs = undefined;
|
|
72
|
+
get isRunning() {
|
|
73
|
+
return this.#running;
|
|
74
|
+
}
|
|
75
|
+
constructor(project, graphId, registry) {
|
|
76
|
+
this.#project = project;
|
|
77
|
+
const graph = graphId
|
|
78
|
+
? project.graphs[graphId]
|
|
79
|
+
: project.metadata.mainGraphId
|
|
80
|
+
? project.graphs[project.metadata.mainGraphId]
|
|
81
|
+
: undefined;
|
|
82
|
+
if (!graph) {
|
|
83
|
+
throw new Error(`Graph ${graphId} not found in project`);
|
|
84
|
+
}
|
|
85
|
+
this.#graph = graph;
|
|
86
|
+
this.#nodeInstances = {};
|
|
87
|
+
this.#connections = {};
|
|
88
|
+
this.#nodesById = {};
|
|
89
|
+
this.#registry = registry ?? globalRivetNodeRegistry;
|
|
90
|
+
this.#emitter.bindMethods(this, ['on', 'off', 'once', 'onAny', 'offAny']);
|
|
91
|
+
// Create node instances and store them in a lookup table
|
|
92
|
+
for (const node of this.#graph.nodes) {
|
|
93
|
+
this.#nodeInstances[node.id] = this.#registry.createDynamicImpl(node);
|
|
94
|
+
this.#nodesById[node.id] = node;
|
|
95
|
+
}
|
|
96
|
+
// Store connections in a lookup table
|
|
97
|
+
for (const conn of this.#graph.connections) {
|
|
98
|
+
if (!this.#nodesById[conn.inputNodeId] || !this.#nodesById[conn.outputNodeId]) {
|
|
99
|
+
if (this.warnOnInvalidGraph) {
|
|
100
|
+
if (!this.#nodesById[conn.inputNodeId]) {
|
|
101
|
+
console.warn(`Missing node ${conn.inputNodeId} in graph ${graphId} (connection from ${this.#nodesById[conn.outputNodeId]?.title})`);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
console.warn(`Missing node ${conn.outputNodeId} in graph ${graphId} (connection to ${this.#nodesById[conn.inputNodeId]?.title}) `);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
this.#connections[conn.inputNodeId] ??= [];
|
|
110
|
+
this.#connections[conn.outputNodeId] ??= [];
|
|
111
|
+
this.#connections[conn.inputNodeId].push(conn);
|
|
112
|
+
this.#connections[conn.outputNodeId].push(conn);
|
|
113
|
+
}
|
|
114
|
+
// Store input and output definitions in a lookup table
|
|
115
|
+
this.#definitions = {};
|
|
116
|
+
for (const node of this.#graph.nodes) {
|
|
117
|
+
const connectionsForNode = this.#connections[node.id] ?? [];
|
|
118
|
+
const inputDefs = this.#nodeInstances[node.id].getInputDefinitions(connectionsForNode, this.#nodesById, this.#project);
|
|
119
|
+
const outputDefs = this.#nodeInstances[node.id].getOutputDefinitions(connectionsForNode, this.#nodesById, this.#project);
|
|
120
|
+
this.#definitions[node.id] = { inputs: inputDefs, outputs: outputDefs };
|
|
121
|
+
// Find all invalid connections to or from the node, then remove them from consideration
|
|
122
|
+
const invalidConnections = connectionsForNode.filter((connection) => {
|
|
123
|
+
if (connection.inputNodeId === node.id) {
|
|
124
|
+
const inputDef = inputDefs.find((def) => def.id === connection.inputId);
|
|
125
|
+
if (!inputDef) {
|
|
126
|
+
if (this.warnOnInvalidGraph) {
|
|
127
|
+
const nodeFrom = this.#nodesById[connection.outputNodeId];
|
|
128
|
+
console.warn(`[Warn] Invalid connection going from "${nodeFrom?.title}".${connection.outputId} to "${node.title}".${connection.inputId}`);
|
|
129
|
+
}
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
const outputDef = outputDefs.find((def) => def.id === connection.outputId);
|
|
135
|
+
if (!outputDef) {
|
|
136
|
+
if (this.warnOnInvalidGraph) {
|
|
137
|
+
const nodeTo = this.#nodesById[connection.inputNodeId];
|
|
138
|
+
console.warn(`[Warn] Invalid connection going from "${node.title}".${connection.outputId} to "${nodeTo?.title}".${connection.inputId}`);
|
|
139
|
+
}
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return false;
|
|
144
|
+
});
|
|
145
|
+
for (const connections of values(this.#connections)) {
|
|
146
|
+
for (const invalidConnection of invalidConnections) {
|
|
147
|
+
const index = connections.indexOf(invalidConnection);
|
|
148
|
+
if (index !== -1) {
|
|
149
|
+
connections.splice(index, 1);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
this.#scc = this.#tarjanSCC();
|
|
155
|
+
this.#nodesNotInCycle = this.#scc.filter((cycle) => cycle.length === 1).flat();
|
|
156
|
+
this.setExternalFunction('echo', async (value) => ({ type: 'any', value }));
|
|
157
|
+
this.#emitter.on('globalSet', ({ id, value }) => {
|
|
158
|
+
this.#emitter.emit(`globalSet:${id}`, value);
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
on = undefined;
|
|
162
|
+
off = undefined;
|
|
163
|
+
once = undefined;
|
|
164
|
+
onAny = undefined;
|
|
165
|
+
offAny = undefined;
|
|
166
|
+
#onUserEventHandlers = new Map();
|
|
167
|
+
onUserEvent(onEvent, listener) {
|
|
168
|
+
const handler = (event, value) => {
|
|
169
|
+
if (event === `userEvent:${onEvent}`) {
|
|
170
|
+
listener(value);
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
this.#onUserEventHandlers.set(listener, handler);
|
|
174
|
+
this.#emitter.onAny(handler);
|
|
175
|
+
}
|
|
176
|
+
offUserEvent(listener) {
|
|
177
|
+
const internalHandler = this.#onUserEventHandlers.get(listener);
|
|
178
|
+
this.#emitter.offAny(internalHandler);
|
|
179
|
+
}
|
|
180
|
+
userInput(nodeId, values) {
|
|
181
|
+
const pending = this.#pendingUserInputs[nodeId];
|
|
182
|
+
if (pending) {
|
|
183
|
+
pending.resolve(values);
|
|
184
|
+
delete this.#pendingUserInputs[nodeId];
|
|
185
|
+
}
|
|
186
|
+
for (const processor of this.#subprocessors) {
|
|
187
|
+
processor.userInput(nodeId, values);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
setExternalFunction(name, fn) {
|
|
191
|
+
this.#externalFunctions[name] = fn;
|
|
192
|
+
}
|
|
193
|
+
async abort(successful = false, error) {
|
|
194
|
+
if (!this.#running || this.#aborted) {
|
|
195
|
+
return Promise.resolve();
|
|
196
|
+
}
|
|
197
|
+
this.#abortController.abort();
|
|
198
|
+
this.#abortSuccessfully = successful;
|
|
199
|
+
this.#abortError = error;
|
|
200
|
+
this.#emitter.emit('graphAbort', { successful, error, graph: this.#graph });
|
|
201
|
+
if (!this.#isSubProcessor) {
|
|
202
|
+
this.#emitter.emit('abort', { successful, error });
|
|
203
|
+
}
|
|
204
|
+
await this.#processingQueue.onIdle();
|
|
205
|
+
}
|
|
206
|
+
pause() {
|
|
207
|
+
if (this.#isPaused === false) {
|
|
208
|
+
this.#isPaused = true;
|
|
209
|
+
this.#emitter.emit('pause', void 0);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
resume() {
|
|
213
|
+
if (this.#isPaused) {
|
|
214
|
+
this.#isPaused = false;
|
|
215
|
+
this.#emitter.emit('resume', void 0);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
setSlowMode(slowMode) {
|
|
219
|
+
this.slowMode = slowMode;
|
|
220
|
+
}
|
|
221
|
+
async #waitUntilUnpaused() {
|
|
222
|
+
if (!this.#isPaused) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
await this.#emitter.once('resume');
|
|
226
|
+
}
|
|
227
|
+
async *events() {
|
|
228
|
+
for await (const [event, data] of this.#emitter.anyEvent()) {
|
|
229
|
+
yield { type: event, ...data };
|
|
230
|
+
if (event === 'finish') {
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
async replayRecording(recorder) {
|
|
236
|
+
const { events } = recorder;
|
|
237
|
+
this.#initProcessState();
|
|
238
|
+
try {
|
|
239
|
+
const nodesByIdAllGraphs = {};
|
|
240
|
+
for (const graph of Object.values(this.#project.graphs)) {
|
|
241
|
+
for (const node of graph.nodes) {
|
|
242
|
+
nodesByIdAllGraphs[node.id] = node;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
const getGraph = (graphId) => {
|
|
246
|
+
const graph = this.#project.graphs[graphId];
|
|
247
|
+
if (!graph) {
|
|
248
|
+
throw new Error(`Mismatch between project and recording: graph ${graphId} not found in project`);
|
|
249
|
+
}
|
|
250
|
+
return graph;
|
|
251
|
+
};
|
|
252
|
+
const getNode = (nodeId) => {
|
|
253
|
+
const node = nodesByIdAllGraphs[nodeId];
|
|
254
|
+
if (!node) {
|
|
255
|
+
throw new Error(`Mismatch between project and recording: node ${nodeId} not found in any graph in project`);
|
|
256
|
+
}
|
|
257
|
+
return node;
|
|
258
|
+
};
|
|
259
|
+
for (const event of events) {
|
|
260
|
+
if (this.#aborted) {
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
await this.#waitUntilUnpaused();
|
|
264
|
+
await match(event)
|
|
265
|
+
.with({ type: 'start' }, ({ data }) => {
|
|
266
|
+
this.#emitter.emit('start', {
|
|
267
|
+
project: this.#project,
|
|
268
|
+
contextValues: data.contextValues,
|
|
269
|
+
inputs: data.inputs,
|
|
270
|
+
startGraph: getGraph(data.startGraph),
|
|
271
|
+
});
|
|
272
|
+
this.#contextValues = data.contextValues;
|
|
273
|
+
this.#graphInputs = data.inputs;
|
|
274
|
+
})
|
|
275
|
+
.with({ type: 'abort' }, ({ data }) => {
|
|
276
|
+
this.#emitter.emit('abort', data);
|
|
277
|
+
})
|
|
278
|
+
.with({ type: 'pause' }, () => { })
|
|
279
|
+
.with({ type: 'resume' }, () => { })
|
|
280
|
+
.with({ type: 'done' }, ({ data }) => {
|
|
281
|
+
this.#emitter.emit('done', data);
|
|
282
|
+
this.#graphOutputs = data.results;
|
|
283
|
+
this.#running = false;
|
|
284
|
+
})
|
|
285
|
+
.with({ type: 'error' }, ({ data }) => {
|
|
286
|
+
this.#emitter.emit('error', data);
|
|
287
|
+
})
|
|
288
|
+
.with({ type: 'globalSet' }, ({ data }) => {
|
|
289
|
+
this.#emitter.emit('globalSet', data);
|
|
290
|
+
})
|
|
291
|
+
.with({ type: 'trace' }, ({ data }) => {
|
|
292
|
+
this.#emitter.emit('trace', data);
|
|
293
|
+
})
|
|
294
|
+
.with({ type: 'graphStart' }, ({ data }) => {
|
|
295
|
+
this.#emitter.emit('graphStart', {
|
|
296
|
+
graph: getGraph(data.graphId),
|
|
297
|
+
inputs: data.inputs,
|
|
298
|
+
});
|
|
299
|
+
})
|
|
300
|
+
.with({ type: 'graphFinish' }, ({ data }) => {
|
|
301
|
+
this.#emitter.emit('graphFinish', {
|
|
302
|
+
graph: getGraph(data.graphId),
|
|
303
|
+
outputs: data.outputs,
|
|
304
|
+
});
|
|
305
|
+
})
|
|
306
|
+
.with({ type: 'graphError' }, ({ data }) => {
|
|
307
|
+
this.#emitter.emit('graphError', {
|
|
308
|
+
graph: getGraph(data.graphId),
|
|
309
|
+
error: data.error,
|
|
310
|
+
});
|
|
311
|
+
})
|
|
312
|
+
.with({ type: 'graphAbort' }, ({ data }) => {
|
|
313
|
+
this.#emitter.emit('graphAbort', {
|
|
314
|
+
graph: getGraph(data.graphId),
|
|
315
|
+
error: data.error,
|
|
316
|
+
successful: data.successful,
|
|
317
|
+
});
|
|
318
|
+
})
|
|
319
|
+
.with({ type: 'nodeStart' }, async ({ data }) => {
|
|
320
|
+
const node = getNode(data.nodeId);
|
|
321
|
+
this.#emitter.emit('nodeStart', {
|
|
322
|
+
node: getNode(data.nodeId),
|
|
323
|
+
inputs: data.inputs,
|
|
324
|
+
processId: data.processId,
|
|
325
|
+
});
|
|
326
|
+
// Every time a chat node starts, we wait for the playback interval
|
|
327
|
+
if (node.type === 'chat') {
|
|
328
|
+
await new Promise((resolve) => setTimeout(resolve, this.recordingPlaybackChatLatency));
|
|
329
|
+
}
|
|
330
|
+
})
|
|
331
|
+
.with({ type: 'nodeFinish' }, ({ data }) => {
|
|
332
|
+
const node = getNode(data.nodeId);
|
|
333
|
+
this.#emitter.emit('nodeFinish', {
|
|
334
|
+
node,
|
|
335
|
+
outputs: data.outputs,
|
|
336
|
+
processId: data.processId,
|
|
337
|
+
});
|
|
338
|
+
this.#nodeResults.set(data.nodeId, data.outputs);
|
|
339
|
+
this.#visitedNodes.add(data.nodeId);
|
|
340
|
+
})
|
|
341
|
+
.with({ type: 'nodeError' }, ({ data }) => {
|
|
342
|
+
this.#emitter.emit('nodeError', {
|
|
343
|
+
node: getNode(data.nodeId),
|
|
344
|
+
error: data.error,
|
|
345
|
+
processId: data.processId,
|
|
346
|
+
});
|
|
347
|
+
this.#erroredNodes.set(data.nodeId, data.error);
|
|
348
|
+
this.#visitedNodes.add(data.nodeId);
|
|
349
|
+
})
|
|
350
|
+
.with({ type: 'nodeExcluded' }, ({ data }) => {
|
|
351
|
+
this.#emitter.emit('nodeExcluded', {
|
|
352
|
+
node: getNode(data.nodeId),
|
|
353
|
+
processId: data.processId,
|
|
354
|
+
inputs: data.inputs,
|
|
355
|
+
outputs: data.outputs,
|
|
356
|
+
reason: data.reason,
|
|
357
|
+
});
|
|
358
|
+
this.#visitedNodes.add(data.nodeId);
|
|
359
|
+
})
|
|
360
|
+
.with({ type: 'nodeOutputsCleared' }, () => { })
|
|
361
|
+
.with({ type: 'partialOutput' }, () => { })
|
|
362
|
+
.with({ type: 'userInput' }, ({ data }) => {
|
|
363
|
+
this.#emitter.emit('userInput', {
|
|
364
|
+
callback: undefined,
|
|
365
|
+
inputs: data.inputs,
|
|
366
|
+
node: getNode(data.nodeId),
|
|
367
|
+
processId: data.processId,
|
|
368
|
+
});
|
|
369
|
+
})
|
|
370
|
+
.with({ type: P.string.startsWith('globalSet:') }, ({ type, data }) => {
|
|
371
|
+
this.#emitter.emit(type, data);
|
|
372
|
+
})
|
|
373
|
+
.with({ type: P.string.startsWith('userEvent:') }, ({ type, data }) => {
|
|
374
|
+
this.#emitter.emit(type, data);
|
|
375
|
+
})
|
|
376
|
+
.with({ type: 'newAbortController' }, () => { })
|
|
377
|
+
.with({ type: 'finish' }, () => {
|
|
378
|
+
this.#emitter.emit('finish', undefined);
|
|
379
|
+
})
|
|
380
|
+
// .with(undefined, () => {})
|
|
381
|
+
.exhaustive();
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
catch (err) {
|
|
385
|
+
this.#emitter.emit('error', { error: getError(err) });
|
|
386
|
+
}
|
|
387
|
+
finally {
|
|
388
|
+
this.#running = false;
|
|
389
|
+
}
|
|
390
|
+
return this.#graphOutputs;
|
|
391
|
+
}
|
|
392
|
+
#initProcessState() {
|
|
393
|
+
this.#running = true;
|
|
394
|
+
this.#nodeResults = new Map();
|
|
395
|
+
this.#erroredNodes = new Map();
|
|
396
|
+
this.#visitedNodes = new Set();
|
|
397
|
+
this.#currentlyProcessing = new Set();
|
|
398
|
+
this.#remainingNodes = new Set(this.#graph.nodes.map((n) => n.id));
|
|
399
|
+
this.#pendingUserInputs = {};
|
|
400
|
+
this.#processingQueue = new PQueue({ concurrency: Infinity });
|
|
401
|
+
this.#graphOutputs = {};
|
|
402
|
+
this.#executionCache ??= new Map();
|
|
403
|
+
this.#queuedNodes = new Set();
|
|
404
|
+
this.#loopControllersSeen = new Set();
|
|
405
|
+
this.#subprocessors = new Set();
|
|
406
|
+
this.#attachedNodeData = new Map();
|
|
407
|
+
this.#globals ??= new Map();
|
|
408
|
+
this.#ignoreNodes = new Set();
|
|
409
|
+
this.#abortController = this.#newAbortController();
|
|
410
|
+
this.#abortController.signal.addEventListener('abort', () => {
|
|
411
|
+
this.#aborted = true;
|
|
412
|
+
});
|
|
413
|
+
this.#aborted = false;
|
|
414
|
+
this.#abortError = undefined;
|
|
415
|
+
this.#abortSuccessfully = false;
|
|
416
|
+
this.#nodeAbortControllers = new Map();
|
|
417
|
+
}
|
|
418
|
+
/** Main function for running a graph. Runs a graph and returns the outputs from the output nodes of the graph. */
|
|
419
|
+
async processGraph(
|
|
420
|
+
/** Required and optional context available to the nodes and all subgraphs. */
|
|
421
|
+
context,
|
|
422
|
+
/** Inputs to the main graph. You should pass all inputs required by the GraphInputNodes of the graph. */
|
|
423
|
+
inputs = {},
|
|
424
|
+
/** Contextual data available to all graphs and subgraphs. Kind of like react context, avoids drilling down data into subgraphs. Be careful when using it. */
|
|
425
|
+
contextValues = {}) {
|
|
426
|
+
try {
|
|
427
|
+
if (this.#running) {
|
|
428
|
+
throw new Error('Cannot process graph while already processing');
|
|
429
|
+
}
|
|
430
|
+
this.#initProcessState();
|
|
431
|
+
this.#context = context;
|
|
432
|
+
this.#graphInputs = inputs;
|
|
433
|
+
this.#contextValues ??= contextValues;
|
|
434
|
+
if (this.#context.tokenizer) {
|
|
435
|
+
this.#context.tokenizer.on('error', (error) => {
|
|
436
|
+
this.#emitter.emit('error', { error });
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
if (!this.#isSubProcessor) {
|
|
440
|
+
this.#emitter.emit('start', {
|
|
441
|
+
contextValues: this.#contextValues,
|
|
442
|
+
inputs: this.#graphInputs,
|
|
443
|
+
project: this.#project,
|
|
444
|
+
startGraph: this.#graph,
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
this.#emitter.emit('graphStart', { graph: this.#graph, inputs: this.#graphInputs });
|
|
448
|
+
const startNodes = this.runToNodeIds
|
|
449
|
+
? this.#graph.nodes.filter((node) => this.runToNodeIds?.includes(node.id))
|
|
450
|
+
: this.#graph.nodes.filter((node) => this.#outputNodesFrom(node).nodes.length === 0);
|
|
451
|
+
await this.#waitUntilUnpaused();
|
|
452
|
+
for (const startNode of startNodes) {
|
|
453
|
+
this.#processingQueue.add(async () => {
|
|
454
|
+
await this.#fetchNodeDataAndProcessNode(startNode);
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
// Anything not queued at this phase here should be ignored
|
|
458
|
+
if (this.runToNodeIds) {
|
|
459
|
+
// For safety, we'll only activate this if runToNodeIds is set, in case there are bugs in the first pass
|
|
460
|
+
for (const node of this.#graph.nodes) {
|
|
461
|
+
if (this.#queuedNodes.has(node.id) === false) {
|
|
462
|
+
this.#ignoreNodes.add(node.id);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
await this.#processingQueue.onIdle();
|
|
467
|
+
// If we've aborted successfully, we can treat the graph like it succeeded
|
|
468
|
+
const erroredNodes = [...this.#erroredNodes.entries()].filter(([nodeId]) => {
|
|
469
|
+
const erroredNodeAttachedData = this.#getAttachedDataTo(nodeId);
|
|
470
|
+
return erroredNodeAttachedData.races == null || erroredNodeAttachedData.races.completed === false;
|
|
471
|
+
});
|
|
472
|
+
if (erroredNodes.length && !this.#abortSuccessfully) {
|
|
473
|
+
const error = this.#abortError ??
|
|
474
|
+
Error(`Graph ${this.#graph.metadata.name} (${this.#graph.metadata.id}) failed to process due to errors in nodes: ${erroredNodes
|
|
475
|
+
.map(([nodeId, error]) => `${this.#nodesById[nodeId].title} (${nodeId}): ${error}`)
|
|
476
|
+
.join(', ')}`);
|
|
477
|
+
this.#emitter.emit('graphError', { graph: this.#graph, error });
|
|
478
|
+
if (!this.#isSubProcessor) {
|
|
479
|
+
this.#emitter.emit('error', { error });
|
|
480
|
+
}
|
|
481
|
+
throw error;
|
|
482
|
+
}
|
|
483
|
+
if (this.#graphOutputs['cost'] == null) {
|
|
484
|
+
this.#graphOutputs['cost'] = {
|
|
485
|
+
type: 'number',
|
|
486
|
+
value: this.#totalCost,
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
const outputValues = this.#graphOutputs;
|
|
490
|
+
this.#running = false;
|
|
491
|
+
this.#emitter.emit('graphFinish', { graph: this.#graph, outputs: outputValues });
|
|
492
|
+
if (!this.#isSubProcessor) {
|
|
493
|
+
this.#emitter.emit('done', { results: outputValues });
|
|
494
|
+
this.#emitter.emit('finish', undefined);
|
|
495
|
+
}
|
|
496
|
+
return outputValues;
|
|
497
|
+
}
|
|
498
|
+
finally {
|
|
499
|
+
this.#running = false;
|
|
500
|
+
if (!this.#isSubProcessor) {
|
|
501
|
+
this.#emitter.emit('finish', undefined);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
async #fetchNodeDataAndProcessNode(node) {
|
|
506
|
+
if (this.#currentlyProcessing.has(node.id) || this.#queuedNodes.has(node.id)) {
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
if (this.#nodeResults.has(node.id) || this.#erroredNodes.has(node.id)) {
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
const inputNodes = this.#inputNodesTo(node);
|
|
513
|
+
// Check if all input nodes are free of errors
|
|
514
|
+
for (const inputNode of inputNodes) {
|
|
515
|
+
if (this.#erroredNodes.has(inputNode.id)) {
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
// Check if all required inputs have connections and if the connected output nodes have been visited
|
|
520
|
+
const connections = this.#connections[node.id] ?? [];
|
|
521
|
+
const inputsReady = this.#definitions[node.id].inputs.every((input) => {
|
|
522
|
+
const connectionToInput = connections?.find((conn) => conn.inputId === input.id && conn.inputNodeId === node.id);
|
|
523
|
+
return connectionToInput || !input.required;
|
|
524
|
+
});
|
|
525
|
+
if (!inputsReady) {
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
this.#emitter.emit('trace', `Node ${node.title} has required inputs nodes: ${inputNodes.map((n) => n.title).join(', ')}`);
|
|
529
|
+
const attachedData = this.#getAttachedDataTo(node);
|
|
530
|
+
if (node.type === 'raceInputs' || attachedData.races) {
|
|
531
|
+
for (const inputNode of inputNodes) {
|
|
532
|
+
const inputNodeAttachedData = this.#getAttachedDataTo(inputNode);
|
|
533
|
+
const raceIds = new Set([...(attachedData.races?.raceIds ?? [])]);
|
|
534
|
+
if (node.type === 'raceInputs') {
|
|
535
|
+
raceIds.add(`race-${node.id}`);
|
|
536
|
+
}
|
|
537
|
+
inputNodeAttachedData.races = {
|
|
538
|
+
propagate: false,
|
|
539
|
+
raceIds: [...raceIds],
|
|
540
|
+
completed: false,
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
this.#queuedNodes.add(node.id);
|
|
545
|
+
this.#processingQueue.addAll(inputNodes.map((inputNode) => {
|
|
546
|
+
return async () => {
|
|
547
|
+
this.#emitter.emit('trace', `Fetching required data for node ${inputNode.title} (${inputNode.id})`);
|
|
548
|
+
await this.#fetchNodeDataAndProcessNode(inputNode);
|
|
549
|
+
};
|
|
550
|
+
}));
|
|
551
|
+
await this.#processNodeIfAllInputsAvailable(node);
|
|
552
|
+
}
|
|
553
|
+
/** If all inputs are present, all conditions met, processes the node. */
|
|
554
|
+
async #processNodeIfAllInputsAvailable(node) {
|
|
555
|
+
const builtInNode = node;
|
|
556
|
+
if (this.#ignoreNodes.has(node.id)) {
|
|
557
|
+
this.#emitter.emit('trace', `Node ${node.title} is ignored`);
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
if (this.#currentlyProcessing.has(node.id)) {
|
|
561
|
+
this.#emitter.emit('trace', `Node ${node.title} is already being processed`);
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
// For a loop controller, it can run multiple times, otherwise we already processed this node so bail out
|
|
565
|
+
if (this.#visitedNodes.has(node.id) && node.type !== 'loopController') {
|
|
566
|
+
this.#emitter.emit('trace', `Node ${node.title} has already been processed`);
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
if (this.#erroredNodes.has(node.id)) {
|
|
570
|
+
this.#emitter.emit('trace', `Node ${node.title} has already errored`);
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
const inputNodes = this.#inputNodesTo(node);
|
|
574
|
+
// Check if all input nodes are free of errors
|
|
575
|
+
for (const inputNode of inputNodes) {
|
|
576
|
+
if (this.#erroredNodes.has(inputNode.id)) {
|
|
577
|
+
this.#emitter.emit('trace', `Node ${node.title} has errored input node ${inputNode.title}`);
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
// Check if all required inputs have connections and if the connected output nodes have been visited
|
|
582
|
+
const connections = this.#connections[node.id] ?? [];
|
|
583
|
+
const inputsReady = this.#definitions[node.id].inputs.every((input) => {
|
|
584
|
+
const connectionToInput = connections?.find((conn) => conn.inputId === input.id && conn.inputNodeId === node.id);
|
|
585
|
+
return connectionToInput || !input.required;
|
|
586
|
+
});
|
|
587
|
+
if (!inputsReady) {
|
|
588
|
+
this.#emitter.emit('trace', `Node ${node.title} has required inputs nodes: ${inputNodes.map((n) => n.title).join(', ')}`);
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
// Excluded because control flow is still in a loop - difference between "will not execute" and "has not executed yet"
|
|
592
|
+
const inputValues = this.#getInputValuesForNode(node);
|
|
593
|
+
if (this.#excludedDueToControlFlow(node, inputValues, nanoid(), 'loop-not-broken')) {
|
|
594
|
+
this.#emitter.emit('trace', `Node ${node.title} is excluded due to control flow`);
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
let waitingForInputNode = false;
|
|
598
|
+
const anyInputIsValid = Object.values(inputValues).some((value) => value && value.type.includes('control-flow-excluded') === false);
|
|
599
|
+
for (const inputNode of inputNodes) {
|
|
600
|
+
// For loop controllers, allow nodes in the same cycle to be not processed yet,
|
|
601
|
+
// but if we're in a 2nd iteration, we do need to wait for them
|
|
602
|
+
if (node.type === 'loopController' &&
|
|
603
|
+
!this.#loopControllersSeen.has(node.id) &&
|
|
604
|
+
this.#nodesAreInSameCycle(node.id, inputNode.id)) {
|
|
605
|
+
continue;
|
|
606
|
+
}
|
|
607
|
+
// Only one visited node required for a raceInputs node
|
|
608
|
+
if (node.type === 'raceInputs' && this.#visitedNodes.has(inputNode.id) && anyInputIsValid) {
|
|
609
|
+
waitingForInputNode = false;
|
|
610
|
+
break;
|
|
611
|
+
}
|
|
612
|
+
if (waitingForInputNode === false && this.#visitedNodes.has(inputNode.id) === false) {
|
|
613
|
+
waitingForInputNode = inputNode.title;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
if (waitingForInputNode) {
|
|
617
|
+
this.#emitter.emit('trace', `Node ${node.title} is waiting for input node ${waitingForInputNode}`);
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
this.#currentlyProcessing.add(node.id);
|
|
621
|
+
if (node.type === 'loopController') {
|
|
622
|
+
this.#loopControllersSeen.add(node.id);
|
|
623
|
+
}
|
|
624
|
+
const attachedData = this.#getAttachedDataTo(node);
|
|
625
|
+
if (attachedData.loopInfo && attachedData.loopInfo.loopControllerId !== node.id) {
|
|
626
|
+
attachedData.loopInfo.nodes.add(node.id);
|
|
627
|
+
}
|
|
628
|
+
if (attachedData.races?.completed) {
|
|
629
|
+
this.#emitter.emit('trace', `Node ${node.title} is part of a race that was completed`);
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
const processId = await this.#processNode(node);
|
|
633
|
+
if (this.slowMode) {
|
|
634
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
635
|
+
}
|
|
636
|
+
this.#emitter.emit('trace', `Finished processing node ${node.title} (${node.id})`);
|
|
637
|
+
this.#visitedNodes.add(node.id);
|
|
638
|
+
this.#currentlyProcessing.delete(node.id);
|
|
639
|
+
this.#remainingNodes.delete(node.id);
|
|
640
|
+
const outputNodes = this.#outputNodesFrom(node);
|
|
641
|
+
// Aggressive - each iteration of the loop controller, we clear everything in the same cycle as the controller
|
|
642
|
+
if (node.type === 'loopController') {
|
|
643
|
+
const loopControllerResults = this.#nodeResults.get(node.id);
|
|
644
|
+
// If the loop controller is excluded, we have to "break" it or else it'll loop forever...
|
|
645
|
+
const breakValue = loopControllerResults['break'];
|
|
646
|
+
const didBreak = !(breakValue?.type === 'control-flow-excluded' && breakValue?.value === 'loop-not-broken') ??
|
|
647
|
+
this.#excludedDueToControlFlow(node, this.#getInputValuesForNode(node), nanoid());
|
|
648
|
+
if (!didBreak) {
|
|
649
|
+
this.#emitter.emit('trace', `Loop controller ${node.title} did not break, so we're looping again`);
|
|
650
|
+
for (const loopNodeId of attachedData.loopInfo?.nodes ?? []) {
|
|
651
|
+
const cycleNode = this.#nodesById[loopNodeId];
|
|
652
|
+
this.#emitter.emit('trace', `Clearing cycle node ${cycleNode.title} (${cycleNode.id})`);
|
|
653
|
+
this.#visitedNodes.delete(cycleNode.id);
|
|
654
|
+
this.#currentlyProcessing.delete(cycleNode.id);
|
|
655
|
+
this.#remainingNodes.add(cycleNode.id);
|
|
656
|
+
this.#nodeResults.delete(cycleNode.id);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
// Abort everything the race depends on - everything already executed won't
|
|
661
|
+
// be aborted, but everything that hasn't will be, effectively terminating all slower branches
|
|
662
|
+
if (node.type === 'raceInputs') {
|
|
663
|
+
const allNodesForRace = [...this.#attachedNodeData.entries()].filter(([, { races }]) => races?.raceIds.includes(`race-${node.id}`));
|
|
664
|
+
for (const [nodeId] of allNodesForRace) {
|
|
665
|
+
for (const [key, abortController] of this.#nodeAbortControllers.entries()) {
|
|
666
|
+
if (key.startsWith(nodeId)) {
|
|
667
|
+
this.#emitter.emit('trace', `Aborting node ${nodeId} because other race branch won`);
|
|
668
|
+
abortController.abort();
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
// Mark every attached data as completed for the race
|
|
672
|
+
for (const [, nodeAttachedData] of [...this.#attachedNodeData.entries()]) {
|
|
673
|
+
if (nodeAttachedData.races?.raceIds.includes(`race-${node.id}`)) {
|
|
674
|
+
nodeAttachedData.races.completed = true;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
let childLoopInfo = attachedData.loopInfo;
|
|
680
|
+
if (builtInNode.type === 'loopController') {
|
|
681
|
+
if (childLoopInfo != null && childLoopInfo.loopControllerId !== builtInNode.id) {
|
|
682
|
+
this.#nodeErrored(node, new Error('Nested loops are not supported'), processId);
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
childLoopInfo = {
|
|
686
|
+
propagate: (parent, connectionsFromParent) => {
|
|
687
|
+
if (parent.type === 'loopController' &&
|
|
688
|
+
connectionsFromParent.some((c) => c.outputId === 'break')) {
|
|
689
|
+
return false;
|
|
690
|
+
}
|
|
691
|
+
return true;
|
|
692
|
+
},
|
|
693
|
+
loopControllerId: node.id,
|
|
694
|
+
// We want to be able to clear every node that _potentially_ could run in the loop
|
|
695
|
+
nodes: childLoopInfo?.nodes ?? new Set(),
|
|
696
|
+
iterationCount: (childLoopInfo?.iterationCount ?? 0) + 1,
|
|
697
|
+
};
|
|
698
|
+
attachedData.loopInfo = childLoopInfo; // Not 100% sure if this is right - sets the childLoopInfo on the loop controller itself, probably fine?
|
|
699
|
+
}
|
|
700
|
+
for (const { node: outputNode, connections: connectionsToOutputNode } of outputNodes.connectionsToNodes) {
|
|
701
|
+
const outputNodeAttachedData = this.#getAttachedDataTo(outputNode);
|
|
702
|
+
const propagatedAttachedData = Object.entries(attachedData).filter(([, value]) => {
|
|
703
|
+
if (!value) {
|
|
704
|
+
return false;
|
|
705
|
+
}
|
|
706
|
+
if (typeof value.propagate === 'boolean') {
|
|
707
|
+
return value.propagate;
|
|
708
|
+
}
|
|
709
|
+
return value.propagate(node, connectionsToOutputNode);
|
|
710
|
+
});
|
|
711
|
+
for (const [key, value] of propagatedAttachedData) {
|
|
712
|
+
outputNodeAttachedData[key] = value;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
// Node is finished, check if we can run any more nodes that depend on this one
|
|
716
|
+
this.#processingQueue.addAll(outputNodes.nodes.map((outputNode) => async () => {
|
|
717
|
+
this.#emitter.emit('trace', `Trying to run output node from ${node.title}: ${outputNode.title} (${outputNode.id})`);
|
|
718
|
+
await this.#processNodeIfAllInputsAvailable(outputNode);
|
|
719
|
+
}));
|
|
720
|
+
}
|
|
721
|
+
#getAttachedDataTo(node) {
|
|
722
|
+
const nodeId = typeof node === 'string' ? node : node.id;
|
|
723
|
+
let nodeData = this.#attachedNodeData.get(nodeId);
|
|
724
|
+
if (nodeData == null) {
|
|
725
|
+
nodeData = {};
|
|
726
|
+
this.#attachedNodeData.set(nodeId, nodeData);
|
|
727
|
+
}
|
|
728
|
+
return nodeData;
|
|
729
|
+
}
|
|
730
|
+
async #processNode(node) {
|
|
731
|
+
const processId = nanoid();
|
|
732
|
+
if (this.#abortController.signal.aborted) {
|
|
733
|
+
this.#nodeErrored(node, new Error('Processing aborted'), processId);
|
|
734
|
+
return processId;
|
|
735
|
+
}
|
|
736
|
+
const inputNodes = this.#inputNodesTo(node);
|
|
737
|
+
const erroredInputNodes = inputNodes.filter((inputNode) => this.#erroredNodes.has(inputNode.id));
|
|
738
|
+
if (erroredInputNodes.length > 0) {
|
|
739
|
+
const error = new Error(`Cannot process node ${node.title} (${node.id}) because it depends on errored nodes: ${erroredInputNodes
|
|
740
|
+
.map((n) => `${n.title} (${n.id})`)
|
|
741
|
+
.join(', ')}`);
|
|
742
|
+
this.#nodeErrored(node, error, processId);
|
|
743
|
+
return processId;
|
|
744
|
+
}
|
|
745
|
+
if (this.#isNodeOfType('userInput', node)) {
|
|
746
|
+
await this.#processUserInputNode(node, processId);
|
|
747
|
+
}
|
|
748
|
+
else if (node.isSplitRun) {
|
|
749
|
+
await this.#processSplitRunNode(node, processId);
|
|
750
|
+
}
|
|
751
|
+
else {
|
|
752
|
+
await this.#processNormalNode(node, processId);
|
|
753
|
+
}
|
|
754
|
+
return processId;
|
|
755
|
+
}
|
|
756
|
+
#isNodeOfType(type, node) {
|
|
757
|
+
return node.type === type;
|
|
758
|
+
}
|
|
759
|
+
async #processUserInputNode(node, processId) {
|
|
760
|
+
try {
|
|
761
|
+
const inputValues = this.#getInputValuesForNode(node);
|
|
762
|
+
if (this.#excludedDueToControlFlow(node, inputValues, processId)) {
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
this.#emitter.emit('nodeStart', { node, inputs: inputValues, processId });
|
|
766
|
+
const results = await new Promise((resolve, reject) => {
|
|
767
|
+
this.#pendingUserInputs[node.id] = {
|
|
768
|
+
resolve,
|
|
769
|
+
reject,
|
|
770
|
+
};
|
|
771
|
+
this.#abortController.signal.addEventListener('abort', () => {
|
|
772
|
+
delete this.#pendingUserInputs[node.id];
|
|
773
|
+
reject(new Error('Processing aborted'));
|
|
774
|
+
});
|
|
775
|
+
this.#emitter.emit('userInput', {
|
|
776
|
+
node,
|
|
777
|
+
inputs: inputValues,
|
|
778
|
+
callback: (results) => {
|
|
779
|
+
resolve(results);
|
|
780
|
+
delete this.#pendingUserInputs[node.id];
|
|
781
|
+
},
|
|
782
|
+
processId,
|
|
783
|
+
});
|
|
784
|
+
});
|
|
785
|
+
const outputValues = this.#nodeInstances[node.id].getOutputValuesFromUserInput(inputValues, results);
|
|
786
|
+
this.#nodeResults.set(node.id, outputValues);
|
|
787
|
+
this.#visitedNodes.add(node.id);
|
|
788
|
+
this.#emitter.emit('nodeFinish', { node, outputs: outputValues, processId });
|
|
789
|
+
}
|
|
790
|
+
catch (e) {
|
|
791
|
+
this.#nodeErrored(node, e, processId);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
async #processSplitRunNode(node, processId) {
|
|
795
|
+
const inputValues = this.#getInputValuesForNode(node);
|
|
796
|
+
if (this.#excludedDueToControlFlow(node, inputValues, processId)) {
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
const splittingAmount = Math.min(max(values(inputValues).map((value) => (Array.isArray(value?.value) ? value?.value.length : 1))) ?? 1, node.splitRunMax ?? 10);
|
|
800
|
+
this.#emitter.emit('nodeStart', { node, inputs: inputValues, processId });
|
|
801
|
+
try {
|
|
802
|
+
let results = [];
|
|
803
|
+
if (node.isSplitSequential) {
|
|
804
|
+
for (let i = 0; i < splittingAmount; i++) {
|
|
805
|
+
if (this.#aborted) {
|
|
806
|
+
throw new Error('Processing aborted');
|
|
807
|
+
}
|
|
808
|
+
const inputs = fromEntries(entries(inputValues).map(([port, value]) => [
|
|
809
|
+
port,
|
|
810
|
+
isArrayDataValue(value) ? arrayizeDataValue(value)[i] ?? undefined : value,
|
|
811
|
+
]));
|
|
812
|
+
try {
|
|
813
|
+
const output = await this.#processNodeWithInputData(node, inputs, i, processId, (node, partialOutputs, index) => this.#emitter.emit('partialOutput', { node, outputs: partialOutputs, index, processId }));
|
|
814
|
+
if (output['cost']?.type === 'number') {
|
|
815
|
+
this.#totalCost += coerceTypeOptional(output['cost'], 'number') ?? 0;
|
|
816
|
+
}
|
|
817
|
+
results.push({ type: 'output', output });
|
|
818
|
+
}
|
|
819
|
+
catch (error) {
|
|
820
|
+
results.push({ type: 'error', error: getError(error) });
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
else {
|
|
825
|
+
results = await Promise.all(range(0, splittingAmount).map(async (i) => {
|
|
826
|
+
const inputs = fromEntries(entries(inputValues).map(([port, value]) => [
|
|
827
|
+
port,
|
|
828
|
+
isArrayDataValue(value) ? arrayizeDataValue(value)[i] ?? undefined : value,
|
|
829
|
+
]));
|
|
830
|
+
try {
|
|
831
|
+
const output = await this.#processNodeWithInputData(node, inputs, i, processId, (node, partialOutputs, index) => this.#emitter.emit('partialOutput', { node, outputs: partialOutputs, index, processId }));
|
|
832
|
+
if (output['cost']?.type === 'number') {
|
|
833
|
+
this.#totalCost += coerceTypeOptional(output['cost'], 'number') ?? 0;
|
|
834
|
+
}
|
|
835
|
+
return { type: 'output', output };
|
|
836
|
+
}
|
|
837
|
+
catch (error) {
|
|
838
|
+
return { type: 'error', error: getError(error) };
|
|
839
|
+
}
|
|
840
|
+
}));
|
|
841
|
+
}
|
|
842
|
+
const errors = results.filter((r) => r.type === 'error').map((r) => r.error);
|
|
843
|
+
if (errors.length === 1) {
|
|
844
|
+
const e = errors[0];
|
|
845
|
+
throw e;
|
|
846
|
+
}
|
|
847
|
+
else if (errors.length > 0) {
|
|
848
|
+
throw new Error(errors.join('\n'));
|
|
849
|
+
}
|
|
850
|
+
// Combine the parallel results into the final output
|
|
851
|
+
// Turn a Record<PortId, DataValue[]> into a Record<PortId, AnyArrayDataValue>
|
|
852
|
+
const aggregateResults = results.reduce((acc, result) => {
|
|
853
|
+
for (const [portId, value] of entries(result.output)) {
|
|
854
|
+
acc[portId] ??= { type: (value?.type + '[]'), value: [] };
|
|
855
|
+
acc[portId].value.push(value?.value);
|
|
856
|
+
}
|
|
857
|
+
return acc;
|
|
858
|
+
}, {});
|
|
859
|
+
this.#nodeResults.set(node.id, aggregateResults);
|
|
860
|
+
this.#visitedNodes.add(node.id);
|
|
861
|
+
this.#totalCost += sum(results.map((r) => coerceTypeOptional(r.output?.['cost'], 'number') ?? 0));
|
|
862
|
+
this.#emitter.emit('nodeFinish', { node, outputs: aggregateResults, processId });
|
|
863
|
+
}
|
|
864
|
+
catch (error) {
|
|
865
|
+
this.#nodeErrored(node, error, processId);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
async #processNormalNode(node, processId) {
|
|
869
|
+
const inputValues = this.#getInputValuesForNode(node);
|
|
870
|
+
if (this.#excludedDueToControlFlow(node, inputValues, processId)) {
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
this.#emitter.emit('nodeStart', { node, inputs: inputValues, processId });
|
|
874
|
+
try {
|
|
875
|
+
const outputValues = await this.#processNodeWithInputData(node, inputValues, 0, processId, (node, partialOutputs, index) => this.#emitter.emit('partialOutput', { node, outputs: partialOutputs, index, processId }));
|
|
876
|
+
this.#nodeResults.set(node.id, outputValues);
|
|
877
|
+
this.#visitedNodes.add(node.id);
|
|
878
|
+
if (outputValues['cost']?.type === 'number') {
|
|
879
|
+
this.#totalCost += coerceTypeOptional(outputValues['cost'], 'number') ?? 0;
|
|
880
|
+
}
|
|
881
|
+
this.#emitter.emit('nodeFinish', { node, outputs: outputValues, processId });
|
|
882
|
+
}
|
|
883
|
+
catch (error) {
|
|
884
|
+
this.#nodeErrored(node, error, processId);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
#nodeErrored(node, e, processId) {
|
|
888
|
+
const error = getError(e);
|
|
889
|
+
this.#emitter.emit('nodeError', { node, error, processId });
|
|
890
|
+
this.#emitter.emit('trace', `Node ${node.title} (${node.id}-${processId}) errored: ${error.stack}`);
|
|
891
|
+
this.#erroredNodes.set(node.id, error.toString());
|
|
892
|
+
}
|
|
893
|
+
getRootProcessor() {
|
|
894
|
+
let processor = this;
|
|
895
|
+
while (processor.#parent) {
|
|
896
|
+
processor = processor.#parent;
|
|
897
|
+
}
|
|
898
|
+
return processor;
|
|
899
|
+
}
|
|
900
|
+
/** Raise a user event on the processor, all subprocessors, and their children. */
|
|
901
|
+
raiseEvent(event, data) {
|
|
902
|
+
this.#emitter.emit(`userEvent:${event}`, data);
|
|
903
|
+
for (const subprocessor of this.#subprocessors) {
|
|
904
|
+
subprocessor.raiseEvent(event, data);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
#newAbortController() {
|
|
908
|
+
const controller = new AbortController();
|
|
909
|
+
this.#emitter.emit('newAbortController', controller);
|
|
910
|
+
return controller;
|
|
911
|
+
}
|
|
912
|
+
async #processNodeWithInputData(node, inputValues, index, processId, partialOutput) {
|
|
913
|
+
const instance = this.#nodeInstances[node.id];
|
|
914
|
+
const nodeAbortController = this.#newAbortController();
|
|
915
|
+
const abortListener = () => {
|
|
916
|
+
nodeAbortController.abort();
|
|
917
|
+
};
|
|
918
|
+
this.#nodeAbortControllers.set(`${node.id}-${processId}`, nodeAbortController);
|
|
919
|
+
this.#abortController.signal.addEventListener('abort', abortListener);
|
|
920
|
+
const plugin = this.#registry.getPluginFor(node.type);
|
|
921
|
+
let tokenizer = this.#context.tokenizer;
|
|
922
|
+
if (!tokenizer) {
|
|
923
|
+
tokenizer = new GptTokenizerTokenizer();
|
|
924
|
+
tokenizer.on('error', (e) => this.#emitter.emit('error', { error: e }));
|
|
925
|
+
}
|
|
926
|
+
const context = {
|
|
927
|
+
...this.#context,
|
|
928
|
+
node,
|
|
929
|
+
tokenizer,
|
|
930
|
+
executor: this.executor ?? 'nodejs',
|
|
931
|
+
project: this.#project,
|
|
932
|
+
executionCache: this.#executionCache,
|
|
933
|
+
graphInputs: this.#graphInputs,
|
|
934
|
+
graphOutputs: this.#graphOutputs,
|
|
935
|
+
attachedData: this.#getAttachedDataTo(node),
|
|
936
|
+
waitEvent: async (event) => {
|
|
937
|
+
return new Promise((resolve, reject) => {
|
|
938
|
+
this.#emitter.once(`userEvent:${event}`).then(resolve).catch(reject);
|
|
939
|
+
nodeAbortController.signal.addEventListener('abort', () => {
|
|
940
|
+
reject(new Error('Process aborted'));
|
|
941
|
+
});
|
|
942
|
+
});
|
|
943
|
+
},
|
|
944
|
+
raiseEvent: (event, data) => {
|
|
945
|
+
this.getRootProcessor().raiseEvent(event, data);
|
|
946
|
+
},
|
|
947
|
+
contextValues: this.#contextValues,
|
|
948
|
+
externalFunctions: { ...this.#externalFunctions },
|
|
949
|
+
onPartialOutputs: (partialOutputs) => {
|
|
950
|
+
partialOutput?.(node, partialOutputs, index);
|
|
951
|
+
const { useAsGraphPartialOutput } = node.data ?? {};
|
|
952
|
+
if (useAsGraphPartialOutput && this.#executor && this.#parent) {
|
|
953
|
+
const executorNode = this.#parent.#nodesById[this.#executor.nodeId];
|
|
954
|
+
if (executorNode) {
|
|
955
|
+
this.#emitter.emit('partialOutput', {
|
|
956
|
+
index: this.#executor.index,
|
|
957
|
+
node: executorNode,
|
|
958
|
+
outputs: partialOutputs,
|
|
959
|
+
processId: this.#executor.processId,
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
},
|
|
964
|
+
signal: nodeAbortController.signal,
|
|
965
|
+
processId,
|
|
966
|
+
getGlobal: (id) => this.#globals.get(id),
|
|
967
|
+
setGlobal: (id, value) => {
|
|
968
|
+
this.#globals.set(id, value);
|
|
969
|
+
this.#emitter.emit('globalSet', { id, value, processId });
|
|
970
|
+
},
|
|
971
|
+
waitForGlobal: async (id) => {
|
|
972
|
+
if (this.#globals.has(id)) {
|
|
973
|
+
return this.#globals.get(id);
|
|
974
|
+
}
|
|
975
|
+
await this.getRootProcessor().#emitter.once(`globalSet:${id}`);
|
|
976
|
+
return this.#globals.get(id);
|
|
977
|
+
},
|
|
978
|
+
createSubProcessor: (subGraphId, { signal } = {}) => {
|
|
979
|
+
const processor = new GraphProcessor(this.#project, subGraphId, this.#registry);
|
|
980
|
+
processor.executor = this.executor;
|
|
981
|
+
processor.#isSubProcessor = true;
|
|
982
|
+
processor.#executionCache = this.#executionCache;
|
|
983
|
+
processor.#externalFunctions = this.#externalFunctions;
|
|
984
|
+
processor.#contextValues = this.#contextValues;
|
|
985
|
+
processor.#parent = this;
|
|
986
|
+
processor.#globals = this.#globals;
|
|
987
|
+
processor.#executor = {
|
|
988
|
+
nodeId: node.id,
|
|
989
|
+
index,
|
|
990
|
+
processId,
|
|
991
|
+
};
|
|
992
|
+
processor.on('nodeError', (e) => this.#emitter.emit('nodeError', e));
|
|
993
|
+
processor.on('nodeFinish', (e) => this.#emitter.emit('nodeFinish', e));
|
|
994
|
+
processor.on('partialOutput', (e) => this.#emitter.emit('partialOutput', e));
|
|
995
|
+
processor.on('nodeExcluded', (e) => this.#emitter.emit('nodeExcluded', e));
|
|
996
|
+
processor.on('nodeStart', (e) => this.#emitter.emit('nodeStart', e));
|
|
997
|
+
processor.on('graphAbort', (e) => this.#emitter.emit('graphAbort', e));
|
|
998
|
+
processor.on('userInput', (e) => this.#emitter.emit('userInput', e)); // TODO!
|
|
999
|
+
processor.on('graphStart', (e) => this.#emitter.emit('graphStart', e));
|
|
1000
|
+
processor.on('graphFinish', (e) => this.#emitter.emit('graphFinish', e));
|
|
1001
|
+
processor.on('globalSet', (e) => this.#emitter.emit('globalSet', e));
|
|
1002
|
+
processor.on('pause', () => {
|
|
1003
|
+
if (!this.#isPaused) {
|
|
1004
|
+
this.pause();
|
|
1005
|
+
}
|
|
1006
|
+
});
|
|
1007
|
+
processor.on('resume', () => {
|
|
1008
|
+
if (this.#isPaused) {
|
|
1009
|
+
this.resume();
|
|
1010
|
+
}
|
|
1011
|
+
});
|
|
1012
|
+
processor.onAny((event, data) => {
|
|
1013
|
+
if (event.startsWith('globalSet:')) {
|
|
1014
|
+
this.#emitter.emit(event, data);
|
|
1015
|
+
}
|
|
1016
|
+
});
|
|
1017
|
+
this.#subprocessors.add(processor);
|
|
1018
|
+
if (signal) {
|
|
1019
|
+
signal.addEventListener('abort', () => processor.abort());
|
|
1020
|
+
}
|
|
1021
|
+
// If parent is aborted, abort subgraph with error (it's fine, success state is on the parent)
|
|
1022
|
+
this.#abortController.signal.addEventListener('abort', () => processor.abort());
|
|
1023
|
+
this.on('pause', () => processor.pause());
|
|
1024
|
+
this.on('resume', () => processor.resume());
|
|
1025
|
+
return processor;
|
|
1026
|
+
},
|
|
1027
|
+
trace: (message) => {
|
|
1028
|
+
this.#emitter.emit('trace', message);
|
|
1029
|
+
},
|
|
1030
|
+
abortGraph: (error) => {
|
|
1031
|
+
this.abort(error === undefined, error);
|
|
1032
|
+
},
|
|
1033
|
+
getPluginConfig: (name) => getPluginConfig(plugin, this.#context.settings, name),
|
|
1034
|
+
};
|
|
1035
|
+
await this.#waitUntilUnpaused();
|
|
1036
|
+
const results = await instance.process(inputValues, context);
|
|
1037
|
+
this.#nodeAbortControllers.delete(`${node.id}-${processId}`);
|
|
1038
|
+
this.#abortController.signal.removeEventListener('abort', abortListener);
|
|
1039
|
+
if (nodeAbortController.signal.aborted) {
|
|
1040
|
+
throw new Error('Aborted');
|
|
1041
|
+
}
|
|
1042
|
+
return results;
|
|
1043
|
+
}
|
|
1044
|
+
#excludedDueToControlFlow(node, inputValues, processId, typeOfExclusion = undefined) {
|
|
1045
|
+
if (node.disabled) {
|
|
1046
|
+
this.#emitter.emit('trace', `Excluding node ${node.title} because it's disabled`);
|
|
1047
|
+
this.#visitedNodes.add(node.id);
|
|
1048
|
+
this.#markAsExcluded(node, processId, inputValues, 'disabled');
|
|
1049
|
+
return true;
|
|
1050
|
+
}
|
|
1051
|
+
const inputsWithValues = entries(inputValues);
|
|
1052
|
+
const controlFlowExcludedValues = inputsWithValues.filter(([, value]) => value &&
|
|
1053
|
+
getScalarTypeOf(value.type) === 'control-flow-excluded' &&
|
|
1054
|
+
(!typeOfExclusion || value.value === typeOfExclusion));
|
|
1055
|
+
const inputIsExcludedValue = inputsWithValues.length > 0 && controlFlowExcludedValues.length > 0;
|
|
1056
|
+
const isWaitingForLoop = controlFlowExcludedValues.some((value) => value?.[1]?.value === 'loop-not-broken');
|
|
1057
|
+
const nodesAllowedToConsumeExcludedValue = [
|
|
1058
|
+
'if',
|
|
1059
|
+
'ifElse',
|
|
1060
|
+
'coalesce',
|
|
1061
|
+
'graphOutput',
|
|
1062
|
+
'raceInputs',
|
|
1063
|
+
'loopController',
|
|
1064
|
+
];
|
|
1065
|
+
const allowedToConsumedExcludedValue = nodesAllowedToConsumeExcludedValue.includes(node.type) && !isWaitingForLoop;
|
|
1066
|
+
if (inputIsExcludedValue && !allowedToConsumedExcludedValue) {
|
|
1067
|
+
if (!isWaitingForLoop) {
|
|
1068
|
+
if (inputIsExcludedValue) {
|
|
1069
|
+
this.#emitter.emit('trace', `Excluding node ${node.title} because of control flow. Input is has excluded value: ${controlFlowExcludedValues[0]?.[0]}`);
|
|
1070
|
+
}
|
|
1071
|
+
this.#visitedNodes.add(node.id);
|
|
1072
|
+
this.#markAsExcluded(node, processId, inputValues, 'input is excluded value');
|
|
1073
|
+
}
|
|
1074
|
+
return true;
|
|
1075
|
+
}
|
|
1076
|
+
return false;
|
|
1077
|
+
}
|
|
1078
|
+
#markAsExcluded(node, processId, inputValues, reason) {
|
|
1079
|
+
const outputs = {};
|
|
1080
|
+
for (const output of this.#definitions[node.id].outputs) {
|
|
1081
|
+
outputs[output.id] = { type: 'control-flow-excluded', value: undefined };
|
|
1082
|
+
}
|
|
1083
|
+
// Prevent infinite loop, a control-flow-excluded to loop controller shouldn't set the break port, let the loop controller handle it
|
|
1084
|
+
if (node.type === 'loopController') {
|
|
1085
|
+
outputs['break'] = { type: 'control-flow-excluded', value: 'loop-not-broken' };
|
|
1086
|
+
}
|
|
1087
|
+
this.#nodeResults.set(node.id, outputs);
|
|
1088
|
+
this.#emitter.emit('nodeExcluded', {
|
|
1089
|
+
node,
|
|
1090
|
+
processId,
|
|
1091
|
+
inputs: inputValues,
|
|
1092
|
+
outputs,
|
|
1093
|
+
reason,
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
1096
|
+
#getInputValuesForNode(node) {
|
|
1097
|
+
const connections = this.#connections[node.id];
|
|
1098
|
+
return this.#definitions[node.id].inputs.reduce((values, input) => {
|
|
1099
|
+
if (!connections) {
|
|
1100
|
+
return values;
|
|
1101
|
+
}
|
|
1102
|
+
const connection = connections.find((conn) => conn.inputId === input.id && conn.inputNodeId === node.id);
|
|
1103
|
+
if (connection) {
|
|
1104
|
+
const outputNode = this.#nodeInstances[connection.outputNodeId].chartNode;
|
|
1105
|
+
const outputNodeOutputs = this.#nodeResults.get(outputNode.id);
|
|
1106
|
+
const outputResult = outputNodeOutputs?.[connection.outputId];
|
|
1107
|
+
values[input.id] = outputResult;
|
|
1108
|
+
}
|
|
1109
|
+
return values;
|
|
1110
|
+
}, {});
|
|
1111
|
+
}
|
|
1112
|
+
/** Gets the nodes that are inputting to the given node. */
|
|
1113
|
+
#inputNodesTo(node) {
|
|
1114
|
+
const connections = this.#connections[node.id];
|
|
1115
|
+
if (!connections) {
|
|
1116
|
+
return [];
|
|
1117
|
+
}
|
|
1118
|
+
const connectionsToNode = connections.filter((conn) => conn.inputNodeId === node.id).filter(isNotNull);
|
|
1119
|
+
// Filter out invalid connections
|
|
1120
|
+
const inputDefinitions = this.#definitions[node.id]?.inputs ?? [];
|
|
1121
|
+
return connectionsToNode
|
|
1122
|
+
.filter((connection) => {
|
|
1123
|
+
const connectionDefinition = inputDefinitions.find((def) => def.id === connection.inputId);
|
|
1124
|
+
return connectionDefinition != null;
|
|
1125
|
+
})
|
|
1126
|
+
.map((conn) => this.#nodesById[conn.outputNodeId])
|
|
1127
|
+
.filter(isNotNull);
|
|
1128
|
+
}
|
|
1129
|
+
/** Gets the nodes that the given node it outputting to. */
|
|
1130
|
+
#outputNodesFrom(node) {
|
|
1131
|
+
const connections = this.#connections[node.id];
|
|
1132
|
+
if (!connections) {
|
|
1133
|
+
return { nodes: [], connections: [], connectionsToNodes: [] };
|
|
1134
|
+
}
|
|
1135
|
+
const connectionsToNode = connections.filter((conn) => conn.outputNodeId === node.id);
|
|
1136
|
+
// Filter out invalid connections
|
|
1137
|
+
const outputDefinitions = this.#definitions[node.id]?.outputs ?? [];
|
|
1138
|
+
const outputConnections = connectionsToNode.filter((connection) => {
|
|
1139
|
+
const connectionDefinition = outputDefinitions.find((def) => def.id === connection.outputId);
|
|
1140
|
+
return connectionDefinition != null;
|
|
1141
|
+
});
|
|
1142
|
+
const outputNodes = uniqBy(outputConnections.map((conn) => this.#nodesById[conn.inputNodeId]).filter(isNotNull), (x) => x.id);
|
|
1143
|
+
const connectionsToNodes = [];
|
|
1144
|
+
outputNodes.forEach((node) => {
|
|
1145
|
+
const connections = outputConnections.filter((conn) => conn.inputNodeId === node.id);
|
|
1146
|
+
connectionsToNodes.push({ connections, node });
|
|
1147
|
+
});
|
|
1148
|
+
return { nodes: outputNodes, connections: outputConnections, connectionsToNodes };
|
|
1149
|
+
}
|
|
1150
|
+
#tarjanSCC() {
|
|
1151
|
+
let index = 0;
|
|
1152
|
+
const stack = [];
|
|
1153
|
+
const indices = new Map();
|
|
1154
|
+
const lowLinks = new Map();
|
|
1155
|
+
const onStack = new Map();
|
|
1156
|
+
const sccs = [];
|
|
1157
|
+
const strongConnect = (node) => {
|
|
1158
|
+
indices.set(node.id, index);
|
|
1159
|
+
lowLinks.set(node.id, index);
|
|
1160
|
+
index++;
|
|
1161
|
+
stack.push(node);
|
|
1162
|
+
onStack.set(node.id, true);
|
|
1163
|
+
const outgoingConnections = this.#connections[node.id]?.filter((conn) => conn.outputNodeId === node.id);
|
|
1164
|
+
for (const connection of outgoingConnections ?? []) {
|
|
1165
|
+
const successor = this.#nodesById[connection.inputNodeId];
|
|
1166
|
+
if (!indices.has(successor.id)) {
|
|
1167
|
+
strongConnect(successor);
|
|
1168
|
+
lowLinks.set(node.id, Math.min(lowLinks.get(node.id), lowLinks.get(successor.id)));
|
|
1169
|
+
}
|
|
1170
|
+
else if (onStack.get(successor.id)) {
|
|
1171
|
+
lowLinks.set(node.id, Math.min(lowLinks.get(node.id), indices.get(successor.id)));
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
if (lowLinks.get(node.id) === indices.get(node.id)) {
|
|
1175
|
+
const scc = [];
|
|
1176
|
+
let connectedNode;
|
|
1177
|
+
do {
|
|
1178
|
+
connectedNode = stack.pop();
|
|
1179
|
+
onStack.set(connectedNode.id, false);
|
|
1180
|
+
scc.push(connectedNode);
|
|
1181
|
+
} while (connectedNode.id !== node.id);
|
|
1182
|
+
sccs.push(scc);
|
|
1183
|
+
}
|
|
1184
|
+
};
|
|
1185
|
+
for (const node of this.#graph.nodes) {
|
|
1186
|
+
if (!indices.has(node.id)) {
|
|
1187
|
+
strongConnect(node);
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
return sccs;
|
|
1191
|
+
}
|
|
1192
|
+
#nodeIsInCycle(nodeId) {
|
|
1193
|
+
return this.#nodesNotInCycle.find((node) => node.id === nodeId) == null;
|
|
1194
|
+
}
|
|
1195
|
+
#nodesAreInSameCycle(a, b) {
|
|
1196
|
+
return this.#scc.find((cycle) => cycle.find((node) => node.id === a) && cycle.find((node) => node.id === b));
|
|
1197
|
+
}
|
|
1198
|
+
}
|