export type FlowVisibility = 'PUBLIC' | 'PRIVATE'; export type FlowStatus = 'DRAFT' | 'EXECUTABLE'; export type Flow = { id: string; name: string; visibility: FlowVisibility; data: FlowData; author: string; description?: string; createdAt: Date; status: FlowStatus; updatedAt: Date; published?: boolean; finalized?: boolean; validationErrors?: FlowValidationError[]; }; export type FlowData = { blocks: FlowBlock[]; containers: FlowContainer[]; connections: FlowBlockConnection[]; dependencies: FlowNodeDependency[]; globalInputs?: FlowGlobalInput[]; }; export type FlowGlobalInput = { name: string; type: string; multiple: boolean; }; export type FlowSubflowValidationError = { entity?: string; id?: string; field?: string; message: string; }; export type FlowSubflowValidationResult = { valid: boolean; errors: FlowSubflowValidationError[]; openInputs: FlowContainerOpenInput[]; openOutputs: FlowContainerOpenOutput[]; }; export type BlockTypeName = "HumanInteractionBlock" | "LLMBlock" | "SourceBlock" | string; export type NodeFamily = 'block' | 'container'; export type BlockTypeSchema = Record | null; export type BlockInteractionContractKind = 'chat-session' | 'single-response' | string; export type BlockInteractionContract = { kind: BlockInteractionContractKind; messageField: string | null; completionField: string | null; historyField: string | null; responseField: string | null; supportsPartialResult: boolean; }; export type BlockType = { type: BlockTypeName; family: NodeFamily; description: string; userInteractive: boolean; interactionContract?: BlockInteractionContract | null; hasExampleBlock?: boolean; exampleBlockEndpoint?: string | null; configurationType: string | null; configurationClass: string | null; schema: BlockTypeSchema; }; export type FlowNodeBase = { id: string; name: string; position?: { x: number, y: number }; inputs: FlowPort[]; outputs: FlowPort[]; specificConfiguration: FlowBlockConfiguration; typeName: BlockTypeName; nodeFamily?: NodeFamily; }; export type FlowBlock = FlowNodeBase & { nodeFamily?: 'block'; }; export type FlowContainer = FlowNodeBase & { nodeFamily: 'container'; }; export type FlowNode = FlowBlock | FlowContainer; export type FlowValueKind = { type: string; multiple: boolean; }; export type FlowPort = { name: string; type: string; multiple: boolean; valueKinds?: FlowValueKind[]; }; export type FlowBlockConnection = { id: string; sourceId: string; sourceName: string; targetId: string; targetName: string; }; export type FlowNodeDependency = { sourceId: string; targetId: string; }; export type FlowValidationError = { code?: string | null; entity?: string | null; id?: string | null; field?: string | null; message: string; relatedNodeIds?: string[]; }; export const FLOW_DEPENDANT_PORT_KEY = '__dependant'; export const FLOW_DEPENDENCY_PORT_KEY = '__dependency'; export const FLOW_DEPENDENCY_SOCKET_TYPE = '__FLOW_DEPENDENCY__'; export function normalizeFlowValidationErrors(raw: unknown): FlowValidationError[] { if (!Array.isArray(raw)) return []; return raw .filter((item): item is Record => !!item && typeof item === 'object' && !Array.isArray(item)) .map((item) => ({ code: typeof item['code'] === 'string' ? item['code'] : null, entity: typeof item['entity'] === 'string' ? item['entity'] : null, id: typeof item['id'] === 'string' ? item['id'] : null, field: typeof item['field'] === 'string' ? item['field'] : null, message: String(item['message'] ?? item['error'] ?? 'Validation error'), relatedNodeIds: normalizeRelatedNodeIds(item) })); } function normalizeRelatedNodeIds(item: Record): string[] { const explicitNodeIds = Array.isArray(item['relatedNodeIds']) ? item['relatedNodeIds'].map((value) => String(value)).filter((value) => value.length > 0) : []; if (explicitNodeIds.length > 0) { return Array.from(new Set(explicitNodeIds)); } const entity = typeof item['entity'] === 'string' ? item['entity'].toLowerCase() : ''; const id = typeof item['id'] === 'string' ? item['id'].trim() : ''; if ((entity === 'block' || entity === 'container') && id.length > 0) { return [id]; } return []; } export type FlowBlockConfiguration = | LLMBlockConfiguration | HumanInteractiveBlockConfiguration | Record; export type FlowContainerOpenInput = FlowPort & { targetBlockId?: string; targetInputName?: string; blockId?: string; inputName?: string; }; export type FlowContainerOpenOutput = FlowPort & { sourceBlockId?: string; sourceOutputName?: string; blockId?: string; outputName?: string; }; export type LLMDescriptor = { provider: string; model: string; }; export type LLMBlockConfiguration = { type: "LLMBlockConfiguration"; name: string; llmDescriptor: LLMDescriptor; prompt: string; }; export type HumanInteractiveBlockConfiguration = { type: "HumanInteractiveBlockConfiguration"; name: string; actionDescription: string; llmDescriptor: LLMDescriptor; inputAsList: boolean; outputAsList: boolean; }; export function currentFlowPortValueKind(port: FlowPort): FlowValueKind { return { type: String(port.type ?? 'ANY').toUpperCase(), multiple: Boolean(port.multiple) }; } export function normalizeFlowPortValueKinds(port: FlowPort): FlowValueKind[] { const rawKinds = Array.isArray(port.valueKinds) ? port.valueKinds : []; const normalized = rawKinds .filter((kind): kind is FlowValueKind => !!kind && typeof kind.type === 'string') .map((kind) => ({ type: String(kind.type).toUpperCase(), multiple: Boolean(kind.multiple) })); if (!normalized.length) { return [currentFlowPortValueKind(port)]; } const unique = new Map(); for (const kind of normalized) { unique.set(`${kind.type}:${kind.multiple ? 'multi' : 'single'}`, kind); } return Array.from(unique.values()); } export function flowValueKindLabel(kind: FlowValueKind): string { const type = String(kind.type ?? 'ANY').toUpperCase(); return kind.multiple ? `${type}[]` : type; } export function areFlowValueKindsCompatible(sourceKinds: FlowValueKind[], targetKinds: FlowValueKind[]): boolean { return sourceKinds.some((source) => targetKinds.some((target) => { if (Boolean(source.multiple) !== Boolean(target.multiple)) return false; const sourceType = String(source.type ?? 'ANY').toUpperCase(); const targetType = String(target.type ?? 'ANY').toUpperCase(); return sourceType === 'ANY' || targetType === 'ANY' || sourceType === targetType; }) ); }