@nodaro/shared 1.4.1

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 (665) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +32 -0
  3. package/dist/index.cjs +12506 -0
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.d.cts +12269 -0
  6. package/dist/index.d.ts +12269 -0
  7. package/dist/index.js +11831 -0
  8. package/dist/index.js.map +1 -0
  9. package/package.json +54 -0
  10. package/src/__tests__/__snapshots__/prompt-builder-segments.test.ts.snap +65 -0
  11. package/src/__tests__/ai-avatar-pricing.test.ts +321 -0
  12. package/src/__tests__/apply-video-negative-prompt.test.ts +76 -0
  13. package/src/__tests__/attach-columns.test.ts +28 -0
  14. package/src/__tests__/audio-crossfade-curves.test.ts +33 -0
  15. package/src/__tests__/build-model-menu.test.ts +58 -0
  16. package/src/__tests__/character-aspect-defaults.test.ts +142 -0
  17. package/src/__tests__/character-mention-slug.test.ts +199 -0
  18. package/src/__tests__/character-reference-photo-kinds.test.ts +32 -0
  19. package/src/__tests__/character-usage-mode.test.ts +147 -0
  20. package/src/__tests__/character-variant-assets.test.ts +142 -0
  21. package/src/__tests__/character-voice.test.ts +130 -0
  22. package/src/__tests__/cinematic-avatar-pricing.test.ts +119 -0
  23. package/src/__tests__/combine-transitions.test.ts +137 -0
  24. package/src/__tests__/component-types.test.ts +87 -0
  25. package/src/__tests__/condition-variables.test.ts +98 -0
  26. package/src/__tests__/credit-identifiers.test.ts +811 -0
  27. package/src/__tests__/default-video-provider.test.ts +63 -0
  28. package/src/__tests__/entity-image-handle.test.ts +43 -0
  29. package/src/__tests__/entity-variants.test.ts +33 -0
  30. package/src/__tests__/fal-lip-sync-sets.test.ts +31 -0
  31. package/src/__tests__/featured-entities.test.ts +33 -0
  32. package/src/__tests__/film-pricing.test.ts +64 -0
  33. package/src/__tests__/filter-condition.test.ts +285 -0
  34. package/src/__tests__/freecut-protocol.test.ts +35 -0
  35. package/src/__tests__/generate-text-items.test.ts +19 -0
  36. package/src/__tests__/generated-results.test.ts +50 -0
  37. package/src/__tests__/graph-utils.test.ts +618 -0
  38. package/src/__tests__/group-aggregation.test.ts +83 -0
  39. package/src/__tests__/i18n-locale-completeness.test.ts +156 -0
  40. package/src/__tests__/image-critic-node.test.ts +86 -0
  41. package/src/__tests__/image-mask-mode.test.ts +16 -0
  42. package/src/__tests__/image-ref-limit.test.ts +78 -0
  43. package/src/__tests__/inpaint-region.test.ts +24 -0
  44. package/src/__tests__/input-field-map-parity.test.ts +30 -0
  45. package/src/__tests__/json-evaluator.test.ts +289 -0
  46. package/src/__tests__/json-path.test.ts +64 -0
  47. package/src/__tests__/lip-sync-pricing.test.ts +142 -0
  48. package/src/__tests__/list-merge.test.ts +96 -0
  49. package/src/__tests__/list-sort.test.ts +134 -0
  50. package/src/__tests__/llm-models.test.ts +445 -0
  51. package/src/__tests__/location-mention-slug.test.ts +246 -0
  52. package/src/__tests__/locations-coverage-critic-schema.test.ts +92 -0
  53. package/src/__tests__/loop-delimiter.test.ts +39 -0
  54. package/src/__tests__/lottie-slots.test.ts +245 -0
  55. package/src/__tests__/model-catalog-flux2-resolutions.test.ts +21 -0
  56. package/src/__tests__/model-constants-location.test.ts +20 -0
  57. package/src/__tests__/model-constants.test.ts +122 -0
  58. package/src/__tests__/model-tree.test.ts +86 -0
  59. package/src/__tests__/no-internal-imports.test.ts +25 -0
  60. package/src/__tests__/node-default-mappings.test.ts +175 -0
  61. package/src/__tests__/node-mappable-fields.test.ts +7 -0
  62. package/src/__tests__/node-preset-export.test.ts +46 -0
  63. package/src/__tests__/node-preset-extract.test.ts +166 -0
  64. package/src/__tests__/node-refs-canonical.test.ts +54 -0
  65. package/src/__tests__/node-refs-combine.test.ts +54 -0
  66. package/src/__tests__/node-refs.test.ts +187 -0
  67. package/src/__tests__/node-runtime-keys.test.ts +99 -0
  68. package/src/__tests__/object-aspect-defaults.test.ts +59 -0
  69. package/src/__tests__/parameter-node-value.test.ts +137 -0
  70. package/src/__tests__/pinterest-url.test.ts +35 -0
  71. package/src/__tests__/pipeline-chat.test.ts +110 -0
  72. package/src/__tests__/pipeline-validation.test.ts +98 -0
  73. package/src/__tests__/presentation-utils.test.ts +1137 -0
  74. package/src/__tests__/producer-types.test.ts +47 -0
  75. package/src/__tests__/prompt-length-limits.test.ts +156 -0
  76. package/src/__tests__/provider-directive-defaults.test.ts +46 -0
  77. package/src/__tests__/reduce-strategy-registry.test.ts +43 -0
  78. package/src/__tests__/reference-board-templates.test.ts +33 -0
  79. package/src/__tests__/reference-roles.test.ts +36 -0
  80. package/src/__tests__/resolve-field-mappings.test.ts +110 -0
  81. package/src/__tests__/resolve-pipeline-model.test.ts +76 -0
  82. package/src/__tests__/scene-node-types.test.ts +80 -0
  83. package/src/__tests__/scraper-actors.test.ts +67 -0
  84. package/src/__tests__/seedance-2-catalog.test.ts +18 -0
  85. package/src/__tests__/seedance-2-credit-id.test.ts +23 -0
  86. package/src/__tests__/seedance-aspects.test.ts +52 -0
  87. package/src/__tests__/selector.test.ts +1151 -0
  88. package/src/__tests__/settled-with-limit.test.ts +214 -0
  89. package/src/__tests__/social-media-specs.test.ts +129 -0
  90. package/src/__tests__/sub-workflow-validation.test.ts +88 -0
  91. package/src/__tests__/switchx-pricing.test.ts +28 -0
  92. package/src/__tests__/to-connected-references.test.ts +153 -0
  93. package/src/__tests__/utilities.test.ts +541 -0
  94. package/src/__tests__/video-analysis-pricing.test.ts +58 -0
  95. package/src/__tests__/video-analysis.test.ts +63 -0
  96. package/src/__tests__/video-audio-capability.test.ts +204 -0
  97. package/src/__tests__/video-gen-providers.test.ts +28 -0
  98. package/src/__tests__/video-mode-aliases.test.ts +150 -0
  99. package/src/__tests__/video-ref-limits.test.ts +72 -0
  100. package/src/__tests__/video-utils.test.ts +303 -0
  101. package/src/__tests__/voice-changer-models.test.ts +33 -0
  102. package/src/__tests__/workflow-export-strip.test.ts +46 -0
  103. package/src/ai-avatar-pricing.ts +213 -0
  104. package/src/ancestor-refs.ts +48 -0
  105. package/src/animals.ts +231 -0
  106. package/src/audio-crossfade-curves.ts +73 -0
  107. package/src/audio-fx-presets.ts +64 -0
  108. package/src/avatar-payload-validators.ts +214 -0
  109. package/src/caption-styles.ts +31 -0
  110. package/src/character-aspect-defaults.ts +102 -0
  111. package/src/character-facets.ts +37 -0
  112. package/src/character-mention-slug.ts +166 -0
  113. package/src/character-usage-mode.ts +109 -0
  114. package/src/character-variant-assets.ts +121 -0
  115. package/src/character-voice.ts +151 -0
  116. package/src/cinematic-avatar-pricing.ts +111 -0
  117. package/src/clone-utils.ts +46 -0
  118. package/src/combine-transitions.ts +482 -0
  119. package/src/community.ts +112 -0
  120. package/src/component-types.ts +127 -0
  121. package/src/condition-variables.ts +65 -0
  122. package/src/credit-estimators/index.ts +15 -0
  123. package/src/credit-estimators/video-utils.ts +165 -0
  124. package/src/credit-identifiers.ts +304 -0
  125. package/src/entity-approval-types.ts +31 -0
  126. package/src/entity-aspect-defaults.ts +82 -0
  127. package/src/entity-asset-types.ts +296 -0
  128. package/src/entity-image-handle.ts +36 -0
  129. package/src/extra-refs.ts +139 -0
  130. package/src/featured-entities.ts +136 -0
  131. package/src/film-pricing.ts +117 -0
  132. package/src/filter-condition.ts +250 -0
  133. package/src/flux2-pricing.ts +18 -0
  134. package/src/freecut-protocol.ts +52 -0
  135. package/src/furniture.ts +167 -0
  136. package/src/generate-text-items.ts +15 -0
  137. package/src/generated-results.ts +63 -0
  138. package/src/group-aggregation.ts +75 -0
  139. package/src/hint-graph-types.ts +18 -0
  140. package/src/i18n/action-fx.ar.ts +89 -0
  141. package/src/i18n/action-fx.de.ts +89 -0
  142. package/src/i18n/action-fx.es.ts +89 -0
  143. package/src/i18n/action-fx.fr.ts +89 -0
  144. package/src/i18n/action-fx.he.ts +89 -0
  145. package/src/i18n/action-fx.hi.ts +89 -0
  146. package/src/i18n/action-fx.ja.ts +89 -0
  147. package/src/i18n/action-fx.ko.ts +89 -0
  148. package/src/i18n/action-fx.pt-BR.ts +89 -0
  149. package/src/i18n/action-fx.ru.ts +89 -0
  150. package/src/i18n/action-fx.zh-CN.ts +89 -0
  151. package/src/i18n/aesthetic.ar.ts +170 -0
  152. package/src/i18n/aesthetic.de.ts +48 -0
  153. package/src/i18n/aesthetic.es.ts +169 -0
  154. package/src/i18n/aesthetic.fr.ts +52 -0
  155. package/src/i18n/aesthetic.he.ts +169 -0
  156. package/src/i18n/aesthetic.hi.ts +55 -0
  157. package/src/i18n/aesthetic.ja.ts +165 -0
  158. package/src/i18n/aesthetic.ko.ts +169 -0
  159. package/src/i18n/aesthetic.pt-BR.ts +148 -0
  160. package/src/i18n/aesthetic.ru.ts +155 -0
  161. package/src/i18n/aesthetic.zh-CN.ts +172 -0
  162. package/src/i18n/animals.ar.ts +145 -0
  163. package/src/i18n/animals.de.ts +144 -0
  164. package/src/i18n/animals.es.ts +158 -0
  165. package/src/i18n/animals.fr.ts +144 -0
  166. package/src/i18n/animals.he.ts +155 -0
  167. package/src/i18n/animals.hi.ts +157 -0
  168. package/src/i18n/animals.ja.ts +159 -0
  169. package/src/i18n/animals.ko.ts +159 -0
  170. package/src/i18n/animals.pt-BR.ts +157 -0
  171. package/src/i18n/animals.ru.ts +155 -0
  172. package/src/i18n/animals.zh-CN.ts +155 -0
  173. package/src/i18n/atmosphere.ar.ts +49 -0
  174. package/src/i18n/atmosphere.de.ts +47 -0
  175. package/src/i18n/atmosphere.es.ts +50 -0
  176. package/src/i18n/atmosphere.fr.ts +47 -0
  177. package/src/i18n/atmosphere.he.ts +47 -0
  178. package/src/i18n/atmosphere.hi.ts +49 -0
  179. package/src/i18n/atmosphere.ja.ts +49 -0
  180. package/src/i18n/atmosphere.ko.ts +49 -0
  181. package/src/i18n/atmosphere.pt-BR.ts +49 -0
  182. package/src/i18n/atmosphere.ru.ts +47 -0
  183. package/src/i18n/atmosphere.zh-CN.ts +152 -0
  184. package/src/i18n/backdrop.ar.ts +53 -0
  185. package/src/i18n/backdrop.de.ts +46 -0
  186. package/src/i18n/backdrop.es.ts +54 -0
  187. package/src/i18n/backdrop.fr.ts +52 -0
  188. package/src/i18n/backdrop.he.ts +51 -0
  189. package/src/i18n/backdrop.hi.ts +59 -0
  190. package/src/i18n/backdrop.ja.ts +61 -0
  191. package/src/i18n/backdrop.ko.ts +59 -0
  192. package/src/i18n/backdrop.pt-BR.ts +59 -0
  193. package/src/i18n/backdrop.ru.ts +57 -0
  194. package/src/i18n/backdrop.zh-CN.ts +57 -0
  195. package/src/i18n/camera-format.ar.ts +40 -0
  196. package/src/i18n/camera-format.de.ts +37 -0
  197. package/src/i18n/camera-format.es.ts +41 -0
  198. package/src/i18n/camera-format.fr.ts +37 -0
  199. package/src/i18n/camera-format.he.ts +37 -0
  200. package/src/i18n/camera-format.hi.ts +42 -0
  201. package/src/i18n/camera-format.ja.ts +37 -0
  202. package/src/i18n/camera-format.ko.ts +37 -0
  203. package/src/i18n/camera-format.pt-BR.ts +46 -0
  204. package/src/i18n/camera-format.ru.ts +42 -0
  205. package/src/i18n/camera-format.zh-CN.ts +42 -0
  206. package/src/i18n/camera-motions.ar.ts +86 -0
  207. package/src/i18n/camera-motions.de.ts +74 -0
  208. package/src/i18n/camera-motions.es.ts +86 -0
  209. package/src/i18n/camera-motions.fr.ts +86 -0
  210. package/src/i18n/camera-motions.he.ts +86 -0
  211. package/src/i18n/camera-motions.hi.ts +99 -0
  212. package/src/i18n/camera-motions.ja.ts +99 -0
  213. package/src/i18n/camera-motions.ko.ts +99 -0
  214. package/src/i18n/camera-motions.pt-BR.ts +99 -0
  215. package/src/i18n/camera-motions.ru.ts +99 -0
  216. package/src/i18n/camera-motions.zh-CN.ts +99 -0
  217. package/src/i18n/character-fx.ar.ts +72 -0
  218. package/src/i18n/character-fx.de.ts +72 -0
  219. package/src/i18n/character-fx.es.ts +72 -0
  220. package/src/i18n/character-fx.fr.ts +72 -0
  221. package/src/i18n/character-fx.he.ts +72 -0
  222. package/src/i18n/character-fx.hi.ts +72 -0
  223. package/src/i18n/character-fx.ja.ts +72 -0
  224. package/src/i18n/character-fx.ko.ts +72 -0
  225. package/src/i18n/character-fx.pt-BR.ts +72 -0
  226. package/src/i18n/character-fx.ru.ts +72 -0
  227. package/src/i18n/character-fx.zh-CN.ts +72 -0
  228. package/src/i18n/color-look.ar.ts +50 -0
  229. package/src/i18n/color-look.de.ts +47 -0
  230. package/src/i18n/color-look.es.ts +50 -0
  231. package/src/i18n/color-look.fr.ts +50 -0
  232. package/src/i18n/color-look.he.ts +49 -0
  233. package/src/i18n/color-look.hi.ts +52 -0
  234. package/src/i18n/color-look.ja.ts +54 -0
  235. package/src/i18n/color-look.ko.ts +54 -0
  236. package/src/i18n/color-look.pt-BR.ts +54 -0
  237. package/src/i18n/color-look.ru.ts +52 -0
  238. package/src/i18n/color-look.zh-CN.ts +53 -0
  239. package/src/i18n/composition-effects.ar.ts +26 -0
  240. package/src/i18n/composition-effects.de.ts +25 -0
  241. package/src/i18n/composition-effects.es.ts +27 -0
  242. package/src/i18n/composition-effects.fr.ts +25 -0
  243. package/src/i18n/composition-effects.he.ts +25 -0
  244. package/src/i18n/composition-effects.hi.ts +27 -0
  245. package/src/i18n/composition-effects.ja.ts +27 -0
  246. package/src/i18n/composition-effects.ko.ts +27 -0
  247. package/src/i18n/composition-effects.pt-BR.ts +25 -0
  248. package/src/i18n/composition-effects.ru.ts +82 -0
  249. package/src/i18n/composition-effects.zh-CN.ts +25 -0
  250. package/src/i18n/era.ar.ts +37 -0
  251. package/src/i18n/era.de.ts +34 -0
  252. package/src/i18n/era.es.ts +37 -0
  253. package/src/i18n/era.fr.ts +37 -0
  254. package/src/i18n/era.he.ts +36 -0
  255. package/src/i18n/era.hi.ts +39 -0
  256. package/src/i18n/era.ja.ts +41 -0
  257. package/src/i18n/era.ko.ts +39 -0
  258. package/src/i18n/era.pt-BR.ts +41 -0
  259. package/src/i18n/era.ru.ts +121 -0
  260. package/src/i18n/era.zh-CN.ts +39 -0
  261. package/src/i18n/exposure-settings.ar.ts +28 -0
  262. package/src/i18n/exposure-settings.de.ts +26 -0
  263. package/src/i18n/exposure-settings.es.ts +28 -0
  264. package/src/i18n/exposure-settings.fr.ts +29 -0
  265. package/src/i18n/exposure-settings.he.ts +28 -0
  266. package/src/i18n/exposure-settings.hi.ts +31 -0
  267. package/src/i18n/exposure-settings.ja.ts +31 -0
  268. package/src/i18n/exposure-settings.ko.ts +31 -0
  269. package/src/i18n/exposure-settings.pt-BR.ts +31 -0
  270. package/src/i18n/exposure-settings.ru.ts +31 -0
  271. package/src/i18n/exposure-settings.zh-CN.ts +31 -0
  272. package/src/i18n/framing.ar.ts +78 -0
  273. package/src/i18n/framing.de.ts +73 -0
  274. package/src/i18n/framing.es.ts +78 -0
  275. package/src/i18n/framing.fr.ts +78 -0
  276. package/src/i18n/framing.he.ts +77 -0
  277. package/src/i18n/framing.hi.ts +82 -0
  278. package/src/i18n/framing.ja.ts +84 -0
  279. package/src/i18n/framing.ko.ts +82 -0
  280. package/src/i18n/framing.pt-BR.ts +84 -0
  281. package/src/i18n/framing.ru.ts +82 -0
  282. package/src/i18n/framing.zh-CN.ts +82 -0
  283. package/src/i18n/furniture.ar.ts +92 -0
  284. package/src/i18n/furniture.de.ts +93 -0
  285. package/src/i18n/furniture.es.ts +101 -0
  286. package/src/i18n/furniture.fr.ts +93 -0
  287. package/src/i18n/furniture.he.ts +101 -0
  288. package/src/i18n/furniture.hi.ts +101 -0
  289. package/src/i18n/furniture.ja.ts +101 -0
  290. package/src/i18n/furniture.ko.ts +101 -0
  291. package/src/i18n/furniture.pt-BR.ts +101 -0
  292. package/src/i18n/furniture.ru.ts +101 -0
  293. package/src/i18n/furniture.zh-CN.ts +101 -0
  294. package/src/i18n/held-prop.ar.ts +74 -0
  295. package/src/i18n/held-prop.de.ts +65 -0
  296. package/src/i18n/held-prop.es.ts +83 -0
  297. package/src/i18n/held-prop.fr.ts +74 -0
  298. package/src/i18n/held-prop.he.ts +73 -0
  299. package/src/i18n/held-prop.hi.ts +82 -0
  300. package/src/i18n/held-prop.ja.ts +84 -0
  301. package/src/i18n/held-prop.ko.ts +84 -0
  302. package/src/i18n/held-prop.pt-BR.ts +84 -0
  303. package/src/i18n/held-prop.ru.ts +82 -0
  304. package/src/i18n/held-prop.zh-CN.ts +82 -0
  305. package/src/i18n/index.ts +208 -0
  306. package/src/i18n/instrumentation.ar.ts +195 -0
  307. package/src/i18n/instrumentation.de.ts +195 -0
  308. package/src/i18n/instrumentation.es.ts +195 -0
  309. package/src/i18n/instrumentation.fr.ts +195 -0
  310. package/src/i18n/instrumentation.he.ts +195 -0
  311. package/src/i18n/instrumentation.hi.ts +195 -0
  312. package/src/i18n/instrumentation.ja.ts +195 -0
  313. package/src/i18n/instrumentation.ko.ts +195 -0
  314. package/src/i18n/instrumentation.pt-BR.ts +195 -0
  315. package/src/i18n/instrumentation.ru.ts +195 -0
  316. package/src/i18n/instrumentation.zh-CN.ts +195 -0
  317. package/src/i18n/lens.ar.ts +24 -0
  318. package/src/i18n/lens.de.ts +24 -0
  319. package/src/i18n/lens.es.ts +24 -0
  320. package/src/i18n/lens.fr.ts +24 -0
  321. package/src/i18n/lens.he.ts +24 -0
  322. package/src/i18n/lens.hi.ts +24 -0
  323. package/src/i18n/lens.ja.ts +24 -0
  324. package/src/i18n/lens.ko.ts +24 -0
  325. package/src/i18n/lens.pt-BR.ts +24 -0
  326. package/src/i18n/lens.ru.ts +25 -0
  327. package/src/i18n/lens.zh-CN.ts +24 -0
  328. package/src/i18n/lighting.ar.ts +86 -0
  329. package/src/i18n/lighting.de.ts +84 -0
  330. package/src/i18n/lighting.es.ts +89 -0
  331. package/src/i18n/lighting.fr.ts +84 -0
  332. package/src/i18n/lighting.he.ts +88 -0
  333. package/src/i18n/lighting.hi.ts +90 -0
  334. package/src/i18n/lighting.ja.ts +90 -0
  335. package/src/i18n/lighting.ko.ts +90 -0
  336. package/src/i18n/lighting.pt-BR.ts +90 -0
  337. package/src/i18n/lighting.ru.ts +88 -0
  338. package/src/i18n/lighting.zh-CN.ts +92 -0
  339. package/src/i18n/loop-subject.ar.ts +39 -0
  340. package/src/i18n/loop-subject.de.ts +39 -0
  341. package/src/i18n/loop-subject.es.ts +39 -0
  342. package/src/i18n/loop-subject.fr.ts +39 -0
  343. package/src/i18n/loop-subject.he.ts +39 -0
  344. package/src/i18n/loop-subject.hi.ts +39 -0
  345. package/src/i18n/loop-subject.ja.ts +39 -0
  346. package/src/i18n/loop-subject.ko.ts +39 -0
  347. package/src/i18n/loop-subject.pt-BR.ts +39 -0
  348. package/src/i18n/loop-subject.ru.ts +39 -0
  349. package/src/i18n/loop-subject.zh-CN.ts +39 -0
  350. package/src/i18n/materials.ar.ts +80 -0
  351. package/src/i18n/materials.de.ts +80 -0
  352. package/src/i18n/materials.es.ts +87 -0
  353. package/src/i18n/materials.fr.ts +80 -0
  354. package/src/i18n/materials.he.ts +86 -0
  355. package/src/i18n/materials.hi.ts +86 -0
  356. package/src/i18n/materials.ja.ts +88 -0
  357. package/src/i18n/materials.ko.ts +88 -0
  358. package/src/i18n/materials.pt-BR.ts +88 -0
  359. package/src/i18n/materials.ru.ts +86 -0
  360. package/src/i18n/materials.zh-CN.ts +86 -0
  361. package/src/i18n/mood.ar.ts +61 -0
  362. package/src/i18n/mood.de.ts +61 -0
  363. package/src/i18n/mood.es.ts +66 -0
  364. package/src/i18n/mood.fr.ts +61 -0
  365. package/src/i18n/mood.he.ts +64 -0
  366. package/src/i18n/mood.hi.ts +66 -0
  367. package/src/i18n/mood.ja.ts +66 -0
  368. package/src/i18n/mood.ko.ts +66 -0
  369. package/src/i18n/mood.pt-BR.ts +64 -0
  370. package/src/i18n/mood.ru.ts +64 -0
  371. package/src/i18n/mood.zh-CN.ts +64 -0
  372. package/src/i18n/music-genre.ar.ts +302 -0
  373. package/src/i18n/music-genre.de.ts +302 -0
  374. package/src/i18n/music-genre.es.ts +302 -0
  375. package/src/i18n/music-genre.fr.ts +302 -0
  376. package/src/i18n/music-genre.he.ts +302 -0
  377. package/src/i18n/music-genre.hi.ts +302 -0
  378. package/src/i18n/music-genre.ja.ts +302 -0
  379. package/src/i18n/music-genre.ko.ts +302 -0
  380. package/src/i18n/music-genre.pt-BR.ts +302 -0
  381. package/src/i18n/music-genre.ru.ts +302 -0
  382. package/src/i18n/music-genre.zh-CN.ts +302 -0
  383. package/src/i18n/music-mood.ar.ts +75 -0
  384. package/src/i18n/music-mood.de.ts +75 -0
  385. package/src/i18n/music-mood.es.ts +75 -0
  386. package/src/i18n/music-mood.fr.ts +75 -0
  387. package/src/i18n/music-mood.he.ts +75 -0
  388. package/src/i18n/music-mood.hi.ts +75 -0
  389. package/src/i18n/music-mood.ja.ts +75 -0
  390. package/src/i18n/music-mood.ko.ts +75 -0
  391. package/src/i18n/music-mood.pt-BR.ts +75 -0
  392. package/src/i18n/music-mood.ru.ts +75 -0
  393. package/src/i18n/music-mood.zh-CN.ts +75 -0
  394. package/src/i18n/person.ar.ts +638 -0
  395. package/src/i18n/person.de.ts +629 -0
  396. package/src/i18n/person.es.ts +645 -0
  397. package/src/i18n/person.fr.ts +621 -0
  398. package/src/i18n/person.he.ts +656 -0
  399. package/src/i18n/person.hi.ts +654 -0
  400. package/src/i18n/person.ja.ts +662 -0
  401. package/src/i18n/person.ko.ts +662 -0
  402. package/src/i18n/person.pt-BR.ts +656 -0
  403. package/src/i18n/person.ru.ts +656 -0
  404. package/src/i18n/person.zh-CN.ts +656 -0
  405. package/src/i18n/photo-genre.ar.ts +58 -0
  406. package/src/i18n/photo-genre.de.ts +52 -0
  407. package/src/i18n/photo-genre.es.ts +64 -0
  408. package/src/i18n/photo-genre.fr.ts +58 -0
  409. package/src/i18n/photo-genre.he.ts +62 -0
  410. package/src/i18n/photo-genre.hi.ts +63 -0
  411. package/src/i18n/photo-genre.ja.ts +65 -0
  412. package/src/i18n/photo-genre.ko.ts +63 -0
  413. package/src/i18n/photo-genre.pt-BR.ts +65 -0
  414. package/src/i18n/photo-genre.ru.ts +63 -0
  415. package/src/i18n/photo-genre.zh-CN.ts +63 -0
  416. package/src/i18n/photographer.ar.ts +75 -0
  417. package/src/i18n/photographer.de.ts +74 -0
  418. package/src/i18n/photographer.es.ts +77 -0
  419. package/src/i18n/photographer.fr.ts +77 -0
  420. package/src/i18n/photographer.he.ts +78 -0
  421. package/src/i18n/photographer.hi.ts +80 -0
  422. package/src/i18n/photographer.ja.ts +84 -0
  423. package/src/i18n/photographer.ko.ts +84 -0
  424. package/src/i18n/photographer.pt-BR.ts +83 -0
  425. package/src/i18n/photographer.ru.ts +78 -0
  426. package/src/i18n/photographer.zh-CN.ts +78 -0
  427. package/src/i18n/pose.ar.ts +95 -0
  428. package/src/i18n/pose.de.ts +96 -0
  429. package/src/i18n/pose.es.ts +104 -0
  430. package/src/i18n/pose.fr.ts +96 -0
  431. package/src/i18n/pose.he.ts +104 -0
  432. package/src/i18n/pose.hi.ts +104 -0
  433. package/src/i18n/pose.ja.ts +104 -0
  434. package/src/i18n/pose.ko.ts +104 -0
  435. package/src/i18n/pose.pt-BR.ts +104 -0
  436. package/src/i18n/pose.ru.ts +104 -0
  437. package/src/i18n/pose.zh-CN.ts +104 -0
  438. package/src/i18n/post-process-effects.ar.ts +25 -0
  439. package/src/i18n/post-process-effects.de.ts +24 -0
  440. package/src/i18n/post-process-effects.es.ts +26 -0
  441. package/src/i18n/post-process-effects.fr.ts +24 -0
  442. package/src/i18n/post-process-effects.he.ts +24 -0
  443. package/src/i18n/post-process-effects.hi.ts +26 -0
  444. package/src/i18n/post-process-effects.ja.ts +26 -0
  445. package/src/i18n/post-process-effects.ko.ts +26 -0
  446. package/src/i18n/post-process-effects.pt-BR.ts +24 -0
  447. package/src/i18n/post-process-effects.ru.ts +24 -0
  448. package/src/i18n/post-process-effects.zh-CN.ts +24 -0
  449. package/src/i18n/render-quality.ar.ts +34 -0
  450. package/src/i18n/render-quality.de.ts +30 -0
  451. package/src/i18n/render-quality.es.ts +35 -0
  452. package/src/i18n/render-quality.fr.ts +34 -0
  453. package/src/i18n/render-quality.he.ts +33 -0
  454. package/src/i18n/render-quality.hi.ts +39 -0
  455. package/src/i18n/render-quality.ja.ts +39 -0
  456. package/src/i18n/render-quality.ko.ts +39 -0
  457. package/src/i18n/render-quality.pt-BR.ts +37 -0
  458. package/src/i18n/render-quality.ru.ts +37 -0
  459. package/src/i18n/render-quality.zh-CN.ts +37 -0
  460. package/src/i18n/seasons.ar.ts +10 -0
  461. package/src/i18n/seasons.de.ts +10 -0
  462. package/src/i18n/seasons.es.ts +10 -0
  463. package/src/i18n/seasons.fr.ts +10 -0
  464. package/src/i18n/seasons.he.ts +10 -0
  465. package/src/i18n/seasons.hi.ts +10 -0
  466. package/src/i18n/seasons.ja.ts +10 -0
  467. package/src/i18n/seasons.ko.ts +10 -0
  468. package/src/i18n/seasons.pt-BR.ts +10 -0
  469. package/src/i18n/seasons.ru.ts +10 -0
  470. package/src/i18n/seasons.zh-CN.ts +10 -0
  471. package/src/i18n/setting.ar.ts +73 -0
  472. package/src/i18n/setting.de.ts +73 -0
  473. package/src/i18n/setting.es.ts +77 -0
  474. package/src/i18n/setting.fr.ts +73 -0
  475. package/src/i18n/setting.he.ts +76 -0
  476. package/src/i18n/setting.hi.ts +76 -0
  477. package/src/i18n/setting.ja.ts +78 -0
  478. package/src/i18n/setting.ko.ts +76 -0
  479. package/src/i18n/setting.pt-BR.ts +78 -0
  480. package/src/i18n/setting.ru.ts +76 -0
  481. package/src/i18n/setting.zh-CN.ts +76 -0
  482. package/src/i18n/style.ar.ts +55 -0
  483. package/src/i18n/style.de.ts +54 -0
  484. package/src/i18n/style.es.ts +54 -0
  485. package/src/i18n/style.fr.ts +54 -0
  486. package/src/i18n/style.he.ts +54 -0
  487. package/src/i18n/style.hi.ts +54 -0
  488. package/src/i18n/style.ja.ts +54 -0
  489. package/src/i18n/style.ko.ts +54 -0
  490. package/src/i18n/style.pt-BR.ts +56 -0
  491. package/src/i18n/style.ru.ts +54 -0
  492. package/src/i18n/style.zh-CN.ts +54 -0
  493. package/src/i18n/styling.ar.ts +295 -0
  494. package/src/i18n/styling.de.ts +285 -0
  495. package/src/i18n/styling.es.ts +317 -0
  496. package/src/i18n/styling.fr.ts +285 -0
  497. package/src/i18n/styling.he.ts +307 -0
  498. package/src/i18n/styling.hi.ts +323 -0
  499. package/src/i18n/styling.ja.ts +317 -0
  500. package/src/i18n/styling.ko.ts +311 -0
  501. package/src/i18n/styling.pt-BR.ts +301 -0
  502. package/src/i18n/styling.ru.ts +301 -0
  503. package/src/i18n/styling.zh-CN.ts +307 -0
  504. package/src/i18n/temporal.ar.ts +31 -0
  505. package/src/i18n/temporal.de.ts +24 -0
  506. package/src/i18n/temporal.es.ts +27 -0
  507. package/src/i18n/temporal.fr.ts +28 -0
  508. package/src/i18n/temporal.he.ts +31 -0
  509. package/src/i18n/temporal.hi.ts +31 -0
  510. package/src/i18n/temporal.ja.ts +31 -0
  511. package/src/i18n/temporal.ko.ts +31 -0
  512. package/src/i18n/temporal.pt-BR.ts +31 -0
  513. package/src/i18n/temporal.ru.ts +31 -0
  514. package/src/i18n/temporal.zh-CN.ts +31 -0
  515. package/src/i18n/transitions.ar.ts +97 -0
  516. package/src/i18n/transitions.de.ts +97 -0
  517. package/src/i18n/transitions.es.ts +97 -0
  518. package/src/i18n/transitions.fr.ts +97 -0
  519. package/src/i18n/transitions.he.ts +97 -0
  520. package/src/i18n/transitions.hi.ts +97 -0
  521. package/src/i18n/transitions.ja.ts +97 -0
  522. package/src/i18n/transitions.ko.ts +97 -0
  523. package/src/i18n/transitions.pt-BR.ts +97 -0
  524. package/src/i18n/transitions.ru.ts +97 -0
  525. package/src/i18n/transitions.zh-CN.ts +97 -0
  526. package/src/i18n/types.ts +158 -0
  527. package/src/i18n/vehicles.ar.ts +137 -0
  528. package/src/i18n/vehicles.de.ts +125 -0
  529. package/src/i18n/vehicles.es.ts +138 -0
  530. package/src/i18n/vehicles.fr.ts +125 -0
  531. package/src/i18n/vehicles.he.ts +136 -0
  532. package/src/i18n/vehicles.hi.ts +138 -0
  533. package/src/i18n/vehicles.ja.ts +138 -0
  534. package/src/i18n/vehicles.ko.ts +138 -0
  535. package/src/i18n/vehicles.pt-BR.ts +136 -0
  536. package/src/i18n/vehicles.ru.ts +136 -0
  537. package/src/i18n/vehicles.zh-CN.ts +136 -0
  538. package/src/i18n/voice-character.ar.ts +126 -0
  539. package/src/i18n/voice-character.de.ts +126 -0
  540. package/src/i18n/voice-character.es.ts +126 -0
  541. package/src/i18n/voice-character.fr.ts +126 -0
  542. package/src/i18n/voice-character.he.ts +126 -0
  543. package/src/i18n/voice-character.hi.ts +126 -0
  544. package/src/i18n/voice-character.ja.ts +126 -0
  545. package/src/i18n/voice-character.ko.ts +126 -0
  546. package/src/i18n/voice-character.pt-BR.ts +126 -0
  547. package/src/i18n/voice-character.ru.ts +126 -0
  548. package/src/i18n/voice-character.zh-CN.ts +126 -0
  549. package/src/i18n/voice-delivery.ar.ts +77 -0
  550. package/src/i18n/voice-delivery.de.ts +77 -0
  551. package/src/i18n/voice-delivery.es.ts +77 -0
  552. package/src/i18n/voice-delivery.fr.ts +77 -0
  553. package/src/i18n/voice-delivery.he.ts +77 -0
  554. package/src/i18n/voice-delivery.hi.ts +77 -0
  555. package/src/i18n/voice-delivery.ja.ts +77 -0
  556. package/src/i18n/voice-delivery.ko.ts +77 -0
  557. package/src/i18n/voice-delivery.pt-BR.ts +77 -0
  558. package/src/i18n/voice-delivery.ru.ts +77 -0
  559. package/src/i18n/voice-delivery.zh-CN.ts +77 -0
  560. package/src/i18n/weapons.ar.ts +115 -0
  561. package/src/i18n/weapons.de.ts +114 -0
  562. package/src/i18n/weapons.es.ts +116 -0
  563. package/src/i18n/weapons.fr.ts +103 -0
  564. package/src/i18n/weapons.he.ts +114 -0
  565. package/src/i18n/weapons.hi.ts +116 -0
  566. package/src/i18n/weapons.ja.ts +116 -0
  567. package/src/i18n/weapons.ko.ts +116 -0
  568. package/src/i18n/weapons.pt-BR.ts +114 -0
  569. package/src/i18n/weapons.ru.ts +114 -0
  570. package/src/i18n/weapons.zh-CN.ts +114 -0
  571. package/src/image-critic-node.ts +61 -0
  572. package/src/image-critic-types.ts +40 -0
  573. package/src/index.ts +866 -0
  574. package/src/inject-upstream.ts +15 -0
  575. package/src/inpaint-region.ts +51 -0
  576. package/src/json-evaluator.ts +732 -0
  577. package/src/json-path.ts +52 -0
  578. package/src/lip-sync-pricing.ts +90 -0
  579. package/src/list-merge.ts +40 -0
  580. package/src/list-sort.ts +122 -0
  581. package/src/list-source-resolver.ts +50 -0
  582. package/src/llm-models.ts +245 -0
  583. package/src/location-mention-slug.ts +291 -0
  584. package/src/location-preset-catalog-map.ts +128 -0
  585. package/src/loop-delimiter.ts +73 -0
  586. package/src/lora-routing.ts +95 -0
  587. package/src/lottie-overlay-catalog.ts +175 -0
  588. package/src/lottie-slots.ts +309 -0
  589. package/src/model-catalog.ts +2386 -0
  590. package/src/model-constants.ts +1910 -0
  591. package/src/model-tree.ts +120 -0
  592. package/src/monetization.ts +15 -0
  593. package/src/multi-pick.ts +42 -0
  594. package/src/node-default-mappings.ts +262 -0
  595. package/src/node-mappable-fields.ts +70 -0
  596. package/src/node-preset-export.ts +56 -0
  597. package/src/node-preset-extract.ts +147 -0
  598. package/src/node-refs.ts +253 -0
  599. package/src/node-runtime-keys.ts +117 -0
  600. package/src/object-aspect-defaults.ts +72 -0
  601. package/src/object-picker-types.ts +22 -0
  602. package/src/parameter-node-value.ts +284 -0
  603. package/src/pinterest-url.ts +28 -0
  604. package/src/pipeline-chat.ts +72 -0
  605. package/src/pipeline-defaults.ts +318 -0
  606. package/src/pipeline-events.ts +162 -0
  607. package/src/pipeline-state-types.ts +196 -0
  608. package/src/pipeline-types.ts +647 -0
  609. package/src/pipeline-validation.ts +87 -0
  610. package/src/presentation-types.ts +23 -0
  611. package/src/presentation-utils.ts +397 -0
  612. package/src/producer-types.ts +188 -0
  613. package/src/progress-curve.ts +99 -0
  614. package/src/provider-directive-defaults.ts +69 -0
  615. package/src/reduce-strategy-registry.ts +121 -0
  616. package/src/reference-board-templates.ts +81 -0
  617. package/src/reference-roles.ts +153 -0
  618. package/src/reference-sheet/__tests__/board-to-column.test.ts +20 -0
  619. package/src/reference-sheet/__tests__/catalog.test.ts +50 -0
  620. package/src/reference-sheet/__tests__/cost.test.ts +38 -0
  621. package/src/reference-sheet/__tests__/no-drift.test.ts +28 -0
  622. package/src/reference-sheet/__tests__/panel-plan.test.ts +61 -0
  623. package/src/reference-sheet/__tests__/panel-prompts.test.ts +17 -0
  624. package/src/reference-sheet/__tests__/plan-generation.test.ts +72 -0
  625. package/src/reference-sheet/__tests__/presets.test.ts +54 -0
  626. package/src/reference-sheet/__tests__/types.test.ts +24 -0
  627. package/src/reference-sheet/catalog.ts +144 -0
  628. package/src/reference-sheet/cost.ts +37 -0
  629. package/src/reference-sheet/credit-id.ts +7 -0
  630. package/src/reference-sheet/index.ts +8 -0
  631. package/src/reference-sheet/panel-plan.ts +65 -0
  632. package/src/reference-sheet/panel-prompts.ts +14 -0
  633. package/src/reference-sheet/plan-generation.ts +79 -0
  634. package/src/reference-sheet/presets.ts +77 -0
  635. package/src/reference-sheet/types.ts +92 -0
  636. package/src/repeat-types.ts +97 -0
  637. package/src/resolve-field-mappings.ts +119 -0
  638. package/src/route-filter.ts +80 -0
  639. package/src/scene-helper-types.ts +249 -0
  640. package/src/scene-node-types.ts +281 -0
  641. package/src/scraper-actors.ts +57 -0
  642. package/src/scraper-output-schemas.ts +25 -0
  643. package/src/seasons.ts +45 -0
  644. package/src/selector.ts +864 -0
  645. package/src/settled-with-limit.ts +42 -0
  646. package/src/shot-element.ts +43 -0
  647. package/src/social-media-specs.ts +121 -0
  648. package/src/social-post.ts +24 -0
  649. package/src/sub-workflow-validation.ts +78 -0
  650. package/src/supported-fonts.ts +40 -0
  651. package/src/surround.ts +113 -0
  652. package/src/switchx-pricing.ts +33 -0
  653. package/src/text-separators.ts +59 -0
  654. package/src/to-connected-references.ts +77 -0
  655. package/src/types.ts +272 -0
  656. package/src/variant-job-id.ts +17 -0
  657. package/src/vehicles.ts +213 -0
  658. package/src/video-analysis-pricing.ts +74 -0
  659. package/src/video-analysis.ts +127 -0
  660. package/src/video-duration.ts +25 -0
  661. package/src/voice-changer-models.ts +36 -0
  662. package/src/voice-matcher-types.ts +37 -0
  663. package/src/voices.ts +92 -0
  664. package/src/weapons.ts +190 -0
  665. package/src/workflow-export.ts +153 -0
@@ -0,0 +1,127 @@
1
+ import { INPUT_FIELD_MAP as PRESENTATION_INPUT_MAP } from "./presentation-utils.js"
2
+
3
+ export interface ComponentHandle {
4
+ id: string
5
+ name: string
6
+ type: "image" | "video" | "audio" | "text"
7
+ required: boolean
8
+ mediaPreview?: boolean
9
+ fieldKey: string
10
+ }
11
+
12
+ export interface ExposedSetting {
13
+ nodeId: string
14
+ field: string
15
+ label: string
16
+ type: "select" | "text" | "number" | "toggle" | "aspect-ratio"
17
+ allowedValues?: unknown[]
18
+ /** Full option list with labels for select fields (preferred over allowedValues). */
19
+ options?: Array<{ value: string; label: string }>
20
+ defaultValue: unknown
21
+ }
22
+
23
+ export interface ComponentMetadata {
24
+ inputs: ComponentHandle[]
25
+ outputs: ComponentHandle[]
26
+ exposedSettings: ExposedSetting[]
27
+ }
28
+
29
+ /** Maps input node types to their data field key for inputOverrides (derived from presentation-utils) */
30
+ export const INPUT_FIELD_MAP: Record<string, string> = Object.fromEntries(
31
+ Object.entries(PRESENTATION_INPUT_MAP).map(([k, v]) => [k, v.key]),
32
+ )
33
+
34
+ /** Separator used in component handle ids that target a specific port on a sub-workflow-input / sub-workflow-output node. */
35
+ export const HANDLE_PORT_SEPARATOR = "::"
36
+
37
+ /**
38
+ * Split a component handle id of the form `nodeId` or `nodeId::portId`.
39
+ * Returns the literal nodeId when no separator is present.
40
+ */
41
+ export function parseHandleId(handleId: string): { nodeId: string; portId?: string } {
42
+ const sep = handleId.indexOf(HANDLE_PORT_SEPARATOR)
43
+ if (sep < 0) return { nodeId: handleId }
44
+ const portId = handleId.slice(sep + HANDLE_PORT_SEPARATOR.length)
45
+ return { nodeId: handleId.slice(0, sep), portId: portId || undefined }
46
+ }
47
+
48
+ /**
49
+ * Apply a value for a component input handle to the inputOverrides map.
50
+ *
51
+ * - Plain handles (id = nodeId) write to `inputOverrides[nodeId][fieldKey]`,
52
+ * which the orchestrator shallow-merges into `node.data` at execution.
53
+ * - Compound handles (id = nodeId::portId) write into
54
+ * `inputOverrides[nodeId].__injectedPortValues[portId]`, the slot that
55
+ * sub-workflow-input nodes read at runtime via output-extractor.
56
+ *
57
+ * Mutates the map in place so multi-port accumulation merges correctly when
58
+ * iterating handles in a loop.
59
+ */
60
+ export function applyHandleInputOverride(
61
+ inputOverrides: Record<string, Record<string, unknown>>,
62
+ handle: { id: string; fieldKey: string },
63
+ value: unknown,
64
+ ): void {
65
+ const { nodeId, portId } = parseHandleId(handle.id)
66
+ if (portId) {
67
+ const existing = (inputOverrides[nodeId]?.__injectedPortValues as Record<string, unknown> | undefined) ?? {}
68
+ inputOverrides[nodeId] = {
69
+ ...inputOverrides[nodeId],
70
+ __injectedPortValues: { ...existing, [portId]: value },
71
+ }
72
+ } else {
73
+ inputOverrides[nodeId] = { ...inputOverrides[nodeId], [handle.fieldKey]: value }
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Has a value already been recorded for this handle in the inputOverrides map?
79
+ * Used to skip fallback wiring when an upstream connection already supplied
80
+ * the input.
81
+ */
82
+ export function isHandleInputWired(
83
+ inputOverrides: Record<string, Record<string, unknown>>,
84
+ handle: { id: string; fieldKey: string },
85
+ ): boolean {
86
+ const { nodeId, portId } = parseHandleId(handle.id)
87
+ if (portId) {
88
+ const injected = inputOverrides[nodeId]?.__injectedPortValues as Record<string, unknown> | undefined
89
+ return injected?.[portId] !== undefined
90
+ }
91
+ return inputOverrides[nodeId]?.[handle.fieldKey] !== undefined
92
+ }
93
+
94
+ /** Maps output media types to NodeOutput field keys */
95
+ export const OUTPUT_FIELD_MAP: Record<string, string> = {
96
+ image: "imageUrl",
97
+ video: "videoUrl",
98
+ audio: "audioUrl",
99
+ text: "text",
100
+ }
101
+
102
+ /**
103
+ * Merge exposed settings into inputOverrides format.
104
+ * exposedSettings is keyed by "nodeId:field" -> value on the component node data.
105
+ *
106
+ * Connected inputs (already present in inputOverrides) always take priority
107
+ * over exposed settings for the same node+field combination. This ensures
108
+ * that mandatory upstream wires are never silently overwritten by stale
109
+ * default / user-configured exposed-setting values.
110
+ */
111
+ export function mergeExposedSettings(
112
+ inputOverrides: Record<string, Record<string, unknown>>,
113
+ exposedSettings: Record<string, unknown>,
114
+ metadata: ComponentMetadata,
115
+ ): Record<string, Record<string, unknown>> {
116
+ const merged = { ...inputOverrides }
117
+ for (const setting of metadata.exposedSettings) {
118
+ const key = `${setting.nodeId}:${setting.field}`
119
+ const value = exposedSettings[key]
120
+ if (value !== undefined) {
121
+ // Skip if a connected input already provided this field
122
+ if (merged[setting.nodeId]?.[setting.field] !== undefined) continue
123
+ merged[setting.nodeId] = { ...merged[setting.nodeId], [setting.field]: value }
124
+ }
125
+ }
126
+ return merged
127
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Build a label→output map for `{Node Label}` refs inside condition values
3
+ * on filter-list and router nodes.
4
+ *
5
+ * Only considers edges whose `targetHandle === VARIABLES_HANDLE_ID` — the
6
+ * dedicated condition-variables input handle. Data-flow edges (targetHandle
7
+ * `"in"`, column handles, etc.) are ignored, so variable sources never
8
+ * influence the filtered/routed list.
9
+ *
10
+ * Duplicate labels are suffixed `Same`, `Same (2)`, `Same (3)` — matches
11
+ * the convention in `buildNodeRefMap` (frontend) / `buildNodeRefsMap`
12
+ * (backend) for text-prompt refs.
13
+ */
14
+
15
+ export const VARIABLES_HANDLE_ID = "variables"
16
+
17
+ type EdgeShape = {
18
+ source: string
19
+ target: string
20
+ sourceHandle?: string | null
21
+ targetHandle?: string | null
22
+ }
23
+
24
+ type NodeShape = {
25
+ id: string
26
+ type?: string
27
+ data: Record<string, unknown>
28
+ }
29
+
30
+ export function buildConditionVariables<
31
+ E extends EdgeShape,
32
+ N extends NodeShape,
33
+ >(
34
+ targetNodeId: string,
35
+ edges: ReadonlyArray<E>,
36
+ nodes: ReadonlyArray<N>,
37
+ extractOutput: (node: N) => string | undefined,
38
+ ): Map<string, string> {
39
+ const pairs: Array<{ label: string; output: string }> = []
40
+ for (const edge of edges) {
41
+ if (edge.target !== targetNodeId) continue
42
+ if (edge.targetHandle !== VARIABLES_HANDLE_ID) continue
43
+ const src = nodes.find((n) => n.id === edge.source)
44
+ if (!src) continue
45
+ const output = extractOutput(src)
46
+ if (output === undefined || output === "") continue
47
+ const label = (src.data.label as string) || src.type || src.id
48
+ pairs.push({ label, output })
49
+ }
50
+
51
+ const map = new Map<string, string>()
52
+ const counts = new Map<string, number>()
53
+ for (const { label } of pairs) counts.set(label, (counts.get(label) ?? 0) + 1)
54
+ const seen = new Map<string, number>()
55
+ for (const { label, output } of pairs) {
56
+ let key = label
57
+ if ((counts.get(label) ?? 0) > 1) {
58
+ const n = (seen.get(label) ?? 0) + 1
59
+ seen.set(label, n)
60
+ if (n > 1) key = `${label} (${n})`
61
+ }
62
+ map.set(key, output)
63
+ }
64
+ return map
65
+ }
@@ -0,0 +1,15 @@
1
+ export {
2
+ VIDEO_UTIL_PRICING,
3
+ estimateLoopVideoCredits,
4
+ estimateTrimVideoCredits,
5
+ estimateCombineVideosCredits,
6
+ estimateLoopTrimAddonCredits,
7
+ assembleNarratedVideoCredits,
8
+ } from "./video-utils.js"
9
+
10
+ export type {
11
+ LoopVideoEstimatorInput,
12
+ TrimVideoEstimatorInput,
13
+ CombineVideosEstimatorInput,
14
+ LoopTrimEstimatorInput,
15
+ } from "./video-utils.js"
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Pure credit estimators for video-utility nodes (loop-video, trim-video,
3
+ * combine-videos). Used by frontend (Run-button display + API call) AND
4
+ * backend (creditGuard reservation) so the displayed cost equals what gets
5
+ * debited.
6
+ */
7
+
8
+ export const VIDEO_UTIL_PRICING = {
9
+ /** 1 credit per 5 seconds of output, ceiling. */
10
+ CREDITS_PER_5_SEC: 1,
11
+ /** Smart-loop-cut work scales at 1 credit per ~24 frames searched. */
12
+ FRAMES_PER_CREDIT: 24,
13
+ /** Used when an upstream node hasn't produced a measurable duration yet. */
14
+ FALLBACK_DURATION_SECONDS: 8,
15
+ } as const
16
+
17
+ export interface LoopVideoEstimatorInput {
18
+ mode?: "repeat" | "duration"
19
+ repeatCount?: number
20
+ targetDuration?: number
21
+ smartLoopCutBeforeRepeat?: boolean
22
+ smartLoopCutLookback?: number
23
+ }
24
+
25
+ export interface TrimVideoEstimatorInput {
26
+ trimMode?: "time" | "seconds" | "keep-first-seconds" | "keep-last-seconds" | "frames" | "smart-loop-cut"
27
+ startTime?: number
28
+ endTime?: number
29
+ /** Seconds-mirror of frames mode: trim N seconds from start AND/OR end. */
30
+ trimStartSeconds?: number
31
+ trimEndSeconds?: number
32
+ /** Keep only the first/last N seconds (probes duration at runtime). */
33
+ keepFirstSeconds?: number
34
+ keepLastSeconds?: number
35
+ trimStartFrames?: number
36
+ trimEndFrames?: number
37
+ smartLoopCutLookback?: number
38
+ }
39
+
40
+ export interface CombineVideosEstimatorInput {
41
+ /** Any id from `COMBINE_TRANSITIONS`. The estimator only branches on
42
+ * `cut` vs non-cut, so accepting the full string keeps the catalog as
43
+ * the single source of truth. */
44
+ transition?: string
45
+ transitionDuration?: number
46
+ trimStartFrames?: number
47
+ trimEndFrames?: number
48
+ }
49
+
50
+ export function estimateLoopVideoCredits(
51
+ data: LoopVideoEstimatorInput,
52
+ upstreamDuration?: number,
53
+ ): number {
54
+ const fallback = VIDEO_UTIL_PRICING.FALLBACK_DURATION_SECONDS
55
+ const inputDuration = upstreamDuration ?? fallback
56
+
57
+ const output =
58
+ data.mode === "duration"
59
+ ? (data.targetDuration ?? fallback)
60
+ : (data.repeatCount ?? 2) * inputDuration
61
+
62
+ const base = Math.ceil(output / 5)
63
+ const cut = data.smartLoopCutBeforeRepeat
64
+ ? Math.ceil((data.smartLoopCutLookback ?? 16) / VIDEO_UTIL_PRICING.FRAMES_PER_CREDIT)
65
+ : 0
66
+
67
+ return Math.max(1, base + cut)
68
+ }
69
+
70
+ export function estimateTrimVideoCredits(
71
+ data: TrimVideoEstimatorInput,
72
+ upstreamDuration?: number,
73
+ ): number {
74
+ const fallback = VIDEO_UTIL_PRICING.FALLBACK_DURATION_SECONDS
75
+ const inputDuration = upstreamDuration ?? fallback
76
+
77
+ if (data.trimMode === "smart-loop-cut") {
78
+ const base = Math.ceil(inputDuration / 5)
79
+ const cut = Math.ceil((data.smartLoopCutLookback ?? 16) / VIDEO_UTIL_PRICING.FRAMES_PER_CREDIT)
80
+ return Math.max(1, base + cut)
81
+ }
82
+
83
+ if (data.trimMode === "frames") {
84
+ // Source fps unknown frontend-side; assume 24.
85
+ const startSec = (data.trimStartFrames ?? 0) / VIDEO_UTIL_PRICING.FRAMES_PER_CREDIT
86
+ const endSec = (data.trimEndFrames ?? 0) / VIDEO_UTIL_PRICING.FRAMES_PER_CREDIT
87
+ const output = Math.max(0, inputDuration - startSec - endSec)
88
+ return Math.max(1, Math.ceil(output / 5))
89
+ }
90
+
91
+ if (data.trimMode === "seconds") {
92
+ const output = Math.max(0, inputDuration - (data.trimStartSeconds ?? 0) - (data.trimEndSeconds ?? 0))
93
+ return Math.max(1, Math.ceil(output / 5))
94
+ }
95
+
96
+ if (data.trimMode === "keep-first-seconds") {
97
+ const output = Math.min(inputDuration, Math.max(0, data.keepFirstSeconds ?? 0))
98
+ return Math.max(1, Math.ceil(output / 5))
99
+ }
100
+
101
+ if (data.trimMode === "keep-last-seconds") {
102
+ const output = Math.min(inputDuration, Math.max(0, data.keepLastSeconds ?? 0))
103
+ return Math.max(1, Math.ceil(output / 5))
104
+ }
105
+
106
+ // "time" mode (default)
107
+ const output = (data.endTime ?? 0) - (data.startTime ?? 0)
108
+ return Math.max(1, Math.ceil(output / 5))
109
+ }
110
+
111
+ export function estimateCombineVideosCredits(
112
+ data: CombineVideosEstimatorInput,
113
+ upstreamDurations: ReadonlyArray<number | undefined>,
114
+ ): number {
115
+ if (upstreamDurations.length === 0) return 1
116
+
117
+ const fallback = VIDEO_UTIL_PRICING.FALLBACK_DURATION_SECONDS
118
+ const n = upstreamDurations.length
119
+
120
+ let total = 0
121
+ for (const d of upstreamDurations) {
122
+ total += typeof d === "number" && Number.isFinite(d) && d >= 0 ? d : fallback
123
+ }
124
+
125
+ if (data.transition && data.transition !== "cut" && n > 1) {
126
+ total -= (data.transitionDuration ?? 0.5) * (n - 1)
127
+ }
128
+
129
+ const trimSecPerClip =
130
+ ((data.trimStartFrames ?? 0) + (data.trimEndFrames ?? 0)) /
131
+ VIDEO_UTIL_PRICING.FRAMES_PER_CREDIT
132
+ total -= trimSecPerClip * n
133
+
134
+ const base = Math.ceil(Math.max(0, total) / 5)
135
+ const inputAdder = Math.max(0, n - 2)
136
+ return Math.max(1, base + inputAdder)
137
+ }
138
+
139
+ export interface LoopTrimEstimatorInput {
140
+ enabled?: boolean
141
+ framesToTest?: number
142
+ }
143
+
144
+ /** Add-on credits charged for the smart-loop-cut post-process applied to an
145
+ * image-to-video output. Returns 0 when loopTrim is undefined or disabled.
146
+ * Formula: ceil(duration / 5) + ceil(framesToTest / 24) — matches the
147
+ * trim-video smart-loop-cut formula for consistency. */
148
+ export function estimateLoopTrimAddonCredits(
149
+ loopTrim: LoopTrimEstimatorInput | undefined,
150
+ outputDurationSeconds: number,
151
+ ): number {
152
+ if (!loopTrim?.enabled) return 0
153
+ const frames = Math.max(1, Math.min(loopTrim.framesToTest ?? 16, 64))
154
+ return Math.ceil(outputDurationSeconds / 5) +
155
+ Math.ceil(frames / VIDEO_UTIL_PRICING.FRAMES_PER_CREDIT)
156
+ }
157
+
158
+ /** BASE credits (at-cost) for assemble-narrated-video: 3 flat + 1 per 6
159
+ * blocks. 6→4, 24→7, 60→13. Single source of truth shared by the backend
160
+ * route/creditGuard (`backend/src/providers/video/narrated-block-fit.ts`
161
+ * re-exports this) and the frontend pre-run estimate
162
+ * (`frontend/src/hooks/use-estimated-credits.ts`). */
163
+ export function assembleNarratedVideoCredits(blockCount: number): number {
164
+ return 3 + Math.ceil(blockCount / 6)
165
+ }
@@ -0,0 +1,304 @@
1
+ /**
2
+ * Build composite credit model identifiers for variable pricing.
3
+ * Shared between frontend and backend.
4
+ */
5
+
6
+ import {
7
+ HIGH_QUALITY_PROVIDERS,
8
+ TWO_K_RESOLUTION_PROVIDERS,
9
+ RESOLUTION_2K_4K_TIERED_PROVIDERS,
10
+ IDEOGRAM_PROVIDERS,
11
+ DURATION_PRICED_PROVIDERS,
12
+ AUDIO_ADDON_PROVIDERS,
13
+ MODE_ADDON_PROVIDERS,
14
+ RESOLUTION_VIDEO_REF_PRICING,
15
+ RESOLUTION_DURATION_PRICING,
16
+ VEO_RESOLUTION_TIERED_PROVIDERS,
17
+ VIDEO_DURATION_TIERS,
18
+ MOTION_DURATION_TIERS,
19
+ T2I_TO_I2I_VARIANT,
20
+ isVeoProvider,
21
+ } from "./model-constants.js"
22
+ import { isFlux2Model } from "./flux2-pricing.js"
23
+ import { MODEL_CATALOG } from "./model-catalog.js"
24
+
25
+ /**
26
+ * Compute composite model identifier for variable credit pricing.
27
+ * Examples: "gpt-image:high", "flux:2K", "nano-banana-pro:4K", "ideogram:TURBO",
28
+ * "flux-2-pro:2MP", "flux-2-max:2MP:1ref" (Flux 2 — billed per megapixel).
29
+ *
30
+ * For image models, uses quality/resolution/renderingSpeed and (Flux 2) the output
31
+ * megapixel count plus (Flux 2 Max) the number of reference images attached at request time.
32
+ * For video models, uses duration/sound params when the model has variable pricing.
33
+ */
34
+ export function buildCreditModelIdentifier(
35
+ provider: string,
36
+ quality?: string,
37
+ resolution?: string,
38
+ renderingSpeed?: string,
39
+ targetResolution?: string,
40
+ referenceImageCount?: number,
41
+ ): string {
42
+ // Flux 2 family: billed per output megapixel AND per reference image (each
43
+ // ref counts as input megapixels). Resolution arrives as "N MP" strings (the
44
+ // UI value space); strip the unit. ALL three models encode the ref count
45
+ // (capped at 8): Pro/Klein i2i always carry the source image as a ref and
46
+ // their cost formula charges per input MP, so the reserved identifier must
47
+ // reflect refs (there is no metered true-up to correct an under-reserved tier).
48
+ if (isFlux2Model(provider)) {
49
+ const mp = (resolution ?? "1 MP").replace(/\s*MP$/i, "").trim()
50
+ return `${provider}:${mp}MP:${Math.min(referenceImageCount ?? 0, 8)}ref`
51
+ }
52
+ if (HIGH_QUALITY_PROVIDERS.has(provider) && quality === "high") {
53
+ return `${provider}:high`
54
+ }
55
+ if (TWO_K_RESOLUTION_PROVIDERS.has(provider) && resolution === "2K") {
56
+ return `${provider}:2K`
57
+ }
58
+ if (provider === "nano-banana-pro" && resolution === "4K") {
59
+ return `${provider}:4K`
60
+ }
61
+ if (RESOLUTION_2K_4K_TIERED_PROVIDERS.has(provider) && (resolution === "2K" || resolution === "4K")) {
62
+ return `${provider}:${resolution}`
63
+ }
64
+ // Topaz Image Upscale: 2K is default (no suffix), 4K/8K get composite identifiers
65
+ if (provider === "topaz-image-upscale" && targetResolution && targetResolution !== "2K") {
66
+ return `${provider}:${targetResolution}`
67
+ }
68
+ if (IDEOGRAM_PROVIDERS.has(provider)) {
69
+ if (renderingSpeed === "TURBO") return `${provider}:TURBO`
70
+ if (renderingSpeed === "QUALITY") return `${provider}:QUALITY`
71
+ }
72
+ return provider
73
+ }
74
+
75
+ /**
76
+ * Reference-aware image-generation credit identifier — the SINGLE source of
77
+ * truth shared by the single-node routes (`/v1/generate-image`,
78
+ * `/v1/image-to-image`) and the workflow orchestrator
79
+ * (`payload-builder.ts`). It centralises the two pricing dimensions that must
80
+ * match across both paths or a Flux 2 generation is billed the wrong tier
81
+ * (Flux 2 commits non-metered / has no upward true-up, so the reserved
82
+ * identifier IS the final charge):
83
+ * - `refCount`: the number of reference images sent to the provider (Flux 2
84
+ * bills per ref). generate-image passes the assembled count (0 for pure
85
+ * text-to-image); image-to-image passes 1 (the primary `imageUrl`) + extras.
86
+ * - `swapToI2i`: generate-image auto-swaps a bare T2I provider to its i2i
87
+ * sibling (`T2I_TO_I2I_VARIANT`) when refs are attached, so the credit id
88
+ * matches the variant actually invoked. image-to-image is already an i2i
89
+ * provider → pass `false`.
90
+ *
91
+ * The LoRA short-circuit (`flux-lora-character`) stays at the call site because
92
+ * the routing decision needs a DB lookup the routes/orchestrator own.
93
+ */
94
+ export function resolveImageGenCreditIdentifier(opts: {
95
+ provider: string | undefined
96
+ quality?: string
97
+ resolution?: string
98
+ renderingSpeed?: string
99
+ refCount: number
100
+ swapToI2i?: boolean
101
+ }): string {
102
+ const provider = opts.provider || "nano-banana"
103
+ const effectiveProvider =
104
+ opts.swapToI2i && opts.refCount > 0 ? (T2I_TO_I2I_VARIANT[provider] ?? provider) : provider
105
+ return buildCreditModelIdentifier(
106
+ effectiveProvider,
107
+ opts.quality,
108
+ opts.resolution,
109
+ opts.renderingSpeed,
110
+ undefined,
111
+ opts.refCount,
112
+ )
113
+ }
114
+
115
+ // T2V-specific credit overrides: some providers have different costs for T2V
116
+ // vs I2V/V2V due to different default resolutions or colliding with image model names.
117
+ const T2V_CREDIT_OVERRIDES: Record<string, string> = {
118
+ "grok": "grok-i2v", // T2V grok, same as I2V grok (not image grok = 4 cr)
119
+ "wan": "wan-t2v", // T2V wan 1080p = (V2V wan 720p = 70 cr)
120
+ "wan-turbo": "wan-turbo-t2v", // T2V wan-turbo 720p = (I2V wan-turbo 480p = 40 cr)
121
+ }
122
+
123
+ /**
124
+ * Compute composite model identifier for video models with duration/audio-based pricing.
125
+ * Examples: "kling-3.0:5s", "kling-3.0:10s:audio", "seedance-2:8s:720p-ref"
126
+ *
127
+ * @param provider - Video model key (e.g., "kling-3.0")
128
+ * @param duration - Video duration in seconds
129
+ * @param sound - Whether audio/sound is enabled
130
+ * @param nodeType - Node type for T2V-specific cost overrides
131
+ * @param mode - Quality variant that affects pricing
132
+ * @param resolution - Output resolution (used by Seedance 2 for 480p/720p pricing)
133
+ * @param hasVideoRef - Whether a reference video is connected (Seedance 2 uses a lower per-second rate when true)
134
+ */
135
+ /** Gemini Omni Video duration tiers (seconds). Mirrors `MODEL_CATALOG["gemini-omni-video"].durations`
136
+ * and `KIE_VIDEO_MODELS["gemini-omni-video"].allowedDurations`. Hoisted to module scope so the
137
+ * nearest-tier snap below doesn't reallocate on every (hot-path) call. */
138
+ const GEMINI_OMNI_DURATIONS = [4, 6, 8, 10]
139
+
140
+ /**
141
+ * LTX 2.3 (Lightricks via Replicate) credit tiers: priced by
142
+ * (resolution-band × duration-seconds). Keys mirror STATIC_CREDIT_COSTS /
143
+ * model_pricing EXACTLY (lowercase band + raw seconds + "s"), e.g.
144
+ * "ltx-2.3-pro:1080p:6s". This map is the source of truth for the nearest-tier
145
+ * snap below, so an off-catalog (resolution, duration) from a direct API/SDK
146
+ * call maps to a SEEDED price instead of hard-failing (price_not_configured
147
+ * 503). Pro: 6/8/10s at every band. Fast: up to 20s at 1080p, 6/8/10s at 2k/4k.
148
+ */
149
+ const LTX_DURATION_TIERS: Record<string, Record<string, number[]>> = {
150
+ "ltx-2.3-pro": { "1080p": [6, 8, 10], "2k": [6, 8, 10], "4k": [6, 8, 10] },
151
+ "ltx-2.3-fast": { "1080p": [6, 8, 10, 12, 14, 16, 18, 20], "2k": [6, 8, 10], "4k": [6, 8, 10] },
152
+ }
153
+
154
+ export function buildVideoCreditModelIdentifier(
155
+ provider: string,
156
+ duration?: number | string,
157
+ sound?: boolean,
158
+ nodeType?: "image-to-video" | "text-to-video",
159
+ mode?: string,
160
+ resolution?: string,
161
+ hasVideoRef?: boolean,
162
+ ): string {
163
+ // T2V overrides: some providers have different base costs for text-to-video
164
+ let effectiveProvider = provider
165
+ if (nodeType === "text-to-video") {
166
+ const override = T2V_CREDIT_OVERRIDES[provider]
167
+ if (override) {
168
+ // If override target also has duration pricing, use it as effective provider
169
+ if (DURATION_PRICED_PROVIDERS.has(override)) {
170
+ effectiveProvider = override
171
+ } else {
172
+ return override
173
+ }
174
+ }
175
+ }
176
+
177
+ // VEO 3.x resolution-tiered pricing: 720p is the base (no suffix), 1080p gets
178
+ // ":1080p" (Fast/Lite only), and 4K gets ":4k" for ALL three tiers — direct-4K
179
+ // generation chains KIE's get-4k-video off the base generation (the base runs
180
+ // at 1080p). Duration is fixed at 8s for VEO so the duration-tier path below
181
+ // does not apply.
182
+ if (isVeoProvider(effectiveProvider)) {
183
+ if (resolution === "4k") return `${effectiveProvider}:4k`
184
+ // 1080p tier is Fast/Lite only (Quality has no 1080p surcharge).
185
+ if (resolution === "1080p" && VEO_RESOLUTION_TIERED_PROVIDERS.has(effectiveProvider)) return `${effectiveProvider}:1080p`
186
+ return effectiveProvider
187
+ }
188
+
189
+ // Gemini Omni Video: priced by (resolution-band × duration), with a flat
190
+ // per-generation rate when a source video is supplied (V2V). Lowercase "4k".
191
+ if (effectiveProvider === "gemini-omni-video") {
192
+ if (hasVideoRef) {
193
+ return resolution === "4k" ? "gemini-omni-video:4k:vref" : "gemini-omni-video:vref"
194
+ }
195
+ // Snap to nearest allowed tier (NOT a min/max clamp) so off-tier durations
196
+ // map to a SEEDED composite; default 8 when unset.
197
+ const raw = parseInt(String(duration ?? 8), 10)
198
+ const d = Number.isNaN(raw) ? 8 : GEMINI_OMNI_DURATIONS.reduce((b, a) => (Math.abs(a - raw) < Math.abs(b - raw) ? a : b))
199
+ return resolution === "4k" ? `gemini-omni-video:4k:${d}` : `gemini-omni-video:${d}`
200
+ }
201
+
202
+ // LTX 2.3: priced by (resolution-band × duration-seconds). The RESERVE must be
203
+ // the correct composite — commit_credits (migration 176) only refunds a surplus
204
+ // (actual < reserved) and NEVER collects an upward delta, so an under-reserved
205
+ // LTX run (the bare-id default = cheapest 1080p:6s tier) stays under-charged
206
+ // even with meteredCost:true. Snap to a seeded tier so the id always prices.
207
+ if (effectiveProvider === "ltx-2.3-pro" || effectiveProvider === "ltx-2.3-fast") {
208
+ const bands = LTX_DURATION_TIERS[effectiveProvider]
209
+ const band = bands[String(resolution)] ? String(resolution) : "1080p"
210
+ const allowed = bands[band]
211
+ const raw = typeof duration === "string" ? parseInt(duration, 10) : (duration ?? allowed[0])
212
+ const want = Number.isNaN(raw) ? allowed[0] : raw
213
+ const dur = allowed.reduce((b, a) => (Math.abs(a - want) < Math.abs(b - want) ? a : b))
214
+ return `${effectiveProvider}:${band}:${dur}s`
215
+ }
216
+
217
+ if (!DURATION_PRICED_PROVIDERS.has(effectiveProvider)) {
218
+ return effectiveProvider
219
+ }
220
+
221
+ const parsed = typeof duration === "string" ? parseInt(duration, 10) : (duration ?? 5)
222
+ const durationSec = Number.isNaN(parsed) ? 5 : parsed
223
+ const tiers = VIDEO_DURATION_TIERS[effectiveProvider]
224
+ if (!tiers) return effectiveProvider
225
+
226
+ // Find the matching duration tier
227
+ const tier = tiers.find(t => durationSec <= t.maxSeconds) ?? tiers[tiers.length - 1]
228
+ let identifier = `${effectiveProvider}:${tier.suffix}`
229
+
230
+ // Append audio suffix if applicable
231
+ if (AUDIO_ADDON_PROVIDERS.has(effectiveProvider) && sound) {
232
+ identifier += ":audio"
233
+ }
234
+
235
+ // Append mode suffix for providers with quality-tiered pricing
236
+ // "high" comes from I2V videoSize field, "pro" comes from T2V mode field
237
+ if (MODE_ADDON_PROVIDERS.has(effectiveProvider) && (mode === "high" || mode === "pro")) {
238
+ identifier += ":high"
239
+ }
240
+
241
+ // Seedance 2.0 family: per-second billing with resolution + video-ref dimensions.
242
+ // KIE accepts 480p / 720p / 1080p natively; per-second rates rise non-linearly
243
+ // so each tier gets its own composite identifier.
244
+ if (RESOLUTION_VIDEO_REF_PRICING.has(effectiveProvider)) {
245
+ // Clamp to the model's catalog resolutions (single source of truth): e.g.
246
+ // seedance-2-mini exposes only 480p/720p, so a stale/explicit 1080p (or 4k)
247
+ // maps to its top priced tier instead of emitting an unpriced composite
248
+ // (which the hard-fail credit guard would 503 on at runtime).
249
+ const supported = MODEL_CATALOG[effectiveProvider]?.resolutions ?? ["480p", "720p", "1080p"]
250
+ const want = resolution === "4k" ? "4k" : resolution === "1080p" ? "1080p" : resolution === "720p" ? "720p" : "480p"
251
+ // Unsupported (e.g. a stale 1080p on seedance-2-mini) clamps to the model's
252
+ // top priced tier so the emitted composite is always seeded.
253
+ const res = supported.includes(want) ? want : (supported[supported.length - 1] ?? "480p")
254
+ identifier += `:${res}`
255
+ if (hasVideoRef) identifier += "-ref"
256
+ }
257
+
258
+ // Duration × resolution pricing without a -ref dimension. Resolutions
259
+ // outside the provider's priced tiers (or undefined) collapse to the first
260
+ // (default) tier so the emitted composite is always seeded — the hard-fail
261
+ // guard fuzzes the whole resolution space.
262
+ const resTiers = RESOLUTION_DURATION_PRICING[effectiveProvider]
263
+ if (resTiers) {
264
+ const res = resolution && resTiers.includes(resolution) ? resolution : resTiers[0]!
265
+ identifier += `:${res}`
266
+ }
267
+
268
+ return identifier
269
+ }
270
+
271
+ /**
272
+ * Compute composite model identifier for motion control with duration-tiered pricing.
273
+ * Examples: "kling-3.0-motion:10s", "kling-3.0-motion:1080p:15s", "motion-transfer:5s"
274
+ *
275
+ * Wan Animate providers use resolution-tiered pricing (not per-second):
276
+ * "wan-animate-move" (480p default), "wan-animate-move:580p", "wan-animate-move:720p"
277
+ *
278
+ * @param provider - Motion control provider key ("kling" for 2.6, "kling-3.0", "wan-animate-move", "wan-animate-replace")
279
+ * @param resolution - "720p" or "1080p" (Kling), "480p" or "580p" or "720p" (Wan Animate)
280
+ * @param videoDuration - Reference video duration in seconds (defaults to 10s, unused for Wan Animate)
281
+ */
282
+ export function buildMotionCreditModelIdentifier(
283
+ provider: string,
284
+ resolution: string,
285
+ videoDuration?: number,
286
+ ): string {
287
+ // Wan Animate providers use resolution-tiered pricing (not duration-based)
288
+ if (provider === "wan-animate-move" || provider === "wan-animate-replace") {
289
+ // 480p is the default (base identifier), 580p and 720p get composite suffix
290
+ if (resolution === "580p" || resolution === "720p") {
291
+ return `${provider}:${resolution}`
292
+ }
293
+ return provider
294
+ }
295
+
296
+ const raw = videoDuration ?? 10
297
+ const durationSec = Math.floor(Number.isNaN(raw) ? 10 : raw) // default 10s; floor to match KIE per-second billing
298
+ const tier = MOTION_DURATION_TIERS.find(t => durationSec <= t.maxSeconds)
299
+ ?? MOTION_DURATION_TIERS[MOTION_DURATION_TIERS.length - 1]
300
+
301
+ const base = provider === "kling-3.0" ? "kling-3.0-motion" : "motion-transfer"
302
+ const resSuffix = resolution === "1080p" ? ":1080p" : ""
303
+ return `${base}${resSuffix}:${tier.suffix}`
304
+ }