@scenario-labs/sdk 2.0.0 → 2.2.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 (269) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/api-promise.d.mts +1 -0
  3. package/api-promise.d.mts.map +1 -1
  4. package/api-promise.d.ts +1 -0
  5. package/api-promise.d.ts.map +1 -1
  6. package/api-promise.mjs +1 -0
  7. package/api-promise.mjs.map +1 -1
  8. package/client.d.mts.map +1 -1
  9. package/client.d.ts.map +1 -1
  10. package/client.mjs.map +1 -1
  11. package/core/api-promise.d.mts.map +1 -1
  12. package/core/api-promise.d.ts.map +1 -1
  13. package/core/api-promise.mjs.map +1 -1
  14. package/core/error.mjs.map +1 -1
  15. package/core/pagination.d.mts.map +1 -1
  16. package/core/pagination.d.ts.map +1 -1
  17. package/core/pagination.mjs.map +1 -1
  18. package/core/resource.d.mts.map +1 -1
  19. package/core/resource.d.ts.map +1 -1
  20. package/core/uploads.d.mts.map +1 -1
  21. package/core/uploads.d.ts.map +1 -1
  22. package/core/uploads.mjs.map +1 -1
  23. package/error.d.mts +1 -0
  24. package/error.d.mts.map +1 -1
  25. package/error.d.ts +1 -0
  26. package/error.d.ts.map +1 -1
  27. package/error.mjs +1 -0
  28. package/error.mjs.map +1 -1
  29. package/index.d.mts +1 -1
  30. package/index.d.mts.map +1 -1
  31. package/index.d.ts +1 -1
  32. package/index.d.ts.map +1 -1
  33. package/index.js +6 -1
  34. package/index.js.map +1 -1
  35. package/index.mjs +1 -1
  36. package/index.mjs.map +1 -1
  37. package/internal/detect-platform.mjs.map +1 -1
  38. package/internal/headers.mjs.map +1 -1
  39. package/internal/parse.d.mts.map +1 -1
  40. package/internal/parse.d.ts.map +1 -1
  41. package/internal/parse.mjs.map +1 -1
  42. package/internal/qs/formats.d.mts.map +1 -1
  43. package/internal/qs/formats.d.ts.map +1 -1
  44. package/internal/qs/index.d.mts +2 -2
  45. package/internal/qs/index.d.mts.map +1 -1
  46. package/internal/qs/index.d.ts +2 -2
  47. package/internal/qs/index.d.ts.map +1 -1
  48. package/internal/qs/index.mjs.map +1 -1
  49. package/internal/qs/stringify.d.mts.map +1 -1
  50. package/internal/qs/stringify.d.ts.map +1 -1
  51. package/internal/qs/stringify.mjs.map +1 -1
  52. package/internal/qs/utils.d.mts.map +1 -1
  53. package/internal/qs/utils.d.ts.map +1 -1
  54. package/internal/qs/utils.mjs.map +1 -1
  55. package/internal/request-options.d.mts.map +1 -1
  56. package/internal/request-options.d.ts.map +1 -1
  57. package/internal/shims.d.mts +6 -0
  58. package/internal/shims.d.mts.map +1 -1
  59. package/internal/shims.d.ts +6 -0
  60. package/internal/shims.d.ts.map +1 -1
  61. package/internal/to-file.d.mts.map +1 -1
  62. package/internal/to-file.d.ts.map +1 -1
  63. package/internal/to-file.mjs.map +1 -1
  64. package/internal/types.d.mts +4 -4
  65. package/internal/types.d.mts.map +1 -1
  66. package/internal/types.d.ts +4 -4
  67. package/internal/types.d.ts.map +1 -1
  68. package/internal/uploads.d.mts.map +1 -1
  69. package/internal/uploads.d.ts.map +1 -1
  70. package/internal/uploads.mjs.map +1 -1
  71. package/internal/utils/base64.mjs.map +1 -1
  72. package/internal/utils/log.d.mts.map +1 -1
  73. package/internal/utils/log.d.ts.map +1 -1
  74. package/internal/utils/log.mjs.map +1 -1
  75. package/internal/utils/path.mjs.map +1 -1
  76. package/internal/utils/query.mjs.map +1 -1
  77. package/internal/utils/values.mjs.map +1 -1
  78. package/internal/utils.d.mts.map +1 -1
  79. package/internal/utils.d.ts.map +1 -1
  80. package/internal/utils.mjs.map +1 -1
  81. package/lib/asset.d.mts.map +1 -1
  82. package/lib/asset.d.ts.map +1 -1
  83. package/lib/asset.mjs.map +1 -1
  84. package/lib/generate.d.mts.map +1 -1
  85. package/lib/generate.d.ts.map +1 -1
  86. package/lib/generate.mjs.map +1 -1
  87. package/lib/job.d.mts.map +1 -1
  88. package/lib/job.d.ts.map +1 -1
  89. package/lib/job.mjs.map +1 -1
  90. package/lib/models.d.mts.map +1 -1
  91. package/lib/models.d.ts.map +1 -1
  92. package/lib/models.mjs.map +1 -1
  93. package/lib/scenario.d.mts.map +1 -1
  94. package/lib/scenario.d.ts.map +1 -1
  95. package/lib/scenario.mjs.map +1 -1
  96. package/lib/upload.d.mts.map +1 -1
  97. package/lib/upload.d.ts.map +1 -1
  98. package/lib/upload.mjs.map +1 -1
  99. package/lib/workflow.d.mts.map +1 -1
  100. package/lib/workflow.d.ts.map +1 -1
  101. package/lib/workflow.mjs.map +1 -1
  102. package/lib/workflow_converter.d.mts +58 -2
  103. package/lib/workflow_converter.d.mts.map +1 -1
  104. package/lib/workflow_converter.d.ts +58 -2
  105. package/lib/workflow_converter.d.ts.map +1 -1
  106. package/lib/workflow_converter.js +350 -35
  107. package/lib/workflow_converter.js.map +1 -1
  108. package/lib/workflow_converter.mjs +345 -35
  109. package/lib/workflow_converter.mjs.map +1 -1
  110. package/lib/workflow_import_validator.d.mts +1 -1
  111. package/lib/workflow_import_validator.d.mts.map +1 -1
  112. package/lib/workflow_import_validator.d.ts +1 -1
  113. package/lib/workflow_import_validator.d.ts.map +1 -1
  114. package/lib/workflow_import_validator.js +1 -0
  115. package/lib/workflow_import_validator.js.map +1 -1
  116. package/lib/workflow_import_validator.mjs +1 -0
  117. package/lib/workflow_import_validator.mjs.map +1 -1
  118. package/package.json +1 -1
  119. package/pagination.d.mts +1 -0
  120. package/pagination.d.mts.map +1 -1
  121. package/pagination.d.ts +1 -0
  122. package/pagination.d.ts.map +1 -1
  123. package/pagination.mjs +1 -0
  124. package/pagination.mjs.map +1 -1
  125. package/resource.d.mts +1 -0
  126. package/resource.d.mts.map +1 -1
  127. package/resource.d.ts +1 -0
  128. package/resource.d.ts.map +1 -1
  129. package/resource.mjs +1 -0
  130. package/resource.mjs.map +1 -1
  131. package/resources/assets/assets.d.mts +289 -135
  132. package/resources/assets/assets.d.mts.map +1 -1
  133. package/resources/assets/assets.d.ts +289 -135
  134. package/resources/assets/assets.d.ts.map +1 -1
  135. package/resources/assets/assets.js.map +1 -1
  136. package/resources/assets/assets.mjs.map +1 -1
  137. package/resources/assets/download.d.mts.map +1 -1
  138. package/resources/assets/download.d.ts.map +1 -1
  139. package/resources/assets/download.mjs.map +1 -1
  140. package/resources/assets/index.d.mts.map +1 -1
  141. package/resources/assets/index.d.ts.map +1 -1
  142. package/resources/assets/index.mjs.map +1 -1
  143. package/resources/assets.d.mts.map +1 -1
  144. package/resources/assets.d.ts.map +1 -1
  145. package/resources/assets.mjs.map +1 -1
  146. package/resources/collections/assets.d.mts.map +1 -1
  147. package/resources/collections/assets.d.ts.map +1 -1
  148. package/resources/collections/assets.mjs.map +1 -1
  149. package/resources/collections/collections.d.mts.map +1 -1
  150. package/resources/collections/collections.d.ts.map +1 -1
  151. package/resources/collections/collections.mjs.map +1 -1
  152. package/resources/collections/index.d.mts.map +1 -1
  153. package/resources/collections/index.d.ts.map +1 -1
  154. package/resources/collections/index.mjs.map +1 -1
  155. package/resources/collections/models.d.mts.map +1 -1
  156. package/resources/collections/models.d.ts.map +1 -1
  157. package/resources/collections/models.mjs.map +1 -1
  158. package/resources/collections.d.mts.map +1 -1
  159. package/resources/collections.d.ts.map +1 -1
  160. package/resources/collections.mjs.map +1 -1
  161. package/resources/generate.d.mts +86 -48
  162. package/resources/generate.d.mts.map +1 -1
  163. package/resources/generate.d.ts +86 -48
  164. package/resources/generate.d.ts.map +1 -1
  165. package/resources/generate.mjs.map +1 -1
  166. package/resources/index.d.mts.map +1 -1
  167. package/resources/index.d.ts.map +1 -1
  168. package/resources/index.mjs.map +1 -1
  169. package/resources/jobs.d.mts +6 -6
  170. package/resources/jobs.d.mts.map +1 -1
  171. package/resources/jobs.d.ts +6 -6
  172. package/resources/jobs.d.ts.map +1 -1
  173. package/resources/jobs.mjs.map +1 -1
  174. package/resources/models/description.d.mts +54 -4
  175. package/resources/models/description.d.mts.map +1 -1
  176. package/resources/models/description.d.ts +54 -4
  177. package/resources/models/description.d.ts.map +1 -1
  178. package/resources/models/description.mjs.map +1 -1
  179. package/resources/models/examples.d.mts +74 -36
  180. package/resources/models/examples.d.mts.map +1 -1
  181. package/resources/models/examples.d.ts +74 -36
  182. package/resources/models/examples.d.ts.map +1 -1
  183. package/resources/models/examples.mjs.map +1 -1
  184. package/resources/models/index.d.mts.map +1 -1
  185. package/resources/models/index.d.ts.map +1 -1
  186. package/resources/models/index.mjs.map +1 -1
  187. package/resources/models/models.d.mts +21 -21
  188. package/resources/models/models.d.mts.map +1 -1
  189. package/resources/models/models.d.ts +21 -21
  190. package/resources/models/models.d.ts.map +1 -1
  191. package/resources/models/models.js.map +1 -1
  192. package/resources/models/models.mjs.map +1 -1
  193. package/resources/models/train.d.mts +8 -8
  194. package/resources/models/train.d.mts.map +1 -1
  195. package/resources/models/train.d.ts +8 -8
  196. package/resources/models/train.d.ts.map +1 -1
  197. package/resources/models/train.mjs.map +1 -1
  198. package/resources/models/training-images.d.mts.map +1 -1
  199. package/resources/models/training-images.d.ts.map +1 -1
  200. package/resources/models/training-images.mjs.map +1 -1
  201. package/resources/models.d.mts.map +1 -1
  202. package/resources/models.d.ts.map +1 -1
  203. package/resources/models.mjs.map +1 -1
  204. package/resources/oscu.d.mts.map +1 -1
  205. package/resources/oscu.d.ts.map +1 -1
  206. package/resources/oscu.mjs.map +1 -1
  207. package/resources/pricing/index.d.mts.map +1 -1
  208. package/resources/pricing/index.d.ts.map +1 -1
  209. package/resources/pricing/index.mjs.map +1 -1
  210. package/resources/pricing/oscu.d.mts.map +1 -1
  211. package/resources/pricing/oscu.d.ts.map +1 -1
  212. package/resources/pricing/oscu.mjs.map +1 -1
  213. package/resources/pricing/pricing.d.mts.map +1 -1
  214. package/resources/pricing/pricing.d.ts.map +1 -1
  215. package/resources/pricing/pricing.mjs.map +1 -1
  216. package/resources/pricing.d.mts.map +1 -1
  217. package/resources/pricing.d.ts.map +1 -1
  218. package/resources/pricing.mjs.map +1 -1
  219. package/resources/search.d.mts +6 -0
  220. package/resources/search.d.mts.map +1 -1
  221. package/resources/search.d.ts +6 -0
  222. package/resources/search.d.ts.map +1 -1
  223. package/resources/search.mjs.map +1 -1
  224. package/resources/tags.d.mts.map +1 -1
  225. package/resources/tags.d.ts.map +1 -1
  226. package/resources/tags.mjs.map +1 -1
  227. package/resources/uploads.d.mts +4 -4
  228. package/resources/uploads.d.mts.map +1 -1
  229. package/resources/uploads.d.ts +4 -4
  230. package/resources/uploads.d.ts.map +1 -1
  231. package/resources/uploads.mjs.map +1 -1
  232. package/resources/usages.d.mts +31 -6
  233. package/resources/usages.d.mts.map +1 -1
  234. package/resources/usages.d.ts +31 -6
  235. package/resources/usages.d.ts.map +1 -1
  236. package/resources/usages.mjs.map +1 -1
  237. package/resources/workflows.d.mts +36 -36
  238. package/resources/workflows.d.mts.map +1 -1
  239. package/resources/workflows.d.ts +36 -36
  240. package/resources/workflows.d.ts.map +1 -1
  241. package/resources/workflows.mjs.map +1 -1
  242. package/resources.d.mts.map +1 -1
  243. package/resources.d.ts.map +1 -1
  244. package/resources.mjs.map +1 -1
  245. package/src/index.ts +9 -0
  246. package/src/lib/workflow_converter.ts +487 -28
  247. package/src/lib/workflow_import_validator.ts +2 -0
  248. package/src/resources/assets/assets.ts +376 -118
  249. package/src/resources/generate.ts +106 -44
  250. package/src/resources/jobs.ts +6 -6
  251. package/src/resources/models/description.ts +68 -2
  252. package/src/resources/models/examples.ts +94 -32
  253. package/src/resources/models/models.ts +21 -14
  254. package/src/resources/models/train.ts +8 -6
  255. package/src/resources/search.ts +7 -0
  256. package/src/resources/uploads.ts +4 -4
  257. package/src/resources/usages.ts +41 -4
  258. package/src/resources/workflows.ts +36 -36
  259. package/src/version.ts +1 -1
  260. package/uploads.d.mts +1 -0
  261. package/uploads.d.mts.map +1 -1
  262. package/uploads.d.ts +1 -0
  263. package/uploads.d.ts.map +1 -1
  264. package/uploads.mjs +1 -0
  265. package/uploads.mjs.map +1 -1
  266. package/version.d.mts +1 -1
  267. package/version.d.ts +1 -1
  268. package/version.js +1 -1
  269. package/version.mjs +1 -1
@@ -1,5 +1,17 @@
1
1
  // Converts a workflow editor state (nodes + edges) to a flow array suitable for the Scenario API.
2
2
  // Ported from fawkes useConvertWorkflowToApp — pure function, no React dependencies.
3
+ const RUNNABLE_TYPES = new Set(['model', 'llm', 'forEach', 'forEachEnd']);
4
+ export const ASPECT_RATIO_PRESETS = [
5
+ '16:9',
6
+ '3:2',
7
+ '4:3',
8
+ '5:4',
9
+ '1:1',
10
+ '4:5',
11
+ '3:4',
12
+ '2:3',
13
+ '9:16',
14
+ ];
3
15
  // ---- Utility functions ----
4
16
  function nodeIdToFlowId(nodeId) {
5
17
  return nodeId.replace(/\s/g, '');
@@ -30,6 +42,96 @@ function convertValueToList(value) {
30
42
  function getConditionalInputHandleId(nodeId) {
31
43
  return `${nodeId}-source-conditional`;
32
44
  }
45
+ function handleTypeIncludes(handleType, value) {
46
+ if (Array.isArray(handleType))
47
+ return handleType.includes(value);
48
+ return handleType === value;
49
+ }
50
+ function parseAspectRatio(aspectRatio) {
51
+ if (!aspectRatio)
52
+ return undefined;
53
+ const [rawWidth, rawHeight] = aspectRatio.split(':');
54
+ const width = Number(rawWidth);
55
+ const height = Number(rawHeight);
56
+ if (!Number.isFinite(width) ||
57
+ !Number.isFinite(height) ||
58
+ width <= 0 ||
59
+ height <= 0 ||
60
+ !Number.isInteger(width) ||
61
+ !Number.isInteger(height)) {
62
+ return undefined;
63
+ }
64
+ return { width, height };
65
+ }
66
+ function getGreatestCommonDivisor(a, b) {
67
+ let x = Math.abs(Math.trunc(a));
68
+ let y = Math.abs(Math.trunc(b));
69
+ while (y) {
70
+ const temp = y;
71
+ y = x % y;
72
+ x = temp;
73
+ }
74
+ return x || 1;
75
+ }
76
+ function makeNumberAMultipleOfX(value, multipleOf) {
77
+ if (multipleOf <= 0)
78
+ return Math.round(value);
79
+ return Math.round(value / multipleOf) * multipleOf;
80
+ }
81
+ function clamp(value, min, max) {
82
+ return Math.min(Math.max(value, min), max);
83
+ }
84
+ export function normalizeAspectRatio(aspectRatio) {
85
+ const parsed = parseAspectRatio(aspectRatio);
86
+ if (!parsed)
87
+ return undefined;
88
+ const divisor = getGreatestCommonDivisor(parsed.width, parsed.height);
89
+ return `${parsed.width / divisor}:${parsed.height / divisor}`;
90
+ }
91
+ export function getAspectRatioModelInputNames(model) {
92
+ if (!model)
93
+ return {};
94
+ const widthInputName = model.uiConfig?.resolutionComponent?.widthInput ??
95
+ model.inputs?.find((input) => input.name === 'width')?.name;
96
+ const heightInputName = model.uiConfig?.resolutionComponent?.heightInput ??
97
+ model.inputs?.find((input) => input.name === 'height')?.name;
98
+ const aspectRatioInputName = model.inputs?.find((input) => input.name === 'aspectRatio')?.name;
99
+ return { widthInputName, heightInputName, aspectRatioInputName };
100
+ }
101
+ export function getAspectRatioBounds(model, { widthInputName, heightInputName }) {
102
+ if (!model || !widthInputName || !heightInputName)
103
+ return undefined;
104
+ const widthInput = model.inputs?.find((input) => input.name === widthInputName);
105
+ const heightInput = model.inputs?.find((input) => input.name === heightInputName);
106
+ const presets = model.uiConfig?.resolutionComponent?.presets;
107
+ const widthPresets = (presets ?? [])
108
+ .map((preset) => preset.width)
109
+ .filter((value) => typeof value === 'number');
110
+ const heightPresets = (presets ?? [])
111
+ .map((preset) => preset.height)
112
+ .filter((value) => typeof value === 'number');
113
+ const multipleOf = widthInput?.step ?? 8;
114
+ return {
115
+ maxWidth: widthInput?.max ?? (widthPresets.length > 0 ? Math.max(...widthPresets) : 2048),
116
+ minWidth: widthInput?.min ?? (widthPresets.length > 0 ? Math.min(...widthPresets) : 64),
117
+ maxHeight: heightInput?.max ?? (heightPresets.length > 0 ? Math.max(...heightPresets) : 2048),
118
+ minHeight: heightInput?.min ?? (heightPresets.length > 0 ? Math.min(...heightPresets) : 64),
119
+ multipleOf,
120
+ };
121
+ }
122
+ export function getDimensionsFromAspectRatio(args) {
123
+ const parsed = parseAspectRatio(args.aspectRatio);
124
+ if (!parsed)
125
+ return undefined;
126
+ const ratio = parsed.width / parsed.height;
127
+ let width = ratio >= 1 ? args.bounds.maxWidth : Math.round(args.bounds.maxHeight * ratio);
128
+ let height = ratio >= 1 ? Math.round(args.bounds.maxWidth / ratio) : args.bounds.maxHeight;
129
+ width = makeNumberAMultipleOfX(width, args.bounds.multipleOf);
130
+ height = makeNumberAMultipleOfX(height, args.bounds.multipleOf);
131
+ width = clamp(width, args.bounds.minWidth, args.bounds.maxWidth);
132
+ height = clamp(height, args.bounds.minHeight, args.bounds.maxHeight);
133
+ return { width, height };
134
+ }
33
135
  function getForEachFlowListInputDescriptor(args) {
34
136
  const { listProvider, outputHandle, inputIndex } = args;
35
137
  const isText = listProvider?.type === 'splitText' ||
@@ -257,11 +359,11 @@ function toStaticInput(name, type, value) {
257
359
  return undefined;
258
360
  return { name, type, value: value };
259
361
  }
260
- function isFormArray(value) {
362
+ function getIsFormArray(value) {
261
363
  return (Array.isArray(value) &&
262
364
  value.every((item) => typeof item === 'object' && item !== null && !Array.isArray(item)));
263
365
  }
264
- function isModelCompose(model) {
366
+ function getIsModelCompose(model) {
265
367
  return !!model?.id && (model.tags ?? []).includes('compose');
266
368
  }
267
369
  function getIsConditionallyExecuted(targetNodeId, nodes, edges) {
@@ -271,6 +373,87 @@ function getIsConditionallyExecuted(targetNodeId, nodes, edges) {
271
373
  return (e.source === targetNodeId && e.sourceHandle === conditionalHandleId && sourceNode?.type === 'ifElse');
272
374
  });
273
375
  }
376
+ function buildDimensionLookupCEL(args) {
377
+ let expression = String(args.fallback);
378
+ for (let i = args.presets.length - 1; i >= 0; i--) {
379
+ const preset = args.presets[i];
380
+ expression = `trim(${args.variable}) == '${preset.aspectRatio}' ? ${preset[args.dimension]} : ${expression}`;
381
+ }
382
+ return expression;
383
+ }
384
+ function applyAspectRatioInputs(args) {
385
+ const modelInputNames = getAspectRatioModelInputNames(args.model);
386
+ const modelBounds = getAspectRatioBounds(args.model, modelInputNames);
387
+ const { widthInputName, heightInputName, aspectRatioInputName } = modelInputNames;
388
+ if (aspectRatioInputName) {
389
+ const transformId = `${args.flowId}_aspectRatio_value`;
390
+ args.flow.push({
391
+ id: transformId,
392
+ type: 'transform',
393
+ logic: { transform: 'trim(aspectRatio)' },
394
+ inputs: [{ name: 'aspectRatio', type: 'string', ref: args.aspectRatioRef }],
395
+ });
396
+ setOrPushFlowInput(args.flowInputs, args.flowInputs.findIndex((inp) => inp.name === aspectRatioInputName), {
397
+ name: aspectRatioInputName,
398
+ type: 'string',
399
+ ref: { node: transformId, name: 'all' },
400
+ });
401
+ }
402
+ if (widthInputName && heightInputName && modelBounds) {
403
+ const presetDimensions = ASPECT_RATIO_PRESETS.flatMap((preset) => {
404
+ const dims = getDimensionsFromAspectRatio({ aspectRatio: preset, bounds: modelBounds });
405
+ if (!dims)
406
+ return [];
407
+ const normalized = normalizeAspectRatio(preset);
408
+ if (!normalized)
409
+ return [];
410
+ return [{ aspectRatio: normalized, ...dims }];
411
+ });
412
+ const fallbackDimensions = presetDimensions.find((preset) => preset.aspectRatio === '1:1') ??
413
+ presetDimensions[0] ?? {
414
+ width: modelBounds.maxWidth,
415
+ height: modelBounds.maxHeight,
416
+ };
417
+ const widthTransformId = `${args.flowId}_aspectRatio_width`;
418
+ const heightTransformId = `${args.flowId}_aspectRatio_height`;
419
+ args.flow.push({
420
+ id: widthTransformId,
421
+ type: 'transform',
422
+ logic: {
423
+ transform: buildDimensionLookupCEL({
424
+ variable: 'aspectRatio',
425
+ presets: presetDimensions,
426
+ dimension: 'width',
427
+ fallback: fallbackDimensions.width,
428
+ }),
429
+ },
430
+ inputs: [{ name: 'aspectRatio', type: 'string', ref: args.aspectRatioRef }],
431
+ });
432
+ args.flow.push({
433
+ id: heightTransformId,
434
+ type: 'transform',
435
+ logic: {
436
+ transform: buildDimensionLookupCEL({
437
+ variable: 'aspectRatio',
438
+ presets: presetDimensions,
439
+ dimension: 'height',
440
+ fallback: fallbackDimensions.height,
441
+ }),
442
+ },
443
+ inputs: [{ name: 'aspectRatio', type: 'string', ref: args.aspectRatioRef }],
444
+ });
445
+ setOrPushFlowInput(args.flowInputs, args.flowInputs.findIndex((inp) => inp.name === widthInputName), {
446
+ name: widthInputName,
447
+ type: 'number',
448
+ ref: { node: widthTransformId, name: 'all' },
449
+ });
450
+ setOrPushFlowInput(args.flowInputs, args.flowInputs.findIndex((inp) => inp.name === heightInputName), {
451
+ name: heightInputName,
452
+ type: 'number',
453
+ ref: { node: heightTransformId, name: 'all' },
454
+ });
455
+ }
456
+ }
274
457
  function getForEachBodyEditorNodeIds(forEachId, forEachEndId, edges) {
275
458
  const bodyNodeIds = new Set();
276
459
  const queue = [];
@@ -322,7 +505,6 @@ function getNodeOutputValue(node, targetHandleId) {
322
505
  }
323
506
  return undefined;
324
507
  }
325
- const RUNNABLE_TYPES = new Set(['model', 'llm', 'forEach', 'forEachEnd']);
326
508
  function internalGetConnectedNodes(nodes, edges, startNodeId, direction, isStoppingAtRunnableNodes) {
327
509
  const nodesById = new Map(nodes.map((n) => {
328
510
  const meta = { id: n.id, type: n.type };
@@ -362,42 +544,44 @@ function internalGetConnectedNodes(nodes, edges, startNodeId, direction, isStopp
362
544
  const idSet = new Set(ids);
363
545
  return nodes.filter((n) => idSet.has(n.id));
364
546
  }
547
+ function resolveIfElseConditionField(field, candidateNodes) {
548
+ const byExactId = candidateNodes.find((n) => n.id === field);
549
+ if (byExactId)
550
+ return { nodeId: field };
551
+ for (const node of candidateNodes) {
552
+ for (const handle of node.data.outputHandles ?? []) {
553
+ if (`${node.id}_${handle.name}` === field) {
554
+ return { nodeId: node.id, targetHandleId: handle.id };
555
+ }
556
+ }
557
+ }
558
+ return undefined;
559
+ }
365
560
  function normalizeNodes(nodes) {
366
561
  return nodes.map((node) => {
367
562
  if (node.type !== 'model' && node.type !== 'llm')
368
563
  return node;
369
- const hasType = (t, v) => Array.isArray(t) ? t.includes(v) : t === v;
370
564
  const inputHandles = (node.data.inputHandles ?? [])
371
565
  .flatMap((h) => (h.subHandles ? h.subHandles : [h]))
372
- .filter((h) => hasType(h.type, 'conditional') ||
373
- hasType(h.type, 'prompt') ||
374
- hasType(h.type, 'image') ||
375
- hasType(h.type, 'video') ||
376
- hasType(h.type, '3d') ||
377
- hasType(h.type, 'audio') ||
566
+ .filter((h) => handleTypeIncludes(h.type, 'conditional') ||
567
+ handleTypeIncludes(h.type, 'aspectRatio') ||
568
+ handleTypeIncludes(h.type, 'prompt') ||
569
+ handleTypeIncludes(h.type, 'image') ||
570
+ handleTypeIncludes(h.type, 'video') ||
571
+ handleTypeIncludes(h.type, '3d') ||
572
+ handleTypeIncludes(h.type, 'audio') ||
378
573
  (node.type === 'llm' && h.name === 'textInputs'));
379
574
  return { ...node, data: { ...node.data, inputHandles } };
380
575
  });
381
576
  }
382
- // ---- Main function ----
383
- /**
384
- * Converts a workflow editor state (nodes, edges, inputKeys) into a flow array
385
- * ready to be passed to `client.workflows.update({ flow: ... })`.
386
- *
387
- * @example
388
- * ```ts
389
- * const flow = convertWorkflowEditorToFlow({ nodes, edges, inputKeys, getModel });
390
- * await client.workflows.update(workflowId, { flow: flow as WorkflowUpdateParams.Flow[] });
391
- * ```
392
- */
393
577
  export function convertWorkflowEditorToFlow(params) {
394
578
  const { edges, inputKeys } = params;
395
579
  const nodes = normalizeNodes(params.nodes);
396
580
  const inputNodeIds = nodes.filter((n) => !!n.data.isInput).map((n) => n.id);
397
- const selectedOutputNodes = nodes.filter((n) => (n.type === 'model' || n.type === 'forEachEnd') && !!n.data.isOutput);
581
+ const selectedOutputNodes = nodes.filter((n) => (n.type === 'model' || n.type === 'forEachEnd' || n.type === 'llm') && !!n.data.isOutput);
398
582
  const selectedOutputNodeIds = selectedOutputNodes.map((n) => n.id);
399
583
  const selectedOutputModelNodeIds = new Set(selectedOutputNodes
400
- .filter((n) => n.type === 'model')
584
+ .filter((n) => n.type === 'model' || n.type === 'llm')
401
585
  .map((n) => n.id));
402
586
  // Collect all nodes in branches that lead to selected outputs
403
587
  const nodesInSelectedBranches = new Set();
@@ -431,6 +615,12 @@ export function convertWorkflowEditorToFlow(params) {
431
615
  .filter((n) => !inputNodeIds.includes(n.id) && n.type !== 'forEachEnd')
432
616
  .map((n) => [n.id, nodeIdToFlowId(n.id)]));
433
617
  const nodeIdToInputNameMap = new Map(inputNodeIds.map((id) => [id, nodeIdToFlowId(id)]));
618
+ const approvalByApprovedNodeId = buildApprovalByApprovedNodeIdMap({
619
+ nodes,
620
+ edges,
621
+ nodesInSelectedBranches,
622
+ selectedOutputNodeIds,
623
+ });
434
624
  const nodeHandleTransformMap = new Map();
435
625
  const ctx = {
436
626
  nodes,
@@ -504,6 +694,7 @@ export function convertWorkflowEditorToFlow(params) {
504
694
  const model = remoteModel === 'unknown' ? undefined : remoteModel;
505
695
  const modelInputs = model?.inputs;
506
696
  const conditionalHandleId = getConditionalInputHandleId(node.id);
697
+ const aspectRatioEdges = [];
507
698
  const edgesByInput = new Map();
508
699
  for (const edge of incomingEdges) {
509
700
  if (edge.sourceHandle === conditionalHandleId)
@@ -511,6 +702,10 @@ export function convertWorkflowEditorToFlow(params) {
511
702
  const handle = getInputHandle(edge, node.data.inputHandles);
512
703
  if (!handle?.name)
513
704
  continue;
705
+ if (handleTypeIncludes(handle.type, 'aspectRatio')) {
706
+ aspectRatioEdges.push(edge);
707
+ continue;
708
+ }
514
709
  const existing = edgesByInput.get(handle.name);
515
710
  if (existing)
516
711
  existing.push(edge);
@@ -523,7 +718,7 @@ export function convertWorkflowEditorToFlow(params) {
523
718
  continue;
524
719
  const modelInput = modelInputs?.find((inp) => inp.name === key);
525
720
  if (modelInput?.type === 'inputs_array') {
526
- if (!isFormArray(value))
721
+ if (!getIsFormArray(value))
527
722
  continue;
528
723
  const nestedInputs = modelInput.inputs ?? [];
529
724
  const items = [];
@@ -544,7 +739,7 @@ export function convertWorkflowEditorToFlow(params) {
544
739
  itemInputs.push(inp);
545
740
  }
546
741
  }
547
- const isValid = isModelCompose(model) ? getHasComposeLayerSourceInput(itemInputs) : itemInputs.length > 0;
742
+ const isValid = getIsModelCompose(model) ? getHasComposeLayerSourceInput(itemInputs) : itemInputs.length > 0;
548
743
  if (isValid)
549
744
  items.push(itemInputs);
550
745
  }
@@ -562,6 +757,18 @@ export function convertWorkflowEditorToFlow(params) {
562
757
  }
563
758
  }
564
759
  }
760
+ if (aspectRatioEdges.length > 0 && model) {
761
+ const aspectRatioRef = getSourceRef(aspectRatioEdges[0], ctx);
762
+ if (aspectRatioRef) {
763
+ applyAspectRatioInputs({
764
+ flow,
765
+ flowInputs,
766
+ flowId,
767
+ aspectRatioRef,
768
+ model,
769
+ });
770
+ }
771
+ }
565
772
  for (const [inputName, inputEdges] of edgesByInput) {
566
773
  const existingIndex = flowInputs.findIndex((inp) => inp.name === inputName);
567
774
  const modelInput = modelInputs?.find((inp) => inp.name === inputName);
@@ -622,7 +829,7 @@ export function convertWorkflowEditorToFlow(params) {
622
829
  const isArr = targetInputType === 'file_array';
623
830
  const isRequired = (targetNode?.type === 'asset' &&
624
831
  !!targetNode.data.isRequired) ||
625
- isModelCompose(model);
832
+ getIsModelCompose(model);
626
833
  transformVariables.push({ name: targetNodeKey, isArray: isArr, isRequired });
627
834
  if (!targetNode?.data.isInput &&
628
835
  (targetNode?.type === 'asset' || targetNode?.type === 'text')) {
@@ -693,7 +900,9 @@ export function convertWorkflowEditorToFlow(params) {
693
900
  type: 'custom-model',
694
901
  modelId: nodeData.modelId,
695
902
  inputs: flowInputs.length > 0 ? flowInputs : undefined,
696
- includeOutputsInWorkflowJob: node.type === 'model' && selectedOutputModelNodeIds.has(node.id) ? true : undefined,
903
+ includeOutputsInWorkflowJob: (node.type === 'model' || node.type === 'llm') && selectedOutputModelNodeIds.has(node.id) ?
904
+ true
905
+ : undefined,
697
906
  });
698
907
  ensureInputVideoTransformsAdded();
699
908
  if (node.type === 'model' && node.data.type === 'video') {
@@ -756,6 +965,19 @@ export function convertWorkflowEditorToFlow(params) {
756
965
  addVideoFirstLastFrameTransforms(node.id, flowId, node, edges, flow, nodeHandleTransformMap);
757
966
  }
758
967
  // ------------------------------------
968
+ // AspectRatio -> transform with ratio literal
969
+ // ------------------------------------
970
+ }
971
+ else if (node.type === 'aspectRatio') {
972
+ const flowInputs = collectConditionalConnectionInputs(incomingEdges, node.id, ctx);
973
+ const value = node.data.output || '1:1';
974
+ flow.push({
975
+ id: flowId,
976
+ type: 'transform',
977
+ logic: { transform: `'${escapeCELString(value)}'` },
978
+ inputs: flowInputs.length > 0 ? flowInputs : undefined,
979
+ });
980
+ // ------------------------------------
759
981
  // TransformText -> transform (CEL)
760
982
  // ------------------------------------
761
983
  }
@@ -834,7 +1056,7 @@ export function convertWorkflowEditorToFlow(params) {
834
1056
  continue;
835
1057
  const outHandle = targetNode.data.outputHandles?.find((h) => h.id === edge.targetHandle);
836
1058
  const outHandleName = outHandle?.name ?? 'output';
837
- const varName = getOutputHandleVarName(targetNode.id, outHandleName);
1059
+ const varName = `${targetNode.id}_${outHandleName}`;
838
1060
  const isArr = (targetNode.type === 'asset' && !!targetNode.data.isMultiple) ||
839
1061
  targetNode.type === 'groupItems' ||
840
1062
  targetNode.type === 'sliceAssets' ||
@@ -943,17 +1165,47 @@ export function convertWorkflowEditorToFlow(params) {
943
1165
  fieldsUsed.add(condition.field);
944
1166
  }
945
1167
  }
946
- for (const fieldNodeId of fieldsUsed) {
1168
+ const edgesIntoIfElse = edges.filter((e) => e.target === node.id);
1169
+ for (const fieldKey of fieldsUsed) {
1170
+ const resolved = resolveIfElseConditionField(fieldKey, nodes);
1171
+ const fieldNodeId = resolved?.nodeId;
1172
+ if (!fieldNodeId)
1173
+ continue;
1174
+ const outputHandleName = resolved?.targetHandleId !== undefined ?
1175
+ nodes
1176
+ .find((n) => n.id === fieldNodeId)
1177
+ ?.data.outputHandles?.find((h) => h.id === resolved.targetHandleId)?.name ?? 'output'
1178
+ : 'output';
1179
+ const celVarName = getOutputHandleVarName(fieldNodeId, outputHandleName);
1180
+ nodeIdToInputNameMap.set(fieldKey, celVarName);
1181
+ const edgeToIfElse = edgesIntoIfElse
1182
+ .filter((e) => e.source === fieldNodeId)
1183
+ .find((e) => {
1184
+ if (resolved.targetHandleId === undefined)
1185
+ return true;
1186
+ return e.sourceHandle === resolved.targetHandleId;
1187
+ }) ?? edgesIntoIfElse.find((e) => e.source === fieldNodeId);
947
1188
  if (inputNodeIds.includes(fieldNodeId)) {
948
- const inputName = nodeIdToInputNameMap.get(fieldNodeId);
949
- if (inputName)
950
- flowInputs.push({ name: inputName, type: 'string', ref: { node: 'workflow', name: inputName } });
1189
+ const workflowInputName = nodeIdToInputNameMap.get(fieldNodeId);
1190
+ if (workflowInputName) {
1191
+ const ref = edgeToIfElse ?
1192
+ getSourceRef(edgeToIfElse, ctx)
1193
+ : { node: 'workflow', name: workflowInputName };
1194
+ flowInputs.push({
1195
+ name: celVarName,
1196
+ type: 'string',
1197
+ ref: ref ?? { node: 'workflow', name: workflowInputName },
1198
+ });
1199
+ }
951
1200
  }
952
1201
  else {
1202
+ const refFromEdge = edgeToIfElse ? getSourceRef(edgeToIfElse, ctx) : undefined;
953
1203
  const targetFlowId = nodeIdToFlowIdMap.get(fieldNodeId);
954
- if (targetFlowId) {
955
- flowInputs.push({ name: targetFlowId, type: 'string', ref: { node: targetFlowId, name: 'all' } });
956
- nodeIdToInputNameMap.set(fieldNodeId, targetFlowId);
1204
+ if (refFromEdge) {
1205
+ flowInputs.push({ name: celVarName, type: 'string', ref: refFromEdge });
1206
+ }
1207
+ else if (targetFlowId) {
1208
+ flowInputs.push({ name: celVarName, type: 'string', ref: { node: targetFlowId, name: 'all' } });
957
1209
  }
958
1210
  }
959
1211
  }
@@ -987,6 +1239,64 @@ export function convertWorkflowEditorToFlow(params) {
987
1239
  for (const [forEachId, forEachFlowItem] of forEachFlowItemRefs) {
988
1240
  forEachFlowItem.loopBodyNodeIds = forEachLoopFlowIds.get(forEachId) ?? [];
989
1241
  }
1242
+ applyApprovalsToFlow({ flow, edges, approvalByApprovedNodeId, nodeIdToFlowIdMap });
990
1243
  return flow;
991
1244
  }
1245
+ function buildApprovalByApprovedNodeIdMap({ nodes, edges, nodesInSelectedBranches, selectedOutputNodeIds, }) {
1246
+ const result = new Map();
1247
+ for (const approvalNode of nodes) {
1248
+ if (approvalNode.type !== 'approval')
1249
+ continue;
1250
+ const approvalEdge = edges.find((e) => e.source === approvalNode.id && e.sourceHandle === `${approvalNode.id}-source-approval`);
1251
+ if (!approvalEdge)
1252
+ continue;
1253
+ const approvedNodeId = approvalEdge.target;
1254
+ if (!nodesInSelectedBranches.has(approvedNodeId) && !selectedOutputNodeIds.includes(approvedNodeId))
1255
+ continue;
1256
+ const approvedNode = nodes.find((n) => n.id === approvedNodeId);
1257
+ if (!approvedNode)
1258
+ continue;
1259
+ const approvedFlowId = approvedNode.type === 'forEachEnd' ?
1260
+ approvedNode.data.parentNodeId ?
1261
+ nodeIdToFlowId(approvedNode.data.parentNodeId)
1262
+ : undefined
1263
+ : nodeIdToFlowId(approvedNodeId);
1264
+ if (!approvedFlowId)
1265
+ continue;
1266
+ result.set(approvedNodeId, {
1267
+ approvalFlowId: nodeIdToFlowId(approvalNode.id),
1268
+ approvedFlowId,
1269
+ message: approvalNode.data.message ?? '',
1270
+ });
1271
+ }
1272
+ return result;
1273
+ }
1274
+ function applyApprovalsToFlow({ flow, edges, approvalByApprovedNodeId, nodeIdToFlowIdMap, }) {
1275
+ if (approvalByApprovedNodeId.size === 0)
1276
+ return;
1277
+ const flowIdToWorkflowNodeId = new Map([...nodeIdToFlowIdMap.entries()].map(([nodeId, flowId]) => [flowId, nodeId]));
1278
+ for (const flowItem of flow) {
1279
+ const workflowNodeId = flowIdToWorkflowNodeId.get(flowItem.id);
1280
+ if (!workflowNodeId)
1281
+ continue;
1282
+ const dependsOn = new Set(flowItem.dependsOn ?? []);
1283
+ const incomingEdges = edges.filter((e) => e.source === workflowNodeId &&
1284
+ !e.data?.isForEachEndEdge);
1285
+ for (const edge of incomingEdges) {
1286
+ const approval = approvalByApprovedNodeId.get(edge.target);
1287
+ if (approval)
1288
+ dependsOn.add(approval.approvalFlowId);
1289
+ }
1290
+ if (dependsOn.size > 0)
1291
+ flowItem.dependsOn = [...dependsOn].sort();
1292
+ }
1293
+ for (const approvalInfo of approvalByApprovedNodeId.values()) {
1294
+ flow.push({
1295
+ id: approvalInfo.approvalFlowId,
1296
+ type: 'user-approval',
1297
+ dependsOn: [approvalInfo.approvedFlowId],
1298
+ label: approvalInfo.message,
1299
+ });
1300
+ }
1301
+ }
992
1302
  //# sourceMappingURL=workflow_converter.mjs.map