@scenario-labs/sdk 2.6.0 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/lib/workflow_converter.d.mts.map +1 -1
- package/lib/workflow_converter.d.ts.map +1 -1
- package/lib/workflow_converter.js +101 -22
- package/lib/workflow_converter.js.map +1 -1
- package/lib/workflow_converter.mjs +101 -22
- package/lib/workflow_converter.mjs.map +1 -1
- package/package.json +1 -1
- package/resources/models/examples.d.mts +2 -1
- package/resources/models/examples.d.mts.map +1 -1
- package/resources/models/examples.d.ts +2 -1
- package/resources/models/examples.d.ts.map +1 -1
- package/src/lib/workflow_converter.ts +119 -24
- package/src/resources/models/examples.ts +2 -1
- package/src/version.ts +1 -1
- package/version.d.mts +1 -1
- package/version.d.ts +1 -1
- package/version.js +1 -1
- package/version.mjs +1 -1
|
@@ -501,7 +501,8 @@ function getSourceRef(
|
|
|
501
501
|
}
|
|
502
502
|
|
|
503
503
|
if (targetNode.type === 'ifElse') {
|
|
504
|
-
|
|
504
|
+
// Approval handles are UI-only and must not shift case/else indices.
|
|
505
|
+
const handles = (targetNode.data.outputHandles ?? []).filter((h) => h.type !== 'approval');
|
|
505
506
|
const handleIndex = handles.findIndex((h) => h.id === edge.targetHandle);
|
|
506
507
|
if (handleIndex !== -1) {
|
|
507
508
|
const targetFlowId = nodeIdToFlowIdMap.get(targetNodeId);
|
|
@@ -572,6 +573,38 @@ function getIsSliceAssetsItemsSourceHandle(nodeId: string, sourceHandle: string
|
|
|
572
573
|
return sourceHandle === `${nodeId}-source-items` || sourceHandle === `${nodeId}-source-assets`;
|
|
573
574
|
}
|
|
574
575
|
|
|
576
|
+
function getConcatListSourceDescriptor(
|
|
577
|
+
targetNode: WorkflowEditorNode,
|
|
578
|
+
outHandle: WorkflowEditorHandleOutput | undefined,
|
|
579
|
+
outHandleName: string,
|
|
580
|
+
): { isArray: boolean; inputType: string } {
|
|
581
|
+
const isArray =
|
|
582
|
+
(targetNode.type === 'asset' && !!(targetNode.data as { isMultiple?: boolean }).isMultiple) ||
|
|
583
|
+
targetNode.type === 'groupItems' ||
|
|
584
|
+
targetNode.type === 'sliceAssets' ||
|
|
585
|
+
targetNode.type === 'splitText' ||
|
|
586
|
+
(targetNode.type === 'llm' && outHandleName === 'outputs') ||
|
|
587
|
+
!!outHandle?.isArray ||
|
|
588
|
+
outHandleName === 'allOutputs';
|
|
589
|
+
|
|
590
|
+
if (!isArray) {
|
|
591
|
+
return {
|
|
592
|
+
isArray: false,
|
|
593
|
+
inputType: outHandle?.type === 'text' ? 'string' : 'file',
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
const isTextList =
|
|
598
|
+
targetNode.type === 'splitText' ||
|
|
599
|
+
(targetNode.type === 'llm' && outHandleName === 'outputs') ||
|
|
600
|
+
outHandle?.type === 'text';
|
|
601
|
+
|
|
602
|
+
return {
|
|
603
|
+
isArray: true,
|
|
604
|
+
inputType: isTextList ? 'string_array' : 'file_array',
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
|
|
575
608
|
/**
|
|
576
609
|
* Builds the concat variables and flow inputs for a list-concatenating node
|
|
577
610
|
* (groupItems, sliceAssets in randomize mode) from its incoming edges.
|
|
@@ -595,18 +628,14 @@ function collectConcatVarsAndInputs(
|
|
|
595
628
|
const outHandle = targetNode.data.outputHandles?.find((h) => h.id === edge.targetHandle);
|
|
596
629
|
const outHandleName = outHandle?.name ?? 'output';
|
|
597
630
|
const varName = getVarName(targetNode.id, outHandleName);
|
|
598
|
-
const
|
|
599
|
-
(targetNode.type === 'asset' && !!(targetNode.data as { isMultiple?: boolean }).isMultiple) ||
|
|
600
|
-
targetNode.type === 'groupItems' ||
|
|
601
|
-
targetNode.type === 'sliceAssets' ||
|
|
602
|
-
outHandleName === 'allOutputs';
|
|
631
|
+
const { isArray, inputType } = getConcatListSourceDescriptor(targetNode, outHandle, outHandleName);
|
|
603
632
|
const isRequired =
|
|
604
633
|
inputNodeIds.includes(targetNode.id) ?
|
|
605
634
|
(targetNode.type === 'asset' && !!(targetNode.data as { isRequired?: boolean }).isRequired) ||
|
|
606
635
|
['firstFrame', 'lastFrame'].includes(outHandleName)
|
|
607
636
|
: true;
|
|
608
|
-
variables.push({ name: varName, isArray
|
|
609
|
-
flowInputs.push({ name: varName, type:
|
|
637
|
+
variables.push({ name: varName, isArray, isRequired });
|
|
638
|
+
flowInputs.push({ name: varName, type: inputType, ref });
|
|
610
639
|
}
|
|
611
640
|
|
|
612
641
|
return { variables, flowInputs };
|
|
@@ -842,6 +871,7 @@ function getForEachBodyEditorNodeIds(
|
|
|
842
871
|
forEachId: string,
|
|
843
872
|
forEachEndId: string,
|
|
844
873
|
edges: WorkflowEditorEdge[],
|
|
874
|
+
nodes: WorkflowEditorNode[],
|
|
845
875
|
): Set<string> {
|
|
846
876
|
const bodyNodeIds = new Set<string>();
|
|
847
877
|
const queue: string[] = [];
|
|
@@ -861,9 +891,77 @@ function getForEachBodyEditorNodeIds(
|
|
|
861
891
|
}
|
|
862
892
|
}
|
|
863
893
|
}
|
|
894
|
+
|
|
895
|
+
addRandomizeClosureToBody(bodyNodeIds, forEachEndId, edges, nodes);
|
|
896
|
+
|
|
864
897
|
return bodyNodeIds;
|
|
865
898
|
}
|
|
866
899
|
|
|
900
|
+
/**
|
|
901
|
+
* The body traversal above only walks *downstream* from the loop's per-item
|
|
902
|
+
* outputs, so an unlocked randomize node sitting on a side branch that merely
|
|
903
|
+
* *feeds* the body (e.g. `randomSample(...) -> promptBuilder -> generator`) is
|
|
904
|
+
* left out — computed once and shared, so every iteration reuses the same draw
|
|
905
|
+
* (scenario-labs/phoenix#8498).
|
|
906
|
+
*
|
|
907
|
+
* A locked seed means the user wants reproducibility, so those stay shared. But
|
|
908
|
+
* for an UNLOCKED randomize node we mutate `bodyNodeIds` in place to also include
|
|
909
|
+
* that node plus the deterministic chain down to the body, so the runtime clones
|
|
910
|
+
* it per iteration and each iteration re-draws. We only pull it in when *every*
|
|
911
|
+
* path out of it lands in the body — if it also feeds a node outside the loop,
|
|
912
|
+
* cloning it would break that external consumer, so we leave it shared.
|
|
913
|
+
*/
|
|
914
|
+
function addRandomizeClosureToBody(
|
|
915
|
+
bodyNodeIds: Set<string>,
|
|
916
|
+
forEachEndId: string,
|
|
917
|
+
edges: WorkflowEditorEdge[],
|
|
918
|
+
nodes: WorkflowEditorNode[],
|
|
919
|
+
): void {
|
|
920
|
+
const nodeById = new Map(nodes.map((n) => [n.id, n]));
|
|
921
|
+
const isUnlockedRandomize = (id: string): boolean => {
|
|
922
|
+
const data = nodeById.get(id)?.data as { isRandomize?: boolean; isSeedLocked?: boolean } | undefined;
|
|
923
|
+
return data?.isRandomize === true && data.isSeedLocked !== true;
|
|
924
|
+
};
|
|
925
|
+
// edge convention: `{ source: consumer, target: provider }` — an input handle
|
|
926
|
+
// on `source` reads an output handle on `target`.
|
|
927
|
+
const providersOf = (id: string): string[] => edges.filter((e) => e.source === id).map((e) => e.target);
|
|
928
|
+
const consumersOf = (id: string): string[] => edges.filter((e) => e.target === id).map((e) => e.source);
|
|
929
|
+
|
|
930
|
+
// Every node strictly upstream of the body (its transitive input providers).
|
|
931
|
+
const upstream = new Set<string>();
|
|
932
|
+
const upstreamQueue = [...bodyNodeIds];
|
|
933
|
+
while (upstreamQueue.length > 0) {
|
|
934
|
+
for (const provider of providersOf(upstreamQueue.pop()!)) {
|
|
935
|
+
if (provider !== forEachEndId && !bodyNodeIds.has(provider) && !upstream.has(provider)) {
|
|
936
|
+
upstream.add(provider);
|
|
937
|
+
upstreamQueue.push(provider);
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
for (const randomId of upstream) {
|
|
943
|
+
if (!isUnlockedRandomize(randomId)) continue;
|
|
944
|
+
// Walk downstream from the randomize node, stopping at the body frontier.
|
|
945
|
+
const chain = new Set<string>();
|
|
946
|
+
const chainQueue = [randomId];
|
|
947
|
+
let escapesLoop = false;
|
|
948
|
+
while (chainQueue.length > 0) {
|
|
949
|
+
const id = chainQueue.pop()!;
|
|
950
|
+
if (bodyNodeIds.has(id)) continue; // reached the loop body — a valid sink
|
|
951
|
+
chain.add(id);
|
|
952
|
+
const consumers = consumersOf(id);
|
|
953
|
+
if (consumers.length === 0) {
|
|
954
|
+
escapesLoop = true; // dead-end outside the loop → do not internalize
|
|
955
|
+
break;
|
|
956
|
+
}
|
|
957
|
+
for (const consumer of consumers) {
|
|
958
|
+
if (!bodyNodeIds.has(consumer)) chainQueue.push(consumer);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
if (!escapesLoop) for (const id of chain) bodyNodeIds.add(id);
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
|
|
867
965
|
function sortNodesByInputKeys(nodes: WorkflowEditorNode[], inputKeys: string[]): WorkflowEditorNode[] {
|
|
868
966
|
return [...nodes].sort((a, b) => {
|
|
869
967
|
const ai = inputKeys.indexOf(a.id);
|
|
@@ -1145,7 +1243,7 @@ export function convertWorkflowEditorToFlow(
|
|
|
1145
1243
|
(n) => n.type === 'forEachEnd' && (n.data as { parentNodeId?: string }).parentNodeId === node.id,
|
|
1146
1244
|
);
|
|
1147
1245
|
if (!endNode) continue;
|
|
1148
|
-
forEachBodyEditorNodeIds.set(node.id, getForEachBodyEditorNodeIds(node.id, endNode.id, edges));
|
|
1246
|
+
forEachBodyEditorNodeIds.set(node.id, getForEachBodyEditorNodeIds(node.id, endNode.id, edges, nodes));
|
|
1149
1247
|
forEachLoopFlowIds.set(node.id, []);
|
|
1150
1248
|
}
|
|
1151
1249
|
|
|
@@ -1331,21 +1429,16 @@ export function convertWorkflowEditorToFlow(
|
|
|
1331
1429
|
targetInputType = outHandle?.type === 'text' ? 'string' : 'file';
|
|
1332
1430
|
} else {
|
|
1333
1431
|
targetNodeKey = getOutputHandleVarName(edge.target, targetOutputHandleName);
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
) ?
|
|
1342
|
-
'file_array'
|
|
1343
|
-
: targetNode?.type === 'llm' && targetOutputHandleName === 'outputs' ? 'string_array'
|
|
1344
|
-
: targetNode?.type === 'asset' || targetNode?.type === 'model' ? 'file'
|
|
1345
|
-
: 'string';
|
|
1432
|
+
if (!targetNode) continue;
|
|
1433
|
+
const descriptor = getConcatListSourceDescriptor(
|
|
1434
|
+
targetNode,
|
|
1435
|
+
targetOutputHandle,
|
|
1436
|
+
targetOutputHandleName,
|
|
1437
|
+
);
|
|
1438
|
+
targetInputType = descriptor.inputType;
|
|
1346
1439
|
}
|
|
1347
1440
|
|
|
1348
|
-
const isArr = targetInputType === 'file_array';
|
|
1441
|
+
const isArr = targetInputType === 'file_array' || targetInputType === 'string_array';
|
|
1349
1442
|
const isRequired =
|
|
1350
1443
|
(targetNode?.type === 'asset' &&
|
|
1351
1444
|
!!(targetNode.data as { isRequired?: boolean }).isRequired) ||
|
|
@@ -1635,7 +1728,9 @@ export function convertWorkflowEditorToFlow(
|
|
|
1635
1728
|
const ref = targetNode ? getSourceRef(assetsEdge, ctx) : undefined;
|
|
1636
1729
|
if (targetNode && ref) {
|
|
1637
1730
|
const outHandle = targetNode.data.outputHandles?.find((h) => h.id === assetsEdge.targetHandle);
|
|
1638
|
-
const
|
|
1731
|
+
const outHandleName = outHandle?.name ?? 'output';
|
|
1732
|
+
const varName = getOutputHandleVarName(targetNode.id, outHandleName);
|
|
1733
|
+
const { inputType } = getConcatListSourceDescriptor(targetNode, outHandle, outHandleName);
|
|
1639
1734
|
const from = parseInt(String(sliceData.from ?? ''), 10) || 0;
|
|
1640
1735
|
const count = parseInt(String(sliceData.count ?? ''), 10) || 0;
|
|
1641
1736
|
const transform =
|
|
@@ -1643,7 +1738,7 @@ export function convertWorkflowEditorToFlow(
|
|
|
1643
1738
|
`slice(${varName}, (${varName}).size() + ${from}, (${varName}).size() + ${from + count})`
|
|
1644
1739
|
: `slice(${varName}, ${from}, ${from + count})`;
|
|
1645
1740
|
const flowInputs: WorkflowEditorFlowInput[] = [
|
|
1646
|
-
{ name: varName, type:
|
|
1741
|
+
{ name: varName, type: inputType, ref },
|
|
1647
1742
|
...collectConditionalConnectionInputs(incomingEdges, node.id, ctx),
|
|
1648
1743
|
];
|
|
1649
1744
|
flow.push({
|
|
@@ -5058,7 +5058,8 @@ export namespace ExampleListResponse {
|
|
|
5058
5058
|
|
|
5059
5059
|
export interface ExampleUpdateParams {
|
|
5060
5060
|
/**
|
|
5061
|
-
* Body param: The list of asset ids to use as examples of the model
|
|
5061
|
+
* Body param: The list of asset ids to use as examples of the model. Maximum of
|
|
5062
|
+
* 150 asset ids.
|
|
5062
5063
|
*/
|
|
5063
5064
|
assetIds: Array<string>;
|
|
5064
5065
|
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '2.
|
|
1
|
+
export const VERSION = '2.7.0'; // x-release-please-version
|
package/version.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "2.
|
|
1
|
+
export declare const VERSION = "2.7.0";
|
|
2
2
|
//# sourceMappingURL=version.d.mts.map
|
package/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "2.
|
|
1
|
+
export declare const VERSION = "2.7.0";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/version.js
CHANGED
package/version.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const VERSION = '2.
|
|
1
|
+
export const VERSION = '2.7.0'; // x-release-please-version
|
|
2
2
|
//# sourceMappingURL=version.mjs.map
|