@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
@@ -2,7 +2,24 @@
2
2
  // Converts a workflow editor state (nodes + edges) to a flow array suitable for the Scenario API.
3
3
  // Ported from fawkes useConvertWorkflowToApp — pure function, no React dependencies.
4
4
  Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.ASPECT_RATIO_PRESETS = void 0;
6
+ exports.normalizeAspectRatio = normalizeAspectRatio;
7
+ exports.getAspectRatioModelInputNames = getAspectRatioModelInputNames;
8
+ exports.getAspectRatioBounds = getAspectRatioBounds;
9
+ exports.getDimensionsFromAspectRatio = getDimensionsFromAspectRatio;
5
10
  exports.convertWorkflowEditorToFlow = convertWorkflowEditorToFlow;
11
+ const RUNNABLE_TYPES = new Set(['model', 'llm', 'forEach', 'forEachEnd']);
12
+ exports.ASPECT_RATIO_PRESETS = [
13
+ '16:9',
14
+ '3:2',
15
+ '4:3',
16
+ '5:4',
17
+ '1:1',
18
+ '4:5',
19
+ '3:4',
20
+ '2:3',
21
+ '9:16',
22
+ ];
6
23
  // ---- Utility functions ----
7
24
  function nodeIdToFlowId(nodeId) {
8
25
  return nodeId.replace(/\s/g, '');
@@ -33,6 +50,96 @@ function convertValueToList(value) {
33
50
  function getConditionalInputHandleId(nodeId) {
34
51
  return `${nodeId}-source-conditional`;
35
52
  }
53
+ function handleTypeIncludes(handleType, value) {
54
+ if (Array.isArray(handleType))
55
+ return handleType.includes(value);
56
+ return handleType === value;
57
+ }
58
+ function parseAspectRatio(aspectRatio) {
59
+ if (!aspectRatio)
60
+ return undefined;
61
+ const [rawWidth, rawHeight] = aspectRatio.split(':');
62
+ const width = Number(rawWidth);
63
+ const height = Number(rawHeight);
64
+ if (!Number.isFinite(width) ||
65
+ !Number.isFinite(height) ||
66
+ width <= 0 ||
67
+ height <= 0 ||
68
+ !Number.isInteger(width) ||
69
+ !Number.isInteger(height)) {
70
+ return undefined;
71
+ }
72
+ return { width, height };
73
+ }
74
+ function getGreatestCommonDivisor(a, b) {
75
+ let x = Math.abs(Math.trunc(a));
76
+ let y = Math.abs(Math.trunc(b));
77
+ while (y) {
78
+ const temp = y;
79
+ y = x % y;
80
+ x = temp;
81
+ }
82
+ return x || 1;
83
+ }
84
+ function makeNumberAMultipleOfX(value, multipleOf) {
85
+ if (multipleOf <= 0)
86
+ return Math.round(value);
87
+ return Math.round(value / multipleOf) * multipleOf;
88
+ }
89
+ function clamp(value, min, max) {
90
+ return Math.min(Math.max(value, min), max);
91
+ }
92
+ function normalizeAspectRatio(aspectRatio) {
93
+ const parsed = parseAspectRatio(aspectRatio);
94
+ if (!parsed)
95
+ return undefined;
96
+ const divisor = getGreatestCommonDivisor(parsed.width, parsed.height);
97
+ return `${parsed.width / divisor}:${parsed.height / divisor}`;
98
+ }
99
+ function getAspectRatioModelInputNames(model) {
100
+ if (!model)
101
+ return {};
102
+ const widthInputName = model.uiConfig?.resolutionComponent?.widthInput ??
103
+ model.inputs?.find((input) => input.name === 'width')?.name;
104
+ const heightInputName = model.uiConfig?.resolutionComponent?.heightInput ??
105
+ model.inputs?.find((input) => input.name === 'height')?.name;
106
+ const aspectRatioInputName = model.inputs?.find((input) => input.name === 'aspectRatio')?.name;
107
+ return { widthInputName, heightInputName, aspectRatioInputName };
108
+ }
109
+ function getAspectRatioBounds(model, { widthInputName, heightInputName }) {
110
+ if (!model || !widthInputName || !heightInputName)
111
+ return undefined;
112
+ const widthInput = model.inputs?.find((input) => input.name === widthInputName);
113
+ const heightInput = model.inputs?.find((input) => input.name === heightInputName);
114
+ const presets = model.uiConfig?.resolutionComponent?.presets;
115
+ const widthPresets = (presets ?? [])
116
+ .map((preset) => preset.width)
117
+ .filter((value) => typeof value === 'number');
118
+ const heightPresets = (presets ?? [])
119
+ .map((preset) => preset.height)
120
+ .filter((value) => typeof value === 'number');
121
+ const multipleOf = widthInput?.step ?? 8;
122
+ return {
123
+ maxWidth: widthInput?.max ?? (widthPresets.length > 0 ? Math.max(...widthPresets) : 2048),
124
+ minWidth: widthInput?.min ?? (widthPresets.length > 0 ? Math.min(...widthPresets) : 64),
125
+ maxHeight: heightInput?.max ?? (heightPresets.length > 0 ? Math.max(...heightPresets) : 2048),
126
+ minHeight: heightInput?.min ?? (heightPresets.length > 0 ? Math.min(...heightPresets) : 64),
127
+ multipleOf,
128
+ };
129
+ }
130
+ function getDimensionsFromAspectRatio(args) {
131
+ const parsed = parseAspectRatio(args.aspectRatio);
132
+ if (!parsed)
133
+ return undefined;
134
+ const ratio = parsed.width / parsed.height;
135
+ let width = ratio >= 1 ? args.bounds.maxWidth : Math.round(args.bounds.maxHeight * ratio);
136
+ let height = ratio >= 1 ? Math.round(args.bounds.maxWidth / ratio) : args.bounds.maxHeight;
137
+ width = makeNumberAMultipleOfX(width, args.bounds.multipleOf);
138
+ height = makeNumberAMultipleOfX(height, args.bounds.multipleOf);
139
+ width = clamp(width, args.bounds.minWidth, args.bounds.maxWidth);
140
+ height = clamp(height, args.bounds.minHeight, args.bounds.maxHeight);
141
+ return { width, height };
142
+ }
36
143
  function getForEachFlowListInputDescriptor(args) {
37
144
  const { listProvider, outputHandle, inputIndex } = args;
38
145
  const isText = listProvider?.type === 'splitText' ||
@@ -260,11 +367,11 @@ function toStaticInput(name, type, value) {
260
367
  return undefined;
261
368
  return { name, type, value: value };
262
369
  }
263
- function isFormArray(value) {
370
+ function getIsFormArray(value) {
264
371
  return (Array.isArray(value) &&
265
372
  value.every((item) => typeof item === 'object' && item !== null && !Array.isArray(item)));
266
373
  }
267
- function isModelCompose(model) {
374
+ function getIsModelCompose(model) {
268
375
  return !!model?.id && (model.tags ?? []).includes('compose');
269
376
  }
270
377
  function getIsConditionallyExecuted(targetNodeId, nodes, edges) {
@@ -274,6 +381,87 @@ function getIsConditionallyExecuted(targetNodeId, nodes, edges) {
274
381
  return (e.source === targetNodeId && e.sourceHandle === conditionalHandleId && sourceNode?.type === 'ifElse');
275
382
  });
276
383
  }
384
+ function buildDimensionLookupCEL(args) {
385
+ let expression = String(args.fallback);
386
+ for (let i = args.presets.length - 1; i >= 0; i--) {
387
+ const preset = args.presets[i];
388
+ expression = `trim(${args.variable}) == '${preset.aspectRatio}' ? ${preset[args.dimension]} : ${expression}`;
389
+ }
390
+ return expression;
391
+ }
392
+ function applyAspectRatioInputs(args) {
393
+ const modelInputNames = getAspectRatioModelInputNames(args.model);
394
+ const modelBounds = getAspectRatioBounds(args.model, modelInputNames);
395
+ const { widthInputName, heightInputName, aspectRatioInputName } = modelInputNames;
396
+ if (aspectRatioInputName) {
397
+ const transformId = `${args.flowId}_aspectRatio_value`;
398
+ args.flow.push({
399
+ id: transformId,
400
+ type: 'transform',
401
+ logic: { transform: 'trim(aspectRatio)' },
402
+ inputs: [{ name: 'aspectRatio', type: 'string', ref: args.aspectRatioRef }],
403
+ });
404
+ setOrPushFlowInput(args.flowInputs, args.flowInputs.findIndex((inp) => inp.name === aspectRatioInputName), {
405
+ name: aspectRatioInputName,
406
+ type: 'string',
407
+ ref: { node: transformId, name: 'all' },
408
+ });
409
+ }
410
+ if (widthInputName && heightInputName && modelBounds) {
411
+ const presetDimensions = exports.ASPECT_RATIO_PRESETS.flatMap((preset) => {
412
+ const dims = getDimensionsFromAspectRatio({ aspectRatio: preset, bounds: modelBounds });
413
+ if (!dims)
414
+ return [];
415
+ const normalized = normalizeAspectRatio(preset);
416
+ if (!normalized)
417
+ return [];
418
+ return [{ aspectRatio: normalized, ...dims }];
419
+ });
420
+ const fallbackDimensions = presetDimensions.find((preset) => preset.aspectRatio === '1:1') ??
421
+ presetDimensions[0] ?? {
422
+ width: modelBounds.maxWidth,
423
+ height: modelBounds.maxHeight,
424
+ };
425
+ const widthTransformId = `${args.flowId}_aspectRatio_width`;
426
+ const heightTransformId = `${args.flowId}_aspectRatio_height`;
427
+ args.flow.push({
428
+ id: widthTransformId,
429
+ type: 'transform',
430
+ logic: {
431
+ transform: buildDimensionLookupCEL({
432
+ variable: 'aspectRatio',
433
+ presets: presetDimensions,
434
+ dimension: 'width',
435
+ fallback: fallbackDimensions.width,
436
+ }),
437
+ },
438
+ inputs: [{ name: 'aspectRatio', type: 'string', ref: args.aspectRatioRef }],
439
+ });
440
+ args.flow.push({
441
+ id: heightTransformId,
442
+ type: 'transform',
443
+ logic: {
444
+ transform: buildDimensionLookupCEL({
445
+ variable: 'aspectRatio',
446
+ presets: presetDimensions,
447
+ dimension: 'height',
448
+ fallback: fallbackDimensions.height,
449
+ }),
450
+ },
451
+ inputs: [{ name: 'aspectRatio', type: 'string', ref: args.aspectRatioRef }],
452
+ });
453
+ setOrPushFlowInput(args.flowInputs, args.flowInputs.findIndex((inp) => inp.name === widthInputName), {
454
+ name: widthInputName,
455
+ type: 'number',
456
+ ref: { node: widthTransformId, name: 'all' },
457
+ });
458
+ setOrPushFlowInput(args.flowInputs, args.flowInputs.findIndex((inp) => inp.name === heightInputName), {
459
+ name: heightInputName,
460
+ type: 'number',
461
+ ref: { node: heightTransformId, name: 'all' },
462
+ });
463
+ }
464
+ }
277
465
  function getForEachBodyEditorNodeIds(forEachId, forEachEndId, edges) {
278
466
  const bodyNodeIds = new Set();
279
467
  const queue = [];
@@ -325,7 +513,6 @@ function getNodeOutputValue(node, targetHandleId) {
325
513
  }
326
514
  return undefined;
327
515
  }
328
- const RUNNABLE_TYPES = new Set(['model', 'llm', 'forEach', 'forEachEnd']);
329
516
  function internalGetConnectedNodes(nodes, edges, startNodeId, direction, isStoppingAtRunnableNodes) {
330
517
  const nodesById = new Map(nodes.map((n) => {
331
518
  const meta = { id: n.id, type: n.type };
@@ -365,42 +552,44 @@ function internalGetConnectedNodes(nodes, edges, startNodeId, direction, isStopp
365
552
  const idSet = new Set(ids);
366
553
  return nodes.filter((n) => idSet.has(n.id));
367
554
  }
555
+ function resolveIfElseConditionField(field, candidateNodes) {
556
+ const byExactId = candidateNodes.find((n) => n.id === field);
557
+ if (byExactId)
558
+ return { nodeId: field };
559
+ for (const node of candidateNodes) {
560
+ for (const handle of node.data.outputHandles ?? []) {
561
+ if (`${node.id}_${handle.name}` === field) {
562
+ return { nodeId: node.id, targetHandleId: handle.id };
563
+ }
564
+ }
565
+ }
566
+ return undefined;
567
+ }
368
568
  function normalizeNodes(nodes) {
369
569
  return nodes.map((node) => {
370
570
  if (node.type !== 'model' && node.type !== 'llm')
371
571
  return node;
372
- const hasType = (t, v) => Array.isArray(t) ? t.includes(v) : t === v;
373
572
  const inputHandles = (node.data.inputHandles ?? [])
374
573
  .flatMap((h) => (h.subHandles ? h.subHandles : [h]))
375
- .filter((h) => hasType(h.type, 'conditional') ||
376
- hasType(h.type, 'prompt') ||
377
- hasType(h.type, 'image') ||
378
- hasType(h.type, 'video') ||
379
- hasType(h.type, '3d') ||
380
- hasType(h.type, 'audio') ||
574
+ .filter((h) => handleTypeIncludes(h.type, 'conditional') ||
575
+ handleTypeIncludes(h.type, 'aspectRatio') ||
576
+ handleTypeIncludes(h.type, 'prompt') ||
577
+ handleTypeIncludes(h.type, 'image') ||
578
+ handleTypeIncludes(h.type, 'video') ||
579
+ handleTypeIncludes(h.type, '3d') ||
580
+ handleTypeIncludes(h.type, 'audio') ||
381
581
  (node.type === 'llm' && h.name === 'textInputs'));
382
582
  return { ...node, data: { ...node.data, inputHandles } };
383
583
  });
384
584
  }
385
- // ---- Main function ----
386
- /**
387
- * Converts a workflow editor state (nodes, edges, inputKeys) into a flow array
388
- * ready to be passed to `client.workflows.update({ flow: ... })`.
389
- *
390
- * @example
391
- * ```ts
392
- * const flow = convertWorkflowEditorToFlow({ nodes, edges, inputKeys, getModel });
393
- * await client.workflows.update(workflowId, { flow: flow as WorkflowUpdateParams.Flow[] });
394
- * ```
395
- */
396
585
  function convertWorkflowEditorToFlow(params) {
397
586
  const { edges, inputKeys } = params;
398
587
  const nodes = normalizeNodes(params.nodes);
399
588
  const inputNodeIds = nodes.filter((n) => !!n.data.isInput).map((n) => n.id);
400
- const selectedOutputNodes = nodes.filter((n) => (n.type === 'model' || n.type === 'forEachEnd') && !!n.data.isOutput);
589
+ const selectedOutputNodes = nodes.filter((n) => (n.type === 'model' || n.type === 'forEachEnd' || n.type === 'llm') && !!n.data.isOutput);
401
590
  const selectedOutputNodeIds = selectedOutputNodes.map((n) => n.id);
402
591
  const selectedOutputModelNodeIds = new Set(selectedOutputNodes
403
- .filter((n) => n.type === 'model')
592
+ .filter((n) => n.type === 'model' || n.type === 'llm')
404
593
  .map((n) => n.id));
405
594
  // Collect all nodes in branches that lead to selected outputs
406
595
  const nodesInSelectedBranches = new Set();
@@ -434,6 +623,12 @@ function convertWorkflowEditorToFlow(params) {
434
623
  .filter((n) => !inputNodeIds.includes(n.id) && n.type !== 'forEachEnd')
435
624
  .map((n) => [n.id, nodeIdToFlowId(n.id)]));
436
625
  const nodeIdToInputNameMap = new Map(inputNodeIds.map((id) => [id, nodeIdToFlowId(id)]));
626
+ const approvalByApprovedNodeId = buildApprovalByApprovedNodeIdMap({
627
+ nodes,
628
+ edges,
629
+ nodesInSelectedBranches,
630
+ selectedOutputNodeIds,
631
+ });
437
632
  const nodeHandleTransformMap = new Map();
438
633
  const ctx = {
439
634
  nodes,
@@ -507,6 +702,7 @@ function convertWorkflowEditorToFlow(params) {
507
702
  const model = remoteModel === 'unknown' ? undefined : remoteModel;
508
703
  const modelInputs = model?.inputs;
509
704
  const conditionalHandleId = getConditionalInputHandleId(node.id);
705
+ const aspectRatioEdges = [];
510
706
  const edgesByInput = new Map();
511
707
  for (const edge of incomingEdges) {
512
708
  if (edge.sourceHandle === conditionalHandleId)
@@ -514,6 +710,10 @@ function convertWorkflowEditorToFlow(params) {
514
710
  const handle = getInputHandle(edge, node.data.inputHandles);
515
711
  if (!handle?.name)
516
712
  continue;
713
+ if (handleTypeIncludes(handle.type, 'aspectRatio')) {
714
+ aspectRatioEdges.push(edge);
715
+ continue;
716
+ }
517
717
  const existing = edgesByInput.get(handle.name);
518
718
  if (existing)
519
719
  existing.push(edge);
@@ -526,7 +726,7 @@ function convertWorkflowEditorToFlow(params) {
526
726
  continue;
527
727
  const modelInput = modelInputs?.find((inp) => inp.name === key);
528
728
  if (modelInput?.type === 'inputs_array') {
529
- if (!isFormArray(value))
729
+ if (!getIsFormArray(value))
530
730
  continue;
531
731
  const nestedInputs = modelInput.inputs ?? [];
532
732
  const items = [];
@@ -547,7 +747,7 @@ function convertWorkflowEditorToFlow(params) {
547
747
  itemInputs.push(inp);
548
748
  }
549
749
  }
550
- const isValid = isModelCompose(model) ? getHasComposeLayerSourceInput(itemInputs) : itemInputs.length > 0;
750
+ const isValid = getIsModelCompose(model) ? getHasComposeLayerSourceInput(itemInputs) : itemInputs.length > 0;
551
751
  if (isValid)
552
752
  items.push(itemInputs);
553
753
  }
@@ -565,6 +765,18 @@ function convertWorkflowEditorToFlow(params) {
565
765
  }
566
766
  }
567
767
  }
768
+ if (aspectRatioEdges.length > 0 && model) {
769
+ const aspectRatioRef = getSourceRef(aspectRatioEdges[0], ctx);
770
+ if (aspectRatioRef) {
771
+ applyAspectRatioInputs({
772
+ flow,
773
+ flowInputs,
774
+ flowId,
775
+ aspectRatioRef,
776
+ model,
777
+ });
778
+ }
779
+ }
568
780
  for (const [inputName, inputEdges] of edgesByInput) {
569
781
  const existingIndex = flowInputs.findIndex((inp) => inp.name === inputName);
570
782
  const modelInput = modelInputs?.find((inp) => inp.name === inputName);
@@ -625,7 +837,7 @@ function convertWorkflowEditorToFlow(params) {
625
837
  const isArr = targetInputType === 'file_array';
626
838
  const isRequired = (targetNode?.type === 'asset' &&
627
839
  !!targetNode.data.isRequired) ||
628
- isModelCompose(model);
840
+ getIsModelCompose(model);
629
841
  transformVariables.push({ name: targetNodeKey, isArray: isArr, isRequired });
630
842
  if (!targetNode?.data.isInput &&
631
843
  (targetNode?.type === 'asset' || targetNode?.type === 'text')) {
@@ -696,7 +908,9 @@ function convertWorkflowEditorToFlow(params) {
696
908
  type: 'custom-model',
697
909
  modelId: nodeData.modelId,
698
910
  inputs: flowInputs.length > 0 ? flowInputs : undefined,
699
- includeOutputsInWorkflowJob: node.type === 'model' && selectedOutputModelNodeIds.has(node.id) ? true : undefined,
911
+ includeOutputsInWorkflowJob: (node.type === 'model' || node.type === 'llm') && selectedOutputModelNodeIds.has(node.id) ?
912
+ true
913
+ : undefined,
700
914
  });
701
915
  ensureInputVideoTransformsAdded();
702
916
  if (node.type === 'model' && node.data.type === 'video') {
@@ -759,6 +973,19 @@ function convertWorkflowEditorToFlow(params) {
759
973
  addVideoFirstLastFrameTransforms(node.id, flowId, node, edges, flow, nodeHandleTransformMap);
760
974
  }
761
975
  // ------------------------------------
976
+ // AspectRatio -> transform with ratio literal
977
+ // ------------------------------------
978
+ }
979
+ else if (node.type === 'aspectRatio') {
980
+ const flowInputs = collectConditionalConnectionInputs(incomingEdges, node.id, ctx);
981
+ const value = node.data.output || '1:1';
982
+ flow.push({
983
+ id: flowId,
984
+ type: 'transform',
985
+ logic: { transform: `'${escapeCELString(value)}'` },
986
+ inputs: flowInputs.length > 0 ? flowInputs : undefined,
987
+ });
988
+ // ------------------------------------
762
989
  // TransformText -> transform (CEL)
763
990
  // ------------------------------------
764
991
  }
@@ -837,7 +1064,7 @@ function convertWorkflowEditorToFlow(params) {
837
1064
  continue;
838
1065
  const outHandle = targetNode.data.outputHandles?.find((h) => h.id === edge.targetHandle);
839
1066
  const outHandleName = outHandle?.name ?? 'output';
840
- const varName = getOutputHandleVarName(targetNode.id, outHandleName);
1067
+ const varName = `${targetNode.id}_${outHandleName}`;
841
1068
  const isArr = (targetNode.type === 'asset' && !!targetNode.data.isMultiple) ||
842
1069
  targetNode.type === 'groupItems' ||
843
1070
  targetNode.type === 'sliceAssets' ||
@@ -946,17 +1173,47 @@ function convertWorkflowEditorToFlow(params) {
946
1173
  fieldsUsed.add(condition.field);
947
1174
  }
948
1175
  }
949
- for (const fieldNodeId of fieldsUsed) {
1176
+ const edgesIntoIfElse = edges.filter((e) => e.target === node.id);
1177
+ for (const fieldKey of fieldsUsed) {
1178
+ const resolved = resolveIfElseConditionField(fieldKey, nodes);
1179
+ const fieldNodeId = resolved?.nodeId;
1180
+ if (!fieldNodeId)
1181
+ continue;
1182
+ const outputHandleName = resolved?.targetHandleId !== undefined ?
1183
+ nodes
1184
+ .find((n) => n.id === fieldNodeId)
1185
+ ?.data.outputHandles?.find((h) => h.id === resolved.targetHandleId)?.name ?? 'output'
1186
+ : 'output';
1187
+ const celVarName = getOutputHandleVarName(fieldNodeId, outputHandleName);
1188
+ nodeIdToInputNameMap.set(fieldKey, celVarName);
1189
+ const edgeToIfElse = edgesIntoIfElse
1190
+ .filter((e) => e.source === fieldNodeId)
1191
+ .find((e) => {
1192
+ if (resolved.targetHandleId === undefined)
1193
+ return true;
1194
+ return e.sourceHandle === resolved.targetHandleId;
1195
+ }) ?? edgesIntoIfElse.find((e) => e.source === fieldNodeId);
950
1196
  if (inputNodeIds.includes(fieldNodeId)) {
951
- const inputName = nodeIdToInputNameMap.get(fieldNodeId);
952
- if (inputName)
953
- flowInputs.push({ name: inputName, type: 'string', ref: { node: 'workflow', name: inputName } });
1197
+ const workflowInputName = nodeIdToInputNameMap.get(fieldNodeId);
1198
+ if (workflowInputName) {
1199
+ const ref = edgeToIfElse ?
1200
+ getSourceRef(edgeToIfElse, ctx)
1201
+ : { node: 'workflow', name: workflowInputName };
1202
+ flowInputs.push({
1203
+ name: celVarName,
1204
+ type: 'string',
1205
+ ref: ref ?? { node: 'workflow', name: workflowInputName },
1206
+ });
1207
+ }
954
1208
  }
955
1209
  else {
1210
+ const refFromEdge = edgeToIfElse ? getSourceRef(edgeToIfElse, ctx) : undefined;
956
1211
  const targetFlowId = nodeIdToFlowIdMap.get(fieldNodeId);
957
- if (targetFlowId) {
958
- flowInputs.push({ name: targetFlowId, type: 'string', ref: { node: targetFlowId, name: 'all' } });
959
- nodeIdToInputNameMap.set(fieldNodeId, targetFlowId);
1212
+ if (refFromEdge) {
1213
+ flowInputs.push({ name: celVarName, type: 'string', ref: refFromEdge });
1214
+ }
1215
+ else if (targetFlowId) {
1216
+ flowInputs.push({ name: celVarName, type: 'string', ref: { node: targetFlowId, name: 'all' } });
960
1217
  }
961
1218
  }
962
1219
  }
@@ -990,6 +1247,64 @@ function convertWorkflowEditorToFlow(params) {
990
1247
  for (const [forEachId, forEachFlowItem] of forEachFlowItemRefs) {
991
1248
  forEachFlowItem.loopBodyNodeIds = forEachLoopFlowIds.get(forEachId) ?? [];
992
1249
  }
1250
+ applyApprovalsToFlow({ flow, edges, approvalByApprovedNodeId, nodeIdToFlowIdMap });
993
1251
  return flow;
994
1252
  }
1253
+ function buildApprovalByApprovedNodeIdMap({ nodes, edges, nodesInSelectedBranches, selectedOutputNodeIds, }) {
1254
+ const result = new Map();
1255
+ for (const approvalNode of nodes) {
1256
+ if (approvalNode.type !== 'approval')
1257
+ continue;
1258
+ const approvalEdge = edges.find((e) => e.source === approvalNode.id && e.sourceHandle === `${approvalNode.id}-source-approval`);
1259
+ if (!approvalEdge)
1260
+ continue;
1261
+ const approvedNodeId = approvalEdge.target;
1262
+ if (!nodesInSelectedBranches.has(approvedNodeId) && !selectedOutputNodeIds.includes(approvedNodeId))
1263
+ continue;
1264
+ const approvedNode = nodes.find((n) => n.id === approvedNodeId);
1265
+ if (!approvedNode)
1266
+ continue;
1267
+ const approvedFlowId = approvedNode.type === 'forEachEnd' ?
1268
+ approvedNode.data.parentNodeId ?
1269
+ nodeIdToFlowId(approvedNode.data.parentNodeId)
1270
+ : undefined
1271
+ : nodeIdToFlowId(approvedNodeId);
1272
+ if (!approvedFlowId)
1273
+ continue;
1274
+ result.set(approvedNodeId, {
1275
+ approvalFlowId: nodeIdToFlowId(approvalNode.id),
1276
+ approvedFlowId,
1277
+ message: approvalNode.data.message ?? '',
1278
+ });
1279
+ }
1280
+ return result;
1281
+ }
1282
+ function applyApprovalsToFlow({ flow, edges, approvalByApprovedNodeId, nodeIdToFlowIdMap, }) {
1283
+ if (approvalByApprovedNodeId.size === 0)
1284
+ return;
1285
+ const flowIdToWorkflowNodeId = new Map([...nodeIdToFlowIdMap.entries()].map(([nodeId, flowId]) => [flowId, nodeId]));
1286
+ for (const flowItem of flow) {
1287
+ const workflowNodeId = flowIdToWorkflowNodeId.get(flowItem.id);
1288
+ if (!workflowNodeId)
1289
+ continue;
1290
+ const dependsOn = new Set(flowItem.dependsOn ?? []);
1291
+ const incomingEdges = edges.filter((e) => e.source === workflowNodeId &&
1292
+ !e.data?.isForEachEndEdge);
1293
+ for (const edge of incomingEdges) {
1294
+ const approval = approvalByApprovedNodeId.get(edge.target);
1295
+ if (approval)
1296
+ dependsOn.add(approval.approvalFlowId);
1297
+ }
1298
+ if (dependsOn.size > 0)
1299
+ flowItem.dependsOn = [...dependsOn].sort();
1300
+ }
1301
+ for (const approvalInfo of approvalByApprovedNodeId.values()) {
1302
+ flow.push({
1303
+ id: approvalInfo.approvalFlowId,
1304
+ type: 'user-approval',
1305
+ dependsOn: [approvalInfo.approvedFlowId],
1306
+ label: approvalInfo.message,
1307
+ });
1308
+ }
1309
+ }
995
1310
  //# sourceMappingURL=workflow_converter.js.map