@nodetool-ai/runtime 0.8.1 → 0.8.3

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 (373) hide show
  1. package/dist/.nodetool-build-stamp +1 -1
  2. package/dist/context.d.ts +31 -3
  3. package/dist/context.d.ts.map +1 -1
  4. package/dist/context.js +369 -160
  5. package/dist/context.js.map +1 -1
  6. package/dist/generation-registry.d.ts +8 -0
  7. package/dist/generation-registry.d.ts.map +1 -1
  8. package/dist/generation-registry.js +18 -0
  9. package/dist/generation-registry.js.map +1 -1
  10. package/dist/host-binaries.d.ts.map +1 -1
  11. package/dist/host-binaries.js +18 -10
  12. package/dist/host-binaries.js.map +1 -1
  13. package/dist/image-codec.d.ts +2 -0
  14. package/dist/image-codec.d.ts.map +1 -1
  15. package/dist/image-codec.js +23 -3
  16. package/dist/image-codec.js.map +1 -1
  17. package/dist/image-model-tasks.d.ts +35 -0
  18. package/dist/image-model-tasks.d.ts.map +1 -0
  19. package/dist/image-model-tasks.js +67 -0
  20. package/dist/image-model-tasks.js.map +1 -0
  21. package/dist/index.d.ts +14 -3
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +10 -2
  24. package/dist/index.js.map +1 -1
  25. package/dist/media-ref-bytes.d.ts +18 -2
  26. package/dist/media-ref-bytes.d.ts.map +1 -1
  27. package/dist/media-ref-bytes.js +26 -16
  28. package/dist/media-ref-bytes.js.map +1 -1
  29. package/dist/node-executor.d.ts +6 -0
  30. package/dist/node-executor.d.ts.map +1 -1
  31. package/dist/prompt-asset-refs.d.ts +12 -1
  32. package/dist/prompt-asset-refs.d.ts.map +1 -1
  33. package/dist/prompt-asset-refs.js +9 -4
  34. package/dist/prompt-asset-refs.js.map +1 -1
  35. package/dist/providers/aki-provider.d.ts +2 -0
  36. package/dist/providers/aki-provider.d.ts.map +1 -1
  37. package/dist/providers/aki-provider.js +6 -0
  38. package/dist/providers/aki-provider.js.map +1 -1
  39. package/dist/providers/api-route-provider.d.ts +22 -0
  40. package/dist/providers/api-route-provider.d.ts.map +1 -0
  41. package/dist/providers/api-route-provider.js +56 -0
  42. package/dist/providers/api-route-provider.js.map +1 -0
  43. package/dist/providers/atlascloud-provider.d.ts +2 -0
  44. package/dist/providers/atlascloud-provider.d.ts.map +1 -1
  45. package/dist/providers/atlascloud-provider.js +22 -0
  46. package/dist/providers/atlascloud-provider.js.map +1 -1
  47. package/dist/providers/base-provider.d.ts +36 -24
  48. package/dist/providers/base-provider.d.ts.map +1 -1
  49. package/dist/providers/base-provider.js +145 -150
  50. package/dist/providers/base-provider.js.map +1 -1
  51. package/dist/providers/chatgpt-provider.d.ts +48 -0
  52. package/dist/providers/chatgpt-provider.d.ts.map +1 -0
  53. package/dist/providers/chatgpt-provider.js +506 -0
  54. package/dist/providers/chatgpt-provider.js.map +1 -0
  55. package/dist/providers/claude-agent-provider.d.ts +6 -36
  56. package/dist/providers/claude-agent-provider.d.ts.map +1 -1
  57. package/dist/providers/claude-agent-provider.js +208 -26
  58. package/dist/providers/claude-agent-provider.js.map +1 -1
  59. package/dist/providers/codex-provider.d.ts +2 -0
  60. package/dist/providers/codex-provider.d.ts.map +1 -1
  61. package/dist/providers/codex-provider.js +15 -2
  62. package/dist/providers/codex-provider.js.map +1 -1
  63. package/dist/providers/cohere-provider.d.ts +2 -1
  64. package/dist/providers/cohere-provider.d.ts.map +1 -1
  65. package/dist/providers/cohere-provider.js +5 -0
  66. package/dist/providers/cohere-provider.js.map +1 -1
  67. package/dist/providers/context-window.d.ts.map +1 -1
  68. package/dist/providers/context-window.js +1 -0
  69. package/dist/providers/context-window.js.map +1 -1
  70. package/dist/providers/core-tools.d.ts +2 -1
  71. package/dist/providers/core-tools.d.ts.map +1 -1
  72. package/dist/providers/core-tools.js +2 -1
  73. package/dist/providers/core-tools.js.map +1 -1
  74. package/dist/providers/cost-calculator.d.ts.map +1 -1
  75. package/dist/providers/cost-calculator.js +9 -0
  76. package/dist/providers/cost-calculator.js.map +1 -1
  77. package/dist/providers/credential-check.d.ts +8 -0
  78. package/dist/providers/credential-check.d.ts.map +1 -1
  79. package/dist/providers/credential-check.js +27 -0
  80. package/dist/providers/credential-check.js.map +1 -1
  81. package/dist/providers/custom-openai-provider.d.ts +2 -0
  82. package/dist/providers/custom-openai-provider.d.ts.map +1 -1
  83. package/dist/providers/custom-openai-provider.js +27 -1
  84. package/dist/providers/custom-openai-provider.js.map +1 -1
  85. package/dist/providers/dreamina-models.d.ts +5 -0
  86. package/dist/providers/dreamina-models.d.ts.map +1 -0
  87. package/dist/providers/dreamina-models.js +57 -0
  88. package/dist/providers/dreamina-models.js.map +1 -0
  89. package/dist/providers/dreamina-music.d.ts +52 -0
  90. package/dist/providers/dreamina-music.d.ts.map +1 -0
  91. package/dist/providers/dreamina-music.js +115 -0
  92. package/dist/providers/dreamina-music.js.map +1 -0
  93. package/dist/providers/dreamina-provider.d.ts +64 -0
  94. package/dist/providers/dreamina-provider.d.ts.map +1 -0
  95. package/dist/providers/dreamina-provider.js +1001 -0
  96. package/dist/providers/dreamina-provider.js.map +1 -0
  97. package/dist/providers/elevenlabs-provider.d.ts +2 -1
  98. package/dist/providers/elevenlabs-provider.d.ts.map +1 -1
  99. package/dist/providers/elevenlabs-provider.js +5 -0
  100. package/dist/providers/elevenlabs-provider.js.map +1 -1
  101. package/dist/providers/evolink-provider.d.ts +2 -0
  102. package/dist/providers/evolink-provider.d.ts.map +1 -1
  103. package/dist/providers/evolink-provider.js +8 -0
  104. package/dist/providers/evolink-provider.js.map +1 -1
  105. package/dist/providers/extension-tab.d.ts +29 -0
  106. package/dist/providers/extension-tab.d.ts.map +1 -0
  107. package/dist/providers/extension-tab.js +144 -0
  108. package/dist/providers/extension-tab.js.map +1 -0
  109. package/dist/providers/fake-provider.d.ts +5 -2
  110. package/dist/providers/fake-provider.d.ts.map +1 -1
  111. package/dist/providers/fake-provider.js +73 -9
  112. package/dist/providers/fake-provider.js.map +1 -1
  113. package/dist/providers/fake-schema-answer.d.ts +10 -0
  114. package/dist/providers/fake-schema-answer.d.ts.map +1 -0
  115. package/dist/providers/fake-schema-answer.js +64 -0
  116. package/dist/providers/fake-schema-answer.js.map +1 -0
  117. package/dist/providers/fal-provider.d.ts +2 -1
  118. package/dist/providers/fal-provider.d.ts.map +1 -1
  119. package/dist/providers/fal-provider.js +32 -2
  120. package/dist/providers/fal-provider.js.map +1 -1
  121. package/dist/providers/gemini-provider.d.ts +2 -1
  122. package/dist/providers/gemini-provider.d.ts.map +1 -1
  123. package/dist/providers/gemini-provider.js +15 -0
  124. package/dist/providers/gemini-provider.js.map +1 -1
  125. package/dist/providers/gemini-web-provider.d.ts +76 -0
  126. package/dist/providers/gemini-web-provider.d.ts.map +1 -0
  127. package/dist/providers/gemini-web-provider.js +752 -0
  128. package/dist/providers/gemini-web-provider.js.map +1 -0
  129. package/dist/providers/higgsfield-provider.d.ts +2 -1
  130. package/dist/providers/higgsfield-provider.d.ts.map +1 -1
  131. package/dist/providers/higgsfield-provider.js +12 -0
  132. package/dist/providers/higgsfield-provider.js.map +1 -1
  133. package/dist/providers/huggingface-provider.d.ts +2 -1
  134. package/dist/providers/huggingface-provider.d.ts.map +1 -1
  135. package/dist/providers/huggingface-provider.js +27 -1
  136. package/dist/providers/huggingface-provider.js.map +1 -1
  137. package/dist/providers/index.d.ts +14 -3
  138. package/dist/providers/index.d.ts.map +1 -1
  139. package/dist/providers/index.js +48 -9
  140. package/dist/providers/index.js.map +1 -1
  141. package/dist/providers/jina-provider.d.ts +2 -1
  142. package/dist/providers/jina-provider.d.ts.map +1 -1
  143. package/dist/providers/jina-provider.js +5 -0
  144. package/dist/providers/jina-provider.js.map +1 -1
  145. package/dist/providers/kie-provider.d.ts +2 -1
  146. package/dist/providers/kie-provider.d.ts.map +1 -1
  147. package/dist/providers/kie-provider.js +14 -0
  148. package/dist/providers/kie-provider.js.map +1 -1
  149. package/dist/providers/llama-provider.d.ts +12 -1
  150. package/dist/providers/llama-provider.d.ts.map +1 -1
  151. package/dist/providers/llama-provider.js +29 -5
  152. package/dist/providers/llama-provider.js.map +1 -1
  153. package/dist/providers/lmstudio-provider.js +2 -2
  154. package/dist/providers/lmstudio-provider.js.map +1 -1
  155. package/dist/providers/loop-tracing.d.ts +17 -0
  156. package/dist/providers/loop-tracing.d.ts.map +1 -0
  157. package/dist/providers/loop-tracing.js +132 -0
  158. package/dist/providers/loop-tracing.js.map +1 -0
  159. package/dist/providers/manifest-models.d.ts +2 -12
  160. package/dist/providers/manifest-models.d.ts.map +1 -1
  161. package/dist/providers/manifest-models.js +45 -3
  162. package/dist/providers/manifest-models.js.map +1 -1
  163. package/dist/providers/meshy-provider.d.ts +2 -1
  164. package/dist/providers/meshy-provider.d.ts.map +1 -1
  165. package/dist/providers/meshy-provider.js +6 -0
  166. package/dist/providers/meshy-provider.js.map +1 -1
  167. package/dist/providers/meta-provider.d.ts +53 -6
  168. package/dist/providers/meta-provider.d.ts.map +1 -1
  169. package/dist/providers/meta-provider.js +289 -6
  170. package/dist/providers/meta-provider.js.map +1 -1
  171. package/dist/providers/meta-segmentation.d.ts +27 -0
  172. package/dist/providers/meta-segmentation.d.ts.map +1 -0
  173. package/dist/providers/meta-segmentation.js +141 -0
  174. package/dist/providers/meta-segmentation.js.map +1 -0
  175. package/dist/providers/meta-transcription.d.ts +31 -0
  176. package/dist/providers/meta-transcription.d.ts.map +1 -0
  177. package/dist/providers/meta-transcription.js +187 -0
  178. package/dist/providers/meta-transcription.js.map +1 -0
  179. package/dist/providers/minimax-provider.d.ts +2 -0
  180. package/dist/providers/minimax-provider.d.ts.map +1 -1
  181. package/dist/providers/minimax-provider.js +11 -0
  182. package/dist/providers/minimax-provider.js.map +1 -1
  183. package/dist/providers/mistral-provider.d.ts +2 -0
  184. package/dist/providers/mistral-provider.d.ts.map +1 -1
  185. package/dist/providers/mistral-provider.js +5 -0
  186. package/dist/providers/mistral-provider.js.map +1 -1
  187. package/dist/providers/node-llama-cpp-provider.d.ts +8 -4
  188. package/dist/providers/node-llama-cpp-provider.d.ts.map +1 -1
  189. package/dist/providers/node-llama-cpp-provider.js +83 -61
  190. package/dist/providers/node-llama-cpp-provider.js.map +1 -1
  191. package/dist/providers/nodetool-provider.d.ts +1 -1
  192. package/dist/providers/nodetool-provider.d.ts.map +1 -1
  193. package/dist/providers/nodetool-provider.js +0 -2
  194. package/dist/providers/nodetool-provider.js.map +1 -1
  195. package/dist/providers/ollama-provider.d.ts +25 -1
  196. package/dist/providers/ollama-provider.d.ts.map +1 -1
  197. package/dist/providers/ollama-provider.js +145 -27
  198. package/dist/providers/ollama-provider.js.map +1 -1
  199. package/dist/providers/openai-compat/client.d.ts +10 -1
  200. package/dist/providers/openai-compat/client.d.ts.map +1 -1
  201. package/dist/providers/openai-compat/client.js +21 -3
  202. package/dist/providers/openai-compat/client.js.map +1 -1
  203. package/dist/providers/openai-compat/index.d.ts +1 -1
  204. package/dist/providers/openai-compat/index.d.ts.map +1 -1
  205. package/dist/providers/openai-compat/index.js +1 -1
  206. package/dist/providers/openai-compat/index.js.map +1 -1
  207. package/dist/providers/openai-compat/types.d.ts +4 -0
  208. package/dist/providers/openai-compat/types.d.ts.map +1 -1
  209. package/dist/providers/openai-compat-provider.d.ts +11 -0
  210. package/dist/providers/openai-compat-provider.d.ts.map +1 -1
  211. package/dist/providers/openai-compat-provider.js +47 -2
  212. package/dist/providers/openai-compat-provider.js.map +1 -1
  213. package/dist/providers/openai-provider.d.ts +23 -2
  214. package/dist/providers/openai-provider.d.ts.map +1 -1
  215. package/dist/providers/openai-provider.js +102 -21
  216. package/dist/providers/openai-provider.js.map +1 -1
  217. package/dist/providers/openrouter-provider.d.ts +52 -1
  218. package/dist/providers/openrouter-provider.d.ts.map +1 -1
  219. package/dist/providers/openrouter-provider.js +372 -11
  220. package/dist/providers/openrouter-provider.js.map +1 -1
  221. package/dist/providers/opper-provider.d.ts +31 -0
  222. package/dist/providers/opper-provider.d.ts.map +1 -0
  223. package/dist/providers/opper-provider.js +91 -0
  224. package/dist/providers/opper-provider.js.map +1 -0
  225. package/dist/providers/provider-error.d.ts.map +1 -1
  226. package/dist/providers/provider-error.js +7 -1
  227. package/dist/providers/provider-error.js.map +1 -1
  228. package/dist/providers/provider-generations.d.ts +2 -2
  229. package/dist/providers/provider-generations.js +2 -2
  230. package/dist/providers/provider-request-log.d.ts.map +1 -1
  231. package/dist/providers/provider-request-log.js +19 -7
  232. package/dist/providers/provider-request-log.js.map +1 -1
  233. package/dist/providers/python-provider.d.ts +6 -1
  234. package/dist/providers/python-provider.d.ts.map +1 -1
  235. package/dist/providers/python-provider.js +35 -14
  236. package/dist/providers/python-provider.js.map +1 -1
  237. package/dist/providers/replicate-provider.d.ts +2 -1
  238. package/dist/providers/replicate-provider.d.ts.map +1 -1
  239. package/dist/providers/replicate-provider.js +22 -0
  240. package/dist/providers/replicate-provider.js.map +1 -1
  241. package/dist/providers/requesty-provider.d.ts +30 -0
  242. package/dist/providers/requesty-provider.d.ts.map +1 -0
  243. package/dist/providers/requesty-provider.js +87 -0
  244. package/dist/providers/requesty-provider.js.map +1 -0
  245. package/dist/providers/responses-api.d.ts +12 -2
  246. package/dist/providers/responses-api.d.ts.map +1 -1
  247. package/dist/providers/responses-api.js +54 -3
  248. package/dist/providers/responses-api.js.map +1 -1
  249. package/dist/providers/reve-provider.d.ts +2 -1
  250. package/dist/providers/reve-provider.d.ts.map +1 -1
  251. package/dist/providers/reve-provider.js +6 -0
  252. package/dist/providers/reve-provider.js.map +1 -1
  253. package/dist/providers/rodin-provider.d.ts +2 -1
  254. package/dist/providers/rodin-provider.d.ts.map +1 -1
  255. package/dist/providers/rodin-provider.js +6 -0
  256. package/dist/providers/rodin-provider.js.map +1 -1
  257. package/dist/providers/scripted-provider.d.ts.map +1 -1
  258. package/dist/providers/scripted-provider.js +6 -1
  259. package/dist/providers/scripted-provider.js.map +1 -1
  260. package/dist/providers/together-operations.d.ts +27 -0
  261. package/dist/providers/together-operations.d.ts.map +1 -0
  262. package/dist/providers/together-operations.js +95 -0
  263. package/dist/providers/together-operations.js.map +1 -0
  264. package/dist/providers/together-provider.d.ts +5 -8
  265. package/dist/providers/together-provider.d.ts.map +1 -1
  266. package/dist/providers/together-provider.js +54 -159
  267. package/dist/providers/together-provider.js.map +1 -1
  268. package/dist/providers/topaz-provider.d.ts +2 -1
  269. package/dist/providers/topaz-provider.d.ts.map +1 -1
  270. package/dist/providers/topaz-provider.js +5 -0
  271. package/dist/providers/topaz-provider.js.map +1 -1
  272. package/dist/providers/types.d.ts +54 -0
  273. package/dist/providers/types.d.ts.map +1 -1
  274. package/dist/providers/types.js.map +1 -1
  275. package/dist/providers/useapi-provider.d.ts +2 -1
  276. package/dist/providers/useapi-provider.d.ts.map +1 -1
  277. package/dist/providers/useapi-provider.js +8 -0
  278. package/dist/providers/useapi-provider.js.map +1 -1
  279. package/dist/providers/vllm-provider.d.ts +9 -4
  280. package/dist/providers/vllm-provider.d.ts.map +1 -1
  281. package/dist/providers/vllm-provider.js +16 -8
  282. package/dist/providers/vllm-provider.js.map +1 -1
  283. package/dist/providers/voyage-provider.d.ts +2 -1
  284. package/dist/providers/voyage-provider.d.ts.map +1 -1
  285. package/dist/providers/voyage-provider.js +5 -0
  286. package/dist/providers/voyage-provider.js.map +1 -1
  287. package/dist/providers/xai-provider.d.ts +2 -0
  288. package/dist/providers/xai-provider.d.ts.map +1 -1
  289. package/dist/providers/xai-provider.js +8 -0
  290. package/dist/providers/xai-provider.js.map +1 -1
  291. package/dist/python-bridge-base.d.ts +16 -6
  292. package/dist/python-bridge-base.d.ts.map +1 -1
  293. package/dist/python-bridge-base.js +117 -40
  294. package/dist/python-bridge-base.js.map +1 -1
  295. package/dist/python-bridge-codec.d.ts +16 -1
  296. package/dist/python-bridge-codec.d.ts.map +1 -1
  297. package/dist/python-bridge-codec.js +60 -2
  298. package/dist/python-bridge-codec.js.map +1 -1
  299. package/dist/python-bridge-framing.d.ts +10 -1
  300. package/dist/python-bridge-framing.d.ts.map +1 -1
  301. package/dist/python-bridge-framing.js +33 -8
  302. package/dist/python-bridge-framing.js.map +1 -1
  303. package/dist/python-bridge-types.d.ts +26 -5
  304. package/dist/python-bridge-types.d.ts.map +1 -1
  305. package/dist/python-bridge-types.js.map +1 -1
  306. package/dist/python-node-executor.d.ts +14 -3
  307. package/dist/python-node-executor.d.ts.map +1 -1
  308. package/dist/python-node-executor.js +185 -3
  309. package/dist/python-node-executor.js.map +1 -1
  310. package/dist/python-stdio-bridge.d.ts +6 -0
  311. package/dist/python-stdio-bridge.d.ts.map +1 -1
  312. package/dist/python-stdio-bridge.js +25 -2
  313. package/dist/python-stdio-bridge.js.map +1 -1
  314. package/dist/recommended-models.d.ts.map +1 -1
  315. package/dist/recommended-models.js +20 -0
  316. package/dist/recommended-models.js.map +1 -1
  317. package/dist/run-context.d.ts +20 -0
  318. package/dist/run-context.d.ts.map +1 -0
  319. package/dist/run-context.js +18 -0
  320. package/dist/run-context.js.map +1 -0
  321. package/dist/run-trace-context.d.ts +5 -0
  322. package/dist/run-trace-context.d.ts.map +1 -0
  323. package/dist/run-trace-context.js +33 -0
  324. package/dist/run-trace-context.js.map +1 -0
  325. package/dist/run-trace-processor.d.ts +65 -0
  326. package/dist/run-trace-processor.d.ts.map +1 -0
  327. package/dist/run-trace-processor.js +418 -0
  328. package/dist/run-trace-processor.js.map +1 -0
  329. package/dist/run-trace-scope.d.ts +28 -0
  330. package/dist/run-trace-scope.d.ts.map +1 -0
  331. package/dist/run-trace-scope.js +51 -0
  332. package/dist/run-trace-scope.js.map +1 -0
  333. package/dist/run-trace-serialization.d.ts +5 -0
  334. package/dist/run-trace-serialization.d.ts.map +1 -0
  335. package/dist/run-trace-serialization.js +114 -0
  336. package/dist/run-trace-serialization.js.map +1 -0
  337. package/dist/span-record.d.ts +4 -0
  338. package/dist/span-record.d.ts.map +1 -0
  339. package/dist/span-record.js +48 -0
  340. package/dist/span-record.js.map +1 -0
  341. package/dist/storage-workspace.d.ts +3 -0
  342. package/dist/storage-workspace.d.ts.map +1 -1
  343. package/dist/storage-workspace.js +32 -17
  344. package/dist/storage-workspace.js.map +1 -1
  345. package/dist/swappable-python-bridge.d.ts +6 -6
  346. package/dist/swappable-python-bridge.d.ts.map +1 -1
  347. package/dist/swappable-python-bridge.js +10 -10
  348. package/dist/swappable-python-bridge.js.map +1 -1
  349. package/dist/telemetry.d.ts +9 -3
  350. package/dist/telemetry.d.ts.map +1 -1
  351. package/dist/telemetry.js +53 -20
  352. package/dist/telemetry.js.map +1 -1
  353. package/dist/testing.d.ts +1 -1
  354. package/dist/testing.d.ts.map +1 -1
  355. package/dist/testing.js +55 -3
  356. package/dist/testing.js.map +1 -1
  357. package/dist/trace-exporters.d.ts +2 -30
  358. package/dist/trace-exporters.d.ts.map +1 -1
  359. package/dist/trace-exporters.js +4 -42
  360. package/dist/trace-exporters.js.map +1 -1
  361. package/dist/traced-storage.d.ts +17 -0
  362. package/dist/traced-storage.d.ts.map +1 -0
  363. package/dist/traced-storage.js +41 -0
  364. package/dist/traced-storage.js.map +1 -0
  365. package/dist/tracing-helpers.d.ts +15 -22
  366. package/dist/tracing-helpers.d.ts.map +1 -1
  367. package/dist/tracing-helpers.js +38 -6
  368. package/dist/tracing-helpers.js.map +1 -1
  369. package/dist/trim-video-window.d.ts +8 -0
  370. package/dist/trim-video-window.d.ts.map +1 -0
  371. package/dist/trim-video-window.js +50 -0
  372. package/dist/trim-video-window.js.map +1 -0
  373. package/package.json +21 -5
@@ -0,0 +1,1001 @@
1
+ import { createHash, createHmac, randomUUID } from "node:crypto";
2
+ import { crc32 } from "node:zlib";
3
+ import { createLogger } from "@nodetool-ai/config";
4
+ import { PROVIDER_IDS } from "@nodetool-ai/protocol";
5
+ import { fetchExternalMedia } from "../external-media-fetch.js";
6
+ import { BaseProvider } from "./base-provider.js";
7
+ import { createDreaminaMusic } from "./dreamina-music.js";
8
+ import { DREAMINA_IMAGE_MODELS, DREAMINA_VIDEO_MODELS } from "./dreamina-models.js";
9
+ const log = createLogger("nodetool.runtime.providers.dreamina");
10
+ /** The extension keeps the debugger attached between polls, then detaches when idle. */
11
+ const IDLE_CLOSE_MS = 30_000;
12
+ const DREAMINA_HOST = "dreamina.capcut.com";
13
+ /**
14
+ * Runs scripts in the tab the NodeTool extension is attached to, through the
15
+ * native messaging host's socket. The tab must already be a logged-in Dreamina
16
+ * page, because the requests rely on that origin and its cookies.
17
+ */
18
+ function createExtensionRunner() {
19
+ let handle = null;
20
+ let opening = null;
21
+ let idleTimer = null;
22
+ let busy = 0;
23
+ const drop = async () => {
24
+ const current = handle;
25
+ handle = null;
26
+ if (current)
27
+ await current.close().catch(() => undefined);
28
+ };
29
+ const open = async () => {
30
+ const { createExtensionPage } = await import("@nodetool-ai/browser");
31
+ const next = await createExtensionPage(undefined, { urlMatch: DREAMINA_HOST });
32
+ try {
33
+ const host = await next.page.evaluate("location.hostname");
34
+ if (host !== DREAMINA_HOST) {
35
+ throw new Error(`The attached tab is ${host || "not a web page"}. Open https://${DREAMINA_HOST}, sign in, and attach the NodeTool extension to that tab`);
36
+ }
37
+ }
38
+ catch (error) {
39
+ await next.close().catch(() => undefined);
40
+ throw error;
41
+ }
42
+ return next;
43
+ };
44
+ return {
45
+ async evaluate(expression) {
46
+ if (idleTimer)
47
+ clearTimeout(idleTimer);
48
+ busy += 1;
49
+ try {
50
+ if (!handle) {
51
+ // One attach serves every concurrent caller.
52
+ opening ??= open().finally(() => { opening = null; });
53
+ handle = await opening;
54
+ }
55
+ return await handle.page.evaluate(expression);
56
+ }
57
+ catch (error) {
58
+ await drop();
59
+ throw error;
60
+ }
61
+ finally {
62
+ busy -= 1;
63
+ if (busy === 0) {
64
+ idleTimer = setTimeout(() => void drop(), IDLE_CLOSE_MS);
65
+ idleTimer.unref?.();
66
+ }
67
+ }
68
+ }
69
+ };
70
+ }
71
+ let pageRunner = null;
72
+ /** Replace the page runner. Tests use this to avoid a real browser. */
73
+ export function setDreaminaPageRunner(runner) {
74
+ pageRunner = runner;
75
+ }
76
+ function runner() {
77
+ return (pageRunner ??= createExtensionRunner());
78
+ }
79
+ /**
80
+ * A request to the tab that did not answer in time. Its outcome is unknown:
81
+ * for the generate request, Dreamina may already run (and bill) the job.
82
+ */
83
+ class DreaminaStallError extends Error {
84
+ }
85
+ /** Whether `error` means a request stalled, so its outcome is unknown. */
86
+ function stalled(error) {
87
+ return error instanceof DreaminaStallError;
88
+ }
89
+ /**
90
+ * Evaluate `expression` in the tab, but stop waiting after `timeoutMs`. The
91
+ * provider keeps its own limit, so a stalled tab or bridge cannot hang a poll.
92
+ */
93
+ async function evaluateBounded(expression, what, timeoutMs = TAB_CALL_TIMEOUT_MS) {
94
+ let timer;
95
+ const timeout = new Promise((_, reject) => {
96
+ timer = setTimeout(() => reject(new DreaminaStallError(`Dreamina tab did not answer ${what} within ${timeoutMs / 1000}s`)), timeoutMs);
97
+ timer.unref?.();
98
+ });
99
+ try {
100
+ return await Promise.race([runner().evaluate(expression), timeout]);
101
+ }
102
+ finally {
103
+ clearTimeout(timer);
104
+ }
105
+ }
106
+ const API_BASE = "https://mweb-api-sg.capcut.com";
107
+ const APP_ID = "513641";
108
+ const APP_VERSION = "8.4.0";
109
+ const DA_VERSION = "3.3.28";
110
+ const WEB_VERSION = "7.5.0";
111
+ const POLL_INTERVAL_MS = 2500;
112
+ const MAX_POLL_FAILURES = 5;
113
+ /** How long a submit cut off by a lost tab has to show up in the history. */
114
+ const UNCONFIRMED_SUBMIT_GRACE_MS = 30_000;
115
+ const GENERATE_PATH = "/mweb/v1/aigc_draft/generate";
116
+ const DEFAULT_TIMEOUT_MS = 5 * 60_000;
117
+ const VIDEO_TIMEOUT_MS = 10 * 60_000;
118
+ const CONFIG_TTL_MS = 5 * 60_000;
119
+ /** One API request in the tab gives up after this, so a stalled request cannot block a poll. */
120
+ const API_TIMEOUT_MS = 30_000;
121
+ /** The whole tab round trip, past the request's own limit, before the provider stops waiting. */
122
+ const TAB_CALL_TIMEOUT_MS = API_TIMEOUT_MS + 15_000;
123
+ /** One image download attempt gives up after this. */
124
+ const DOWNLOAD_TIMEOUT_MS = 60_000;
125
+ const VIDEO_FPS = 24;
126
+ /** Dreamina's `image_ratio` enum, in the order the web app numbers it. */
127
+ const RATIO_TYPES = {
128
+ "1:1": 1,
129
+ "3:4": 2,
130
+ "16:9": 3,
131
+ "4:3": 4,
132
+ "9:16": 5,
133
+ "2:3": 6,
134
+ "3:2": 7,
135
+ "21:9": 8
136
+ };
137
+ /** `task.status` values returned by `get_history_by_ids`. */
138
+ const TASK_DONE = 50;
139
+ const TASK_FAILED = 30;
140
+ function region() {
141
+ const fromEnv = process.env.DREAMINA_REGION;
142
+ if (fromEnv)
143
+ return fromEnv.toUpperCase();
144
+ return new Intl.DateTimeFormat().resolvedOptions().locale.split("-")[1]?.toUpperCase() ?? "US";
145
+ }
146
+ /** The web app signs each call as md5 over the path tail, platform, version and clock. */
147
+ function signedHeaders(path) {
148
+ const deviceTime = String(Math.floor(Date.now() / 1000));
149
+ const sign = createHash("md5").update(`9e2c|${path.slice(-7)}|7|${APP_VERSION}|${deviceTime}||11ac`).digest("hex");
150
+ const loc = region();
151
+ return {
152
+ "Content-Type": "application/json",
153
+ sign,
154
+ "device-time": deviceTime,
155
+ tdid: "",
156
+ "sign-ver": "1",
157
+ pf: "7",
158
+ appvr: APP_VERSION,
159
+ loc,
160
+ lan: "en",
161
+ appid: APP_ID
162
+ };
163
+ }
164
+ /**
165
+ * Whether `error` means the Dreamina tab went away: closed, reloaded or
166
+ * navigated while a command ran. The runner drops its attach on any error, so
167
+ * the next call attaches afresh.
168
+ */
169
+ function tabLost(error) {
170
+ return error instanceof Error && /detached|target[ _]closed|no tab with/i.test(error.message);
171
+ }
172
+ /**
173
+ * Run `step` and, if the tab is lost, once more on a fresh attach. Only for
174
+ * steps that cost nothing and can repeat: never the generate request.
175
+ */
176
+ async function retryOnTabLoss(what, step) {
177
+ try {
178
+ return await step();
179
+ }
180
+ catch (error) {
181
+ if (!tabLost(error))
182
+ throw error;
183
+ log.warn("Dreamina tab lost, retrying on a fresh attach", { what, error: error.message });
184
+ return step();
185
+ }
186
+ }
187
+ async function callApi(path, body) {
188
+ return path === GENERATE_PATH ? callApiOnce(path, body) : retryOnTabLoss(path, () => callApiOnce(path, body));
189
+ }
190
+ async function callApiOnce(path, body) {
191
+ const query = new URLSearchParams({ aid: APP_ID, device_platform: "web", region: region(), da_version: DA_VERSION, web_version: WEB_VERSION, aigc_features: "app_lip_sync" });
192
+ const request = { url: `${API_BASE}${path}?${query}`, headers: signedHeaders(path), body: JSON.stringify(body) };
193
+ const expression = `(() => { const a = ${JSON.stringify(request)};
194
+ return new Promise((resolve, reject) => {
195
+ const x = new XMLHttpRequest();
196
+ x.open("POST", a.url);
197
+ x.withCredentials = true;
198
+ for (const k of Object.keys(a.headers)) x.setRequestHeader(k, a.headers[k]);
199
+ x.onload = () => { try { resolve(JSON.parse(x.responseText)); } catch { reject(new Error("Dreamina returned a non-JSON response (HTTP " + x.status + ")")); } };
200
+ x.onerror = () => reject(new Error("Dreamina request failed"));
201
+ x.timeout = ${API_TIMEOUT_MS};
202
+ x.ontimeout = () => reject(new Error("Dreamina request timed out after ${API_TIMEOUT_MS / 1000}s"));
203
+ x.send(a.body);
204
+ });
205
+ })()`;
206
+ const started = Date.now();
207
+ let response;
208
+ try {
209
+ response = (await evaluateBounded(expression, path));
210
+ }
211
+ catch (error) {
212
+ // A request that ran out of time has an unknown outcome, like a lost tab.
213
+ if (error instanceof Error && /timed out after/.test(error.message))
214
+ throw new DreaminaStallError(error.message);
215
+ throw error;
216
+ }
217
+ log.debug("Dreamina API call", { path, ret: response.ret, ms: Date.now() - started });
218
+ // `submit_audit_job` answers with an empty `ret`, which carries no error.
219
+ if (response.ret !== "0" && response.ret !== "") {
220
+ const hint = response.ret === "1015" || response.ret === "1014" ? " (is the Dreamina tab logged in?)" : "";
221
+ throw new Error(`Dreamina ${path} failed: ${response.errmsg ?? "unknown error"} (ret ${response.ret})${hint}`);
222
+ }
223
+ return response.data;
224
+ }
225
+ function aspectRatioFor(params, fallback = "1:1") {
226
+ if (params.aspectRatio && RATIO_TYPES[params.aspectRatio])
227
+ return params.aspectRatio;
228
+ if (params.aspectRatio)
229
+ throw new Error(`Dreamina does not support aspect ratio ${params.aspectRatio}. Use one of ${Object.keys(RATIO_TYPES).join(", ")}`);
230
+ if (params.width && params.height)
231
+ return nearestRatio(params.width, params.height);
232
+ return fallback;
233
+ }
234
+ function draftFor(model, prompt, negativePrompt, seed, ratioType, size, resolution) {
235
+ const id = () => randomUUID();
236
+ const component = {
237
+ type: "image_base_component",
238
+ id: id(),
239
+ min_version: "3.0.2",
240
+ aigc_mode: "workbench",
241
+ metadata: { type: "", id: id(), created_platform: 3, created_platform_version: "", created_time_in_ms: String(Date.now()), created_did: "" },
242
+ generate_type: "generate",
243
+ abilities: {
244
+ type: "",
245
+ id: id(),
246
+ generate: {
247
+ type: "",
248
+ id: id(),
249
+ core_param: {
250
+ type: "",
251
+ id: id(),
252
+ model,
253
+ prompt,
254
+ negative_prompt: negativePrompt,
255
+ seed,
256
+ sample_strength: 0.5,
257
+ image_ratio: ratioType,
258
+ large_image_info: { type: "", id: id(), height: size.height, width: size.width, resolution_type: resolution },
259
+ intelligent_ratio: false,
260
+ generate_type: 0
261
+ },
262
+ gen_option: { type: "", id: id(), gen_count: 1, generate_all: false }
263
+ }
264
+ }
265
+ };
266
+ return JSON.stringify({ type: "draft", id: id(), min_version: "3.0.2", min_features: [], is_from_tsn: true, version: DA_VERSION, main_component_id: component.id, component_list: [component] });
267
+ }
268
+ /**
269
+ * Image-to-image draft: the site's "blend" component with one `byte_edit`
270
+ * ability that carries the source images. The prompt keeps the `##` lead the
271
+ * web editor writes in front of a prompt that has references.
272
+ */
273
+ function blendDraftFor(model, prompt, negativePrompt, seed, strength, ratioType, size, resolution, sources) {
274
+ const id = () => randomUUID();
275
+ const component = {
276
+ type: "image_base_component",
277
+ id: id(),
278
+ min_version: "3.0.2",
279
+ aigc_mode: "workbench",
280
+ metadata: { type: "", id: id(), created_platform: 3, created_platform_version: "", created_time_in_ms: String(Date.now()), created_did: "" },
281
+ generate_type: "blend",
282
+ abilities: {
283
+ type: "",
284
+ id: id(),
285
+ blend: {
286
+ type: "",
287
+ id: id(),
288
+ min_features: [],
289
+ core_param: {
290
+ type: "",
291
+ id: id(),
292
+ model,
293
+ prompt: `##${prompt}`,
294
+ negative_prompt: negativePrompt,
295
+ seed,
296
+ sample_strength: strength,
297
+ image_ratio: ratioType,
298
+ large_image_info: { type: "", id: id(), height: size.height, width: size.width, resolution_type: resolution },
299
+ intelligent_ratio: false,
300
+ generate_type: 0
301
+ },
302
+ ability_list: [{
303
+ type: "",
304
+ id: id(),
305
+ name: "byte_edit",
306
+ image_uri_list: sources.map((image) => image.uri),
307
+ image_list: sources.map((image) => ({ type: "image", id: id(), source_from: "upload", platform_type: 1, name: "", image_uri: image.uri, width: image.width, height: image.height, format: "", title: "", uri: image.uri })),
308
+ strength: strength
309
+ }],
310
+ prompt_placeholder_info_list: [{ type: "", id: id(), ability_index: 0 }],
311
+ postedit_param: { type: "", id: id(), generate_type: 0 }
312
+ },
313
+ gen_option: { type: "", id: id(), gen_count: 1, generate_all: false }
314
+ }
315
+ };
316
+ return JSON.stringify({ type: "draft", id: id(), min_version: "3.0.2", min_features: [], is_from_tsn: true, version: DA_VERSION, main_component_id: component.id, component_list: [component] });
317
+ }
318
+ /** The nearest Dreamina ratio to a pixel size. */
319
+ function nearestRatio(width, height) {
320
+ const target = width / height;
321
+ let best = "1:1";
322
+ let bestDiff = Infinity;
323
+ for (const key of Object.keys(RATIO_TYPES)) {
324
+ const [w, h] = key.split(":").map(Number);
325
+ const diff = Math.abs(w / h - target);
326
+ if (diff < bestDiff) {
327
+ best = key;
328
+ bestDiff = diff;
329
+ }
330
+ }
331
+ return best;
332
+ }
333
+ const MAX_REFERENCE_IMAGES = 9;
334
+ const MAX_REFERENCE_VIDEOS = 3;
335
+ const MAX_REFERENCE_AUDIOS = 3;
336
+ /** Chrome accepts at most 1 MiB per host-to-extension message, so the page receives uploads in pieces. */
337
+ const UPLOAD_PIECE_CHARS = 400_000;
338
+ /** Run one request in the Dreamina tab, so its origin and cookies apply. */
339
+ async function pageFetch(request) {
340
+ const expression = `(async () => {
341
+ const a = ${JSON.stringify(request)};
342
+ let body;
343
+ if (a.body && "staged" in a.body) {
344
+ const binary = atob(window.__ntUp[a.body.staged]);
345
+ body = Uint8Array.from(binary, (c) => c.charCodeAt(0));
346
+ delete window.__ntUp[a.body.staged];
347
+ } else if (a.body) {
348
+ body = a.body.text;
349
+ }
350
+ const response = await fetch(a.url, { method: a.method, headers: a.headers, body, credentials: "omit" });
351
+ return { status: response.status, text: await response.text() };
352
+ })()`;
353
+ return (await runner().evaluate(expression));
354
+ }
355
+ /** Stage `bytes` in the page as base64 pieces and return the staging id. */
356
+ async function stageBytes(bytes) {
357
+ const id = randomUUID();
358
+ const base64 = Buffer.from(bytes).toString("base64");
359
+ for (let at = 0; at < base64.length; at += UPLOAD_PIECE_CHARS) {
360
+ const piece = base64.slice(at, at + UPLOAD_PIECE_CHARS);
361
+ await runner().evaluate(`(() => { window.__ntUp ??= {}; window.__ntUp[${JSON.stringify(id)}] = (window.__ntUp[${JSON.stringify(id)}] ?? "") + ${JSON.stringify(piece)}; return true; })()`);
362
+ }
363
+ await runner().evaluate(`(() => { window.__ntUp ??= {}; window.__ntUp[${JSON.stringify(id)}] ??= ""; return true; })()`);
364
+ return id;
365
+ }
366
+ const sha256Hex = (data) => createHash("sha256").update(data).digest("hex");
367
+ const hmac = (key, data) => createHmac("sha256", key).update(data).digest();
368
+ const encodeQuery = (value) => encodeURIComponent(value).replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`);
369
+ /** AWS Signature V4 headers for an ImageX or VOD call, which signs only the amz headers. */
370
+ function imagexHeaders(token, method, query, payload, service = "imagex") {
371
+ const amzDate = new Date().toISOString().replace(/[:-]|\.\d{3}/g, "");
372
+ const day = amzDate.slice(0, 8);
373
+ const region = !token.region || token.region.length <= 3 ? "ap-singapore-1" : token.region;
374
+ const headers = { "x-amz-date": amzDate, "x-amz-security-token": token.session_token };
375
+ if (method === "POST")
376
+ headers["x-amz-content-sha256"] = sha256Hex(payload);
377
+ const signedNames = Object.keys(headers).sort();
378
+ const canonicalQuery = Object.keys(query).sort().map((k) => `${encodeQuery(k)}=${encodeQuery(query[k])}`).join("&");
379
+ const canonicalHeaders = signedNames.map((n) => `${n}:${headers[n]}\n`).join("");
380
+ const canonical = [method, "/", canonicalQuery, canonicalHeaders, signedNames.join(";"), sha256Hex(payload)].join("\n");
381
+ const scope = `${day}/${region}/${service}/aws4_request`;
382
+ const toSign = ["AWS4-HMAC-SHA256", amzDate, scope, sha256Hex(canonical)].join("\n");
383
+ const key = hmac(hmac(hmac(hmac(`AWS4${token.secret_access_key}`, day), region), service), "aws4_request");
384
+ const signature = createHmac("sha256", key).update(toSign).digest("hex");
385
+ if (method === "POST") {
386
+ headers["Content-Type"] = "application/json";
387
+ }
388
+ return {
389
+ ...headers,
390
+ Authorization: `AWS4-HMAC-SHA256 Credential=${token.access_key_id}/${scope}, SignedHeaders=${signedNames.join(";")}, Signature=${signature}`
391
+ };
392
+ }
393
+ /** Upload an image through ImageX the way the web app does and return its store URI. */
394
+ function uploadImage(bytes) {
395
+ return retryOnTabLoss("image upload", () => uploadImageOnce(bytes));
396
+ }
397
+ async function uploadImageOnce(bytes) {
398
+ const token = await callApi("/mweb/v1/get_upload_token", { scene: 2 });
399
+ const account = (await runner().evaluate(`fetch("https://dreamina.capcut.com/passport/web/account/info/?aid=${APP_ID}&account_sdk_source=web&language=en", { credentials: "include" }).then((r) => r.json())`));
400
+ const userId = account.data?.user_id_str;
401
+ if (!userId)
402
+ throw new Error("Dreamina account info has no user id (is the tab logged in?)");
403
+ const applyQuery = { Action: "ApplyImageUpload", Version: "2018-08-01", ServiceId: token.space_name, FileSize: String(bytes.length), s: Math.random().toString(36).slice(2, 13), device_platform: "web" };
404
+ const applyUrl = `https://${token.upload_domain}/?${Object.entries(applyQuery).map(([k, v]) => `${encodeQuery(k)}=${encodeQuery(v)}`).join("&")}`;
405
+ const apply = await pageFetch({ url: applyUrl, method: "GET", headers: imagexHeaders(token, "GET", applyQuery, "") });
406
+ const address = JSON.parse(apply.text).Result?.UploadAddress;
407
+ const store = address?.StoreInfos?.[0];
408
+ const host = address?.UploadHosts?.[0];
409
+ if (apply.status !== 200 || !store || !host || !address?.SessionKey)
410
+ throw new Error(`Dreamina image upload was refused (ApplyImageUpload HTTP ${apply.status})`);
411
+ const staged = await stageBytes(bytes);
412
+ const upload = await pageFetch({
413
+ url: `https://${host}/upload/v1/${store.StoreUri}`,
414
+ method: "POST",
415
+ headers: {
416
+ Authorization: store.Auth,
417
+ "Content-CRC32": (crc32(bytes) >>> 0).toString(16).padStart(8, "0"),
418
+ "X-Storage-U": userId,
419
+ "Content-Type": "application/octet-stream",
420
+ "Content-Disposition": 'attachment; filename="undefined"'
421
+ },
422
+ body: { staged }
423
+ });
424
+ if (upload.status !== 200)
425
+ throw new Error(`Dreamina image upload failed (HTTP ${upload.status})`);
426
+ const commitQuery = { Action: "CommitImageUpload", Version: "2018-08-01", ServiceId: token.space_name };
427
+ const commitBody = JSON.stringify({ SessionKey: address.SessionKey });
428
+ const commit = await pageFetch({
429
+ url: `https://${token.upload_domain}/?${Object.entries(commitQuery).map(([k, v]) => `${encodeQuery(k)}=${encodeQuery(v)}`).join("&")}`,
430
+ method: "POST",
431
+ headers: imagexHeaders(token, "POST", commitQuery, commitBody),
432
+ body: { text: commitBody }
433
+ });
434
+ const result = JSON.parse(commit.text).Result?.PluginResult?.[0];
435
+ if (commit.status !== 200 || !result?.ImageUri)
436
+ throw new Error(`Dreamina image upload could not be committed (HTTP ${commit.status})`);
437
+ await callApi("/mweb/v1/imagex/submit_audit_job", { uri_list: [result.ImageUri] });
438
+ return { uri: result.ImageUri, width: result.ImageWidth, height: result.ImageHeight };
439
+ }
440
+ /** Upload a video or audio file through VOD the way the web app does and return its vid and size. VOD files audio as `video`. */
441
+ function uploadVideo(bytes) {
442
+ return retryOnTabLoss("video upload", () => uploadVideoOnce(bytes));
443
+ }
444
+ async function uploadVideoOnce(bytes) {
445
+ const token = await callApi("/mweb/v1/get_upload_token", { scene: 1 });
446
+ const account = (await runner().evaluate(`fetch("https://dreamina.capcut.com/passport/web/account/info/?aid=${APP_ID}&account_sdk_source=web&language=en", { credentials: "include" }).then((r) => r.json())`));
447
+ const userId = account.data?.user_id_str;
448
+ if (!userId)
449
+ throw new Error("Dreamina account info has no user id (is the tab logged in?)");
450
+ const applyQuery = { Action: "ApplyUploadInner", Version: "2020-11-19", SpaceName: token.space_name, FileType: "video", IsInner: "1", FileSize: String(bytes.length), s: Math.random().toString(36).slice(2, 13), device_platform: "web" };
451
+ const url = (query) => `https://${token.upload_domain}/?${Object.entries(query).map(([k, v]) => `${encodeQuery(k)}=${encodeQuery(v)}`).join("&")}`;
452
+ const apply = await pageFetch({ url: url(applyQuery), method: "GET", headers: imagexHeaders(token, "GET", applyQuery, "", "vod") });
453
+ const node = JSON.parse(apply.text).Result?.InnerUploadAddress?.UploadNodes?.[0];
454
+ const store = node?.StoreInfos?.[0];
455
+ if (apply.status !== 200 || !store || !node?.UploadHost || !node.SessionKey)
456
+ throw new Error(`Dreamina video upload was refused (ApplyUploadInner HTTP ${apply.status})`);
457
+ const staged = await stageBytes(bytes);
458
+ const upload = await pageFetch({
459
+ url: `https://${node.UploadHost}/upload/v1/${store.StoreUri}`,
460
+ method: "POST",
461
+ headers: {
462
+ Authorization: store.Auth,
463
+ "Content-CRC32": (crc32(bytes) >>> 0).toString(16).padStart(8, "0"),
464
+ "X-Storage-U": userId,
465
+ "Content-Type": "application/octet-stream",
466
+ "Content-Disposition": 'attachment; filename="undefined"'
467
+ },
468
+ body: { staged }
469
+ });
470
+ if (upload.status !== 200)
471
+ throw new Error(`Dreamina video upload failed (HTTP ${upload.status})`);
472
+ const commitQuery = { Action: "CommitUploadInner", Version: "2020-11-19", SpaceName: token.space_name };
473
+ const commitBody = JSON.stringify({ SessionKey: node.SessionKey, Functions: [{ name: "GetMeta" }] });
474
+ const commit = await pageFetch({
475
+ url: url(commitQuery),
476
+ method: "POST",
477
+ headers: imagexHeaders(token, "POST", commitQuery, commitBody, "vod"),
478
+ body: { text: commitBody }
479
+ });
480
+ const result = JSON.parse(commit.text).Result?.Results?.[0];
481
+ if (commit.status !== 200 || !result?.Vid)
482
+ throw new Error(`Dreamina video upload could not be committed (HTTP ${commit.status})`);
483
+ const meta = result.VideoMeta;
484
+ return { vid: result.Vid, width: meta?.Width ?? 0, height: meta?.Height ?? 0, durationMs: Math.round((meta?.Duration ?? 0) * 1000) };
485
+ }
486
+ /** Ask Dreamina's review whether it accepts these references for this model. */
487
+ async function auditReferences(model, images, videos = [], audios = []) {
488
+ const data = await callApi("/mweb/v1/execute_generate_audit", {
489
+ scene: 2,
490
+ model_key: model,
491
+ material_list: [
492
+ ...images.map((image) => ({ uri: image.uri, material_type: 1 })),
493
+ ...videos.map((video) => ({ vid: video.vid, material_type: 2 })),
494
+ ...audios.map((audio) => ({ vid: audio.vid, material_type: 3 }))
495
+ ]
496
+ });
497
+ const rejected = (data.result_list ?? []).findIndex((entry) => entry.audit_decision !== 1);
498
+ if (rejected >= 0) {
499
+ const kind = rejected < images.length ? `image ${rejected + 1}` : rejected < images.length + videos.length ? `video ${rejected - images.length + 1}` : `audio ${rejected - images.length - videos.length + 1}`;
500
+ throw new Error(`Dreamina review rejected reference ${kind}`);
501
+ }
502
+ }
503
+ /**
504
+ * Split a prompt on `[Image N]`, `[Video N]` and `[Audio N]` markers into the
505
+ * segments the site's editor produces: every image, video and audio leads,
506
+ * then the prompt follows with each marker as an inline mention of its
507
+ * material. A material's index counts images first, then videos, then audios.
508
+ */
509
+ function referenceMeta(prompt, imageCount, videoCount = 0, audioCount = 0) {
510
+ const counts = { image: imageCount, video: videoCount, audio: audioCount };
511
+ const offsets = { image: 0, video: imageCount, audio: imageCount + videoCount };
512
+ const media = (kind, index) => ({ type: "", id: randomUUID(), meta_type: kind, text: "", material_ref: { type: "", id: randomUUID(), material_idx: offsets[kind] + index } });
513
+ const text = (value) => ({ type: "", id: randomUUID(), meta_type: "text", text: value });
514
+ // Captured from the web app: the materials always lead, in order, even when
515
+ // the prompt mentions them again inline.
516
+ const parts = [
517
+ ...Array.from({ length: imageCount }, (_, i) => media("image", i)),
518
+ ...Array.from({ length: videoCount }, (_, i) => media("video", i)),
519
+ ...Array.from({ length: audioCount }, (_, i) => media("audio", i))
520
+ ];
521
+ let last = 0;
522
+ for (const match of prompt.matchAll(/\[(Image|Video|Audio) (\d+)\]/g)) {
523
+ const kind = match[1].toLowerCase();
524
+ const index = Number(match[2]) - 1;
525
+ if (index < 0 || index >= counts[kind])
526
+ throw new Error(`The prompt refers to [${match[1]} ${match[2]}] but only ${counts[kind]} reference ${kind}(s) were given`);
527
+ if (match.index > last)
528
+ parts.push(text(prompt.slice(last, match.index)));
529
+ parts.push(media(kind, index));
530
+ last = match.index + match[0].length;
531
+ }
532
+ if (last < prompt.length)
533
+ parts.push(text(prompt.slice(last)));
534
+ return parts;
535
+ }
536
+ function videoDraftFor(model, prompt, seed, ratio, resolution, durationMs, submitId, references = [], frames, videos = [], audios = []) {
537
+ const id = () => randomUUID();
538
+ // Seedance 2.5's 480p preview model renders in Dreamina's draft mode. The web
539
+ // app marks the input as a draft on the current schema version, and the API
540
+ // answers "invalid parameter" (ret 1000) without it.
541
+ const draftMode = model.endsWith("_draft");
542
+ const frameImage = (image) => ({ type: "image", source_from: "upload", platform_type: 1, name: "", image_uri: image.uri, aigc_image: {}, width: image.width, height: image.height, format: "", uri: image.uri });
543
+ const videoInput = {
544
+ type: "",
545
+ id: id(),
546
+ min_version: draftMode ? DA_VERSION : references.length + videos.length + audios.length > 0 ? "3.3.9" : "3.0.5",
547
+ prompt: references.length + videos.length + audios.length > 0 ? "" : prompt,
548
+ ...(draftMode && { is_draft_mode: true }),
549
+ video_mode: 2,
550
+ fps: VIDEO_FPS,
551
+ duration_ms: durationMs,
552
+ resolution,
553
+ idip_meta_list: [],
554
+ };
555
+ if (frames) {
556
+ videoInput.first_frame_image = frameImage(frames.first);
557
+ if (frames.last) {
558
+ videoInput.end_frame_image = frameImage(frames.last);
559
+ videoInput.ending_control = "1.0";
560
+ }
561
+ }
562
+ if (references.length + videos.length + audios.length > 0) {
563
+ videoInput.unified_edit_input = {
564
+ material_list: [...references.map((image) => ({
565
+ type: "",
566
+ id: id(),
567
+ material_type: "image",
568
+ image_info: { type: "image", source_from: "upload", platform_type: 1, name: "", image_uri: image.uri, aigc_image: {}, width: image.width, height: image.height, format: "", title: "", uri: image.uri }
569
+ })), ...videos.map((video) => ({
570
+ type: "",
571
+ id: id(),
572
+ material_type: "video",
573
+ video_info: { type: "video", source_from: "upload", name: "", vid: video.vid, fps: 0, width: video.width, height: video.height, duration: video.durationMs }
574
+ })), ...audios.map((audio) => ({
575
+ type: "",
576
+ id: id(),
577
+ material_type: "audio",
578
+ audio_info: { type: "audio", source_from: "upload", vid: audio.vid, duration: audio.durationMs, name: "" }
579
+ }))],
580
+ meta_list: referenceMeta(prompt, references.length, videos.length, audios.length)
581
+ };
582
+ }
583
+ const component = {
584
+ type: "video_base_component",
585
+ id: id(),
586
+ min_version: "1.0.0",
587
+ aigc_mode: "workbench",
588
+ metadata: { type: "", id: id(), created_platform: 3, created_platform_version: "", created_time_in_ms: String(Date.now()), created_did: "" },
589
+ generate_type: "gen_video",
590
+ abilities: {
591
+ type: "",
592
+ id: id(),
593
+ gen_video: {
594
+ type: "",
595
+ id: id(),
596
+ text_to_video_params: {
597
+ type: "",
598
+ id: id(),
599
+ video_gen_inputs: [videoInput],
600
+ video_aspect_ratio: ratio,
601
+ seed,
602
+ model_req_key: model,
603
+ priority: 0
604
+ },
605
+ video_task_extra: JSON.stringify({ isDefaultSeed: 1, originSubmitId: submitId, isRegenerate: false, enterFrom: "click", position: "page_bottom_box", promptType: "original_prompt", ...(frames ? { functionMode: "first_last_frames", generatorFeature: "firstLastFrames" } : { functionMode: "omni_reference", generatorFeature: "omniReference" }), batchNumber: 1, hasRejectedAudit: 0 })
606
+ }
607
+ },
608
+ process_type: 1
609
+ };
610
+ // Captured from the web app: reference input declares the unified-edit
611
+ // feature on the 3.3.9 schema, and every Seedance 2.5 model the 2.5 result
612
+ // action.
613
+ const unifiedEdit = references.length + videos.length + audios.length > 0;
614
+ const minFeatures = [
615
+ ...(unifiedEdit ? ["AIGC_Video_UnifiedEdit"] : []),
616
+ ...(model.includes("seedance_45") ? ["AIGC_Video_Seedance25ResultAction"] : [])
617
+ ];
618
+ const minVersion = draftMode ? DA_VERSION : unifiedEdit ? "3.3.9" : "3.0.5";
619
+ return JSON.stringify({ type: "draft", id: id(), min_version: minVersion, min_features: minFeatures, is_from_tsn: true, version: DA_VERSION, main_component_id: component.id, component_list: [component] });
620
+ }
621
+ /** Submit a generation and poll until it finishes, then return the finished record. */
622
+ async function submitAndAwait(body, submitId, timeoutMs, signal) {
623
+ // A lost tab leaves the submit's outcome unknown: Dreamina may already run
624
+ // (and bill) the job. Sending it again could pay twice, so the job is looked
625
+ // up by its submit id instead, and only a job that never appears fails.
626
+ let unconfirmed = null;
627
+ try {
628
+ await callApi(GENERATE_PATH, body);
629
+ }
630
+ catch (error) {
631
+ if (!tabLost(error) && !stalled(error))
632
+ throw error;
633
+ unconfirmed = error;
634
+ log.warn("Dreamina submit has no answer, looking the job up by id", { submitId, error: unconfirmed.message });
635
+ }
636
+ const started = Date.now();
637
+ const deadline = started + timeoutMs;
638
+ log.info("Dreamina generation submitted", { submitId, timeoutMs, confirmed: !unconfirmed });
639
+ let pollFailures = 0;
640
+ let lastStatus;
641
+ for (;;) {
642
+ await sleep(POLL_INTERVAL_MS, signal);
643
+ let history;
644
+ try {
645
+ history = await callApi("/mweb/v1/get_history_by_ids", { submit_ids: [submitId] });
646
+ }
647
+ catch (error) {
648
+ // Reading the history is idempotent, and Dreamina answers it with a
649
+ // transient error while a fresh task registers. The submit is never retried.
650
+ log.warn("Dreamina poll failed", { submitId, pollFailures: pollFailures + 1, error: error instanceof Error ? error.message : String(error) });
651
+ if (++pollFailures > MAX_POLL_FAILURES)
652
+ throw error;
653
+ if (Date.now() > deadline) {
654
+ throw new Error(`Dreamina generation ${submitId} timed out after ${timeoutMs / 1000}s (last task status ${lastStatus ?? "none"}, last poll failed: ${error instanceof Error ? error.message : String(error)})`);
655
+ }
656
+ continue;
657
+ }
658
+ pollFailures = 0;
659
+ const record = history[submitId];
660
+ if (record) {
661
+ unconfirmed = null;
662
+ }
663
+ else if (unconfirmed && Date.now() - started > UNCONFIRMED_SUBMIT_GRACE_MS) {
664
+ throw new Error(`Dreamina generation ${submitId} did not reach Dreamina before the tab was lost (${unconfirmed.message}). Keep the Dreamina tab open and try again`);
665
+ }
666
+ const status = record?.task?.status;
667
+ if (status !== lastStatus) {
668
+ log.info("Dreamina task status", { submitId, status, hasRecord: record !== undefined, elapsedMs: Date.now() - started });
669
+ lastStatus = status;
670
+ }
671
+ if (status === TASK_DONE)
672
+ return record;
673
+ if (status === TASK_FAILED) {
674
+ throw new Error(`Dreamina generation failed: ${record?.fail_msg || "unknown reason"} (fail_code ${record?.fail_code ?? "unknown"})`);
675
+ }
676
+ if (Date.now() > deadline) {
677
+ log.warn("Dreamina generation timed out", { submitId, lastStatus, record: JSON.stringify(record ?? null).slice(0, 2000) });
678
+ throw new Error(`Dreamina generation ${submitId} timed out after ${timeoutMs / 1000}s (last task status ${lastStatus ?? "none"})`);
679
+ }
680
+ }
681
+ }
682
+ async function downloadOnce(url, signal) {
683
+ const limit = AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS);
684
+ const combined = signal ? AbortSignal.any([signal, limit]) : limit;
685
+ const response = await fetchExternalMedia(url, { signal: combined });
686
+ if (!response.ok)
687
+ throw new Error(`Dreamina download failed: HTTP ${response.status}`);
688
+ return new Uint8Array(await response.arrayBuffer());
689
+ }
690
+ /** Fetch `url` inside the Dreamina tab, which can always reach Dreamina's CDN. */
691
+ async function downloadViaTab(url) {
692
+ const expression = `(async () => {
693
+ const response = await fetch(${JSON.stringify(url)}, { credentials: "omit", signal: AbortSignal.timeout(${DOWNLOAD_TIMEOUT_MS}) });
694
+ if (!response.ok) throw new Error("Dreamina download failed in the tab: HTTP " + response.status);
695
+ const bytes = new Uint8Array(await response.arrayBuffer());
696
+ let binary = "";
697
+ for (let i = 0; i < bytes.length; i += 0x8000) binary += String.fromCharCode.apply(null, bytes.subarray(i, i + 0x8000));
698
+ return btoa(binary);
699
+ })()`;
700
+ const base64 = (await evaluateBounded(expression, "the download", DOWNLOAD_TIMEOUT_MS + 15_000));
701
+ return new Uint8Array(Buffer.from(base64, "base64"));
702
+ }
703
+ /**
704
+ * Download a finished result. The job is paid for by now, so a slow or broken
705
+ * connection gets a second attempt, then the tab's own connection.
706
+ */
707
+ async function download(url, signal) {
708
+ let lastError;
709
+ for (let attempt = 1; attempt <= 2; attempt++) {
710
+ try {
711
+ return await downloadOnce(url, signal);
712
+ }
713
+ catch (error) {
714
+ if (signal?.aborted)
715
+ throw error;
716
+ lastError = error;
717
+ log.warn("Dreamina download failed", { attempt, error: error instanceof Error ? error.message : String(error) });
718
+ }
719
+ }
720
+ try {
721
+ return await downloadViaTab(url);
722
+ }
723
+ catch (error) {
724
+ log.warn("Dreamina download through the tab failed", { error: error instanceof Error ? error.message : String(error) });
725
+ throw new Error(`Dreamina download failed: ${lastError instanceof Error ? lastError.message : String(lastError)}. The result is still in your Dreamina history`);
726
+ }
727
+ }
728
+ function videoEnum(model, key) {
729
+ const option = model.options?.find((o) => o.key === key)?.enum_val;
730
+ const values = (option?.string_value ?? option?.int_value ?? []);
731
+ return { values, defaultIndex: option?.default_val_idx ?? 0 };
732
+ }
733
+ /** Seedance 2.x models list reference input as `unified_edit`, as an option or an input type. */
734
+ function takesReferences(model) {
735
+ return Boolean(model.options?.some((o) => o.key === "unified_edit")) || videoEnum(model, "input_media_type").values.includes("unified_edit");
736
+ }
737
+ /**
738
+ * Turn a start frame, an end frame and named references into omni-reference
739
+ * input: every image becomes a reference, and the prompt opens with one
740
+ * mention per image (`[Image 2] is Mara.`), which the draft turns into inline
741
+ * image mentions. The caller's own references keep their numbers, so markers
742
+ * already in the prompt still point at them. Named references past the
743
+ * model's limit drop.
744
+ */
745
+ function omniInputs(prompt, inputs) {
746
+ const references = [...inputs.references];
747
+ const mentions = [];
748
+ const mention = (image, says) => {
749
+ references.push(image);
750
+ mentions.push(says(`[Image ${references.length}]`));
751
+ };
752
+ if (inputs.first)
753
+ mention(inputs.first, (m) => `${m} is the first frame.`);
754
+ if (inputs.last)
755
+ mention(inputs.last, (m) => `${m} is the last frame.`);
756
+ for (const named of inputs.named ?? []) {
757
+ if (references.length >= MAX_REFERENCE_IMAGES) {
758
+ log.warn("Dreamina reference limit reached, dropping a named reference", { name: named.name, limit: MAX_REFERENCE_IMAGES });
759
+ continue;
760
+ }
761
+ mention(named.image, (m) => (named.name ? `${m} is ${named.name}.` : `${m} is a reference.`));
762
+ }
763
+ return { prompt: [mentions.join(" "), prompt].filter((part) => part.trim().length > 0).join("\n\n"), references };
764
+ }
765
+ function videoInputTypes(model) {
766
+ return videoEnum(model, "input_media_type").values.map(String);
767
+ }
768
+ /** Models that list `first_frame` among their input types take a start image. */
769
+ function takesFirstFrame(model) {
770
+ return videoInputTypes(model).includes("first_frame");
771
+ }
772
+ function videoDurations(model) {
773
+ return videoEnum(model, "frames").values.map((frames) => Number(frames) / VIDEO_FPS);
774
+ }
775
+ function sleep(ms, signal) {
776
+ return new Promise((resolve, reject) => {
777
+ if (signal?.aborted)
778
+ return reject(new Error("Dreamina generation aborted"));
779
+ const timer = setTimeout(resolve, ms);
780
+ signal?.addEventListener("abort", () => { clearTimeout(timer); reject(new Error("Dreamina generation aborted")); }, { once: true });
781
+ });
782
+ }
783
+ /**
784
+ * Dreamina (CapCut) image generation through the user's own logged-in Chrome.
785
+ * Calls the same web API the site uses, from inside the attached Dreamina tab,
786
+ * so the account's cookies and credits apply. Local installs only.
787
+ */
788
+ export class DreaminaProvider extends BaseProvider {
789
+ configCache = null;
790
+ videoConfigCache = null;
791
+ music = createDreaminaMusic({ callApi, submitAndAwait, download, appId: Number(APP_ID), daVersion: DA_VERSION });
792
+ declaredCapabilities() {
793
+ return ["text_to_image", "image_to_image", "text_to_video", "image_to_video", "reference_to_video", "text_to_music"];
794
+ }
795
+ static requiredSecrets() { return []; }
796
+ constructor(_secrets = {}) { super(PROVIDER_IDS.DREAMINA); }
797
+ async generateMessage(_args) { throw new Error("dreamina does not support chat generation"); }
798
+ // eslint-disable-next-line require-yield
799
+ async *generateMessages(_args) { throw new Error("dreamina does not support chat generation"); }
800
+ async imageConfigs() {
801
+ if (this.configCache && Date.now() - this.configCache.at < CONFIG_TTL_MS)
802
+ return this.configCache.models;
803
+ const data = await callApi("/mweb/v1/get_common_config", {});
804
+ const models = (data.model_list ?? []).filter((m) => m.feats?.includes("t2i"));
805
+ this.configCache = { at: Date.now(), models };
806
+ return models;
807
+ }
808
+ /** Served from the captured catalog, so listing never waits on the browser tab. */
809
+ async getAvailableImageModels() {
810
+ return [...DREAMINA_IMAGE_MODELS];
811
+ }
812
+ async imageConfig(modelId) {
813
+ const config = (await this.imageConfigs()).find((m) => m.model_req_key === modelId);
814
+ if (!config)
815
+ throw new Error(`Unknown Dreamina image model: ${modelId}`);
816
+ return config;
817
+ }
818
+ /** Pick the resolution, ratio and pixel size a model offers for a request. */
819
+ imageSize(config, requestedResolution, ratio) {
820
+ const resolutions = Object.keys(config.resolution_map ?? {});
821
+ const requested = requestedResolution?.toLowerCase();
822
+ const resolution = requested && resolutions.includes(requested)
823
+ ? requested
824
+ : config.default_resolution_type || (resolutions.includes("2k") ? "2k" : resolutions[0]);
825
+ if (!resolution)
826
+ throw new Error(`Dreamina model ${config.model_name} lists no resolutions`);
827
+ const ratioType = RATIO_TYPES[ratio];
828
+ const size = config.resolution_map?.[resolution]?.image_ratio_sizes?.find((s) => s.ratio_type === ratioType);
829
+ if (!size)
830
+ throw new Error(`Dreamina model ${config.model_name} does not offer ${ratio} at ${resolution}`);
831
+ return { resolution, ratioType, size };
832
+ }
833
+ async submitImage(config, draft, signal) {
834
+ const submitId = randomUUID();
835
+ const record = await submitAndAwait({
836
+ extend: { root_model: config.model_req_key },
837
+ submit_id: submitId,
838
+ metrics_extra: JSON.stringify({ promptSource: "custom", generateCount: 1, enterFrom: "click", position: "page_bottom_box", isBoxSelect: false, isCutout: false, hasRejectedAudit: 0, generateId: submitId, isRegenerate: false }),
839
+ draft_content: draft(submitId),
840
+ http_common_info: { aid: Number(APP_ID) }
841
+ }, submitId, DEFAULT_TIMEOUT_MS, signal ?? undefined);
842
+ const url = record.item_list?.[0]?.image?.large_images?.[0]?.image_url;
843
+ if (!url)
844
+ throw new Error("Dreamina finished without an image (the prompt may have been rejected by review)");
845
+ log.debug("Dreamina image generation completed", { model: config.model_req_key, submitId });
846
+ return download(url, signal ?? undefined);
847
+ }
848
+ async textToImage(params) {
849
+ const config = await this.imageConfig(params.model.id);
850
+ const { resolution, ratioType, size } = this.imageSize(config, params.resolution, aspectRatioFor(params));
851
+ const seed = params.seed ?? Math.floor(Math.random() * 4_000_000_000);
852
+ return this.submitImage(config, () => draftFor(config.model_req_key, params.prompt, params.negativePrompt ?? "", seed, ratioType, size, resolution), params.signal);
853
+ }
854
+ /**
855
+ * Image-to-image through the site's "blend" mode: the source images guide a
856
+ * new image that follows the prompt. `strength` is Dreamina's reference
857
+ * strength (0 to 1, default 0.5). Without a ratio the output follows the
858
+ * first source image. Only models that list `byte_edit` take sources.
859
+ */
860
+ async imageToImage(images, params) {
861
+ if (images.length === 0)
862
+ throw new Error("imageToImage needs at least one source image");
863
+ if (images.length > MAX_REFERENCE_IMAGES)
864
+ throw new Error(`Dreamina takes at most ${MAX_REFERENCE_IMAGES} source images, not ${images.length}`);
865
+ const config = await this.imageConfig(params.model.id);
866
+ if (!config.feats?.includes("byte_edit"))
867
+ throw new Error(`Dreamina model ${config.model_name} does not take source images`);
868
+ const sources = [];
869
+ for (const image of images)
870
+ sources.push(await uploadImage(image));
871
+ await auditReferences(config.model_req_key, sources);
872
+ const ratio = aspectRatioFor({ aspectRatio: params.aspectRatio, width: params.targetWidth, height: params.targetHeight }, nearestRatio(sources[0].width, sources[0].height));
873
+ const { resolution, ratioType, size } = this.imageSize(config, params.resolution, ratio);
874
+ const seed = params.seed ?? Math.floor(Math.random() * 4_000_000_000);
875
+ const strength = Math.min(1, Math.max(0, params.strength ?? 0.5));
876
+ return this.submitImage(config, () => blendDraftFor(config.model_req_key, params.prompt, params.negativePrompt ?? "", seed, strength, ratioType, size, resolution, sources), params.signal);
877
+ }
878
+ async videoConfigs() {
879
+ if (this.videoConfigCache && Date.now() - this.videoConfigCache.at < CONFIG_TTL_MS)
880
+ return this.videoConfigCache.models;
881
+ const data = await callApi("/mweb/v1/video_generate/get_common_config", {});
882
+ const models = data.model_list ?? [];
883
+ this.videoConfigCache = { at: Date.now(), models };
884
+ return models;
885
+ }
886
+ async getAvailableVideoModels() {
887
+ return [...DREAMINA_VIDEO_MODELS];
888
+ }
889
+ async textToVideo(params) {
890
+ return this.generateVideo(params, { references: [], named: params.references ?? [] });
891
+ }
892
+ /**
893
+ * Image-to-video. Models that take references (Seedance 2.x) always render
894
+ * in "omni reference" mode: the image, `endImage` and `references` all go in
895
+ * as references, and the prompt names each one, starting with
896
+ * `[Image 1] is the first frame.` Older models use "first and last frames"
897
+ * mode, where `endImage` needs `end_frame` and references are ignored.
898
+ */
899
+ async imageToVideo(image, params) {
900
+ return this.generateVideo({ ...params, prompt: params.prompt ?? "" }, { references: [], first: image, last: params.endImage ?? undefined, named: params.references ?? [] });
901
+ }
902
+ /**
903
+ * Reference-to-video through Dreamina's "omni reference" mode: the images
904
+ * condition the clip, and `[Image N]`, `[Video N]` and `[Audio N]` markers in
905
+ * the prompt place them in the text. Without markers the images and videos lead and the
906
+ * prompt follows. A reference video supplies motion or camera work and a
907
+ * reference audio supplies the sound. Audio needs at least one image or video
908
+ * beside it. Only the Seedance 2.x models take references.
909
+ */
910
+ async referenceToVideo(inputs, params) {
911
+ const audios = inputs.audios ?? [];
912
+ if (inputs.images.length + inputs.videos.length === 0)
913
+ throw new Error(audios.length > 0 ? "A reference audio needs at least one reference image or video beside it" : "referenceToVideo needs at least one reference image or video");
914
+ if (audios.length > MAX_REFERENCE_AUDIOS)
915
+ throw new Error(`Dreamina takes at most ${MAX_REFERENCE_AUDIOS} reference audios, not ${audios.length}`);
916
+ if (inputs.images.length > MAX_REFERENCE_IMAGES)
917
+ throw new Error(`Dreamina takes at most ${MAX_REFERENCE_IMAGES} reference images, not ${inputs.images.length}`);
918
+ if (inputs.videos.length > MAX_REFERENCE_VIDEOS)
919
+ throw new Error(`Dreamina takes at most ${MAX_REFERENCE_VIDEOS} reference videos, not ${inputs.videos.length}`);
920
+ return this.generateVideo(params, { references: inputs.images, videos: inputs.videos, audios, named: params.references ?? undefined });
921
+ }
922
+ async getAvailableMusicModels() {
923
+ return this.music.models();
924
+ }
925
+ /** Instrumental tracks need no model key. Vocal models appear only where the account lists them. */
926
+ async textToMusic(params) {
927
+ return this.music.generate(params);
928
+ }
929
+ async generateVideo(params, requested) {
930
+ const config = (await this.videoConfigs()).find((m) => m.model_req_key === params.model.id);
931
+ if (!config)
932
+ throw new Error(`Unknown Dreamina video model: ${params.model.id}`);
933
+ // Models that take references always render in omni-reference mode, so a
934
+ // start frame sits beside the entity images instead of excluding them.
935
+ const omni = takesReferences(config) && Boolean(requested.first || requested.named?.length)
936
+ ? omniInputs(params.prompt, requested)
937
+ : null;
938
+ if (!omni && requested.named?.length) {
939
+ log.debug("Dreamina model takes no references, ignoring them", { model: config.model_req_key, count: requested.named.length });
940
+ }
941
+ const inputs = omni ? { ...requested, references: omni.references, first: undefined, last: undefined } : requested;
942
+ const prompt = omni?.prompt ?? params.prompt;
943
+ const resolutions = videoEnum(config, "resolution");
944
+ const resolution = params.resolution
945
+ ? resolutions.values.map(String).find((r) => r.toLowerCase() === params.resolution.toLowerCase())
946
+ : String(resolutions.values[resolutions.defaultIndex] ?? resolutions.values[0] ?? "");
947
+ if (!resolution)
948
+ throw new Error(`Dreamina model ${config.model_name} does not offer ${params.resolution}. Use one of ${resolutions.values.join(", ")}`);
949
+ const ratios = videoEnum(config, "video_aspect_ratio");
950
+ const ratio = params.aspectRatio ?? String(ratios.values[ratios.defaultIndex] ?? "16:9");
951
+ if (!ratios.values.includes(ratio))
952
+ throw new Error(`Dreamina model ${config.model_name} does not offer ${ratio}. Use one of ${ratios.values.join(", ")}`);
953
+ const frames = videoEnum(config, "frames");
954
+ const durations = videoDurations(config);
955
+ const seconds = params.durationSeconds ?? Number(frames.values[frames.defaultIndex]) / VIDEO_FPS;
956
+ if (!durations.includes(seconds))
957
+ throw new Error(`Dreamina model ${config.model_name} offers ${durations.join(", ")} second clips, not ${seconds}`);
958
+ const videoInputs = inputs.videos ?? [];
959
+ const audioInputs = inputs.audios ?? [];
960
+ if (inputs.references.length + videoInputs.length + audioInputs.length > 0 && !takesReferences(config)) {
961
+ throw new Error(`Dreamina model ${config.model_name} does not take reference ${inputs.references.length > 0 ? "images" : videoInputs.length > 0 ? "videos" : "audio"}. Use a Seedance 2.x model`);
962
+ }
963
+ if (inputs.first && !takesFirstFrame(config))
964
+ throw new Error(`Dreamina model ${config.model_name} does not take a first frame image`);
965
+ if (inputs.last && !videoInputTypes(config).includes("end_frame"))
966
+ throw new Error(`Dreamina model ${config.model_name} does not take a last frame image`);
967
+ const references = [];
968
+ for (const image of inputs.references)
969
+ references.push(await uploadImage(image));
970
+ const videos = [];
971
+ for (const video of videoInputs)
972
+ videos.push(await uploadVideo(video));
973
+ const audios = [];
974
+ for (const audio of audioInputs)
975
+ audios.push(await uploadVideo(audio));
976
+ let bounds;
977
+ if (inputs.first) {
978
+ bounds = { first: await uploadImage(inputs.first) };
979
+ if (inputs.last)
980
+ bounds.last = await uploadImage(inputs.last);
981
+ }
982
+ const audited = [...references, ...(bounds ? [bounds.first, ...(bounds.last ? [bounds.last] : [])] : [])];
983
+ if (audited.length + videos.length + audios.length > 0)
984
+ await auditReferences(config.model_req_key, audited, videos, audios);
985
+ const submitId = randomUUID();
986
+ const seed = params.seed ?? Math.floor(Math.random() * 4_000_000_000);
987
+ const record = await submitAndAwait({
988
+ extend: { root_model: config.model_req_key },
989
+ submit_id: submitId,
990
+ metrics_extra: JSON.stringify({ isDefaultSeed: 1, originSubmitId: submitId, isRegenerate: false }),
991
+ draft_content: videoDraftFor(config.model_req_key, prompt, seed, ratio, resolution, seconds * 1000, submitId, references, bounds, videos, audios),
992
+ http_common_info: { aid: Number(APP_ID) }
993
+ }, submitId, (params.timeoutSeconds ?? VIDEO_TIMEOUT_MS / 1000) * 1000, params.signal);
994
+ const url = record.item_list?.[0]?.video?.transcoded_video?.origin?.video_url;
995
+ if (!url)
996
+ throw new Error("Dreamina finished without a video");
997
+ log.debug("Dreamina video generation completed", { model: config.model_req_key, submitId });
998
+ return download(url, params.signal);
999
+ }
1000
+ }
1001
+ //# sourceMappingURL=dreamina-provider.js.map