= ({ report }) => {
+ const { status: nodeStatus } = report;
+ const [currentSnapshotIndex, setCurrentSnapshotIndex] = useState(0);
+
+ const snapshots = report.snapshots || [];
+ const currentSnapshot = snapshots[currentSnapshotIndex] || snapshots[0];
+
+ // 节点 5 个状态
+ const isNodePending = nodeStatus === WorkflowStatus.Pending;
+ const isNodeProcessing = nodeStatus === WorkflowStatus.Processing;
+ const isNodeFailed = nodeStatus === WorkflowStatus.Failed;
+ const isNodeSucceed = nodeStatus === WorkflowStatus.Succeeded;
+ const isNodeCanceled = nodeStatus === WorkflowStatus.Canceled;
+
+ const tagColor = useMemo(() => {
+ if (isNodeSucceed) {
+ return 'node-status-succeed';
+ }
+ if (isNodeFailed) {
+ return 'node-status-failed';
+ }
+ if (isNodeProcessing) {
+ return 'node-status-processing';
+ }
+ }, [isNodeSucceed, isNodeFailed, isNodeProcessing]);
+
+ const renderIcon = () => {
+ if (isNodeProcessing) {
+ return (
+
+ );
+ }
+ if (isNodeSucceed) {
+ return ;
+ }
+ return ;
+ };
+ const renderDesc = () => {
+ const getDesc = () => {
+ if (isNodeProcessing) {
+ return 'Running';
+ } else if (isNodePending) {
+ return 'Run terminated';
+ } else if (isNodeSucceed) {
+ return 'Succeed';
+ } else if (isNodeFailed) {
+ return 'Failed';
+ } else if (isNodeCanceled) {
+ return 'Canceled';
+ }
+ };
+
+ const desc = getDesc();
+
+ return desc ? {desc}
: null;
+ };
+ const renderCost = () => (
+
+ {msToSeconds(report.timeCost)}
+
+ );
+
+ const renderSnapshotNavigation = () => {
+ if (snapshots.length <= 1) {
+ return null;
+ }
+
+ const count = (
+
+ Total: {snapshots.length}
+
+ );
+
+ if (snapshots.length <= displayCount) {
+ return (
+ <>
+ {count}
+
+ {snapshots.map((_, index) => (
+
+ ))}
+
+ >
+ );
+ }
+
+ // 超过5个时,前5个显示为按钮,剩余的放在下拉选择中
+ return (
+ <>
+ {count}
+
+ {snapshots.slice(0, displayCount).map((_, index) => (
+
+ ))}
+
+
+ >
+ );
+ };
+
+ if (!report) {
+ return null;
+ }
+
+ return (
+
+ {renderIcon()}
+ {renderDesc()}
+ {renderCost()}
+ >
+ }
+ >
+
+ {renderSnapshotNavigation()}
+
+
+
+
+
+
+ );
+};
diff --git a/apps/demo-free-layout/src/components/testrun/node-status-bar/viewer/index.css b/apps/demo-free-layout/src/components/testrun/node-status-bar/viewer/index.css
new file mode 100644
index 00000000..72625e64
--- /dev/null
+++ b/apps/demo-free-layout/src/components/testrun/node-status-bar/viewer/index.css
@@ -0,0 +1,137 @@
+.node-status-data-structure-viewer {
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
+ font-size: 14px;
+ line-height: 1.5;
+ color: #333;
+ background: #fafafa;
+ border-radius: 6px;
+ padding: 12px 12px 12px 0;
+ margin: 12px;
+ border: 1px solid #e1e4e8;
+ overflow: hidden;
+}
+
+.tree-node {
+ margin: 2px 0;
+}
+
+.tree-node-header {
+ display: flex;
+ align-items: flex-start;
+ gap: 4px;
+ min-height: 20px;
+ padding: 2px 0;
+ border-radius: 3px;
+ transition: background-color 0.15s ease;
+}
+
+.tree-node-header:hover {
+ background-color: rgba(0, 0, 0, 0.04);
+}
+
+.expand-button {
+ background: none;
+ border: none;
+ cursor: pointer;
+ font-size: 10px;
+ color: #666;
+ width: 16px;
+ height: 16px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 2px;
+ transition: all 0.15s ease;
+ padding: 0;
+ margin: 0;
+}
+
+.expand-button:hover {
+ background-color: rgba(0, 0, 0, 0.1);
+ color: #333;
+}
+
+.expand-button.expanded {
+ transform: rotate(90deg);
+}
+
+.expand-button.collapsed {
+ transform: rotate(0deg);
+}
+
+.expand-placeholder {
+ width: 16px;
+ height: 16px;
+ display: inline-block;
+ flex-shrink: 0;
+}
+
+.node-label {
+ color: #0969da;
+ font-weight: 500;
+ cursor: pointer;
+ user-select: auto;
+ margin-right: 4px;
+}
+
+.node-label:hover {
+ text-decoration: underline;
+}
+
+.node-value {
+ margin-left: 4px;
+}
+
+.primitive-value-quote {
+ color: #8f8f8f;
+}
+
+.primitive-value {
+ cursor: pointer;
+ user-select: all;
+ padding: 1px 3px;
+ border-radius: 3px;
+ transition: background-color 0.15s ease;
+}
+
+.primitive-value:hover {
+ background-color: rgba(0, 0, 0, 0.05);
+}
+
+.primitive-value.string {
+ color: #032f62;
+ background-color: rgba(3, 47, 98, 0.05);
+}
+
+.primitive-value.number {
+ color: #005cc5;
+ background-color: rgba(0, 92, 197, 0.05);
+}
+
+.primitive-value.boolean {
+ color: #e36209;
+ background-color: rgba(227, 98, 9, 0.05);
+}
+
+.primitive-value.null,
+.primitive-value.undefined {
+ color: #6a737d;
+ font-style: italic;
+ background-color: rgba(106, 115, 125, 0.05);
+}
+
+.tree-node-children {
+ margin-left: 8px;
+ padding-left: 8px;
+ position: relative;
+}
+
+.tree-node-children::before {
+ content: '';
+ position: absolute;
+ left: 0;
+ top: 0;
+ bottom: 0;
+ width: 1px;
+ background: #e1e4e8;
+}
diff --git a/apps/demo-free-layout/src/components/testrun/node-status-bar/viewer/index.tsx b/apps/demo-free-layout/src/components/testrun/node-status-bar/viewer/index.tsx
new file mode 100644
index 00000000..10e319ac
--- /dev/null
+++ b/apps/demo-free-layout/src/components/testrun/node-status-bar/viewer/index.tsx
@@ -0,0 +1,154 @@
+import React, { useState } from 'react';
+
+import './index.css';
+import { Toast } from '@douyinfe/semi-ui';
+
+interface DataStructureViewerProps {
+ data: any;
+ level?: number;
+}
+
+interface TreeNodeProps {
+ label: string;
+ value: any;
+ level: number;
+ isLast?: boolean;
+}
+
+const TreeNode: React.FC = ({ label, value, level, isLast = false }) => {
+ const [isExpanded, setIsExpanded] = useState(true);
+
+ const handleCopy = (text: string) => {
+ navigator.clipboard.writeText(text);
+ Toast.success('Copied');
+ };
+
+ const isExpandable = (val: any) =>
+ val !== null &&
+ typeof val === 'object' &&
+ ((Array.isArray(val) && val.length > 0) ||
+ (!Array.isArray(val) && Object.keys(val).length > 0));
+
+ const renderPrimitiveValue = (val: any) => {
+ if (val === null) return null;
+ if (val === undefined) return undefined;
+
+ switch (typeof val) {
+ case 'string':
+ return (
+
+ {'"'}
+ handleCopy(val)}>
+ {val}
+
+ {'"'}
+
+ );
+ case 'number':
+ return (
+ handleCopy(String(val))}>
+ {val}
+
+ );
+ case 'boolean':
+ return (
+ handleCopy(val.toString())}
+ >
+ {val.toString()}
+
+ );
+ default:
+ return (
+ handleCopy(String(val))}>
+ {String(val)}
+
+ );
+ }
+ };
+
+ const renderChildren = () => {
+ if (Array.isArray(value)) {
+ return value.map((item, index) => (
+
+ ));
+ } else {
+ const entries = Object.entries(value);
+ return entries.map(([key, val], index) => (
+
+ ));
+ }
+ };
+
+ return (
+
+
+ {isExpandable(value) ? (
+
+ ) : (
+
+ )}
+
+ handleCopy(
+ JSON.stringify({
+ [label]: value,
+ })
+ )
+ }
+ >
+ {label}
+
+ {!isExpandable(value) && {renderPrimitiveValue(value)}}
+
+ {isExpandable(value) && isExpanded && (
+
{renderChildren()}
+ )}
+
+ );
+};
+
+export const DataStructureViewer: React.FC = ({ data, level = 0 }) => {
+ if (data === null || data === undefined || typeof data !== 'object') {
+ return (
+
+
+
+ );
+ }
+
+ const entries = Object.entries(data);
+
+ return (
+
+ {entries.map(([key, value], index) => (
+
+ ))}
+
+ );
+};
diff --git a/apps/demo-free-layout/src/components/testrun/testrun-button/index.tsx b/apps/demo-free-layout/src/components/testrun/testrun-button/index.tsx
new file mode 100644
index 00000000..c838f39f
--- /dev/null
+++ b/apps/demo-free-layout/src/components/testrun/testrun-button/index.tsx
@@ -0,0 +1,78 @@
+import { useState, useEffect, useCallback } from 'react';
+
+import { useClientContext, getNodeForm, FlowNodeEntity } from '@flowgram.ai/free-layout-editor';
+import { Button, Badge, SideSheet } from '@douyinfe/semi-ui';
+import { IconPlay } from '@douyinfe/semi-icons';
+
+import { TestRunSideSheet } from '../testrun-sidesheet';
+
+export function TestRunButton(props: { disabled: boolean }) {
+ const [errorCount, setErrorCount] = useState(0);
+ const clientContext = useClientContext();
+ const [visible, setVisible] = useState(false);
+
+ const updateValidateData = useCallback(() => {
+ const allForms = clientContext.document.getAllNodes().map((node) => getNodeForm(node));
+ const count = allForms.filter((form) => form?.state.invalid).length;
+ setErrorCount(count);
+ }, [clientContext]);
+
+ /**
+ * Validate all node and Save
+ */
+ const onTestRun = useCallback(async () => {
+ const allForms = clientContext.document.getAllNodes().map((node) => getNodeForm(node));
+ await Promise.all(allForms.map(async (form) => form?.validate()));
+ console.log('>>>>> save data: ', clientContext.document.toJSON());
+ setVisible(true);
+ }, [clientContext]);
+
+ /**
+ * Listen single node validate
+ */
+ useEffect(() => {
+ const listenSingleNodeValidate = (node: FlowNodeEntity) => {
+ const form = getNodeForm(node);
+ if (form) {
+ const formValidateDispose = form.onValidate(() => updateValidateData());
+ node.onDispose(() => formValidateDispose.dispose());
+ }
+ };
+ clientContext.document.getAllNodes().map((node) => listenSingleNodeValidate(node));
+ const dispose = clientContext.document.onNodeCreate(({ node }) =>
+ listenSingleNodeValidate(node)
+ );
+ return () => dispose.dispose();
+ }, [clientContext]);
+
+ const button =
+ errorCount === 0 ? (
+ }
+ style={{ backgroundColor: 'rgba(0,178,60,1)', borderRadius: '8px', color: '#fff' }}
+ >
+ Test Run
+
+ ) : (
+
+ }
+ style={{ backgroundColor: 'rgba(255,115,0, 1)', borderRadius: '8px', color: '#fff' }}
+ >
+ Test Run
+
+
+ );
+
+ return (
+ <>
+ {button}
+ setVisible((v) => !v)} />
+ >
+ );
+}
diff --git a/apps/demo-free-layout/src/components/testrun/testrun-sidesheet/index.tsx b/apps/demo-free-layout/src/components/testrun/testrun-sidesheet/index.tsx
new file mode 100644
index 00000000..5b9dd297
--- /dev/null
+++ b/apps/demo-free-layout/src/components/testrun/testrun-sidesheet/index.tsx
@@ -0,0 +1,138 @@
+import { FC, useEffect, useState } from 'react';
+
+import { WorkflowInputs, WorkflowOutputs } from '@flowgram.ai/runtime-interface';
+import { useService } from '@flowgram.ai/free-layout-editor';
+import { Button, JsonViewer, SideSheet } from '@douyinfe/semi-ui';
+import { IconPlay, IconSpin, IconStop } from '@douyinfe/semi-icons';
+
+import { NodeStatusGroup } from '../node-status-bar/group';
+import { WorkflowRuntimeService } from '../../../plugins/runtime-plugin/runtime-service';
+
+interface TestRunSideSheetProps {
+ visible: boolean;
+ onCancel: () => void;
+}
+
+export const TestRunSideSheet: FC = ({ visible, onCancel }) => {
+ const runtimeService = useService(WorkflowRuntimeService);
+ const [isRunning, setRunning] = useState(false);
+ const [value, setValue] = useState(`{}`);
+ const [error, setError] = useState();
+ const [result, setResult] = useState<
+ | {
+ inputs: WorkflowInputs;
+ outputs: WorkflowOutputs;
+ }
+ | undefined
+ >();
+
+ const onTestRun = async () => {
+ if (isRunning) {
+ await runtimeService.taskCancel();
+ return;
+ }
+ setResult(undefined);
+ setError(undefined);
+ setRunning(true);
+ try {
+ await runtimeService.taskRun(value);
+ } catch (e: any) {
+ setError(e.message);
+ }
+ };
+
+ const onClose = async () => {
+ await runtimeService.taskCancel();
+ setValue(`{}`);
+ setRunning(false);
+ onCancel();
+ };
+
+ useEffect(() => {
+ const disposer = runtimeService.onTerminated(({ result }) => {
+ setRunning(false);
+ setResult(result);
+ });
+ return () => disposer.dispose();
+ }, []);
+
+ const renderRunning = (
+
+ );
+
+ const renderForm = (
+
+
+ Input
+
+
+
+ {error}
+
+
+
+
+
+ );
+
+ const renderButton = (
+ : }
+ style={{
+ backgroundColor: isRunning ? 'rgba(87,104,161,0.08)' : 'rgba(0,178,60,1)',
+ borderRadius: '8px',
+ color: isRunning ? 'rgba(15,21,40,0.82)' : '#fff',
+ marginBottom: '16px',
+ width: '100%',
+ height: '40px',
+ }}
+ >
+ {isRunning ? 'Cancel' : 'Test Run'}
+
+ );
+
+ return (
+
+ {isRunning ? renderRunning : renderForm}
+
+ );
+};
diff --git a/apps/demo-free-layout/src/components/tools/index.tsx b/apps/demo-free-layout/src/components/tools/index.tsx
index 92aadf9f..9e33e20b 100644
--- a/apps/demo-free-layout/src/components/tools/index.tsx
+++ b/apps/demo-free-layout/src/components/tools/index.tsx
@@ -5,12 +5,11 @@ import { useClientContext } from '@flowgram.ai/free-layout-editor';
import { Tooltip, IconButton, Divider } from '@douyinfe/semi-ui';
import { IconUndo, IconRedo } from '@douyinfe/semi-icons';
+import { TestRunButton } from '../testrun/testrun-button';
import { AddNode } from '../add-node';
import { ZoomSelect } from './zoom-select';
import { SwitchLine } from './switch-line';
import { ToolContainer, ToolSection } from './styles';
-import { Save } from './save';
-import { Run } from './run';
import { Readonly } from './readonly';
import { MinimapSwitch } from './minimap-switch';
import { Minimap } from './minimap';
@@ -71,8 +70,7 @@ export const DemoTools = () => {
-
-
+
);
diff --git a/apps/demo-free-layout/src/components/tools/run.tsx b/apps/demo-free-layout/src/components/tools/run.tsx
index 689856cb..d3b09b0a 100644
--- a/apps/demo-free-layout/src/components/tools/run.tsx
+++ b/apps/demo-free-layout/src/components/tools/run.tsx
@@ -3,17 +3,17 @@ import { useState } from 'react';
import { useService } from '@flowgram.ai/free-layout-editor';
import { Button } from '@douyinfe/semi-ui';
-import { RunningService } from '../../services';
+import { WorkflowRuntimeService } from '../../plugins/runtime-plugin/runtime-service';
/**
* Run the simulation and highlight the lines
*/
export function Run() {
const [isRunning, setRunning] = useState(false);
- const runningService = useService(RunningService);
+ const runtimeService = useService(WorkflowRuntimeService);
const onRun = async () => {
setRunning(true);
- await runningService.startRun();
+ await runtimeService.taskRun('{}');
setRunning(false);
};
return (
diff --git a/apps/demo-free-layout/src/form-components/form-outputs/index.tsx b/apps/demo-free-layout/src/form-components/form-outputs/index.tsx
index 905a9a39..fb0abf23 100644
--- a/apps/demo-free-layout/src/form-components/form-outputs/index.tsx
+++ b/apps/demo-free-layout/src/form-components/form-outputs/index.tsx
@@ -1,3 +1,5 @@
+import { FC } from 'react';
+
import { Field } from '@flowgram.ai/free-layout-editor';
import { TypeTag } from '../type-tag';
@@ -5,13 +7,17 @@ import { JsonSchema } from '../../typings';
import { useIsSidebar } from '../../hooks';
import { FormOutputsContainer } from './styles';
-export function FormOutputs() {
+interface FormOutputsProps {
+ name?: string;
+}
+
+export const FormOutputs: FC = ({ name = 'outputs' }) => {
const isSidebar = useIsSidebar();
if (isSidebar) {
return null;
}
return (
- name={'outputs'}>
+ name={name}>
{({ field }) => {
const properties = field.value?.properties;
if (properties) {
@@ -25,4 +31,4 @@ export function FormOutputs() {
}}
);
-}
+};
diff --git a/apps/demo-free-layout/src/hooks/use-editor-props.tsx b/apps/demo-free-layout/src/hooks/use-editor-props.tsx
index 34aff837..9e322eab 100644
--- a/apps/demo-free-layout/src/hooks/use-editor-props.tsx
+++ b/apps/demo-free-layout/src/hooks/use-editor-props.tsx
@@ -13,8 +13,9 @@ import { createContainerNodePlugin } from '@flowgram.ai/free-container-plugin';
import { onDragLineEnd } from '../utils';
import { FlowNodeRegistry, FlowDocumentJSON } from '../typings';
import { shortcuts } from '../shortcuts';
-import { CustomService, RunningService } from '../services';
-import { createSyncVariablePlugin, createContextMenuPlugin } from '../plugins';
+import { CustomService } from '../services';
+import { WorkflowRuntimeService } from '../plugins/runtime-plugin/runtime-service';
+import { createSyncVariablePlugin, createRuntimePlugin, createContextMenuPlugin } from '../plugins';
import { defaultFormMeta } from '../nodes/default-form-meta';
import { WorkflowNodeType } from '../nodes';
import { SelectorBoxPopover } from '../components/selector-box-popover';
@@ -160,7 +161,7 @@ export function useEditorProps(
/**
* Running line
*/
- isFlowingLine: (ctx, line) => ctx.get(RunningService).isFlowingLine(line),
+ isFlowingLine: (ctx, line) => ctx.get(WorkflowRuntimeService).isFlowingLine(line),
/**
* Shortcuts
@@ -171,7 +172,6 @@ export function useEditorProps(
*/
onBind: ({ bind }) => {
bind(CustomService).toSelf().inSingletonScope();
- bind(RunningService).toSelf().inSingletonScope();
},
/**
* Playground init
@@ -264,6 +264,15 @@ export function useEditorProps(
* ContextMenu plugin
*/
createContextMenuPlugin({}),
+ createRuntimePlugin({
+ mode: 'browser',
+ // mode: 'server',
+ // serverConfig: {
+ // domain: 'localhost',
+ // port: 4000,
+ // protocol: 'http',
+ // },
+ }),
],
}),
[]
diff --git a/apps/demo-free-layout/src/initial-data.ts b/apps/demo-free-layout/src/initial-data.ts
index 1bb98246..1f17e3cb 100644
--- a/apps/demo-free-layout/src/initial-data.ts
+++ b/apps/demo-free-layout/src/initial-data.ts
@@ -48,7 +48,7 @@ export const initialData: FlowDocumentJSON = {
meta: {
position: {
x: 640,
- y: 363.25,
+ y: 318.25,
},
},
data: {
@@ -86,13 +86,13 @@ export const initialData: FlowDocumentJSON = {
type: 'end',
meta: {
position: {
- x: 2220,
+ x: 2202.9953917050693,
y: 381.75,
},
},
data: {
title: 'End',
- outputs: {
+ inputs: {
type: 'object',
properties: {
result: {
@@ -102,160 +102,13 @@ export const initialData: FlowDocumentJSON = {
},
},
},
- {
- id: 'loop_H8M3U',
- type: 'loop',
- meta: {
- position: {
- x: 1020,
- y: 547.96875,
- },
- },
- data: {
- title: 'Loop_2',
- batchFor: {
- type: 'ref',
- content: ['start_0', 'array_obj'],
- },
- outputs: {
- type: 'object',
- properties: {
- result: {
- type: 'string',
- },
- },
- },
- },
- blocks: [
- {
- id: 'llm_CBdCg',
- type: 'llm',
- meta: {
- position: {
- x: 180,
- y: 0,
- },
- },
- data: {
- title: 'LLM_4',
- inputsValues: {
- modelType: {
- type: 'constant',
- content: 'gpt-3.5-turbo',
- },
- temperature: {
- type: 'constant',
- content: 0.5,
- },
- systemPrompt: {
- type: 'constant',
- content: 'You are an AI assistant.',
- },
- prompt: {
- type: 'constant',
- content: '',
- },
- },
- inputs: {
- type: 'object',
- required: ['modelType', 'temperature', 'prompt'],
- properties: {
- modelType: {
- type: 'string',
- },
- temperature: {
- type: 'number',
- },
- systemPrompt: {
- type: 'string',
- },
- prompt: {
- type: 'string',
- },
- },
- },
- outputs: {
- type: 'object',
- properties: {
- result: {
- type: 'string',
- },
- },
- },
- },
- },
- {
- id: 'llm_gZafu',
- type: 'llm',
- meta: {
- position: {
- x: 640,
- y: 0,
- },
- },
- data: {
- title: 'LLM_5',
- inputsValues: {
- modelType: {
- type: 'constant',
- content: 'gpt-3.5-turbo',
- },
- temperature: {
- type: 'constant',
- content: 0.5,
- },
- systemPrompt: {
- type: 'constant',
- content: 'You are an AI assistant.',
- },
- prompt: {
- type: 'constant',
- content: '',
- },
- },
- inputs: {
- type: 'object',
- required: ['modelType', 'temperature', 'prompt'],
- properties: {
- modelType: {
- type: 'string',
- },
- temperature: {
- type: 'number',
- },
- systemPrompt: {
- type: 'string',
- },
- prompt: {
- type: 'string',
- },
- },
- },
- outputs: {
- type: 'object',
- properties: {
- result: {
- type: 'string',
- },
- },
- },
- },
- },
- ],
- edges: [
- {
- sourceNodeID: 'llm_CBdCg',
- targetNodeID: 'llm_gZafu',
- },
- ],
- },
{
id: '159623',
type: 'comment',
meta: {
position: {
x: 640,
- y: 522.46875,
+ y: 573.96875,
},
},
data: {
@@ -267,35 +120,42 @@ export const initialData: FlowDocumentJSON = {
},
},
{
- id: 'group_V-_st',
- type: 'group',
+ id: 'loop_sGybT',
+ type: 'loop',
meta: {
position: {
- x: 1020,
- y: 96.25,
+ x: 1373.5714285714287,
+ y: 394.9758064516129,
},
},
data: {
- title: 'LLM_Group',
- color: 'Violet',
+ title: 'Loop_1',
},
blocks: [
{
- id: 'llm_0',
+ id: 'llm_6aSyo',
type: 'llm',
meta: {
position: {
- x: 640,
- y: 0,
+ x: -196.8663594470046,
+ y: 142.0046082949309,
},
},
data: {
- title: 'LLM_0',
+ title: 'LLM_3',
inputsValues: {
- modelType: {
+ modelName: {
type: 'constant',
content: 'gpt-3.5-turbo',
},
+ apiKey: {
+ type: 'constant',
+ content: 'sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ },
+ apiHost: {
+ type: 'constant',
+ content: 'https://mock-ai-url/api/v3',
+ },
temperature: {
type: 'constant',
content: 0.5,
@@ -311,9 +171,15 @@ export const initialData: FlowDocumentJSON = {
},
inputs: {
type: 'object',
- required: ['modelType', 'temperature', 'prompt'],
+ required: ['modelName', 'apiKey', 'apiHost', 'temperature', 'prompt'],
properties: {
- modelType: {
+ modelName: {
+ type: 'string',
+ },
+ apiKey: {
+ type: 'string',
+ },
+ apiHost: {
type: 'string',
},
temperature: {
@@ -338,21 +204,29 @@ export const initialData: FlowDocumentJSON = {
},
},
{
- id: 'llm_l_TcE',
+ id: 'llm_ZqKlP',
type: 'llm',
meta: {
position: {
- x: 180,
- y: 0,
+ x: 253.1797235023041,
+ y: 142.00460829493088,
},
},
data: {
- title: 'LLM_1',
+ title: 'LLM_4',
inputsValues: {
- modelType: {
+ modelName: {
type: 'constant',
content: 'gpt-3.5-turbo',
},
+ apiKey: {
+ type: 'constant',
+ content: 'sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ },
+ apiHost: {
+ type: 'constant',
+ content: 'https://mock-ai-url/api/v3',
+ },
temperature: {
type: 'constant',
content: 0.5,
@@ -368,9 +242,15 @@ export const initialData: FlowDocumentJSON = {
},
inputs: {
type: 'object',
- required: ['modelType', 'temperature', 'prompt'],
+ required: ['modelName', 'apiKey', 'apiHost', 'temperature', 'prompt'],
properties: {
- modelType: {
+ modelName: {
+ type: 'string',
+ },
+ apiKey: {
+ type: 'string',
+ },
+ apiHost: {
type: 'string',
},
temperature: {
@@ -397,18 +277,179 @@ export const initialData: FlowDocumentJSON = {
],
edges: [
{
- sourceNodeID: 'llm_l_TcE',
- targetNodeID: 'llm_0',
+ sourceNodeID: 'llm_6aSyo',
+ targetNodeID: 'llm_ZqKlP',
+ },
+ ],
+ },
+ {
+ id: 'group_5ci0o',
+ type: 'group',
+ meta: {
+ position: {
+ x: 0,
+ y: 0,
+ },
+ },
+ data: {},
+ blocks: [
+ {
+ id: 'llm_8--A3',
+ type: 'llm',
+ meta: {
+ position: {
+ x: 1177.8341013824886,
+ y: 19.25,
+ },
+ },
+ data: {
+ title: 'LLM_1',
+ inputsValues: {
+ modelName: {
+ type: 'constant',
+ content: 'gpt-3.5-turbo',
+ },
+ apiKey: {
+ type: 'constant',
+ content: 'sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ },
+ apiHost: {
+ type: 'constant',
+ content: 'https://mock-ai-url/api/v3',
+ },
+ temperature: {
+ type: 'constant',
+ content: 0.5,
+ },
+ systemPrompt: {
+ type: 'constant',
+ content: 'You are an AI assistant.',
+ },
+ prompt: {
+ type: 'constant',
+ content: '',
+ },
+ },
+ inputs: {
+ type: 'object',
+ required: ['modelName', 'apiKey', 'apiHost', 'temperature', 'prompt'],
+ properties: {
+ modelName: {
+ type: 'string',
+ },
+ apiKey: {
+ type: 'string',
+ },
+ apiHost: {
+ type: 'string',
+ },
+ temperature: {
+ type: 'number',
+ },
+ systemPrompt: {
+ type: 'string',
+ },
+ prompt: {
+ type: 'string',
+ },
+ },
+ },
+ outputs: {
+ type: 'object',
+ properties: {
+ result: {
+ type: 'string',
+ },
+ },
+ },
+ },
},
{
- sourceNodeID: 'llm_0',
- targetNodeID: 'end_0',
+ id: 'llm_vTyMa',
+ type: 'llm',
+ meta: {
+ position: {
+ x: 1625.6221198156682,
+ y: 19.25,
+ },
+ },
+ data: {
+ title: 'LLM_2',
+ inputsValues: {
+ modelName: {
+ type: 'constant',
+ content: 'gpt-3.5-turbo',
+ },
+ apiKey: {
+ type: 'constant',
+ content: 'sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ },
+ apiHost: {
+ type: 'constant',
+ content: 'https://mock-ai-url/api/v3',
+ },
+ temperature: {
+ type: 'constant',
+ content: 0.5,
+ },
+ systemPrompt: {
+ type: 'constant',
+ content: 'You are an AI assistant.',
+ },
+ prompt: {
+ type: 'constant',
+ content: '',
+ },
+ },
+ inputs: {
+ type: 'object',
+ required: ['modelName', 'apiKey', 'apiHost', 'temperature', 'prompt'],
+ properties: {
+ modelName: {
+ type: 'string',
+ },
+ apiKey: {
+ type: 'string',
+ },
+ apiHost: {
+ type: 'string',
+ },
+ temperature: {
+ type: 'number',
+ },
+ systemPrompt: {
+ type: 'string',
+ },
+ prompt: {
+ type: 'string',
+ },
+ },
+ },
+ outputs: {
+ type: 'object',
+ properties: {
+ result: {
+ type: 'string',
+ },
+ },
+ },
+ },
},
+ ],
+ edges: [
{
sourceNodeID: 'condition_0',
- targetNodeID: 'llm_l_TcE',
+ targetNodeID: 'llm_8--A3',
sourcePortID: 'if_0',
},
+ {
+ sourceNodeID: 'llm_8--A3',
+ targetNodeID: 'llm_vTyMa',
+ },
+ {
+ sourceNodeID: 'llm_vTyMa',
+ targetNodeID: 'end_0',
+ },
],
},
],
@@ -419,20 +460,20 @@ export const initialData: FlowDocumentJSON = {
},
{
sourceNodeID: 'condition_0',
- targetNodeID: 'llm_l_TcE',
+ targetNodeID: 'llm_8--A3',
sourcePortID: 'if_0',
},
{
sourceNodeID: 'condition_0',
- targetNodeID: 'loop_H8M3U',
+ targetNodeID: 'loop_sGybT',
sourcePortID: 'if_f0rOAt',
},
{
- sourceNodeID: 'llm_0',
+ sourceNodeID: 'llm_vTyMa',
targetNodeID: 'end_0',
},
{
- sourceNodeID: 'loop_H8M3U',
+ sourceNodeID: 'loop_sGybT',
targetNodeID: 'end_0',
},
],
diff --git a/apps/demo-free-layout/src/nodes/end/form-meta.tsx b/apps/demo-free-layout/src/nodes/end/form-meta.tsx
index 0b7c9de4..b8fddf15 100644
--- a/apps/demo-free-layout/src/nodes/end/form-meta.tsx
+++ b/apps/demo-free-layout/src/nodes/end/form-meta.tsx
@@ -15,7 +15,7 @@ export const renderForm = () => {
>) => (
@@ -43,7 +43,7 @@ export const renderForm = () => {
)}
/>
-
+
>
);
@@ -52,7 +52,7 @@ export const renderForm = () => {
<>
-
+
>
);
diff --git a/apps/demo-free-layout/src/nodes/llm/index.ts b/apps/demo-free-layout/src/nodes/llm/index.ts
index 51829ed6..2b8023b0 100644
--- a/apps/demo-free-layout/src/nodes/llm/index.ts
+++ b/apps/demo-free-layout/src/nodes/llm/index.ts
@@ -15,7 +15,7 @@ export const LLMNodeRegistry: FlowNodeRegistry = {
meta: {
size: {
width: 360,
- height: 305,
+ height: 300,
},
},
onAdd() {
@@ -25,10 +25,18 @@ export const LLMNodeRegistry: FlowNodeRegistry = {
data: {
title: `LLM_${++index}`,
inputsValues: {
- modelType: {
+ modelName: {
type: 'constant',
content: 'gpt-3.5-turbo',
},
+ apiKey: {
+ type: 'constant',
+ content: 'sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ },
+ apiHost: {
+ type: 'constant',
+ content: 'https://mock-ai-url/api/v3',
+ },
temperature: {
type: 'constant',
content: 0.5,
@@ -44,9 +52,15 @@ export const LLMNodeRegistry: FlowNodeRegistry = {
},
inputs: {
type: 'object',
- required: ['modelType', 'temperature', 'prompt'],
+ required: ['modelName', 'apiKey', 'apiHost', 'temperature', 'prompt'],
properties: {
- modelType: {
+ modelName: {
+ type: 'string',
+ },
+ apiKey: {
+ type: 'string',
+ },
+ apiHost: {
type: 'string',
},
temperature: {
diff --git a/apps/demo-free-layout/src/nodes/loop/index.ts b/apps/demo-free-layout/src/nodes/loop/index.ts
index 4e89acfe..fad3936b 100644
--- a/apps/demo-free-layout/src/nodes/loop/index.ts
+++ b/apps/demo-free-layout/src/nodes/loop/index.ts
@@ -39,8 +39,8 @@ export const LoopNodeRegistry: FlowNodeRegistry = {
* 子画布 padding 设置
*/
padding: () => ({
- top: 125,
- bottom: 100,
+ top: 120,
+ bottom: 60,
left: 100,
right: 100,
}),
diff --git a/apps/demo-free-layout/src/nodes/loop/loop-form-render.tsx b/apps/demo-free-layout/src/nodes/loop/loop-form-render.tsx
index cdc07208..97456965 100644
--- a/apps/demo-free-layout/src/nodes/loop/loop-form-render.tsx
+++ b/apps/demo-free-layout/src/nodes/loop/loop-form-render.tsx
@@ -14,6 +14,7 @@ interface LoopNodeJSON extends FlowNodeJSON {
export const LoopFormRender = ({ form }: FormRenderProps) => {
const isSidebar = useIsSidebar();
const { readonly } = useNodeRenderContext();
+ const formHeight = 85;
const batchFor = (
name={`batchFor`}>
@@ -48,7 +49,7 @@ export const LoopFormRender = ({ form }: FormRenderProps) => {
{batchFor}
-
+
>
diff --git a/apps/demo-free-layout/src/plugins/index.ts b/apps/demo-free-layout/src/plugins/index.ts
index 0376fd87..83efe551 100644
--- a/apps/demo-free-layout/src/plugins/index.ts
+++ b/apps/demo-free-layout/src/plugins/index.ts
@@ -1,2 +1,3 @@
export { createSyncVariablePlugin } from './sync-variable-plugin/sync-variable-plugin';
export { createContextMenuPlugin } from './context-menu-plugin';
+export { createRuntimePlugin } from './runtime-plugin';
diff --git a/apps/demo-free-layout/src/plugins/runtime-plugin/browser-client/index.ts b/apps/demo-free-layout/src/plugins/runtime-plugin/browser-client/index.ts
new file mode 100644
index 00000000..82c05a4a
--- /dev/null
+++ b/apps/demo-free-layout/src/plugins/runtime-plugin/browser-client/index.ts
@@ -0,0 +1,17 @@
+/* eslint-disable no-console */
+import { TaskCancelAPI, TaskReportAPI, TaskResultAPI, TaskRunAPI } from '@flowgram.ai/runtime-js';
+import { FlowGramAPIName, IRuntimeClient } from '@flowgram.ai/runtime-interface';
+import { injectable } from '@flowgram.ai/free-layout-editor';
+
+@injectable()
+export class WorkflowRuntimeClient implements IRuntimeClient {
+ constructor() {}
+
+ public [FlowGramAPIName.TaskRun]: IRuntimeClient[FlowGramAPIName.TaskRun] = TaskRunAPI;
+
+ public [FlowGramAPIName.TaskReport]: IRuntimeClient[FlowGramAPIName.TaskReport] = TaskReportAPI;
+
+ public [FlowGramAPIName.TaskResult]: IRuntimeClient[FlowGramAPIName.TaskResult] = TaskResultAPI;
+
+ public [FlowGramAPIName.TaskCancel]: IRuntimeClient[FlowGramAPIName.TaskCancel] = TaskCancelAPI;
+}
diff --git a/apps/demo-free-layout/src/plugins/runtime-plugin/create-runtime-plugin.ts b/apps/demo-free-layout/src/plugins/runtime-plugin/create-runtime-plugin.ts
new file mode 100644
index 00000000..0826920a
--- /dev/null
+++ b/apps/demo-free-layout/src/plugins/runtime-plugin/create-runtime-plugin.ts
@@ -0,0 +1,23 @@
+import { definePluginCreator, PluginContext } from '@flowgram.ai/free-layout-editor';
+
+import { RuntimePluginOptions } from './type';
+import { WorkflowRuntimeServerClient } from './server-client';
+import { WorkflowRuntimeService } from './runtime-service';
+import { WorkflowRuntimeClient } from './browser-client';
+
+export const createRuntimePlugin = definePluginCreator({
+ onBind({ bind, rebind }, options) {
+ bind(WorkflowRuntimeClient).toSelf().inSingletonScope();
+ bind(WorkflowRuntimeServerClient).toSelf().inSingletonScope();
+ if (options.mode === 'server') {
+ rebind(WorkflowRuntimeClient).to(WorkflowRuntimeServerClient);
+ }
+ bind(WorkflowRuntimeService).toSelf().inSingletonScope();
+ },
+ onInit(ctx, options) {
+ if (options.mode === 'server') {
+ const serverClient = ctx.get(WorkflowRuntimeServerClient);
+ serverClient.init(options.serverConfig);
+ }
+ },
+});
diff --git a/apps/demo-free-layout/src/plugins/runtime-plugin/index.ts b/apps/demo-free-layout/src/plugins/runtime-plugin/index.ts
new file mode 100644
index 00000000..fbff0a8b
--- /dev/null
+++ b/apps/demo-free-layout/src/plugins/runtime-plugin/index.ts
@@ -0,0 +1,2 @@
+export { createRuntimePlugin } from './create-runtime-plugin';
+export { WorkflowRuntimeClient } from './browser-client';
diff --git a/apps/demo-free-layout/src/plugins/runtime-plugin/runtime-service/index.ts b/apps/demo-free-layout/src/plugins/runtime-plugin/runtime-service/index.ts
new file mode 100644
index 00000000..5435cf9c
--- /dev/null
+++ b/apps/demo-free-layout/src/plugins/runtime-plugin/runtime-service/index.ts
@@ -0,0 +1,172 @@
+import {
+ IReport,
+ NodeReport,
+ WorkflowInputs,
+ WorkflowOutputs,
+ WorkflowStatus,
+} from '@flowgram.ai/runtime-interface';
+import {
+ injectable,
+ inject,
+ WorkflowDocument,
+ Playground,
+ WorkflowLineEntity,
+ WorkflowNodeEntity,
+ WorkflowNodeLinesData,
+ Emitter,
+ getNodeForm,
+} from '@flowgram.ai/free-layout-editor';
+
+import { WorkflowRuntimeClient } from '../browser-client';
+
+const SYNC_TASK_REPORT_INTERVAL = 500;
+
+interface NodeRunningStatus {
+ nodeID: string;
+ status: WorkflowStatus;
+ nodeResultLength: number;
+}
+
+@injectable()
+export class WorkflowRuntimeService {
+ @inject(Playground) playground: Playground;
+
+ @inject(WorkflowDocument) document: WorkflowDocument;
+
+ @inject(WorkflowRuntimeClient) runtimeClient: WorkflowRuntimeClient;
+
+ private runningNodes: WorkflowNodeEntity[] = [];
+
+ private taskID?: string;
+
+ private syncTaskReportIntervalID?: ReturnType;
+
+ private reportEmitter = new Emitter();
+
+ private resetEmitter = new Emitter<{}>();
+
+ public terminatedEmitter = new Emitter<{
+ result?: {
+ inputs: WorkflowInputs;
+ outputs: WorkflowOutputs;
+ };
+ }>();
+
+ private nodeRunningStatus: Map;
+
+ public onNodeReportChange = this.reportEmitter.event;
+
+ public onReset = this.resetEmitter.event;
+
+ public onTerminated = this.terminatedEmitter.event;
+
+ public isFlowingLine(line: WorkflowLineEntity) {
+ return this.runningNodes.some((node) =>
+ node.getData(WorkflowNodeLinesData).inputLines.includes(line)
+ );
+ }
+
+ public async taskRun(inputsString: string): Promise {
+ if (this.taskID) {
+ await this.taskCancel();
+ }
+ if (!this.validate()) {
+ return;
+ }
+ this.reset();
+ const output = await this.runtimeClient.TaskRun({
+ schema: JSON.stringify(this.document.toJSON()),
+ inputs: JSON.parse(inputsString) as WorkflowInputs,
+ });
+ if (!output) {
+ this.terminatedEmitter.fire({});
+ return;
+ }
+ this.taskID = output.taskID;
+ this.syncTaskReportIntervalID = setInterval(() => {
+ this.syncTaskReport();
+ }, SYNC_TASK_REPORT_INTERVAL);
+ }
+
+ public async taskCancel(): Promise {
+ if (!this.taskID) {
+ return;
+ }
+ await this.runtimeClient.TaskCancel({
+ taskID: this.taskID,
+ });
+ }
+
+ private async validate(): Promise {
+ const allForms = this.document.getAllNodes().map((node) => getNodeForm(node));
+ const formValidations = await Promise.all(allForms.map(async (form) => form?.validate()));
+ const validations = formValidations.filter((validation) => validation !== undefined);
+ const isValid = validations.every((validation) => validation);
+ return isValid;
+ }
+
+ private reset(): void {
+ this.taskID = undefined;
+ this.nodeRunningStatus = new Map();
+ this.runningNodes = [];
+ if (this.syncTaskReportIntervalID) {
+ clearInterval(this.syncTaskReportIntervalID);
+ }
+ this.resetEmitter.fire({});
+ }
+
+ private async syncTaskReport(): Promise {
+ if (!this.taskID) {
+ return;
+ }
+ const output = await this.runtimeClient.TaskReport({
+ taskID: this.taskID,
+ });
+ if (!output) {
+ clearInterval(this.syncTaskReportIntervalID);
+ console.error('Sync task report failed');
+ return;
+ }
+ const { workflowStatus, inputs, outputs } = output;
+ if (workflowStatus.terminated) {
+ clearInterval(this.syncTaskReportIntervalID);
+ if (Object.keys(outputs).length > 0) {
+ this.terminatedEmitter.fire({ result: { inputs, outputs } });
+ } else {
+ this.terminatedEmitter.fire({});
+ }
+ }
+ this.updateReport(output);
+ }
+
+ private updateReport(report: IReport): void {
+ const { reports } = report;
+ this.runningNodes = [];
+ this.document.getAllNodes().forEach((node) => {
+ const nodeID = node.id;
+ const nodeReport = reports[nodeID];
+ if (!nodeReport) {
+ return;
+ }
+ if (nodeReport.status === WorkflowStatus.Processing) {
+ this.runningNodes.push(node);
+ }
+ const runningStatus = this.nodeRunningStatus.get(nodeID);
+ if (
+ !runningStatus ||
+ nodeReport.status !== runningStatus.status ||
+ nodeReport.snapshots.length !== runningStatus.nodeResultLength
+ ) {
+ this.nodeRunningStatus.set(nodeID, {
+ nodeID,
+ status: nodeReport.status,
+ nodeResultLength: nodeReport.snapshots.length,
+ });
+ this.reportEmitter.fire(nodeReport);
+ this.document.linesManager.forceUpdate();
+ } else if (nodeReport.status === WorkflowStatus.Processing) {
+ this.reportEmitter.fire(nodeReport);
+ }
+ });
+ }
+}
diff --git a/apps/demo-free-layout/src/plugins/runtime-plugin/server-client/constant.ts b/apps/demo-free-layout/src/plugins/runtime-plugin/server-client/constant.ts
new file mode 100644
index 00000000..a689542e
--- /dev/null
+++ b/apps/demo-free-layout/src/plugins/runtime-plugin/server-client/constant.ts
@@ -0,0 +1,7 @@
+import { ServerConfig } from '../type';
+
+export const DEFAULT_SERVER_CONFIG: ServerConfig = {
+ domain: 'localhost',
+ port: 4000,
+ protocol: 'http',
+};
diff --git a/apps/demo-free-layout/src/plugins/runtime-plugin/server-client/index.ts b/apps/demo-free-layout/src/plugins/runtime-plugin/server-client/index.ts
new file mode 100644
index 00000000..0ce8a93e
--- /dev/null
+++ b/apps/demo-free-layout/src/plugins/runtime-plugin/server-client/index.ts
@@ -0,0 +1,134 @@
+import {
+ FlowGramAPIName,
+ IRuntimeClient,
+ TaskCancelInput,
+ TaskCancelOutput,
+ TaskReportInput,
+ TaskReportOutput,
+ TaskResultInput,
+ TaskResultOutput,
+ TaskRunInput,
+ TaskRunOutput,
+} from '@flowgram.ai/runtime-interface';
+import { injectable } from '@flowgram.ai/free-layout-editor';
+
+import type { ServerError } from './type';
+import { DEFAULT_SERVER_CONFIG } from './constant';
+import { ServerConfig } from '../type';
+
+@injectable()
+export class WorkflowRuntimeServerClient implements IRuntimeClient {
+ private config: ServerConfig = DEFAULT_SERVER_CONFIG;
+
+ constructor() {}
+
+ public init(config: ServerConfig) {
+ this.config = config;
+ }
+
+ public async [FlowGramAPIName.TaskRun](input: TaskRunInput): Promise {
+ try {
+ const body = JSON.stringify(input);
+ const response = await fetch(this.getURL('/api/task/run'), {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: body,
+ redirect: 'follow',
+ });
+ const output: TaskRunOutput | ServerError = await response.json();
+ if (this.isError(output)) {
+ console.error('TaskRun failed', output);
+ return;
+ }
+ return output;
+ } catch (e) {
+ console.error(e);
+ return;
+ }
+ }
+
+ public async [FlowGramAPIName.TaskReport](
+ input: TaskReportInput
+ ): Promise {
+ try {
+ const response = await fetch(this.getURL(`/api/task/report?taskID=${input.taskID}`), {
+ method: 'GET',
+ redirect: 'follow',
+ });
+ const output: TaskReportOutput | ServerError = await response.json();
+ if (this.isError(output)) {
+ console.error('TaskReport failed', output);
+ return;
+ }
+ return output;
+ } catch (e) {
+ console.error(e);
+ return;
+ }
+ }
+
+ public async [FlowGramAPIName.TaskResult](
+ input: TaskResultInput
+ ): Promise {
+ try {
+ const response = await fetch(this.getURL(`/api/task/result?taskID=${input.taskID}`), {
+ method: 'GET',
+ redirect: 'follow',
+ });
+ const output: TaskResultOutput | ServerError = await response.json();
+ if (this.isError(output)) {
+ console.error('TaskReport failed', output);
+ return {
+ success: false,
+ };
+ }
+ return output;
+ } catch (e) {
+ console.error(e);
+ return {
+ success: false,
+ };
+ }
+ }
+
+ public async [FlowGramAPIName.TaskCancel](input: TaskCancelInput): Promise {
+ try {
+ const body = JSON.stringify(input);
+ const response = await fetch(this.getURL(`/api/task/cancel`), {
+ method: 'PUT',
+ redirect: 'follow',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body,
+ });
+ const output: TaskCancelOutput | ServerError = await response.json();
+ if (this.isError(output)) {
+ console.error('TaskReport failed', output);
+ return {
+ success: false,
+ };
+ }
+ return output;
+ } catch (e) {
+ console.error(e);
+ return {
+ success: false,
+ };
+ }
+ }
+
+ private isError(output: unknown | undefined): output is ServerError {
+ return !!output && (output as ServerError).code !== undefined;
+ }
+
+ private getURL(path: string): string {
+ const protocol = this.config.protocol ?? window.location.protocol;
+ const host = this.config.port
+ ? `${this.config.domain}:${this.config.port}`
+ : this.config.domain;
+ return `${protocol}://${host}${path}`;
+ }
+}
diff --git a/apps/demo-free-layout/src/plugins/runtime-plugin/server-client/type.ts b/apps/demo-free-layout/src/plugins/runtime-plugin/server-client/type.ts
new file mode 100644
index 00000000..d0c90a22
--- /dev/null
+++ b/apps/demo-free-layout/src/plugins/runtime-plugin/server-client/type.ts
@@ -0,0 +1,4 @@
+export interface ServerError {
+ code: string;
+ message: string;
+}
diff --git a/apps/demo-free-layout/src/plugins/runtime-plugin/type.ts b/apps/demo-free-layout/src/plugins/runtime-plugin/type.ts
new file mode 100644
index 00000000..0f76f17b
--- /dev/null
+++ b/apps/demo-free-layout/src/plugins/runtime-plugin/type.ts
@@ -0,0 +1,16 @@
+export interface RuntimeBrowserOptions {
+ mode?: 'browser';
+}
+
+export interface RuntimeServerOptions {
+ mode: 'server';
+ serverConfig: ServerConfig;
+}
+
+export type RuntimePluginOptions = RuntimeBrowserOptions | RuntimeServerOptions;
+
+export interface ServerConfig {
+ domain: string;
+ port?: number;
+ protocol?: string;
+}
diff --git a/apps/demo-free-layout/src/services/index.ts b/apps/demo-free-layout/src/services/index.ts
index 21259a88..01db6d60 100644
--- a/apps/demo-free-layout/src/services/index.ts
+++ b/apps/demo-free-layout/src/services/index.ts
@@ -1,2 +1 @@
export { CustomService } from './custom-service';
-export { RunningService } from './running-service';
diff --git a/apps/demo-free-layout/src/services/running-service.ts b/apps/demo-free-layout/src/services/running-service.ts
deleted file mode 100644
index f7d2e9a2..00000000
--- a/apps/demo-free-layout/src/services/running-service.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-import {
- injectable,
- inject,
- WorkflowDocument,
- Playground,
- delay,
- WorkflowLineEntity,
- WorkflowNodeEntity,
- WorkflowNodeLinesData,
-} from '@flowgram.ai/free-layout-editor';
-const RUNNING_INTERVAL = 1000;
-
-@injectable()
-export class RunningService {
- @inject(Playground) playground: Playground;
-
- @inject(WorkflowDocument) document: WorkflowDocument;
-
- private _runningNodes: WorkflowNodeEntity[] = [];
-
- async addRunningNode(node: WorkflowNodeEntity): Promise {
- this._runningNodes.push(node);
- node.renderData.node.classList.add('node-running');
- this.document.linesManager.forceUpdate(); // Refresh line renderer
- await delay(RUNNING_INTERVAL);
- // Child Nodes
- await Promise.all(node.blocks.map((nextNode) => this.addRunningNode(nextNode)));
- // Sibling Nodes
- const nextNodes = node.getData(WorkflowNodeLinesData).outputNodes;
- await Promise.all(nextNodes.map((nextNode) => this.addRunningNode(nextNode)));
- }
-
- async startRun(): Promise {
- await this.addRunningNode(this.document.getNode('start_0')!);
- this._runningNodes.forEach((node) => {
- node.renderData.node.classList.remove('node-running');
- });
- this._runningNodes = [];
- this.document.linesManager.forceUpdate();
- }
-
- isFlowingLine(line: WorkflowLineEntity) {
- return this._runningNodes.some((node) =>
- node.getData(WorkflowNodeLinesData).outputLines.includes(line)
- );
- }
-}
diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml
index a871ca0f..935af3b3 100644
--- a/common/config/rush/pnpm-lock.yaml
+++ b/common/config/rush/pnpm-lock.yaml
@@ -223,6 +223,12 @@ importers:
'@flowgram.ai/minimap-plugin':
specifier: workspace:*
version: link:../../packages/plugins/minimap-plugin
+ '@flowgram.ai/runtime-interface':
+ specifier: workspace:*
+ version: link:../../packages/runtime/interface
+ '@flowgram.ai/runtime-js':
+ specifier: workspace:*
+ version: link:../../packages/runtime/js-core
lodash-es:
specifier: ^4.17.21
version: 4.17.21
@@ -764,7 +770,7 @@ importers:
version: 15.13.0
openai:
specifier: ~4.98.0
- version: 4.98.0
+ version: 4.98.0(ws@8.18.0)(zod@3.25.56)
raw-loader:
specifier: ^4.0.2
version: 4.0.2(webpack@5.76.0)
@@ -1755,7 +1761,7 @@ importers:
version: 8.57.1
tsup:
specifier: ^8.0.1
- version: 8.3.5(typescript@5.0.4)
+ version: 8.3.5(tsx@4.19.4)(typescript@5.0.4)
vitest:
specifier: ^0.34.6
version: 0.34.6(jsdom@22.1.0)
@@ -2472,7 +2478,7 @@ importers:
version: 5.3.11(@babel/core@7.26.10)(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)
tsup:
specifier: ^8.0.1
- version: 8.3.5(typescript@5.0.4)
+ version: 8.3.5(tsx@4.19.4)(typescript@5.0.4)
vitest:
specifier: ^0.34.6
version: 0.34.6(jsdom@22.1.0)
@@ -3260,7 +3266,7 @@ importers:
version: 5.3.11(@babel/core@7.26.10)(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)
tsup:
specifier: ^8.0.1
- version: 8.3.5(typescript@5.0.4)
+ version: 8.3.5(tsx@4.19.4)(typescript@5.0.4)
vitest:
specifier: ^0.34.6
version: 0.34.6(jsdom@22.1.0)
@@ -3735,6 +3741,192 @@ importers:
specifier: ^0.34.6
version: 0.34.6(jsdom@22.1.0)
+ ../../packages/runtime/interface:
+ dependencies:
+ zod:
+ specifier: ^3.24.4
+ version: 3.25.56
+ devDependencies:
+ '@flowgram.ai/eslint-config':
+ specifier: workspace:*
+ version: link:../../../config/eslint-config
+ '@flowgram.ai/ts-config':
+ specifier: workspace:*
+ version: link:../../../config/ts-config
+ eslint:
+ specifier: ^8.54.0
+ version: 8.57.1
+ tsup:
+ specifier: ^8.0.1
+ version: 8.3.5(typescript@5.0.4)
+ typescript:
+ specifier: ^5.0.4
+ version: 5.0.4
+ vitest:
+ specifier: ^0.34.6
+ version: 0.34.6(jsdom@22.1.0)
+
+ ../../packages/runtime/js-core:
+ dependencies:
+ '@langchain/core':
+ specifier: ^0.3.57
+ version: 0.3.57
+ '@langchain/openai':
+ specifier: ^0.5.11
+ version: 0.5.12(@langchain/core@0.3.57)(ws@8.18.0)
+ lodash-es:
+ specifier: ^4.17.21
+ version: 4.17.21
+ uuid:
+ specifier: ^9.0.0
+ version: 9.0.1
+ zod:
+ specifier: ^3.24.4
+ version: 3.25.56
+ devDependencies:
+ '@flowgram.ai/eslint-config':
+ specifier: workspace:*
+ version: link:../../../config/eslint-config
+ '@flowgram.ai/runtime-interface':
+ specifier: workspace:*
+ version: link:../interface
+ '@flowgram.ai/ts-config':
+ specifier: workspace:*
+ version: link:../../../config/ts-config
+ '@types/lodash-es':
+ specifier: ^4.17.12
+ version: 4.17.12
+ '@types/uuid':
+ specifier: ^9.0.1
+ version: 9.0.8
+ '@vitest/coverage-v8':
+ specifier: ^0.32.0
+ version: 0.32.4(vitest@0.34.6)
+ dotenv:
+ specifier: ~16.5.0
+ version: 16.5.0
+ eslint:
+ specifier: ^8.54.0
+ version: 8.57.1
+ tsup:
+ specifier: ^8.0.1
+ version: 8.3.5(typescript@5.0.4)
+ typescript:
+ specifier: ^5.0.4
+ version: 5.0.4
+ vitest:
+ specifier: ^0.34.6
+ version: 0.34.6(jsdom@22.1.0)
+
+ ../../packages/runtime/nodejs:
+ dependencies:
+ '@fastify/cors':
+ specifier: ^8.2.1
+ version: 8.5.0
+ '@fastify/swagger':
+ specifier: ^8.5.1
+ version: 8.15.0
+ '@fastify/swagger-ui':
+ specifier: 4.1.0
+ version: 4.1.0
+ '@fastify/websocket':
+ specifier: ^10.0.1
+ version: 10.0.1
+ '@flowgram.ai/runtime-interface':
+ specifier: workspace:*
+ version: link:../interface
+ '@flowgram.ai/runtime-js':
+ specifier: workspace:*
+ version: link:../js-core
+ '@langchain/core':
+ specifier: ^0.3.57
+ version: 0.3.57
+ '@langchain/openai':
+ specifier: ^0.5.11
+ version: 0.5.12(@langchain/core@0.3.57)(ws@8.18.0)
+ '@trpc/server':
+ specifier: ^10.27.1
+ version: 10.45.2
+ fastify:
+ specifier: ^4.17.0
+ version: 4.29.1
+ lodash-es:
+ specifier: ^4.17.21
+ version: 4.17.21
+ trpc-openapi:
+ specifier: ^1.2.0
+ version: 1.2.0(@trpc/server@10.45.2)(@types/node@18.19.68)(zod@3.25.56)
+ tslib:
+ specifier: ^2.8.1
+ version: 2.8.1
+ ws:
+ specifier: ^8.0.0
+ version: 8.18.0
+ zod:
+ specifier: ^3.24.4
+ version: 3.25.56
+ devDependencies:
+ '@babel/eslint-parser':
+ specifier: ~7.19.1
+ version: 7.19.1(@babel/core@7.26.10)(eslint@8.57.1)
+ '@eslint/eslintrc':
+ specifier: ^3
+ version: 3.3.1
+ '@flowgram.ai/eslint-config':
+ specifier: workspace:*
+ version: link:../../../config/eslint-config
+ '@flowgram.ai/ts-config':
+ specifier: workspace:*
+ version: link:../../../config/ts-config
+ '@types/cors':
+ specifier: ^2.8.13
+ version: 2.8.19
+ '@types/lodash-es':
+ specifier: ^4.17.12
+ version: 4.17.12
+ '@types/node':
+ specifier: ^18
+ version: 18.19.68
+ '@types/ws':
+ specifier: ^8.2.0
+ version: 8.18.1
+ '@typescript-eslint/eslint-plugin':
+ specifier: ^6.10.0
+ version: 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.57.1)(typescript@5.0.4)
+ '@typescript-eslint/parser':
+ specifier: ^6.10.0
+ version: 6.21.0(eslint@8.57.1)(typescript@5.0.4)
+ '@vitest/coverage-v8':
+ specifier: ^0.32.0
+ version: 0.32.4(vitest@0.34.6)
+ dotenv:
+ specifier: ~16.5.0
+ version: 16.5.0
+ eslint:
+ specifier: ^8.54.0
+ version: 8.57.1
+ eslint-plugin-json:
+ specifier: ^4.0.1
+ version: 4.0.1
+ npm-run-all:
+ specifier: ^4.1.5
+ version: 4.1.5
+ tsup:
+ specifier: ^8.0.1
+ version: 8.3.5(tsx@4.19.4)(typescript@5.0.4)
+ tsx:
+ specifier: ~4.19.4
+ version: 4.19.4
+ typescript:
+ specifier: ^5.0.4
+ version: 5.0.4
+ vitest:
+ specifier: ^0.34.6
+ version: 0.34.6(jsdom@22.1.0)
+ wait-port:
+ specifier: ^1.0.1
+ version: 1.1.0
+
../../packages/variable-engine/variable-core:
dependencies:
'@flowgram.ai/core':
@@ -5230,6 +5422,10 @@ packages:
/@bufbuild/protobuf@2.2.3:
resolution: {integrity: sha512-tFQoXHJdkEOSwj5tRIZSPNUuXK3RaR7T1nUrPgbYX1pUbvqqaaZAsfo+NXBPsz5rZMSKVFrgK1WL8Q/MSLvprg==}
+ /@cfworker/json-schema@4.1.1:
+ resolution: {integrity: sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==}
+ dev: false
+
/@codemirror/autocomplete@6.18.4:
resolution: {integrity: sha512-sFAphGQIqyQZfP2ZBsSHV7xQvo9Py0rV0dW7W3IMRdS+zDuNb2l3no78CvUaWKGfzFjI4FTrLdUSj86IGb2hRA==}
dependencies:
@@ -6236,6 +6432,100 @@ packages:
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
dev: true
+ /@fastify/accept-negotiator@1.1.0:
+ resolution: {integrity: sha512-OIHZrb2ImZ7XG85HXOONLcJWGosv7sIvM2ifAPQVhg9Lv7qdmMBNVaai4QTdyuaqbKM5eO6sLSQOYI7wEQeCJQ==}
+ engines: {node: '>=14'}
+ dev: false
+
+ /@fastify/ajv-compiler@3.6.0:
+ resolution: {integrity: sha512-LwdXQJjmMD+GwLOkP7TVC68qa+pSSogeWWmznRJ/coyTcfe9qA05AHFSe1eZFwK6q+xVRpChnvFUkf1iYaSZsQ==}
+ dependencies:
+ ajv: 8.17.1
+ ajv-formats: 2.1.1(ajv@8.17.1)
+ fast-uri: 2.4.0
+ dev: false
+
+ /@fastify/cors@8.5.0:
+ resolution: {integrity: sha512-/oZ1QSb02XjP0IK1U0IXktEsw/dUBTxJOW7IpIeO8c/tNalw/KjoNSJv1Sf6eqoBPO+TDGkifq6ynFK3v68HFQ==}
+ dependencies:
+ fastify-plugin: 4.5.1
+ mnemonist: 0.39.6
+ dev: false
+
+ /@fastify/error@3.4.1:
+ resolution: {integrity: sha512-wWSvph+29GR783IhmvdwWnN4bUxTD01Vm5Xad4i7i1VuAOItLvbPAb69sb0IQ2N57yprvhNIwAP5B6xfKTmjmQ==}
+ dev: false
+
+ /@fastify/fast-json-stringify-compiler@4.3.0:
+ resolution: {integrity: sha512-aZAXGYo6m22Fk1zZzEUKBvut/CIIQe/BapEORnxiD5Qr0kPHqqI69NtEMCme74h+at72sPhbkb4ZrLd1W3KRLA==}
+ dependencies:
+ fast-json-stringify: 5.16.1
+ dev: false
+
+ /@fastify/merge-json-schemas@0.1.1:
+ resolution: {integrity: sha512-fERDVz7topgNjtXsJTTW1JKLy0rhuLRcquYqNR9rF7OcVpCa2OVW49ZPDIhaRRCaUuvVxI+N416xUoF76HNSXA==}
+ dependencies:
+ fast-deep-equal: 3.1.3
+ dev: false
+
+ /@fastify/send@2.1.0:
+ resolution: {integrity: sha512-yNYiY6sDkexoJR0D8IDy3aRP3+L4wdqCpvx5WP+VtEU58sn7USmKynBzDQex5X42Zzvw2gNzzYgP90UfWShLFA==}
+ dependencies:
+ '@lukeed/ms': 2.0.2
+ escape-html: 1.0.3
+ fast-decode-uri-component: 1.0.1
+ http-errors: 2.0.0
+ mime: 3.0.0
+ dev: false
+
+ /@fastify/static@7.0.4:
+ resolution: {integrity: sha512-p2uKtaf8BMOZWLs6wu+Ihg7bWNBdjNgCwDza4MJtTqg+5ovKmcbgbR9Xs5/smZ1YISfzKOCNYmZV8LaCj+eJ1Q==}
+ dependencies:
+ '@fastify/accept-negotiator': 1.1.0
+ '@fastify/send': 2.1.0
+ content-disposition: 0.5.4
+ fastify-plugin: 4.5.1
+ fastq: 1.17.1
+ glob: 10.4.5
+ dev: false
+
+ /@fastify/swagger-ui@4.1.0:
+ resolution: {integrity: sha512-Bqsd6VFQR7WoT6eRammOF8/gXf5GKywq2zYy8/3fj2rsZw43cmXdfsEKxVAmAwOW2Nv+dnyQaf5qM6kBqyXRlw==}
+ dependencies:
+ '@fastify/static': 7.0.4
+ fastify-plugin: 4.5.1
+ openapi-types: 12.1.3
+ rfdc: 1.4.1
+ yaml: 2.8.0
+ dev: false
+
+ /@fastify/swagger@8.15.0:
+ resolution: {integrity: sha512-zy+HEEKFqPMS2sFUsQU5X0MHplhKJvWeohBwTCkBAJA/GDYGLGUWQaETEhptiqxK7Hs0fQB9B4MDb3pbwIiCwA==}
+ dependencies:
+ fastify-plugin: 4.5.1
+ json-schema-resolver: 2.0.0
+ openapi-types: 12.1.3
+ rfdc: 1.4.1
+ yaml: 2.8.0
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
+ /@fastify/websocket@10.0.1:
+ resolution: {integrity: sha512-8/pQIxTPRD8U94aILTeJ+2O3el/r19+Ej5z1O1mXlqplsUH7KzCjAI0sgd5DM/NoPjAi5qLFNIjgM5+9/rGSNw==}
+ dependencies:
+ duplexify: 4.1.3
+ fastify-plugin: 4.5.1
+ ws: 8.18.0
+ transitivePeerDependencies:
+ - bufferutil
+ - utf-8-validate
+ dev: false
+
+ /@hapi/bourne@3.0.0:
+ resolution: {integrity: sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==}
+ dev: false
+
/@humanwhocodes/config-array@0.13.0:
resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==}
engines: {node: '>=10.10.0'}
@@ -6510,6 +6800,42 @@ packages:
'@jridgewell/sourcemap-codec': 1.5.0
dev: false
+ /@langchain/core@0.3.57:
+ resolution: {integrity: sha512-jz28qCTKJmi47b6jqhQ6vYRTG5jRpqhtPQjriRTB5wR8mgvzo6xKs0fG/kExS3ZvM79ytD1npBvgf8i19xOo9Q==}
+ engines: {node: '>=18'}
+ dependencies:
+ '@cfworker/json-schema': 4.1.1
+ ansi-styles: 5.2.0
+ camelcase: 6.3.0
+ decamelize: 1.2.0
+ js-tiktoken: 1.0.20
+ langsmith: 0.3.30
+ mustache: 4.2.0
+ p-queue: 6.6.2
+ p-retry: 4.6.2
+ uuid: 10.0.0
+ zod: 3.25.56
+ zod-to-json-schema: 3.24.5(zod@3.25.56)
+ transitivePeerDependencies:
+ - openai
+ dev: false
+
+ /@langchain/openai@0.5.12(@langchain/core@0.3.57)(ws@8.18.0):
+ resolution: {integrity: sha512-k7rxBY3ed/HIiMLd6HBqFibsfB0+L6c82H8JgXDqKeyUoACJIi1JaKHXmofmCeF2SBXBU9dog4gEGpHfcUDGUA==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@langchain/core': '>=0.3.48 <0.4.0'
+ dependencies:
+ '@langchain/core': 0.3.57
+ js-tiktoken: 1.0.20
+ openai: 4.98.0(ws@8.18.0)(zod@3.25.56)
+ zod: 3.25.56
+ zod-to-json-schema: 3.24.5(zod@3.25.56)
+ transitivePeerDependencies:
+ - encoding
+ - ws
+ dev: false
+
/@lezer/common@1.2.3:
resolution: {integrity: sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==}
dev: false
@@ -6550,6 +6876,11 @@ packages:
'@lezer/common': 1.2.3
dev: false
+ /@lukeed/ms@2.0.2:
+ resolution: {integrity: sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==}
+ engines: {node: '>=8'}
+ dev: false
+
/@marijn/find-cluster-break@1.0.2:
resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==}
dev: false
@@ -8042,6 +8373,10 @@ packages:
engines: {node: '>= 10'}
dev: true
+ /@trpc/server@10.45.2:
+ resolution: {integrity: sha512-wOrSThNNE4HUnuhJG6PfDRp4L2009KDVxsd+2VYH8ro6o/7/jwYZ8Uu5j+VaW+mOmc8EHerHzGcdbGNQSAUPgg==}
+ dev: false
+
/@tsconfig/node10@1.0.11:
resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==}
dev: false
@@ -8114,6 +8449,12 @@ packages:
resolution: {integrity: sha512-/pC9HAB5I/xMlc5FP77qjCnI16ChlJfW0tGa0IUcFn38VJrTV6DeZ60NU5KZBtaOZqjdpwTWohz5HU1RrhiYxQ==}
dev: true
+ /@types/cors@2.8.19:
+ resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==}
+ dependencies:
+ '@types/node': 18.19.68
+ dev: true
+
/@types/debug@4.1.12:
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
dependencies:
@@ -8263,7 +8604,6 @@ packages:
dependencies:
'@types/node': 18.19.68
form-data: 4.0.1
- dev: true
/@types/node@18.19.68:
resolution: {integrity: sha512-QGtpFH1vB99ZmTa63K4/FU8twThj4fuVSBkGddTp7uIL/cuoLWIUSL2RcOaigBhfR+hg5pgGkBnkoOxrTVBMKw==}
@@ -8311,6 +8651,10 @@ packages:
'@types/prop-types': 15.7.14
csstype: 3.1.3
+ /@types/retry@0.12.0:
+ resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==}
+ dev: false
+
/@types/scheduler@0.16.8:
resolution: {integrity: sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==}
dev: true
@@ -8342,6 +8686,20 @@ packages:
/@types/unist@3.0.3:
resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
+ /@types/uuid@10.0.0:
+ resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==}
+ dev: false
+
+ /@types/uuid@9.0.8:
+ resolution: {integrity: sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==}
+ dev: true
+
+ /@types/ws@8.18.1:
+ resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}
+ dependencies:
+ '@types/node': 18.19.68
+ dev: true
+
/@typescript-eslint/eslint-plugin@5.38.1(@typescript-eslint/parser@5.38.1)(eslint@8.57.1)(typescript@5.0.4):
resolution: {integrity: sha512-ky7EFzPhqz3XlhS7vPOoMDaQnQMn+9o5ICR9CPr/6bw8HrFkzhMSxuA3gRfiJVvs7geYrSeawGJjZoZQKCOglQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -8906,7 +9264,18 @@ packages:
engines: {node: '>=6.5'}
dependencies:
event-target-shim: 5.0.1
- dev: true
+
+ /abstract-logging@2.0.1:
+ resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==}
+ dev: false
+
+ /accepts@1.3.8:
+ resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
+ engines: {node: '>= 0.6'}
+ dependencies:
+ mime-types: 2.1.35
+ negotiator: 0.6.3
+ dev: false
/acorn-import-assertions@1.9.0(acorn@8.14.0):
resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==}
@@ -8948,7 +9317,6 @@ packages:
engines: {node: '>= 8.0.0'}
dependencies:
humanize-ms: 1.2.1
- dev: true
/ajv-formats@2.1.1(ajv@8.17.1):
resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==}
@@ -8960,6 +9328,17 @@ packages:
dependencies:
ajv: 8.17.1
+ /ajv-formats@3.0.1(ajv@8.17.1):
+ resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==}
+ peerDependencies:
+ ajv: ^8.0.0
+ peerDependenciesMeta:
+ ajv:
+ optional: true
+ dependencies:
+ ajv: 8.17.1
+ dev: false
+
/ajv-keywords@3.5.2(ajv@6.12.6):
resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==}
peerDependencies:
@@ -9013,6 +9392,13 @@ packages:
/ansi-sequence-parser@1.1.1:
resolution: {integrity: sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==}
+ /ansi-styles@3.2.1:
+ resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==}
+ engines: {node: '>=4'}
+ dependencies:
+ color-convert: 1.9.3
+ dev: true
+
/ansi-styles@4.3.0:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
@@ -9022,7 +9408,6 @@ packages:
/ansi-styles@5.2.0:
resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==}
engines: {node: '>=10'}
- dev: true
/ansi-styles@6.2.1:
resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
@@ -9170,19 +9555,30 @@ packages:
/asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
- dev: true
/at-least-node@1.0.0:
resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==}
engines: {node: '>= 4.0.0'}
dev: false
+ /atomic-sleep@1.0.0:
+ resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==}
+ engines: {node: '>=8.0.0'}
+ dev: false
+
/available-typed-arrays@1.0.7:
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
engines: {node: '>= 0.4'}
dependencies:
possible-typed-array-names: 1.0.0
+ /avvio@8.4.0:
+ resolution: {integrity: sha512-CDSwaxINFy59iNwhYnkvALBwZiTydGkOecZyPkqBpABYR1KqGEsET0VOOYDwtleZSUIdeY36DC2bSZ24CO1igA==}
+ dependencies:
+ '@fastify/error': 3.4.1
+ fastq: 1.17.1
+ dev: false
+
/axe-core@4.10.2:
resolution: {integrity: sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w==}
engines: {node: '>=4'}
@@ -9367,6 +9763,11 @@ packages:
dependencies:
streamsearch: 1.1.0
+ /bytes@3.1.2:
+ resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
+ engines: {node: '>= 0.8'}
+ dev: false
+
/cac@6.7.14:
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
engines: {node: '>=8'}
@@ -9410,6 +9811,11 @@ packages:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'}
+ /camelcase@6.3.0:
+ resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
+ engines: {node: '>=10'}
+ dev: false
+
/camelize@1.0.1:
resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==}
@@ -9435,6 +9841,15 @@ packages:
type-detect: 4.1.0
dev: true
+ /chalk@2.4.2:
+ resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
+ engines: {node: '>=4'}
+ dependencies:
+ ansi-styles: 3.2.1
+ escape-string-regexp: 1.0.5
+ supports-color: 5.5.0
+ dev: true
+
/chalk@4.1.2:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
@@ -9569,16 +9984,37 @@ packages:
engines: {node: '>=6'}
dev: false
+ /co-body@6.2.0:
+ resolution: {integrity: sha512-Kbpv2Yd1NdL1V/V4cwLVxraHDV6K8ayohr2rmH0J87Er8+zJjcTa6dAn9QMPC9CRgU8+aNajKbSf1TzDB1yKPA==}
+ engines: {node: '>=8.0.0'}
+ dependencies:
+ '@hapi/bourne': 3.0.0
+ inflation: 2.1.0
+ qs: 6.14.0
+ raw-body: 2.5.2
+ type-is: 1.6.18
+ dev: false
+
/collapse-white-space@2.1.0:
resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==}
dev: false
+ /color-convert@1.9.3:
+ resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
+ dependencies:
+ color-name: 1.1.3
+ dev: true
+
/color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
dependencies:
color-name: 1.1.4
+ /color-name@1.1.3:
+ resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==}
+ dev: true
+
/color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
@@ -9607,7 +10043,6 @@ packages:
engines: {node: '>= 0.8'}
dependencies:
delayed-stream: 1.0.0
- dev: true
/comma-separated-tokens@1.0.8:
resolution: {integrity: sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==}
@@ -9628,6 +10063,11 @@ packages:
engines: {node: '>= 6'}
dev: true
+ /commander@9.5.0:
+ resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==}
+ engines: {node: ^12.20.0 || >=14}
+ dev: true
+
/compute-scroll-into-view@1.0.20:
resolution: {integrity: sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==}
dev: false
@@ -9644,6 +10084,12 @@ packages:
engines: {node: ^14.18.0 || >=16.10.0}
dev: true
+ /console-table-printer@2.14.2:
+ resolution: {integrity: sha512-TyXKHIzSBFAuxRpgB4MA3RhFVzghJGpG8/eHmpWGm/2ezdswpbdVkxN7xTvDM3snIDKc8UrUs2NiR4LFjv/F1w==}
+ dependencies:
+ simple-wcswidth: 1.0.1
+ dev: false
+
/content-disposition@0.5.4:
resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
engines: {node: '>= 0.6'}
@@ -9654,6 +10100,15 @@ packages:
/convert-source-map@2.0.0:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+ /cookie-es@1.2.2:
+ resolution: {integrity: sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==}
+ dev: false
+
+ /cookie@0.7.2:
+ resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
+ engines: {node: '>= 0.6'}
+ dev: false
+
/copy-anything@2.0.6:
resolution: {integrity: sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==}
dependencies:
@@ -9724,6 +10179,12 @@ packages:
shebang-command: 2.0.0
which: 2.0.2
+ /crossws@0.3.5:
+ resolution: {integrity: sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==}
+ dependencies:
+ uncrypto: 0.1.3
+ dev: false
+
/css-color-keywords@1.0.0:
resolution: {integrity: sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==}
engines: {node: '>=4'}
@@ -9825,6 +10286,11 @@ packages:
ms: 2.1.3
supports-color: 5.5.0
+ /decamelize@1.2.0:
+ resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==}
+ engines: {node: '>=0.10.0'}
+ dev: false
+
/decimal.js@10.4.3:
resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==}
dev: true
@@ -9959,15 +10425,32 @@ packages:
has-property-descriptors: 1.0.2
object-keys: 1.1.1
+ /defu@6.1.4:
+ resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
+ dev: false
+
/delayed-stream@1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
- dev: true
+
+ /depd@1.1.2:
+ resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==}
+ engines: {node: '>= 0.6'}
+ dev: false
+
+ /depd@2.0.0:
+ resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
+ engines: {node: '>= 0.8'}
+ dev: false
/dequal@2.0.3:
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
engines: {node: '>=6'}
+ /destr@2.0.5:
+ resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==}
+ dev: false
+
/detect-indent@7.0.1:
resolution: {integrity: sha512-Mc7QhQ8s+cLrnUfU/Ji94vG/r8M26m8f++vyres4ZoojaRDpZ1eSIh/EpzLNwlWuvzSZ3UbDFspjFvTDXe6e/g==}
engines: {node: '>=12.20'}
@@ -10123,6 +10606,15 @@ packages:
resolution: {integrity: sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==}
dev: false
+ /duplexify@4.1.3:
+ resolution: {integrity: sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==}
+ dependencies:
+ end-of-stream: 1.4.4
+ inherits: 2.0.4
+ readable-stream: 3.6.2
+ stream-shift: 1.0.3
+ dev: false
+
/eastasianwidth@0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
@@ -10174,6 +10666,12 @@ packages:
dev: true
optional: true
+ /error-ex@1.3.2:
+ resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
+ dependencies:
+ is-arrayish: 0.2.1
+ dev: true
+
/error-stack-parser@2.1.4:
resolution: {integrity: sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==}
dependencies:
@@ -10460,10 +10958,13 @@ packages:
resolution: {integrity: sha512-GwBr6yViW3ttx1kb7/Oh+gKQ1/TrhYwxKqVmg5gS+BK+Qe2KrOa/Vh7w3HPBvgGf0LfcDGoY9I6NHKoA5Hozhw==}
dev: false
+ /escape-html@1.0.3:
+ resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
+ dev: false
+
/escape-string-regexp@1.0.5:
resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
engines: {node: '>=0.8.0'}
- dev: false
/escape-string-regexp@4.0.0:
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
@@ -10950,7 +11451,10 @@ packages:
/event-target-shim@5.0.1:
resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
engines: {node: '>=6'}
- dev: true
+
+ /eventemitter3@4.0.7:
+ resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
+ dev: false
/events@3.3.0:
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
@@ -11027,10 +11531,18 @@ packages:
engines: {node: '>=18'}
dev: true
+ /fast-content-type-parse@1.1.0:
+ resolution: {integrity: sha512-fBHHqSTFLVnR61C+gltJuE5GkVQMV0S2nqUO8TJ+5Z3qAKG8vAx4FKai1s5jq/inV1+sREynIWSuQ6HgoSXpDQ==}
+ dev: false
+
/fast-copy@3.0.2:
resolution: {integrity: sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==}
dev: false
+ /fast-decode-uri-component@1.0.1:
+ resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==}
+ dev: false
+
/fast-deep-equal@3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
@@ -11066,12 +11578,64 @@ packages:
/fast-json-stable-stringify@2.1.0:
resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
+ /fast-json-stringify@5.16.1:
+ resolution: {integrity: sha512-KAdnLvy1yu/XrRtP+LJnxbBGrhN+xXu+gt3EUvZhYGKCr3lFHq/7UFJHHFgmJKoqlh6B40bZLEv7w46B0mqn1g==}
+ dependencies:
+ '@fastify/merge-json-schemas': 0.1.1
+ ajv: 8.17.1
+ ajv-formats: 3.0.1(ajv@8.17.1)
+ fast-deep-equal: 3.1.3
+ fast-uri: 2.4.0
+ json-schema-ref-resolver: 1.0.1
+ rfdc: 1.4.1
+ dev: false
+
/fast-levenshtein@2.0.6:
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
+ /fast-querystring@1.1.2:
+ resolution: {integrity: sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==}
+ dependencies:
+ fast-decode-uri-component: 1.0.1
+ dev: false
+
+ /fast-redact@3.5.0:
+ resolution: {integrity: sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==}
+ engines: {node: '>=6'}
+ dev: false
+
+ /fast-uri@2.4.0:
+ resolution: {integrity: sha512-ypuAmmMKInk5q7XcepxlnUWDLWv4GFtaJqAzWKqn62IpQ3pejtr5dTVbt3vwqVaMKmkNR55sTT+CqUKIaT21BA==}
+ dev: false
+
/fast-uri@3.0.6:
resolution: {integrity: sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==}
+ /fastify-plugin@4.5.1:
+ resolution: {integrity: sha512-stRHYGeuqpEZTL1Ef0Ovr2ltazUT9g844X5z/zEBFLG8RYlpDiOCIG+ATvYEp+/zmc7sN29mcIMp8gvYplYPIQ==}
+ dev: false
+
+ /fastify@4.29.1:
+ resolution: {integrity: sha512-m2kMNHIG92tSNWv+Z3UeTR9AWLLuo7KctC7mlFPtMEVrfjIhmQhkQnT9v15qA/BfVq3vvj134Y0jl9SBje3jXQ==}
+ dependencies:
+ '@fastify/ajv-compiler': 3.6.0
+ '@fastify/error': 3.4.1
+ '@fastify/fast-json-stringify-compiler': 4.3.0
+ abstract-logging: 2.0.1
+ avvio: 8.4.0
+ fast-content-type-parse: 1.1.0
+ fast-json-stringify: 5.16.1
+ find-my-way: 8.2.2
+ light-my-request: 5.14.0
+ pino: 9.7.0
+ process-warning: 3.0.0
+ proxy-addr: 2.0.7
+ rfdc: 1.4.1
+ secure-json-parse: 2.7.0
+ semver: 7.6.3
+ toad-cache: 3.7.0
+ dev: false
+
/fastq@1.17.1:
resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==}
dependencies:
@@ -11167,6 +11731,15 @@ packages:
dependencies:
to-regex-range: 5.0.1
+ /find-my-way@8.2.2:
+ resolution: {integrity: sha512-Dobi7gcTEq8yszimcfp/R7+owiT4WncAJ7VTTgFH1jYJ5GaG1FbhjwDG820hptN0QDFvzVY3RfCzdInvGPGzjA==}
+ engines: {node: '>=14'}
+ dependencies:
+ fast-deep-equal: 3.1.3
+ fast-querystring: 1.1.2
+ safe-regex2: 3.1.0
+ dev: false
+
/find-up@5.0.0:
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
engines: {node: '>=10'}
@@ -11207,7 +11780,6 @@ packages:
/form-data-encoder@1.7.2:
resolution: {integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==}
- dev: true
/form-data@2.5.3:
resolution: {integrity: sha512-XHIrMD0NpDrNM/Ckf7XJiBbLl57KEhT3+i3yY+eWm+cqYZJQTZrKo8Y8AWKnuV5GT4scfuUGt9LzNoIx3dU1nQ==}
@@ -11227,7 +11799,6 @@ packages:
asynckit: 0.4.0
combined-stream: 1.0.8
mime-types: 2.1.35
- dev: true
/format@0.2.2:
resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==}
@@ -11239,7 +11810,6 @@ packages:
dependencies:
node-domexception: 1.0.0
web-streams-polyfill: 4.0.0-beta.3
- dev: true
/formdata-polyfill@4.0.10:
resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
@@ -11247,6 +11817,16 @@ packages:
dependencies:
fetch-blob: 3.2.0
+ /forwarded@0.2.0:
+ resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
+ engines: {node: '>= 0.6'}
+ dev: false
+
+ /fresh@0.5.2:
+ resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
+ engines: {node: '>= 0.6'}
+ dev: false
+
/from2@2.3.0:
resolution: {integrity: sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==}
dependencies:
@@ -11497,6 +12077,20 @@ packages:
section-matter: 1.0.0
strip-bom-string: 1.0.0
+ /h3@1.15.3:
+ resolution: {integrity: sha512-z6GknHqyX0h9aQaTx22VZDf6QyZn+0Nh+Ym8O/u0SGSkyF5cuTJYKlc8MkzW3Nzf9LE1ivcpmYC3FUGpywhuUQ==}
+ dependencies:
+ cookie-es: 1.2.2
+ crossws: 0.3.5
+ defu: 6.1.4
+ destr: 2.0.5
+ iron-webcrypto: 1.2.1
+ node-mock-http: 1.0.0
+ radix3: 1.1.2
+ ufo: 1.6.1
+ uncrypto: 0.1.3
+ dev: false
+
/handlebars@4.7.8:
resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==}
engines: {node: '>=0.4.7'}
@@ -11701,6 +12295,10 @@ packages:
dependencies:
react-is: 16.13.1
+ /hosted-git-info@2.8.9:
+ resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==}
+ dev: true
+
/html-encoding-sniffer@3.0.0:
resolution: {integrity: sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==}
engines: {node: '>=12'}
@@ -11754,6 +12352,17 @@ packages:
resolution: {integrity: sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==}
dev: false
+ /http-errors@2.0.0:
+ resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
+ engines: {node: '>= 0.8'}
+ dependencies:
+ depd: 2.0.0
+ inherits: 2.0.4
+ setprototypeof: 1.2.0
+ statuses: 2.0.1
+ toidentifier: 1.0.1
+ dev: false
+
/http-proxy-agent@5.0.0:
resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==}
engines: {node: '>= 6'}
@@ -11783,7 +12392,6 @@ packages:
resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==}
dependencies:
ms: 2.1.3
- dev: true
/iconv-lite@0.4.24:
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
@@ -11833,6 +12441,11 @@ packages:
resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
engines: {node: '>=0.8.19'}
+ /inflation@2.1.0:
+ resolution: {integrity: sha512-t54PPJHG1Pp7VQvxyVCJ9mBbjG3Hqryges9bXoOO6GExCPa+//i/d5GSuFtpx3ALLd7lgIAur6zrIlBQyJuMlQ==}
+ engines: {node: '>= 0.8.0'}
+ dev: false
+
/inflight@1.0.6:
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
@@ -11908,6 +12521,15 @@ packages:
reflect-metadata: 0.2.2
dev: false
+ /ipaddr.js@1.9.1:
+ resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
+ engines: {node: '>= 0.10'}
+ dev: false
+
+ /iron-webcrypto@1.2.1:
+ resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==}
+ dev: false
+
/is-absolute-url@4.0.1:
resolution: {integrity: sha512-/51/TKE88Lmm7Gc4/8btclNXWS+g50wXhYJq8HWIBAGUBnoAdRu1aXeh364t/O7wXDAcTJDP8PNuNKWUDWie+A==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
@@ -11945,6 +12567,10 @@ packages:
call-bind: 1.0.8
get-intrinsic: 1.2.6
+ /is-arrayish@0.2.1:
+ resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
+ dev: true
+
/is-arrayish@0.3.2:
resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==}
requiresBuild: true
@@ -12291,6 +12917,12 @@ packages:
engines: {node: '>=10'}
dev: true
+ /js-tiktoken@1.0.20:
+ resolution: {integrity: sha512-Xlaqhhs8VfCd6Sh7a1cFkZHQbYTLCwVJJWiHVxBYzLPxW0XsoxBy1hitmjkdIjD3Aon5BXLHFwU5O8WUx6HH+A==}
+ dependencies:
+ base64-js: 1.5.1
+ dev: false
+
/js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
@@ -12363,9 +12995,30 @@ packages:
/json-buffer@3.0.1:
resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
+ /json-parse-better-errors@1.0.2:
+ resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==}
+ dev: true
+
/json-parse-even-better-errors@2.3.1:
resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
+ /json-schema-ref-resolver@1.0.1:
+ resolution: {integrity: sha512-EJAj1pgHc1hxF6vo2Z3s69fMjO1INq6eGHXZ8Z6wCQeldCuwxGK9Sxf4/cScGn3FZubCVUehfWtcDM/PLteCQw==}
+ dependencies:
+ fast-deep-equal: 3.1.3
+ dev: false
+
+ /json-schema-resolver@2.0.0:
+ resolution: {integrity: sha512-pJ4XLQP4Q9HTxl6RVDLJ8Cyh1uitSs0CzDBAz1uoJ4sRD/Bk7cFSXL1FUXDW3zJ7YnfliJx6eu8Jn283bpZ4Yg==}
+ engines: {node: '>=10'}
+ dependencies:
+ debug: 4.4.0(supports-color@5.5.0)
+ rfdc: 1.4.1
+ uri-js: 4.4.1
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
/json-schema-traverse@0.4.1:
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
@@ -12424,6 +13077,23 @@ packages:
resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
engines: {node: '>=6'}
+ /langsmith@0.3.30:
+ resolution: {integrity: sha512-ZaiaOx9MysuSQlAkRw8mjm7iqhrlF7HI0LCTLxiNBEWBPywdkgI7UnN+s7KtlRiM0tP1cOLm+dQY++Fi33jkPQ==}
+ peerDependencies:
+ openai: '*'
+ peerDependenciesMeta:
+ openai:
+ optional: true
+ dependencies:
+ '@types/uuid': 10.0.0
+ chalk: 4.1.2
+ console-table-printer: 2.14.2
+ p-queue: 6.6.2
+ p-retry: 4.6.2
+ semver: 7.6.3
+ uuid: 10.0.0
+ dev: false
+
/language-subtag-registry@0.3.23:
resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==}
@@ -12509,6 +13179,14 @@ packages:
prelude-ls: 1.2.1
type-check: 0.4.0
+ /light-my-request@5.14.0:
+ resolution: {integrity: sha512-aORPWntbpH5esaYpGOOmri0OHDOe3wC5M2MQxZ9dvMLZm6DnaAn0kJlcbU9hwsQgLzmZyReKwFwwPkR+nHu5kA==}
+ dependencies:
+ cookie: 0.7.2
+ process-warning: 3.0.0
+ set-cookie-parser: 2.7.1
+ dev: false
+
/lightningcss-darwin-arm64@1.29.2:
resolution: {integrity: sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA==}
engines: {node: '>= 12.0.0'}
@@ -12626,6 +13304,16 @@ packages:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
dev: true
+ /load-json-file@4.0.0:
+ resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==}
+ engines: {node: '>=4'}
+ dependencies:
+ graceful-fs: 4.2.11
+ parse-json: 4.0.0
+ pify: 3.0.0
+ strip-bom: 3.0.0
+ dev: true
+
/load-tsconfig@0.2.5:
resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
@@ -12657,6 +13345,10 @@ packages:
/lodash-es@4.17.21:
resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
+ /lodash.clonedeep@4.5.0:
+ resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==}
+ dev: false
+
/lodash.debounce@4.0.8:
resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==}
dev: false
@@ -13146,6 +13838,11 @@ packages:
'@types/mdast': 4.0.4
dev: false
+ /media-typer@0.3.0:
+ resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
+ engines: {node: '>= 0.6'}
+ dev: false
+
/medium-zoom@1.1.0:
resolution: {integrity: sha512-ewyDsp7k4InCUp3jRmwHBRFGyjBimKps/AJLjRSox+2q/2H4p/PNpQf+pwONWlJiOudkBXtbdmVbFjqyybfTmQ==}
@@ -13153,6 +13850,15 @@ packages:
resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==}
dev: false
+ /memorystream@0.3.1:
+ resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==}
+ engines: {node: '>= 0.10.0'}
+ dev: true
+
+ /merge-descriptors@1.0.3:
+ resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==}
+ dev: false
+
/merge-stream@2.0.0:
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
@@ -13160,6 +13866,11 @@ packages:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'}
+ /methods@1.1.2:
+ resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==}
+ engines: {node: '>= 0.6'}
+ dev: false
+
/micromark-core-commonmark@1.1.0:
resolution: {integrity: sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==}
dependencies:
@@ -13817,8 +14528,12 @@ packages:
engines: {node: '>=4'}
hasBin: true
requiresBuild: true
- dev: true
- optional: true
+
+ /mime@3.0.0:
+ resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==}
+ engines: {node: '>=10.0.0'}
+ hasBin: true
+ dev: false
/mimic-fn@2.1.0:
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
@@ -13876,6 +14591,12 @@ packages:
ufo: 1.5.4
dev: true
+ /mnemonist@0.39.6:
+ resolution: {integrity: sha512-A/0v5Z59y63US00cRSLiloEIw3t5G+MiKz4BhX21FI+YBJXBOGW0ohFxTxO08dsOYlzxo87T7vGfZKYp2bcAWA==}
+ dependencies:
+ obliterator: 2.0.5
+ dev: false
+
/monaco-editor@0.52.2:
resolution: {integrity: sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==}
dev: false
@@ -13887,6 +14608,11 @@ packages:
/ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+ /mustache@4.2.0:
+ resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==}
+ hasBin: true
+ dev: false
+
/mute-stream@1.0.0:
resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
@@ -13931,6 +14657,11 @@ packages:
dev: true
optional: true
+ /negotiator@0.6.3:
+ resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
+ engines: {node: '>= 0.6'}
+ dev: false
+
/neo-async@2.6.2:
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
@@ -14000,7 +14731,6 @@ packages:
optional: true
dependencies:
whatwg-url: 5.0.0
- dev: true
/node-fetch@3.3.2:
resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==}
@@ -14010,9 +14740,47 @@ packages:
fetch-blob: 3.2.0
formdata-polyfill: 4.0.10
+ /node-mock-http@1.0.0:
+ resolution: {integrity: sha512-0uGYQ1WQL1M5kKvGRXWQ3uZCHtLTO8hln3oBjIusM75WoesZ909uQJs/Hb946i2SS+Gsrhkaa6iAO17jRIv6DQ==}
+ dev: false
+
+ /node-mocks-http@1.17.2(@types/node@18.19.68):
+ resolution: {integrity: sha512-HVxSnjNzE9NzoWMx9T9z4MLqwMpLwVvA0oVZ+L+gXskYXEJ6tFn3Kx4LargoB6ie7ZlCLplv7QbWO6N+MysWGA==}
+ engines: {node: '>=14'}
+ peerDependencies:
+ '@types/express': ^4.17.21 || ^5.0.0
+ '@types/node': '*'
+ peerDependenciesMeta:
+ '@types/express':
+ optional: true
+ '@types/node':
+ optional: true
+ dependencies:
+ '@types/node': 18.19.68
+ accepts: 1.3.8
+ content-disposition: 0.5.4
+ depd: 1.1.2
+ fresh: 0.5.2
+ merge-descriptors: 1.0.3
+ methods: 1.1.2
+ mime: 1.6.0
+ parseurl: 1.3.3
+ range-parser: 1.2.1
+ type-is: 1.6.18
+ dev: false
+
/node-releases@2.0.19:
resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==}
+ /normalize-package-data@2.5.0:
+ resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==}
+ dependencies:
+ hosted-git-info: 2.8.9
+ resolve: 1.22.8
+ semver: 5.7.2
+ validate-npm-package-license: 3.0.4
+ dev: true
+
/normalize-path@3.0.0:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'}
@@ -14026,6 +14794,22 @@ packages:
sort-keys: 2.0.0
dev: false
+ /npm-run-all@4.1.5:
+ resolution: {integrity: sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==}
+ engines: {node: '>= 4'}
+ hasBin: true
+ dependencies:
+ ansi-styles: 3.2.1
+ chalk: 2.4.2
+ cross-spawn: 6.0.6
+ memorystream: 0.3.1
+ minimatch: 3.1.2
+ pidtree: 0.3.1
+ read-pkg: 3.0.0
+ shell-quote: 1.8.3
+ string.prototype.padend: 3.1.6
+ dev: true
+
/npm-run-path@2.0.2:
resolution: {integrity: sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==}
engines: {node: '>=4'}
@@ -14117,6 +14901,15 @@ packages:
define-properties: 1.2.1
es-object-atoms: 1.0.0
+ /obliterator@2.0.5:
+ resolution: {integrity: sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw==}
+ dev: false
+
+ /on-exit-leak-free@2.1.2:
+ resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==}
+ engines: {node: '>=14.0.0'}
+ dev: false
+
/once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
dependencies:
@@ -14128,7 +14921,7 @@ packages:
dependencies:
mimic-fn: 2.1.0
- /openai@4.98.0:
+ /openai@4.98.0(ws@8.18.0)(zod@3.25.56):
resolution: {integrity: sha512-TmDKur1WjxxMPQAtLG5sgBSCJmX7ynTsGmewKzoDwl1fRxtbLOsiR0FA/AOAAtYUmP6azal+MYQuOENfdU+7yg==}
hasBin: true
peerDependencies:
@@ -14147,9 +14940,14 @@ packages:
form-data-encoder: 1.7.2
formdata-node: 4.4.1
node-fetch: 2.7.0
+ ws: 8.18.0
+ zod: 3.25.56
transitivePeerDependencies:
- encoding
- dev: true
+
+ /openapi-types@12.1.3:
+ resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==}
+ dev: false
/optionator@0.9.4:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
@@ -14226,6 +15024,22 @@ packages:
dependencies:
p-limit: 3.1.0
+ /p-queue@6.6.2:
+ resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==}
+ engines: {node: '>=8'}
+ dependencies:
+ eventemitter3: 4.0.7
+ p-timeout: 3.2.0
+ dev: false
+
+ /p-retry@4.6.2:
+ resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==}
+ engines: {node: '>=8'}
+ dependencies:
+ '@types/retry': 0.12.0
+ retry: 0.13.1
+ dev: false
+
/p-timeout@2.0.1:
resolution: {integrity: sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==}
engines: {node: '>=4'}
@@ -14233,6 +15047,13 @@ packages:
p-finally: 1.0.0
dev: false
+ /p-timeout@3.2.0:
+ resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==}
+ engines: {node: '>=8'}
+ dependencies:
+ p-finally: 1.0.0
+ dev: false
+
/package-json-from-dist@1.0.1:
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
@@ -14264,6 +15085,14 @@ packages:
is-decimal: 2.0.1
is-hexadecimal: 2.0.1
+ /parse-json@4.0.0:
+ resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==}
+ engines: {node: '>=4'}
+ dependencies:
+ error-ex: 1.3.2
+ json-parse-better-errors: 1.0.2
+ dev: true
+
/parse-node-version@1.0.1:
resolution: {integrity: sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==}
engines: {node: '>= 0.10'}
@@ -14280,6 +15109,11 @@ packages:
leac: 0.6.0
peberminta: 0.9.0
+ /parseurl@1.3.3:
+ resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
+ engines: {node: '>= 0.8'}
+ dev: false
+
/path-exists@4.0.0:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'}
@@ -14307,6 +15141,13 @@ packages:
lru-cache: 10.4.3
minipass: 7.1.2
+ /path-type@3.0.0:
+ resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==}
+ engines: {node: '>=4'}
+ dependencies:
+ pify: 3.0.0
+ dev: true
+
/path-type@4.0.0:
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
engines: {node: '>=8'}
@@ -14344,6 +15185,12 @@ packages:
resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==}
engines: {node: '>=12'}
+ /pidtree@0.3.1:
+ resolution: {integrity: sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==}
+ engines: {node: '>=0.10'}
+ hasBin: true
+ dev: true
+
/pify@2.3.0:
resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
engines: {node: '>=0.10.0'}
@@ -14352,7 +15199,6 @@ packages:
/pify@3.0.0:
resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==}
engines: {node: '>=4'}
- dev: false
/pify@4.0.1:
resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==}
@@ -14371,6 +15217,33 @@ packages:
engines: {node: '>=0.10.0'}
dev: false
+ /pino-abstract-transport@2.0.0:
+ resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==}
+ dependencies:
+ split2: 4.2.0
+ dev: false
+
+ /pino-std-serializers@7.0.0:
+ resolution: {integrity: sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==}
+ dev: false
+
+ /pino@9.7.0:
+ resolution: {integrity: sha512-vnMCM6xZTb1WDmLvtG2lE/2p+t9hDEIvTWJsu6FejkE62vB7gDhvzrpFR4Cw2to+9JNQxVnkAKVPA1KPB98vWg==}
+ hasBin: true
+ dependencies:
+ atomic-sleep: 1.0.0
+ fast-redact: 3.5.0
+ on-exit-leak-free: 2.1.2
+ pino-abstract-transport: 2.0.0
+ pino-std-serializers: 7.0.0
+ process-warning: 5.0.0
+ quick-format-unescaped: 4.0.4
+ real-require: 0.2.0
+ safe-stable-stringify: 2.5.0
+ sonic-boom: 4.2.0
+ thread-stream: 3.1.0
+ dev: false
+
/pirates@4.0.6:
resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==}
engines: {node: '>= 6'}
@@ -14404,7 +15277,7 @@ packages:
resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==}
engines: {node: '>= 0.4'}
- /postcss-load-config@6.0.1:
+ /postcss-load-config@6.0.1(tsx@4.19.4):
resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==}
engines: {node: '>= 18'}
peerDependencies:
@@ -14423,6 +15296,7 @@ packages:
optional: true
dependencies:
lilconfig: 3.1.3
+ tsx: 4.19.4
dev: true
/postcss-value-parser@4.2.0:
@@ -14518,6 +15392,14 @@ packages:
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
dev: false
+ /process-warning@3.0.0:
+ resolution: {integrity: sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==}
+ dev: false
+
+ /process-warning@5.0.0:
+ resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==}
+ dev: false
+
/prop-types@15.8.1:
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
dependencies:
@@ -14533,6 +15415,14 @@ packages:
/property-information@6.5.0:
resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==}
+ /proxy-addr@2.0.7:
+ resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
+ engines: {node: '>= 0.10'}
+ dependencies:
+ forwarded: 0.2.0
+ ipaddr.js: 1.9.1
+ dev: false
+
/prr@1.0.1:
resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==}
requiresBuild: true
@@ -14555,6 +15445,13 @@ packages:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
+ /qs@6.14.0:
+ resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==}
+ engines: {node: '>=0.6'}
+ dependencies:
+ side-channel: 1.1.0
+ dev: false
+
/query-string@5.1.1:
resolution: {integrity: sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==}
engines: {node: '>=0.10.0'}
@@ -14571,11 +15468,34 @@ packages:
/queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
+ /quick-format-unescaped@4.0.4:
+ resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==}
+ dev: false
+
+ /radix3@1.1.2:
+ resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==}
+ dev: false
+
/randombytes@2.1.0:
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
dependencies:
safe-buffer: 5.2.1
+ /range-parser@1.2.1:
+ resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
+ engines: {node: '>= 0.6'}
+ dev: false
+
+ /raw-body@2.5.2:
+ resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==}
+ engines: {node: '>= 0.8'}
+ dependencies:
+ bytes: 3.1.2
+ http-errors: 2.0.0
+ iconv-lite: 0.4.24
+ unpipe: 1.0.0
+ dev: false
+
/raw-loader@4.0.2(webpack@5.76.0):
resolution: {integrity: sha512-ZnScIV3ag9A4wPX/ZayxL/jZH+euYb6FcUinPcgiQW0+UBtEv0O6Q3lGd3cqJ+GHH+rksEv3Pj99oxJ3u3VIKA==}
engines: {node: '>= 10.13.0'}
@@ -14756,6 +15676,15 @@ packages:
dependencies:
loose-envify: 1.4.0
+ /read-pkg@3.0.0:
+ resolution: {integrity: sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==}
+ engines: {node: '>=4'}
+ dependencies:
+ load-json-file: 4.0.0
+ normalize-package-data: 2.5.0
+ path-type: 3.0.0
+ dev: true
+
/readable-stream@2.3.8:
resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==}
dependencies:
@@ -14788,6 +15717,11 @@ packages:
engines: {node: '>= 14.16.0'}
dev: true
+ /real-require@0.2.0:
+ resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==}
+ engines: {node: '>= 12.13.0'}
+ dev: false
+
/rechoir@0.6.2:
resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==}
engines: {node: '>= 0.10'}
@@ -15096,10 +16030,24 @@ packages:
signal-exit: 3.0.7
dev: false
+ /ret@0.4.3:
+ resolution: {integrity: sha512-0f4Memo5QP7WQyUEAYUO3esD/XjOc3Zjjg5CPsAq1p8sIu0XPeMbHJemKA0BO7tV0X7+A0FoEpbmHXWxPyD3wQ==}
+ engines: {node: '>=10'}
+ dev: false
+
+ /retry@0.13.1:
+ resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==}
+ engines: {node: '>= 4'}
+ dev: false
+
/reusify@1.0.4:
resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
+ /rfdc@1.4.1:
+ resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
+ dev: false
+
/rimraf@3.0.2:
resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
deprecated: Rimraf versions prior to v4 are no longer supported
@@ -15250,6 +16198,17 @@ packages:
es-errors: 1.3.0
is-regex: 1.2.1
+ /safe-regex2@3.1.0:
+ resolution: {integrity: sha512-RAAZAGbap2kBfbVhvmnTFv73NWLMvDGOITFYTZBAaY8eR+Ir4ef7Up/e7amo+y1+AH+3PtLkrt9mvcTsG9LXug==}
+ dependencies:
+ ret: 0.4.3
+ dev: false
+
+ /safe-stable-stringify@2.5.0:
+ resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==}
+ engines: {node: '>=10'}
+ dev: false
+
/safer-buffer@2.1.2:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
@@ -15511,6 +16470,10 @@ packages:
extend-shallow: 2.0.1
kind-of: 6.0.3
+ /secure-json-parse@2.7.0:
+ resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==}
+ dev: false
+
/seek-bzip@1.0.6:
resolution: {integrity: sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ==}
hasBin: true
@@ -15546,6 +16509,10 @@ packages:
resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==}
dev: false
+ /set-cookie-parser@2.7.1:
+ resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==}
+ dev: false
+
/set-function-length@1.2.2:
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
engines: {node: '>= 0.4'}
@@ -15566,6 +16533,10 @@ packages:
functions-have-names: 1.2.3
has-property-descriptors: 1.0.2
+ /setprototypeof@1.2.0:
+ resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
+ dev: false
+
/shallow-clone@3.0.1:
resolution: {integrity: sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==}
engines: {node: '>=8'}
@@ -15628,6 +16599,11 @@ packages:
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
engines: {node: '>=8'}
+ /shell-quote@1.8.3:
+ resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==}
+ engines: {node: '>= 0.4'}
+ dev: true
+
/shelljs@0.9.2:
resolution: {integrity: sha512-S3I64fEiKgTZzKCC46zT/Ib9meqofLrQVbpSswtjFfAVDW+AZ54WTnAM/3/yENoxz/V1Cy6u3kiiEbQ4DNphvw==}
engines: {node: '>=18'}
@@ -15710,10 +16686,20 @@ packages:
is-arrayish: 0.3.2
optional: true
+ /simple-wcswidth@1.0.1:
+ resolution: {integrity: sha512-xMO/8eNREtaROt7tJvWJqHBDTMFN4eiQ5I4JRMuilwfnFcV5W9u7RUkueNkdw0jPqGMX36iCywelS5yilTuOxg==}
+ dev: false
+
/slash@3.0.0:
resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
engines: {node: '>=8'}
+ /sonic-boom@4.2.0:
+ resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==}
+ dependencies:
+ atomic-sleep: 1.0.0
+ dev: false
+
/sort-keys-length@1.0.1:
resolution: {integrity: sha512-GRbEOUqCxemTAk/b32F2xa8wDTs+Z1QHOkbhJDQTvv/6G3ZkbJ+frYWsTcc7cBB3Fu4wy4XlLCuNtJuMn7Gsvw==}
engines: {node: '>=0.10.0'}
@@ -15784,6 +16770,33 @@ packages:
/space-separated-tokens@2.0.2:
resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
+ /spdx-correct@3.2.0:
+ resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==}
+ dependencies:
+ spdx-expression-parse: 3.0.1
+ spdx-license-ids: 3.0.21
+ dev: true
+
+ /spdx-exceptions@2.5.0:
+ resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==}
+ dev: true
+
+ /spdx-expression-parse@3.0.1:
+ resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==}
+ dependencies:
+ spdx-exceptions: 2.5.0
+ spdx-license-ids: 3.0.21
+ dev: true
+
+ /spdx-license-ids@3.0.21:
+ resolution: {integrity: sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==}
+ dev: true
+
+ /split2@4.2.0:
+ resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
+ engines: {node: '>= 10.x'}
+ dev: false
+
/sprintf-js@1.0.3:
resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
@@ -15810,6 +16823,11 @@ packages:
outvariant: 1.4.0
dev: false
+ /statuses@2.0.1:
+ resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
+ engines: {node: '>= 0.8'}
+ dev: false
+
/std-env@3.8.0:
resolution: {integrity: sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==}
dev: true
@@ -15821,6 +16839,10 @@ packages:
internal-slot: 1.0.7
dev: true
+ /stream-shift@1.0.3:
+ resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==}
+ dev: false
+
/streamsearch@1.1.0:
resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}
engines: {node: '>=10.0.0'}
@@ -15875,6 +16897,16 @@ packages:
set-function-name: 2.0.2
side-channel: 1.1.0
+ /string.prototype.padend@3.1.6:
+ resolution: {integrity: sha512-XZpspuSB7vJWhvJc9DLSlrXl1mcA2BdoY5jjnS135ydXqLoqhs96JjDtCkjJEQHvfqZIp9hBuBMgI589peyx9Q==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.8
+ define-properties: 1.2.1
+ es-abstract: 1.23.5
+ es-object-atoms: 1.0.0
+ dev: true
+
/string.prototype.repeat@1.0.0:
resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==}
dependencies:
@@ -16185,6 +17217,12 @@ packages:
any-promise: 1.3.0
dev: true
+ /thread-stream@3.1.0:
+ resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==}
+ dependencies:
+ real-require: 0.2.0
+ dev: false
+
/through@2.3.8:
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
dev: false
@@ -16244,9 +17282,19 @@ packages:
dependencies:
is-number: 7.0.0
+ /toad-cache@3.7.0:
+ resolution: {integrity: sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==}
+ engines: {node: '>=12'}
+ dev: false
+
/toggle-selection@1.0.6:
resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==}
+ /toidentifier@1.0.1:
+ resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
+ engines: {node: '>=0.6'}
+ dev: false
+
/tough-cookie@4.1.4:
resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==}
engines: {node: '>=6'}
@@ -16259,7 +17307,6 @@ packages:
/tr46@0.0.3:
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
- dev: true
/tr46@1.0.1:
resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==}
@@ -16292,6 +17339,25 @@ packages:
/trough@2.2.0:
resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==}
+ /trpc-openapi@1.2.0(@trpc/server@10.45.2)(@types/node@18.19.68)(zod@3.25.56):
+ resolution: {integrity: sha512-pfYoCd/3KYXWXvUPZBKJw455OOwngKN/6SIcj7Yit19OMLJ+8yVZkEvGEeg5wUSwfsiTdRsKuvqkRPXVSwV7ew==}
+ peerDependencies:
+ '@trpc/server': ^10.0.0
+ zod: ^3.14.4
+ dependencies:
+ '@trpc/server': 10.45.2
+ co-body: 6.2.0
+ h3: 1.15.3
+ lodash.clonedeep: 4.5.0
+ node-mocks-http: 1.17.2(@types/node@18.19.68)
+ openapi-types: 12.1.3
+ zod: 3.25.56
+ zod-to-json-schema: 3.24.5(zod@3.25.56)
+ transitivePeerDependencies:
+ - '@types/express'
+ - '@types/node'
+ dev: false
+
/ts-api-utils@1.4.3(typescript@5.0.4):
resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==}
engines: {node: '>=16'}
@@ -16349,6 +17415,49 @@ packages:
/tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+ /tsup@8.3.5(tsx@4.19.4)(typescript@5.0.4):
+ resolution: {integrity: sha512-Tunf6r6m6tnZsG9GYWndg0z8dEV7fD733VBFzFJ5Vcm1FtlXB8xBD/rtrBi2a3YKEV7hHtxiZtW5EAVADoe1pA==}
+ engines: {node: '>=18'}
+ hasBin: true
+ peerDependencies:
+ '@microsoft/api-extractor': ^7.36.0
+ '@swc/core': ^1
+ postcss: ^8.4.12
+ typescript: '>=4.5.0'
+ peerDependenciesMeta:
+ '@microsoft/api-extractor':
+ optional: true
+ '@swc/core':
+ optional: true
+ postcss:
+ optional: true
+ typescript:
+ optional: true
+ dependencies:
+ bundle-require: 5.0.0(esbuild@0.24.0)
+ cac: 6.7.14
+ chokidar: 4.0.1
+ consola: 3.2.3
+ debug: 4.4.0(supports-color@5.5.0)
+ esbuild: 0.24.0
+ joycon: 3.1.1
+ picocolors: 1.1.1
+ postcss-load-config: 6.0.1(tsx@4.19.4)
+ resolve-from: 5.0.0
+ rollup: 4.40.2
+ source-map: 0.8.0-beta.0
+ sucrase: 3.35.0
+ tinyexec: 0.3.1
+ tinyglobby: 0.2.13
+ tree-kill: 1.2.2
+ typescript: 5.0.4
+ transitivePeerDependencies:
+ - jiti
+ - supports-color
+ - tsx
+ - yaml
+ dev: true
+
/tsup@8.3.5(typescript@5.0.4):
resolution: {integrity: sha512-Tunf6r6m6tnZsG9GYWndg0z8dEV7fD733VBFzFJ5Vcm1FtlXB8xBD/rtrBi2a3YKEV7hHtxiZtW5EAVADoe1pA==}
engines: {node: '>=18'}
@@ -16376,7 +17485,7 @@ packages:
esbuild: 0.24.0
joycon: 3.1.1
picocolors: 1.1.1
- postcss-load-config: 6.0.1
+ postcss-load-config: 6.0.1(tsx@4.19.4)
resolve-from: 5.0.0
rollup: 4.28.1
source-map: 0.8.0-beta.0
@@ -16433,6 +17542,14 @@ packages:
engines: {node: '>=10'}
dev: false
+ /type-is@1.6.18:
+ resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
+ engines: {node: '>= 0.6'}
+ dependencies:
+ media-typer: 0.3.0
+ mime-types: 2.1.35
+ dev: false
+
/type@2.7.3:
resolution: {integrity: sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==}
dev: false
@@ -16524,6 +17641,10 @@ packages:
resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==}
dev: true
+ /ufo@1.6.1:
+ resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==}
+ dev: false
+
/uglify-js@3.19.3:
resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==}
engines: {node: '>=0.8.0'}
@@ -16546,6 +17667,10 @@ packages:
through: 2.3.8
dev: false
+ /uncrypto@0.1.3:
+ resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==}
+ dev: false
+
/undici-types@5.26.5:
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
@@ -16686,6 +17811,11 @@ packages:
resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
engines: {node: '>= 10.0.0'}
+ /unpipe@1.0.0:
+ resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
+ engines: {node: '>= 0.8'}
+ dev: false
+
/update-browserslist-db@1.1.1(browserslist@4.24.2):
resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==}
hasBin: true
@@ -16729,6 +17859,16 @@ packages:
engines: {node: '>= 4'}
dev: false
+ /uuid@10.0.0:
+ resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==}
+ hasBin: true
+ dev: false
+
+ /uuid@9.0.1:
+ resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==}
+ hasBin: true
+ dev: false
+
/uvu@0.5.6:
resolution: {integrity: sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==}
engines: {node: '>=8'}
@@ -16752,6 +17892,13 @@ packages:
convert-source-map: 2.0.0
dev: true
+ /validate-npm-package-license@3.0.4:
+ resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
+ dependencies:
+ spdx-correct: 3.2.0
+ spdx-expression-parse: 3.0.1
+ dev: true
+
/varint@6.0.0:
resolution: {integrity: sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==}
@@ -17006,6 +18153,18 @@ packages:
xml-name-validator: 4.0.0
dev: true
+ /wait-port@1.1.0:
+ resolution: {integrity: sha512-3e04qkoN3LxTMLakdqeWth8nih8usyg+sf1Bgdf9wwUkp05iuK1eSY/QpLvscT/+F/gA89+LpUmmgBtesbqI2Q==}
+ engines: {node: '>=10'}
+ hasBin: true
+ dependencies:
+ chalk: 4.1.2
+ commander: 9.5.0
+ debug: 4.4.0(supports-color@5.5.0)
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
/watchpack@2.4.2:
resolution: {integrity: sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==}
engines: {node: '>=10.13.0'}
@@ -17029,11 +18188,9 @@ packages:
/web-streams-polyfill@4.0.0-beta.3:
resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==}
engines: {node: '>= 14'}
- dev: true
/webidl-conversions@3.0.1:
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
- dev: true
/webidl-conversions@4.0.2:
resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==}
@@ -17121,7 +18278,6 @@ packages:
dependencies:
tr46: 0.0.3
webidl-conversions: 3.0.1
- dev: true
/whatwg-url@7.1.0:
resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==}
@@ -17251,7 +18407,6 @@ packages:
optional: true
utf-8-validate:
optional: true
- dev: true
/xml-name-validator@4.0.0:
resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==}
@@ -17274,6 +18429,12 @@ packages:
engines: {node: '>=18'}
dev: false
+ /yaml@2.8.0:
+ resolution: {integrity: sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==}
+ engines: {node: '>= 14.6'}
+ hasBin: true
+ dev: false
+
/yauzl@2.10.0:
resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==}
dependencies:
@@ -17300,5 +18461,16 @@ packages:
engines: {node: '>=18'}
dev: false
+ /zod-to-json-schema@3.24.5(zod@3.25.56):
+ resolution: {integrity: sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==}
+ peerDependencies:
+ zod: ^3.24.1
+ dependencies:
+ zod: 3.25.56
+ dev: false
+
+ /zod@3.25.56:
+ resolution: {integrity: sha512-rd6eEF3BTNvQnR2e2wwolfTmUTnp70aUTqr0oaGbHifzC3BKJsoV+Gat8vxUMR1hwOKBs6El+qWehrHbCpW6SQ==}
+
/zwitch@2.0.4:
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
diff --git a/cspell.json b/cspell.json
index 0b6c7f27..b0e9a96c 100644
--- a/cspell.json
+++ b/cspell.json
@@ -9,15 +9,17 @@
"douyinfe",
"flowgram",
"flowgram.ai",
+ "gedit",
"Hoverable",
+ "langchain",
"openbracket",
"rsbuild",
"rspack",
"rspress",
"Sandpack",
+ "testrun",
"zoomin",
- "zoomout",
- "gedit"
+ "zoomout"
],
"ignoreWords": [],
"import": []
diff --git a/packages/plugins/free-container-plugin/src/sub-canvas/components/render/index.tsx b/packages/plugins/free-container-plugin/src/sub-canvas/components/render/index.tsx
index fd109ecc..f4c6a7a1 100644
--- a/packages/plugins/free-container-plugin/src/sub-canvas/components/render/index.tsx
+++ b/packages/plugins/free-container-plugin/src/sub-canvas/components/render/index.tsx
@@ -1,23 +1,20 @@
import React, { CSSProperties, type FC } from 'react';
-import { useCurrentEntity } from '@flowgram.ai/free-layout-core';
-
import { SubCanvasRenderStyle } from './style';
import { SubCanvasTips } from '../tips';
import { SubCanvasBorder } from '../border';
import { SubCanvasBackground } from '../background';
import { useNodeSize, useSyncNodeRenderSize } from '../../hooks';
-interface ISubCanvasBorder {
+interface ISubCanvasRender {
+ offsetY: number;
className?: string;
style?: CSSProperties;
}
-export const SubCanvasRender: FC = ({ className, style }) => {
- const node = useCurrentEntity();
+export const SubCanvasRender: FC = ({ className, style, offsetY }) => {
const nodeSize = useNodeSize();
const nodeHeight = nodeSize?.height ?? 0;
- const { padding } = node.transform;
useSyncNodeRenderSize(nodeSize);
@@ -25,7 +22,7 @@ export const SubCanvasRender: FC = ({ className, style }) => {
Promise;
+ [FlowGramAPIName.TaskReport]: (input: TaskReportInput) => Promise;
+ [FlowGramAPIName.TaskResult]: (input: TaskResultInput) => Promise;
+ [FlowGramAPIName.TaskCancel]: (input: TaskCancelInput) => Promise;
+}
diff --git a/packages/runtime/interface/src/index.ts b/packages/runtime/interface/src/index.ts
new file mode 100644
index 00000000..2436056f
--- /dev/null
+++ b/packages/runtime/interface/src/index.ts
@@ -0,0 +1,5 @@
+export * from './api';
+export * from './schema';
+export * from './node';
+export * from './runtime';
+export * from './client';
diff --git a/packages/runtime/interface/src/node/constant.ts b/packages/runtime/interface/src/node/constant.ts
new file mode 100644
index 00000000..5b10342d
--- /dev/null
+++ b/packages/runtime/interface/src/node/constant.ts
@@ -0,0 +1,11 @@
+export enum FlowGramNode {
+ Root = 'root',
+ Start = 'start',
+ End = 'end',
+ LLM = 'llm',
+ code = 'code',
+ Condition = 'condition',
+ Loop = 'loop',
+ Comment = 'comment',
+ Group = 'group',
+}
diff --git a/packages/runtime/interface/src/node/end/index.ts b/packages/runtime/interface/src/node/end/index.ts
new file mode 100644
index 00000000..d078fe2f
--- /dev/null
+++ b/packages/runtime/interface/src/node/end/index.ts
@@ -0,0 +1,13 @@
+import { IFlowConstantRefValue } from '@schema/value';
+import { WorkflowNodeSchema } from '@schema/node';
+import { IJsonSchema } from '@schema/json-schema';
+import { FlowGramNode } from '@node/constant';
+
+interface EndNodeData {
+ title: string;
+ inputs: IJsonSchema<'object'>;
+ outputs: IJsonSchema<'object'>;
+ outputValues: Record;
+}
+
+export type EndNodeSchema = WorkflowNodeSchema;
diff --git a/packages/runtime/interface/src/node/index.ts b/packages/runtime/interface/src/node/index.ts
new file mode 100644
index 00000000..d5377b2a
--- /dev/null
+++ b/packages/runtime/interface/src/node/index.ts
@@ -0,0 +1,4 @@
+export { FlowGramNode } from './constant';
+export { EndNodeSchema } from './end';
+export { LLMNodeSchema } from './llm';
+export { StartNodeSchema } from './start';
diff --git a/packages/runtime/interface/src/node/llm/index.ts b/packages/runtime/interface/src/node/llm/index.ts
new file mode 100644
index 00000000..3bf87f3d
--- /dev/null
+++ b/packages/runtime/interface/src/node/llm/index.ts
@@ -0,0 +1,20 @@
+import { IFlowConstantRefValue } from '@schema/value';
+import { WorkflowNodeSchema } from '@schema/node';
+import { IJsonSchema } from '@schema/json-schema';
+import { FlowGramNode } from '@node/constant';
+
+interface LLMNodeData {
+ title: string;
+ inputs: IJsonSchema<'object'>;
+ outputs: IJsonSchema<'object'>;
+ inputValues: {
+ apiKey: IFlowConstantRefValue;
+ modelType: IFlowConstantRefValue;
+ baseURL: IFlowConstantRefValue;
+ temperature: IFlowConstantRefValue;
+ systemPrompt: IFlowConstantRefValue;
+ prompt: IFlowConstantRefValue;
+ };
+}
+
+export type LLMNodeSchema = WorkflowNodeSchema;
diff --git a/packages/runtime/interface/src/node/start/index.ts b/packages/runtime/interface/src/node/start/index.ts
new file mode 100644
index 00000000..f0dc5b86
--- /dev/null
+++ b/packages/runtime/interface/src/node/start/index.ts
@@ -0,0 +1,10 @@
+import { WorkflowNodeSchema } from '@schema/node';
+import { IJsonSchema } from '@schema/json-schema';
+import { FlowGramNode } from '@node/constant';
+
+interface StartNodeData {
+ title: string;
+ outputs: IJsonSchema<'object'>;
+}
+
+export type StartNodeSchema = WorkflowNodeSchema;
diff --git a/packages/runtime/interface/src/runtime/base/index.ts b/packages/runtime/interface/src/runtime/base/index.ts
new file mode 100644
index 00000000..e5f0222a
--- /dev/null
+++ b/packages/runtime/interface/src/runtime/base/index.ts
@@ -0,0 +1,3 @@
+export type { VOData } from './value-object';
+export type { InvokeParams, WorkflowRuntimeInvoke } from './invoke';
+export type { WorkflowInputs, WorkflowOutputs } from './inputs-outputs';
diff --git a/packages/runtime/interface/src/runtime/base/inputs-outputs.ts b/packages/runtime/interface/src/runtime/base/inputs-outputs.ts
new file mode 100644
index 00000000..a2b9cfda
--- /dev/null
+++ b/packages/runtime/interface/src/runtime/base/inputs-outputs.ts
@@ -0,0 +1,2 @@
+export type WorkflowInputs = Record;
+export type WorkflowOutputs = Record;
diff --git a/packages/runtime/interface/src/runtime/base/invoke.ts b/packages/runtime/interface/src/runtime/base/invoke.ts
new file mode 100644
index 00000000..da68cdd8
--- /dev/null
+++ b/packages/runtime/interface/src/runtime/base/invoke.ts
@@ -0,0 +1,9 @@
+import { WorkflowSchema } from '@schema/index';
+import { WorkflowInputs } from './inputs-outputs';
+
+export interface InvokeParams {
+ schema: WorkflowSchema;
+ inputs: WorkflowInputs;
+}
+
+export type WorkflowRuntimeInvoke = (params: InvokeParams) => Promise;
diff --git a/packages/runtime/interface/src/runtime/base/value-object.ts b/packages/runtime/interface/src/runtime/base/value-object.ts
new file mode 100644
index 00000000..cf5053a4
--- /dev/null
+++ b/packages/runtime/interface/src/runtime/base/value-object.ts
@@ -0,0 +1 @@
+export type VOData = Omit;
diff --git a/packages/runtime/interface/src/runtime/container/index.ts b/packages/runtime/interface/src/runtime/container/index.ts
new file mode 100644
index 00000000..3bcb2e4b
--- /dev/null
+++ b/packages/runtime/interface/src/runtime/container/index.ts
@@ -0,0 +1,5 @@
+export type ContainerService = any;
+
+export interface IContainer {
+ get(key: any): T;
+}
diff --git a/packages/runtime/interface/src/runtime/context/index.ts b/packages/runtime/interface/src/runtime/context/index.ts
new file mode 100644
index 00000000..44ee0ea8
--- /dev/null
+++ b/packages/runtime/interface/src/runtime/context/index.ts
@@ -0,0 +1,25 @@
+import { IVariableStore } from '@runtime/variable';
+import { IStatusCenter } from '@runtime/status';
+import { ISnapshotCenter } from '@runtime/snapshot';
+import { IIOCenter } from '@runtime/io-center';
+import { IState } from '../state';
+import { IReporter } from '../reporter';
+import { IDocument } from '../document';
+import { InvokeParams } from '../base';
+
+export interface ContextData {
+ variableStore: IVariableStore;
+ state: IState;
+ document: IDocument;
+ ioCenter: IIOCenter;
+ snapshotCenter: ISnapshotCenter;
+ statusCenter: IStatusCenter;
+ reporter: IReporter;
+}
+
+export interface IContext extends ContextData {
+ id: string;
+ init(params: InvokeParams): void;
+ dispose(): void;
+ sub(): IContext;
+}
diff --git a/packages/runtime/interface/src/runtime/document/document.ts b/packages/runtime/interface/src/runtime/document/document.ts
new file mode 100644
index 00000000..52ff19ed
--- /dev/null
+++ b/packages/runtime/interface/src/runtime/document/document.ts
@@ -0,0 +1,14 @@
+import { WorkflowSchema } from '@schema/index';
+import { INode } from './node';
+import { IEdge } from './edge';
+
+export interface IDocument {
+ id: string;
+ nodes: INode[];
+ edges: IEdge[];
+ root: INode;
+ start: INode;
+ end: INode;
+ init(schema: WorkflowSchema): void;
+ dispose(): void;
+}
diff --git a/packages/runtime/interface/src/runtime/document/edge.ts b/packages/runtime/interface/src/runtime/document/edge.ts
new file mode 100644
index 00000000..aaa0858e
--- /dev/null
+++ b/packages/runtime/interface/src/runtime/document/edge.ts
@@ -0,0 +1,16 @@
+import { IPort } from './port';
+import { INode } from './node';
+
+export interface IEdge {
+ id: string;
+ from: INode;
+ to: INode;
+ fromPort: IPort;
+ toPort: IPort;
+}
+
+export interface CreateEdgeParams {
+ id: string;
+ from: INode;
+ to: INode;
+}
diff --git a/packages/runtime/interface/src/runtime/document/index.ts b/packages/runtime/interface/src/runtime/document/index.ts
new file mode 100644
index 00000000..486ec297
--- /dev/null
+++ b/packages/runtime/interface/src/runtime/document/index.ts
@@ -0,0 +1,4 @@
+export type { IDocument } from './document';
+export type { IEdge, CreateEdgeParams } from './edge';
+export type { NodeDeclare as NodeVariable, INode, CreateNodeParams } from './node';
+export type { IPort, CreatePortParams } from './port';
diff --git a/packages/runtime/interface/src/runtime/document/node.ts b/packages/runtime/interface/src/runtime/document/node.ts
new file mode 100644
index 00000000..db3a3c77
--- /dev/null
+++ b/packages/runtime/interface/src/runtime/document/node.ts
@@ -0,0 +1,41 @@
+import { IFlowConstantRefValue, IJsonSchema, PositionSchema } from '@schema/index';
+import { FlowGramNode } from '@node/constant';
+import { IPort } from './port';
+import { IEdge } from './edge';
+
+export interface NodeDeclare {
+ inputsValues?: Record;
+ inputs?: IJsonSchema;
+ outputs?: IJsonSchema;
+}
+
+export interface INode {
+ id: string;
+ type: FlowGramNode;
+ name: string;
+ position: PositionSchema;
+ declare: NodeDeclare;
+ data: T;
+ ports: {
+ inputs: IPort[];
+ outputs: IPort[];
+ };
+ edges: {
+ inputs: IEdge[];
+ outputs: IEdge[];
+ };
+ parent: INode | null;
+ children: INode[];
+ prev: INode[];
+ next: INode[];
+ isBranch: boolean;
+}
+
+export interface CreateNodeParams {
+ id: string;
+ type: FlowGramNode;
+ name: string;
+ position: PositionSchema;
+ variable?: NodeDeclare;
+ data?: any;
+}
diff --git a/packages/runtime/interface/src/runtime/document/port.ts b/packages/runtime/interface/src/runtime/document/port.ts
new file mode 100644
index 00000000..099ea291
--- /dev/null
+++ b/packages/runtime/interface/src/runtime/document/port.ts
@@ -0,0 +1,16 @@
+import { WorkflowPortType } from '@schema/index';
+import { INode } from './node';
+import { IEdge } from './edge';
+
+export interface IPort {
+ id: string;
+ node: INode;
+ edges: IEdge[];
+ type: WorkflowPortType;
+}
+
+export interface CreatePortParams {
+ id: string;
+ node: INode;
+ type: WorkflowPortType;
+}
diff --git a/packages/runtime/interface/src/runtime/engine/index.ts b/packages/runtime/interface/src/runtime/engine/index.ts
new file mode 100644
index 00000000..7fa0c6da
--- /dev/null
+++ b/packages/runtime/interface/src/runtime/engine/index.ts
@@ -0,0 +1,16 @@
+import { ITask } from '../task';
+import { IExecutor } from '../executor';
+import { INode } from '../document';
+import { IContext } from '../context';
+import { InvokeParams } from '../base';
+
+export interface EngineServices {
+ Executor: IExecutor;
+}
+
+export interface IEngine {
+ invoke(params: InvokeParams): ITask;
+ executeNode(params: { context: IContext; node: INode }): Promise;
+}
+
+export const IEngine = Symbol.for('Engine');
diff --git a/packages/runtime/interface/src/runtime/executor/executor.ts b/packages/runtime/interface/src/runtime/executor/executor.ts
new file mode 100644
index 00000000..874d62b1
--- /dev/null
+++ b/packages/runtime/interface/src/runtime/executor/executor.ts
@@ -0,0 +1,8 @@
+import { ExecutionContext, ExecutionResult, INodeExecutor } from './node-executor';
+
+export interface IExecutor {
+ execute: (context: ExecutionContext) => Promise;
+ register: (executor: INodeExecutor) => void;
+}
+
+export const IExecutor = Symbol.for('Executor');
diff --git a/packages/runtime/interface/src/runtime/executor/index.ts b/packages/runtime/interface/src/runtime/executor/index.ts
new file mode 100644
index 00000000..3830c4c9
--- /dev/null
+++ b/packages/runtime/interface/src/runtime/executor/index.ts
@@ -0,0 +1,7 @@
+export { IExecutor } from './executor';
+export type {
+ ExecutionContext,
+ ExecutionResult,
+ INodeExecutor,
+ INodeExecutorFactory,
+} from './node-executor';
diff --git a/packages/runtime/interface/src/runtime/executor/node-executor.ts b/packages/runtime/interface/src/runtime/executor/node-executor.ts
new file mode 100644
index 00000000..0ade2c9c
--- /dev/null
+++ b/packages/runtime/interface/src/runtime/executor/node-executor.ts
@@ -0,0 +1,26 @@
+import { FlowGramNode } from '@node/index';
+import { INode } from '../document';
+import { IContext } from '../context';
+import { IContainer } from '../container';
+import { WorkflowInputs, WorkflowOutputs } from '../base';
+
+export interface ExecutionContext {
+ node: INode;
+ inputs: WorkflowInputs;
+ container: IContainer;
+ runtime: IContext;
+}
+
+export interface ExecutionResult {
+ outputs: WorkflowOutputs;
+ branch?: string;
+}
+
+export interface INodeExecutor {
+ type: FlowGramNode;
+ execute: (context: ExecutionContext) => Promise;
+}
+
+export interface INodeExecutorFactory {
+ new (): INodeExecutor;
+}
diff --git a/packages/runtime/interface/src/runtime/index.ts b/packages/runtime/interface/src/runtime/index.ts
new file mode 100644
index 00000000..941163e8
--- /dev/null
+++ b/packages/runtime/interface/src/runtime/index.ts
@@ -0,0 +1,14 @@
+export * from './container';
+export * from './base';
+export * from './engine';
+export * from './context';
+export * from './document';
+export * from './executor';
+export * from './io-center';
+export * from './snapshot';
+export * from './reporter';
+export * from './state';
+export * from './status';
+export * from './task';
+export * from './validation';
+export * from './variable';
diff --git a/packages/runtime/interface/src/runtime/io-center/index.ts b/packages/runtime/interface/src/runtime/io-center/index.ts
new file mode 100644
index 00000000..8de30c25
--- /dev/null
+++ b/packages/runtime/interface/src/runtime/io-center/index.ts
@@ -0,0 +1,17 @@
+import { WorkflowInputs, WorkflowOutputs } from '../base';
+
+export interface IOData {
+ inputs: WorkflowInputs;
+ outputs: WorkflowOutputs;
+}
+
+/** Input & Output */
+export interface IIOCenter {
+ inputs: WorkflowInputs;
+ outputs: WorkflowOutputs;
+ setInputs(inputs: WorkflowInputs): void;
+ setOutputs(outputs: WorkflowOutputs): void;
+ init(inputs: WorkflowInputs): void;
+ dispose(): void;
+ export(): IOData;
+}
diff --git a/packages/runtime/interface/src/runtime/reporter/index.ts b/packages/runtime/interface/src/runtime/reporter/index.ts
new file mode 100644
index 00000000..30ad917f
--- /dev/null
+++ b/packages/runtime/interface/src/runtime/reporter/index.ts
@@ -0,0 +1,23 @@
+import { StatusData, IStatusCenter } from '../status';
+import { Snapshot, ISnapshotCenter } from '../snapshot';
+import { WorkflowInputs, WorkflowOutputs } from '../base';
+export interface NodeReport extends StatusData {
+ id: string;
+ snapshots: Snapshot[];
+}
+
+export interface IReport {
+ id: string;
+ inputs: WorkflowInputs;
+ outputs: WorkflowOutputs;
+ workflowStatus: StatusData;
+ reports: Record;
+}
+
+export interface IReporter {
+ snapshotCenter: ISnapshotCenter;
+ statusCenter: IStatusCenter;
+ init(): void;
+ dispose(): void;
+ export(): IReport;
+}
diff --git a/packages/runtime/interface/src/runtime/snapshot/index.ts b/packages/runtime/interface/src/runtime/snapshot/index.ts
new file mode 100644
index 00000000..11970c14
--- /dev/null
+++ b/packages/runtime/interface/src/runtime/snapshot/index.ts
@@ -0,0 +1,2 @@
+export type { ISnapshot, Snapshot, SnapshotData } from './snapshot';
+export type { ISnapshotCenter } from './snapshot-center';
diff --git a/packages/runtime/interface/src/runtime/snapshot/snapshot-center.ts b/packages/runtime/interface/src/runtime/snapshot/snapshot-center.ts
new file mode 100644
index 00000000..b98fe540
--- /dev/null
+++ b/packages/runtime/interface/src/runtime/snapshot/snapshot-center.ts
@@ -0,0 +1,10 @@
+import { ISnapshot, Snapshot, SnapshotData } from './snapshot';
+
+export interface ISnapshotCenter {
+ id: string;
+ create(snapshot: Partial): ISnapshot;
+ exportAll(): Snapshot[];
+ export(): Record;
+ init(): void;
+ dispose(): void;
+}
diff --git a/packages/runtime/interface/src/runtime/snapshot/snapshot.ts b/packages/runtime/interface/src/runtime/snapshot/snapshot.ts
new file mode 100644
index 00000000..8392ddcc
--- /dev/null
+++ b/packages/runtime/interface/src/runtime/snapshot/snapshot.ts
@@ -0,0 +1,21 @@
+import { WorkflowInputs, WorkflowOutputs } from '../base';
+
+export interface SnapshotData {
+ nodeID: string;
+ inputs: WorkflowInputs;
+ outputs: WorkflowOutputs;
+ data: any;
+ branch?: string;
+}
+
+export interface Snapshot extends SnapshotData {
+ id: string;
+}
+
+export interface ISnapshot {
+ id: string;
+ data: Partial;
+ addData(data: Partial): void;
+ validate(): boolean;
+ export(): Snapshot;
+}
diff --git a/packages/runtime/interface/src/runtime/state/index.ts b/packages/runtime/interface/src/runtime/state/index.ts
new file mode 100644
index 00000000..be177b3e
--- /dev/null
+++ b/packages/runtime/interface/src/runtime/state/index.ts
@@ -0,0 +1,20 @@
+import { IFlowConstantRefValue, IFlowRefValue, WorkflowVariableType } from '@schema/index';
+import { IVariableParseResult, IVariableStore } from '../variable';
+import { INode } from '../document';
+import { WorkflowInputs, WorkflowOutputs } from '../base';
+
+export interface IState {
+ id: string;
+ variableStore: IVariableStore;
+ init(): void;
+ dispose(): void;
+ getNodeInputs(node: INode): WorkflowInputs;
+ setNodeOutputs(params: { node: INode; outputs: WorkflowOutputs }): void;
+ parseRef(ref: IFlowRefValue): IVariableParseResult | null;
+ parseValue(
+ flowValue: IFlowConstantRefValue,
+ type?: WorkflowVariableType
+ ): IVariableParseResult | null;
+ isExecutedNode(node: INode): boolean;
+ addExecutedNode(node: INode): void;
+}
diff --git a/packages/runtime/interface/src/runtime/status/index.ts b/packages/runtime/interface/src/runtime/status/index.ts
new file mode 100644
index 00000000..c79031d5
--- /dev/null
+++ b/packages/runtime/interface/src/runtime/status/index.ts
@@ -0,0 +1,33 @@
+export enum WorkflowStatus {
+ Pending = 'pending',
+ Processing = 'processing',
+ Succeeded = 'succeeded',
+ Failed = 'failed',
+ Canceled = 'canceled',
+}
+
+export interface StatusData {
+ status: WorkflowStatus;
+ terminated: boolean;
+ startTime: number;
+ endTime?: number;
+ timeCost: number;
+}
+
+export interface IStatus extends StatusData {
+ id: string;
+ process(): void;
+ success(): void;
+ fail(): void;
+ cancel(): void;
+ export(): StatusData;
+}
+
+export interface IStatusCenter {
+ workflow: IStatus;
+ nodeStatus(nodeID: string): IStatus;
+ init(): void;
+ dispose(): void;
+ getStatusNodeIDs(status: WorkflowStatus): string[];
+ exportNodeStatus(): Record;
+}
diff --git a/packages/runtime/interface/src/runtime/task/index.ts b/packages/runtime/interface/src/runtime/task/index.ts
new file mode 100644
index 00000000..2edaefef
--- /dev/null
+++ b/packages/runtime/interface/src/runtime/task/index.ts
@@ -0,0 +1,14 @@
+import { IContext } from '../context';
+import { WorkflowOutputs } from '../base';
+
+export interface ITask {
+ id: string;
+ processing: Promise;
+ context: IContext;
+ cancel(): void;
+}
+
+export interface TaskParams {
+ processing: Promise;
+ context: IContext;
+}
diff --git a/packages/runtime/interface/src/runtime/validation/index.ts b/packages/runtime/interface/src/runtime/validation/index.ts
new file mode 100644
index 00000000..e8c5ca42
--- /dev/null
+++ b/packages/runtime/interface/src/runtime/validation/index.ts
@@ -0,0 +1,12 @@
+import { WorkflowSchema } from '@schema/index';
+
+export interface ValidationResult {
+ valid: boolean;
+ errors?: string[];
+}
+
+export interface IValidation {
+ validate(schema: WorkflowSchema): ValidationResult;
+}
+
+export const IValidation = Symbol.for('Validation');
diff --git a/packages/runtime/interface/src/runtime/variable/index.ts b/packages/runtime/interface/src/runtime/variable/index.ts
new file mode 100644
index 00000000..2d4b821a
--- /dev/null
+++ b/packages/runtime/interface/src/runtime/variable/index.ts
@@ -0,0 +1,44 @@
+import { WorkflowVariableType } from '@schema/index';
+
+interface VariableTypeInfo {
+ type: WorkflowVariableType;
+ itemsType?: WorkflowVariableType;
+}
+
+export interface IVariable extends VariableTypeInfo {
+ id: string;
+ nodeID: string;
+ key: string;
+ value: T;
+}
+
+export interface IVariableParseResult extends VariableTypeInfo {
+ value: T;
+ type: WorkflowVariableType;
+}
+
+export interface IVariableStore {
+ id: string;
+ store: Map>;
+ setParent(parent: IVariableStore): void;
+ setVariable(
+ params: {
+ nodeID: string;
+ key: string;
+ value: Object;
+ } & VariableTypeInfo
+ ): void;
+ setValue(params: {
+ nodeID: string;
+ variableKey: string;
+ variablePath?: string[];
+ value: Object;
+ }): void;
+ getValue(params: {
+ nodeID: string;
+ variableKey: string;
+ variablePath?: string[];
+ }): IVariableParseResult | null;
+ init(): void;
+ dispose(): void;
+}
diff --git a/packages/runtime/interface/src/schema/constant.ts b/packages/runtime/interface/src/schema/constant.ts
new file mode 100644
index 00000000..329e9ab8
--- /dev/null
+++ b/packages/runtime/interface/src/schema/constant.ts
@@ -0,0 +1,14 @@
+export enum WorkflowPortType {
+ Input = 'input',
+ Output = 'output',
+}
+
+export enum WorkflowVariableType {
+ String = 'string',
+ Integer = 'integer',
+ Number = 'number',
+ Boolean = 'boolean',
+ Object = 'object',
+ Array = 'array',
+ Null = 'null',
+}
diff --git a/packages/runtime/interface/src/schema/edge.ts b/packages/runtime/interface/src/schema/edge.ts
new file mode 100644
index 00000000..0fae12a3
--- /dev/null
+++ b/packages/runtime/interface/src/schema/edge.ts
@@ -0,0 +1,6 @@
+export interface WorkflowEdgeSchema {
+ sourceNodeID: string;
+ targetNodeID: string;
+ sourcePortID?: string;
+ targetPortID?: string;
+}
diff --git a/packages/runtime/interface/src/schema/index.ts b/packages/runtime/interface/src/schema/index.ts
new file mode 100644
index 00000000..5db4878a
--- /dev/null
+++ b/packages/runtime/interface/src/schema/index.ts
@@ -0,0 +1,8 @@
+export { WorkflowEdgeSchema } from './edge';
+export { JsonSchemaBasicType, IJsonSchema, IBasicJsonSchema } from './json-schema';
+export { WorkflowNodeMetaSchema } from './node-meta';
+export { WorkflowNodeSchema } from './node';
+export { WorkflowSchema } from './workflow';
+export { XYSchema, PositionSchema } from './xy';
+export { WorkflowPortType, WorkflowVariableType } from './constant';
+export { IFlowConstantRefValue, IFlowConstantValue, IFlowRefValue } from './value';
diff --git a/packages/runtime/interface/src/schema/json-schema.ts b/packages/runtime/interface/src/schema/json-schema.ts
new file mode 100644
index 00000000..7626535e
--- /dev/null
+++ b/packages/runtime/interface/src/schema/json-schema.ts
@@ -0,0 +1,33 @@
+// TODO copy packages/materials/form-materials/src/typings/json-schema/index.ts
+
+export type JsonSchemaBasicType =
+ | 'boolean'
+ | 'string'
+ | 'integer'
+ | 'number'
+ | 'object'
+ | 'array'
+ | 'map';
+
+export interface IJsonSchema {
+ type: T;
+ default?: any;
+ title?: string;
+ description?: string;
+ enum?: (string | number)[];
+ properties?: Record>;
+ additionalProperties?: IJsonSchema;
+ items?: IJsonSchema;
+ required?: string[];
+ $ref?: string;
+ extra?: {
+ index?: number;
+ // Used in BaseType.isEqualWithJSONSchema, the type comparison will be weak
+ weak?: boolean;
+ // Set the render component
+ formComponent?: string;
+ [key: string]: any;
+ };
+}
+
+export type IBasicJsonSchema = IJsonSchema;
diff --git a/packages/runtime/interface/src/schema/node-meta.ts b/packages/runtime/interface/src/schema/node-meta.ts
new file mode 100644
index 00000000..67241104
--- /dev/null
+++ b/packages/runtime/interface/src/schema/node-meta.ts
@@ -0,0 +1,6 @@
+import { PositionSchema } from './xy';
+
+export interface WorkflowNodeMetaSchema {
+ position: PositionSchema;
+ canvasPosition?: PositionSchema;
+}
diff --git a/packages/runtime/interface/src/schema/node.ts b/packages/runtime/interface/src/schema/node.ts
new file mode 100644
index 00000000..e17ba8f0
--- /dev/null
+++ b/packages/runtime/interface/src/schema/node.ts
@@ -0,0 +1,19 @@
+import type { IFlowConstantRefValue } from './value';
+import type { WorkflowNodeMetaSchema } from './node-meta';
+import { IJsonSchema } from './json-schema';
+import type { WorkflowEdgeSchema } from './edge';
+
+export interface WorkflowNodeSchema {
+ id: string;
+ type: T;
+ meta: WorkflowNodeMetaSchema;
+ data: D & {
+ title?: string;
+ inputsValues?: Record;
+ inputs?: IJsonSchema;
+ outputs?: IJsonSchema;
+ [key: string]: any;
+ };
+ blocks?: WorkflowNodeSchema[];
+ edges?: WorkflowEdgeSchema[];
+}
diff --git a/packages/runtime/interface/src/schema/value.ts b/packages/runtime/interface/src/schema/value.ts
new file mode 100644
index 00000000..406eb60a
--- /dev/null
+++ b/packages/runtime/interface/src/schema/value.ts
@@ -0,0 +1,29 @@
+// TODO copy packages/materials/form-materials/src/typings/flow-value/index.ts
+
+export interface IFlowConstantValue {
+ type: 'constant';
+ content?: string | number | boolean;
+}
+
+export interface IFlowRefValue {
+ type: 'ref';
+ content?: string[];
+}
+
+export interface IFlowExpressionValue {
+ type: 'expression';
+ content?: string;
+}
+
+export interface IFlowTemplateValue {
+ type: 'template';
+ content?: string;
+}
+
+export type IFlowValue =
+ | IFlowConstantValue
+ | IFlowRefValue
+ | IFlowExpressionValue
+ | IFlowTemplateValue;
+
+export type IFlowConstantRefValue = IFlowConstantValue | IFlowRefValue;
diff --git a/packages/runtime/interface/src/schema/workflow.ts b/packages/runtime/interface/src/schema/workflow.ts
new file mode 100644
index 00000000..91846c9f
--- /dev/null
+++ b/packages/runtime/interface/src/schema/workflow.ts
@@ -0,0 +1,7 @@
+import type { WorkflowNodeSchema } from './node';
+import type { WorkflowEdgeSchema } from './edge';
+
+export interface WorkflowSchema {
+ nodes: WorkflowNodeSchema[];
+ edges: WorkflowEdgeSchema[];
+}
diff --git a/packages/runtime/interface/src/schema/xy.ts b/packages/runtime/interface/src/schema/xy.ts
new file mode 100644
index 00000000..7cbe86b1
--- /dev/null
+++ b/packages/runtime/interface/src/schema/xy.ts
@@ -0,0 +1,6 @@
+export interface XYSchema {
+ x: number;
+ y: number;
+}
+
+export type PositionSchema = XYSchema;
diff --git a/packages/runtime/interface/tsconfig.json b/packages/runtime/interface/tsconfig.json
new file mode 100644
index 00000000..d97bca09
--- /dev/null
+++ b/packages/runtime/interface/tsconfig.json
@@ -0,0 +1,29 @@
+{
+ "extends": "@flowgram.ai/ts-config/tsconfig.flow.path.json",
+ "compilerOptions": {
+ "baseUrl": "src",
+ "paths": {
+ "@api/*": [
+ "api/*"
+ ],
+ "@node/*": [
+ "node/*"
+ ],
+ "@runtime/*": [
+ "runtime/*"
+ ],
+ "@schema/*": [
+ "schema/*"
+ ],
+ "@client/*": [
+ "client/*"
+ ]
+ }
+ },
+ "include": [
+ "./src"
+ ],
+ "exclude": [
+ "node_modules"
+ ]
+}
diff --git a/packages/runtime/js-core/.eslintrc.cjs b/packages/runtime/js-core/.eslintrc.cjs
new file mode 100644
index 00000000..b1ea5bf8
--- /dev/null
+++ b/packages/runtime/js-core/.eslintrc.cjs
@@ -0,0 +1,6 @@
+const { defineConfig } = require('@flowgram.ai/eslint-config');
+
+module.exports = defineConfig({
+ preset: 'base',
+ packageRoot: __dirname,
+});
diff --git a/packages/runtime/js-core/package.json b/packages/runtime/js-core/package.json
new file mode 100644
index 00000000..db1f2cbf
--- /dev/null
+++ b/packages/runtime/js-core/package.json
@@ -0,0 +1,53 @@
+{
+ "name": "@flowgram.ai/runtime-js",
+ "version": "0.1.8",
+ "homepage": "https://flowgram.ai/",
+ "repository": "https://github.com/bytedance/flowgram.ai",
+ "license": "MIT",
+ "exports": {
+ "types": "./dist/index.d.ts",
+ "import": "./dist/esm/index.js",
+ "require": "./dist/index.js"
+ },
+ "main": "./dist/index.js",
+ "module": "./dist/esm/index.js",
+ "types": "./dist/index.d.ts",
+ "files": [
+ "dist"
+ ],
+ "scripts": {
+ "dev": "npm run watch",
+ "build": "npm run build:fast -- --dts-resolve",
+ "build:fast": "tsup src/index.ts --format cjs,esm --sourcemap --legacy-output",
+ "build:watch": "npm run build:fast -- --dts-resolve",
+ "clean": "rimraf dist",
+ "test": "vitest run",
+ "test:cov": "vitest run --coverage",
+ "ts-check": "tsc --noEmit",
+ "watch": "npm run build:fast -- --dts-resolve --watch --ignore-watch dist"
+ },
+ "dependencies": {
+ "@langchain/openai": "^0.5.11",
+ "@langchain/core": "^0.3.57",
+ "lodash-es": "^4.17.21",
+ "uuid": "^9.0.0",
+ "zod": "^3.24.4"
+ },
+ "devDependencies": {
+ "@flowgram.ai/runtime-interface": "workspace:*",
+ "@flowgram.ai/eslint-config": "workspace:*",
+ "@flowgram.ai/ts-config": "workspace:*",
+ "@types/lodash-es": "^4.17.12",
+ "@types/uuid": "^9.0.1",
+ "@vitest/coverage-v8": "^0.32.0",
+ "eslint": "^8.54.0",
+ "dotenv": "~16.5.0",
+ "tsup": "^8.0.1",
+ "typescript": "^5.0.4",
+ "vitest": "^0.34.6"
+ },
+ "publishConfig": {
+ "access": "public",
+ "registry": "https://registry.npmjs.org/"
+ }
+}
diff --git a/packages/runtime/js-core/src/api/index.ts b/packages/runtime/js-core/src/api/index.ts
new file mode 100644
index 00000000..92551ddb
--- /dev/null
+++ b/packages/runtime/js-core/src/api/index.ts
@@ -0,0 +1,17 @@
+import { FlowGramAPIName } from '@flowgram.ai/runtime-interface';
+
+import { TaskRunAPI } from './task-run';
+import { TaskResultAPI } from './task-result';
+import { TaskReportAPI } from './task-report';
+import { TaskCancelAPI } from './task-cancel';
+
+export { TaskRunAPI, TaskResultAPI, TaskReportAPI, TaskCancelAPI };
+
+export const WorkflowRuntimeAPIs: Record any> = {
+ [FlowGramAPIName.TaskRun]: TaskRunAPI,
+ [FlowGramAPIName.TaskReport]: TaskReportAPI,
+ [FlowGramAPIName.TaskResult]: TaskResultAPI,
+ [FlowGramAPIName.TaskCancel]: TaskCancelAPI,
+ [FlowGramAPIName.ServerInfo]: () => {}, // TODO
+ [FlowGramAPIName.Validation]: () => {}, // TODO
+};
diff --git a/packages/runtime/js-core/src/api/task-cancel.ts b/packages/runtime/js-core/src/api/task-cancel.ts
new file mode 100644
index 00000000..48fb31cc
--- /dev/null
+++ b/packages/runtime/js-core/src/api/task-cancel.ts
@@ -0,0 +1,13 @@
+import { TaskCancelInput, TaskCancelOutput } from '@flowgram.ai/runtime-interface';
+
+import { WorkflowApplication } from '@application/workflow';
+
+export const TaskCancelAPI = async (input: TaskCancelInput): Promise => {
+ const app = WorkflowApplication.instance;
+ const { taskID } = input;
+ const success = app.cancel(taskID);
+ const output: TaskCancelOutput = {
+ success,
+ };
+ return output;
+};
diff --git a/packages/runtime/js-core/src/api/task-report.ts b/packages/runtime/js-core/src/api/task-report.ts
new file mode 100644
index 00000000..ddc2477b
--- /dev/null
+++ b/packages/runtime/js-core/src/api/task-report.ts
@@ -0,0 +1,21 @@
+/* eslint-disable no-console */
+import {
+ TaskReportInput,
+ TaskReportOutput,
+ TaskReportDefine,
+} from '@flowgram.ai/runtime-interface';
+
+import { WorkflowApplication } from '@application/workflow';
+
+export const TaskReportAPI = async (input: TaskReportInput): Promise => {
+ const app = WorkflowApplication.instance;
+ const { taskID } = input;
+ const output: TaskReportOutput = app.report(taskID);
+ try {
+ TaskReportDefine.schema.output.parse(output);
+ } catch (e) {
+ console.log('> TaskReportAPI - output: ', JSON.stringify(output));
+ console.error(e);
+ }
+ return output;
+};
diff --git a/packages/runtime/js-core/src/api/task-result.ts b/packages/runtime/js-core/src/api/task-result.ts
new file mode 100644
index 00000000..250b9d83
--- /dev/null
+++ b/packages/runtime/js-core/src/api/task-result.ts
@@ -0,0 +1,10 @@
+import { TaskResultInput, TaskResultOutput } from '@flowgram.ai/runtime-interface';
+
+import { WorkflowApplication } from '@application/workflow';
+
+export const TaskResultAPI = async (input: TaskResultInput): Promise => {
+ const app = WorkflowApplication.instance;
+ const { taskID } = input;
+ const output: TaskResultOutput = app.result(taskID);
+ return output;
+};
diff --git a/packages/runtime/js-core/src/api/task-run.ts b/packages/runtime/js-core/src/api/task-run.ts
new file mode 100644
index 00000000..6f38661c
--- /dev/null
+++ b/packages/runtime/js-core/src/api/task-run.ts
@@ -0,0 +1,17 @@
+import { TaskRunInput, TaskRunOutput } from '@flowgram.ai/runtime-interface';
+
+import { WorkflowApplication } from '@application/workflow';
+
+export const TaskRunAPI = async (input: TaskRunInput): Promise => {
+ const app = WorkflowApplication.instance;
+ const { schema: stringSchema, inputs } = input;
+ const schema = JSON.parse(stringSchema);
+ const taskID = app.run({
+ schema,
+ inputs,
+ });
+ const output: TaskRunOutput = {
+ taskID,
+ };
+ return output;
+};
diff --git a/packages/runtime/js-core/src/application/index.ts b/packages/runtime/js-core/src/application/index.ts
new file mode 100644
index 00000000..4c54593e
--- /dev/null
+++ b/packages/runtime/js-core/src/application/index.ts
@@ -0,0 +1 @@
+export { WorkflowApplication } from './workflow';
diff --git a/packages/runtime/js-core/src/application/workflow.ts b/packages/runtime/js-core/src/application/workflow.ts
new file mode 100644
index 00000000..852bb58c
--- /dev/null
+++ b/packages/runtime/js-core/src/application/workflow.ts
@@ -0,0 +1,76 @@
+/* eslint-disable no-console */
+import {
+ InvokeParams,
+ IContainer,
+ IEngine,
+ ITask,
+ IReport,
+ WorkflowOutputs,
+} from '@flowgram.ai/runtime-interface';
+
+import { WorkflowRuntimeContainer } from '@workflow/container';
+
+export class WorkflowApplication {
+ private container: IContainer;
+
+ public tasks: Map;
+
+ constructor() {
+ this.container = WorkflowRuntimeContainer.instance;
+ this.tasks = new Map();
+ }
+
+ public run(params: InvokeParams): string {
+ const engine = this.container.get(IEngine);
+ const task = engine.invoke(params);
+ this.tasks.set(task.id, task);
+ console.log('> POST TaskRun - taskID: ', task.id);
+ console.log(params.inputs);
+ task.processing.then((output) => {
+ console.log('> LOG Task finished: ', task.id);
+ console.log(output);
+ });
+ return task.id;
+ }
+
+ public cancel(taskID: string): boolean {
+ console.log('> PUT TaskCancel - taskID: ', taskID);
+ const task = this.tasks.get(taskID);
+ if (!task) {
+ return false;
+ }
+ task.cancel();
+ return true;
+ }
+
+ public report(taskID: string): IReport | undefined {
+ const task = this.tasks.get(taskID);
+ console.log('> GET TaskReport - taskID: ', taskID);
+ if (!task) {
+ return;
+ }
+ return task.context.reporter.export();
+ }
+
+ public result(taskID: string): WorkflowOutputs | undefined {
+ console.log('> GET TaskResult - taskID: ', taskID);
+ const task = this.tasks.get(taskID);
+ if (!task) {
+ return;
+ }
+ if (!task.context.statusCenter.workflow.terminated) {
+ return;
+ }
+ return task.context.ioCenter.outputs;
+ }
+
+ private static _instance: WorkflowApplication;
+
+ public static get instance(): WorkflowApplication {
+ if (this._instance) {
+ return this._instance;
+ }
+ this._instance = new WorkflowApplication();
+ return this._instance;
+ }
+}
diff --git a/packages/runtime/js-core/src/domain/__tests__/config.ts b/packages/runtime/js-core/src/domain/__tests__/config.ts
new file mode 100644
index 00000000..c15cd66b
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/__tests__/config.ts
@@ -0,0 +1 @@
+export const ENABLE_REAL_LLM = false;
diff --git a/packages/runtime/js-core/src/domain/__tests__/executor/index.ts b/packages/runtime/js-core/src/domain/__tests__/executor/index.ts
new file mode 100644
index 00000000..32d19fbc
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/__tests__/executor/index.ts
@@ -0,0 +1,13 @@
+import { INodeExecutorFactory } from '@flowgram.ai/runtime-interface';
+
+import { StartExecutor } from '@nodes/start';
+import { EndExecutor } from '@nodes/end';
+import { ConditionExecutor } from '@nodes/condition';
+import { MockLLMExecutor } from './llm';
+
+export const MockWorkflowRuntimeNodeExecutors: INodeExecutorFactory[] = [
+ StartExecutor,
+ EndExecutor,
+ MockLLMExecutor,
+ ConditionExecutor,
+];
diff --git a/packages/runtime/js-core/src/domain/__tests__/executor/llm.ts b/packages/runtime/js-core/src/domain/__tests__/executor/llm.ts
new file mode 100644
index 00000000..a47f7772
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/__tests__/executor/llm.ts
@@ -0,0 +1,18 @@
+import { ExecutionContext, ExecutionResult } from '@flowgram.ai/runtime-interface';
+
+import { LLMExecutor, LLMExecutorInputs } from '@nodes/llm';
+import { delay } from '@infra/utils';
+
+export class MockLLMExecutor extends LLMExecutor {
+ public async execute(context: ExecutionContext): Promise {
+ const inputs = context.inputs as LLMExecutorInputs;
+ this.checkInputs(inputs);
+ await delay(100); // TODO mock node run
+ const result = `Hi, I'm an AI assistant, my name is ${inputs.modelName}, temperature is ${inputs.temperature}, system prompt is "${inputs.systemPrompt}", prompt is "${inputs.prompt}"`;
+ return {
+ outputs: {
+ result,
+ },
+ };
+ }
+}
diff --git a/packages/runtime/js-core/src/domain/__tests__/schemas/basic-llm.test.ts b/packages/runtime/js-core/src/domain/__tests__/schemas/basic-llm.test.ts
new file mode 100644
index 00000000..b552e323
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/__tests__/schemas/basic-llm.test.ts
@@ -0,0 +1,78 @@
+import { beforeEach, describe, expect, it } from 'vitest';
+import { IContainer, IEngine, IExecutor, WorkflowStatus } from '@flowgram.ai/runtime-interface';
+
+import { LLMExecutor } from '@nodes/llm';
+import { snapshotsToVOData } from '../utils';
+import { WorkflowRuntimeContainer } from '../../container';
+import { TestSchemas } from '.';
+
+let container: IContainer;
+
+beforeEach(() => {
+ container = WorkflowRuntimeContainer.instance;
+ const executor = container.get(IExecutor);
+ executor.register(new LLMExecutor());
+});
+
+describe('workflow runtime basic test', () => {
+ it('should execute workflow', async () => {
+ if (process.env.ENABLE_MODEL_TEST !== 'true') {
+ return;
+ }
+ if (!process.env.MODEL_NAME || !process.env.API_KEY || !process.env.API_HOST) {
+ throw new Error('Missing environment variables');
+ }
+ const engine = container.get(IEngine);
+ const modelName = process.env.MODEL_NAME;
+ const apiKey = process.env.API_KEY;
+ const apiHost = process.env.API_HOST;
+ const { context, processing } = engine.invoke({
+ schema: TestSchemas.basicLLMSchema,
+ inputs: {
+ model_name: modelName,
+ api_key: apiKey,
+ api_host: apiHost,
+ prompt: 'Just give me the answer of "1+1=?", just one number, no other words',
+ },
+ });
+ expect(context.statusCenter.workflow.status).toBe(WorkflowStatus.Processing);
+ const result = await processing;
+ expect(context.statusCenter.workflow.status).toBe(WorkflowStatus.Succeeded);
+ expect(result).toStrictEqual({
+ answer: '2',
+ });
+ const snapshots = snapshotsToVOData(context.snapshotCenter.exportAll());
+ expect(snapshots).toStrictEqual([
+ {
+ nodeID: 'start_0',
+ inputs: {},
+ outputs: {
+ model_name: modelName,
+ api_key: apiKey,
+ api_host: apiHost,
+ prompt: 'Just give me the answer of "1+1=?", just one number, no other words',
+ },
+ data: {},
+ },
+ {
+ nodeID: 'llm_0',
+ inputs: {
+ modelName: modelName,
+ apiKey: apiKey,
+ apiHost: apiHost,
+ temperature: 0,
+ prompt: 'Just give me the answer of "1+1=?", just one number, no other words',
+ systemPrompt: 'You are a helpful AI assistant.',
+ },
+ outputs: { result: '2' },
+ data: {},
+ },
+ {
+ nodeID: 'end_0',
+ inputs: { answer: '2' },
+ outputs: { answer: '2' },
+ data: {},
+ },
+ ]);
+ });
+});
diff --git a/packages/runtime/js-core/src/domain/__tests__/schemas/basic-llm.ts b/packages/runtime/js-core/src/domain/__tests__/schemas/basic-llm.ts
new file mode 100644
index 00000000..876a7ec9
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/__tests__/schemas/basic-llm.ts
@@ -0,0 +1,169 @@
+import type { WorkflowSchema } from '@flowgram.ai/runtime-interface';
+
+export const basicLLMSchema: WorkflowSchema = {
+ nodes: [
+ {
+ id: 'start_0',
+ type: 'start',
+ meta: {
+ position: {
+ x: 0,
+ y: 0,
+ },
+ },
+ data: {
+ title: 'Start',
+ outputs: {
+ type: 'object',
+ properties: {
+ model_name: {
+ key: 14,
+ name: 'model_name',
+ type: 'string',
+ extra: {
+ index: 1,
+ },
+ isPropertyRequired: true,
+ },
+ prompt: {
+ key: 5,
+ name: 'prompt',
+ type: 'string',
+ extra: {
+ index: 3,
+ },
+ isPropertyRequired: true,
+ },
+ api_key: {
+ key: 19,
+ name: 'api_key',
+ type: 'string',
+ extra: {
+ index: 4,
+ },
+ isPropertyRequired: true,
+ },
+ api_host: {
+ key: 20,
+ name: 'api_host',
+ type: 'string',
+ extra: {
+ index: 5,
+ },
+ isPropertyRequired: true,
+ },
+ },
+ required: ['model_name', 'prompt', 'api_key', 'api_host'],
+ },
+ },
+ },
+ {
+ id: 'end_0',
+ type: 'end',
+ meta: {
+ position: {
+ x: 1000,
+ y: 0,
+ },
+ },
+ data: {
+ title: 'End',
+ inputsValues: {
+ answer: {
+ type: 'ref',
+ content: ['llm_0', 'result'],
+ },
+ },
+ inputs: {
+ type: 'object',
+ properties: {
+ answer: {
+ type: 'string',
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'llm_0',
+ type: 'llm',
+ meta: {
+ position: {
+ x: 500,
+ y: 0,
+ },
+ },
+ data: {
+ title: 'LLM_0',
+ inputsValues: {
+ modelName: {
+ type: 'ref',
+ content: ['start_0', 'model_name'],
+ },
+ apiKey: {
+ type: 'ref',
+ content: ['start_0', 'api_key'],
+ },
+ apiHost: {
+ type: 'ref',
+ content: ['start_0', 'api_host'],
+ },
+ temperature: {
+ type: 'constant',
+ content: 0,
+ },
+ prompt: {
+ type: 'ref',
+ content: ['start_0', 'prompt'],
+ },
+ systemPrompt: {
+ type: 'constant',
+ content: 'You are a helpful AI assistant.',
+ },
+ },
+ inputs: {
+ type: 'object',
+ required: ['modelName', 'temperature', 'prompt'],
+ properties: {
+ modelName: {
+ type: 'string',
+ },
+ apiKey: {
+ type: 'string',
+ },
+ apiHost: {
+ type: 'string',
+ },
+ temperature: {
+ type: 'number',
+ },
+ systemPrompt: {
+ type: 'string',
+ },
+ prompt: {
+ type: 'string',
+ },
+ },
+ },
+ outputs: {
+ type: 'object',
+ properties: {
+ result: {
+ type: 'string',
+ },
+ },
+ },
+ },
+ },
+ ],
+ edges: [
+ {
+ sourceNodeID: 'start_0',
+ targetNodeID: 'llm_0',
+ },
+ {
+ sourceNodeID: 'llm_0',
+ targetNodeID: 'end_0',
+ },
+ ],
+};
diff --git a/packages/runtime/js-core/src/domain/__tests__/schemas/basic.test.ts b/packages/runtime/js-core/src/domain/__tests__/schemas/basic.test.ts
new file mode 100644
index 00000000..69b1b894
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/__tests__/schemas/basic.test.ts
@@ -0,0 +1,79 @@
+import { describe, expect, it } from 'vitest';
+import { IContainer, IEngine, WorkflowStatus } from '@flowgram.ai/runtime-interface';
+
+import { snapshotsToVOData } from '../utils';
+import { WorkflowRuntimeContainer } from '../../container';
+import { TestSchemas } from '.';
+
+const container: IContainer = WorkflowRuntimeContainer.instance;
+
+describe('WorkflowRuntime basic schema', () => {
+ it('should execute a workflow with input', async () => {
+ const engine = container.get(IEngine);
+ const { context, processing } = engine.invoke({
+ schema: TestSchemas.basicSchema,
+ inputs: {
+ model_name: 'ai-model',
+ llm_settings: {
+ temperature: 0.5,
+ },
+ prompt: 'How are you?',
+ },
+ });
+ expect(context.statusCenter.workflow.status).toBe(WorkflowStatus.Processing);
+ const result = await processing;
+ expect(context.statusCenter.workflow.status).toBe(WorkflowStatus.Succeeded);
+ expect(result).toStrictEqual({
+ llm_res: `Hi, I'm an AI assistant, my name is ai-model, temperature is 0.5, system prompt is "You are a helpful AI assistant.", prompt is "How are you?"`,
+ llm_prompt: 'How are you?',
+ });
+ const snapshots = snapshotsToVOData(context.snapshotCenter.exportAll());
+ expect(snapshots).toStrictEqual([
+ {
+ nodeID: 'start_0',
+ inputs: {},
+ outputs: {
+ model_name: 'ai-model',
+ llm_settings: { temperature: 0.5 },
+ prompt: 'How are you?',
+ },
+ data: {},
+ },
+ {
+ nodeID: 'llm_0',
+ inputs: {
+ modelName: 'ai-model',
+ apiKey: 'sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ apiHost: 'https://mock-ai-url/api/v3',
+ temperature: 0.5,
+ prompt: 'How are you?',
+ systemPrompt: 'You are a helpful AI assistant.',
+ },
+ outputs: {
+ result:
+ 'Hi, I\'m an AI assistant, my name is ai-model, temperature is 0.5, system prompt is "You are a helpful AI assistant.", prompt is "How are you?"',
+ },
+ data: {},
+ },
+ {
+ nodeID: 'end_0',
+ inputs: {
+ llm_res:
+ 'Hi, I\'m an AI assistant, my name is ai-model, temperature is 0.5, system prompt is "You are a helpful AI assistant.", prompt is "How are you?"',
+ llm_prompt: 'How are you?',
+ },
+ outputs: {
+ llm_res:
+ 'Hi, I\'m an AI assistant, my name is ai-model, temperature is 0.5, system prompt is "You are a helpful AI assistant.", prompt is "How are you?"',
+ llm_prompt: 'How are you?',
+ },
+ data: {},
+ },
+ ]);
+ const report = context.reporter.export();
+ expect(report.workflowStatus.status).toBe(WorkflowStatus.Succeeded);
+ expect(report.reports.start_0.status).toBe(WorkflowStatus.Succeeded);
+ expect(report.reports.llm_0.status).toBe(WorkflowStatus.Succeeded);
+ expect(report.reports.end_0.status).toBe(WorkflowStatus.Succeeded);
+ });
+});
diff --git a/packages/runtime/js-core/src/domain/__tests__/schemas/basic.ts b/packages/runtime/js-core/src/domain/__tests__/schemas/basic.ts
new file mode 100644
index 00000000..a79b596d
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/__tests__/schemas/basic.ts
@@ -0,0 +1,177 @@
+import type { WorkflowSchema } from '@flowgram.ai/runtime-interface';
+
+export const basicSchema: WorkflowSchema = {
+ nodes: [
+ {
+ id: 'start_0',
+ type: 'start',
+ meta: {
+ position: {
+ x: 180,
+ y: 69,
+ },
+ },
+ data: {
+ title: 'Start',
+ outputs: {
+ type: 'object',
+ properties: {
+ model_name: {
+ key: 14,
+ name: 'model_name',
+ type: 'string',
+ extra: {
+ index: 1,
+ },
+ isPropertyRequired: true,
+ },
+ llm_settings: {
+ key: 17,
+ name: 'llm_settings',
+ type: 'object',
+ extra: {
+ index: 2,
+ },
+ properties: {
+ temperature: {
+ key: 18,
+ name: 'temperature',
+ type: 'number',
+ extra: {
+ index: 1,
+ },
+ },
+ },
+ required: [],
+ },
+ prompt: {
+ key: 19,
+ name: 'prompt',
+ type: 'string',
+ extra: {
+ index: 3,
+ },
+ isPropertyRequired: true,
+ },
+ },
+ required: ['model_name', 'prompt'],
+ },
+ },
+ },
+ {
+ id: 'end_0',
+ type: 'end',
+ meta: {
+ position: {
+ x: 1121.3,
+ y: 69,
+ },
+ },
+ data: {
+ title: 'End',
+ inputsValues: {
+ llm_res: {
+ type: 'ref',
+ content: ['llm_0', 'result'],
+ },
+ llm_prompt: {
+ type: 'ref',
+ content: ['start_0', 'prompt'],
+ },
+ },
+ inputs: {
+ type: 'object',
+ properties: {
+ llm_res: {
+ type: 'string',
+ },
+ llm_prompt: {
+ type: 'string',
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'llm_0',
+ type: 'llm',
+ meta: {
+ position: {
+ x: 650.65,
+ y: 0,
+ },
+ },
+ data: {
+ title: 'LLM_1',
+ inputsValues: {
+ modelName: {
+ type: 'ref',
+ content: ['start_0', 'model_name'],
+ },
+ apiKey: {
+ type: 'constant',
+ content: 'sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ },
+ apiHost: {
+ type: 'constant',
+ content: 'https://mock-ai-url/api/v3',
+ },
+ temperature: {
+ type: 'ref',
+ content: ['start_0', 'llm_settings', 'temperature'],
+ },
+ prompt: {
+ type: 'ref',
+ content: ['start_0', 'prompt'],
+ },
+ systemPrompt: {
+ type: 'constant',
+ content: 'You are a helpful AI assistant.',
+ },
+ },
+ inputs: {
+ type: 'object',
+ required: ['modelName', 'temperature', 'prompt'],
+ properties: {
+ modelName: {
+ type: 'string',
+ },
+ apiKey: {
+ type: 'string',
+ },
+ apiHost: {
+ type: 'string',
+ },
+ temperature: {
+ type: 'number',
+ },
+ systemPrompt: {
+ type: 'string',
+ },
+ prompt: {
+ type: 'string',
+ },
+ },
+ },
+ outputs: {
+ type: 'object',
+ properties: {
+ result: {
+ type: 'string',
+ },
+ },
+ },
+ },
+ },
+ ],
+ edges: [
+ {
+ sourceNodeID: 'start_0',
+ targetNodeID: 'llm_0',
+ },
+ {
+ sourceNodeID: 'llm_0',
+ targetNodeID: 'end_0',
+ },
+ ],
+};
diff --git a/packages/runtime/js-core/src/domain/__tests__/schemas/branch.test.ts b/packages/runtime/js-core/src/domain/__tests__/schemas/branch.test.ts
new file mode 100644
index 00000000..70c49d8d
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/__tests__/schemas/branch.test.ts
@@ -0,0 +1,184 @@
+import { describe, expect, it } from 'vitest';
+import { IContainer, IEngine, WorkflowStatus } from '@flowgram.ai/runtime-interface';
+
+import { snapshotsToVOData } from '../utils';
+import { WorkflowRuntimeContainer } from '../../container';
+import { TestSchemas } from '.';
+
+const container: IContainer = WorkflowRuntimeContainer.instance;
+
+describe('WorkflowRuntime branch schema', () => {
+ it('should execute a workflow with branch 1', async () => {
+ const engine = container.get(IEngine);
+ const { context, processing } = engine.invoke({
+ schema: TestSchemas.branchSchema,
+ inputs: {
+ model_id: 1,
+ prompt: 'Tell me a joke',
+ },
+ });
+ expect(context.statusCenter.workflow.status).toBe(WorkflowStatus.Processing);
+ const result = await processing;
+ expect(context.statusCenter.workflow.status).toBe(WorkflowStatus.Succeeded);
+ expect(result).toStrictEqual({
+ m1_res: `Hi, I'm an AI assistant, my name is AI_MODEL_1, temperature is 0.5, system prompt is "I'm Model 1.", prompt is "Tell me a joke"`,
+ });
+ const snapshots = snapshotsToVOData(context.snapshotCenter.exportAll());
+ expect(snapshots).toStrictEqual([
+ {
+ nodeID: 'start_0',
+ inputs: {},
+ outputs: { model_id: 1, prompt: 'Tell me a joke' },
+ data: {},
+ },
+ {
+ nodeID: 'condition_0',
+ inputs: {},
+ outputs: {},
+ data: {
+ conditions: [
+ {
+ value: {
+ left: { type: 'ref', content: ['start_0', 'model_id'] },
+ operator: 'eq',
+ right: { type: 'constant', content: 1 },
+ },
+ key: 'if_1',
+ },
+ {
+ value: {
+ left: { type: 'ref', content: ['start_0', 'model_id'] },
+ operator: 'eq',
+ right: { type: 'constant', content: 2 },
+ },
+ key: 'if_2',
+ },
+ ],
+ },
+ branch: 'if_1',
+ },
+ {
+ nodeID: 'llm_1',
+ inputs: {
+ modelName: 'AI_MODEL_1',
+ apiKey: 'sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ apiHost: 'https://mock-ai-url/api/v3',
+ temperature: 0.5,
+ systemPrompt: "I'm Model 1.",
+ prompt: 'Tell me a joke',
+ },
+ outputs: {
+ result:
+ 'Hi, I\'m an AI assistant, my name is AI_MODEL_1, temperature is 0.5, system prompt is "I\'m Model 1.", prompt is "Tell me a joke"',
+ },
+ data: {},
+ },
+ {
+ nodeID: 'end_0',
+ inputs: {
+ m1_res:
+ 'Hi, I\'m an AI assistant, my name is AI_MODEL_1, temperature is 0.5, system prompt is "I\'m Model 1.", prompt is "Tell me a joke"',
+ },
+ outputs: {
+ m1_res:
+ 'Hi, I\'m an AI assistant, my name is AI_MODEL_1, temperature is 0.5, system prompt is "I\'m Model 1.", prompt is "Tell me a joke"',
+ },
+ data: {},
+ },
+ ]);
+
+ const report = context.reporter.export();
+ expect(report.workflowStatus.status).toBe(WorkflowStatus.Succeeded);
+ expect(report.reports.start_0.status).toBe(WorkflowStatus.Succeeded);
+ expect(report.reports.condition_0.status).toBe(WorkflowStatus.Succeeded);
+ expect(report.reports.llm_1.status).toBe(WorkflowStatus.Succeeded);
+ expect(report.reports.end_0.status).toBe(WorkflowStatus.Succeeded);
+ });
+
+ it('should execute a workflow with branch 2', async () => {
+ const engine = container.get(IEngine);
+ const { context, processing } = engine.invoke({
+ schema: TestSchemas.branchSchema,
+ inputs: {
+ model_id: 2,
+ prompt: 'Tell me a story',
+ },
+ });
+ expect(context.statusCenter.workflow.status).toBe(WorkflowStatus.Processing);
+ const result = await processing;
+ expect(context.statusCenter.workflow.status).toBe(WorkflowStatus.Succeeded);
+ expect(result).toStrictEqual({
+ m2_res: `Hi, I'm an AI assistant, my name is AI_MODEL_2, temperature is 0.6, system prompt is "I'm Model 2.", prompt is "Tell me a story"`,
+ });
+ const snapshots = snapshotsToVOData(context.snapshotCenter.exportAll());
+ expect(snapshots).toStrictEqual([
+ {
+ nodeID: 'start_0',
+ inputs: {},
+ outputs: { model_id: 2, prompt: 'Tell me a story' },
+ data: {},
+ },
+ {
+ nodeID: 'condition_0',
+ inputs: {},
+ outputs: {},
+ data: {
+ conditions: [
+ {
+ value: {
+ left: { type: 'ref', content: ['start_0', 'model_id'] },
+ operator: 'eq',
+ right: { type: 'constant', content: 1 },
+ },
+ key: 'if_1',
+ },
+ {
+ value: {
+ left: { type: 'ref', content: ['start_0', 'model_id'] },
+ operator: 'eq',
+ right: { type: 'constant', content: 2 },
+ },
+ key: 'if_2',
+ },
+ ],
+ },
+ branch: 'if_2',
+ },
+ {
+ nodeID: 'llm_2',
+ inputs: {
+ modelName: 'AI_MODEL_2',
+ apiKey: 'sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ apiHost: 'https://mock-ai-url/api/v3',
+ temperature: 0.6,
+ systemPrompt: "I'm Model 2.",
+ prompt: 'Tell me a story',
+ },
+ outputs: {
+ result:
+ 'Hi, I\'m an AI assistant, my name is AI_MODEL_2, temperature is 0.6, system prompt is "I\'m Model 2.", prompt is "Tell me a story"',
+ },
+ data: {},
+ },
+ {
+ nodeID: 'end_0',
+ inputs: {
+ m2_res:
+ 'Hi, I\'m an AI assistant, my name is AI_MODEL_2, temperature is 0.6, system prompt is "I\'m Model 2.", prompt is "Tell me a story"',
+ },
+ outputs: {
+ m2_res:
+ 'Hi, I\'m an AI assistant, my name is AI_MODEL_2, temperature is 0.6, system prompt is "I\'m Model 2.", prompt is "Tell me a story"',
+ },
+ data: {},
+ },
+ ]);
+
+ const report = context.reporter.export();
+ expect(report.workflowStatus.status).toBe(WorkflowStatus.Succeeded);
+ expect(report.reports.start_0.status).toBe(WorkflowStatus.Succeeded);
+ expect(report.reports.condition_0.status).toBe(WorkflowStatus.Succeeded);
+ expect(report.reports.llm_2.status).toBe(WorkflowStatus.Succeeded);
+ expect(report.reports.end_0.status).toBe(WorkflowStatus.Succeeded);
+ });
+});
diff --git a/packages/runtime/js-core/src/domain/__tests__/schemas/branch.ts b/packages/runtime/js-core/src/domain/__tests__/schemas/branch.ts
new file mode 100644
index 00000000..75a5f636
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/__tests__/schemas/branch.ts
@@ -0,0 +1,287 @@
+import { WorkflowSchema } from '@flowgram.ai/runtime-interface';
+
+export const branchSchema: WorkflowSchema = {
+ nodes: [
+ {
+ id: 'start_0',
+ type: 'start',
+ meta: {
+ position: {
+ x: 0,
+ y: 0,
+ },
+ },
+ data: {
+ title: 'Start',
+ outputs: {
+ type: 'object',
+ properties: {
+ model_id: {
+ key: 0,
+ name: 'model_id',
+ isPropertyRequired: false,
+ type: 'integer',
+ default: 'Hello Flow.',
+ extra: {
+ index: 0,
+ },
+ },
+ prompt: {
+ key: 5,
+ name: 'prompt',
+ isPropertyRequired: false,
+ type: 'string',
+ extra: {
+ index: 1,
+ },
+ },
+ },
+ required: [],
+ },
+ },
+ },
+ {
+ id: 'end_0',
+ type: 'end',
+ meta: {
+ position: {
+ x: 1500,
+ y: 0,
+ },
+ },
+ data: {
+ title: 'End',
+ inputs: {
+ type: 'object',
+ properties: {
+ m1_res: {
+ type: 'string',
+ },
+ m2_res: {
+ type: 'string',
+ },
+ },
+ },
+ inputsValues: {
+ m1_res: {
+ type: 'ref',
+ content: ['llm_1', 'result'],
+ },
+ m2_res: {
+ type: 'ref',
+ content: ['llm_2', 'result'],
+ },
+ },
+ },
+ },
+ {
+ id: 'condition_0',
+ type: 'condition',
+ meta: {
+ position: {
+ x: 500,
+ y: 0,
+ },
+ },
+ data: {
+ title: 'Condition',
+ conditions: [
+ {
+ value: {
+ left: {
+ type: 'ref',
+ content: ['start_0', 'model_id'],
+ },
+ operator: 'eq',
+ right: {
+ type: 'constant',
+ content: 1,
+ },
+ },
+ key: 'if_1',
+ },
+ {
+ value: {
+ left: {
+ type: 'ref',
+ content: ['start_0', 'model_id'],
+ },
+ operator: 'eq',
+ right: {
+ type: 'constant',
+ content: 2,
+ },
+ },
+ key: 'if_2',
+ },
+ ],
+ },
+ },
+ {
+ id: 'llm_1',
+ type: 'llm',
+ meta: {
+ position: {
+ x: 1000,
+ y: -500,
+ },
+ },
+ data: {
+ title: 'LLM_1',
+ inputsValues: {
+ modelName: {
+ type: 'constant',
+ content: 'AI_MODEL_1',
+ },
+ apiKey: {
+ type: 'constant',
+ content: 'sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ },
+ apiHost: {
+ type: 'constant',
+ content: 'https://mock-ai-url/api/v3',
+ },
+ temperature: {
+ type: 'constant',
+ content: 0.5,
+ },
+ systemPrompt: {
+ type: 'constant',
+ content: "I'm Model 1.",
+ },
+ prompt: {
+ type: 'ref',
+ content: ['start_0', 'prompt'],
+ },
+ },
+ inputs: {
+ type: 'object',
+ required: ['modelName', 'temperature', 'prompt'],
+ properties: {
+ modelName: {
+ type: 'string',
+ },
+ apiKey: {
+ type: 'string',
+ },
+ apiHost: {
+ type: 'string',
+ },
+ temperature: {
+ type: 'number',
+ },
+ systemPrompt: {
+ type: 'string',
+ },
+ prompt: {
+ type: 'string',
+ },
+ },
+ },
+ outputs: {
+ type: 'object',
+ properties: {
+ result: {
+ type: 'string',
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'llm_2',
+ type: 'llm',
+ meta: {
+ position: {
+ x: 1000,
+ y: 500,
+ },
+ },
+ data: {
+ title: 'LLM_2',
+ inputsValues: {
+ modelName: {
+ type: 'constant',
+ content: 'AI_MODEL_2',
+ },
+ apiKey: {
+ type: 'constant',
+ content: 'sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ },
+ apiHost: {
+ type: 'constant',
+ content: 'https://mock-ai-url/api/v3',
+ },
+ temperature: {
+ type: 'constant',
+ content: 0.6,
+ },
+ systemPrompt: {
+ type: 'constant',
+ content: "I'm Model 2.",
+ },
+ prompt: {
+ type: 'ref',
+ content: ['start_0', 'prompt'],
+ },
+ },
+ inputs: {
+ type: 'object',
+ required: ['modelName', 'temperature', 'prompt'],
+ properties: {
+ modelName: {
+ type: 'string',
+ },
+ apiKey: {
+ type: 'string',
+ },
+ apiHost: {
+ type: 'string',
+ },
+ temperature: {
+ type: 'number',
+ },
+ systemPrompt: {
+ type: 'string',
+ },
+ prompt: {
+ type: 'string',
+ },
+ },
+ },
+ outputs: {
+ type: 'object',
+ properties: {
+ result: {
+ type: 'string',
+ },
+ },
+ },
+ },
+ },
+ ],
+ edges: [
+ {
+ sourceNodeID: 'start_0',
+ targetNodeID: 'condition_0',
+ },
+ {
+ sourceNodeID: 'llm_1',
+ targetNodeID: 'end_0',
+ },
+ {
+ sourceNodeID: 'llm_2',
+ targetNodeID: 'end_0',
+ },
+ {
+ sourceNodeID: 'condition_0',
+ targetNodeID: 'llm_1',
+ sourcePortID: 'if_1',
+ },
+ {
+ sourceNodeID: 'condition_0',
+ targetNodeID: 'llm_2',
+ sourcePortID: 'if_2',
+ },
+ ],
+};
diff --git a/packages/runtime/js-core/src/domain/__tests__/schemas/index.ts b/packages/runtime/js-core/src/domain/__tests__/schemas/index.ts
new file mode 100644
index 00000000..473e4809
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/__tests__/schemas/index.ts
@@ -0,0 +1,13 @@
+import { twoLLMSchema } from './two-llm';
+import { loopSchema } from './loop';
+import { branchSchema } from './branch';
+import { basicLLMSchema } from './basic-llm';
+import { basicSchema } from './basic';
+
+export const TestSchemas = {
+ twoLLMSchema,
+ basicSchema,
+ branchSchema,
+ basicLLMSchema,
+ loopSchema,
+};
diff --git a/packages/runtime/js-core/src/domain/__tests__/schemas/loop.test.ts b/packages/runtime/js-core/src/domain/__tests__/schemas/loop.test.ts
new file mode 100644
index 00000000..07ab10c4
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/__tests__/schemas/loop.test.ts
@@ -0,0 +1,189 @@
+import { describe, expect, it } from 'vitest';
+import { IContainer, IEngine, WorkflowStatus } from '@flowgram.ai/runtime-interface';
+
+import { snapshotsToVOData } from '../utils';
+import { WorkflowRuntimeContainer } from '../../container';
+import { TestSchemas } from '.';
+
+const container: IContainer = WorkflowRuntimeContainer.instance;
+
+describe('WorkflowRuntime loop schema', () => {
+ it('should execute a workflow with input', async () => {
+ const engine = container.get(IEngine);
+ const { context, processing } = engine.invoke({
+ schema: TestSchemas.loopSchema,
+ inputs: {
+ prompt: 'How are you?',
+ tasks: [
+ 'TASK - A',
+ 'TASK - B',
+ 'TASK - C',
+ 'TASK - D',
+ 'TASK - E',
+ 'TASK - F',
+ 'TASK - G',
+ 'TASK - H',
+ ],
+ },
+ });
+ expect(context.statusCenter.workflow.status).toBe(WorkflowStatus.Processing);
+ const result = await processing;
+ expect(context.statusCenter.workflow.status).toBe(WorkflowStatus.Succeeded);
+ expect(result).toStrictEqual({});
+ const snapshots = snapshotsToVOData(context.snapshotCenter.exportAll());
+ expect(snapshots).toStrictEqual([
+ {
+ nodeID: 'start_0',
+ inputs: {},
+ outputs: {
+ prompt: 'How are you?',
+ tasks: [
+ 'TASK - A',
+ 'TASK - B',
+ 'TASK - C',
+ 'TASK - D',
+ 'TASK - E',
+ 'TASK - F',
+ 'TASK - G',
+ 'TASK - H',
+ ],
+ },
+ data: {},
+ },
+ {
+ nodeID: 'loop_0',
+ inputs: {},
+ outputs: {},
+ data: { batchFor: { type: 'ref', content: ['start_0', 'tasks'] } },
+ },
+ {
+ nodeID: 'llm_0',
+ inputs: {
+ modelName: 'AI_MODEL_1',
+ apiKey: 'sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ apiHost: 'https://mock-ai-url/api/v3',
+ temperature: 0.6,
+ prompt: 'TASK - A',
+ },
+ outputs: {
+ result:
+ 'Hi, I\'m an AI assistant, my name is AI_MODEL_1, temperature is 0.6, system prompt is "undefined", prompt is "TASK - A"',
+ },
+ data: {},
+ },
+ {
+ nodeID: 'llm_0',
+ inputs: {
+ modelName: 'AI_MODEL_1',
+ apiKey: 'sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ apiHost: 'https://mock-ai-url/api/v3',
+ temperature: 0.6,
+ prompt: 'TASK - B',
+ },
+ outputs: {
+ result:
+ 'Hi, I\'m an AI assistant, my name is AI_MODEL_1, temperature is 0.6, system prompt is "undefined", prompt is "TASK - B"',
+ },
+ data: {},
+ },
+ {
+ nodeID: 'llm_0',
+ inputs: {
+ modelName: 'AI_MODEL_1',
+ apiKey: 'sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ apiHost: 'https://mock-ai-url/api/v3',
+ temperature: 0.6,
+ prompt: 'TASK - C',
+ },
+ outputs: {
+ result:
+ 'Hi, I\'m an AI assistant, my name is AI_MODEL_1, temperature is 0.6, system prompt is "undefined", prompt is "TASK - C"',
+ },
+ data: {},
+ },
+ {
+ nodeID: 'llm_0',
+ inputs: {
+ modelName: 'AI_MODEL_1',
+ apiKey: 'sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ apiHost: 'https://mock-ai-url/api/v3',
+ temperature: 0.6,
+ prompt: 'TASK - D',
+ },
+ outputs: {
+ result:
+ 'Hi, I\'m an AI assistant, my name is AI_MODEL_1, temperature is 0.6, system prompt is "undefined", prompt is "TASK - D"',
+ },
+ data: {},
+ },
+ {
+ nodeID: 'llm_0',
+ inputs: {
+ modelName: 'AI_MODEL_1',
+ apiKey: 'sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ apiHost: 'https://mock-ai-url/api/v3',
+ temperature: 0.6,
+ prompt: 'TASK - E',
+ },
+ outputs: {
+ result:
+ 'Hi, I\'m an AI assistant, my name is AI_MODEL_1, temperature is 0.6, system prompt is "undefined", prompt is "TASK - E"',
+ },
+ data: {},
+ },
+ {
+ nodeID: 'llm_0',
+ inputs: {
+ modelName: 'AI_MODEL_1',
+ apiKey: 'sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ apiHost: 'https://mock-ai-url/api/v3',
+ temperature: 0.6,
+ prompt: 'TASK - F',
+ },
+ outputs: {
+ result:
+ 'Hi, I\'m an AI assistant, my name is AI_MODEL_1, temperature is 0.6, system prompt is "undefined", prompt is "TASK - F"',
+ },
+ data: {},
+ },
+ {
+ nodeID: 'llm_0',
+ inputs: {
+ modelName: 'AI_MODEL_1',
+ apiKey: 'sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ apiHost: 'https://mock-ai-url/api/v3',
+ temperature: 0.6,
+ prompt: 'TASK - G',
+ },
+ outputs: {
+ result:
+ 'Hi, I\'m an AI assistant, my name is AI_MODEL_1, temperature is 0.6, system prompt is "undefined", prompt is "TASK - G"',
+ },
+ data: {},
+ },
+ {
+ nodeID: 'llm_0',
+ inputs: {
+ modelName: 'AI_MODEL_1',
+ apiKey: 'sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ apiHost: 'https://mock-ai-url/api/v3',
+ temperature: 0.6,
+ prompt: 'TASK - H',
+ },
+ outputs: {
+ result:
+ 'Hi, I\'m an AI assistant, my name is AI_MODEL_1, temperature is 0.6, system prompt is "undefined", prompt is "TASK - H"',
+ },
+ data: {},
+ },
+ { nodeID: 'end_0', inputs: {}, outputs: {}, data: {} },
+ ]);
+ const report = context.reporter.export();
+ expect(report.workflowStatus.status).toBe(WorkflowStatus.Succeeded);
+ expect(report.reports.start_0.status).toBe(WorkflowStatus.Succeeded);
+ expect(report.reports.loop_0.status).toBe(WorkflowStatus.Succeeded);
+ expect(report.reports.llm_0.status).toBe(WorkflowStatus.Succeeded);
+ expect(report.reports.end_0.status).toBe(WorkflowStatus.Succeeded);
+ expect(report.reports.llm_0.snapshots.length).toBe(8);
+ });
+});
diff --git a/packages/runtime/js-core/src/domain/__tests__/schemas/loop.ts b/packages/runtime/js-core/src/domain/__tests__/schemas/loop.ts
new file mode 100644
index 00000000..bd16ea8a
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/__tests__/schemas/loop.ts
@@ -0,0 +1,164 @@
+import { WorkflowSchema } from '@flowgram.ai/runtime-interface';
+
+export const loopSchema: WorkflowSchema = {
+ nodes: [
+ {
+ id: 'start_0',
+ type: 'start',
+ meta: {
+ position: {
+ x: 180,
+ y: 218.5,
+ },
+ },
+ data: {
+ title: 'Start',
+ outputs: {
+ type: 'object',
+ properties: {
+ tasks: {
+ key: 7,
+ name: 'tasks',
+ isPropertyRequired: true,
+ type: 'array',
+ extra: {
+ index: 0,
+ },
+ items: {
+ type: 'string',
+ },
+ },
+ system_prompt: {
+ key: 1,
+ name: 'system_prompt',
+ isPropertyRequired: true,
+ type: 'string',
+ extra: {
+ index: 1,
+ },
+ },
+ },
+ required: ['tasks', 'system_prompt'],
+ },
+ },
+ },
+ {
+ id: 'end_0',
+ type: 'end',
+ meta: {
+ position: {
+ x: 1340,
+ y: 228.5,
+ },
+ },
+ data: {
+ title: 'End',
+ inputs: {
+ type: 'object',
+ properties: {},
+ },
+ inputsValues: {},
+ },
+ },
+ {
+ id: 'loop_0',
+ type: 'loop',
+ meta: {
+ position: {
+ x: 560,
+ y: 125,
+ },
+ },
+ data: {
+ title: 'Loop_1',
+ batchFor: {
+ type: 'ref',
+ content: ['start_0', 'tasks'],
+ },
+ },
+ blocks: [
+ {
+ id: 'llm_0',
+ type: 'llm',
+ meta: {
+ position: {
+ x: 200,
+ y: 0,
+ },
+ },
+ data: {
+ title: 'LLM_1',
+ inputsValues: {
+ modelName: {
+ type: 'constant',
+ content: 'AI_MODEL_1',
+ },
+ apiKey: {
+ type: 'constant',
+ content: 'sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ },
+ apiHost: {
+ type: 'constant',
+ content: 'https://mock-ai-url/api/v3',
+ },
+ temperature: {
+ type: 'constant',
+ content: 0.6,
+ },
+ systemPrompt: {
+ type: 'ref',
+ content: ['start_0', 'system_prompt'],
+ },
+ prompt: {
+ type: 'ref',
+ content: ['loop_0_locals', 'item'],
+ },
+ },
+ inputs: {
+ type: 'object',
+ required: ['modelName', 'apiKey', 'apiHost', 'temperature', 'prompt'],
+ properties: {
+ modelName: {
+ type: 'string',
+ },
+ apiKey: {
+ type: 'string',
+ },
+ apiHost: {
+ type: 'string',
+ },
+ temperature: {
+ type: 'number',
+ },
+ systemPrompt: {
+ type: 'string',
+ },
+ prompt: {
+ type: 'string',
+ },
+ },
+ },
+ outputs: {
+ type: 'object',
+ properties: {
+ result: {
+ type: 'string',
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ ],
+ edges: [
+ {
+ sourceNodeID: 'start_0',
+ targetNodeID: 'loop_0',
+ },
+ {
+ sourceNodeID: 'loop_0',
+ targetNodeID: 'end_0',
+ },
+ ],
+};
diff --git a/packages/runtime/js-core/src/domain/__tests__/schemas/two-llm.ts b/packages/runtime/js-core/src/domain/__tests__/schemas/two-llm.ts
new file mode 100644
index 00000000..f0bd6e17
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/__tests__/schemas/two-llm.ts
@@ -0,0 +1,267 @@
+import { WorkflowSchema } from '@flowgram.ai/runtime-interface';
+
+export const twoLLMSchema: WorkflowSchema = {
+ nodes: [
+ {
+ id: 'start_0',
+ type: 'start',
+ meta: {
+ position: {
+ x: 180,
+ y: 222.5,
+ },
+ },
+ data: {
+ title: 'Start',
+ outputs: {
+ type: 'object',
+ properties: {
+ query: {
+ key: 5,
+ name: 'query',
+ isPropertyRequired: false,
+ type: 'string',
+ default: 'Hello Flow.',
+ extra: {
+ index: 0,
+ },
+ },
+ enable: {
+ key: 6,
+ name: 'enable',
+ isPropertyRequired: false,
+ type: 'boolean',
+ default: true,
+ extra: {
+ index: 1,
+ },
+ },
+ array_obj: {
+ key: 7,
+ name: 'array_obj',
+ isPropertyRequired: false,
+ type: 'array',
+ items: {
+ type: 'object',
+ properties: {
+ int: {
+ type: 'number',
+ },
+ str: {
+ type: 'string',
+ },
+ },
+ },
+ extra: {
+ index: 2,
+ },
+ },
+ num: {
+ key: 10,
+ name: 'num',
+ isPropertyRequired: false,
+ type: 'number',
+ extra: {
+ index: 3,
+ },
+ },
+ model: {
+ key: 24,
+ name: 'model',
+ type: 'string',
+ extra: {
+ index: 5,
+ },
+ },
+ },
+ required: [],
+ },
+ },
+ },
+ {
+ id: 'end_0',
+ type: 'end',
+ meta: {
+ position: {
+ x: 1100,
+ y: 235.5,
+ },
+ },
+ data: {
+ title: 'End',
+ outputs: {
+ type: 'object',
+ properties: {
+ result: {
+ type: 'string',
+ default: {
+ type: 'ref',
+ content: ['llm_BjEpK', 'result'],
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'llm_2',
+ type: 'llm',
+ meta: {
+ position: {
+ x: 640,
+ y: 327,
+ },
+ },
+ data: {
+ title: 'LLM_2',
+ inputsValues: {
+ systemPrompt: {
+ type: 'constant',
+ content: 'BBBB',
+ },
+ modelName: {
+ type: 'ref',
+ content: ['start_0', 'model'],
+ },
+ apiKey: {
+ type: 'constant',
+ content: 'sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ },
+ apiHost: {
+ type: 'constant',
+ content: 'https://mock-ai-url/api/v3',
+ },
+ temperature: {
+ type: 'ref',
+ content: ['start_0', 'num'],
+ },
+ prompt: {
+ type: 'ref',
+ content: ['start_0', 'query'],
+ },
+ },
+ inputs: {
+ type: 'object',
+ required: ['modelName', 'temperature', 'prompt'],
+ properties: {
+ modelName: {
+ type: 'string',
+ },
+ apiKey: {
+ type: 'string',
+ },
+ apiHost: {
+ type: 'string',
+ },
+ temperature: {
+ type: 'number',
+ },
+ systemPrompt: {
+ type: 'string',
+ },
+ prompt: {
+ type: 'string',
+ },
+ },
+ },
+ outputs: {
+ type: 'object',
+ properties: {
+ result: {
+ type: 'string',
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'llm_1',
+ type: 'llm',
+ meta: {
+ position: {
+ x: 640,
+ y: 0,
+ },
+ },
+ data: {
+ title: 'LLM_1',
+ inputsValues: {
+ modelName: {
+ type: 'ref',
+ content: ['start_0', 'model'],
+ },
+ apiKey: {
+ type: 'constant',
+ content: 'sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ },
+ apiHost: {
+ type: 'constant',
+ content: 'https://mock-ai-url/api/v3',
+ },
+ temperature: {
+ type: 'ref',
+ content: ['start_0', 'num'],
+ },
+ systemPrompt: {
+ type: 'constant',
+ content: 'AAAA',
+ },
+ prompt: {
+ type: 'ref',
+ content: ['start_0', 'query'],
+ },
+ },
+ inputs: {
+ type: 'object',
+ required: ['modelName', 'temperature', 'prompt'],
+ properties: {
+ modelName: {
+ type: 'string',
+ },
+ apiKey: {
+ type: 'string',
+ },
+ apiHost: {
+ type: 'string',
+ },
+ temperature: {
+ type: 'number',
+ },
+ systemPrompt: {
+ type: 'string',
+ },
+ prompt: {
+ type: 'string',
+ },
+ },
+ },
+ outputs: {
+ type: 'object',
+ properties: {
+ result: {
+ type: 'string',
+ },
+ },
+ },
+ },
+ },
+ ],
+ edges: [
+ {
+ sourceNodeID: 'start_0',
+ targetNodeID: 'llm_1',
+ },
+ {
+ sourceNodeID: 'start_0',
+ targetNodeID: 'llm_2',
+ },
+ {
+ sourceNodeID: 'llm_2',
+ targetNodeID: 'end_0',
+ },
+ {
+ sourceNodeID: 'llm_1',
+ targetNodeID: 'end_0',
+ },
+ ],
+};
diff --git a/packages/runtime/js-core/src/domain/__tests__/setup.ts b/packages/runtime/js-core/src/domain/__tests__/setup.ts
new file mode 100644
index 00000000..ad0b7168
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/__tests__/setup.ts
@@ -0,0 +1,8 @@
+import { IExecutor } from '@flowgram.ai/runtime-interface';
+
+import { MockLLMExecutor } from './executor/llm';
+import { WorkflowRuntimeContainer } from '../container';
+
+const container = WorkflowRuntimeContainer.instance;
+const executor = container.get(IExecutor);
+executor.register(new MockLLMExecutor());
diff --git a/packages/runtime/js-core/src/domain/__tests__/utils/index.ts b/packages/runtime/js-core/src/domain/__tests__/utils/index.ts
new file mode 100644
index 00000000..85797488
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/__tests__/utils/index.ts
@@ -0,0 +1 @@
+export { snapshotsToVOData } from './snapshot';
diff --git a/packages/runtime/js-core/src/domain/__tests__/utils/snapshot.ts b/packages/runtime/js-core/src/domain/__tests__/utils/snapshot.ts
new file mode 100644
index 00000000..75ac2f33
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/__tests__/utils/snapshot.ts
@@ -0,0 +1,16 @@
+import { Snapshot, VOData } from '@flowgram.ai/runtime-interface';
+
+export const snapshotsToVOData = (snapshots: Snapshot[]): VOData[] =>
+ snapshots.map((snapshot) => {
+ const { nodeID, inputs, outputs, data, branch } = snapshot;
+ const newSnapshot: VOData = {
+ nodeID,
+ inputs,
+ outputs,
+ data,
+ };
+ if (branch) {
+ newSnapshot.branch = branch;
+ }
+ return newSnapshot;
+ });
diff --git a/packages/runtime/js-core/src/domain/container/index.test.ts b/packages/runtime/js-core/src/domain/container/index.test.ts
new file mode 100644
index 00000000..79bd4857
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/container/index.test.ts
@@ -0,0 +1,43 @@
+import { describe, expect, it } from 'vitest';
+import { IEngine, IExecutor, IValidation } from '@flowgram.ai/runtime-interface';
+
+import { WorkflowRuntimeContainer } from './index';
+
+describe('WorkflowRuntimeContainer', () => {
+ it('should create a container instance', () => {
+ const container = new WorkflowRuntimeContainer({});
+ expect(container).toBeDefined();
+ expect(container).toBeInstanceOf(WorkflowRuntimeContainer);
+ });
+
+ it('should get services correctly', () => {
+ const mockServices = {
+ [IValidation]: { id: 'validation' },
+ [IExecutor]: { id: 'executor' },
+ [IEngine]: { id: 'engine' },
+ };
+
+ const container = new WorkflowRuntimeContainer(mockServices);
+
+ expect(container.get(IValidation)).toEqual({ id: 'validation' });
+ expect(container.get(IExecutor)).toEqual({ id: 'executor' });
+ expect(container.get(IEngine)).toEqual({ id: 'engine' });
+ });
+
+ it('should maintain singleton instance', () => {
+ const instance1 = WorkflowRuntimeContainer.instance;
+ const instance2 = WorkflowRuntimeContainer.instance;
+
+ expect(instance1).toBeDefined();
+ expect(instance2).toBeDefined();
+ expect(instance1).toBe(instance2);
+ });
+
+ it('should create services correctly', () => {
+ const services = (WorkflowRuntimeContainer as any).create();
+
+ expect(services[IValidation]).toBeDefined();
+ expect(services[IExecutor]).toBeDefined();
+ expect(services[IEngine]).toBeDefined();
+ });
+});
diff --git a/packages/runtime/js-core/src/domain/container/index.ts b/packages/runtime/js-core/src/domain/container/index.ts
new file mode 100644
index 00000000..91f2dc2f
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/container/index.ts
@@ -0,0 +1,46 @@
+import {
+ ContainerService,
+ IContainer,
+ IEngine,
+ IExecutor,
+ IValidation,
+} from '@flowgram.ai/runtime-interface';
+
+import { WorkflowRuntimeNodeExecutors } from '@nodes/index';
+import { WorkflowRuntimeValidation } from '../validation';
+import { WorkflowRuntimeExecutor } from '../executor';
+import { WorkflowRuntimeEngine } from '../engine';
+
+export class WorkflowRuntimeContainer implements IContainer {
+ constructor(private readonly services: Record) {}
+
+ public get(key: any): T {
+ return this.services[key] as T;
+ }
+
+ private static _instance: IContainer;
+
+ public static get instance(): IContainer {
+ if (this._instance) {
+ return this._instance;
+ }
+ const services = this.create();
+ this._instance = new WorkflowRuntimeContainer(services);
+ return this._instance;
+ }
+
+ private static create(): Record {
+ // services
+ const Validation = new WorkflowRuntimeValidation();
+ const Executor = new WorkflowRuntimeExecutor(WorkflowRuntimeNodeExecutors);
+ const Engine = new WorkflowRuntimeEngine({
+ Executor,
+ });
+
+ return {
+ [IValidation]: Validation,
+ [IExecutor]: Executor,
+ [IEngine]: Engine,
+ };
+ }
+}
diff --git a/packages/runtime/js-core/src/domain/context/index.ts b/packages/runtime/js-core/src/domain/context/index.ts
new file mode 100644
index 00000000..d084214b
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/context/index.ts
@@ -0,0 +1,116 @@
+import {
+ InvokeParams,
+ IContext,
+ IDocument,
+ IState,
+ ISnapshotCenter,
+ IVariableStore,
+ IStatusCenter,
+ IReporter,
+ IIOCenter,
+ ContextData,
+} from '@flowgram.ai/runtime-interface';
+
+import { uuid } from '@infra/utils';
+import { WorkflowRuntimeVariableStore } from '../variable';
+import { WorkflowRuntimeStatusCenter } from '../status';
+import { WorkflowRuntimeState } from '../state';
+import { WorkflowRuntimeSnapshotCenter } from '../snapshot';
+import { WorkflowRuntimeReporter } from '../report';
+import { WorkflowRuntimeIOCenter } from '../io-center';
+import { WorkflowRuntimeDocument } from '../document';
+
+export class WorkflowRuntimeContext implements IContext {
+ public readonly id: string;
+
+ public readonly document: IDocument;
+
+ public readonly variableStore: IVariableStore;
+
+ public readonly state: IState;
+
+ public readonly ioCenter: IIOCenter;
+
+ public readonly snapshotCenter: ISnapshotCenter;
+
+ public readonly statusCenter: IStatusCenter;
+
+ public readonly reporter: IReporter;
+
+ private subContexts: IContext[] = [];
+
+ constructor(data: ContextData) {
+ this.id = uuid();
+ this.document = data.document;
+ this.variableStore = data.variableStore;
+ this.state = data.state;
+ this.ioCenter = data.ioCenter;
+ this.snapshotCenter = data.snapshotCenter;
+ this.statusCenter = data.statusCenter;
+ this.reporter = data.reporter;
+ }
+
+ public init(params: InvokeParams): void {
+ const { schema, inputs } = params;
+ this.document.init(schema);
+ this.variableStore.init();
+ this.state.init();
+ this.ioCenter.init(inputs);
+ this.snapshotCenter.init();
+ this.statusCenter.init();
+ this.reporter.init();
+ }
+
+ public dispose(): void {
+ this.subContexts.forEach((subContext) => {
+ subContext.dispose();
+ });
+ this.subContexts = [];
+ this.document.dispose();
+ this.variableStore.dispose();
+ this.state.dispose();
+ this.ioCenter.dispose();
+ this.snapshotCenter.dispose();
+ this.statusCenter.dispose();
+ this.reporter.dispose();
+ }
+
+ public sub(): IContext {
+ const variableStore = new WorkflowRuntimeVariableStore();
+ variableStore.setParent(this.variableStore);
+ const state = new WorkflowRuntimeState(variableStore);
+ const contextData: ContextData = {
+ document: this.document,
+ ioCenter: this.ioCenter,
+ snapshotCenter: this.snapshotCenter,
+ statusCenter: this.statusCenter,
+ reporter: this.reporter,
+ variableStore,
+ state,
+ };
+ const subContext = new WorkflowRuntimeContext(contextData);
+ this.subContexts.push(subContext);
+ subContext.variableStore.init();
+ subContext.state.init();
+ return subContext;
+ }
+
+ public static create(): IContext {
+ const document = new WorkflowRuntimeDocument();
+ const variableStore = new WorkflowRuntimeVariableStore();
+ const state = new WorkflowRuntimeState(variableStore);
+ const ioCenter = new WorkflowRuntimeIOCenter();
+ const snapshotCenter = new WorkflowRuntimeSnapshotCenter();
+ const statusCenter = new WorkflowRuntimeStatusCenter();
+ const reporter = new WorkflowRuntimeReporter(ioCenter, snapshotCenter, statusCenter);
+ return new WorkflowRuntimeContext({
+ document,
+ variableStore,
+ state,
+ ioCenter,
+ snapshotCenter,
+ statusCenter,
+ reporter,
+ });
+ }
+}
diff --git a/packages/runtime/js-core/src/domain/document/document/create-store.test.ts b/packages/runtime/js-core/src/domain/document/document/create-store.test.ts
new file mode 100644
index 00000000..b361faa0
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/document/document/create-store.test.ts
@@ -0,0 +1,109 @@
+import { describe, expect, it } from 'vitest';
+import { FlowGramNode, WorkflowPortType } from '@flowgram.ai/runtime-interface';
+
+import { createStore } from './create-store';
+
+describe('createStore', () => {
+ it('should create an empty store', () => {
+ const store = createStore({
+ flattenSchema: { nodes: [], edges: [] },
+ nodeBlocks: new Map(),
+ nodeEdges: new Map(),
+ });
+
+ expect(store.nodes.size).toBe(1); // 只有 root 节点
+ expect(store.edges.size).toBe(0);
+ expect(store.ports.size).toBe(0);
+
+ const rootNode = store.nodes.get(FlowGramNode.Root);
+ expect(rootNode).toBeDefined();
+ expect(rootNode?.type).toBe(FlowGramNode.Root);
+ expect(rootNode?.position).toEqual({ x: 0, y: 0 });
+ });
+
+ it('should create store with nodes and edges', () => {
+ const store = createStore({
+ flattenSchema: {
+ nodes: [
+ {
+ id: 'node1',
+ type: 'TestNode',
+ meta: { position: { x: 100, y: 100 } },
+ data: {
+ title: 'Test Node 1',
+ inputsValues: { test: 'value' },
+ inputs: ['input1'],
+ outputs: ['output1'],
+ },
+ },
+ {
+ id: 'node2',
+ type: 'TestNode',
+ meta: { position: { x: 200, y: 200 } },
+ data: {
+ title: 'Test Node 2',
+ },
+ },
+ ],
+ edges: [
+ {
+ sourceNodeID: 'node1',
+ targetNodeID: 'node2',
+ sourcePortID: 'output1',
+ targetPortID: 'input1',
+ },
+ ],
+ },
+ nodeBlocks: new Map([['root', ['node1', 'node2']]]),
+ nodeEdges: new Map([['root', ['node1-node2']]]),
+ });
+
+ // 验证节点创建
+ expect(store.nodes.size).toBe(3); // root + 2个测试节点
+ const node1 = store.nodes.get('node1');
+ expect(node1?.type).toBe('TestNode');
+ expect(node1?.name).toBe('Test Node 1');
+ expect(node1?.position).toEqual({ x: 100, y: 100 });
+ expect(node1?.declare).toEqual({
+ inputsValues: { test: 'value' },
+ inputs: ['input1'],
+ outputs: ['output1'],
+ });
+
+ // 验证边创建
+ expect(store.edges.size).toBe(1);
+ const edge = Array.from(store.edges.values())[0];
+ expect(edge.from).toBe(node1);
+ expect(edge.to).toBe(store.nodes.get('node2'));
+
+ // 验证端口创建
+ expect(store.ports.size).toBe(2); // 输入端口和输出端口
+ const outputPort = store.ports.get('output1');
+ const inputPort = store.ports.get('input1');
+ expect(outputPort?.type).toBe(WorkflowPortType.Output);
+ expect(inputPort?.type).toBe(WorkflowPortType.Input);
+
+ // 验证节点关系
+ const rootNode = store.nodes.get(FlowGramNode.Root);
+ expect(rootNode?.children).toHaveLength(2);
+ expect(node1?.parent).toBe(rootNode);
+ });
+
+ it('should throw error for invalid edge', () => {
+ expect(() =>
+ createStore({
+ flattenSchema: {
+ nodes: [],
+ edges: [
+ {
+ sourceNodeID: 'nonexistent1',
+ targetNodeID: 'nonexistent2',
+ },
+ ],
+ },
+ nodeBlocks: new Map(),
+ nodeEdges: new Map(),
+ })
+ ).toThrow('invalid edge schema ID');
+ });
+});
diff --git a/packages/runtime/js-core/src/domain/document/document/create-store.ts b/packages/runtime/js-core/src/domain/document/document/create-store.ts
new file mode 100644
index 00000000..68d7f07b
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/document/document/create-store.ts
@@ -0,0 +1,133 @@
+import {
+ FlowGramNode,
+ WorkflowPortType,
+ type CreateEdgeParams,
+ type CreateNodeParams,
+ type CreatePortParams,
+} from '@flowgram.ai/runtime-interface';
+
+import { WorkflowRuntimeEdge, WorkflowRuntimeNode, WorkflowRuntimePort } from '../entity';
+import { FlattenData } from './flat-schema';
+
+export interface DocumentStore {
+ nodes: Map;
+ edges: Map;
+ ports: Map;
+}
+
+const createNode = (store: DocumentStore, params: CreateNodeParams): WorkflowRuntimeNode => {
+ const node = new WorkflowRuntimeNode(params);
+ store.nodes.set(node.id, node);
+ return node;
+};
+
+const createEdge = (store: DocumentStore, params: CreateEdgeParams): WorkflowRuntimeEdge => {
+ const edge = new WorkflowRuntimeEdge(params);
+ store.edges.set(edge.id, edge);
+ return edge;
+};
+
+const getOrCreatePort = (store: DocumentStore, params: CreatePortParams): WorkflowRuntimePort => {
+ const createdPort = store.ports.get(params.id);
+ if (createdPort) {
+ return createdPort as WorkflowRuntimePort;
+ }
+ const port = new WorkflowRuntimePort(params);
+ store.ports.set(port.id, port);
+ return port;
+};
+
+export const createStore = (params: FlattenData): DocumentStore => {
+ const { flattenSchema, nodeBlocks } = params;
+ const { nodes, edges } = flattenSchema;
+ const store: DocumentStore = {
+ nodes: new Map(),
+ edges: new Map(),
+ ports: new Map(),
+ };
+ // create root node
+ createNode(store, {
+ id: FlowGramNode.Root,
+ type: FlowGramNode.Root,
+ name: FlowGramNode.Root,
+ position: { x: 0, y: 0 },
+ });
+ // create nodes
+ nodes.forEach((nodeSchema) => {
+ const id = nodeSchema.id;
+ const type = nodeSchema.type as FlowGramNode;
+ const {
+ title = `${type}-${id}-untitled`,
+ inputsValues,
+ inputs,
+ outputs,
+ ...data
+ } = nodeSchema.data ?? {};
+ createNode(store, {
+ id,
+ type,
+ name: title,
+ position: nodeSchema.meta.position,
+ variable: { inputsValues, inputs, outputs },
+ data,
+ });
+ });
+ // create node relations
+ nodeBlocks.forEach((blockIDs, parentID) => {
+ const parent = store.nodes.get(parentID) as WorkflowRuntimeNode;
+ const children = blockIDs
+ .map((id) => store.nodes.get(id))
+ .filter(Boolean) as WorkflowRuntimeNode[];
+ children.forEach((child) => {
+ child.parent = parent;
+ parent.addChild(child);
+ });
+ });
+ // create edges & ports
+ edges.forEach((edgeSchema) => {
+ const id = WorkflowRuntimeEdge.createID(edgeSchema);
+ const {
+ sourceNodeID,
+ targetNodeID,
+ sourcePortID = 'defaultOutput',
+ targetPortID = 'defaultInput',
+ } = edgeSchema;
+ const from = store.nodes.get(sourceNodeID);
+ const to = store.nodes.get(targetNodeID);
+ if (!from || !to) {
+ throw new Error(`invalid edge schema ID: ${id}, from: ${sourceNodeID}, to: ${targetNodeID}`);
+ }
+ const edge = createEdge(store, {
+ id,
+ from,
+ to,
+ });
+
+ // create from port
+ const fromPort = getOrCreatePort(store, {
+ node: from,
+ id: sourcePortID,
+ type: WorkflowPortType.Output,
+ });
+
+ // build relation
+ fromPort.addEdge(edge);
+ edge.fromPort = fromPort;
+ from.addPort(fromPort);
+ from.addOutputEdge(edge);
+
+ // create to port
+ const toPort = getOrCreatePort(store, {
+ node: to,
+ id: targetPortID,
+ type: WorkflowPortType.Input,
+ });
+
+ // build relation
+ toPort.addEdge(edge);
+ edge.toPort = toPort;
+ to.addPort(toPort);
+ to.addInputEdge(edge);
+ });
+ return store;
+};
diff --git a/packages/runtime/js-core/src/domain/document/document/flat-schema.test.ts b/packages/runtime/js-core/src/domain/document/document/flat-schema.test.ts
new file mode 100644
index 00000000..1b235149
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/document/document/flat-schema.test.ts
@@ -0,0 +1,73 @@
+import { describe, expect, it } from 'vitest';
+import { FlowGramNode, WorkflowSchema } from '@flowgram.ai/runtime-interface';
+
+import { flatSchema } from './flat-schema';
+
+describe('flatSchema', () => {
+ it('should handle empty schema', () => {
+ const result = flatSchema({});
+ expect(result.flattenSchema.nodes).toEqual([]);
+ expect(result.flattenSchema.edges).toEqual([]);
+ expect(result.nodeBlocks.get(FlowGramNode.Root)).toEqual([]);
+ expect(result.nodeEdges.get(FlowGramNode.Root)).toEqual([]);
+ });
+
+ it('should handle basic schema without nested blocks', () => {
+ const schema = {
+ nodes: [
+ { id: 'node1', type: 'test' },
+ { id: 'node2', type: 'test' },
+ ],
+ edges: [{ sourceNodeID: 'node1', targetNodeID: 'node2' }],
+ } as unknown as WorkflowSchema;
+
+ const result = flatSchema(schema);
+ expect(result.flattenSchema.nodes).toEqual(schema.nodes);
+ expect(result.flattenSchema.edges).toEqual(schema.edges);
+ expect(result.nodeBlocks.get(FlowGramNode.Root)).toEqual(['node1', 'node2']);
+ expect(result.nodeEdges.get(FlowGramNode.Root)).toEqual(['node1-node2']);
+ });
+
+ it('should flatten nested blocks and edges', () => {
+ const schema = {
+ nodes: [
+ {
+ id: 'parent',
+ type: 'container',
+ blocks: [
+ { id: 'child1', type: 'test' },
+ {
+ id: 'child2',
+ type: 'test',
+ blocks: [{ id: 'grandchild', type: 'test' }],
+ edges: [{ sourceNodeID: 'child2', targetNodeID: 'grandchild' }],
+ },
+ ],
+ edges: [{ sourceNodeID: 'child1', targetNodeID: 'child2' }],
+ },
+ ],
+ edges: [],
+ } as unknown as WorkflowSchema;
+
+ const result = flatSchema(schema);
+
+ // 验证节点被正确展平
+ expect(result.flattenSchema.nodes.map((n) => n.id)).toEqual([
+ 'parent',
+ 'child1',
+ 'child2',
+ 'grandchild',
+ ]);
+
+ // 验证边被正确展平
+ expect(result.flattenSchema.edges.length).toBe(2);
+
+ // 验证节点关系映射
+ expect(result.nodeBlocks.get('parent')).toEqual(['child1', 'child2']);
+ expect(result.nodeBlocks.get('child2')).toEqual(['grandchild']);
+
+ // 验证边关系映射
+ expect(result.nodeEdges.get('parent')).toEqual(['child1-child2']);
+ expect(result.nodeEdges.get('child2')).toEqual(['child2-grandchild']);
+ });
+});
diff --git a/packages/runtime/js-core/src/domain/document/document/flat-schema.ts b/packages/runtime/js-core/src/domain/document/document/flat-schema.ts
new file mode 100644
index 00000000..48e84b34
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/document/document/flat-schema.ts
@@ -0,0 +1,73 @@
+import { WorkflowNodeSchema, WorkflowSchema, FlowGramNode } from '@flowgram.ai/runtime-interface';
+
+import { WorkflowRuntimeEdge } from '../entity';
+
+export interface FlattenData {
+ flattenSchema: WorkflowSchema;
+ nodeBlocks: Map;
+ nodeEdges: Map;
+}
+
+type FlatSchema = (json: Partial) => FlattenData;
+
+const flatLayer = (data: FlattenData, nodeSchema: WorkflowNodeSchema) => {
+ const { blocks, edges } = nodeSchema;
+ if (blocks) {
+ data.flattenSchema.nodes.push(...blocks);
+ const blockIDs: string[] = [];
+ blocks.forEach((block) => {
+ blockIDs.push(block.id);
+ // 递归处理子节点的 blocks 和 edges
+ if (block.blocks) {
+ flatLayer(data, block);
+ }
+ });
+ data.nodeBlocks.set(nodeSchema.id, blockIDs);
+ delete nodeSchema.blocks;
+ }
+ if (edges) {
+ data.flattenSchema.edges.push(...edges);
+ const edgeIDs: string[] = [];
+ edges.forEach((edge) => {
+ const edgeID = WorkflowRuntimeEdge.createID(edge);
+ edgeIDs.push(edgeID);
+ });
+ data.nodeEdges.set(nodeSchema.id, edgeIDs);
+ delete nodeSchema.edges;
+ }
+};
+
+/**
+ * flat the tree json structure, extract the structure information to map
+ */
+export const flatSchema: FlatSchema = (schema = { nodes: [], edges: [] }) => {
+ const rootNodes = schema.nodes ?? [];
+ const rootEdges = schema.edges ?? [];
+
+ const data: FlattenData = {
+ flattenSchema: {
+ nodes: [],
+ edges: [],
+ },
+ nodeBlocks: new Map(),
+ nodeEdges: new Map(),
+ };
+
+ const root: WorkflowNodeSchema = {
+ id: FlowGramNode.Root,
+ type: FlowGramNode.Root,
+ blocks: rootNodes,
+ edges: rootEdges,
+ meta: {
+ position: {
+ x: 0,
+ y: 0,
+ },
+ },
+ data: {},
+ };
+
+ flatLayer(data, root);
+
+ return data;
+};
diff --git a/packages/runtime/js-core/src/domain/document/document/index.test.ts b/packages/runtime/js-core/src/domain/document/document/index.test.ts
new file mode 100644
index 00000000..240f4f8c
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/document/document/index.test.ts
@@ -0,0 +1,86 @@
+import { describe, expect, it } from 'vitest';
+
+import { TestSchemas } from '@workflow/__tests__/schemas';
+import { WorkflowRuntimeDocument } from './index';
+
+describe('WorkflowRuntimeDocument create', () => {
+ it('should create instance', () => {
+ const document = new WorkflowRuntimeDocument();
+ expect(document).toBeDefined();
+ expect(document.id).toBeDefined();
+ });
+
+ it('should has root', () => {
+ const schema = {
+ nodes: [],
+ edges: [],
+ };
+ const document = new WorkflowRuntimeDocument();
+ document.init(schema);
+ expect(document.root).toBeDefined();
+ });
+
+ it('should init', () => {
+ const document = new WorkflowRuntimeDocument();
+ document.init(TestSchemas.basicSchema);
+ const nodeIDs = document.nodes.map((n) => n.id);
+ const edgeIDs = document.edges.map((e) => e.id);
+ expect(nodeIDs).toEqual(['root', 'start_0', 'end_0', 'llm_0']);
+ expect(edgeIDs).toEqual(['start_0-llm_0', 'llm_0-end_0']);
+ });
+
+ it('should dispose', () => {
+ const document = new WorkflowRuntimeDocument();
+ document.init(TestSchemas.basicSchema);
+ expect(document.nodes.length).toBe(4);
+ expect(document.edges.length).toBe(2);
+ document.dispose();
+ expect(document.nodes.length).toBe(0);
+ expect(document.edges.length).toBe(0);
+ });
+
+ it('should has start & end', () => {
+ const document = new WorkflowRuntimeDocument();
+ document.init(TestSchemas.basicSchema);
+ expect(document.start.id).toBe('start_0');
+ expect(document.end.id).toBe('end_0');
+ });
+
+ it('should get node by id', () => {
+ const document = new WorkflowRuntimeDocument();
+ document.init(TestSchemas.basicSchema);
+
+ const node = document.getNode('llm_0');
+ expect(node).toBeDefined();
+ expect(node?.id).toBe('llm_0');
+ expect(node?.type).toBe('llm');
+
+ const nonExistNode = document.getNode('non_exist');
+ expect(nonExistNode).toBeNull();
+ });
+
+ it('should get edge by id', () => {
+ const document = new WorkflowRuntimeDocument();
+ document.init(TestSchemas.basicSchema);
+
+ const edge = document.getEdge('start_0-llm_0');
+ expect(edge).toBeDefined();
+ expect(edge?.id).toBe('start_0-llm_0');
+
+ const nonExistEdge = document.getEdge('non_exist');
+ expect(nonExistEdge).toBeNull();
+ });
+
+ it('should init with two LLM schema', () => {
+ const document = new WorkflowRuntimeDocument();
+ document.init(TestSchemas.twoLLMSchema);
+
+ expect(document.nodes.length).toBeGreaterThan(4); // 包含 root, start, end 和至少两个 LLM 节点
+ expect(document.edges.length).toBeGreaterThan(2); // 至少有 3 条边连接这些节点
+
+ // 验证所有必需的节点都存在
+ expect(document.root).toBeDefined();
+ expect(document.start).toBeDefined();
+ expect(document.end).toBeDefined();
+ });
+});
diff --git a/packages/runtime/js-core/src/domain/document/document/index.ts b/packages/runtime/js-core/src/domain/document/document/index.ts
new file mode 100644
index 00000000..d8efb48a
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/document/document/index.ts
@@ -0,0 +1,72 @@
+import {
+ type WorkflowSchema,
+ FlowGramNode,
+ type IDocument,
+ type IEdge,
+ type INode,
+} from '@flowgram.ai/runtime-interface';
+
+import { uuid } from '@infra/utils';
+import { flatSchema } from './flat-schema';
+import { createStore, DocumentStore } from './create-store';
+
+export class WorkflowRuntimeDocument implements IDocument {
+ public readonly id: string;
+
+ private store: DocumentStore;
+
+ constructor() {
+ this.id = uuid();
+ }
+
+ public get root(): INode {
+ const rootNode = this.getNode(FlowGramNode.Root);
+ if (!rootNode) {
+ throw new Error('Root node not found');
+ }
+ return rootNode;
+ }
+
+ public get start(): INode {
+ const startNode = this.nodes.find((n) => n.type === FlowGramNode.Start);
+ if (!startNode) {
+ throw new Error('Start node not found');
+ }
+ return startNode;
+ }
+
+ public get end(): INode {
+ const endNode = this.nodes.find((n) => n.type === FlowGramNode.End);
+ if (!endNode) {
+ throw new Error('End node not found');
+ }
+ return endNode;
+ }
+
+ public getNode(id: string): INode | null {
+ return this.store.nodes.get(id) ?? null;
+ }
+
+ public getEdge(id: string): IEdge | null {
+ return this.store.edges.get(id) ?? null;
+ }
+
+ public get nodes(): INode[] {
+ return Array.from(this.store.nodes.values());
+ }
+
+ public get edges(): IEdge[] {
+ return Array.from(this.store.edges.values());
+ }
+
+ public init(schema: WorkflowSchema): void {
+ const flattenSchema = flatSchema(schema);
+ this.store = createStore(flattenSchema);
+ }
+
+ public dispose(): void {
+ this.store.edges.clear();
+ this.store.nodes.clear();
+ this.store.ports.clear();
+ }
+}
diff --git a/packages/runtime/js-core/src/domain/document/entity/edge/index.test.ts b/packages/runtime/js-core/src/domain/document/entity/edge/index.test.ts
new file mode 100644
index 00000000..35fe88b2
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/document/entity/edge/index.test.ts
@@ -0,0 +1,98 @@
+import { beforeEach, describe, expect, it } from 'vitest';
+import { WorkflowEdgeSchema, CreateEdgeParams, INode, IPort } from '@flowgram.ai/runtime-interface';
+
+import { WorkflowRuntimeEdge } from '.';
+
+describe('WorkflowRuntimeEdge', () => {
+ let edge: WorkflowRuntimeEdge;
+ let mockFromNode: INode;
+ let mockToNode: INode;
+ let mockParams: CreateEdgeParams;
+
+ beforeEach(() => {
+ mockFromNode = {
+ id: 'from-node',
+ } as INode;
+
+ mockToNode = {
+ id: 'to-node',
+ } as INode;
+
+ mockParams = {
+ id: 'test-edge',
+ from: mockFromNode,
+ to: mockToNode,
+ };
+
+ edge = new WorkflowRuntimeEdge(mockParams);
+ });
+
+ describe('constructor', () => {
+ it('should initialize with provided params', () => {
+ expect(edge.id).toBe(mockParams.id);
+ expect(edge.from).toBe(mockParams.from);
+ expect(edge.to).toBe(mockParams.to);
+ });
+ });
+
+ describe('ports', () => {
+ let mockFromPort: IPort;
+ let mockToPort: IPort;
+
+ beforeEach(() => {
+ mockFromPort = { id: 'from-port' } as IPort;
+ mockToPort = { id: 'to-port' } as IPort;
+ });
+
+ it('should set and get fromPort correctly', () => {
+ edge.fromPort = mockFromPort;
+ expect(edge.fromPort).toBe(mockFromPort);
+ });
+
+ it('should set and get toPort correctly', () => {
+ edge.toPort = mockToPort;
+ expect(edge.toPort).toBe(mockToPort);
+ });
+ });
+
+ describe('createID', () => {
+ it('should create ID with port IDs', () => {
+ const schema: WorkflowEdgeSchema = {
+ sourceNodeID: 'source',
+ sourcePortID: 'sourcePort',
+ targetNodeID: 'target',
+ targetPortID: 'targetPort',
+ };
+
+ const id = WorkflowRuntimeEdge.createID(schema);
+ expect(id).toBe('source:sourcePort-target:targetPort');
+ });
+
+ it('should create ID without port IDs', () => {
+ const schema: WorkflowEdgeSchema = {
+ sourceNodeID: 'source',
+ targetNodeID: 'target',
+ };
+
+ const id = WorkflowRuntimeEdge.createID(schema);
+ expect(id).toBe('source-target');
+ });
+
+ it('should create ID with mixed port IDs', () => {
+ const schemaWithSourcePort: WorkflowEdgeSchema = {
+ sourceNodeID: 'source',
+ sourcePortID: 'sourcePort',
+ targetNodeID: 'target',
+ };
+
+ const schemaWithTargetPort: WorkflowEdgeSchema = {
+ sourceNodeID: 'source',
+ targetNodeID: 'target',
+ targetPortID: 'targetPort',
+ };
+
+ expect(WorkflowRuntimeEdge.createID(schemaWithSourcePort)).toBe('source:sourcePort-target');
+ expect(WorkflowRuntimeEdge.createID(schemaWithTargetPort)).toBe('source-target:targetPort');
+ });
+ });
+});
diff --git a/packages/runtime/js-core/src/domain/document/entity/edge/index.ts b/packages/runtime/js-core/src/domain/document/entity/edge/index.ts
new file mode 100644
index 00000000..d135403e
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/document/entity/edge/index.ts
@@ -0,0 +1,49 @@
+import {
+ WorkflowEdgeSchema,
+ CreateEdgeParams,
+ IEdge,
+ INode,
+ IPort,
+} from '@flowgram.ai/runtime-interface';
+
+export class WorkflowRuntimeEdge implements IEdge {
+ public readonly id: string;
+
+ public readonly from: INode;
+
+ public readonly to: INode;
+
+ private _fromPort: IPort;
+
+ private _toPort: IPort;
+
+ constructor(params: CreateEdgeParams) {
+ const { id, from, to } = params;
+ this.id = id;
+ this.from = from;
+ this.to = to;
+ }
+
+ public get fromPort() {
+ return this._fromPort;
+ }
+
+ public set fromPort(port: IPort) {
+ this._fromPort = port;
+ }
+
+ public get toPort() {
+ return this._toPort;
+ }
+
+ public set toPort(port: IPort) {
+ this._toPort = port;
+ }
+
+ public static createID(schema: WorkflowEdgeSchema): string {
+ const { sourceNodeID, sourcePortID, targetNodeID, targetPortID } = schema;
+ const sourcePart = sourcePortID ? `${sourceNodeID}:${sourcePortID}` : sourceNodeID;
+ const targetPart = targetPortID ? `${targetNodeID}:${targetPortID}` : targetNodeID;
+ return `${sourcePart}-${targetPart}`;
+ }
+}
diff --git a/packages/runtime/js-core/src/domain/document/entity/index.ts b/packages/runtime/js-core/src/domain/document/entity/index.ts
new file mode 100644
index 00000000..e2556c36
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/document/entity/index.ts
@@ -0,0 +1,3 @@
+export { WorkflowRuntimeEdge } from './edge';
+export { WorkflowRuntimeNode } from './node';
+export { WorkflowRuntimePort } from './port';
diff --git a/packages/runtime/js-core/src/domain/document/entity/node/index.test.ts b/packages/runtime/js-core/src/domain/document/entity/node/index.test.ts
new file mode 100644
index 00000000..61c98b7a
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/document/entity/node/index.test.ts
@@ -0,0 +1,152 @@
+import { beforeEach, describe, expect, it } from 'vitest';
+import {
+ FlowGramNode,
+ PositionSchema,
+ WorkflowPortType,
+ CreateNodeParams,
+ IEdge,
+ INode,
+ IPort,
+} from '@flowgram.ai/runtime-interface';
+
+import { WorkflowRuntimeNode } from './index';
+
+describe('WorkflowRuntimeNode', () => {
+ let node: WorkflowRuntimeNode;
+ let mockParams: CreateNodeParams;
+
+ beforeEach(() => {
+ mockParams = {
+ id: 'test-node',
+ type: FlowGramNode.Start,
+ name: 'Test Node',
+ position: { x: 0, y: 0 } as PositionSchema,
+ variable: {},
+ data: { testData: 'data' },
+ };
+ node = new WorkflowRuntimeNode(mockParams);
+ });
+
+ describe('constructor', () => {
+ it('should initialize with provided params', () => {
+ expect(node.id).toBe(mockParams.id);
+ expect(node.type).toBe(mockParams.type);
+ expect(node.name).toBe(mockParams.name);
+ expect(node.position).toBe(mockParams.position);
+ expect(node.declare).toEqual(mockParams.variable);
+ expect(node.data).toEqual(mockParams.data);
+ });
+
+ it('should initialize with default values when optional params are not provided', () => {
+ const minimalParams = {
+ id: 'test-node',
+ type: FlowGramNode.Start,
+ name: 'Test Node',
+ position: { x: 0, y: 0 } as PositionSchema,
+ };
+ const minimalNode = new WorkflowRuntimeNode(minimalParams);
+ expect(minimalNode.declare).toEqual({});
+ expect(minimalNode.data).toEqual({});
+ });
+ });
+
+ describe('ports', () => {
+ let inputPort: IPort;
+ let outputPort: IPort;
+
+ beforeEach(() => {
+ inputPort = { id: 'input-1', type: WorkflowPortType.Input, node, edges: [] };
+ outputPort = { id: 'output-1', type: WorkflowPortType.Output, node, edges: [] };
+ node.addPort(inputPort);
+ node.addPort(outputPort);
+ });
+
+ it('should correctly categorize input and output ports', () => {
+ const { inputs, outputs } = node.ports;
+ expect(inputs).toHaveLength(1);
+ expect(outputs).toHaveLength(1);
+ expect(inputs[0]).toBe(inputPort);
+ expect(outputs[0]).toBe(outputPort);
+ });
+ });
+
+ describe('edges', () => {
+ let inputEdge: IEdge;
+ let outputEdge: IEdge;
+ let fromNode: INode;
+ let toNode: INode;
+
+ beforeEach(() => {
+ fromNode = new WorkflowRuntimeNode({ ...mockParams, id: 'from-node' });
+ toNode = new WorkflowRuntimeNode({ ...mockParams, id: 'to-node' });
+ inputEdge = {
+ id: 'input-edge',
+ from: fromNode,
+ to: node,
+ fromPort: {} as IPort,
+ toPort: {} as IPort,
+ };
+ outputEdge = {
+ id: 'output-edge',
+ from: node,
+ to: toNode,
+ fromPort: {} as IPort,
+ toPort: {} as IPort,
+ };
+ node.addInputEdge(inputEdge);
+ node.addOutputEdge(outputEdge);
+ });
+
+ it('should correctly store input and output edges', () => {
+ const { inputs, outputs } = node.edges;
+ expect(inputs).toHaveLength(1);
+ expect(outputs).toHaveLength(1);
+ expect(inputs[0]).toBe(inputEdge);
+ expect(outputs[0]).toBe(outputEdge);
+ });
+
+ it('should update prev and next nodes when adding edges', () => {
+ expect(node.prev).toHaveLength(1);
+ expect(node.next).toHaveLength(1);
+ expect(node.prev[0]).toBe(fromNode);
+ expect(node.next[0]).toBe(toNode);
+ });
+ });
+
+ describe('parent and children', () => {
+ let parentNode: INode;
+ let childNode: INode;
+
+ beforeEach(() => {
+ parentNode = new WorkflowRuntimeNode({ ...mockParams, id: 'parent-node' });
+ childNode = new WorkflowRuntimeNode({ ...mockParams, id: 'child-node' });
+ });
+
+ it('should handle parent-child relationships', () => {
+ node.parent = parentNode;
+ node.addChild(childNode);
+
+ expect(node.parent).toBe(parentNode);
+ expect(node.children).toHaveLength(1);
+ expect(node.children[0]).toBe(childNode);
+ });
+ });
+
+ describe('isBranch', () => {
+ it('should return true when node has multiple output ports', () => {
+ const outputPort1 = { id: 'output-1', type: WorkflowPortType.Output, node, edges: [] };
+ const outputPort2 = { id: 'output-2', type: WorkflowPortType.Output, node, edges: [] };
+ node.addPort(outputPort1);
+ node.addPort(outputPort2);
+
+ expect(node.isBranch).toBe(true);
+ });
+
+ it('should return false when node has one or zero output ports', () => {
+ const outputPort = { id: 'output-1', type: WorkflowPortType.Output, node, edges: [] };
+ node.addPort(outputPort);
+
+ expect(node.isBranch).toBe(false);
+ });
+ });
+});
diff --git a/packages/runtime/js-core/src/domain/document/entity/node/index.ts b/packages/runtime/js-core/src/domain/document/entity/node/index.ts
new file mode 100644
index 00000000..b2329c1e
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/document/entity/node/index.ts
@@ -0,0 +1,113 @@
+import {
+ FlowGramNode,
+ PositionSchema,
+ CreateNodeParams,
+ IEdge,
+ INode,
+ IPort,
+ NodeVariable,
+ WorkflowPortType,
+} from '@flowgram.ai/runtime-interface';
+
+export class WorkflowRuntimeNode implements INode {
+ public readonly id: string;
+
+ public readonly type: FlowGramNode;
+
+ public readonly name: string;
+
+ public readonly position: PositionSchema;
+
+ public readonly declare: NodeVariable;
+
+ public readonly data: T;
+
+ private _parent: INode | null;
+
+ private readonly _children: INode[];
+
+ private readonly _ports: IPort[];
+
+ private readonly _inputEdges: IEdge[];
+
+ private readonly _outputEdges: IEdge[];
+
+ private readonly _prev: INode[];
+
+ private readonly _next: INode[];
+
+ constructor(params: CreateNodeParams) {
+ const { id, type, name, position, variable, data } = params;
+ this.id = id;
+ this.type = type;
+ this.name = name;
+ this.position = position;
+ this.declare = variable ?? {};
+ this.data = data ?? {};
+ this._parent = null;
+ this._children = [];
+ this._ports = [];
+ this._inputEdges = [];
+ this._outputEdges = [];
+ this._prev = [];
+ this._next = [];
+ }
+
+ public get ports() {
+ const inputs = this._ports.filter((port) => port.type === WorkflowPortType.Input);
+ const outputs = this._ports.filter((port) => port.type === WorkflowPortType.Output);
+ return {
+ inputs,
+ outputs,
+ };
+ }
+
+ public get edges() {
+ return {
+ inputs: this._inputEdges,
+ outputs: this._outputEdges,
+ };
+ }
+
+ public get parent() {
+ return this._parent;
+ }
+
+ public set parent(parent: INode | null) {
+ this._parent = parent;
+ }
+
+ public get children() {
+ return this._children;
+ }
+
+ public addChild(child: INode) {
+ this._children.push(child);
+ }
+
+ public addPort(port: IPort) {
+ this._ports.push(port);
+ }
+
+ public addInputEdge(edge: IEdge) {
+ this._inputEdges.push(edge);
+ this._prev.push(edge.from);
+ }
+
+ public addOutputEdge(edge: IEdge) {
+ this._outputEdges.push(edge);
+ this._next.push(edge.to);
+ }
+
+ public get prev() {
+ return this._prev;
+ }
+
+ public get next() {
+ return this._next;
+ }
+
+ public get isBranch() {
+ return this.ports.outputs.length > 1;
+ }
+}
diff --git a/packages/runtime/js-core/src/domain/document/entity/port/index.test.ts b/packages/runtime/js-core/src/domain/document/entity/port/index.test.ts
new file mode 100644
index 00000000..dec6b4ab
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/document/entity/port/index.test.ts
@@ -0,0 +1,74 @@
+import { beforeEach, describe, expect, it } from 'vitest';
+import { WorkflowPortType, CreatePortParams, IEdge, INode } from '@flowgram.ai/runtime-interface';
+
+import { WorkflowRuntimePort } from '.';
+
+describe('WorkflowRuntimePort', () => {
+ let port: WorkflowRuntimePort;
+ let mockNode: INode;
+ let mockParams: CreatePortParams;
+
+ beforeEach(() => {
+ mockNode = {
+ id: 'test-node',
+ } as INode;
+
+ mockParams = {
+ id: 'test-port',
+ node: mockNode,
+ type: WorkflowPortType.Input,
+ };
+
+ port = new WorkflowRuntimePort(mockParams);
+ });
+
+ describe('constructor', () => {
+ it('should initialize with provided params', () => {
+ expect(port.id).toBe(mockParams.id);
+ expect(port.node).toBe(mockParams.node);
+ expect(port.type).toBe(mockParams.type);
+ expect(port.edges).toEqual([]);
+ });
+
+ it('should initialize with different port types', () => {
+ const inputPort = new WorkflowRuntimePort({
+ ...mockParams,
+ type: WorkflowPortType.Input,
+ });
+ expect(inputPort.type).toBe(WorkflowPortType.Input);
+
+ const outputPort = new WorkflowRuntimePort({
+ ...mockParams,
+ type: WorkflowPortType.Output,
+ });
+ expect(outputPort.type).toBe(WorkflowPortType.Output);
+ });
+ });
+
+ describe('edges management', () => {
+ it('should add edge correctly', () => {
+ const mockEdge: IEdge = {
+ id: 'test-edge',
+ } as IEdge;
+
+ port.addEdge(mockEdge);
+ expect(port.edges).toHaveLength(1);
+ expect(port.edges[0]).toBe(mockEdge);
+ });
+
+ it('should maintain edge order when adding multiple edges', () => {
+ const mockEdge1: IEdge = { id: 'edge-1' } as IEdge;
+ const mockEdge2: IEdge = { id: 'edge-2' } as IEdge;
+ const mockEdge3: IEdge = { id: 'edge-3' } as IEdge;
+
+ port.addEdge(mockEdge1);
+ port.addEdge(mockEdge2);
+ port.addEdge(mockEdge3);
+
+ expect(port.edges).toHaveLength(3);
+ expect(port.edges[0]).toBe(mockEdge1);
+ expect(port.edges[1]).toBe(mockEdge2);
+ expect(port.edges[2]).toBe(mockEdge3);
+ });
+ });
+});
diff --git a/packages/runtime/js-core/src/domain/document/entity/port/index.ts b/packages/runtime/js-core/src/domain/document/entity/port/index.ts
new file mode 100644
index 00000000..d3a23e97
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/document/entity/port/index.ts
@@ -0,0 +1,33 @@
+import {
+ WorkflowPortType,
+ CreatePortParams,
+ IEdge,
+ INode,
+ IPort,
+} from '@flowgram.ai/runtime-interface';
+
+export class WorkflowRuntimePort implements IPort {
+ public readonly id: string;
+
+ public readonly node: INode;
+
+ public readonly type: WorkflowPortType;
+
+ private readonly _edges: IEdge[];
+
+ constructor(params: CreatePortParams) {
+ const { id, node } = params;
+ this.id = id;
+ this.node = node;
+ this.type = params.type;
+ this._edges = [];
+ }
+
+ public get edges() {
+ return this._edges;
+ }
+
+ public addEdge(edge: IEdge) {
+ this._edges.push(edge);
+ }
+}
diff --git a/packages/runtime/js-core/src/domain/document/index.ts b/packages/runtime/js-core/src/domain/document/index.ts
new file mode 100644
index 00000000..a14cb15d
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/document/index.ts
@@ -0,0 +1 @@
+export { WorkflowRuntimeDocument } from './document';
diff --git a/packages/runtime/js-core/src/domain/engine/index.test.ts b/packages/runtime/js-core/src/domain/engine/index.test.ts
new file mode 100644
index 00000000..dead56c8
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/engine/index.test.ts
@@ -0,0 +1,53 @@
+import { beforeEach, describe, expect, it } from 'vitest';
+
+import { TestSchemas } from '@workflow/__tests__/schemas';
+import { MockWorkflowRuntimeNodeExecutors } from '@workflow/__tests__/executor';
+import { WorkflowRuntimeExecutor } from '../executor';
+import { WorkflowRuntimeEngine } from './index';
+
+let engine: WorkflowRuntimeEngine;
+
+beforeEach(() => {
+ const Executor = new WorkflowRuntimeExecutor(MockWorkflowRuntimeNodeExecutors);
+ engine = new WorkflowRuntimeEngine({
+ Executor,
+ });
+});
+
+describe('WorkflowRuntimeEngine', () => {
+ it('should create a WorkflowRuntimeEngine', () => {
+ expect(engine).toBeDefined();
+ });
+
+ it('should execute a workflow with input', async () => {
+ const { processing } = engine.invoke({
+ schema: TestSchemas.basicSchema,
+ inputs: {
+ model_name: 'ai-model',
+ llm_settings: {
+ temperature: 0.5,
+ },
+ prompt: 'How are you?',
+ },
+ });
+ const result = await processing;
+ expect(result).toStrictEqual({
+ llm_res: `Hi, I'm an AI assistant, my name is ai-model, temperature is 0.5, system prompt is "You are a helpful AI assistant.", prompt is "How are you?"`,
+ llm_prompt: 'How are you?',
+ });
+ });
+
+ it('should execute a workflow with branch', async () => {
+ const { processing } = engine.invoke({
+ schema: TestSchemas.branchSchema,
+ inputs: {
+ model_id: 1,
+ prompt: 'Tell me a joke',
+ },
+ });
+ const result = await processing;
+ expect(result).toStrictEqual({
+ m1_res: `Hi, I'm an AI assistant, my name is AI_MODEL_1, temperature is 0.5, system prompt is "I'm Model 1.", prompt is "Tell me a joke"`,
+ });
+ });
+});
diff --git a/packages/runtime/js-core/src/domain/engine/index.ts b/packages/runtime/js-core/src/domain/engine/index.ts
new file mode 100644
index 00000000..0fce5c33
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/engine/index.ts
@@ -0,0 +1,133 @@
+import {
+ EngineServices,
+ IEngine,
+ IExecutor,
+ INode,
+ WorkflowOutputs,
+ IContext,
+ InvokeParams,
+ ITask,
+ FlowGramNode,
+} from '@flowgram.ai/runtime-interface';
+
+import { WorkflowRuntimeTask } from '../task';
+import { WorkflowRuntimeContext } from '../context';
+import { WorkflowRuntimeContainer } from '../container';
+
+export class WorkflowRuntimeEngine implements IEngine {
+ private readonly executor: IExecutor;
+
+ constructor(service: EngineServices) {
+ this.executor = service.Executor;
+ }
+
+ public invoke(params: InvokeParams): ITask {
+ const context = WorkflowRuntimeContext.create();
+ context.init(params);
+ const processing = this.process(context);
+ processing.then(() => {
+ context.dispose();
+ });
+ return WorkflowRuntimeTask.create({
+ processing,
+ context,
+ });
+ }
+
+ public async executeNode(params: { context: IContext; node: INode }) {
+ const { node, context } = params;
+ if (!this.canExecuteNode({ node, context })) {
+ return;
+ }
+ context.statusCenter.nodeStatus(node.id).process();
+ try {
+ const inputs = context.state.getNodeInputs(node);
+ const snapshot = context.snapshotCenter.create({
+ nodeID: node.id,
+ data: node.data,
+ inputs,
+ });
+ const result = await this.executor.execute({
+ node,
+ inputs,
+ runtime: context,
+ container: WorkflowRuntimeContainer.instance,
+ });
+ if (context.statusCenter.workflow.terminated) {
+ return;
+ }
+ const { outputs, branch } = result;
+ snapshot.addData({ outputs, branch });
+ context.state.setNodeOutputs({ node, outputs });
+ context.state.addExecutedNode(node);
+ context.statusCenter.nodeStatus(node.id).success();
+ const nextNodes = this.getNextNodes({ node, branch, context });
+ await this.executeNext({ node, nextNodes, context });
+ } catch (e) {
+ context.statusCenter.nodeStatus(node.id).fail();
+ console.error(e);
+ return;
+ }
+ }
+
+ private async process(context: IContext): Promise {
+ const startNode = context.document.start;
+ context.statusCenter.workflow.process();
+ try {
+ await this.executeNode({ node: startNode, context });
+ const outputs = context.ioCenter.outputs;
+ context.statusCenter.workflow.success();
+ return outputs;
+ } catch (e) {
+ context.statusCenter.workflow.fail();
+ throw e;
+ }
+ }
+
+ private canExecuteNode(params: { context: IContext; node: INode }) {
+ const { node, context } = params;
+ const prevNodes = node.prev;
+ if (prevNodes.length === 0) {
+ return true;
+ }
+ return prevNodes.every((prevNode) => context.state.isExecutedNode(prevNode));
+ }
+
+ private getNextNodes(params: { context: IContext; node: INode; branch?: string }) {
+ const { node, branch, context } = params;
+ const allNextNodes = node.next;
+ if (!branch) {
+ return allNextNodes;
+ }
+ const targetPort = node.ports.outputs.find((port) => port.id === branch);
+ if (!targetPort) {
+ throw new Error(`branch ${branch} not found`);
+ }
+ const nextNodeIDs: Set = new Set(targetPort.edges.map((edge) => edge.to.id));
+ const nextNodes = allNextNodes.filter((nextNode) => nextNodeIDs.has(nextNode.id));
+ const skipNodes = allNextNodes.filter((nextNode) => !nextNodeIDs.has(nextNode.id));
+ skipNodes.forEach((skipNode) => {
+ context.state.addExecutedNode(skipNode);
+ });
+ return nextNodes;
+ }
+
+ private async executeNext(params: { context: IContext; node: INode; nextNodes: INode[] }) {
+ const { context, node, nextNodes } = params;
+ if (node.type === FlowGramNode.End) {
+ return;
+ }
+ if (nextNodes.length === 0) {
+ // throw new Error(`node ${node.id} has no next nodes`); // inside loop node may have no next nodes
+ return;
+ }
+ await Promise.all(
+ nextNodes.map((nextNode) =>
+ this.executeNode({
+ node: nextNode,
+ context,
+ })
+ )
+ );
+ }
+}
diff --git a/packages/runtime/js-core/src/domain/executor/index.ts b/packages/runtime/js-core/src/domain/executor/index.ts
new file mode 100644
index 00000000..e5c46518
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/executor/index.ts
@@ -0,0 +1,33 @@
+import { FlowGramNode } from '@flowgram.ai/runtime-interface';
+import {
+ ExecutionContext,
+ ExecutionResult,
+ IExecutor,
+ INodeExecutor,
+ INodeExecutorFactory,
+} from '@flowgram.ai/runtime-interface';
+
+export class WorkflowRuntimeExecutor implements IExecutor {
+ private nodeExecutors: Map = new Map();
+
+ constructor(nodeExecutors: INodeExecutorFactory[]) {
+ // register node executors
+ nodeExecutors.forEach((executor) => {
+ this.register(new executor());
+ });
+ }
+
+ public register(executor: INodeExecutor): void {
+ this.nodeExecutors.set(executor.type, executor);
+ }
+
+ public async execute(context: ExecutionContext): Promise {
+ const nodeType = context.node.type;
+ const nodeExecutor = this.nodeExecutors.get(nodeType);
+ if (!nodeExecutor) {
+ throw new Error(`no executor found for node type ${nodeType}`);
+ }
+ const output = await nodeExecutor.execute(context);
+ return output;
+ }
+}
diff --git a/packages/runtime/js-core/src/domain/io-center/index.ts b/packages/runtime/js-core/src/domain/io-center/index.ts
new file mode 100644
index 00000000..316f99d0
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/io-center/index.ts
@@ -0,0 +1,36 @@
+import { IIOCenter, IOData, WorkflowInputs, WorkflowOutputs } from '@flowgram.ai/runtime-interface';
+
+export class WorkflowRuntimeIOCenter implements IIOCenter {
+ private _inputs: WorkflowInputs;
+
+ private _outputs: WorkflowOutputs;
+
+ public init(inputs: WorkflowInputs): void {
+ this.setInputs(inputs);
+ }
+
+ public dispose(): void {}
+
+ public get inputs(): WorkflowInputs {
+ return this._inputs ?? {};
+ }
+
+ public get outputs(): WorkflowOutputs {
+ return this._outputs ?? {};
+ }
+
+ public setInputs(inputs: WorkflowInputs): void {
+ this._inputs = inputs;
+ }
+
+ public setOutputs(outputs: WorkflowOutputs): void {
+ this._outputs = outputs;
+ }
+
+ public export(): IOData {
+ return {
+ inputs: this._inputs,
+ outputs: this._outputs,
+ };
+ }
+}
diff --git a/packages/runtime/js-core/src/domain/report/index.ts b/packages/runtime/js-core/src/domain/report/index.ts
new file mode 100644
index 00000000..de1b3c42
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/report/index.ts
@@ -0,0 +1 @@
+export { WorkflowRuntimeReporter } from './reporter';
diff --git a/packages/runtime/js-core/src/domain/report/report-value-object/index.ts b/packages/runtime/js-core/src/domain/report/report-value-object/index.ts
new file mode 100644
index 00000000..879faa52
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/report/report-value-object/index.ts
@@ -0,0 +1,10 @@
+import { IReport, VOData } from '@flowgram.ai/runtime-interface';
+
+import { uuid } from '@infra/utils';
+
+export namespace WorkflowRuntimeReport {
+ export const create = (params: VOData): IReport => ({
+ id: uuid(),
+ ...params,
+ });
+}
diff --git a/packages/runtime/js-core/src/domain/report/reporter/index.ts b/packages/runtime/js-core/src/domain/report/reporter/index.ts
new file mode 100644
index 00000000..5d901f42
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/report/reporter/index.ts
@@ -0,0 +1,49 @@
+import {
+ ISnapshotCenter,
+ IReporter,
+ IStatusCenter,
+ IIOCenter,
+ IReport,
+ NodeReport,
+} from '@flowgram.ai/runtime-interface';
+
+import { WorkflowRuntimeReport } from '../report-value-object';
+
+export class WorkflowRuntimeReporter implements IReporter {
+ constructor(
+ public readonly ioCenter: IIOCenter,
+ public readonly snapshotCenter: ISnapshotCenter,
+ public readonly statusCenter: IStatusCenter
+ ) {}
+
+ public init(): void {}
+
+ public dispose(): void {}
+
+ public export(): IReport {
+ const report = WorkflowRuntimeReport.create({
+ inputs: this.ioCenter.inputs,
+ outputs: this.ioCenter.outputs,
+ workflowStatus: this.statusCenter.workflow.export(),
+ reports: this.nodeReports(),
+ });
+ return report;
+ }
+
+ private nodeReports(): Record {
+ const reports: Record = {};
+ const statuses = this.statusCenter.exportNodeStatus();
+ const snapshots = this.snapshotCenter.export();
+ Object.keys(statuses).forEach((nodeID) => {
+ const status = statuses[nodeID];
+ const nodeSnapshots = snapshots[nodeID] || [];
+ const nodeReport: NodeReport = {
+ id: nodeID,
+ ...status,
+ snapshots: nodeSnapshots,
+ };
+ reports[nodeID] = nodeReport;
+ });
+ return reports;
+ }
+}
diff --git a/packages/runtime/js-core/src/domain/snapshot/index.ts b/packages/runtime/js-core/src/domain/snapshot/index.ts
new file mode 100644
index 00000000..2b8c57af
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/snapshot/index.ts
@@ -0,0 +1 @@
+export { WorkflowRuntimeSnapshotCenter } from './snapshot-center';
diff --git a/packages/runtime/js-core/src/domain/snapshot/snapshot-center/index.ts b/packages/runtime/js-core/src/domain/snapshot/snapshot-center/index.ts
new file mode 100644
index 00000000..43480bee
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/snapshot/snapshot-center/index.ts
@@ -0,0 +1,44 @@
+import { Snapshot, ISnapshotCenter, SnapshotData, ISnapshot } from '@flowgram.ai/runtime-interface';
+
+import { uuid } from '@infra/utils';
+import { WorkflowRuntimeSnapshot } from '../snapshot-entity';
+
+export class WorkflowRuntimeSnapshotCenter implements ISnapshotCenter {
+ public readonly id: string;
+
+ private snapshots: ISnapshot[];
+
+ constructor() {
+ this.id = uuid();
+ }
+
+ public create(snapshotData: Partial): ISnapshot {
+ const snapshot = WorkflowRuntimeSnapshot.create(snapshotData);
+ this.snapshots.push(snapshot);
+ return snapshot;
+ }
+
+ public init(): void {
+ this.snapshots = [];
+ }
+
+ public dispose(): void {
+ // because the data is not persisted, do not clear the execution result
+ }
+
+ public exportAll(): Snapshot[] {
+ return this.snapshots.slice().map((snapshot) => snapshot.export());
+ }
+
+ public export(): Record {
+ const result: Record = {};
+ this.exportAll().forEach((snapshot) => {
+ if (result[snapshot.nodeID]) {
+ result[snapshot.nodeID].push(snapshot);
+ } else {
+ result[snapshot.nodeID] = [snapshot];
+ }
+ });
+ return result;
+ }
+}
diff --git a/packages/runtime/js-core/src/domain/snapshot/snapshot-entity/index.ts b/packages/runtime/js-core/src/domain/snapshot/snapshot-entity/index.ts
new file mode 100644
index 00000000..f6d403c9
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/snapshot/snapshot-entity/index.ts
@@ -0,0 +1,35 @@
+import { ISnapshot, Snapshot, SnapshotData } from '@flowgram.ai/runtime-interface';
+
+import { uuid } from '@infra/utils';
+
+export class WorkflowRuntimeSnapshot implements ISnapshot {
+ public readonly id: string;
+
+ public readonly data: Partial;
+
+ public constructor(data: Partial) {
+ this.id = uuid();
+ this.data = data;
+ }
+
+ public addData(data: Partial): void {
+ Object.assign(this.data, data);
+ }
+
+ public validate(): boolean {
+ const required = ['nodeID', 'inputs', 'outputs', 'data'] as (keyof SnapshotData)[];
+ return required.every((key) => this.data[key] !== undefined);
+ }
+
+ public export(): Snapshot {
+ const snapshot: Snapshot = {
+ id: this.id,
+ ...this.data,
+ } as Snapshot;
+ return snapshot;
+ }
+
+ public static create(params: Partial): ISnapshot {
+ return new WorkflowRuntimeSnapshot(params);
+ }
+}
diff --git a/packages/runtime/js-core/src/domain/state/index.ts b/packages/runtime/js-core/src/domain/state/index.ts
new file mode 100644
index 00000000..0b1af1dd
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/state/index.ts
@@ -0,0 +1,134 @@
+import { isNil } from 'lodash-es';
+import {
+ IState,
+ IFlowConstantRefValue,
+ IFlowRefValue,
+ IVariableParseResult,
+ INode,
+ WorkflowInputs,
+ WorkflowOutputs,
+ IVariableStore,
+ WorkflowVariableType,
+} from '@flowgram.ai/runtime-interface';
+
+import { uuid, WorkflowRuntimeType } from '@infra/utils';
+
+export class WorkflowRuntimeState implements IState {
+ public readonly id: string;
+
+ private executedNodes: Set;
+
+ constructor(public readonly variableStore: IVariableStore) {
+ this.id = uuid();
+ }
+
+ public init(): void {
+ this.executedNodes = new Set();
+ }
+
+ public dispose(): void {
+ this.executedNodes.clear();
+ }
+
+ public getNodeInputs(node: INode): WorkflowInputs {
+ const inputsDeclare = node.declare.inputs;
+ const inputsValues = node.declare.inputsValues;
+ if (!inputsDeclare || !inputsValues) {
+ return {};
+ }
+ return Object.entries(inputsValues).reduce((prev, [key, inputValue]) => {
+ const typeInfo = inputsDeclare.properties?.[key];
+ if (!typeInfo) {
+ return prev;
+ }
+ const expectType = typeInfo.type as WorkflowVariableType;
+ // get value
+ const result = this.parseValue(inputValue);
+ if (!result) {
+ return prev;
+ }
+ const { value, type } = result;
+ if (!WorkflowRuntimeType.isTypeEqual(type, expectType)) {
+ return prev;
+ }
+ prev[key] = value;
+ return prev;
+ }, {} as WorkflowInputs);
+ }
+
+ public setNodeOutputs(params: { node: INode; outputs: WorkflowOutputs }): void {
+ const { node, outputs } = params;
+ const outputsDeclare = node.declare.outputs;
+ // TODO validation service type check, deeply compare input & schema
+ if (!outputsDeclare) {
+ return;
+ }
+ Object.entries(outputs).forEach(([key, value]) => {
+ const typeInfo = outputsDeclare.properties?.[key];
+ if (!typeInfo) {
+ return;
+ }
+ const type = typeInfo.type as WorkflowVariableType;
+ const itemsType = typeInfo.items?.type as WorkflowVariableType;
+ // create variable
+ this.variableStore.setVariable({
+ nodeID: node.id,
+ key,
+ value,
+ type,
+ itemsType,
+ });
+ });
+ }
+
+ public parseRef(ref: IFlowRefValue): IVariableParseResult | null {
+ if (ref?.type !== 'ref') {
+ throw new Error(`invalid ref value: ${ref}`);
+ }
+ if (!ref.content || ref.content.length < 2) {
+ return null;
+ }
+ const [nodeID, variableKey, ...variablePath] = ref.content;
+ const result = this.variableStore.getValue({
+ nodeID,
+ variableKey,
+ variablePath,
+ });
+ if (!result) {
+ return null;
+ }
+ return result;
+ }
+
+ public parseValue(flowValue: IFlowConstantRefValue): IVariableParseResult | null {
+ if (!flowValue?.type) {
+ throw new Error(`invalid flow value type: ${(flowValue as any).type}`);
+ }
+ // constant
+ if (flowValue.type === 'constant') {
+ const value = flowValue.content as T;
+ const type = WorkflowRuntimeType.getWorkflowType(value);
+ if (isNil(value) || !type) {
+ return null;
+ }
+ return {
+ value,
+ type,
+ };
+ }
+ // ref
+ if (flowValue.type === 'ref') {
+ return this.parseRef(flowValue);
+ }
+ // unknown type
+ throw new Error(`unknown flow value type: ${(flowValue as any).type}`);
+ }
+
+ public isExecutedNode(node: INode): boolean {
+ return this.executedNodes.has(node.id);
+ }
+
+ public addExecutedNode(node: INode): void {
+ this.executedNodes.add(node.id);
+ }
+}
diff --git a/packages/runtime/js-core/src/domain/status/index.ts b/packages/runtime/js-core/src/domain/status/index.ts
new file mode 100644
index 00000000..c0db0c02
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/status/index.ts
@@ -0,0 +1 @@
+export { WorkflowRuntimeStatusCenter } from './status-center';
diff --git a/packages/runtime/js-core/src/domain/status/status-center/index.ts b/packages/runtime/js-core/src/domain/status/status-center/index.ts
new file mode 100644
index 00000000..3c9a693b
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/status/status-center/index.ts
@@ -0,0 +1,50 @@
+import { IStatus, IStatusCenter, StatusData, WorkflowStatus } from '@flowgram.ai/runtime-interface';
+
+import { WorkflowRuntimeStatus } from '../status-entity';
+
+export class WorkflowRuntimeStatusCenter implements IStatusCenter {
+ private _workflowStatus: IStatus;
+
+ private _nodeStatus: Map;
+
+ public startTime: number;
+
+ public endTime?: number;
+
+ public init(): void {
+ this._workflowStatus = WorkflowRuntimeStatus.create();
+ this._nodeStatus = new Map();
+ }
+
+ public dispose(): void {
+ // because the data is not persisted, do not clear the execution result
+ }
+
+ public get workflow(): IStatus {
+ return this._workflowStatus;
+ }
+
+ public get workflowStatus(): IStatus {
+ return this._workflowStatus;
+ }
+
+ public nodeStatus(nodeID: string): IStatus {
+ if (!this._nodeStatus.has(nodeID)) {
+ this._nodeStatus.set(nodeID, WorkflowRuntimeStatus.create());
+ }
+ const status = this._nodeStatus.get(nodeID)!;
+ return status;
+ }
+
+ public getStatusNodeIDs(status: WorkflowStatus): string[] {
+ return Array.from(this._nodeStatus.entries())
+ .filter(([, nodeStatus]) => nodeStatus.status === status)
+ .map(([nodeID]) => nodeID);
+ }
+
+ public exportNodeStatus(): Record {
+ return Object.fromEntries(
+ Array.from(this._nodeStatus.entries()).map(([nodeID, status]) => [nodeID, status.export()])
+ );
+ }
+}
diff --git a/packages/runtime/js-core/src/domain/status/status-entity/index.ts b/packages/runtime/js-core/src/domain/status/status-entity/index.ts
new file mode 100644
index 00000000..27edc535
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/status/status-entity/index.ts
@@ -0,0 +1,91 @@
+import { IStatus, StatusData, WorkflowStatus } from '@flowgram.ai/runtime-interface';
+
+import { uuid } from '@infra/utils';
+
+export class WorkflowRuntimeStatus implements IStatus {
+ public readonly id: string;
+
+ private _status: WorkflowStatus;
+
+ private _startTime: number;
+
+ private _endTime?: number;
+
+ constructor() {
+ this.id = uuid();
+ this._status = WorkflowStatus.Pending;
+ }
+
+ public get status(): WorkflowStatus {
+ return this._status;
+ }
+
+ public get terminated(): boolean {
+ return [WorkflowStatus.Succeeded, WorkflowStatus.Failed, WorkflowStatus.Canceled].includes(
+ this.status
+ );
+ }
+
+ public get startTime(): number {
+ return this._startTime;
+ }
+
+ public get endTime(): number | undefined {
+ return this._endTime;
+ }
+
+ public get timeCost(): number {
+ if (!this.startTime) {
+ return 0;
+ }
+ if (this.endTime) {
+ return this.endTime - this.startTime;
+ }
+ return Date.now() - this.startTime;
+ }
+
+ public process(): void {
+ this._status = WorkflowStatus.Processing;
+ this._startTime = Date.now();
+ this._endTime = undefined;
+ }
+
+ public success(): void {
+ if (this.terminated) {
+ return;
+ }
+ this._status = WorkflowStatus.Succeeded;
+ this._endTime = Date.now();
+ }
+
+ public fail(): void {
+ if (this.terminated) {
+ return;
+ }
+ this._status = WorkflowStatus.Failed;
+ this._endTime = Date.now();
+ }
+
+ public cancel(): void {
+ if (this.terminated) {
+ return;
+ }
+ this._status = WorkflowStatus.Canceled;
+ this._endTime = Date.now();
+ }
+
+ public export(): StatusData {
+ return {
+ status: this.status,
+ terminated: this.terminated,
+ startTime: this.startTime,
+ endTime: this.endTime,
+ timeCost: this.timeCost,
+ };
+ }
+
+ public static create(): WorkflowRuntimeStatus {
+ const status = new WorkflowRuntimeStatus();
+ return status;
+ }
+}
diff --git a/packages/runtime/js-core/src/domain/task/index.ts b/packages/runtime/js-core/src/domain/task/index.ts
new file mode 100644
index 00000000..de07a7f6
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/task/index.ts
@@ -0,0 +1,35 @@
+import {
+ IContext,
+ ITask,
+ TaskParams,
+ WorkflowOutputs,
+ WorkflowStatus,
+} from '@flowgram.ai/runtime-interface';
+
+import { uuid } from '@infra/utils';
+
+export class WorkflowRuntimeTask implements ITask {
+ public readonly id: string;
+
+ public readonly processing: Promise;
+
+ public readonly context: IContext;
+
+ constructor(params: TaskParams) {
+ this.id = uuid();
+ this.context = params.context;
+ this.processing = params.processing;
+ }
+
+ public cancel(): void {
+ this.context.statusCenter.workflow.cancel();
+ const cancelNodeIDs = this.context.statusCenter.getStatusNodeIDs(WorkflowStatus.Processing);
+ cancelNodeIDs.forEach((nodeID) => {
+ this.context.statusCenter.nodeStatus(nodeID).cancel();
+ });
+ }
+
+ public static create(params: TaskParams): WorkflowRuntimeTask {
+ return new WorkflowRuntimeTask(params);
+ }
+}
diff --git a/packages/runtime/js-core/src/domain/validation/index.ts b/packages/runtime/js-core/src/domain/validation/index.ts
new file mode 100644
index 00000000..c3c64a79
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/validation/index.ts
@@ -0,0 +1,18 @@
+import { WorkflowSchema, IValidation, ValidationResult } from '@flowgram.ai/runtime-interface';
+
+export class WorkflowRuntimeValidation implements IValidation {
+ validate(schema: WorkflowSchema): ValidationResult {
+ // TODO
+ // 检查成环
+ // 检查边的节点是否存在
+ // 检查跨层级连线
+ // 检查是否只有一个开始节点和一个结束节点
+ // 检查开始节点是否在根节点
+ // 检查结束节点是否在根节点
+
+ // 注册节点检查器
+ return {
+ valid: true,
+ };
+ }
+}
diff --git a/packages/runtime/js-core/src/domain/variable/index.ts b/packages/runtime/js-core/src/domain/variable/index.ts
new file mode 100644
index 00000000..da775ecb
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/variable/index.ts
@@ -0,0 +1 @@
+export { WorkflowRuntimeVariableStore } from './variable-store';
diff --git a/packages/runtime/js-core/src/domain/variable/variable-store/index.test.ts b/packages/runtime/js-core/src/domain/variable/variable-store/index.test.ts
new file mode 100644
index 00000000..61b43405
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/variable/variable-store/index.test.ts
@@ -0,0 +1,201 @@
+import { beforeEach, describe, expect, it } from 'vitest';
+import { IVariableStore, WorkflowVariableType } from '@flowgram.ai/runtime-interface';
+
+import { WorkflowRuntimeVariableStore } from './index';
+
+describe('WorkflowRuntimeVariableStore', () => {
+ let variableStore: IVariableStore;
+
+ beforeEach(() => {
+ variableStore = new WorkflowRuntimeVariableStore();
+ variableStore.init();
+ });
+
+ it('should create a store with unique id', () => {
+ const store1 = new WorkflowRuntimeVariableStore();
+ const store2 = new WorkflowRuntimeVariableStore();
+ expect(store1.id).toBeTruthy();
+ expect(store2.id).toBeTruthy();
+ expect(store1.id).not.toBe(store2.id);
+ });
+
+ describe('set', () => {
+ it('should set variable', () => {
+ const value = { foo: 'bar' };
+ variableStore.setVariable({
+ nodeID: 'node1',
+ key: 'var1',
+ value,
+ type: WorkflowVariableType.Object,
+ });
+
+ expect(variableStore.store.get('node1')?.get('var1')?.value).toEqual(value);
+ });
+
+ it('should update existing variable', () => {
+ variableStore.setVariable({
+ nodeID: 'node1',
+ key: 'var1',
+ value: { foo: 'bar' },
+ type: WorkflowVariableType.Object,
+ });
+
+ variableStore.setVariable({
+ nodeID: 'node1',
+ key: 'var1',
+ value: { baz: 'qux' },
+ type: WorkflowVariableType.Object,
+ });
+
+ expect(variableStore.store.get('node1')?.get('var1')?.value).toEqual({ baz: 'qux' });
+ });
+ });
+
+ describe('setValue', () => {
+ it('should set value without path', () => {
+ const value = { foo: 'bar' };
+ variableStore.setValue({
+ nodeID: 'node1',
+ variableKey: 'var1',
+ value,
+ });
+
+ expect(variableStore.store.get('node1')?.get('var1')?.value).toEqual(value);
+ });
+
+ it('should set value with path', () => {
+ variableStore.setValue({
+ nodeID: 'node1',
+ variableKey: 'var1',
+ variablePath: ['foo', 'bar'],
+ value: 'baz',
+ });
+
+ expect(variableStore.store.get('node1')?.get('var1')?.value).toEqual({
+ foo: { bar: 'baz' },
+ });
+ });
+
+ it('should update existing value', () => {
+ variableStore.setValue({
+ nodeID: 'node1',
+ variableKey: 'var1',
+ value: { foo: 'bar' },
+ });
+
+ variableStore.setValue({
+ nodeID: 'node1',
+ variableKey: 'var1',
+ value: { baz: 'qux' },
+ });
+
+ expect(variableStore.store.get('node1')?.get('var1')?.value).toEqual({ baz: 'qux' });
+ });
+ });
+
+ describe('get', () => {
+ beforeEach(() => {
+ variableStore.setValue({
+ nodeID: 'node1',
+ variableKey: 'var1',
+ value: { foo: { bar: 'baz' } },
+ });
+ });
+
+ it('should get value without path', () => {
+ const result = variableStore.getValue({
+ nodeID: 'node1',
+ variableKey: 'var1',
+ });
+
+ expect(result?.value).toEqual({ foo: { bar: 'baz' } });
+ });
+
+ it('should get value with path', () => {
+ const result = variableStore.getValue({
+ nodeID: 'node1',
+ variableKey: 'var1',
+ variablePath: ['foo', 'bar'],
+ });
+
+ expect(result?.value).toBe('baz');
+ });
+
+ it('should get value with empty path', () => {
+ const result = variableStore.getValue({
+ nodeID: 'node1',
+ variableKey: 'var1',
+ variablePath: [],
+ });
+
+ expect(result?.value).toStrictEqual({ foo: { bar: 'baz' } });
+ });
+
+ it('should get value with undefined path', () => {
+ const result = variableStore.getValue({
+ nodeID: 'node1',
+ variableKey: 'var1',
+ });
+
+ expect(result?.value).toStrictEqual({ foo: { bar: 'baz' } });
+ });
+
+ it('should return undefined for non-existent node', () => {
+ const result = variableStore.getValue({
+ nodeID: 'non-existent',
+ variableKey: 'var1',
+ });
+
+ expect(result?.value).toBeUndefined();
+ });
+
+ it('should return undefined for non-existent variable', () => {
+ const result = variableStore.getValue({
+ nodeID: 'node1',
+ variableKey: 'non-existent',
+ });
+
+ expect(result?.value).toBeUndefined();
+ });
+
+ it('should return undefined for non-existent path', () => {
+ const result = variableStore.getValue({
+ nodeID: 'node1',
+ variableKey: 'var1',
+ variablePath: ['non', 'existent'],
+ });
+
+ expect(result?.value).toBeUndefined();
+ });
+
+ it('should get number value', () => {
+ variableStore.setVariable({
+ nodeID: 'start_0',
+ key: 'llm_settings',
+ value: { temperature: 0.5 },
+ type: WorkflowVariableType.Object,
+ });
+
+ const result = variableStore.getValue({
+ nodeID: 'start_0',
+ variableKey: 'llm_settings',
+ variablePath: ['temperature'],
+ });
+
+ expect(result?.value).toBe(0.5);
+ });
+
+ it('should return 0', () => {
+ variableStore.setValue({
+ nodeID: 'node1',
+ variableKey: 'var1',
+ value: 0,
+ });
+ const result = variableStore.getValue({
+ nodeID: 'node1',
+ variableKey: 'var1',
+ });
+ expect(result?.value).toBe(0);
+ });
+ });
+});
diff --git a/packages/runtime/js-core/src/domain/variable/variable-store/index.ts b/packages/runtime/js-core/src/domain/variable/variable-store/index.ts
new file mode 100644
index 00000000..fa5064c9
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/variable/variable-store/index.ts
@@ -0,0 +1,135 @@
+import { get, set } from 'lodash-es';
+import {
+ WorkflowVariableType,
+ IVariableStore,
+ IVariable,
+ IVariableParseResult,
+} from '@flowgram.ai/runtime-interface';
+
+import { uuid, WorkflowRuntimeType } from '@infra/utils';
+import { WorkflowRuntimeVariable } from '../variable-value-object';
+
+export class WorkflowRuntimeVariableStore implements IVariableStore {
+ public readonly id: string;
+
+ private parent?: WorkflowRuntimeVariableStore;
+
+ constructor() {
+ this.id = uuid();
+ }
+
+ public store: Map>;
+
+ public init(): void {
+ this.store = new Map();
+ }
+
+ public dispose(): void {
+ this.store.clear();
+ }
+
+ public setParent(parent: IVariableStore): void {
+ this.parent = parent as WorkflowRuntimeVariableStore;
+ }
+
+ public globalGet(nodeID: string): Map | undefined {
+ const store = this.store.get(nodeID);
+ if (!store && this.parent) {
+ return this.parent.globalGet(nodeID);
+ }
+ return store;
+ }
+
+ public setVariable(params: {
+ nodeID: string;
+ key: string;
+ value: Object;
+ type: WorkflowVariableType;
+ itemsType?: WorkflowVariableType;
+ }): void {
+ const { nodeID, key, value, type, itemsType } = params;
+ if (!this.store.has(nodeID)) {
+ // create node store
+ this.store.set(nodeID, new Map());
+ }
+ const nodeStore = this.store.get(nodeID)!;
+ // create variable store
+ const variable = WorkflowRuntimeVariable.create({
+ nodeID,
+ key,
+ value,
+ type, // TODO check type
+ itemsType, // TODO check is array
+ });
+ nodeStore.set(key, variable);
+ }
+
+ public setValue(params: {
+ nodeID: string;
+ variableKey: string;
+ variablePath?: string[];
+ value: Object;
+ }): void {
+ const { nodeID, variableKey, variablePath, value } = params;
+ if (!this.store.has(nodeID)) {
+ // create node store
+ this.store.set(nodeID, new Map());
+ }
+ const nodeStore = this.store.get(nodeID)!;
+ if (!nodeStore.has(variableKey)) {
+ // create variable store
+ const variable = WorkflowRuntimeVariable.create({
+ nodeID,
+ key: variableKey,
+ value: {},
+ type: WorkflowVariableType.Object,
+ });
+ nodeStore.set(variableKey, variable);
+ }
+ const variable = nodeStore.get(variableKey)!;
+ if (!variablePath) {
+ variable.value = value;
+ return;
+ }
+ set(variable.value, variablePath, value);
+ }
+
+ public getValue(params: {
+ nodeID: string;
+ variableKey: string;
+ variablePath?: string[];
+ }): IVariableParseResult | null {
+ const { nodeID, variableKey, variablePath } = params;
+ const variable = this.globalGet(nodeID)?.get(variableKey);
+ if (!variable) {
+ return null;
+ }
+ if (!variablePath || variablePath.length === 0) {
+ return {
+ value: variable.value as T,
+ type: variable.type,
+ itemsType: variable.itemsType,
+ };
+ }
+ const value = get(variable.value, variablePath) as T;
+ const type = WorkflowRuntimeType.getWorkflowType(value);
+ if (!type) {
+ return null;
+ }
+ if (type === WorkflowVariableType.Array && Array.isArray(value)) {
+ const itemsType = WorkflowRuntimeType.getWorkflowType(value[0]);
+ if (!itemsType) {
+ return null;
+ }
+ return {
+ value,
+ type,
+ itemsType,
+ };
+ }
+ return {
+ value,
+ type,
+ };
+ }
+}
diff --git a/packages/runtime/js-core/src/domain/variable/variable-value-object/index.ts b/packages/runtime/js-core/src/domain/variable/variable-value-object/index.ts
new file mode 100644
index 00000000..33acd7e2
--- /dev/null
+++ b/packages/runtime/js-core/src/domain/variable/variable-value-object/index.ts
@@ -0,0 +1,10 @@
+import { IVariable, VOData } from '@flowgram.ai/runtime-interface';
+
+import { uuid } from '@infra/utils';
+
+export namespace WorkflowRuntimeVariable {
+ export const create = (params: VOData): IVariable => ({
+ id: uuid(),
+ ...params,
+ });
+}
diff --git a/packages/runtime/js-core/src/index.ts b/packages/runtime/js-core/src/index.ts
new file mode 100644
index 00000000..b1c13e73
--- /dev/null
+++ b/packages/runtime/js-core/src/index.ts
@@ -0,0 +1 @@
+export * from './api';
diff --git a/packages/runtime/js-core/src/infrastructure/index.ts b/packages/runtime/js-core/src/infrastructure/index.ts
new file mode 100644
index 00000000..04bca77e
--- /dev/null
+++ b/packages/runtime/js-core/src/infrastructure/index.ts
@@ -0,0 +1 @@
+export * from './utils';
diff --git a/packages/runtime/js-core/src/infrastructure/utils/delay.ts b/packages/runtime/js-core/src/infrastructure/utils/delay.ts
new file mode 100644
index 00000000..4557adc6
--- /dev/null
+++ b/packages/runtime/js-core/src/infrastructure/utils/delay.ts
@@ -0,0 +1 @@
+export const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
diff --git a/packages/runtime/js-core/src/infrastructure/utils/index.ts b/packages/runtime/js-core/src/infrastructure/utils/index.ts
new file mode 100644
index 00000000..1f176ec7
--- /dev/null
+++ b/packages/runtime/js-core/src/infrastructure/utils/index.ts
@@ -0,0 +1,3 @@
+export { delay } from './delay';
+export { uuid } from './uuid';
+export { WorkflowRuntimeType } from './runtime-type';
diff --git a/packages/runtime/js-core/src/infrastructure/utils/runtime-type.ts b/packages/runtime/js-core/src/infrastructure/utils/runtime-type.ts
new file mode 100644
index 00000000..96e09668
--- /dev/null
+++ b/packages/runtime/js-core/src/infrastructure/utils/runtime-type.ts
@@ -0,0 +1,60 @@
+import { WorkflowVariableType } from '@flowgram.ai/runtime-interface';
+
+export namespace WorkflowRuntimeType {
+ export const getWorkflowType = (value?: unknown): WorkflowVariableType | null => {
+ // 处理 null 和 undefined 的情况
+ if (value === null || value === undefined) {
+ return WorkflowVariableType.Null;
+ }
+
+ // 处理基本类型
+ if (typeof value === 'string') {
+ return WorkflowVariableType.String;
+ }
+
+ if (typeof value === 'boolean') {
+ return WorkflowVariableType.Boolean;
+ }
+
+ if (typeof value === 'number') {
+ if (Number.isInteger(value)) {
+ return WorkflowVariableType.Integer;
+ }
+ return WorkflowVariableType.Number;
+ }
+
+ // 处理数组
+ if (Array.isArray(value)) {
+ return WorkflowVariableType.Array;
+ }
+
+ // 处理普通对象
+ if (typeof value === 'object') {
+ return WorkflowVariableType.Object;
+ }
+
+ return null;
+ };
+
+ export const isMatchWorkflowType = (value: unknown, type: WorkflowVariableType): boolean => {
+ const workflowType = getWorkflowType(value);
+ if (!workflowType) {
+ return false;
+ }
+ return workflowType === type;
+ };
+
+ export const isTypeEqual = (
+ leftType: WorkflowVariableType,
+ rightType: WorkflowVariableType
+ ): boolean => {
+ // 处理 Number 和 Integer 等价的情况
+ if (
+ (leftType === WorkflowVariableType.Number && rightType === WorkflowVariableType.Integer) ||
+ (leftType === WorkflowVariableType.Integer && rightType === WorkflowVariableType.Number)
+ ) {
+ return true;
+ }
+ return leftType === rightType;
+ };
+}
diff --git a/packages/runtime/js-core/src/infrastructure/utils/uuid.ts b/packages/runtime/js-core/src/infrastructure/utils/uuid.ts
new file mode 100644
index 00000000..8eb7c015
--- /dev/null
+++ b/packages/runtime/js-core/src/infrastructure/utils/uuid.ts
@@ -0,0 +1,3 @@
+import { v4 } from 'uuid';
+
+export const uuid = v4;
diff --git a/packages/runtime/js-core/src/nodes/condition/handlers/array.ts b/packages/runtime/js-core/src/nodes/condition/handlers/array.ts
new file mode 100644
index 00000000..bb09b26a
--- /dev/null
+++ b/packages/runtime/js-core/src/nodes/condition/handlers/array.ts
@@ -0,0 +1,16 @@
+import { isNil } from 'lodash-es';
+
+import { ConditionHandler, ConditionOperation } from '../type';
+
+export const conditionArrayHandler: ConditionHandler = (condition) => {
+ const { operator } = condition;
+ const leftValue = condition.leftValue as object;
+ // Switch case share scope, so we need to use if else here
+ if (operator === ConditionOperation.IS_EMPTY) {
+ return isNil(leftValue);
+ }
+ if (operator === ConditionOperation.IS_NOT_EMPTY) {
+ return !isNil(leftValue);
+ }
+ return false;
+};
diff --git a/packages/runtime/js-core/src/nodes/condition/handlers/boolean.ts b/packages/runtime/js-core/src/nodes/condition/handlers/boolean.ts
new file mode 100644
index 00000000..01ddd1ee
--- /dev/null
+++ b/packages/runtime/js-core/src/nodes/condition/handlers/boolean.ts
@@ -0,0 +1,38 @@
+import { isNil } from 'lodash-es';
+
+import { ConditionHandler, ConditionOperation } from '../type';
+
+export const conditionBooleanHandler: ConditionHandler = (condition) => {
+ const { operator } = condition;
+ const leftValue = condition.leftValue as boolean;
+ // Switch case share scope, so we need to use if else here
+ if (operator === ConditionOperation.EQ) {
+ const rightValue = condition.rightValue as boolean;
+ return leftValue === rightValue;
+ }
+ if (operator === ConditionOperation.NEQ) {
+ const rightValue = condition.rightValue as boolean;
+ return leftValue !== rightValue;
+ }
+ if (operator === ConditionOperation.IS_TRUE) {
+ return leftValue === true;
+ }
+ if (operator === ConditionOperation.IS_FALSE) {
+ return leftValue === false;
+ }
+ if (operator === ConditionOperation.IN) {
+ const rightValue = condition.rightValue as boolean[];
+ return rightValue.includes(leftValue);
+ }
+ if (operator === ConditionOperation.NIN) {
+ const rightValue = condition.rightValue as boolean[];
+ return !rightValue.includes(leftValue);
+ }
+ if (operator === ConditionOperation.IS_EMPTY) {
+ return isNil(leftValue);
+ }
+ if (operator === ConditionOperation.IS_NOT_EMPTY) {
+ return !isNil(leftValue);
+ }
+ return false;
+};
diff --git a/packages/runtime/js-core/src/nodes/condition/handlers/index.ts b/packages/runtime/js-core/src/nodes/condition/handlers/index.ts
new file mode 100644
index 00000000..1467d955
--- /dev/null
+++ b/packages/runtime/js-core/src/nodes/condition/handlers/index.ts
@@ -0,0 +1,19 @@
+import { WorkflowVariableType } from '@flowgram.ai/runtime-interface';
+
+import { ConditionHandlers } from '../type';
+import { conditionStringHandler } from './string';
+import { conditionObjectHandler } from './object';
+import { conditionNumberHandler } from './number';
+import { conditionNullHandler } from './null';
+import { conditionBooleanHandler } from './boolean';
+import { conditionArrayHandler } from './array';
+
+export const conditionHandlers: ConditionHandlers = {
+ [WorkflowVariableType.String]: conditionStringHandler,
+ [WorkflowVariableType.Number]: conditionNumberHandler,
+ [WorkflowVariableType.Integer]: conditionNumberHandler,
+ [WorkflowVariableType.Boolean]: conditionBooleanHandler,
+ [WorkflowVariableType.Object]: conditionObjectHandler,
+ [WorkflowVariableType.Array]: conditionArrayHandler,
+ [WorkflowVariableType.Null]: conditionNullHandler,
+};
diff --git a/packages/runtime/js-core/src/nodes/condition/handlers/null.ts b/packages/runtime/js-core/src/nodes/condition/handlers/null.ts
new file mode 100644
index 00000000..ced17ef8
--- /dev/null
+++ b/packages/runtime/js-core/src/nodes/condition/handlers/null.ts
@@ -0,0 +1,19 @@
+import { isNil } from 'lodash-es';
+
+import { ConditionHandler, ConditionOperation } from '../type';
+
+export const conditionNullHandler: ConditionHandler = (condition) => {
+ const { operator } = condition;
+ const leftValue = condition.leftValue as unknown | null;
+ // Switch case share scope, so we need to use if else here
+ if (operator === ConditionOperation.EQ) {
+ return isNil(leftValue) && isNil(condition.rightValue);
+ }
+ if (operator === ConditionOperation.IS_EMPTY) {
+ return isNil(leftValue);
+ }
+ if (operator === ConditionOperation.IS_NOT_EMPTY) {
+ return !isNil(leftValue);
+ }
+ return false;
+};
diff --git a/packages/runtime/js-core/src/nodes/condition/handlers/number.ts b/packages/runtime/js-core/src/nodes/condition/handlers/number.ts
new file mode 100644
index 00000000..28697069
--- /dev/null
+++ b/packages/runtime/js-core/src/nodes/condition/handlers/number.ts
@@ -0,0 +1,48 @@
+import { isNil } from 'lodash-es';
+
+import { ConditionHandler, ConditionOperation } from '../type';
+
+export const conditionNumberHandler: ConditionHandler = (condition) => {
+ const { operator } = condition;
+ const leftValue = condition.leftValue as number;
+ // Switch case share scope, so we need to use if else here
+ if (operator === ConditionOperation.EQ) {
+ const rightValue = condition.rightValue as number;
+ return leftValue === rightValue;
+ }
+ if (operator === ConditionOperation.NEQ) {
+ const rightValue = condition.rightValue as number;
+ return leftValue !== rightValue;
+ }
+ if (operator === ConditionOperation.GT) {
+ const rightValue = condition.rightValue as number;
+ return leftValue > rightValue;
+ }
+ if (operator === ConditionOperation.GTE) {
+ const rightValue = condition.rightValue as number;
+ return leftValue >= rightValue;
+ }
+ if (operator === ConditionOperation.LT) {
+ const rightValue = condition.rightValue as number;
+ return leftValue < rightValue;
+ }
+ if (operator === ConditionOperation.LTE) {
+ const rightValue = condition.rightValue as number;
+ return leftValue <= rightValue;
+ }
+ if (operator === ConditionOperation.IN) {
+ const rightValue = condition.rightValue as number[];
+ return rightValue.includes(leftValue);
+ }
+ if (operator === ConditionOperation.NIN) {
+ const rightValue = condition.rightValue as number[];
+ return !rightValue.includes(leftValue);
+ }
+ if (operator === ConditionOperation.IS_EMPTY) {
+ return isNil(leftValue);
+ }
+ if (operator === ConditionOperation.IS_NOT_EMPTY) {
+ return !isNil(leftValue);
+ }
+ return false;
+};
diff --git a/packages/runtime/js-core/src/nodes/condition/handlers/object.ts b/packages/runtime/js-core/src/nodes/condition/handlers/object.ts
new file mode 100644
index 00000000..e7fd7a83
--- /dev/null
+++ b/packages/runtime/js-core/src/nodes/condition/handlers/object.ts
@@ -0,0 +1,16 @@
+import { isNil } from 'lodash-es';
+
+import { ConditionHandler, ConditionOperation } from '../type';
+
+export const conditionObjectHandler: ConditionHandler = (condition) => {
+ const { operator } = condition;
+ const leftValue = condition.leftValue as object;
+ // Switch case share scope, so we need to use if else here
+ if (operator === ConditionOperation.IS_EMPTY) {
+ return isNil(leftValue);
+ }
+ if (operator === ConditionOperation.IS_NOT_EMPTY) {
+ return !isNil(leftValue);
+ }
+ return false;
+};
diff --git a/packages/runtime/js-core/src/nodes/condition/handlers/string.ts b/packages/runtime/js-core/src/nodes/condition/handlers/string.ts
new file mode 100644
index 00000000..e5a78123
--- /dev/null
+++ b/packages/runtime/js-core/src/nodes/condition/handlers/string.ts
@@ -0,0 +1,40 @@
+import { isNil } from 'lodash-es';
+
+import { ConditionHandler, ConditionOperation } from '../type';
+
+export const conditionStringHandler: ConditionHandler = (condition) => {
+ const { operator } = condition;
+ const leftValue = condition.leftValue as string;
+ // Switch case share scope, so we need to use if else here
+ if (operator === ConditionOperation.EQ) {
+ const rightValue = condition.rightValue as string;
+ return leftValue === rightValue;
+ }
+ if (operator === ConditionOperation.NEQ) {
+ const rightValue = condition.rightValue as string;
+ return leftValue !== rightValue;
+ }
+ if (operator === ConditionOperation.CONTAINS) {
+ const rightValue = condition.rightValue as string;
+ return leftValue.includes(rightValue);
+ }
+ if (operator === ConditionOperation.NOT_CONTAINS) {
+ const rightValue = condition.rightValue as string;
+ return !leftValue.includes(rightValue);
+ }
+ if (operator === ConditionOperation.IN) {
+ const rightValue = condition.rightValue as string[];
+ return rightValue.includes(leftValue);
+ }
+ if (operator === ConditionOperation.NIN) {
+ const rightValue = condition.rightValue as string[];
+ return !rightValue.includes(leftValue);
+ }
+ if (operator === ConditionOperation.IS_EMPTY) {
+ return isNil(leftValue);
+ }
+ if (operator === ConditionOperation.IS_NOT_EMPTY) {
+ return !isNil(leftValue);
+ }
+ return false;
+};
diff --git a/packages/runtime/js-core/src/nodes/condition/index.ts b/packages/runtime/js-core/src/nodes/condition/index.ts
new file mode 100644
index 00000000..4be8156d
--- /dev/null
+++ b/packages/runtime/js-core/src/nodes/condition/index.ts
@@ -0,0 +1,82 @@
+import { isNil } from 'lodash-es';
+import {
+ ExecutionContext,
+ ExecutionResult,
+ FlowGramNode,
+ INodeExecutor,
+ WorkflowVariableType,
+} from '@flowgram.ai/runtime-interface';
+
+import { ConditionItem, ConditionValue, Conditions } from './type';
+import { conditionRules } from './rules';
+import { conditionHandlers } from './handlers';
+
+export class ConditionExecutor implements INodeExecutor {
+ public type = FlowGramNode.Condition;
+
+ public async execute(context: ExecutionContext): Promise {
+ const conditions: Conditions = context.node.data?.conditions;
+ if (!conditions) {
+ return {
+ outputs: {},
+ };
+ }
+ const parsedConditions = conditions
+ .map((item) => this.parseCondition(item, context))
+ .filter((item) => this.checkCondition(item));
+ const activatedCondition = parsedConditions.find((item) => this.handleCondition(item));
+ if (!activatedCondition) {
+ return {
+ outputs: {},
+ };
+ }
+ return {
+ outputs: {},
+ branch: activatedCondition.key,
+ };
+ }
+
+ private parseCondition(item: ConditionItem, context: ExecutionContext): ConditionValue {
+ const { key, value } = item;
+ const { left, operator, right } = value;
+ const parsedLeft = context.runtime.state.parseRef(left);
+ const leftValue = parsedLeft?.value ?? null;
+ const leftType = parsedLeft?.type ?? WorkflowVariableType.Null;
+ const parsedRight = Boolean(right) ? context.runtime.state.parseValue(right) : null;
+ const rightValue = parsedRight?.value ?? null;
+ const rightType = parsedRight?.type ?? WorkflowVariableType.Null;
+ return {
+ key,
+ leftValue,
+ leftType,
+ rightValue,
+ rightType,
+ operator,
+ };
+ }
+
+ private checkCondition(condition: ConditionValue): boolean {
+ const rule = conditionRules[condition.leftType];
+ if (isNil(rule)) {
+ throw new Error(`condition left type ${condition.leftType} is not supported`);
+ }
+ const ruleType = rule[condition.operator];
+ if (isNil(ruleType)) {
+ throw new Error(`condition operator ${condition.operator} is not supported`);
+ }
+ if (ruleType !== condition.rightType) {
+ // throw new Error(`condition right type expected ${ruleType}, got ${condition.rightType}`);
+ return false;
+ }
+ return true;
+ }
+
+ private handleCondition(condition: ConditionValue): boolean {
+ const handler = conditionHandlers[condition.leftType];
+ if (!handler) {
+ throw new Error(`condition left type ${condition.leftType} is not supported`);
+ }
+ const isActive = handler(condition);
+ return isActive;
+ }
+}
diff --git a/packages/runtime/js-core/src/nodes/condition/rules.ts b/packages/runtime/js-core/src/nodes/condition/rules.ts
new file mode 100644
index 00000000..5c4740dc
--- /dev/null
+++ b/packages/runtime/js-core/src/nodes/condition/rules.ts
@@ -0,0 +1,63 @@
+import { WorkflowVariableType } from '@flowgram.ai/runtime-interface';
+
+import { ConditionOperation, ConditionRules } from './type';
+
+export const conditionRules: ConditionRules = {
+ [WorkflowVariableType.String]: {
+ [ConditionOperation.EQ]: WorkflowVariableType.String,
+ [ConditionOperation.NEQ]: WorkflowVariableType.String,
+ [ConditionOperation.CONTAINS]: WorkflowVariableType.String,
+ [ConditionOperation.NOT_CONTAINS]: WorkflowVariableType.String,
+ [ConditionOperation.IN]: WorkflowVariableType.Array,
+ [ConditionOperation.NIN]: WorkflowVariableType.Array,
+ [ConditionOperation.IS_EMPTY]: WorkflowVariableType.String,
+ [ConditionOperation.IS_NOT_EMPTY]: WorkflowVariableType.String,
+ },
+ [WorkflowVariableType.Number]: {
+ [ConditionOperation.EQ]: WorkflowVariableType.Number,
+ [ConditionOperation.NEQ]: WorkflowVariableType.Number,
+ [ConditionOperation.GT]: WorkflowVariableType.Number,
+ [ConditionOperation.GTE]: WorkflowVariableType.Number,
+ [ConditionOperation.LT]: WorkflowVariableType.Number,
+ [ConditionOperation.LTE]: WorkflowVariableType.Number,
+ [ConditionOperation.IN]: WorkflowVariableType.Array,
+ [ConditionOperation.NIN]: WorkflowVariableType.Array,
+ [ConditionOperation.IS_EMPTY]: WorkflowVariableType.Null,
+ [ConditionOperation.IS_NOT_EMPTY]: WorkflowVariableType.Null,
+ },
+ [WorkflowVariableType.Integer]: {
+ [ConditionOperation.EQ]: WorkflowVariableType.Integer,
+ [ConditionOperation.NEQ]: WorkflowVariableType.Integer,
+ [ConditionOperation.GT]: WorkflowVariableType.Integer,
+ [ConditionOperation.GTE]: WorkflowVariableType.Integer,
+ [ConditionOperation.LT]: WorkflowVariableType.Integer,
+ [ConditionOperation.LTE]: WorkflowVariableType.Integer,
+ [ConditionOperation.IN]: WorkflowVariableType.Array,
+ [ConditionOperation.NIN]: WorkflowVariableType.Array,
+ [ConditionOperation.IS_EMPTY]: WorkflowVariableType.Null,
+ [ConditionOperation.IS_NOT_EMPTY]: WorkflowVariableType.Null,
+ },
+ [WorkflowVariableType.Boolean]: {
+ [ConditionOperation.EQ]: WorkflowVariableType.Boolean,
+ [ConditionOperation.NEQ]: WorkflowVariableType.Boolean,
+ [ConditionOperation.IS_TRUE]: WorkflowVariableType.Null,
+ [ConditionOperation.IS_FALSE]: WorkflowVariableType.Null,
+ [ConditionOperation.IN]: WorkflowVariableType.Array,
+ [ConditionOperation.NIN]: WorkflowVariableType.Array,
+ [ConditionOperation.IS_EMPTY]: WorkflowVariableType.Null,
+ [ConditionOperation.IS_NOT_EMPTY]: WorkflowVariableType.Null,
+ },
+ [WorkflowVariableType.Object]: {
+ [ConditionOperation.IS_EMPTY]: WorkflowVariableType.Null,
+ [ConditionOperation.IS_NOT_EMPTY]: WorkflowVariableType.Null,
+ },
+ [WorkflowVariableType.Array]: {
+ [ConditionOperation.IS_EMPTY]: WorkflowVariableType.Null,
+ [ConditionOperation.IS_NOT_EMPTY]: WorkflowVariableType.Null,
+ },
+ [WorkflowVariableType.Null]: {
+ [ConditionOperation.EQ]: WorkflowVariableType.Null,
+ [ConditionOperation.IS_EMPTY]: WorkflowVariableType.Null,
+ [ConditionOperation.IS_NOT_EMPTY]: WorkflowVariableType.Null,
+ },
+};
diff --git a/packages/runtime/js-core/src/nodes/condition/type.ts b/packages/runtime/js-core/src/nodes/condition/type.ts
new file mode 100644
index 00000000..22659830
--- /dev/null
+++ b/packages/runtime/js-core/src/nodes/condition/type.ts
@@ -0,0 +1,50 @@
+import {
+ WorkflowVariableType,
+ IFlowConstantRefValue,
+ IFlowRefValue,
+} from '@flowgram.ai/runtime-interface';
+
+export enum ConditionOperation {
+ EQ = 'eq',
+ NEQ = 'neq',
+ GT = 'gt',
+ GTE = 'gte',
+ LT = 'lt',
+ LTE = 'lte',
+ IN = 'in',
+ NIN = 'nin',
+ CONTAINS = 'contains',
+ NOT_CONTAINS = 'not_contains',
+ IS_EMPTY = 'is_empty',
+ IS_NOT_EMPTY = 'is_not_empty',
+ IS_TRUE = 'is_true',
+ IS_FALSE = 'is_false',
+}
+
+export interface ConditionItem {
+ key: string;
+ value: {
+ left: IFlowRefValue;
+ operator: ConditionOperation;
+ right: IFlowConstantRefValue;
+ };
+}
+
+export type Conditions = ConditionItem[];
+
+export type ConditionRule = Partial>;
+
+export type ConditionRules = Record;
+
+export interface ConditionValue {
+ key: string;
+ leftValue: unknown | null;
+ rightValue: unknown | null;
+ leftType: WorkflowVariableType;
+ rightType: WorkflowVariableType;
+ operator: ConditionOperation;
+}
+
+export type ConditionHandler = (condition: ConditionValue) => boolean;
+
+export type ConditionHandlers = Record;
diff --git a/packages/runtime/js-core/src/nodes/end/index.ts b/packages/runtime/js-core/src/nodes/end/index.ts
new file mode 100644
index 00000000..c6ad6551
--- /dev/null
+++ b/packages/runtime/js-core/src/nodes/end/index.ts
@@ -0,0 +1,17 @@
+import {
+ ExecutionContext,
+ ExecutionResult,
+ FlowGramNode,
+ INodeExecutor,
+} from '@flowgram.ai/runtime-interface';
+
+export class EndExecutor implements INodeExecutor {
+ public type = FlowGramNode.End;
+
+ public async execute(context: ExecutionContext): Promise {
+ context.runtime.ioCenter.setOutputs(context.inputs);
+ return {
+ outputs: context.inputs,
+ };
+ }
+}
diff --git a/packages/runtime/js-core/src/nodes/index.ts b/packages/runtime/js-core/src/nodes/index.ts
new file mode 100644
index 00000000..c953671f
--- /dev/null
+++ b/packages/runtime/js-core/src/nodes/index.ts
@@ -0,0 +1,15 @@
+import { INodeExecutorFactory } from '@flowgram.ai/runtime-interface';
+
+import { StartExecutor } from './start';
+import { LoopExecutor } from './loop';
+import { LLMExecutor } from './llm';
+import { EndExecutor } from './end';
+import { ConditionExecutor } from './condition';
+
+export const WorkflowRuntimeNodeExecutors: INodeExecutorFactory[] = [
+ StartExecutor,
+ EndExecutor,
+ LLMExecutor,
+ ConditionExecutor,
+ LoopExecutor,
+];
diff --git a/packages/runtime/js-core/src/nodes/llm/index.ts b/packages/runtime/js-core/src/nodes/llm/index.ts
new file mode 100644
index 00000000..4ce55d94
--- /dev/null
+++ b/packages/runtime/js-core/src/nodes/llm/index.ts
@@ -0,0 +1,69 @@
+import { isNil } from 'lodash-es';
+import { ChatOpenAI } from '@langchain/openai';
+import { SystemMessage, HumanMessage, BaseMessageLike } from '@langchain/core/messages';
+import {
+ ExecutionContext,
+ ExecutionResult,
+ FlowGramNode,
+ INodeExecutor,
+} from '@flowgram.ai/runtime-interface';
+
+export interface LLMExecutorInputs {
+ modelName: string;
+ apiKey: string;
+ apiHost: string;
+ temperature: number;
+ systemPrompt?: string;
+ prompt: string;
+}
+
+export class LLMExecutor implements INodeExecutor {
+ public type = FlowGramNode.LLM;
+
+ public async execute(context: ExecutionContext): Promise {
+ const inputs = context.inputs as LLMExecutorInputs;
+ this.checkInputs(inputs);
+
+ const { modelName, temperature, apiKey, apiHost, systemPrompt, prompt } = inputs;
+
+ const model = new ChatOpenAI({
+ modelName,
+ temperature,
+ apiKey,
+ configuration: {
+ baseURL: apiHost,
+ },
+ });
+
+ const messages: BaseMessageLike[] = [];
+
+ if (systemPrompt) {
+ messages.push(new SystemMessage(systemPrompt));
+ }
+ messages.push(new HumanMessage(prompt));
+
+ const apiMessage = await model.invoke(messages);
+
+ const result = apiMessage.content;
+ return {
+ outputs: {
+ result,
+ },
+ };
+ }
+
+ protected checkInputs(inputs: LLMExecutorInputs) {
+ const { modelName, temperature, apiKey, apiHost, prompt } = inputs;
+ const missingInputs = [];
+
+ if (isNil(modelName)) missingInputs.push('modelName');
+ if (isNil(temperature)) missingInputs.push('temperature');
+ if (isNil(apiKey)) missingInputs.push('apiKey');
+ if (isNil(apiHost)) missingInputs.push('apiHost');
+ if (isNil(prompt)) missingInputs.push('prompt');
+
+ if (missingInputs.length > 0) {
+ throw new Error(`LLM node missing required inputs: ${missingInputs.join(', ')}`);
+ }
+ }
+}
diff --git a/packages/runtime/js-core/src/nodes/loop/index.ts b/packages/runtime/js-core/src/nodes/loop/index.ts
new file mode 100644
index 00000000..13f76752
--- /dev/null
+++ b/packages/runtime/js-core/src/nodes/loop/index.ts
@@ -0,0 +1,77 @@
+import { isNil } from 'lodash-es';
+import {
+ ExecutionContext,
+ ExecutionResult,
+ FlowGramNode,
+ IEngine,
+ INodeExecutor,
+ IVariableParseResult,
+ WorkflowVariableType,
+} from '@flowgram.ai/runtime-interface';
+
+type LoopArray = Array;
+
+export interface LoopExecutorInputs {
+ batchFor: LoopArray;
+}
+
+export class LoopExecutor implements INodeExecutor {
+ public type = FlowGramNode.Loop;
+
+ public async execute(context: ExecutionContext): Promise {
+ const loopNodeID = context.node.id;
+ const loopArrayResult = context.runtime.state.parseRef(context.node.data.batchFor)!;
+ this.checkLoopArray(loopArrayResult);
+
+ const loopArray = loopArrayResult.value;
+ const itemsType = loopArrayResult.itemsType!;
+ const engine = context.container.get(IEngine);
+ const subNodes = context.node.children;
+ const startSubNodes = subNodes.filter((node) => node.prev.length === 0);
+
+ if (loopArray.length === 0 || startSubNodes.length === 0) {
+ return {
+ outputs: {},
+ };
+ }
+
+ // not use Array method to make error stack more concise, and better performance
+ for (let i = 0; i < loopArray.length; i++) {
+ const loopItem = loopArray[i];
+ const subContext = context.runtime.sub();
+ subContext.variableStore.setVariable({
+ nodeID: `${loopNodeID}_locals`,
+ key: 'item',
+ type: itemsType,
+ value: loopItem,
+ });
+ await Promise.all(
+ startSubNodes.map((node) =>
+ engine.executeNode({
+ context: subContext,
+ node,
+ })
+ )
+ );
+ }
+
+ return {
+ outputs: {},
+ };
+ }
+
+ private checkLoopArray(loopArrayResult: IVariableParseResult | null): void {
+ const loopArray = loopArrayResult?.value;
+ if (!loopArray || isNil(loopArray) || !Array.isArray(loopArray)) {
+ throw new Error('batchFor is required');
+ }
+ const loopArrayType = loopArrayResult.type;
+ if (loopArrayType !== WorkflowVariableType.Array) {
+ throw new Error('batchFor must be an array');
+ }
+ const loopArrayItemType = loopArrayResult.itemsType;
+ if (isNil(loopArrayItemType)) {
+ throw new Error('batchFor items must be array items');
+ }
+ }
+}
diff --git a/packages/runtime/js-core/src/nodes/start/index.ts b/packages/runtime/js-core/src/nodes/start/index.ts
new file mode 100644
index 00000000..b51de1d8
--- /dev/null
+++ b/packages/runtime/js-core/src/nodes/start/index.ts
@@ -0,0 +1,16 @@
+import {
+ ExecutionContext,
+ ExecutionResult,
+ FlowGramNode,
+ INodeExecutor,
+} from '@flowgram.ai/runtime-interface';
+
+export class StartExecutor implements INodeExecutor {
+ public type = FlowGramNode.Start;
+
+ public async execute(context: ExecutionContext): Promise {
+ return {
+ outputs: context.runtime.ioCenter.inputs,
+ };
+ }
+}
diff --git a/packages/runtime/js-core/tsconfig.json b/packages/runtime/js-core/tsconfig.json
new file mode 100644
index 00000000..4f6dd941
--- /dev/null
+++ b/packages/runtime/js-core/tsconfig.json
@@ -0,0 +1,26 @@
+{
+ "extends": "@flowgram.ai/ts-config/tsconfig.flow.path.json",
+ "compilerOptions": {
+ "baseUrl": "src",
+ "paths": {
+ "@application/*": [
+ "application/*"
+ ],
+ "@workflow/*": [
+ "domain/*"
+ ],
+ "@infra/*": [
+ "infrastructure/*"
+ ],
+ "@nodes/*": [
+ "nodes/*"
+ ],
+ }
+ },
+ "include": [
+ "./src"
+ ],
+ "exclude": [
+ "node_modules"
+ ]
+}
diff --git a/packages/runtime/js-core/vitest.config.ts b/packages/runtime/js-core/vitest.config.ts
new file mode 100644
index 00000000..e4e2c35b
--- /dev/null
+++ b/packages/runtime/js-core/vitest.config.ts
@@ -0,0 +1,39 @@
+import { config } from "dotenv";
+import path from 'path';
+import { defineConfig } from 'vitest/config';
+
+export default defineConfig({
+ build: {
+ commonjsOptions: {
+ transformMixedEsModules: true,
+ },
+ },
+ resolve: {
+ alias: [
+ {find: "@application", replacement: path.resolve(__dirname, './src/application') },
+ {find: "@workflow", replacement: path.resolve(__dirname, './src/domain') },
+ {find: "@infra", replacement: path.resolve(__dirname, './src/infrastructure') },
+ {find: "@nodes", replacement: path.resolve(__dirname, './src/nodes') },
+ ],
+ },
+ test: {
+ globals: true,
+ mockReset: false,
+ environment: 'jsdom',
+ testTimeout: 15000,
+ setupFiles: [path.resolve(__dirname, './src/domain/__tests__/setup.ts')],
+ include: ['**/?(*.){test,spec}.?(c|m)[jt]s?(x)'],
+ exclude: [
+ '**/__mocks__**',
+ '**/node_modules/**',
+ '**/dist/**',
+ '**/lib/**', // lib 编译结果忽略掉
+ '**/cypress/**',
+ '**/.{idea,git,cache,output,temp}/**',
+ '**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build}.config.*',
+ ],
+ env: {
+ ...config({ path: path.resolve(__dirname, './.env/.env.test') }).parsed
+ }
+ },
+});
diff --git a/packages/runtime/nodejs/.eslintignore b/packages/runtime/nodejs/.eslintignore
new file mode 100644
index 00000000..cf64a52f
--- /dev/null
+++ b/packages/runtime/nodejs/.eslintignore
@@ -0,0 +1 @@
+.eslintrc.cjs
diff --git a/packages/runtime/nodejs/.eslintrc.cjs b/packages/runtime/nodejs/.eslintrc.cjs
new file mode 100644
index 00000000..9c2f4ba5
--- /dev/null
+++ b/packages/runtime/nodejs/.eslintrc.cjs
@@ -0,0 +1,24 @@
+const { defineConfig } = require('@flowgram.ai/eslint-config');
+
+module.exports = defineConfig({
+ parser: '@typescript-eslint/parser',
+ preset: 'node',
+ packageRoot: __dirname,
+ parserOptions: {
+ requireConfigFile: false,
+ ecmaVersion: 2017,
+ sourceType: "module",
+ ecmaFeatures: {
+ modules: true,
+ }
+ },
+ rules: {
+ 'no-console': 'off',
+ },
+ plugins: ['json', '@typescript-eslint'],
+ settings: {
+ react: {
+ version: '18',
+ },
+ },
+});
diff --git a/packages/runtime/nodejs/.gitignore b/packages/runtime/nodejs/.gitignore
new file mode 100644
index 00000000..5ef6a520
--- /dev/null
+++ b/packages/runtime/nodejs/.gitignore
@@ -0,0 +1,41 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.*
+.yarn/*
+!.yarn/patches
+!.yarn/plugins
+!.yarn/releases
+!.yarn/versions
+
+# testing
+/coverage
+
+# next.js
+/.next/
+/out/
+
+# production
+/build
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+.pnpm-debug.log*
+
+# env files (can opt-in for committing if needed)
+.env*
+
+# vercel
+.vercel
+
+# typescript
+*.tsbuildinfo
+next-env.d.ts
diff --git a/packages/runtime/nodejs/README.md b/packages/runtime/nodejs/README.md
new file mode 100644
index 00000000..d7615d89
--- /dev/null
+++ b/packages/runtime/nodejs/README.md
@@ -0,0 +1,16 @@
+# FlowGram Runtime NodeJS
+
+## Starting the server
+```bash
+pnpm dev
+```
+
+## Building the server
+```bash
+pnpm build
+```
+
+## Running the server
+```bash
+pnpm start
+```
diff --git a/packages/runtime/nodejs/package.json b/packages/runtime/nodejs/package.json
new file mode 100644
index 00000000..3e5571ae
--- /dev/null
+++ b/packages/runtime/nodejs/package.json
@@ -0,0 +1,62 @@
+{
+ "name": "@flowgram.ai/runtime-nodejs",
+ "version": "0.1.0",
+ "description": "",
+ "keywords": [],
+ "license": "MIT",
+ "scripts": {
+ "dev": "tsx watch src",
+ "build": "npm run build:fast -- --dts-resolve",
+ "build:fast": "tsup src/index.ts --format cjs,esm --sourcemap --legacy-output",
+ "build:watch": "npm run build:fast -- --dts-resolve",
+ "lint": "eslint --cache src",
+ "lint-fix": "eslint --fix src",
+ "type-check": "tsc",
+ "start": "node dist/index.js",
+ "test": "exit 0",
+ "test:cov": "exit 0"
+ },
+ "dependencies": {
+ "@flowgram.ai/runtime-js": "workspace:*",
+ "@flowgram.ai/runtime-interface": "workspace:*",
+ "@fastify/cors": "^8.2.1",
+ "@fastify/swagger": "^8.5.1",
+ "@fastify/swagger-ui": "4.1.0",
+ "@langchain/openai": "^0.5.11",
+ "@langchain/core": "^0.3.57",
+ "@fastify/websocket": "^10.0.1",
+ "@trpc/server": "^10.27.1",
+ "trpc-openapi": "^1.2.0",
+ "fastify": "^4.17.0",
+ "tslib": "^2.8.1",
+ "lodash-es": "^4.17.21",
+ "ws": "^8.0.0",
+ "zod": "^3.24.4"
+ },
+ "devDependencies": {
+ "@flowgram.ai/ts-config": "workspace:*",
+ "@flowgram.ai/eslint-config": "workspace:*",
+ "@types/cors": "^2.8.13",
+ "dotenv": "~16.5.0",
+ "@types/node": "^18",
+ "@types/ws": "^8.2.0",
+ "@types/lodash-es": "^4.17.12",
+ "eslint": "^8.54.0",
+ "npm-run-all": "^4.1.5",
+ "@babel/eslint-parser": "~7.19.1",
+ "typescript": "^5.0.4",
+ "tsup": "^8.0.1",
+ "tsx": "~4.19.4",
+ "eslint-plugin-json": "^4.0.1",
+ "@typescript-eslint/eslint-plugin": "^6.10.0",
+ "@typescript-eslint/parser": "^6.10.0",
+ "@eslint/eslintrc": "^3",
+ "@vitest/coverage-v8": "^0.32.0",
+ "vitest": "^0.34.6",
+ "wait-port": "^1.0.1"
+ },
+ "publishConfig": {
+ "access": "public",
+ "registry": "https://registry.npmjs.org/"
+ }
+}
diff --git a/packages/runtime/nodejs/src/api/create-api.ts b/packages/runtime/nodejs/src/api/create-api.ts
new file mode 100644
index 00000000..1c2abefc
--- /dev/null
+++ b/packages/runtime/nodejs/src/api/create-api.ts
@@ -0,0 +1,63 @@
+import z from 'zod';
+import { WorkflowRuntimeAPIs } from '@flowgram.ai/runtime-js';
+import { FlowGramAPIMethod, FlowGramAPIName, FlowGramAPIs } from '@flowgram.ai/runtime-interface';
+
+import { APIHandler } from './type';
+import { publicProcedure } from './trpc';
+
+export const createAPI = (apiName: FlowGramAPIName): APIHandler => {
+ const define = FlowGramAPIs[apiName];
+ const caller = WorkflowRuntimeAPIs[apiName];
+ if (define.method === FlowGramAPIMethod.GET) {
+ const procedure = publicProcedure
+ .meta({
+ openapi: {
+ method: define.method,
+ path: define.path,
+ summary: define.name,
+ tags: [define.module],
+ },
+ })
+ .input(define.schema.input)
+ .output(z.union([define.schema.output, z.undefined()]))
+ .query(async (opts) => {
+ const input = opts.input;
+ try {
+ const output = await caller(input);
+ return output;
+ } catch {
+ return undefined;
+ }
+ });
+
+ return {
+ define,
+ procedure: procedure as any,
+ };
+ }
+
+ const procedure = publicProcedure
+ .meta({
+ openapi: {
+ method: define.method,
+ path: define.path,
+ summary: define.name,
+ tags: [define.module],
+ },
+ })
+ .input(define.schema.input)
+ .output(z.union([define.schema.output, z.undefined()]))
+ .mutation(async (opts) => {
+ const input = opts.input;
+ try {
+ const output = await caller(input);
+ return output;
+ } catch {
+ return undefined;
+ }
+ });
+ return {
+ define,
+ procedure: procedure as any,
+ };
+};
diff --git a/packages/runtime/nodejs/src/api/index.ts b/packages/runtime/nodejs/src/api/index.ts
new file mode 100644
index 00000000..ec640ef6
--- /dev/null
+++ b/packages/runtime/nodejs/src/api/index.ts
@@ -0,0 +1,16 @@
+import { FlowGramAPINames } from '@flowgram.ai/runtime-interface';
+
+import { APIRouter } from './type';
+import { router } from './trpc';
+import { createAPI } from './create-api';
+
+const APIS = FlowGramAPINames.map((apiName) => createAPI(apiName));
+
+export const routers = APIS.reduce((acc, api) => {
+ acc[api.define.path] = api.procedure;
+ return acc;
+}, {} as APIRouter);
+
+export const appRouter = router(routers);
+
+export type AppRouter = typeof appRouter;
diff --git a/packages/runtime/nodejs/src/api/trpc.ts b/packages/runtime/nodejs/src/api/trpc.ts
new file mode 100644
index 00000000..3a62ba7c
--- /dev/null
+++ b/packages/runtime/nodejs/src/api/trpc.ts
@@ -0,0 +1,16 @@
+import { OpenApiMeta } from 'trpc-openapi';
+import { initTRPC } from '@trpc/server';
+
+import type { Context } from '../server/context';
+
+const t = initTRPC
+ .context()
+ .meta()
+ .create({
+ errorFormatter({ shape }) {
+ return shape;
+ },
+ });
+
+export const router = t.router;
+export const publicProcedure = t.procedure;
diff --git a/packages/runtime/nodejs/src/api/type.ts b/packages/runtime/nodejs/src/api/type.ts
new file mode 100644
index 00000000..0f9aa56d
--- /dev/null
+++ b/packages/runtime/nodejs/src/api/type.ts
@@ -0,0 +1,9 @@
+import { BuildProcedure } from '@trpc/server';
+import { FlowGramAPIDefine } from '@flowgram.ai/runtime-interface';
+
+export interface APIHandler {
+ define: FlowGramAPIDefine;
+ procedure: BuildProcedure;
+}
+
+export type APIRouter = Record;
diff --git a/packages/runtime/nodejs/src/config/index.ts b/packages/runtime/nodejs/src/config/index.ts
new file mode 100644
index 00000000..9e50faa2
--- /dev/null
+++ b/packages/runtime/nodejs/src/config/index.ts
@@ -0,0 +1,13 @@
+import type { ServerParams } from '@server/type';
+
+export const ServerConfig: ServerParams = {
+ name: 'flowgram-runtime',
+ title: 'FlowGram Runtime',
+ description: 'FlowGram Runtime Demo',
+ runtime: 'nodejs',
+ version: '0.0.1',
+ dev: false,
+ port: 4000,
+ basePath: '/api',
+ docsPath: '/docs',
+};
diff --git a/packages/runtime/nodejs/src/index.ts b/packages/runtime/nodejs/src/index.ts
new file mode 100644
index 00000000..ce697545
--- /dev/null
+++ b/packages/runtime/nodejs/src/index.ts
@@ -0,0 +1,8 @@
+import { createServer } from '@server/index';
+
+async function main() {
+ const server = await createServer();
+ server.start();
+}
+
+main();
diff --git a/packages/runtime/nodejs/src/server/context.ts b/packages/runtime/nodejs/src/server/context.ts
new file mode 100644
index 00000000..20978063
--- /dev/null
+++ b/packages/runtime/nodejs/src/server/context.ts
@@ -0,0 +1,8 @@
+import type { CreateFastifyContextOptions } from '@trpc/server/adapters/fastify';
+
+export function createContext(ctx: CreateFastifyContextOptions) {
+ const { req, res } = ctx;
+ return { req, res };
+}
+
+export type Context = Awaited>;
diff --git a/packages/runtime/nodejs/src/server/docs.ts b/packages/runtime/nodejs/src/server/docs.ts
new file mode 100644
index 00000000..3748b1b7
--- /dev/null
+++ b/packages/runtime/nodejs/src/server/docs.ts
@@ -0,0 +1,14 @@
+import { generateOpenApiDocument } from 'trpc-openapi';
+
+import { ServerConfig } from '@config/index';
+import { appRouter } from '@api/index';
+
+// Generate OpenAPI schema document
+export const serverDocument = generateOpenApiDocument(appRouter, {
+ title: ServerConfig.title,
+ description: ServerConfig.description,
+ version: ServerConfig.version,
+ baseUrl: `http://localhost:${ServerConfig.port}${ServerConfig.basePath}`,
+ docsUrl: 'https://flowgram.ai',
+ tags: ['Task'],
+});
diff --git a/packages/runtime/nodejs/src/server/index.ts b/packages/runtime/nodejs/src/server/index.ts
new file mode 100644
index 00000000..3eba1b97
--- /dev/null
+++ b/packages/runtime/nodejs/src/server/index.ts
@@ -0,0 +1,89 @@
+import { fastifyTRPCOpenApiPlugin } from 'trpc-openapi';
+import fastify from 'fastify';
+import { fastifyTRPCPlugin } from '@trpc/server/adapters/fastify';
+import { ServerInfoDefine, type ServerInfoOutput } from '@flowgram.ai/runtime-interface';
+import ws from '@fastify/websocket';
+import fastifySwaggerUI from '@fastify/swagger-ui';
+import fastifySwagger from '@fastify/swagger';
+import cors from '@fastify/cors';
+
+import { ServerConfig } from '@config/index';
+import { appRouter } from '@api/index';
+import { serverDocument } from './docs';
+import { createContext } from './context';
+
+export async function createServer() {
+ const server = fastify({ logger: ServerConfig.dev });
+
+ await server.register(cors);
+ await server.register(ws);
+ await server.register(fastifyTRPCPlugin, {
+ prefix: '/trpc',
+ useWss: false,
+ trpcOptions: { router: appRouter, createContext },
+ });
+ await server.register(fastifyTRPCOpenApiPlugin, {
+ basePath: ServerConfig.basePath,
+ router: appRouter,
+ createContext,
+ } as any);
+
+ await server.register(fastifySwagger, {
+ mode: 'static',
+ specification: { document: serverDocument },
+ uiConfig: { displayOperationId: true },
+ exposeRoute: true,
+ } as any);
+
+ await server.register(fastifySwaggerUI, {
+ routePrefix: ServerConfig.docsPath,
+ uiConfig: {
+ docExpansion: 'full',
+ deepLinking: false,
+ },
+ uiHooks: {
+ onRequest: function (request, reply, next) {
+ next();
+ },
+ preHandler: function (request, reply, next) {
+ next();
+ },
+ },
+ staticCSP: true,
+ transformStaticCSP: (header) => header,
+ transformSpecification: (swaggerObject, request, reply) => swaggerObject,
+ transformSpecificationClone: true,
+ });
+
+ server.get(ServerInfoDefine.path, async (): Promise => {
+ const serverTime = new Date();
+ const output: ServerInfoOutput = {
+ name: ServerConfig.name,
+ title: ServerConfig.title,
+ description: ServerConfig.description,
+ runtime: ServerConfig.runtime,
+ version: ServerConfig.version,
+ time: serverTime.toISOString(),
+ };
+ return output;
+ });
+
+ const stop = async () => {
+ await server.close();
+ };
+ const start = async () => {
+ try {
+ const address = await server.listen({ port: ServerConfig.port });
+ await server.ready();
+ server.swagger();
+ console.log(
+ `> Listen Port: ${ServerConfig.port}\n> Server Address: ${address}\n> API Docs: http://localhost:4000/docs`
+ );
+ } catch (err) {
+ server.log.error(err);
+ process.exit(1);
+ }
+ };
+
+ return { server, start, stop };
+}
diff --git a/packages/runtime/nodejs/src/server/type.ts b/packages/runtime/nodejs/src/server/type.ts
new file mode 100644
index 00000000..c40f8cb8
--- /dev/null
+++ b/packages/runtime/nodejs/src/server/type.ts
@@ -0,0 +1,8 @@
+import { ServerInfoOutput } from '@flowgram.ai/runtime-interface';
+
+export interface ServerParams extends Omit {
+ dev: boolean;
+ port: number;
+ basePath: string;
+ docsPath: string;
+}
diff --git a/packages/runtime/nodejs/tsconfig.json b/packages/runtime/nodejs/tsconfig.json
new file mode 100644
index 00000000..26bd95ea
--- /dev/null
+++ b/packages/runtime/nodejs/tsconfig.json
@@ -0,0 +1,48 @@
+{
+ "extends": "@flowgram.ai/ts-config/tsconfig.flow.path.json",
+ "compilerOptions": {
+ "target": "esnext",
+ "lib": [
+ "dom",
+ "dom.iterable",
+ "esnext"
+ ],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "plugins": [],
+ "baseUrl": "src",
+ "paths": {
+ "@api/*": [
+ "api/*"
+ ],
+ "@application/*": [
+ "application/*"
+ ],
+ "@server/*": [
+ "server/*"
+ ],
+ "@config/*": [
+ "config/*"
+ ],
+ "@workflow/*": [
+ "workflow/*"
+ ]
+ }
+ },
+ "include": [
+ "**/*.ts",
+ "**/*.tsx",
+ "src/workflow/executor/condition/constant.ts"
+ ],
+ "exclude": [
+ "node_modules"
+ ]
+}
diff --git a/packages/runtime/nodejs/vitest.config.ts b/packages/runtime/nodejs/vitest.config.ts
new file mode 100644
index 00000000..1e8c3611
--- /dev/null
+++ b/packages/runtime/nodejs/vitest.config.ts
@@ -0,0 +1,40 @@
+import { config } from "dotenv";
+import path from 'path';
+import { defineConfig } from 'vitest/config';
+
+export default defineConfig({
+ build: {
+ commonjsOptions: {
+ transformMixedEsModules: true,
+ },
+ },
+ resolve: {
+ alias: [
+ {find: "@api", replacement: path.resolve(__dirname, './src/api') },
+ {find: "@application", replacement: path.resolve(__dirname, './src/application') },
+ {find: "@server", replacement: path.resolve(__dirname, './src/server') },
+ {find: "@config", replacement: path.resolve(__dirname, './src/config') },
+ {find: "@workflow", replacement: path.resolve(__dirname, './src/workflow') },
+ ],
+ },
+ test: {
+ globals: true,
+ mockReset: false,
+ environment: 'jsdom',
+ testTimeout: 15000,
+ setupFiles: [path.resolve(__dirname, './src/workflow/__tests__/setup.ts')],
+ include: ['**/?(*.){test,spec}.?(c|m)[jt]s?(x)'],
+ exclude: [
+ '**/__mocks__**',
+ '**/node_modules/**',
+ '**/dist/**',
+ '**/lib/**', // lib 编译结果忽略掉
+ '**/cypress/**',
+ '**/.{idea,git,cache,output,temp}/**',
+ '**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build}.config.*',
+ ],
+ env: {
+ ...config({ path: path.resolve(__dirname, './.env/.env.test') }).parsed
+ }
+ },
+});
diff --git a/rush.json b/rush.json
index d55c3431..7d6641a1 100644
--- a/rush.json
+++ b/rush.json
@@ -4,7 +4,6 @@
*/
{
"$schema": "https://developer.microsoft.com/json-schemas/rush/v5/rush.schema.json",
-
/**
* (Required) This specifies the version of the Rush engine to be used in this repo.
* Rush's "version selector" feature ensures that the globally installed tool will
@@ -17,7 +16,6 @@
* correct error-underlining and tab-completion for editors such as VS Code.
*/
"rushVersion": "5.140.1",
-
/**
* The next field selects which package manager should be installed and determines its version.
* Rush installs its own local copy of the package manager to ensure that your build process
@@ -27,10 +25,8 @@
* for details about these alternatives.
*/
"pnpmVersion": "8.15.8",
-
// "npmVersion": "6.14.15",
// "yarnVersion": "1.9.4",
-
/**
* Older releases of the Node.js engine may be missing features required by your system.
* Other releases may have bugs. In particular, the "latest" version will not be a
@@ -43,7 +39,6 @@
* LTS versions: https://nodejs.org/en/download/releases/
*/
"nodeSupportedVersionRange": ">=18.20.3 <19.0.0 || >=20.14.0 <23.0.0",
-
/**
* If the version check above fails, Rush will display a message showing the current
* node version and the supported version range. You can use this setting to provide
@@ -51,7 +46,6 @@
* tool or script you'd like the user to use to get in line with the expected version.
*/
// "nodeSupportedVersionInstructions": "Run 'nvs use' to switch to the expected node version.",
-
/**
* Odd-numbered major versions of Node.js are experimental. Even-numbered releases
* spend six months in a stabilization period before the first Long Term Support (LTS) version.
@@ -64,14 +58,12 @@
* to disable Rush's warning.
*/
// "suppressNodeLtsWarning": false,
-
/**
* Rush normally prints a warning if it detects that the current version is not one published to the
* public npmjs.org registry. If you need to block calls to the npm registry, you can use this setting to disable
* Rush's check.
*/
// "suppressRushIsPublicVersionCheck": false,
-
/**
* Large monorepos can become intimidating for newcomers if project folder paths don't follow
* a consistent and recognizable pattern. When the system allows nested folder trees,
@@ -97,7 +89,6 @@
*/
"projectFolderMinDepth": 2,
"projectFolderMaxDepth": 4,
-
/**
* Today the npmjs.com registry enforces fairly strict naming rules for packages, but in the early
* days there was no standard and hardly any enforcement. A few large legacy projects are still using
@@ -110,7 +101,6 @@
* The default value is false.
*/
// "allowMostlyStandardPackageNames": true,
-
/**
* This feature helps you to review and approve new packages before they are introduced
* to your monorepo. For example, you may be concerned about licensing, code quality,
@@ -146,7 +136,6 @@
// */
// // "ignoredNpmScopes": ["@types"]
// },
-
/**
* If you use Git as your version control system, this section has some additional
* optional features you can use.
@@ -196,7 +185,6 @@
*/
// "changefilesCommitMessage": "Rush change"
},
-
"repository": {
/**
* The URL of this Git repository, used by "rush change" to determine the base branch for your PR.
@@ -225,7 +213,6 @@
*/
// "defaultRemote": "origin"
},
-
/**
* Event hooks are customized script actions that Rush executes when specific events occur
*/
@@ -236,33 +223,27 @@
"preRushInstall": [
// "common/scripts/pre-rush-install.js"
],
-
/**
* A list of shell commands to run after "rush install" or "rush update" finishes installation
*/
"postRushInstall": [],
-
/**
* A list of shell commands to run before "rush build" or "rush rebuild" starts building
*/
"preRushBuild": [],
-
/**
* A list of shell commands to run after "rush build" or "rush rebuild" finishes building
*/
"postRushBuild": [],
-
/**
* A list of shell commands to run before the "rushx" command starts
*/
"preRushx": [],
-
/**
* A list of shell commands to run after the "rushx" command finishes
*/
"postRushx": []
},
-
/**
* Installation variants allow you to maintain a parallel set of configuration files that can be
* used to build the entire monorepo with an alternate set of dependencies. For example, suppose
@@ -293,7 +274,6 @@
// "description": "Build this repo using the previous release of the SDK"
// }
],
-
/**
* Rush can collect anonymous telemetry about everyday developer activity such as
* success/failure of installs, builds, and other operations. You can use this to identify
@@ -303,14 +283,12 @@
* in the "eventHooks" section.
*/
// "telemetryEnabled": false,
-
/**
* Allows creation of hotfix changes. This feature is experimental so it is disabled by default.
* If this is set, 'rush change' only allows a 'hotfix' change type to be specified. This change type
* will be used when publishing subsequent changes from the monorepo.
*/
// "hotfixChangeEnabled": false,
-
/**
* This is an optional, but recommended, list of allowed tags that can be applied to Rush projects
* using the "tags" setting in this file. This list is useful for preventing mistakes such as misspelling,
@@ -320,7 +298,6 @@
* ".", and "@" characters.
*/
// "allowedProjectTags": [ "tools", "frontend-team", "1.0.0-release" ],
-
/**
* (Required) This is the inventory of projects to be managed by Rush.
*
@@ -431,368 +408,581 @@
{
"packageName": "@flowgram.ai/e2e-fixed-layout",
"projectFolder": "e2e/fixed-layout",
- "tags": ["e2e"]
+ "tags": [
+ "e2e"
+ ]
},
{
"packageName": "@flowgram.ai/e2e-free-layout",
"projectFolder": "e2e/free-layout",
- "tags": ["e2e"]
+ "tags": [
+ "e2e"
+ ]
},
// eslint 通用配置
{
"packageName": "@flowgram.ai/eslint-config",
"projectFolder": "config/eslint-config",
"versionPolicyName": "publishPolicy",
- "tags": ["config"]
+ "tags": [
+ "config"
+ ]
},
// ts 通用配置
{
"packageName": "@flowgram.ai/ts-config",
"projectFolder": "config/ts-config",
"versionPolicyName": "publishPolicy",
- "tags": ["config"]
+ "tags": [
+ "config"
+ ]
},
{
"packageName": "@flowgram.ai/create-app",
"projectFolder": "apps/create-app",
"versionPolicyName": "publishPolicy",
- "tags": ["cli"]
+ "tags": [
+ "cli"
+ ]
},
// 官网
{
"packageName": "@flowgram.ai/docs",
"projectFolder": "apps/docs",
- "tags": ["docs"]
+ "tags": [
+ "docs"
+ ]
},
// demos
{
"packageName": "@flowgram.ai/demo-fixed-layout",
"projectFolder": "apps/demo-fixed-layout",
- "tags": ["level-1", "team-flow", "demo"],
+ "tags": [
+ "level-1",
+ "team-flow",
+ "demo"
+ ],
"versionPolicyName": "appPolicy"
},
{
"packageName": "@flowgram.ai/utils",
"projectFolder": "packages/common/utils",
"versionPolicyName": "publishPolicy",
- "tags": ["level-1", "team-flow"]
+ "tags": [
+ "level-1",
+ "team-flow"
+ ]
},
{
"packageName": "@flowgram.ai/core",
"projectFolder": "packages/canvas-engine/core",
"versionPolicyName": "publishPolicy",
- "tags": ["level-1", "team-flow"]
+ "tags": [
+ "level-1",
+ "team-flow"
+ ]
},
{
"packageName": "@flowgram.ai/document",
"projectFolder": "packages/canvas-engine/document",
"versionPolicyName": "publishPolicy",
- "tags": ["level-1", "team-flow"]
+ "tags": [
+ "level-1",
+ "team-flow"
+ ]
},
{
"packageName": "@flowgram.ai/renderer",
"projectFolder": "packages/canvas-engine/renderer",
"versionPolicyName": "publishPolicy",
- "tags": ["level-1", "team-flow"]
+ "tags": [
+ "level-1",
+ "team-flow"
+ ]
},
{
"packageName": "@flowgram.ai/reactive",
"projectFolder": "packages/common/reactive",
"versionPolicyName": "publishPolicy",
- "tags": ["level-1", "team-flow"]
+ "tags": [
+ "level-1",
+ "team-flow"
+ ]
},
{
"packageName": "@flowgram.ai/background-plugin",
"projectFolder": "packages/plugins/background-plugin",
"versionPolicyName": "publishPolicy",
- "tags": ["level-1", "team-flow"]
+ "tags": [
+ "level-1",
+ "team-flow"
+ ]
},
{
"packageName": "@flowgram.ai/fixed-layout-core",
"projectFolder": "packages/canvas-engine/fixed-layout-core",
"versionPolicyName": "publishPolicy",
- "tags": ["level-1", "team-flow"]
+ "tags": [
+ "level-1",
+ "team-flow"
+ ]
},
{
"packageName": "@flowgram.ai/free-layout-core",
"projectFolder": "packages/canvas-engine/free-layout-core",
"versionPolicyName": "publishPolicy",
- "tags": ["level-1", "team-flow"]
+ "tags": [
+ "level-1",
+ "team-flow"
+ ]
},
{
"packageName": "@flowgram.ai/editor",
"projectFolder": "packages/client/editor",
"versionPolicyName": "publishPolicy",
- "tags": ["level-1", "team-flow"]
+ "tags": [
+ "level-1",
+ "team-flow"
+ ]
},
{
"packageName": "@flowgram.ai/fixed-drag-plugin",
"projectFolder": "packages/plugins/fixed-drag-plugin",
"versionPolicyName": "publishPolicy",
- "tags": ["level-1", "team-flow"]
+ "tags": [
+ "level-1",
+ "team-flow"
+ ]
},
{
"packageName": "@flowgram.ai/fixed-history-plugin",
"projectFolder": "packages/plugins/fixed-history-plugin",
"versionPolicyName": "publishPolicy",
- "tags": ["level-1", "team-flow"]
+ "tags": [
+ "level-1",
+ "team-flow"
+ ]
},
{
"packageName": "@flowgram.ai/fixed-layout-editor",
"projectFolder": "packages/client/fixed-layout-editor",
"versionPolicyName": "publishPolicy",
- "tags": ["level-1", "team-flow"]
+ "tags": [
+ "level-1",
+ "team-flow"
+ ]
},
{
"packageName": "@flowgram.ai/fixed-reactor-plugin",
"projectFolder": "packages/plugins/fixed-reactor-plugin",
"versionPolicyName": "publishPolicy",
- "tags": ["level-1", "team-flow"]
+ "tags": [
+ "level-1",
+ "team-flow"
+ ]
},
{
"packageName": "@flowgram.ai/form",
"projectFolder": "packages/node-engine/form",
"versionPolicyName": "publishPolicy",
- "tags": ["level-1", "team-flow"]
+ "tags": [
+ "level-1",
+ "team-flow"
+ ]
},
{
"packageName": "@flowgram.ai/form-core",
"projectFolder": "packages/node-engine/form-core",
"versionPolicyName": "publishPolicy",
- "tags": ["level-1", "team-flow"]
+ "tags": [
+ "level-1",
+ "team-flow"
+ ]
},
{
"packageName": "@flowgram.ai/free-history-plugin",
"projectFolder": "packages/plugins/free-history-plugin",
"versionPolicyName": "publishPolicy",
- "tags": ["level-1", "team-flow"]
+ "tags": [
+ "level-1",
+ "team-flow"
+ ]
},
{
"packageName": "@flowgram.ai/free-hover-plugin",
"projectFolder": "packages/plugins/free-hover-plugin",
"versionPolicyName": "publishPolicy",
- "tags": ["level-1", "team-flow"]
+ "tags": [
+ "level-1",
+ "team-flow"
+ ]
},
{
"packageName": "@flowgram.ai/free-layout-editor",
"projectFolder": "packages/client/free-layout-editor",
"versionPolicyName": "publishPolicy",
- "tags": ["level-1", "team-flow"]
+ "tags": [
+ "level-1",
+ "team-flow"
+ ]
},
{
"packageName": "@flowgram.ai/free-lines-plugin",
"projectFolder": "packages/plugins/free-lines-plugin",
"versionPolicyName": "publishPolicy",
- "tags": ["level-1", "team-flow"]
+ "tags": [
+ "level-1",
+ "team-flow"
+ ]
},
{
"packageName": "@flowgram.ai/free-node-panel-plugin",
"projectFolder": "packages/plugins/free-node-panel-plugin",
"versionPolicyName": "publishPolicy",
- "tags": ["level-1", "team-flow"]
+ "tags": [
+ "level-1",
+ "team-flow"
+ ]
},
{
"packageName": "@flowgram.ai/free-snap-plugin",
"projectFolder": "packages/plugins/free-snap-plugin",
"versionPolicyName": "publishPolicy",
- "tags": ["level-1", "team-flow"]
+ "tags": [
+ "level-1",
+ "team-flow"
+ ]
},
{
"packageName": "@flowgram.ai/free-stack-plugin",
"projectFolder": "packages/plugins/free-stack-plugin",
"versionPolicyName": "publishPolicy",
- "tags": ["level-1", "team-flow"]
+ "tags": [
+ "level-1",
+ "team-flow"
+ ]
},
{
"packageName": "@flowgram.ai/free-container-plugin",
"projectFolder": "packages/plugins/free-container-plugin",
"versionPolicyName": "publishPolicy",
- "tags": ["level-1", "team-flow"]
+ "tags": [
+ "level-1",
+ "team-flow"
+ ]
},
{
"packageName": "@flowgram.ai/free-group-plugin",
"projectFolder": "packages/plugins/free-group-plugin",
"versionPolicyName": "publishPolicy",
- "tags": ["level-1", "team-flow"]
+ "tags": [
+ "level-1",
+ "team-flow"
+ ]
},
{
"packageName": "@flowgram.ai/group-plugin",
"projectFolder": "packages/plugins/group-plugin",
"versionPolicyName": "publishPolicy",
- "tags": ["level-1", "team-flow"]
+ "tags": [
+ "level-1",
+ "team-flow"
+ ]
},
{
"packageName": "@flowgram.ai/history-node-plugin",
"projectFolder": "packages/plugins/history-node-plugin",
"versionPolicyName": "publishPolicy",
- "tags": ["level-1", "team-flow"]
+ "tags": [
+ "level-1",
+ "team-flow"
+ ]
},
{
"packageName": "@flowgram.ai/minimap-plugin",
"projectFolder": "packages/plugins/minimap-plugin",
"versionPolicyName": "publishPolicy",
- "tags": ["level-1", "team-flow"]
+ "tags": [
+ "level-1",
+ "team-flow"
+ ]
},
{
"packageName": "@flowgram.ai/node",
"projectFolder": "packages/node-engine/node",
"versionPolicyName": "publishPolicy",
- "tags": ["level-1", "team-flow"]
+ "tags": [
+ "level-1",
+ "team-flow"
+ ]
},
{
"packageName": "@flowgram.ai/node-core-plugin",
"projectFolder": "packages/plugins/node-core-plugin",
"versionPolicyName": "publishPolicy",
- "tags": ["level-1", "team-flow"]
+ "tags": [
+ "level-1",
+ "team-flow"
+ ]
},
{
"packageName": "@flowgram.ai/node-variable-plugin",
"projectFolder": "packages/plugins/node-variable-plugin",
"versionPolicyName": "publishPolicy",
- "tags": ["level-1", "team-flow"]
+ "tags": [
+ "level-1",
+ "team-flow"
+ ]
},
{
"packageName": "@flowgram.ai/playground-react",
"projectFolder": "packages/client/playground-react",
"versionPolicyName": "publishPolicy",
- "tags": ["level-1", "team-flow"]
+ "tags": [
+ "level-1",
+ "team-flow"
+ ]
},
{
"packageName": "@flowgram.ai/redux-devtool-plugin",
"projectFolder": "packages/plugins/redux-devtool-plugin",
"versionPolicyName": "publishPolicy",
- "tags": ["level-1", "team-flow"]
+ "tags": [
+ "level-1",
+ "team-flow"
+ ]
},
{
"packageName": "@flowgram.ai/select-box-plugin",
"projectFolder": "packages/plugins/select-box-plugin",
"versionPolicyName": "publishPolicy",
- "tags": ["level-1", "team-flow"]
+ "tags": [
+ "level-1",
+ "team-flow"
+ ]
},
{
"packageName": "@flowgram.ai/shortcuts-plugin",
"projectFolder": "packages/plugins/shortcuts-plugin",
"versionPolicyName": "publishPolicy",
- "tags": ["level-1", "team-flow"]
+ "tags": [
+ "level-1",
+ "team-flow"
+ ]
},
{
"packageName": "@flowgram.ai/variable-core",
"projectFolder": "packages/variable-engine/variable-core",
"versionPolicyName": "publishPolicy",
- "tags": ["level-1", "team-flow"]
+ "tags": [
+ "level-1",
+ "team-flow"
+ ]
},
{
"packageName": "@flowgram.ai/variable-layout",
"projectFolder": "packages/variable-engine/variable-layout",
"versionPolicyName": "publishPolicy",
- "tags": ["level-1", "team-flow"]
+ "tags": [
+ "level-1",
+ "team-flow"
+ ]
},
{
"packageName": "@flowgram.ai/variable-plugin",
"projectFolder": "packages/plugins/variable-plugin",
"versionPolicyName": "publishPolicy",
- "tags": ["level-1", "team-flow"]
+ "tags": [
+ "level-1",
+ "team-flow"
+ ]
},
{
"packageName": "@flowgram.ai/command",
"projectFolder": "packages/common/command",
"versionPolicyName": "publishPolicy",
- "tags": ["level-1", "team-flow"]
+ "tags": [
+ "level-1",
+ "team-flow"
+ ]
},
{
"packageName": "@flowgram.ai/history",
"projectFolder": "packages/common/history",
"versionPolicyName": "publishPolicy",
- "tags": ["level-1", "team-flow"]
+ "tags": [
+ "level-1",
+ "team-flow"
+ ]
},
{
"packageName": "@flowgram.ai/history-storage",
"projectFolder": "packages/common/history-storage",
"versionPolicyName": "publishPolicy",
- "tags": ["level-2", "team-flow"]
+ "tags": [
+ "level-2",
+ "team-flow"
+ ]
},
{
"packageName": "@flowgram.ai/materials-plugin",
"projectFolder": "packages/plugins/materials-plugin",
"versionPolicyName": "publishPolicy",
- "tags": ["level-1", "team-flow"]
+ "tags": [
+ "level-1",
+ "team-flow"
+ ]
},
{
"packageName": "@flowgram.ai/fixed-semi-materials",
"projectFolder": "packages/materials/fixed-semi-materials",
"versionPolicyName": "publishPolicy",
- "tags": ["level-1", "team-flow"]
+ "tags": [
+ "level-1",
+ "team-flow"
+ ]
},
{
- "packageName": "@flowgram.ai/form-materials",
- "projectFolder": "packages/materials/form-materials",
- "versionPolicyName": "publishPolicy",
- "tags": ["level-1", "team-flow"]
+ "packageName": "@flowgram.ai/form-materials",
+ "projectFolder": "packages/materials/form-materials",
+ "versionPolicyName": "publishPolicy",
+ "tags": [
+ "level-1",
+ "team-flow"
+ ]
},
{
"packageName": "@flowgram.ai/free-auto-layout-plugin",
"projectFolder": "packages/plugins/free-auto-layout-plugin",
"versionPolicyName": "publishPolicy",
- "tags": ["level-1", "team-flow"]
+ "tags": [
+ "level-1",
+ "team-flow"
+ ]
},
{
"packageName": "@flowgram.ai/i18n-plugin",
"projectFolder": "packages/plugins/i18n-plugin",
"versionPolicyName": "publishPolicy",
- "tags": ["level-1", "team-flow"]
+ "tags": [
+ "level-1",
+ "team-flow"
+ ]
},
{
"packageName": "@flowgram.ai/i18n",
"projectFolder": "packages/common/i18n",
"versionPolicyName": "publishPolicy",
- "tags": ["level-1", "team-flow"]
+ "tags": [
+ "level-1",
+ "team-flow"
+ ]
},
{
"packageName": "@flowgram.ai/demo-free-layout",
"projectFolder": "apps/demo-free-layout",
- "tags": ["level-1", "team-flow", "demo"],
+ "tags": [
+ "level-1",
+ "team-flow",
+ "demo"
+ ],
"versionPolicyName": "appPolicy"
},
{
"packageName": "@flowgram.ai/demo-fixed-layout-simple",
"projectFolder": "apps/demo-fixed-layout-simple",
"versionPolicyName": "appPolicy",
- "tags": ["level-1", "team-flow", "demo"]
+ "tags": [
+ "level-1",
+ "team-flow",
+ "demo"
+ ]
},
{
"packageName": "@flowgram.ai/demo-free-layout-simple",
"projectFolder": "apps/demo-free-layout-simple",
"versionPolicyName": "appPolicy",
- "tags": ["level-1", "team-flow", "demo"]
+ "tags": [
+ "level-1",
+ "team-flow",
+ "demo"
+ ]
},
{
"packageName": "@flowgram.ai/demo-node-form",
"projectFolder": "apps/demo-node-form",
- "tags": ["level-1", "team-flow", "demo"]
+ "tags": [
+ "level-1",
+ "team-flow",
+ "demo"
+ ]
},
{
"packageName": "@flowgram.ai/demo-nextjs",
"projectFolder": "apps/demo-nextjs",
"versionPolicyName": "appPolicy",
- "tags": ["level-1", "team-flow", "demo"]
+ "tags": [
+ "level-1",
+ "team-flow",
+ "demo"
+ ]
},
{
"packageName": "@flowgram.ai/demo-react-16",
"projectFolder": "apps/demo-react-16",
"versionPolicyName": "appPolicy",
- "tags": ["level-1", "team-flow", "demo"]
+ "tags": [
+ "level-1",
+ "team-flow",
+ "demo"
+ ]
},
{
"packageName": "@flowgram.ai/demo-vite",
"projectFolder": "apps/demo-vite",
"versionPolicyName": "appPolicy",
- "tags": ["level-1", "team-flow", "demo"]
+ "tags": [
+ "level-1",
+ "team-flow",
+ "demo"
+ ]
},
{
"packageName": "@flowgram.ai/demo-playground",
"projectFolder": "apps/demo-playground",
"versionPolicyName": "appPolicy",
- "tags": ["level-1", "team-flow", "demo"]
+ "tags": [
+ "level-1",
+ "team-flow",
+ "demo"
+ ]
+ },
+ {
+ "packageName": "@flowgram.ai/runtime-nodejs",
+ "projectFolder": "packages/runtime/nodejs",
+ "versionPolicyName": "appPolicy",
+ "tags": [
+ "level-1",
+ "team-flow"
+ ]
+ },
+ {
+ "packageName": "@flowgram.ai/runtime-js",
+ "projectFolder": "packages/runtime/js-core",
+ "versionPolicyName": "appPolicy",
+ "tags": [
+ "level-1",
+ "team-flow"
+ ]
+ },
+ {
+ "packageName": "@flowgram.ai/runtime-interface",
+ "projectFolder": "packages/runtime/interface",
+ "versionPolicyName": "appPolicy",
+ "tags": [
+ "level-1",
+ "team-flow"
+ ]
}
]
}