@nodaro/shared 2.18.0 → 2.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/dist/index.cjs +251 -12
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +100 -6
  4. package/dist/index.d.ts +100 -6
  5. package/dist/index.js +243 -13
  6. package/dist/index.js.map +1 -1
  7. package/package.json +1 -1
  8. package/src/__tests__/credit-identifiers.test.ts +47 -0
  9. package/src/__tests__/gvp-supported-providers.test.ts +22 -1
  10. package/src/__tests__/prompt-length-limits.test.ts +4 -0
  11. package/src/__tests__/video-audio-capability.test.ts +43 -0
  12. package/src/__tests__/video-mode-for-inputs.test.ts +1 -1
  13. package/src/__tests__/wan-3-catalog.test.ts +243 -0
  14. package/src/credit-identifiers.ts +42 -9
  15. package/src/i18n/held-prop.ar.ts +2 -0
  16. package/src/i18n/held-prop.de.ts +3 -0
  17. package/src/i18n/held-prop.es.ts +3 -0
  18. package/src/i18n/held-prop.fr.ts +2 -0
  19. package/src/i18n/held-prop.he.ts +3 -0
  20. package/src/i18n/held-prop.hi.ts +2 -0
  21. package/src/i18n/held-prop.ja.ts +3 -0
  22. package/src/i18n/held-prop.ko.ts +3 -0
  23. package/src/i18n/held-prop.pt-BR.ts +3 -0
  24. package/src/i18n/held-prop.ru.ts +2 -0
  25. package/src/i18n/held-prop.zh-CN.ts +3 -0
  26. package/src/i18n/person.ar.ts +3 -0
  27. package/src/i18n/person.de.ts +4 -0
  28. package/src/i18n/person.es.ts +4 -0
  29. package/src/i18n/person.fr.ts +3 -0
  30. package/src/i18n/person.he.ts +4 -0
  31. package/src/i18n/person.hi.ts +3 -0
  32. package/src/i18n/person.ja.ts +4 -0
  33. package/src/i18n/person.ko.ts +4 -0
  34. package/src/i18n/person.pt-BR.ts +4 -0
  35. package/src/i18n/person.ru.ts +3 -0
  36. package/src/i18n/person.zh-CN.ts +4 -0
  37. package/src/i18n/setting.ar.ts +2 -0
  38. package/src/i18n/setting.de.ts +3 -0
  39. package/src/i18n/setting.es.ts +3 -0
  40. package/src/i18n/setting.fr.ts +2 -0
  41. package/src/i18n/setting.he.ts +3 -0
  42. package/src/i18n/setting.hi.ts +2 -0
  43. package/src/i18n/setting.ja.ts +3 -0
  44. package/src/i18n/setting.ko.ts +3 -0
  45. package/src/i18n/setting.pt-BR.ts +3 -0
  46. package/src/i18n/setting.ru.ts +2 -0
  47. package/src/i18n/setting.zh-CN.ts +3 -0
  48. package/src/i18n/style.ar.ts +2 -0
  49. package/src/i18n/style.de.ts +3 -0
  50. package/src/i18n/style.es.ts +3 -0
  51. package/src/i18n/style.fr.ts +2 -0
  52. package/src/i18n/style.he.ts +3 -0
  53. package/src/i18n/style.hi.ts +2 -0
  54. package/src/i18n/style.ja.ts +3 -0
  55. package/src/i18n/style.ko.ts +3 -0
  56. package/src/i18n/style.pt-BR.ts +3 -0
  57. package/src/i18n/style.ru.ts +2 -0
  58. package/src/i18n/style.zh-CN.ts +3 -0
  59. package/src/i18n/styling.ar.ts +4 -0
  60. package/src/i18n/styling.de.ts +5 -0
  61. package/src/i18n/styling.es.ts +5 -0
  62. package/src/i18n/styling.fr.ts +4 -0
  63. package/src/i18n/styling.he.ts +5 -0
  64. package/src/i18n/styling.hi.ts +4 -0
  65. package/src/i18n/styling.ja.ts +5 -0
  66. package/src/i18n/styling.ko.ts +5 -0
  67. package/src/i18n/styling.pt-BR.ts +5 -0
  68. package/src/i18n/styling.ru.ts +4 -0
  69. package/src/i18n/styling.zh-CN.ts +5 -0
  70. package/src/index.ts +9 -0
  71. package/src/model-catalog.ts +101 -0
  72. package/src/model-constants.ts +182 -7
  73. package/src/node-default-mappings.ts +5 -0
  74. package/src/video-ui-defaults.ts +66 -0
@@ -228,6 +228,12 @@ const VIDEO_RATIOS_SEEDANCE_2 = ["16:9", "9:16", "1:1", "4:3", "3:4", "21:9", "a
228
228
  // portrait-social and 21:9/9:21 cinematic (docs.kie.ai/market/happyhorse-1-1).
229
229
  // Kept separate so the wider set can't leak to models that don't support it.
230
230
  const VIDEO_RATIOS_HAPPYHORSE_11 = ["16:9", "9:16", "1:1", "4:3", "3:4", "4:5", "5:4", "21:9", "9:21"] as const
231
+ // Wan 3.0 (standard + Prime) — the model's own six-ratio enum, `adaptive` first
232
+ // because that is the KIE default ("automatically selects the ratio based on the
233
+ // input media and intent"). Deliberately NO 21:9 — the Wan 3.0 enum rejects it,
234
+ // so VIDEO_RATIOS_SEEDANCE_2 must NOT be reused here
235
+ // (docs.kie.ai/market/wan/3-0-video and /3-0-video-prime — identical schemas).
236
+ const VIDEO_RATIOS_WAN_3 = ["adaptive", "16:9", "4:3", "1:1", "3:4", "9:16"] as const
231
237
 
232
238
  // =============================================================================
233
239
  // IMAGE MODELS
@@ -1179,6 +1185,41 @@ const VIDEO_MODELS: Record<string, ModelCatalogEntry> = {
1179
1185
  { identifier: "gemini-omni-video:4k:vref", credits: 900, note: "video-edit 4K" },
1180
1186
  ],
1181
1187
  },
1188
+ // Gemini Omni Flash 1.1 — the faster/cheaper Omni SKU (KIE
1189
+ // `google/gemini-omni-flash-1-1`). Identical request surface to
1190
+ // gemini-omni-video, so every capability below is deliberate PARITY with the
1191
+ // sibling: same 4/6/8/10s ladder, same 720p/1080p + 4K bands, same
1192
+ // features (the KIE schema also exposes first/last frame, but the pro
1193
+ // sibling's frame path is unwired here too — no "end-frame" until it is).
1194
+ // 360p is on the KIE enum but NOT exposed: it sits in the same credit band as
1195
+ // 720p/1080p, so it could only hand the user a worse render at the same price.
1196
+ "gemini-omni-flash": {
1197
+ id: "gemini-omni-flash",
1198
+ kind: "video",
1199
+ modes: ["i2v", "t2v"] as const, // V2V is served via the i2v handle, not a catalog mode
1200
+ family: "Google",
1201
+ label: "Gemini Omni Flash",
1202
+ series: "Gemini",
1203
+ description: "Google Gemini Omni Flash — faster/cheaper Omni tier: multimodal video with native audio, text/image-to-video + video-edit.",
1204
+ useCases: ["cinematic", "fast"],
1205
+ features: ["audio", "reference-image"],
1206
+ aspectRatios: VIDEO_RATIOS_HV,
1207
+ resolutions: ["720p", "1080p", "4k"],
1208
+ durations: [4, 6, 8, 10],
1209
+ pricing: [
1210
+ { identifier: "gemini-omni-flash", credits: 270, note: "default — 8s 720p/1080p (the credit builder's duration fallback)" },
1211
+ { identifier: "gemini-omni-flash:4", credits: 160, note: "720p/1080p 4s" },
1212
+ { identifier: "gemini-omni-flash:6", credits: 210, note: "720p/1080p 6s" },
1213
+ { identifier: "gemini-omni-flash:8", credits: 270, note: "720p/1080p 8s" },
1214
+ { identifier: "gemini-omni-flash:10", credits: 320, note: "720p/1080p 10s" },
1215
+ { identifier: "gemini-omni-flash:4k:4", credits: 370, note: "4K 4s" },
1216
+ { identifier: "gemini-omni-flash:4k:6", credits: 420, note: "4K 6s" },
1217
+ { identifier: "gemini-omni-flash:4k:8", credits: 480, note: "4K 8s" },
1218
+ { identifier: "gemini-omni-flash:4k:10", credits: 530, note: "4K 10s" },
1219
+ { identifier: "gemini-omni-flash:vref", credits: 420, note: "video-edit 720p/1080p" },
1220
+ { identifier: "gemini-omni-flash:4k:vref", credits: 630, note: "video-edit 4K" },
1221
+ ],
1222
+ },
1182
1223
 
1183
1224
  // ── Kling ──
1184
1225
  "kling": {
@@ -1450,6 +1491,66 @@ const VIDEO_MODELS: Record<string, ModelCatalogEntry> = {
1450
1491
  },
1451
1492
 
1452
1493
  // ── Wan ──
1494
+ // Wan 3.0 (standard + Prime) — a NEW generation, not a tier of the 2.x ladder.
1495
+ // One id per SKU serves BOTH modes (no t2v twin, no alias): first/last frame
1496
+ // OR an all-purpose reference mode (images/videos/audio, bound in the prompt
1497
+ // as Image1/Video1/Audio1 — no space, unlike Wan 2.x), native audio behind a
1498
+ // boolean `audio` lever, 2-30s at 480p/720p/1080p.
1499
+ // `video-reference` is deliberately NOT declared: KIE accepts reference videos,
1500
+ // but the extend chain is unwired and KIE caps input-video + output at 30s,
1501
+ // which the segment bounds cannot express. Precedent: gemini-omni-video carries
1502
+ // ref limits without the feature. The ref CAPS still live in
1503
+ // VIDEO_REF_LIMITS_BY_PROVIDER (model-constants).
1504
+ // `resolutions` is ascending (repo convention); the DEFAULT tier (720p) is
1505
+ // declared explicitly in PRICING_DEFAULT_RESOLUTION, never by array position.
1506
+ "wan-3": {
1507
+ id: "wan-3",
1508
+ kind: "video",
1509
+ modes: ["i2v", "t2v"] as const,
1510
+ family: "Alibaba",
1511
+ label: "Wan 3.0",
1512
+ series: "Wan",
1513
+ description: "Wan 3.0 — multimodal: first/last frame or image/video/audio references, native audio, 2-30s at 480p/720p/1080p.",
1514
+ useCases: ["motion", "narrative", "long-form"],
1515
+ features: ["end-frame", "audio", "reference-image"],
1516
+ aspectRatios: VIDEO_RATIOS_WAN_3,
1517
+ durations: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30],
1518
+ resolutions: ["480p", "720p", "1080p"],
1519
+ pricing: [
1520
+ { identifier: "wan-3", credits: 200, note: "default 5s 720p — see :Ns:res variants for exact" },
1521
+ { identifier: "wan-3:5s:720p", credits: 200, note: "5s 720p (the default tier)" },
1522
+ { identifier: "wan-3:8s:480p", credits: 160, note: "8s 480p" },
1523
+ { identifier: "wan-3:8s:720p", credits: 320, note: "8s 720p" },
1524
+ { identifier: "wan-3:8s:1080p", credits: 640, note: "8s 1080p" },
1525
+ { identifier: "wan-3:30s:480p", credits: 600, note: "30s 480p (max)" },
1526
+ { identifier: "wan-3:30s:720p", credits: 1200, note: "30s 720p (max)" },
1527
+ { identifier: "wan-3:30s:1080p", credits: 2400, note: "30s 1080p (max)" },
1528
+ ],
1529
+ },
1530
+ "wan-3-prime": {
1531
+ id: "wan-3-prime",
1532
+ kind: "video",
1533
+ modes: ["i2v", "t2v"] as const,
1534
+ family: "Alibaba",
1535
+ label: "Wan 3.0 Prime",
1536
+ series: "Wan",
1537
+ description: "Wan 3.0 Prime — Alibaba's high-speed Wan 3.0 tier: same multimodal surface and 2-30s range, faster turnaround at a higher per-second rate.",
1538
+ useCases: ["fast", "motion", "narrative"],
1539
+ features: ["end-frame", "audio", "reference-image"],
1540
+ aspectRatios: VIDEO_RATIOS_WAN_3,
1541
+ durations: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30],
1542
+ resolutions: ["480p", "720p", "1080p"],
1543
+ pricing: [
1544
+ { identifier: "wan-3-prime", credits: 320, note: "default 5s 720p — see :Ns:res variants for exact" },
1545
+ { identifier: "wan-3-prime:5s:720p", credits: 320, note: "5s 720p (the default tier)" },
1546
+ { identifier: "wan-3-prime:8s:480p", credits: 250, note: "8s 480p" },
1547
+ { identifier: "wan-3-prime:8s:720p", credits: 510, note: "8s 720p" },
1548
+ { identifier: "wan-3-prime:8s:1080p", credits: 1010, note: "8s 1080p" },
1549
+ { identifier: "wan-3-prime:30s:480p", credits: 920, note: "30s 480p (max)" },
1550
+ { identifier: "wan-3-prime:30s:720p", credits: 1890, note: "30s 720p (max)" },
1551
+ { identifier: "wan-3-prime:30s:1080p", credits: 3780, note: "30s 1080p (max)" },
1552
+ ],
1553
+ },
1453
1554
  "wan-i2v": {
1454
1555
  id: "wan-i2v",
1455
1556
  kind: "video",
@@ -139,6 +139,13 @@ export const MAX_VIDEO_PROMPT_CHARS_BY_PROVIDER: Record<string, number> = {
139
139
  // (docs.kie.ai/market/bytedance/seedance-2-5).
140
140
  "seedance-2-5": 30000,
141
141
  "gemini-omni-video": 20000,
142
+ // Gemini Omni Flash 1.1 — same 20000-char prompt surface as the pro sibling
143
+ // (docs.kie.ai/market/google/gemini-omni-flash-1-1).
144
+ "gemini-omni-flash": 20000,
145
+ // Wan 3.0 / 3.0 Prime — 20000 chars, excess truncated by the provider
146
+ // (docs.kie.ai/market/wan/3-0-video and /3-0-video-prime).
147
+ "wan-3": 20000,
148
+ "wan-3-prime": 20000,
142
149
  "bytedance-lite": 10000,
143
150
  "bytedance-pro": 10000,
144
151
  "bytedance-pro-fast": 10000,
@@ -750,6 +757,11 @@ export const IMAGE_TO_VIDEO_PROVIDERS = [
750
757
  "runway-kie",
751
758
  "kling-3-omni",
752
759
  "gemini-omni-video",
760
+ "gemini-omni-flash",
761
+ // Wan 3.0 family — one id per SKU serves BOTH modes (no t2v twin, no alias),
762
+ // so each id is listed in this array AND in TEXT_TO_VIDEO_PROVIDERS.
763
+ "wan-3",
764
+ "wan-3-prime",
753
765
  "ltx-2.3-pro",
754
766
  "ltx-2.3-fast",
755
767
  // xAI Grok Imagine Video 1.5 — KIE i2v (image_urls required). Also listed in
@@ -788,6 +800,12 @@ export const TEXT_TO_VIDEO_PROVIDERS = [
788
800
  "happyhorse",
789
801
  "runway-kie",
790
802
  "gemini-omni-video",
803
+ "gemini-omni-flash",
804
+ // Wan 3.0 family — REAL t2v (prompt-only runs are the documented default
805
+ // mode), so they belong in this main section and must NOT be gated by
806
+ // VIDEO_PROVIDERS_REQUIRING_IMAGE.
807
+ "wan-3",
808
+ "wan-3-prime",
791
809
  "ltx-2.3-pro",
792
810
  "ltx-2.3-fast",
793
811
  // ── i2v-only providers (image required) ──────────────────────────────────
@@ -1245,6 +1263,12 @@ export const DURATION_PRICED_PROVIDERS = new Set([
1245
1263
  "happyhorse",
1246
1264
  "happyhorse-i2v",
1247
1265
  "happyhorse-ref2v",
1266
+ // Wan 3.0 family — per-second billing at three resolution rates. NOTE:
1267
+ // gemini-omni-flash is deliberately ABSENT (like gemini-omni-video): the
1268
+ // Gemini Omni branch in buildVideoCreditModelIdentifier early-returns before
1269
+ // this gate is consulted.
1270
+ "wan-3",
1271
+ "wan-3-prime",
1248
1272
  ])
1249
1273
 
1250
1274
  /**
@@ -1302,6 +1326,75 @@ export function normalizeMinimaxH3Resolution(resolution: string | undefined): "7
1302
1326
  return resolution?.trim().toLowerCase() === "768p" ? "768P" : MINIMAX_H3_DEFAULT_RESOLUTION
1303
1327
  }
1304
1328
 
1329
+ /**
1330
+ * Wan 3.0 family — the KIE "wan/3-0-video" market model and its high-speed
1331
+ * "wan/3-0-video-prime" sibling. The two share a BYTE-IDENTICAL input schema
1332
+ * (first/last frame OR an all-purpose reference mode with images/videos/audio,
1333
+ * native `audio` boolean, 2-30s, 480P/720P/1080P, six aspect ratios with
1334
+ * `adaptive` as the default); only the per-second rate differs. Prime is the
1335
+ * FASTER tier at a higher rate — never describe it as "higher quality".
1336
+ *
1337
+ * Exact-membership set — NEVER match on a "wan" prefix: "wan", "wan-i2v",
1338
+ * "wan-turbo", "wan-flash", "wan-videoedit" and the "wan-2.7-*" ids are
1339
+ * unrelated 2.x models with a different surface (and a different reference
1340
+ * token format — see WAN_3_DOCTRINE in @nodaro/prompts).
1341
+ */
1342
+ export const WAN_3_PROVIDERS = new Set<string>([
1343
+ "wan-3",
1344
+ "wan-3-prime",
1345
+ ])
1346
+
1347
+ export function isWan3Provider(provider: string | undefined): boolean {
1348
+ return !!provider && WAN_3_PROVIDERS.has(provider)
1349
+ }
1350
+
1351
+ /** Wan 3.0 resolution the PLATFORM renders when the param is omitted or unknown.
1352
+ * Deliberately NOT KIE's own default (1080P): 720p is the tier the bare credit
1353
+ * identifier prices, so the provider layer pins it to keep render == billed. */
1354
+ export const WAN_3_DEFAULT_RESOLUTION = "720P"
1355
+
1356
+ /**
1357
+ * SINGLE normalization for the Wan 3.0 resolution lever (KIE wire enum
1358
+ * "480P" | "720P" | "1080P"). Everything INTERNAL — the catalog, QUALITY_MAP,
1359
+ * dropdowns, RESOLUTION_DURATION_PRICING keys and every emitted credit
1360
+ * identifier — stays LOWERCASE; this helper is the ONE place the uppercase wire
1361
+ * form is produced (backend KIE payload build), the twin of
1362
+ * {@link normalizeMinimaxH3Resolution}.
1363
+ *
1364
+ * Case-insensitive. Anything else (undefined, garbage, a stale "4k") collapses
1365
+ * to {@link WAN_3_DEFAULT_RESOLUTION} — the tier the default credit identifier
1366
+ * reserves — so the billed tier can never undercut the rendered one.
1367
+ */
1368
+ export function normalizeWan3Resolution(resolution: string | undefined): "480P" | "720P" | "1080P" {
1369
+ switch (resolution?.trim().toLowerCase()) {
1370
+ case "480p": return "480P"
1371
+ case "1080p": return "1080P"
1372
+ case "720p": return "720P"
1373
+ default: return WAN_3_DEFAULT_RESOLUTION
1374
+ }
1375
+ }
1376
+
1377
+ /**
1378
+ * Google Gemini Omni family — the KIE "gemini-omni-video" market model and its
1379
+ * faster/cheaper "google/gemini-omni-flash-1-1" sibling. Both take the SAME
1380
+ * request shape (prompt + image_urls references, video-edit through the i2v
1381
+ * handle, 4/6/8/10s, 720p/1080p or a 4K band) and are dispatched through the
1382
+ * same runner, so every behavioural branch that used to compare against the
1383
+ * literal "gemini-omni-video" must ask this predicate instead — V2V routing,
1384
+ * the 7-slot reference quota, the mode swap and the aspect-ratio panels.
1385
+ *
1386
+ * Exact-membership set — NEVER match on a "gemini" prefix (the Gemini LLM ids
1387
+ * would collide).
1388
+ */
1389
+ export const GEMINI_OMNI_PROVIDERS = new Set<string>([
1390
+ "gemini-omni-video",
1391
+ "gemini-omni-flash",
1392
+ ])
1393
+
1394
+ export function isGeminiOmniProvider(provider: string | undefined): boolean {
1395
+ return !!provider && GEMINI_OMNI_PROVIDERS.has(provider)
1396
+ }
1397
+
1305
1398
  /**
1306
1399
  * Video models that are CATALOGUED and priced but have NO working dispatch
1307
1400
  * path today — selecting one fails at the router, not at validation.
@@ -1553,9 +1646,15 @@ export function maxSegmentsFor(provider: string | undefined, capSec: number): nu
1553
1646
  * param at all (inferred from the frame). Only the pure-t2v endpoint requires
1554
1647
  * a concrete ratio — the KIE provider layer coerces `adaptive` → `16:9` there
1555
1648
  * (see applyMinimaxH3Params in backend kie/video.ts).
1649
+ *
1650
+ * Wan 3.0 also defaults to `adaptive` — it is the model's own KIE default and
1651
+ * the first value in its aspect enum, so an un-set node must not silently run
1652
+ * at 16:9 while the panel displays Adaptive.
1556
1653
  */
1557
1654
  export function defaultVideoAspectRatio(provider: string | undefined): string {
1558
- return isSeedance2Provider(provider) || isMinimaxH3Provider(provider) ? "adaptive" : "16:9"
1655
+ return isSeedance2Provider(provider) || isMinimaxH3Provider(provider) || isWan3Provider(provider)
1656
+ ? "adaptive"
1657
+ : "16:9"
1559
1658
  }
1560
1659
 
1561
1660
  /**
@@ -1656,6 +1755,10 @@ export const SEEDANCE_2_R2V_MAX_AUDIO_SEC_BY_PROVIDER: Record<string, number> =
1656
1755
  // MiniMax Hailuo 3 — documented hard limit: each reference audio segment
1657
1756
  // 2-15s, ≤15s total (docs.kie.ai/market/minimax-h3/reference-to-video).
1658
1757
  "minimax-h3": 15,
1758
+ // Wan 3.0 family — documented hard limit: each reference audio clip 1-15s,
1759
+ // ≤15s combined (docs.kie.ai/market/wan/3-0-video).
1760
+ "wan-3": 15,
1761
+ "wan-3-prime": 15,
1659
1762
  }
1660
1763
 
1661
1764
  /** The verified r2v reference-audio cap (seconds) for a provider, or null when
@@ -1711,8 +1814,17 @@ export const VIDEO_REF_LIMITS_BY_PROVIDER: Record<
1711
1814
  // bills input images beyond the first 5 (11 KIE cr each) — see the
1712
1815
  // minimax-h3 credit helper in backend ee/billing.
1713
1816
  "minimax-h3": { ...SEEDANCE_2_REF_LIMITS },
1817
+ // Wan 3.0 family — all-purpose reference mode: up to 10 images / 5 videos
1818
+ // (each 1-15s, ≤15s combined) / 5 audio clips (each 1-15s, ≤15s combined).
1819
+ // The reference arrays are mutually EXCLUSIVE with first/last frame at the
1820
+ // provider (docs.kie.ai/market/wan/3-0-video).
1821
+ "wan-3": { images: 10, videos: 5, audio: 5 },
1822
+ "wan-3-prime": { images: 10, videos: 5, audio: 5 },
1714
1823
  // Multi-image reference providers.
1715
1824
  "gemini-omni-video": { images: 7, videos: 1 },
1825
+ // Gemini Omni Flash 1.1 — identical quota to the pro sibling
1826
+ // (images + 2×videos + character_ids ≤ 7).
1827
+ "gemini-omni-flash": { images: 7, videos: 1 },
1716
1828
  "kling-3-omni": { images: 7 }, // catalog/docs: "end frame + up to 7 reference images"
1717
1829
  "grok-i2v": { images: 7 }, // backend kie/models.ts maxRefImages: 7
1718
1830
  "happyhorse-ref2v": { images: 9 }, // backend kie/models.ts maxRefImages: 9
@@ -1738,10 +1850,15 @@ export const RESOLUTION_VIDEO_REF_PRICING = SEEDANCE_2_PROVIDERS
1738
1850
  /**
1739
1851
  * Video models priced by (duration × resolution) WITHOUT a video-ref dimension.
1740
1852
  * Identifier suffix: `:{resolution}`, appended after the duration tier; value =
1741
- * the provider's PRICED resolution tiers. Any resolution outside the list
1742
- * (or undefined) collapses to the FIRST entry — the provider's default tier —
1743
- * so the builder can never emit an unpriced composite (the hard-fail guard
1744
- * fuzzes the full resolution space). Distinct from
1853
+ * the provider's PRICED resolution tiers.
1854
+ *
1855
+ * Any resolution outside the list (or undefined) collapses to the provider's
1856
+ * DEFAULT tier, so the builder can never emit an unpriced composite (the
1857
+ * hard-fail guard fuzzes the full resolution space). That default is
1858
+ * {@link PRICING_DEFAULT_RESOLUTION} when the provider declares one, and
1859
+ * otherwise the FIRST entry here — so a member with a declared default (wan-3)
1860
+ * can list its tiers in the repo-standard ascending order without index 0
1861
+ * silently becoming the billed default. Distinct from
1745
1862
  * RESOLUTION_VIDEO_REF_PRICING (Seedance gen), which also adds `-ref` variants.
1746
1863
  */
1747
1864
  export const RESOLUTION_DURATION_PRICING: Record<string, readonly string[]> = {
@@ -1757,6 +1874,12 @@ export const RESOLUTION_DURATION_PRICING: Record<string, readonly string[]> = {
1757
1874
  "happyhorse": ["720p", "1080p"],
1758
1875
  "happyhorse-i2v": ["720p", "1080p"],
1759
1876
  "happyhorse-ref2v": ["720p", "1080p"],
1877
+ // Wan 3.0 / Prime — per-second billing at three published resolution rates.
1878
+ // Ascending, matching the catalog; the BILLED default is declared explicitly
1879
+ // in PRICING_DEFAULT_RESOLUTION ("720p") rather than carried by index 0, and
1880
+ // the provider layer pins the same 720P so render == billed.
1881
+ "wan-3": ["480p", "720p", "1080p"],
1882
+ "wan-3-prime": ["480p", "720p", "1080p"],
1760
1883
  }
1761
1884
 
1762
1885
  /**
@@ -1779,6 +1902,13 @@ export const NATIVE_ADAPTIVE_ASPECT: Record<string, string> = {
1779
1902
  "seedance-2-fast": "adaptive",
1780
1903
  "seedance-2-mini": "adaptive",
1781
1904
  "seedance-2-5": "adaptive",
1905
+ // Wan 3.0 family: `adaptive` is the enum DEFAULT and KIE documents it as
1906
+ // "automatically selects the ratio based on the input media and intent"
1907
+ // (docs.kie.ai/market/wan/3-0-video) — i.e. match-the-input, not merely
1908
+ // "provider default" (the grok `auto` trap). DOC-verified only; re-confirm
1909
+ // with a live job before relying on it for off-catalog ratios.
1910
+ "wan-3": "adaptive",
1911
+ "wan-3-prime": "adaptive",
1782
1912
  }
1783
1913
 
1784
1914
  /**
@@ -1877,8 +2007,9 @@ export type VideoAudioMode = "none" | "ambient" | "native_speech" | "audio_drive
1877
2007
 
1878
2008
  export interface VideoAudioCapability {
1879
2009
  mode: VideoAudioMode
1880
- /** Provider-option field carrying the on/off toggle, when user-controllable. */
1881
- field?: "generateAudio" | "sound"
2010
+ /** Provider-option field carrying the on/off toggle, when user-controllable.
2011
+ * `audio` is Wan 3.0's own boolean lever (KIE `input.audio`, default true). */
2012
+ field?: "generateAudio" | "sound" | "audio"
1882
2013
  /** Audio is always produced and can't be turned off by the user (VEO 3.x). */
1883
2014
  alwaysOn?: boolean
1884
2015
  /** Enabling audio raises the credit cost (Kling — see AUDIO_ADDON_PROVIDERS). */
@@ -1937,6 +2068,19 @@ export const VIDEO_AUDIO_CAPABILITY: Record<string, VideoAudioCapability> = {
1937
2068
  // KIE API exposes NO audio on/off parameter (audio is always produced), so
1938
2069
  // there is no toggle field — alwaysOn, like VEO.
1939
2070
  "minimax-h3": { mode: "audio_driven", alwaysOn: true },
2071
+ // Wan 3.0 family — a single boolean `audio` lever (KIE default true) produces
2072
+ // an ambient/SFX track with the clip. Classified "ambient", NOT
2073
+ // "native_speech" or "audio_driven": the schema documents no dialogue
2074
+ // guarantee, and reference audio is a generic conditioning array, not a
2075
+ // verified lip-sync transport. Upgrade only on a live probe (the kling-3.0
2076
+ // standard) — `audio_driven` would reroute the Story→Video dialogue pipeline
2077
+ // and skip the lip-sync pass. `defaultOn` mirrors the KIE default so an
2078
+ // intent-less request is described honestly; audio is priced into the uniform
2079
+ // per-second rate, so NOT cost-affecting (no `:audio` composite).
2080
+ // gemini-omni-flash is deliberately ABSENT — gemini-omni-video is absent too
2081
+ // (mode "none"), and the siblings must not disagree.
2082
+ "wan-3": { mode: "ambient", field: "audio", defaultOn: true },
2083
+ "wan-3-prime": { mode: "ambient", field: "audio", defaultOn: true },
1940
2084
  }
1941
2085
 
1942
2086
  const VIDEO_AUDIO_NONE: VideoAudioCapability = { mode: "none" }
@@ -2008,6 +2152,7 @@ export function applyVideoAudioToggle(
2008
2152
  const enabled = cap.affectsCost ? opts?.sound : (opts?.sound ?? opts?.generateAudio)
2009
2153
  if (enabled === undefined) return // no intent — keep the model's own default
2010
2154
  if (cap.field === "generateAudio") input.generate_audio = enabled
2155
+ else if (cap.field === "audio") input.audio = enabled
2011
2156
  else input.sound = enabled
2012
2157
  }
2013
2158
 
@@ -2063,6 +2208,11 @@ export const VIDEO_VARIABLE_PRICING: Record<string, "duration" | "duration+audio
2063
2208
  "minimax-h3": "duration+resolution",
2064
2209
  // Grok Imagine Video 1.5 — per-second billing split 480p/720p (no video-ref dimension).
2065
2210
  "grok-imagine-video-1.5": "duration+resolution",
2211
+ // Wan 3.0 family — per-second billing at three resolution rates (no video-ref
2212
+ // dimension: reference runs bill output seconds only). No gemini-omni-flash
2213
+ // entry, mirroring gemini-omni-video which has none either.
2214
+ "wan-3": "duration+resolution",
2215
+ "wan-3-prime": "duration+resolution",
2066
2216
  }
2067
2217
 
2068
2218
  /**
@@ -2098,6 +2248,14 @@ export const PRICING_DEFAULT_DURATION_SEC: Record<string, number> = {
2098
2248
  export const PRICING_DEFAULT_RESOLUTION: Record<string, string> = {
2099
2249
  // KIE renders 720p when `resolution` is omitted (kie/models.ts extraParams).
2100
2250
  "seedance-2-5": "720p",
2251
+ // Wan 3.0 family — KIE's OWN default is 1080P, but the provider layer pins
2252
+ // 720P (normalizeWan3Resolution / WAN_3_DEFAULT_RESOLUTION) so the rendered
2253
+ // tier equals the tier the bare identifier bills. This declaration is what
2254
+ // makes the billed default explicit instead of an array ordering: the
2255
+ // RESOLUTION_DURATION_PRICING branch consults it before falling back to the
2256
+ // first tier, so the catalog can stay ascending without repricing anything.
2257
+ "wan-3": "720p",
2258
+ "wan-3-prime": "720p",
2101
2259
  }
2102
2260
 
2103
2261
  /** HappyHorse 1.1 per-second tiers — one per allowed duration (3–15s), shared
@@ -2122,6 +2280,21 @@ const SEEDANCE_2_5_DURATION_TIERS: Array<{ maxSeconds: number; suffix: string }>
2122
2280
  (_, i) => ({ maxSeconds: i + 4, suffix: `${i + 4}s` }),
2123
2281
  )
2124
2282
 
2283
+ /**
2284
+ * Wan 3.0 per-second tiers — one per allowed duration (2-30s), shared by both
2285
+ * SKUs (the ladder shape is identical; only the seeded rates differ).
2286
+ *
2287
+ * Per-second is REQUIRED, not a nicety: the ladder snaps a request UP to the
2288
+ * first tier whose `maxSeconds` covers it and falls back to the LAST tier when
2289
+ * none does, so a coarse ladder over a 29-value range would reserve a
2290
+ * short-clip price for a 30s render. `commit_credits` only ever refunds a
2291
+ * surplus and can never collect an upward delta.
2292
+ */
2293
+ const WAN_3_DURATION_TIERS: Array<{ maxSeconds: number; suffix: string }> = Array.from(
2294
+ { length: 29 },
2295
+ (_, i) => ({ maxSeconds: i + 2, suffix: `${i + 2}s` }),
2296
+ )
2297
+
2125
2298
  /**
2126
2299
  * Duration tier breakpoints for variable-priced video models.
2127
2300
  * Maps provider → array of { maxSeconds, suffix } in ascending order.
@@ -2202,6 +2375,8 @@ export const VIDEO_DURATION_TIERS: Record<string, Array<{ maxSeconds: number; su
2202
2375
  { maxSeconds: 15, suffix: "15s" },
2203
2376
  ],
2204
2377
  "seedance-2-5": SEEDANCE_2_5_DURATION_TIERS,
2378
+ "wan-3": WAN_3_DURATION_TIERS,
2379
+ "wan-3-prime": WAN_3_DURATION_TIERS,
2205
2380
  // MiniMax Hailuo 3 — true per-second billing (KIE 36.5 cr/s @2K, 22.5 cr/s
2206
2381
  // @768P). One tier per allowed second (4-15s) so the composite identifier
2207
2382
  // maps 1:1 to the seeded price — no rounding/overcharge for any on-menu
@@ -187,6 +187,10 @@ const QUALITY_MAP: Record<string, QualityMapping> = {
187
187
  // Seedance 2.5 spans 480p/720p/1080p on KIE (1080p accepted since the
188
188
  // 2026-08-17 re-probe; 4k still rejected), so each quality rung gets its own tier.
189
189
  "seedance-2-5": { field: "resolution", values: { low: "480p", mid: "720p", high: "1080p" } },
190
+ // Wan 3.0 family — 480p / 720p / 1080p (lowercase catalog spellings; the
191
+ // uppercase KIE wire form is produced only by normalizeWan3Resolution).
192
+ "wan-3": { field: "resolution", values: { low: "480p", mid: "720p", high: "1080p" } },
193
+ "wan-3-prime": { field: "resolution", values: { low: "480p", mid: "720p", high: "1080p" } },
190
194
  "wan-2.7-i2v": { field: "resolution", values: { low: "720p", mid: "1080p", high: "1080p" } },
191
195
  "wan-2.7-t2v": { field: "resolution", values: { low: "720p", mid: "1080p", high: "1080p" } },
192
196
  "happyhorse": { field: "resolution", values: { low: "720p", mid: "1080p", high: "1080p" } },
@@ -200,6 +204,7 @@ const QUALITY_MAP: Record<string, QualityMapping> = {
200
204
  "ltx-2.3-pro": { field: "resolution", values: { low: "1080p", mid: "2k", high: "4k" } },
201
205
  "ltx-2.3-fast": { field: "resolution", values: { low: "1080p", mid: "2k", high: "4k" } },
202
206
  "gemini-omni-video": { field: "resolution", values: { low: "720p", mid: "1080p", high: "4k" } },
207
+ "gemini-omni-flash": { field: "resolution", values: { low: "720p", mid: "1080p", high: "4k" } },
203
208
  // Grok Imagine Video 1.5 — only 480p/720p (no 1080p tier); high clamps to 720p.
204
209
  "grok-imagine-video-1.5": { field: "resolution", values: { low: "480p", mid: "720p", high: "720p" } },
205
210
  // Replicate Flux 2 family — MP-based resolution (matching IMAGE_RESOLUTION_OPTIONS["flux-2-*"]).
@@ -0,0 +1,66 @@
1
+ /**
2
+ * UI-default fills for the unified video nodes — the ONE source of truth shared
3
+ * by the backend DAG payload builder, the config-panel fail-safe snaps and the
4
+ * node's hover/run strip.
5
+ *
6
+ * Several config panels RENDER a default without persisting it to node data, so
7
+ * an untouched node submits `aspectRatio` / `resolution` undefined and the
8
+ * enqueued job row (and the /v1/jobs echo) then disagrees with what actually
9
+ * renders. Worse, the panels' generic snap writes `resolutions[0]` for a stale
10
+ * value, which is the CHEAPEST tier under the repo's ascending-resolution
11
+ * convention — not necessarily the tier the model renders and bills.
12
+ *
13
+ * These helpers were local to `backend/src/services/workflow-engine/payload-builder.ts`
14
+ * until the Wan 3.0 launch gave the platform its first provider whose declared
15
+ * billing default differs from `resolutions[0]`; three surfaces then had to
16
+ * agree, so they live here.
17
+ *
18
+ * The resolution fill is per-family ON PURPOSE. Wan 3.0 reads its DECLARED
19
+ * billing default (PRICING_DEFAULT_RESOLUTION = 720p) because its catalog list
20
+ * is ascending, so an index-0 fill would write 480p — a tier both `runWan3`
21
+ * (which renders 720P) and the credit identifier (which bills the 720p row)
22
+ * disagree with. The Seedance family keeps its historical first-catalog-tier
23
+ * fill, deliberately: switching it to PRICING_DEFAULT_RESOLUTION would reprice
24
+ * live seedance-2-5 runs, whose declared default (720p) differs from the 480p
25
+ * this has always filled. That 480p-fill vs 720p-billing-default divergence on
26
+ * seedance-2-5 is a KNOWN pre-existing gap, out of scope here — do not "align"
27
+ * the branches without repricing it deliberately.
28
+ */
29
+
30
+ import {
31
+ PRICING_DEFAULT_RESOLUTION,
32
+ isSeedance2Provider,
33
+ isMinimaxH3Provider,
34
+ isWan3Provider,
35
+ isGeminiOmniProvider,
36
+ } from "./model-constants.js"
37
+ import { MODEL_CATALOG } from "./model-catalog.js"
38
+
39
+ /** `adaptive` is the aspect default for Seedance 2, MiniMax H3 and Wan 3.0. */
40
+ export function uiAspectRatioFill(provider: string): string | undefined {
41
+ return isSeedance2Provider(provider) || isMinimaxH3Provider(provider) || isWan3Provider(provider)
42
+ ? "adaptive"
43
+ : undefined
44
+ }
45
+
46
+ /** See the file docstring — per-family on purpose; never a generic
47
+ * PRICING_DEFAULT_RESOLUTION read (that would reprice seedance-2-5). */
48
+ export function uiResolutionFill(provider: string): string | undefined {
49
+ if (isWan3Provider(provider)) return PRICING_DEFAULT_RESOLUTION[provider]
50
+ if (isSeedance2Provider(provider)) return MODEL_CATALOG[provider]?.resolutions?.[0]
51
+ return undefined
52
+ }
53
+
54
+ /**
55
+ * Duration the node RENDERS and BILLS when `data.duration` is unset, for the
56
+ * families whose credit identifier declares a duration fallback that is NOT
57
+ * `durations[0]`. Wan 3.0's bare identifier is the 5s tier while its ladder
58
+ * starts at 2s; the Gemini Omni family's is the 8s tier while its ladder starts
59
+ * at 4s. Everyone else falls back to the first listed duration, so this returns
60
+ * undefined and the caller keeps `durations[0]`.
61
+ */
62
+ export function uiDurationFill(provider: string): number | undefined {
63
+ if (isWan3Provider(provider)) return 5
64
+ if (isGeminiOmniProvider(provider)) return 8
65
+ return undefined
66
+ }