@scenario-labs/sdk 2.5.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 +52 -0
- package/lib/workflow_converter.d.mts +1 -0
- package/lib/workflow_converter.d.mts.map +1 -1
- package/lib/workflow_converter.d.ts +1 -0
- package/lib/workflow_converter.d.ts.map +1 -1
- package/lib/workflow_converter.js +105 -24
- package/lib/workflow_converter.js.map +1 -1
- package/lib/workflow_converter.mjs +105 -24
- package/lib/workflow_converter.mjs.map +1 -1
- package/package.json +1 -1
- package/resources/generate.d.mts +2 -0
- package/resources/generate.d.mts.map +1 -1
- package/resources/generate.d.ts +2 -0
- package/resources/generate.d.ts.map +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 +126 -26
- package/src/resources/generate.ts +2 -0
- 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
|
@@ -57,6 +57,7 @@ export type WorkflowEditorNode =
|
|
|
57
57
|
count?: number | string;
|
|
58
58
|
isRandomize?: boolean;
|
|
59
59
|
randomSeed?: string;
|
|
60
|
+
isSeedLocked?: boolean;
|
|
60
61
|
value?: string;
|
|
61
62
|
};
|
|
62
63
|
}
|
|
@@ -500,7 +501,8 @@ function getSourceRef(
|
|
|
500
501
|
}
|
|
501
502
|
|
|
502
503
|
if (targetNode.type === 'ifElse') {
|
|
503
|
-
|
|
504
|
+
// Approval handles are UI-only and must not shift case/else indices.
|
|
505
|
+
const handles = (targetNode.data.outputHandles ?? []).filter((h) => h.type !== 'approval');
|
|
504
506
|
const handleIndex = handles.findIndex((h) => h.id === edge.targetHandle);
|
|
505
507
|
if (handleIndex !== -1) {
|
|
506
508
|
const targetFlowId = nodeIdToFlowIdMap.get(targetNodeId);
|
|
@@ -571,6 +573,38 @@ function getIsSliceAssetsItemsSourceHandle(nodeId: string, sourceHandle: string
|
|
|
571
573
|
return sourceHandle === `${nodeId}-source-items` || sourceHandle === `${nodeId}-source-assets`;
|
|
572
574
|
}
|
|
573
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
|
+
|
|
574
608
|
/**
|
|
575
609
|
* Builds the concat variables and flow inputs for a list-concatenating node
|
|
576
610
|
* (groupItems, sliceAssets in randomize mode) from its incoming edges.
|
|
@@ -594,18 +628,14 @@ function collectConcatVarsAndInputs(
|
|
|
594
628
|
const outHandle = targetNode.data.outputHandles?.find((h) => h.id === edge.targetHandle);
|
|
595
629
|
const outHandleName = outHandle?.name ?? 'output';
|
|
596
630
|
const varName = getVarName(targetNode.id, outHandleName);
|
|
597
|
-
const
|
|
598
|
-
(targetNode.type === 'asset' && !!(targetNode.data as { isMultiple?: boolean }).isMultiple) ||
|
|
599
|
-
targetNode.type === 'groupItems' ||
|
|
600
|
-
targetNode.type === 'sliceAssets' ||
|
|
601
|
-
outHandleName === 'allOutputs';
|
|
631
|
+
const { isArray, inputType } = getConcatListSourceDescriptor(targetNode, outHandle, outHandleName);
|
|
602
632
|
const isRequired =
|
|
603
633
|
inputNodeIds.includes(targetNode.id) ?
|
|
604
634
|
(targetNode.type === 'asset' && !!(targetNode.data as { isRequired?: boolean }).isRequired) ||
|
|
605
635
|
['firstFrame', 'lastFrame'].includes(outHandleName)
|
|
606
636
|
: true;
|
|
607
|
-
variables.push({ name: varName, isArray
|
|
608
|
-
flowInputs.push({ name: varName, type:
|
|
637
|
+
variables.push({ name: varName, isArray, isRequired });
|
|
638
|
+
flowInputs.push({ name: varName, type: inputType, ref });
|
|
609
639
|
}
|
|
610
640
|
|
|
611
641
|
return { variables, flowInputs };
|
|
@@ -841,6 +871,7 @@ function getForEachBodyEditorNodeIds(
|
|
|
841
871
|
forEachId: string,
|
|
842
872
|
forEachEndId: string,
|
|
843
873
|
edges: WorkflowEditorEdge[],
|
|
874
|
+
nodes: WorkflowEditorNode[],
|
|
844
875
|
): Set<string> {
|
|
845
876
|
const bodyNodeIds = new Set<string>();
|
|
846
877
|
const queue: string[] = [];
|
|
@@ -860,9 +891,77 @@ function getForEachBodyEditorNodeIds(
|
|
|
860
891
|
}
|
|
861
892
|
}
|
|
862
893
|
}
|
|
894
|
+
|
|
895
|
+
addRandomizeClosureToBody(bodyNodeIds, forEachEndId, edges, nodes);
|
|
896
|
+
|
|
863
897
|
return bodyNodeIds;
|
|
864
898
|
}
|
|
865
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
|
+
|
|
866
965
|
function sortNodesByInputKeys(nodes: WorkflowEditorNode[], inputKeys: string[]): WorkflowEditorNode[] {
|
|
867
966
|
return [...nodes].sort((a, b) => {
|
|
868
967
|
const ai = inputKeys.indexOf(a.id);
|
|
@@ -1144,7 +1243,7 @@ export function convertWorkflowEditorToFlow(
|
|
|
1144
1243
|
(n) => n.type === 'forEachEnd' && (n.data as { parentNodeId?: string }).parentNodeId === node.id,
|
|
1145
1244
|
);
|
|
1146
1245
|
if (!endNode) continue;
|
|
1147
|
-
forEachBodyEditorNodeIds.set(node.id, getForEachBodyEditorNodeIds(node.id, endNode.id, edges));
|
|
1246
|
+
forEachBodyEditorNodeIds.set(node.id, getForEachBodyEditorNodeIds(node.id, endNode.id, edges, nodes));
|
|
1148
1247
|
forEachLoopFlowIds.set(node.id, []);
|
|
1149
1248
|
}
|
|
1150
1249
|
|
|
@@ -1330,21 +1429,16 @@ export function convertWorkflowEditorToFlow(
|
|
|
1330
1429
|
targetInputType = outHandle?.type === 'text' ? 'string' : 'file';
|
|
1331
1430
|
} else {
|
|
1332
1431
|
targetNodeKey = getOutputHandleVarName(edge.target, targetOutputHandleName);
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
) ?
|
|
1341
|
-
'file_array'
|
|
1342
|
-
: targetNode?.type === 'llm' && targetOutputHandleName === 'outputs' ? 'string_array'
|
|
1343
|
-
: targetNode?.type === 'asset' || targetNode?.type === 'model' ? 'file'
|
|
1344
|
-
: 'string';
|
|
1432
|
+
if (!targetNode) continue;
|
|
1433
|
+
const descriptor = getConcatListSourceDescriptor(
|
|
1434
|
+
targetNode,
|
|
1435
|
+
targetOutputHandle,
|
|
1436
|
+
targetOutputHandleName,
|
|
1437
|
+
);
|
|
1438
|
+
targetInputType = descriptor.inputType;
|
|
1345
1439
|
}
|
|
1346
1440
|
|
|
1347
|
-
const isArr = targetInputType === 'file_array';
|
|
1441
|
+
const isArr = targetInputType === 'file_array' || targetInputType === 'string_array';
|
|
1348
1442
|
const isRequired =
|
|
1349
1443
|
(targetNode?.type === 'asset' &&
|
|
1350
1444
|
!!(targetNode.data as { isRequired?: boolean }).isRequired) ||
|
|
@@ -1599,6 +1693,7 @@ export function convertWorkflowEditorToFlow(
|
|
|
1599
1693
|
count?: number | string;
|
|
1600
1694
|
isRandomize?: boolean;
|
|
1601
1695
|
randomSeed?: string;
|
|
1696
|
+
isSeedLocked?: boolean;
|
|
1602
1697
|
};
|
|
1603
1698
|
const conditionalHandleId = getConditionalInputHandleId(node.id);
|
|
1604
1699
|
const itemsEdges = incomingEdges.filter(
|
|
@@ -1612,8 +1707,11 @@ export function convertWorkflowEditorToFlow(
|
|
|
1612
1707
|
flowInputs.push(...collectConditionalConnectionInputs(incomingEdges, node.id, ctx));
|
|
1613
1708
|
|
|
1614
1709
|
const count = Math.max(1, parseInt(String(sliceData.count ?? ''), 10) || 1);
|
|
1615
|
-
const
|
|
1616
|
-
const transform =
|
|
1710
|
+
const listExpr = buildConcatArrayTransform(variables);
|
|
1711
|
+
const transform =
|
|
1712
|
+
sliceData.isSeedLocked === true ?
|
|
1713
|
+
`randomSample(${listExpr}, ${count}, ${parseInt(String(sliceData.randomSeed ?? ''), 10) || 0})`
|
|
1714
|
+
: `randomSample(${listExpr}, ${count})`;
|
|
1617
1715
|
|
|
1618
1716
|
flow.push({
|
|
1619
1717
|
id: flowId,
|
|
@@ -1630,7 +1728,9 @@ export function convertWorkflowEditorToFlow(
|
|
|
1630
1728
|
const ref = targetNode ? getSourceRef(assetsEdge, ctx) : undefined;
|
|
1631
1729
|
if (targetNode && ref) {
|
|
1632
1730
|
const outHandle = targetNode.data.outputHandles?.find((h) => h.id === assetsEdge.targetHandle);
|
|
1633
|
-
const
|
|
1731
|
+
const outHandleName = outHandle?.name ?? 'output';
|
|
1732
|
+
const varName = getOutputHandleVarName(targetNode.id, outHandleName);
|
|
1733
|
+
const { inputType } = getConcatListSourceDescriptor(targetNode, outHandle, outHandleName);
|
|
1634
1734
|
const from = parseInt(String(sliceData.from ?? ''), 10) || 0;
|
|
1635
1735
|
const count = parseInt(String(sliceData.count ?? ''), 10) || 0;
|
|
1636
1736
|
const transform =
|
|
@@ -1638,7 +1738,7 @@ export function convertWorkflowEditorToFlow(
|
|
|
1638
1738
|
`slice(${varName}, (${varName}).size() + ${from}, (${varName}).size() + ${from + count})`
|
|
1639
1739
|
: `slice(${varName}, ${from}, ${from + count})`;
|
|
1640
1740
|
const flowInputs: WorkflowEditorFlowInput[] = [
|
|
1641
|
-
{ name: varName, type:
|
|
1741
|
+
{ name: varName, type: inputType, ref },
|
|
1642
1742
|
...collectConditionalConnectionInputs(incomingEdges, node.id, ctx),
|
|
1643
1743
|
];
|
|
1644
1744
|
flow.push({
|
|
@@ -11354,6 +11354,8 @@ export interface GeneratePromptParams {
|
|
|
11354
11354
|
* - in `contextual` mode, images condition prompt generation by using their actual
|
|
11355
11355
|
* descriptions as context
|
|
11356
11356
|
* - in all other modes, it supersedes the `modelId` parameter when provided.
|
|
11357
|
+
* - `contextual-v2` (and modes aliased to it) accepts up to 15 references; the
|
|
11358
|
+
* other modes accept up to 5.
|
|
11357
11359
|
*/
|
|
11358
11360
|
images?: Array<string>;
|
|
11359
11361
|
|
|
@@ -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
|