@nodetool-ai/runtime 0.8.1 → 0.8.2

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 (256) 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 +366 -158
  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 +15 -0
  18. package/dist/image-model-tasks.d.ts.map +1 -0
  19. package/dist/image-model-tasks.js +24 -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/providers/aki-provider.d.ts +2 -0
  32. package/dist/providers/aki-provider.d.ts.map +1 -1
  33. package/dist/providers/aki-provider.js +6 -0
  34. package/dist/providers/aki-provider.js.map +1 -1
  35. package/dist/providers/atlascloud-provider.d.ts +2 -0
  36. package/dist/providers/atlascloud-provider.d.ts.map +1 -1
  37. package/dist/providers/atlascloud-provider.js +22 -0
  38. package/dist/providers/atlascloud-provider.js.map +1 -1
  39. package/dist/providers/base-provider.d.ts +36 -24
  40. package/dist/providers/base-provider.d.ts.map +1 -1
  41. package/dist/providers/base-provider.js +124 -148
  42. package/dist/providers/base-provider.js.map +1 -1
  43. package/dist/providers/claude-agent-provider.d.ts +6 -36
  44. package/dist/providers/claude-agent-provider.d.ts.map +1 -1
  45. package/dist/providers/claude-agent-provider.js +208 -26
  46. package/dist/providers/claude-agent-provider.js.map +1 -1
  47. package/dist/providers/codex-provider.d.ts +2 -0
  48. package/dist/providers/codex-provider.d.ts.map +1 -1
  49. package/dist/providers/codex-provider.js +15 -2
  50. package/dist/providers/codex-provider.js.map +1 -1
  51. package/dist/providers/cohere-provider.d.ts +2 -1
  52. package/dist/providers/cohere-provider.d.ts.map +1 -1
  53. package/dist/providers/cohere-provider.js +5 -0
  54. package/dist/providers/cohere-provider.js.map +1 -1
  55. package/dist/providers/core-tools.d.ts +2 -1
  56. package/dist/providers/core-tools.d.ts.map +1 -1
  57. package/dist/providers/core-tools.js +2 -1
  58. package/dist/providers/core-tools.js.map +1 -1
  59. package/dist/providers/credential-check.d.ts.map +1 -1
  60. package/dist/providers/credential-check.js +5 -0
  61. package/dist/providers/credential-check.js.map +1 -1
  62. package/dist/providers/custom-openai-provider.d.ts +2 -0
  63. package/dist/providers/custom-openai-provider.d.ts.map +1 -1
  64. package/dist/providers/custom-openai-provider.js +8 -0
  65. package/dist/providers/custom-openai-provider.js.map +1 -1
  66. package/dist/providers/dreamina-models.d.ts +5 -0
  67. package/dist/providers/dreamina-models.d.ts.map +1 -0
  68. package/dist/providers/dreamina-models.js +57 -0
  69. package/dist/providers/dreamina-models.js.map +1 -0
  70. package/dist/providers/dreamina-music.d.ts +52 -0
  71. package/dist/providers/dreamina-music.d.ts.map +1 -0
  72. package/dist/providers/dreamina-music.js +115 -0
  73. package/dist/providers/dreamina-music.js.map +1 -0
  74. package/dist/providers/dreamina-provider.d.ts +62 -0
  75. package/dist/providers/dreamina-provider.d.ts.map +1 -0
  76. package/dist/providers/dreamina-provider.js +790 -0
  77. package/dist/providers/dreamina-provider.js.map +1 -0
  78. package/dist/providers/elevenlabs-provider.d.ts +2 -1
  79. package/dist/providers/elevenlabs-provider.d.ts.map +1 -1
  80. package/dist/providers/elevenlabs-provider.js +5 -0
  81. package/dist/providers/elevenlabs-provider.js.map +1 -1
  82. package/dist/providers/evolink-provider.d.ts +2 -0
  83. package/dist/providers/evolink-provider.d.ts.map +1 -1
  84. package/dist/providers/evolink-provider.js +8 -0
  85. package/dist/providers/evolink-provider.js.map +1 -1
  86. package/dist/providers/fake-provider.d.ts +2 -1
  87. package/dist/providers/fake-provider.d.ts.map +1 -1
  88. package/dist/providers/fake-provider.js +10 -0
  89. package/dist/providers/fake-provider.js.map +1 -1
  90. package/dist/providers/fal-provider.d.ts +2 -1
  91. package/dist/providers/fal-provider.d.ts.map +1 -1
  92. package/dist/providers/fal-provider.js +27 -0
  93. package/dist/providers/fal-provider.js.map +1 -1
  94. package/dist/providers/gemini-provider.d.ts +2 -1
  95. package/dist/providers/gemini-provider.d.ts.map +1 -1
  96. package/dist/providers/gemini-provider.js +15 -0
  97. package/dist/providers/gemini-provider.js.map +1 -1
  98. package/dist/providers/higgsfield-provider.d.ts +2 -1
  99. package/dist/providers/higgsfield-provider.d.ts.map +1 -1
  100. package/dist/providers/higgsfield-provider.js +12 -0
  101. package/dist/providers/higgsfield-provider.js.map +1 -1
  102. package/dist/providers/huggingface-provider.d.ts +2 -1
  103. package/dist/providers/huggingface-provider.d.ts.map +1 -1
  104. package/dist/providers/huggingface-provider.js +10 -0
  105. package/dist/providers/huggingface-provider.js.map +1 -1
  106. package/dist/providers/index.d.ts +5 -2
  107. package/dist/providers/index.d.ts.map +1 -1
  108. package/dist/providers/index.js +13 -2
  109. package/dist/providers/index.js.map +1 -1
  110. package/dist/providers/jina-provider.d.ts +2 -1
  111. package/dist/providers/jina-provider.d.ts.map +1 -1
  112. package/dist/providers/jina-provider.js +5 -0
  113. package/dist/providers/jina-provider.js.map +1 -1
  114. package/dist/providers/kie-provider.d.ts +2 -1
  115. package/dist/providers/kie-provider.d.ts.map +1 -1
  116. package/dist/providers/kie-provider.js +14 -0
  117. package/dist/providers/kie-provider.js.map +1 -1
  118. package/dist/providers/llama-provider.d.ts +4 -1
  119. package/dist/providers/llama-provider.d.ts.map +1 -1
  120. package/dist/providers/llama-provider.js +9 -3
  121. package/dist/providers/llama-provider.js.map +1 -1
  122. package/dist/providers/loop-tracing.d.ts +17 -0
  123. package/dist/providers/loop-tracing.d.ts.map +1 -0
  124. package/dist/providers/loop-tracing.js +132 -0
  125. package/dist/providers/loop-tracing.js.map +1 -0
  126. package/dist/providers/meshy-provider.d.ts +2 -1
  127. package/dist/providers/meshy-provider.d.ts.map +1 -1
  128. package/dist/providers/meshy-provider.js +6 -0
  129. package/dist/providers/meshy-provider.js.map +1 -1
  130. package/dist/providers/minimax-provider.d.ts +2 -0
  131. package/dist/providers/minimax-provider.d.ts.map +1 -1
  132. package/dist/providers/minimax-provider.js +11 -0
  133. package/dist/providers/minimax-provider.js.map +1 -1
  134. package/dist/providers/mistral-provider.d.ts +2 -0
  135. package/dist/providers/mistral-provider.d.ts.map +1 -1
  136. package/dist/providers/mistral-provider.js +5 -0
  137. package/dist/providers/mistral-provider.js.map +1 -1
  138. package/dist/providers/node-llama-cpp-provider.d.ts +2 -1
  139. package/dist/providers/node-llama-cpp-provider.d.ts.map +1 -1
  140. package/dist/providers/node-llama-cpp-provider.js +8 -3
  141. package/dist/providers/node-llama-cpp-provider.js.map +1 -1
  142. package/dist/providers/nodetool-provider.d.ts +1 -1
  143. package/dist/providers/nodetool-provider.d.ts.map +1 -1
  144. package/dist/providers/nodetool-provider.js +0 -2
  145. package/dist/providers/nodetool-provider.js.map +1 -1
  146. package/dist/providers/ollama-provider.d.ts +2 -1
  147. package/dist/providers/ollama-provider.d.ts.map +1 -1
  148. package/dist/providers/ollama-provider.js +5 -0
  149. package/dist/providers/ollama-provider.js.map +1 -1
  150. package/dist/providers/openai-provider.d.ts +10 -2
  151. package/dist/providers/openai-provider.d.ts.map +1 -1
  152. package/dist/providers/openai-provider.js +63 -16
  153. package/dist/providers/openai-provider.js.map +1 -1
  154. package/dist/providers/openrouter-provider.d.ts +52 -1
  155. package/dist/providers/openrouter-provider.d.ts.map +1 -1
  156. package/dist/providers/openrouter-provider.js +372 -11
  157. package/dist/providers/openrouter-provider.js.map +1 -1
  158. package/dist/providers/provider-error.d.ts.map +1 -1
  159. package/dist/providers/provider-error.js +7 -1
  160. package/dist/providers/provider-error.js.map +1 -1
  161. package/dist/providers/provider-generations.d.ts +2 -2
  162. package/dist/providers/provider-generations.js +2 -2
  163. package/dist/providers/provider-request-log.d.ts.map +1 -1
  164. package/dist/providers/provider-request-log.js +18 -7
  165. package/dist/providers/provider-request-log.js.map +1 -1
  166. package/dist/providers/python-provider.d.ts +6 -1
  167. package/dist/providers/python-provider.d.ts.map +1 -1
  168. package/dist/providers/python-provider.js +30 -11
  169. package/dist/providers/python-provider.js.map +1 -1
  170. package/dist/providers/replicate-provider.d.ts +2 -1
  171. package/dist/providers/replicate-provider.d.ts.map +1 -1
  172. package/dist/providers/replicate-provider.js +22 -0
  173. package/dist/providers/replicate-provider.js.map +1 -1
  174. package/dist/providers/requesty-provider.d.ts +30 -0
  175. package/dist/providers/requesty-provider.d.ts.map +1 -0
  176. package/dist/providers/requesty-provider.js +87 -0
  177. package/dist/providers/requesty-provider.js.map +1 -0
  178. package/dist/providers/reve-provider.d.ts +2 -1
  179. package/dist/providers/reve-provider.d.ts.map +1 -1
  180. package/dist/providers/reve-provider.js +6 -0
  181. package/dist/providers/reve-provider.js.map +1 -1
  182. package/dist/providers/rodin-provider.d.ts +2 -1
  183. package/dist/providers/rodin-provider.d.ts.map +1 -1
  184. package/dist/providers/rodin-provider.js +6 -0
  185. package/dist/providers/rodin-provider.js.map +1 -1
  186. package/dist/providers/together-operations.d.ts +27 -0
  187. package/dist/providers/together-operations.d.ts.map +1 -0
  188. package/dist/providers/together-operations.js +95 -0
  189. package/dist/providers/together-operations.js.map +1 -0
  190. package/dist/providers/together-provider.d.ts +5 -8
  191. package/dist/providers/together-provider.d.ts.map +1 -1
  192. package/dist/providers/together-provider.js +54 -159
  193. package/dist/providers/together-provider.js.map +1 -1
  194. package/dist/providers/topaz-provider.d.ts +2 -1
  195. package/dist/providers/topaz-provider.d.ts.map +1 -1
  196. package/dist/providers/topaz-provider.js +5 -0
  197. package/dist/providers/topaz-provider.js.map +1 -1
  198. package/dist/providers/types.d.ts +38 -0
  199. package/dist/providers/types.d.ts.map +1 -1
  200. package/dist/providers/types.js.map +1 -1
  201. package/dist/providers/useapi-provider.d.ts +2 -1
  202. package/dist/providers/useapi-provider.d.ts.map +1 -1
  203. package/dist/providers/useapi-provider.js +8 -0
  204. package/dist/providers/useapi-provider.js.map +1 -1
  205. package/dist/providers/vllm-provider.d.ts +2 -4
  206. package/dist/providers/vllm-provider.d.ts.map +1 -1
  207. package/dist/providers/vllm-provider.js +1 -5
  208. package/dist/providers/vllm-provider.js.map +1 -1
  209. package/dist/providers/voyage-provider.d.ts +2 -1
  210. package/dist/providers/voyage-provider.d.ts.map +1 -1
  211. package/dist/providers/voyage-provider.js +5 -0
  212. package/dist/providers/voyage-provider.js.map +1 -1
  213. package/dist/providers/xai-provider.d.ts +2 -0
  214. package/dist/providers/xai-provider.d.ts.map +1 -1
  215. package/dist/providers/xai-provider.js +8 -0
  216. package/dist/providers/xai-provider.js.map +1 -1
  217. package/dist/recommended-models.d.ts.map +1 -1
  218. package/dist/recommended-models.js +20 -0
  219. package/dist/recommended-models.js.map +1 -1
  220. package/dist/run-context.d.ts +20 -0
  221. package/dist/run-context.d.ts.map +1 -0
  222. package/dist/run-context.js +18 -0
  223. package/dist/run-context.js.map +1 -0
  224. package/dist/run-trace-context.d.ts +30 -0
  225. package/dist/run-trace-context.d.ts.map +1 -0
  226. package/dist/run-trace-context.js +80 -0
  227. package/dist/run-trace-context.js.map +1 -0
  228. package/dist/run-trace-processor.d.ts +65 -0
  229. package/dist/run-trace-processor.d.ts.map +1 -0
  230. package/dist/run-trace-processor.js +417 -0
  231. package/dist/run-trace-processor.js.map +1 -0
  232. package/dist/run-trace-serialization.d.ts +5 -0
  233. package/dist/run-trace-serialization.d.ts.map +1 -0
  234. package/dist/run-trace-serialization.js +113 -0
  235. package/dist/run-trace-serialization.js.map +1 -0
  236. package/dist/storage-workspace.d.ts +3 -0
  237. package/dist/storage-workspace.d.ts.map +1 -1
  238. package/dist/storage-workspace.js +32 -17
  239. package/dist/storage-workspace.js.map +1 -1
  240. package/dist/telemetry.d.ts +9 -3
  241. package/dist/telemetry.d.ts.map +1 -1
  242. package/dist/telemetry.js +53 -20
  243. package/dist/telemetry.js.map +1 -1
  244. package/dist/trace-exporters.d.ts +2 -29
  245. package/dist/trace-exporters.d.ts.map +1 -1
  246. package/dist/trace-exporters.js +10 -2
  247. package/dist/trace-exporters.js.map +1 -1
  248. package/dist/tracing-helpers.d.ts +15 -22
  249. package/dist/tracing-helpers.d.ts.map +1 -1
  250. package/dist/tracing-helpers.js +38 -6
  251. package/dist/tracing-helpers.js.map +1 -1
  252. package/dist/trim-video-window.d.ts +8 -0
  253. package/dist/trim-video-window.d.ts.map +1 -0
  254. package/dist/trim-video-window.js +50 -0
  255. package/dist/trim-video-window.js.map +1 -0
  256. package/package.json +19 -5
@@ -0,0 +1,790 @@
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
+ const API_BASE = "https://mweb-api-sg.capcut.com";
80
+ const APP_ID = "513641";
81
+ const APP_VERSION = "8.4.0";
82
+ const DA_VERSION = "3.3.28";
83
+ const WEB_VERSION = "7.5.0";
84
+ const POLL_INTERVAL_MS = 2500;
85
+ const MAX_POLL_FAILURES = 5;
86
+ const DEFAULT_TIMEOUT_MS = 5 * 60_000;
87
+ const VIDEO_TIMEOUT_MS = 10 * 60_000;
88
+ const CONFIG_TTL_MS = 5 * 60_000;
89
+ const VIDEO_FPS = 24;
90
+ /** Dreamina's `image_ratio` enum, in the order the web app numbers it. */
91
+ const RATIO_TYPES = {
92
+ "1:1": 1,
93
+ "3:4": 2,
94
+ "16:9": 3,
95
+ "4:3": 4,
96
+ "9:16": 5,
97
+ "2:3": 6,
98
+ "3:2": 7,
99
+ "21:9": 8
100
+ };
101
+ /** `task.status` values returned by `get_history_by_ids`. */
102
+ const TASK_DONE = 50;
103
+ const TASK_FAILED = 30;
104
+ function region() {
105
+ const fromEnv = process.env.DREAMINA_REGION;
106
+ if (fromEnv)
107
+ return fromEnv.toUpperCase();
108
+ return new Intl.DateTimeFormat().resolvedOptions().locale.split("-")[1]?.toUpperCase() ?? "US";
109
+ }
110
+ /** The web app signs each call as md5 over the path tail, platform, version and clock. */
111
+ function signedHeaders(path) {
112
+ const deviceTime = String(Math.floor(Date.now() / 1000));
113
+ const sign = createHash("md5").update(`9e2c|${path.slice(-7)}|7|${APP_VERSION}|${deviceTime}||11ac`).digest("hex");
114
+ const loc = region();
115
+ return {
116
+ "Content-Type": "application/json",
117
+ sign,
118
+ "device-time": deviceTime,
119
+ tdid: "",
120
+ "sign-ver": "1",
121
+ pf: "7",
122
+ appvr: APP_VERSION,
123
+ loc,
124
+ lan: "en",
125
+ appid: APP_ID
126
+ };
127
+ }
128
+ async function callApi(path, body) {
129
+ 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" });
130
+ const request = { url: `${API_BASE}${path}?${query}`, headers: signedHeaders(path), body: JSON.stringify(body) };
131
+ const expression = `(() => { const a = ${JSON.stringify(request)};
132
+ return new Promise((resolve, reject) => {
133
+ const x = new XMLHttpRequest();
134
+ x.open("POST", a.url);
135
+ x.withCredentials = true;
136
+ for (const k of Object.keys(a.headers)) x.setRequestHeader(k, a.headers[k]);
137
+ x.onload = () => { try { resolve(JSON.parse(x.responseText)); } catch { reject(new Error("Dreamina returned a non-JSON response (HTTP " + x.status + ")")); } };
138
+ x.onerror = () => reject(new Error("Dreamina request failed"));
139
+ x.send(a.body);
140
+ });
141
+ })()`;
142
+ const response = (await runner().evaluate(expression));
143
+ // `submit_audit_job` answers with an empty `ret`, which carries no error.
144
+ if (response.ret !== "0" && response.ret !== "") {
145
+ const hint = response.ret === "1015" || response.ret === "1014" ? " (is the Dreamina tab logged in?)" : "";
146
+ throw new Error(`Dreamina ${path} failed: ${response.errmsg ?? "unknown error"} (ret ${response.ret})${hint}`);
147
+ }
148
+ return response.data;
149
+ }
150
+ function aspectRatioFor(params, fallback = "1:1") {
151
+ if (params.aspectRatio && RATIO_TYPES[params.aspectRatio])
152
+ return params.aspectRatio;
153
+ if (params.aspectRatio)
154
+ throw new Error(`Dreamina does not support aspect ratio ${params.aspectRatio}. Use one of ${Object.keys(RATIO_TYPES).join(", ")}`);
155
+ if (params.width && params.height)
156
+ return nearestRatio(params.width, params.height);
157
+ return fallback;
158
+ }
159
+ function draftFor(model, prompt, negativePrompt, seed, ratioType, size, resolution) {
160
+ const id = () => randomUUID();
161
+ const component = {
162
+ type: "image_base_component",
163
+ id: id(),
164
+ min_version: "3.0.2",
165
+ aigc_mode: "workbench",
166
+ metadata: { type: "", id: id(), created_platform: 3, created_platform_version: "", created_time_in_ms: String(Date.now()), created_did: "" },
167
+ generate_type: "generate",
168
+ abilities: {
169
+ type: "",
170
+ id: id(),
171
+ generate: {
172
+ type: "",
173
+ id: id(),
174
+ core_param: {
175
+ type: "",
176
+ id: id(),
177
+ model,
178
+ prompt,
179
+ negative_prompt: negativePrompt,
180
+ seed,
181
+ sample_strength: 0.5,
182
+ image_ratio: ratioType,
183
+ large_image_info: { type: "", id: id(), height: size.height, width: size.width, resolution_type: resolution },
184
+ intelligent_ratio: false,
185
+ generate_type: 0
186
+ },
187
+ gen_option: { type: "", id: id(), gen_count: 1, generate_all: false }
188
+ }
189
+ }
190
+ };
191
+ 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] });
192
+ }
193
+ /**
194
+ * Image-to-image draft: the site's "blend" component with one `byte_edit`
195
+ * ability that carries the source images. The prompt keeps the `##` lead the
196
+ * web editor writes in front of a prompt that has references.
197
+ */
198
+ function blendDraftFor(model, prompt, negativePrompt, seed, strength, ratioType, size, resolution, sources) {
199
+ const id = () => randomUUID();
200
+ const component = {
201
+ type: "image_base_component",
202
+ id: id(),
203
+ min_version: "3.0.2",
204
+ aigc_mode: "workbench",
205
+ metadata: { type: "", id: id(), created_platform: 3, created_platform_version: "", created_time_in_ms: String(Date.now()), created_did: "" },
206
+ generate_type: "blend",
207
+ abilities: {
208
+ type: "",
209
+ id: id(),
210
+ blend: {
211
+ type: "",
212
+ id: id(),
213
+ min_features: [],
214
+ core_param: {
215
+ type: "",
216
+ id: id(),
217
+ model,
218
+ prompt: `##${prompt}`,
219
+ negative_prompt: negativePrompt,
220
+ seed,
221
+ sample_strength: strength,
222
+ image_ratio: ratioType,
223
+ large_image_info: { type: "", id: id(), height: size.height, width: size.width, resolution_type: resolution },
224
+ intelligent_ratio: false,
225
+ generate_type: 0
226
+ },
227
+ ability_list: [{
228
+ type: "",
229
+ id: id(),
230
+ name: "byte_edit",
231
+ image_uri_list: sources.map((image) => image.uri),
232
+ 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 })),
233
+ strength: strength
234
+ }],
235
+ prompt_placeholder_info_list: [{ type: "", id: id(), ability_index: 0 }],
236
+ postedit_param: { type: "", id: id(), generate_type: 0 }
237
+ },
238
+ gen_option: { type: "", id: id(), gen_count: 1, generate_all: false }
239
+ }
240
+ };
241
+ 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] });
242
+ }
243
+ /** The nearest Dreamina ratio to a pixel size. */
244
+ function nearestRatio(width, height) {
245
+ const target = width / height;
246
+ let best = "1:1";
247
+ let bestDiff = Infinity;
248
+ for (const key of Object.keys(RATIO_TYPES)) {
249
+ const [w, h] = key.split(":").map(Number);
250
+ const diff = Math.abs(w / h - target);
251
+ if (diff < bestDiff) {
252
+ best = key;
253
+ bestDiff = diff;
254
+ }
255
+ }
256
+ return best;
257
+ }
258
+ const MAX_REFERENCE_IMAGES = 9;
259
+ const MAX_REFERENCE_VIDEOS = 3;
260
+ const MAX_REFERENCE_AUDIOS = 3;
261
+ /** Chrome accepts at most 1 MiB per host-to-extension message, so the page receives uploads in pieces. */
262
+ const UPLOAD_PIECE_CHARS = 400_000;
263
+ /** Run one request in the Dreamina tab, so its origin and cookies apply. */
264
+ async function pageFetch(request) {
265
+ const expression = `(async () => {
266
+ const a = ${JSON.stringify(request)};
267
+ let body;
268
+ if (a.body && "staged" in a.body) {
269
+ const binary = atob(window.__ntUp[a.body.staged]);
270
+ body = Uint8Array.from(binary, (c) => c.charCodeAt(0));
271
+ delete window.__ntUp[a.body.staged];
272
+ } else if (a.body) {
273
+ body = a.body.text;
274
+ }
275
+ const response = await fetch(a.url, { method: a.method, headers: a.headers, body, credentials: "omit" });
276
+ return { status: response.status, text: await response.text() };
277
+ })()`;
278
+ return (await runner().evaluate(expression));
279
+ }
280
+ /** Stage `bytes` in the page as base64 pieces and return the staging id. */
281
+ async function stageBytes(bytes) {
282
+ const id = randomUUID();
283
+ const base64 = Buffer.from(bytes).toString("base64");
284
+ for (let at = 0; at < base64.length; at += UPLOAD_PIECE_CHARS) {
285
+ const piece = base64.slice(at, at + UPLOAD_PIECE_CHARS);
286
+ await runner().evaluate(`(() => { window.__ntUp ??= {}; window.__ntUp[${JSON.stringify(id)}] = (window.__ntUp[${JSON.stringify(id)}] ?? "") + ${JSON.stringify(piece)}; return true; })()`);
287
+ }
288
+ await runner().evaluate(`(() => { window.__ntUp ??= {}; window.__ntUp[${JSON.stringify(id)}] ??= ""; return true; })()`);
289
+ return id;
290
+ }
291
+ const sha256Hex = (data) => createHash("sha256").update(data).digest("hex");
292
+ const hmac = (key, data) => createHmac("sha256", key).update(data).digest();
293
+ const encodeQuery = (value) => encodeURIComponent(value).replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`);
294
+ /** AWS Signature V4 headers for an ImageX or VOD call, which signs only the amz headers. */
295
+ function imagexHeaders(token, method, query, payload, service = "imagex") {
296
+ const amzDate = new Date().toISOString().replace(/[:-]|\.\d{3}/g, "");
297
+ const day = amzDate.slice(0, 8);
298
+ const region = !token.region || token.region.length <= 3 ? "ap-singapore-1" : token.region;
299
+ const headers = { "x-amz-date": amzDate, "x-amz-security-token": token.session_token };
300
+ if (method === "POST")
301
+ headers["x-amz-content-sha256"] = sha256Hex(payload);
302
+ const signedNames = Object.keys(headers).sort();
303
+ const canonicalQuery = Object.keys(query).sort().map((k) => `${encodeQuery(k)}=${encodeQuery(query[k])}`).join("&");
304
+ const canonicalHeaders = signedNames.map((n) => `${n}:${headers[n]}\n`).join("");
305
+ const canonical = [method, "/", canonicalQuery, canonicalHeaders, signedNames.join(";"), sha256Hex(payload)].join("\n");
306
+ const scope = `${day}/${region}/${service}/aws4_request`;
307
+ const toSign = ["AWS4-HMAC-SHA256", amzDate, scope, sha256Hex(canonical)].join("\n");
308
+ const key = hmac(hmac(hmac(hmac(`AWS4${token.secret_access_key}`, day), region), service), "aws4_request");
309
+ const signature = createHmac("sha256", key).update(toSign).digest("hex");
310
+ if (method === "POST") {
311
+ headers["Content-Type"] = "application/json";
312
+ }
313
+ return {
314
+ ...headers,
315
+ Authorization: `AWS4-HMAC-SHA256 Credential=${token.access_key_id}/${scope}, SignedHeaders=${signedNames.join(";")}, Signature=${signature}`
316
+ };
317
+ }
318
+ /** Upload an image through ImageX the way the web app does and return its store URI. */
319
+ async function uploadImage(bytes) {
320
+ const token = await callApi("/mweb/v1/get_upload_token", { scene: 2 });
321
+ 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())`));
322
+ const userId = account.data?.user_id_str;
323
+ if (!userId)
324
+ throw new Error("Dreamina account info has no user id (is the tab logged in?)");
325
+ 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" };
326
+ const applyUrl = `https://${token.upload_domain}/?${Object.entries(applyQuery).map(([k, v]) => `${encodeQuery(k)}=${encodeQuery(v)}`).join("&")}`;
327
+ const apply = await pageFetch({ url: applyUrl, method: "GET", headers: imagexHeaders(token, "GET", applyQuery, "") });
328
+ const address = JSON.parse(apply.text).Result?.UploadAddress;
329
+ const store = address?.StoreInfos?.[0];
330
+ const host = address?.UploadHosts?.[0];
331
+ if (apply.status !== 200 || !store || !host || !address?.SessionKey)
332
+ throw new Error(`Dreamina image upload was refused (ApplyImageUpload HTTP ${apply.status})`);
333
+ const staged = await stageBytes(bytes);
334
+ const upload = await pageFetch({
335
+ url: `https://${host}/upload/v1/${store.StoreUri}`,
336
+ method: "POST",
337
+ headers: {
338
+ Authorization: store.Auth,
339
+ "Content-CRC32": (crc32(bytes) >>> 0).toString(16).padStart(8, "0"),
340
+ "X-Storage-U": userId,
341
+ "Content-Type": "application/octet-stream",
342
+ "Content-Disposition": 'attachment; filename="undefined"'
343
+ },
344
+ body: { staged }
345
+ });
346
+ if (upload.status !== 200)
347
+ throw new Error(`Dreamina image upload failed (HTTP ${upload.status})`);
348
+ const commitQuery = { Action: "CommitImageUpload", Version: "2018-08-01", ServiceId: token.space_name };
349
+ const commitBody = JSON.stringify({ SessionKey: address.SessionKey });
350
+ const commit = await pageFetch({
351
+ url: `https://${token.upload_domain}/?${Object.entries(commitQuery).map(([k, v]) => `${encodeQuery(k)}=${encodeQuery(v)}`).join("&")}`,
352
+ method: "POST",
353
+ headers: imagexHeaders(token, "POST", commitQuery, commitBody),
354
+ body: { text: commitBody }
355
+ });
356
+ const result = JSON.parse(commit.text).Result?.PluginResult?.[0];
357
+ if (commit.status !== 200 || !result?.ImageUri)
358
+ throw new Error(`Dreamina image upload could not be committed (HTTP ${commit.status})`);
359
+ await callApi("/mweb/v1/imagex/submit_audit_job", { uri_list: [result.ImageUri] });
360
+ return { uri: result.ImageUri, width: result.ImageWidth, height: result.ImageHeight };
361
+ }
362
+ /** 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`. */
363
+ async function uploadVideo(bytes) {
364
+ const token = await callApi("/mweb/v1/get_upload_token", { scene: 1 });
365
+ 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())`));
366
+ const userId = account.data?.user_id_str;
367
+ if (!userId)
368
+ throw new Error("Dreamina account info has no user id (is the tab logged in?)");
369
+ 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" };
370
+ const url = (query) => `https://${token.upload_domain}/?${Object.entries(query).map(([k, v]) => `${encodeQuery(k)}=${encodeQuery(v)}`).join("&")}`;
371
+ const apply = await pageFetch({ url: url(applyQuery), method: "GET", headers: imagexHeaders(token, "GET", applyQuery, "", "vod") });
372
+ const node = JSON.parse(apply.text).Result?.InnerUploadAddress?.UploadNodes?.[0];
373
+ const store = node?.StoreInfos?.[0];
374
+ if (apply.status !== 200 || !store || !node?.UploadHost || !node.SessionKey)
375
+ throw new Error(`Dreamina video upload was refused (ApplyUploadInner HTTP ${apply.status})`);
376
+ const staged = await stageBytes(bytes);
377
+ const upload = await pageFetch({
378
+ url: `https://${node.UploadHost}/upload/v1/${store.StoreUri}`,
379
+ method: "POST",
380
+ headers: {
381
+ Authorization: store.Auth,
382
+ "Content-CRC32": (crc32(bytes) >>> 0).toString(16).padStart(8, "0"),
383
+ "X-Storage-U": userId,
384
+ "Content-Type": "application/octet-stream",
385
+ "Content-Disposition": 'attachment; filename="undefined"'
386
+ },
387
+ body: { staged }
388
+ });
389
+ if (upload.status !== 200)
390
+ throw new Error(`Dreamina video upload failed (HTTP ${upload.status})`);
391
+ const commitQuery = { Action: "CommitUploadInner", Version: "2020-11-19", SpaceName: token.space_name };
392
+ const commitBody = JSON.stringify({ SessionKey: node.SessionKey, Functions: [{ name: "GetMeta" }] });
393
+ const commit = await pageFetch({
394
+ url: url(commitQuery),
395
+ method: "POST",
396
+ headers: imagexHeaders(token, "POST", commitQuery, commitBody, "vod"),
397
+ body: { text: commitBody }
398
+ });
399
+ const result = JSON.parse(commit.text).Result?.Results?.[0];
400
+ if (commit.status !== 200 || !result?.Vid)
401
+ throw new Error(`Dreamina video upload could not be committed (HTTP ${commit.status})`);
402
+ const meta = result.VideoMeta;
403
+ return { vid: result.Vid, width: meta?.Width ?? 0, height: meta?.Height ?? 0, durationMs: Math.round((meta?.Duration ?? 0) * 1000) };
404
+ }
405
+ /** Ask Dreamina's review whether it accepts these references for this model. */
406
+ async function auditReferences(model, images, videos = [], audios = []) {
407
+ const data = await callApi("/mweb/v1/execute_generate_audit", {
408
+ scene: 2,
409
+ model_key: model,
410
+ material_list: [
411
+ ...images.map((image) => ({ uri: image.uri, material_type: 1 })),
412
+ ...videos.map((video) => ({ vid: video.vid, material_type: 2 })),
413
+ ...audios.map((audio) => ({ vid: audio.vid, material_type: 3 }))
414
+ ]
415
+ });
416
+ const rejected = (data.result_list ?? []).findIndex((entry) => entry.audit_decision !== 1);
417
+ if (rejected >= 0) {
418
+ 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}`;
419
+ throw new Error(`Dreamina review rejected reference ${kind}`);
420
+ }
421
+ }
422
+ /**
423
+ * Split a prompt on `[Image N]`, `[Video N]` and `[Audio N]` markers into the
424
+ * text, image, video and audio segments the site's editor produces. A
425
+ * material's index counts images first, then videos, then audios. Without
426
+ * markers every image, video and audio leads and the prompt follows.
427
+ */
428
+ function referenceMeta(prompt, imageCount, videoCount = 0, audioCount = 0) {
429
+ const counts = { image: imageCount, video: videoCount, audio: audioCount };
430
+ const offsets = { image: 0, video: imageCount, audio: imageCount + videoCount };
431
+ const media = (kind, index) => ({ meta_type: kind, text: "", material_ref: { material_idx: offsets[kind] + index } });
432
+ if (!/\[(Image|Video|Audio) (\d+)\]/.test(prompt)) {
433
+ return [
434
+ ...Array.from({ length: imageCount }, (_, i) => media("image", i)),
435
+ ...Array.from({ length: videoCount }, (_, i) => media("video", i)),
436
+ ...Array.from({ length: audioCount }, (_, i) => media("audio", i)),
437
+ ...(prompt ? [{ meta_type: "text", text: prompt }] : [])
438
+ ];
439
+ }
440
+ const parts = [];
441
+ let last = 0;
442
+ for (const match of prompt.matchAll(/\[(Image|Video|Audio) (\d+)\]/g)) {
443
+ const kind = match[1].toLowerCase();
444
+ const index = Number(match[2]) - 1;
445
+ if (index < 0 || index >= counts[kind])
446
+ throw new Error(`The prompt refers to [${match[1]} ${match[2]}] but only ${counts[kind]} reference ${kind}(s) were given`);
447
+ if (match.index > last)
448
+ parts.push({ meta_type: "text", text: prompt.slice(last, match.index) });
449
+ parts.push(media(kind, index));
450
+ last = match.index + match[0].length;
451
+ }
452
+ if (last < prompt.length)
453
+ parts.push({ meta_type: "text", text: prompt.slice(last) });
454
+ return parts;
455
+ }
456
+ function videoDraftFor(model, prompt, seed, ratio, resolution, durationMs, submitId, references = [], frames, videos = [], audios = []) {
457
+ const id = () => randomUUID();
458
+ 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 });
459
+ const videoInput = {
460
+ type: "",
461
+ id: id(),
462
+ min_version: references.length + videos.length + audios.length > 0 ? "3.3.9" : "3.0.5",
463
+ prompt: references.length + videos.length + audios.length > 0 ? "" : prompt,
464
+ video_mode: 2,
465
+ fps: VIDEO_FPS,
466
+ duration_ms: durationMs,
467
+ resolution,
468
+ idip_meta_list: [],
469
+ };
470
+ if (frames) {
471
+ videoInput.first_frame_image = frameImage(frames.first);
472
+ if (frames.last) {
473
+ videoInput.end_frame_image = frameImage(frames.last);
474
+ videoInput.ending_control = "1.0";
475
+ }
476
+ }
477
+ if (references.length + videos.length + audios.length > 0) {
478
+ videoInput.unified_edit_input = {
479
+ material_list: [...references.map((image) => ({
480
+ material_type: "image",
481
+ 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 }
482
+ })), ...videos.map((video) => ({
483
+ material_type: "video",
484
+ video_info: { type: "video", source_from: "upload", name: "", vid: video.vid, fps: 0, width: video.width, height: video.height, duration: video.durationMs }
485
+ })), ...audios.map((audio) => ({
486
+ material_type: "audio",
487
+ audio_info: { type: "audio", source_from: "upload", vid: audio.vid, duration: audio.durationMs, name: "" }
488
+ }))],
489
+ meta_list: referenceMeta(prompt, references.length, videos.length, audios.length)
490
+ };
491
+ }
492
+ const component = {
493
+ type: "video_base_component",
494
+ id: id(),
495
+ min_version: "1.0.0",
496
+ aigc_mode: "workbench",
497
+ metadata: { type: "", id: id(), created_platform: 3, created_platform_version: "", created_time_in_ms: String(Date.now()), created_did: "" },
498
+ generate_type: "gen_video",
499
+ abilities: {
500
+ type: "",
501
+ id: id(),
502
+ gen_video: {
503
+ type: "",
504
+ id: id(),
505
+ text_to_video_params: {
506
+ type: "",
507
+ id: id(),
508
+ video_gen_inputs: [videoInput],
509
+ video_aspect_ratio: ratio,
510
+ seed,
511
+ model_req_key: model,
512
+ priority: 0
513
+ },
514
+ 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 })
515
+ }
516
+ },
517
+ process_type: 1
518
+ };
519
+ return JSON.stringify({ type: "draft", id: id(), min_version: "3.0.5", min_features: [], is_from_tsn: true, version: DA_VERSION, main_component_id: component.id, component_list: [component] });
520
+ }
521
+ /** Submit a generation and poll until it finishes, then return the finished record. */
522
+ async function submitAndAwait(body, submitId, timeoutMs, signal) {
523
+ await callApi("/mweb/v1/aigc_draft/generate", body);
524
+ const deadline = Date.now() + timeoutMs;
525
+ let pollFailures = 0;
526
+ for (;;) {
527
+ await sleep(POLL_INTERVAL_MS, signal);
528
+ let history;
529
+ try {
530
+ history = await callApi("/mweb/v1/get_history_by_ids", { submit_ids: [submitId] });
531
+ }
532
+ catch (error) {
533
+ // Reading the history is idempotent, and Dreamina answers it with a
534
+ // transient error while a fresh task registers. The submit is never retried.
535
+ if (++pollFailures > MAX_POLL_FAILURES)
536
+ throw error;
537
+ continue;
538
+ }
539
+ pollFailures = 0;
540
+ const record = history[submitId];
541
+ const status = record?.task?.status;
542
+ if (status === TASK_DONE)
543
+ return record;
544
+ if (status === TASK_FAILED) {
545
+ throw new Error(`Dreamina generation failed: ${record?.fail_msg || "unknown reason"} (fail_code ${record?.fail_code ?? "unknown"})`);
546
+ }
547
+ if (Date.now() > deadline)
548
+ throw new Error(`Dreamina generation timed out after ${timeoutMs / 1000}s`);
549
+ }
550
+ }
551
+ async function download(url, signal) {
552
+ const response = await fetchExternalMedia(url, signal ? { signal } : undefined);
553
+ if (!response.ok)
554
+ throw new Error(`Dreamina download failed: HTTP ${response.status}`);
555
+ return new Uint8Array(await response.arrayBuffer());
556
+ }
557
+ function videoEnum(model, key) {
558
+ const option = model.options?.find((o) => o.key === key)?.enum_val;
559
+ const values = (option?.string_value ?? option?.int_value ?? []);
560
+ return { values, defaultIndex: option?.default_val_idx ?? 0 };
561
+ }
562
+ /** Seedance 2.x models list reference input as `unified_edit`, as an option or an input type. */
563
+ function takesReferences(model) {
564
+ return Boolean(model.options?.some((o) => o.key === "unified_edit")) || videoEnum(model, "input_media_type").values.includes("unified_edit");
565
+ }
566
+ function videoInputTypes(model) {
567
+ return videoEnum(model, "input_media_type").values.map(String);
568
+ }
569
+ /** Models that list `first_frame` among their input types take a start image. */
570
+ function takesFirstFrame(model) {
571
+ return videoInputTypes(model).includes("first_frame");
572
+ }
573
+ function videoDurations(model) {
574
+ return videoEnum(model, "frames").values.map((frames) => Number(frames) / VIDEO_FPS);
575
+ }
576
+ function sleep(ms, signal) {
577
+ return new Promise((resolve, reject) => {
578
+ if (signal?.aborted)
579
+ return reject(new Error("Dreamina generation aborted"));
580
+ const timer = setTimeout(resolve, ms);
581
+ signal?.addEventListener("abort", () => { clearTimeout(timer); reject(new Error("Dreamina generation aborted")); }, { once: true });
582
+ });
583
+ }
584
+ /**
585
+ * Dreamina (CapCut) image generation through the user's own logged-in Chrome.
586
+ * Calls the same web API the site uses, from inside the attached Dreamina tab,
587
+ * so the account's cookies and credits apply. Local installs only.
588
+ */
589
+ export class DreaminaProvider extends BaseProvider {
590
+ configCache = null;
591
+ videoConfigCache = null;
592
+ music = createDreaminaMusic({ callApi, submitAndAwait, download, appId: Number(APP_ID), daVersion: DA_VERSION });
593
+ declaredCapabilities() {
594
+ return ["text_to_image", "image_to_image", "text_to_video", "image_to_video", "reference_to_video", "text_to_music"];
595
+ }
596
+ static requiredSecrets() { return []; }
597
+ constructor(_secrets = {}) { super(PROVIDER_IDS.DREAMINA); }
598
+ async generateMessage(_args) { throw new Error("dreamina does not support chat generation"); }
599
+ // eslint-disable-next-line require-yield
600
+ async *generateMessages(_args) { throw new Error("dreamina does not support chat generation"); }
601
+ async imageConfigs() {
602
+ if (this.configCache && Date.now() - this.configCache.at < CONFIG_TTL_MS)
603
+ return this.configCache.models;
604
+ const data = await callApi("/mweb/v1/get_common_config", {});
605
+ const models = (data.model_list ?? []).filter((m) => m.feats?.includes("t2i"));
606
+ this.configCache = { at: Date.now(), models };
607
+ return models;
608
+ }
609
+ /** Served from the captured catalog, so listing never waits on the browser tab. */
610
+ async getAvailableImageModels() {
611
+ return [...DREAMINA_IMAGE_MODELS];
612
+ }
613
+ async imageConfig(modelId) {
614
+ const config = (await this.imageConfigs()).find((m) => m.model_req_key === modelId);
615
+ if (!config)
616
+ throw new Error(`Unknown Dreamina image model: ${modelId}`);
617
+ return config;
618
+ }
619
+ /** Pick the resolution, ratio and pixel size a model offers for a request. */
620
+ imageSize(config, requestedResolution, ratio) {
621
+ const resolutions = Object.keys(config.resolution_map ?? {});
622
+ const requested = requestedResolution?.toLowerCase();
623
+ const resolution = requested && resolutions.includes(requested)
624
+ ? requested
625
+ : config.default_resolution_type || (resolutions.includes("2k") ? "2k" : resolutions[0]);
626
+ if (!resolution)
627
+ throw new Error(`Dreamina model ${config.model_name} lists no resolutions`);
628
+ const ratioType = RATIO_TYPES[ratio];
629
+ const size = config.resolution_map?.[resolution]?.image_ratio_sizes?.find((s) => s.ratio_type === ratioType);
630
+ if (!size)
631
+ throw new Error(`Dreamina model ${config.model_name} does not offer ${ratio} at ${resolution}`);
632
+ return { resolution, ratioType, size };
633
+ }
634
+ async submitImage(config, draft, signal) {
635
+ const submitId = randomUUID();
636
+ const record = await submitAndAwait({
637
+ extend: { root_model: config.model_req_key },
638
+ submit_id: submitId,
639
+ metrics_extra: JSON.stringify({ promptSource: "custom", generateCount: 1, enterFrom: "click", position: "page_bottom_box", isBoxSelect: false, isCutout: false, hasRejectedAudit: 0, generateId: submitId, isRegenerate: false }),
640
+ draft_content: draft(submitId),
641
+ http_common_info: { aid: Number(APP_ID) }
642
+ }, submitId, DEFAULT_TIMEOUT_MS, signal ?? undefined);
643
+ const url = record.item_list?.[0]?.image?.large_images?.[0]?.image_url;
644
+ if (!url)
645
+ throw new Error("Dreamina finished without an image (the prompt may have been rejected by review)");
646
+ log.debug("Dreamina image generation completed", { model: config.model_req_key, submitId });
647
+ return download(url, signal ?? undefined);
648
+ }
649
+ async textToImage(params) {
650
+ const config = await this.imageConfig(params.model.id);
651
+ const { resolution, ratioType, size } = this.imageSize(config, params.resolution, aspectRatioFor(params));
652
+ const seed = params.seed ?? Math.floor(Math.random() * 4_000_000_000);
653
+ return this.submitImage(config, () => draftFor(config.model_req_key, params.prompt, params.negativePrompt ?? "", seed, ratioType, size, resolution), params.signal);
654
+ }
655
+ /**
656
+ * Image-to-image through the site's "blend" mode: the source images guide a
657
+ * new image that follows the prompt. `strength` is Dreamina's reference
658
+ * strength (0 to 1, default 0.5). Without a ratio the output follows the
659
+ * first source image. Only models that list `byte_edit` take sources.
660
+ */
661
+ async imageToImage(images, params) {
662
+ if (images.length === 0)
663
+ throw new Error("imageToImage needs at least one source image");
664
+ if (images.length > MAX_REFERENCE_IMAGES)
665
+ throw new Error(`Dreamina takes at most ${MAX_REFERENCE_IMAGES} source images, not ${images.length}`);
666
+ const config = await this.imageConfig(params.model.id);
667
+ if (!config.feats?.includes("byte_edit"))
668
+ throw new Error(`Dreamina model ${config.model_name} does not take source images`);
669
+ const sources = [];
670
+ for (const image of images)
671
+ sources.push(await uploadImage(image));
672
+ await auditReferences(config.model_req_key, sources);
673
+ const ratio = aspectRatioFor({ aspectRatio: params.aspectRatio, width: params.targetWidth, height: params.targetHeight }, nearestRatio(sources[0].width, sources[0].height));
674
+ const { resolution, ratioType, size } = this.imageSize(config, params.resolution, ratio);
675
+ const seed = params.seed ?? Math.floor(Math.random() * 4_000_000_000);
676
+ const strength = Math.min(1, Math.max(0, params.strength ?? 0.5));
677
+ return this.submitImage(config, () => blendDraftFor(config.model_req_key, params.prompt, params.negativePrompt ?? "", seed, strength, ratioType, size, resolution, sources), params.signal);
678
+ }
679
+ async videoConfigs() {
680
+ if (this.videoConfigCache && Date.now() - this.videoConfigCache.at < CONFIG_TTL_MS)
681
+ return this.videoConfigCache.models;
682
+ const data = await callApi("/mweb/v1/video_generate/get_common_config", {});
683
+ const models = data.model_list ?? [];
684
+ this.videoConfigCache = { at: Date.now(), models };
685
+ return models;
686
+ }
687
+ async getAvailableVideoModels() {
688
+ return [...DREAMINA_VIDEO_MODELS];
689
+ }
690
+ async textToVideo(params) {
691
+ return this.generateVideo(params, { references: [] });
692
+ }
693
+ /**
694
+ * Image-to-video through Dreamina's "first and last frames" mode. The image
695
+ * is the first frame. `endImage` sets the last frame on models that list
696
+ * `end_frame`. The prompt describes the motion and may stay empty.
697
+ */
698
+ async imageToVideo(image, params) {
699
+ return this.generateVideo({ ...params, prompt: params.prompt ?? "" }, { references: [], first: image, last: params.endImage ?? undefined });
700
+ }
701
+ /**
702
+ * Reference-to-video through Dreamina's "omni reference" mode: the images
703
+ * condition the clip, and `[Image N]`, `[Video N]` and `[Audio N]` markers in
704
+ * the prompt place them in the text. Without markers the images and videos lead and the
705
+ * prompt follows. A reference video supplies motion or camera work and a
706
+ * reference audio supplies the sound. Audio needs at least one image or video
707
+ * beside it. Only the Seedance 2.x models take references.
708
+ */
709
+ async referenceToVideo(inputs, params) {
710
+ const audios = inputs.audios ?? [];
711
+ if (inputs.images.length + inputs.videos.length === 0)
712
+ 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");
713
+ if (audios.length > MAX_REFERENCE_AUDIOS)
714
+ throw new Error(`Dreamina takes at most ${MAX_REFERENCE_AUDIOS} reference audios, not ${audios.length}`);
715
+ if (inputs.images.length > MAX_REFERENCE_IMAGES)
716
+ throw new Error(`Dreamina takes at most ${MAX_REFERENCE_IMAGES} reference images, not ${inputs.images.length}`);
717
+ if (inputs.videos.length > MAX_REFERENCE_VIDEOS)
718
+ throw new Error(`Dreamina takes at most ${MAX_REFERENCE_VIDEOS} reference videos, not ${inputs.videos.length}`);
719
+ return this.generateVideo(params, { references: inputs.images, videos: inputs.videos, audios });
720
+ }
721
+ async getAvailableMusicModels() {
722
+ return this.music.models();
723
+ }
724
+ /** Instrumental tracks need no model key. Vocal models appear only where the account lists them. */
725
+ async textToMusic(params) {
726
+ return this.music.generate(params);
727
+ }
728
+ async generateVideo(params, inputs) {
729
+ const config = (await this.videoConfigs()).find((m) => m.model_req_key === params.model.id);
730
+ if (!config)
731
+ throw new Error(`Unknown Dreamina video model: ${params.model.id}`);
732
+ const resolutions = videoEnum(config, "resolution");
733
+ const resolution = params.resolution
734
+ ? resolutions.values.map(String).find((r) => r.toLowerCase() === params.resolution.toLowerCase())
735
+ : String(resolutions.values[resolutions.defaultIndex] ?? resolutions.values[0] ?? "");
736
+ if (!resolution)
737
+ throw new Error(`Dreamina model ${config.model_name} does not offer ${params.resolution}. Use one of ${resolutions.values.join(", ")}`);
738
+ const ratios = videoEnum(config, "video_aspect_ratio");
739
+ const ratio = params.aspectRatio ?? String(ratios.values[ratios.defaultIndex] ?? "16:9");
740
+ if (!ratios.values.includes(ratio))
741
+ throw new Error(`Dreamina model ${config.model_name} does not offer ${ratio}. Use one of ${ratios.values.join(", ")}`);
742
+ const frames = videoEnum(config, "frames");
743
+ const durations = videoDurations(config);
744
+ const seconds = params.durationSeconds ?? Number(frames.values[frames.defaultIndex]) / VIDEO_FPS;
745
+ if (!durations.includes(seconds))
746
+ throw new Error(`Dreamina model ${config.model_name} offers ${durations.join(", ")} second clips, not ${seconds}`);
747
+ const videoInputs = inputs.videos ?? [];
748
+ const audioInputs = inputs.audios ?? [];
749
+ if (inputs.references.length + videoInputs.length + audioInputs.length > 0 && !takesReferences(config)) {
750
+ 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`);
751
+ }
752
+ if (inputs.first && !takesFirstFrame(config))
753
+ throw new Error(`Dreamina model ${config.model_name} does not take a first frame image`);
754
+ if (inputs.last && !videoInputTypes(config).includes("end_frame"))
755
+ throw new Error(`Dreamina model ${config.model_name} does not take a last frame image`);
756
+ const references = [];
757
+ for (const image of inputs.references)
758
+ references.push(await uploadImage(image));
759
+ const videos = [];
760
+ for (const video of videoInputs)
761
+ videos.push(await uploadVideo(video));
762
+ const audios = [];
763
+ for (const audio of audioInputs)
764
+ audios.push(await uploadVideo(audio));
765
+ let bounds;
766
+ if (inputs.first) {
767
+ bounds = { first: await uploadImage(inputs.first) };
768
+ if (inputs.last)
769
+ bounds.last = await uploadImage(inputs.last);
770
+ }
771
+ const audited = [...references, ...(bounds ? [bounds.first, ...(bounds.last ? [bounds.last] : [])] : [])];
772
+ if (audited.length + videos.length + audios.length > 0)
773
+ await auditReferences(config.model_req_key, audited, videos, audios);
774
+ const submitId = randomUUID();
775
+ const seed = params.seed ?? Math.floor(Math.random() * 4_000_000_000);
776
+ const record = await submitAndAwait({
777
+ extend: { root_model: config.model_req_key },
778
+ submit_id: submitId,
779
+ metrics_extra: JSON.stringify({ isDefaultSeed: 1, originSubmitId: submitId, isRegenerate: false }),
780
+ draft_content: videoDraftFor(config.model_req_key, params.prompt, seed, ratio, resolution, seconds * 1000, submitId, references, bounds, videos, audios),
781
+ http_common_info: { aid: Number(APP_ID) }
782
+ }, submitId, (params.timeoutSeconds ?? VIDEO_TIMEOUT_MS / 1000) * 1000, params.signal);
783
+ const url = record.item_list?.[0]?.video?.transcoded_video?.origin?.video_url;
784
+ if (!url)
785
+ throw new Error("Dreamina finished without a video");
786
+ log.debug("Dreamina video generation completed", { model: config.model_req_key, submitId });
787
+ return download(url, params.signal);
788
+ }
789
+ }
790
+ //# sourceMappingURL=dreamina-provider.js.map