@scenario-labs/sdk 2.6.0 → 3.0.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.
Files changed (221) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/client.d.mts +10 -10
  3. package/client.d.mts.map +1 -1
  4. package/client.d.ts +10 -10
  5. package/client.d.ts.map +1 -1
  6. package/client.js.map +1 -1
  7. package/client.mjs.map +1 -1
  8. package/lib/job.d.mts +2 -2
  9. package/lib/job.d.mts.map +1 -1
  10. package/lib/job.d.ts +2 -2
  11. package/lib/job.d.ts.map +1 -1
  12. package/lib/job.js +3 -3
  13. package/lib/job.js.map +1 -1
  14. package/lib/job.mjs +3 -3
  15. package/lib/job.mjs.map +1 -1
  16. package/lib/upload.d.mts +2 -2
  17. package/lib/upload.d.mts.map +1 -1
  18. package/lib/upload.d.ts +2 -2
  19. package/lib/upload.d.ts.map +1 -1
  20. package/lib/upload.js +4 -4
  21. package/lib/upload.js.map +1 -1
  22. package/lib/upload.mjs +4 -4
  23. package/lib/upload.mjs.map +1 -1
  24. package/lib/workflow.d.mts +2 -2
  25. package/lib/workflow.d.mts.map +1 -1
  26. package/lib/workflow.d.ts +2 -2
  27. package/lib/workflow.d.ts.map +1 -1
  28. package/lib/workflow.js +2 -2
  29. package/lib/workflow.js.map +1 -1
  30. package/lib/workflow.mjs +2 -2
  31. package/lib/workflow.mjs.map +1 -1
  32. package/lib/workflow_converter.d.mts.map +1 -1
  33. package/lib/workflow_converter.d.ts.map +1 -1
  34. package/lib/workflow_converter.js +101 -22
  35. package/lib/workflow_converter.js.map +1 -1
  36. package/lib/workflow_converter.mjs +101 -22
  37. package/lib/workflow_converter.mjs.map +1 -1
  38. package/package.json +1 -1
  39. package/resources/assets/assets.d.mts +1091 -8
  40. package/resources/assets/assets.d.mts.map +1 -1
  41. package/resources/assets/assets.d.ts +1091 -8
  42. package/resources/assets/assets.d.ts.map +1 -1
  43. package/resources/assets/assets.js +34 -16
  44. package/resources/assets/assets.js.map +1 -1
  45. package/resources/assets/assets.mjs +34 -16
  46. package/resources/assets/assets.mjs.map +1 -1
  47. package/resources/assets/download.d.mts +28 -5
  48. package/resources/assets/download.d.mts.map +1 -1
  49. package/resources/assets/download.d.ts +28 -5
  50. package/resources/assets/download.d.ts.map +1 -1
  51. package/resources/assets/download.js +8 -6
  52. package/resources/assets/download.js.map +1 -1
  53. package/resources/assets/download.mjs +8 -6
  54. package/resources/assets/download.mjs.map +1 -1
  55. package/resources/assets/index.d.mts +1 -1
  56. package/resources/assets/index.d.mts.map +1 -1
  57. package/resources/assets/index.d.ts +1 -1
  58. package/resources/assets/index.d.ts.map +1 -1
  59. package/resources/assets/index.js.map +1 -1
  60. package/resources/assets/index.mjs.map +1 -1
  61. package/resources/collections/assets.d.mts +15 -4
  62. package/resources/collections/assets.d.mts.map +1 -1
  63. package/resources/collections/assets.d.ts +15 -4
  64. package/resources/collections/assets.d.ts.map +1 -1
  65. package/resources/collections/assets.js +14 -4
  66. package/resources/collections/assets.js.map +1 -1
  67. package/resources/collections/assets.mjs +14 -4
  68. package/resources/collections/assets.mjs.map +1 -1
  69. package/resources/collections/collections.d.mts +38 -9
  70. package/resources/collections/collections.d.mts.map +1 -1
  71. package/resources/collections/collections.d.ts +38 -9
  72. package/resources/collections/collections.d.ts.map +1 -1
  73. package/resources/collections/collections.js +11 -8
  74. package/resources/collections/collections.js.map +1 -1
  75. package/resources/collections/collections.mjs +11 -8
  76. package/resources/collections/collections.mjs.map +1 -1
  77. package/resources/collections/index.d.mts +1 -1
  78. package/resources/collections/index.d.mts.map +1 -1
  79. package/resources/collections/index.d.ts +1 -1
  80. package/resources/collections/index.d.ts.map +1 -1
  81. package/resources/collections/index.js.map +1 -1
  82. package/resources/collections/index.mjs.map +1 -1
  83. package/resources/collections/models.d.mts +15 -4
  84. package/resources/collections/models.d.mts.map +1 -1
  85. package/resources/collections/models.d.ts +15 -4
  86. package/resources/collections/models.d.ts.map +1 -1
  87. package/resources/collections/models.js +14 -4
  88. package/resources/collections/models.js.map +1 -1
  89. package/resources/collections/models.mjs +14 -4
  90. package/resources/collections/models.mjs.map +1 -1
  91. package/resources/generate.d.mts +941 -0
  92. package/resources/generate.d.mts.map +1 -1
  93. package/resources/generate.d.ts +941 -0
  94. package/resources/generate.d.ts.map +1 -1
  95. package/resources/generate.js +28 -16
  96. package/resources/generate.js.map +1 -1
  97. package/resources/generate.mjs +28 -16
  98. package/resources/generate.mjs.map +1 -1
  99. package/resources/index.d.mts +5 -5
  100. package/resources/index.d.mts.map +1 -1
  101. package/resources/index.d.ts +5 -5
  102. package/resources/index.d.ts.map +1 -1
  103. package/resources/index.js.map +1 -1
  104. package/resources/index.mjs.map +1 -1
  105. package/resources/jobs.d.mts +146 -2
  106. package/resources/jobs.d.mts.map +1 -1
  107. package/resources/jobs.d.ts +146 -2
  108. package/resources/jobs.d.ts.map +1 -1
  109. package/resources/jobs.js +8 -4
  110. package/resources/jobs.js.map +1 -1
  111. package/resources/jobs.mjs +8 -4
  112. package/resources/jobs.mjs.map +1 -1
  113. package/resources/models/description.d.mts +10 -0
  114. package/resources/models/description.d.mts.map +1 -1
  115. package/resources/models/description.d.ts +10 -0
  116. package/resources/models/description.d.ts.map +1 -1
  117. package/resources/models/description.js +2 -2
  118. package/resources/models/description.js.map +1 -1
  119. package/resources/models/description.mjs +2 -2
  120. package/resources/models/description.mjs.map +1 -1
  121. package/resources/models/examples.d.mts +346 -1
  122. package/resources/models/examples.d.mts.map +1 -1
  123. package/resources/models/examples.d.ts +346 -1
  124. package/resources/models/examples.d.ts.map +1 -1
  125. package/resources/models/examples.js +2 -2
  126. package/resources/models/examples.js.map +1 -1
  127. package/resources/models/examples.mjs +2 -2
  128. package/resources/models/examples.mjs.map +1 -1
  129. package/resources/models/index.d.mts +1 -1
  130. package/resources/models/index.d.mts.map +1 -1
  131. package/resources/models/index.d.ts +1 -1
  132. package/resources/models/index.d.ts.map +1 -1
  133. package/resources/models/index.js.map +1 -1
  134. package/resources/models/index.mjs.map +1 -1
  135. package/resources/models/models.d.mts +71 -13
  136. package/resources/models/models.d.mts.map +1 -1
  137. package/resources/models/models.d.ts +71 -13
  138. package/resources/models/models.d.ts.map +1 -1
  139. package/resources/models/models.js +30 -18
  140. package/resources/models/models.js.map +1 -1
  141. package/resources/models/models.mjs +30 -18
  142. package/resources/models/models.mjs.map +1 -1
  143. package/resources/models/train.d.mts +93 -0
  144. package/resources/models/train.d.mts.map +1 -1
  145. package/resources/models/train.d.ts +93 -0
  146. package/resources/models/train.d.ts.map +1 -1
  147. package/resources/models/train.js +4 -4
  148. package/resources/models/train.js.map +1 -1
  149. package/resources/models/train.mjs +4 -4
  150. package/resources/models/train.mjs.map +1 -1
  151. package/resources/models/training-images.d.mts +22 -2
  152. package/resources/models/training-images.d.mts.map +1 -1
  153. package/resources/models/training-images.d.ts +22 -2
  154. package/resources/models/training-images.d.ts.map +1 -1
  155. package/resources/models/training-images.js +15 -8
  156. package/resources/models/training-images.js.map +1 -1
  157. package/resources/models/training-images.mjs +15 -8
  158. package/resources/models/training-images.mjs.map +1 -1
  159. package/resources/search.d.mts +10 -0
  160. package/resources/search.d.mts.map +1 -1
  161. package/resources/search.d.ts +10 -0
  162. package/resources/search.d.ts.map +1 -1
  163. package/resources/search.js +4 -4
  164. package/resources/search.js.map +1 -1
  165. package/resources/search.mjs +4 -4
  166. package/resources/search.mjs.map +1 -1
  167. package/resources/tags.d.mts +5 -0
  168. package/resources/tags.d.mts.map +1 -1
  169. package/resources/tags.d.ts +5 -0
  170. package/resources/tags.d.ts.map +1 -1
  171. package/resources/uploads.d.mts +35 -20
  172. package/resources/uploads.d.mts.map +1 -1
  173. package/resources/uploads.d.ts +35 -20
  174. package/resources/uploads.d.ts.map +1 -1
  175. package/resources/uploads.js +8 -6
  176. package/resources/uploads.js.map +1 -1
  177. package/resources/uploads.mjs +8 -6
  178. package/resources/uploads.mjs.map +1 -1
  179. package/resources/usages.d.mts +53 -10
  180. package/resources/usages.d.mts.map +1 -1
  181. package/resources/usages.d.ts +53 -10
  182. package/resources/usages.d.ts.map +1 -1
  183. package/resources/workflows.d.mts +446 -266
  184. package/resources/workflows.d.mts.map +1 -1
  185. package/resources/workflows.d.ts +446 -266
  186. package/resources/workflows.d.ts.map +1 -1
  187. package/resources/workflows.js +20 -12
  188. package/resources/workflows.js.map +1 -1
  189. package/resources/workflows.mjs +20 -12
  190. package/resources/workflows.mjs.map +1 -1
  191. package/src/client.ts +14 -0
  192. package/src/lib/job.ts +8 -3
  193. package/src/lib/upload.ts +5 -3
  194. package/src/lib/workflow.ts +3 -1
  195. package/src/lib/workflow_converter.ts +119 -24
  196. package/src/resources/assets/assets.ts +1310 -32
  197. package/src/resources/assets/download.ts +40 -7
  198. package/src/resources/assets/index.ts +1 -0
  199. package/src/resources/collections/assets.ts +29 -6
  200. package/src/resources/collections/collections.ts +59 -12
  201. package/src/resources/collections/index.ts +2 -0
  202. package/src/resources/collections/models.ts +29 -6
  203. package/src/resources/generate.ts +1247 -30
  204. package/src/resources/index.ts +7 -0
  205. package/src/resources/jobs.ts +169 -4
  206. package/src/resources/models/description.ts +14 -2
  207. package/src/resources/models/examples.ts +396 -3
  208. package/src/resources/models/index.ts +1 -0
  209. package/src/resources/models/models.ts +112 -26
  210. package/src/resources/models/train.ts +126 -4
  211. package/src/resources/models/training-images.ts +41 -10
  212. package/src/resources/search.ts +16 -4
  213. package/src/resources/tags.ts +6 -0
  214. package/src/resources/uploads.ts +47 -22
  215. package/src/resources/usages.ts +63 -10
  216. package/src/resources/workflows.ts +783 -563
  217. package/src/version.ts +1 -1
  218. package/version.d.mts +1 -1
  219. package/version.d.ts +1 -1
  220. package/version.js +1 -1
  221. package/version.mjs +1 -1
package/src/client.ts CHANGED
@@ -60,6 +60,7 @@ import {
60
60
  JobListParams,
61
61
  JobListResponse,
62
62
  JobListResponsesJobsCursor,
63
+ JobRetrieveParams,
63
64
  JobRetrieveResponse,
64
65
  JobTriggerActionParams,
65
66
  JobTriggerActionResponse,
@@ -79,6 +80,7 @@ import { TagListParams, TagListResponse, TagListResponsesTagsCursor, Tags } from
79
80
  import {
80
81
  UploadCreateParams,
81
82
  UploadCreateResponse,
83
+ UploadRetrieveParams,
82
84
  UploadRetrieveResponse,
83
85
  UploadTriggerActionParams,
84
86
  UploadTriggerActionResponse,
@@ -89,12 +91,14 @@ import {
89
91
  WorkflowApproveUserParams,
90
92
  WorkflowCreateParams,
91
93
  WorkflowCreateResponse,
94
+ WorkflowDeleteParams,
92
95
  WorkflowDeleteResponse,
93
96
  WorkflowGetTagsParams,
94
97
  WorkflowGetTagsResponse,
95
98
  WorkflowListParams,
96
99
  WorkflowListResponse,
97
100
  WorkflowListResponsesWorkflowsCursor,
101
+ WorkflowRetrieveParams,
98
102
  WorkflowRetrieveResponse,
99
103
  WorkflowRunParams,
100
104
  WorkflowRunResponse,
@@ -134,10 +138,12 @@ import {
134
138
  import {
135
139
  CollectionCreateParams,
136
140
  CollectionCreateResponse,
141
+ CollectionDeleteParams,
137
142
  CollectionDeleteResponse,
138
143
  CollectionListParams,
139
144
  CollectionListResponse,
140
145
  CollectionListResponsesCollectionsCursor,
146
+ CollectionRetrieveParams,
141
147
  CollectionRetrieveResponse,
142
148
  CollectionUpdateParams,
143
149
  CollectionUpdateResponse,
@@ -150,6 +156,7 @@ import {
150
156
  ModelCreateResponse,
151
157
  ModelDeleteImagesParams,
152
158
  ModelDeleteImagesResponse,
159
+ ModelDeleteParams,
153
160
  ModelDeleteResponse,
154
161
  ModelDownloadParams,
155
162
  ModelDownloadResponse,
@@ -1067,8 +1074,10 @@ export declare namespace Scenario {
1067
1074
  type CollectionDeleteResponse as CollectionDeleteResponse,
1068
1075
  type CollectionListResponsesCollectionsCursor as CollectionListResponsesCollectionsCursor,
1069
1076
  type CollectionCreateParams as CollectionCreateParams,
1077
+ type CollectionRetrieveParams as CollectionRetrieveParams,
1070
1078
  type CollectionUpdateParams as CollectionUpdateParams,
1071
1079
  type CollectionListParams as CollectionListParams,
1080
+ type CollectionDeleteParams as CollectionDeleteParams,
1072
1081
  };
1073
1082
 
1074
1083
  export {
@@ -1097,6 +1106,7 @@ export declare namespace Scenario {
1097
1106
  type JobListResponse as JobListResponse,
1098
1107
  type JobTriggerActionResponse as JobTriggerActionResponse,
1099
1108
  type JobListResponsesJobsCursor as JobListResponsesJobsCursor,
1109
+ type JobRetrieveParams as JobRetrieveParams,
1100
1110
  type JobListParams as JobListParams,
1101
1111
  type JobTriggerActionParams as JobTriggerActionParams,
1102
1112
  };
@@ -1119,6 +1129,7 @@ export declare namespace Scenario {
1119
1129
  type ModelRetrieveParams as ModelRetrieveParams,
1120
1130
  type ModelUpdateParams as ModelUpdateParams,
1121
1131
  type ModelListParams as ModelListParams,
1132
+ type ModelDeleteParams as ModelDeleteParams,
1122
1133
  type ModelCopyParams as ModelCopyParams,
1123
1134
  type ModelDeleteImagesParams as ModelDeleteImagesParams,
1124
1135
  type ModelDownloadParams as ModelDownloadParams,
@@ -1154,6 +1165,7 @@ export declare namespace Scenario {
1154
1165
  type UploadRetrieveResponse as UploadRetrieveResponse,
1155
1166
  type UploadTriggerActionResponse as UploadTriggerActionResponse,
1156
1167
  type UploadCreateParams as UploadCreateParams,
1168
+ type UploadRetrieveParams as UploadRetrieveParams,
1157
1169
  type UploadTriggerActionParams as UploadTriggerActionParams,
1158
1170
  };
1159
1171
 
@@ -1175,8 +1187,10 @@ export declare namespace Scenario {
1175
1187
  type WorkflowUserApprovalResponse as WorkflowUserApprovalResponse,
1176
1188
  type WorkflowListResponsesWorkflowsCursor as WorkflowListResponsesWorkflowsCursor,
1177
1189
  type WorkflowCreateParams as WorkflowCreateParams,
1190
+ type WorkflowRetrieveParams as WorkflowRetrieveParams,
1178
1191
  type WorkflowUpdateParams as WorkflowUpdateParams,
1179
1192
  type WorkflowListParams as WorkflowListParams,
1193
+ type WorkflowDeleteParams as WorkflowDeleteParams,
1180
1194
  type WorkflowGetTagsParams as WorkflowGetTagsParams,
1181
1195
  type WorkflowRunParams as WorkflowRunParams,
1182
1196
  type WorkflowUserApprovalParams as WorkflowUserApprovalParams,
package/src/lib/job.ts CHANGED
@@ -3,6 +3,7 @@ import type { APIPromise } from '../core/api-promise';
3
3
  import type { RequestOptions } from '../internal/request-options';
4
4
  import {
5
5
  Jobs,
6
+ type JobRetrieveParams,
6
7
  type JobRetrieveResponse,
7
8
  type JobTriggerActionParams,
8
9
  type JobTriggerActionResponse,
@@ -49,7 +50,7 @@ export class JobMethods {
49
50
  let elapsed = 0;
50
51
 
51
52
  while (elapsed < timeoutMs) {
52
- const response = await this._client.jobs.retrieve(this.jobId, withScope(scope));
53
+ const response = await this._client.jobs.retrieve(this.jobId, undefined, withScope(scope));
53
54
 
54
55
  if (TERMINAL_STATUSES.has(response.job.status)) {
55
56
  return Job.from(this._client, response.job, scope);
@@ -107,8 +108,12 @@ export function enhanceJob<T extends { job: { jobId: string; status: string } }>
107
108
  * typically fetch individual jobs via `retrieve` before acting on them.
108
109
  */
109
110
  export class EnhancedJobs extends Jobs {
110
- override retrieve(jobID: string, options?: RequestOptions): APIPromise<WithJob<JobRetrieveResponse>> {
111
- return enhanceJob(this._client, super.retrieve(jobID, options), options);
111
+ override retrieve(
112
+ jobID: string,
113
+ query: JobRetrieveParams | null | undefined = {},
114
+ options?: RequestOptions,
115
+ ): APIPromise<WithJob<JobRetrieveResponse>> {
116
+ return enhanceJob(this._client, super.retrieve(jobID, query, options), options);
112
117
  }
113
118
 
114
119
  override triggerAction(
package/src/lib/upload.ts CHANGED
@@ -5,6 +5,7 @@ import {
5
5
  Uploads,
6
6
  type UploadCreateParams,
7
7
  type UploadCreateResponse,
8
+ type UploadRetrieveParams,
8
9
  type UploadRetrieveResponse,
9
10
  type UploadTriggerActionParams,
10
11
  type UploadTriggerActionResponse,
@@ -113,7 +114,7 @@ export class UploadMethods {
113
114
  async wait(this: Upload, options?: UploadWaitOptions): Promise<Upload> {
114
115
  const scope = waitOverrideScope(options) ?? this._scope;
115
116
  const final = await pollUntilImported(
116
- () => this._client.uploads.retrieve(this.id, withScope(scope)),
117
+ () => this._client.uploads.retrieve(this.id, undefined, withScope(scope)),
117
118
  options?.intervalMs ?? 2_000,
118
119
  options?.timeoutMs ?? 300_000,
119
120
  );
@@ -174,9 +175,10 @@ export class EnhancedUploads extends Uploads {
174
175
 
175
176
  override retrieve(
176
177
  uploadID: string,
178
+ query: UploadRetrieveParams | null | undefined = {},
177
179
  options?: RequestOptions,
178
180
  ): APIPromise<WithUpload<UploadRetrieveResponse>> {
179
- return enhanceUpload(this._client, super.retrieve(uploadID, options), options);
181
+ return enhanceUpload(this._client, super.retrieve(uploadID, query, options), options);
180
182
  }
181
183
 
182
184
  override triggerAction(
@@ -269,7 +271,7 @@ export class EnhancedUploads extends Uploads {
269
271
 
270
272
  // Step 4 — poll until the upload has been processed into an entity.
271
273
  const final = await pollUntilImported(
272
- () => this.retrieve(uploadId, options),
274
+ () => this.retrieve(uploadId, undefined, options),
273
275
  params.pollIntervalMs ?? 2_000,
274
276
  params.pollTimeoutMs ?? 300_000,
275
277
  );
@@ -4,6 +4,7 @@ import {
4
4
  Workflows,
5
5
  WorkflowUserApprovalResponse,
6
6
  type WorkflowApproveUserParams,
7
+ type WorkflowRetrieveParams,
7
8
  type WorkflowRetrieveResponse,
8
9
  type WorkflowRunParams,
9
10
  type WorkflowRunResponse,
@@ -104,11 +105,12 @@ export class EnhancedWorkflows extends Workflows {
104
105
  */
105
106
  override retrieve(
106
107
  workflowID: string,
108
+ query: WorkflowRetrieveParams | null | undefined = {},
107
109
  options?: RequestOptions,
108
110
  ): APIPromise<EnhancedWorkflowRetrieveResponse> {
109
111
  const client = this._client as Scenario;
110
112
  const scope = effectiveScope(options, client.projectId);
111
- return super.retrieve(workflowID, options)._thenUnwrap((data) => ({
113
+ return super.retrieve(workflowID, query, options)._thenUnwrap((data) => ({
112
114
  ...data,
113
115
  workflow: WorkflowEntity.from(client, data.workflow, scope),
114
116
  }));
@@ -501,7 +501,8 @@ function getSourceRef(
501
501
  }
502
502
 
503
503
  if (targetNode.type === 'ifElse') {
504
- const handles = targetNode.data.outputHandles ?? [];
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 isArr =
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: isArr, isRequired });
609
- flowInputs.push({ name: varName, type: isArr ? 'file_array' : 'file', ref });
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
- targetInputType =
1335
- (
1336
- (targetNode?.type === 'asset' &&
1337
- !!(targetNode.data as { isMultiple?: boolean }).isMultiple) ||
1338
- targetNode?.type === 'groupItems' ||
1339
- targetNode?.type === 'sliceAssets' ||
1340
- targetOutputHandleName === 'allOutputs'
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 varName = getOutputHandleVarName(targetNode.id, outHandle?.name ?? 'output');
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: 'file_array', ref },
1741
+ { name: varName, type: inputType, ref },
1647
1742
  ...collectConditionalConnectionInputs(incomingEdges, node.id, ctx),
1648
1743
  ];
1649
1744
  flow.push({