@nodaro/shared 3.8.0 → 3.10.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 (53) hide show
  1. package/dist/index.cjs +325 -63
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +806 -41
  4. package/dist/index.d.ts +806 -41
  5. package/dist/index.js +295 -64
  6. package/dist/index.js.map +1 -1
  7. package/package.json +1 -1
  8. package/src/__tests__/credit-identifiers.test.ts +51 -1
  9. package/src/__tests__/gvp-supported-providers.test.ts +20 -3
  10. package/src/__tests__/model-catalog-sections.test.ts +59 -0
  11. package/src/__tests__/model-tree.test.ts +5 -2
  12. package/src/__tests__/parameter-node-value.test.ts +35 -0
  13. package/src/__tests__/pricing-default-duration.test.ts +45 -0
  14. package/src/__tests__/prompt-length-limits.test.ts +4 -0
  15. package/src/__tests__/scene3d-delivery-review.test.ts +512 -0
  16. package/src/__tests__/suno-credit-type.test.ts +46 -0
  17. package/src/__tests__/template-categories.test.ts +45 -0
  18. package/src/__tests__/video-ref-limits.test.ts +11 -2
  19. package/src/credit-identifiers.ts +64 -14
  20. package/src/i18n/camera-motions.ar.ts +5 -5
  21. package/src/i18n/camera-motions.de.ts +5 -5
  22. package/src/i18n/camera-motions.es.ts +5 -5
  23. package/src/i18n/camera-motions.fr.ts +5 -5
  24. package/src/i18n/camera-motions.he.ts +5 -5
  25. package/src/i18n/camera-motions.hi.ts +5 -5
  26. package/src/i18n/camera-motions.ja.ts +5 -5
  27. package/src/i18n/camera-motions.ko.ts +5 -5
  28. package/src/i18n/camera-motions.pt-BR.ts +5 -5
  29. package/src/i18n/camera-motions.ru.ts +5 -5
  30. package/src/i18n/camera-motions.zh-CN.ts +5 -5
  31. package/src/i18n/character-motion.ar.ts +1031 -0
  32. package/src/i18n/character-motion.de.ts +1031 -0
  33. package/src/i18n/character-motion.es.ts +1031 -0
  34. package/src/i18n/character-motion.fr.ts +1031 -0
  35. package/src/i18n/character-motion.he.ts +1031 -0
  36. package/src/i18n/character-motion.hi.ts +1031 -0
  37. package/src/i18n/character-motion.ja.ts +1031 -0
  38. package/src/i18n/character-motion.ko.ts +1031 -0
  39. package/src/i18n/character-motion.pt-BR.ts +1031 -0
  40. package/src/i18n/character-motion.ru.ts +1031 -0
  41. package/src/i18n/character-motion.zh-CN.ts +1031 -0
  42. package/src/i18n/types.ts +1 -0
  43. package/src/index.ts +28 -0
  44. package/src/model-catalog.ts +88 -35
  45. package/src/model-constants.ts +118 -24
  46. package/src/node-execution-state.ts +95 -0
  47. package/src/parameter-node-value.ts +33 -0
  48. package/src/presentation-utils.ts +1 -0
  49. package/src/pro-3d-render.ts +159 -0
  50. package/src/scene3d-delivery-notes.ts +490 -0
  51. package/src/scene3d-v2-plan.ts +8 -2
  52. package/src/smart-cut-windows.ts +15 -10
  53. package/src/template-categories.ts +67 -0
package/src/i18n/types.ts CHANGED
@@ -120,6 +120,7 @@ export const I18N_CATALOGS = [
120
120
  "camera-format",
121
121
  "camera-motions",
122
122
  "character-fx",
123
+ "character-motion",
123
124
  "color-look",
124
125
  "composition-effects",
125
126
  "era",
package/src/index.ts CHANGED
@@ -89,6 +89,12 @@ export {
89
89
  AI_WRITER_PROVIDERS,
90
90
  QA_CHECK_PROVIDERS,
91
91
  SUNO_MODELS,
92
+ SUNO_ACTIVE_MODELS,
93
+ SUNO_LEGACY_MODELS,
94
+ DEFAULT_SUNO_MODEL,
95
+ SUNO_DURATION_MODELS,
96
+ isLegacySunoModel,
97
+ sunoModelHonoursDuration,
92
98
  SUNO_ADD_TRACK_MODELS,
93
99
  VOICE_DESIGN_MODELS,
94
100
  DEFAULT_VOICE_DESIGN_MODEL,
@@ -161,6 +167,7 @@ export {
161
167
  DEFAULT_VIDEO_DURATION_SEC,
162
168
  applyDefaultVideoSelection,
163
169
  PRICING_DEFAULT_DURATION_SEC,
170
+ pricedOutputDurationSec,
164
171
  PRICING_DEFAULT_RESOLUTION,
165
172
  } from "./model-constants.js"
166
173
 
@@ -247,6 +254,7 @@ export {
247
254
  pricedVideoSelection,
248
255
  buildMotionCreditModelIdentifier,
249
256
  sunoCreditType,
257
+ SUNO_VERSION_CREDIT_KEYS,
250
258
  SUNO_VERSION_PRICED_OPERATIONS,
251
259
  SUNO_SELECT_OPERATIONS,
252
260
  } from "./credit-identifiers.js"
@@ -540,6 +548,8 @@ export type { LocationCatalogRef } from "./location-preset-catalog-map.js"
540
548
  export {
541
549
  PARAMETER_NODE_TYPES,
542
550
  HINT_EXEMPT_PARAMETER_TYPES,
551
+ VIDEO_ONLY_PARAMETER_NODE_TYPES,
552
+ EXECUTION_GRAPH_COMPOSED_PARAMETER_TYPES,
543
553
  getParameterValue,
544
554
  setRegisteredPersonPackFields,
545
555
  } from "./parameter-node-value.js"
@@ -724,6 +734,8 @@ export {
724
734
  MODEL_RECOMMENDATIONS,
725
735
  listModels,
726
736
  groupByFamily,
737
+ groupByKindAndFamily,
738
+ MODEL_KINDS,
727
739
  getModel,
728
740
  validateModelInput,
729
741
  // Frontend picker derivers
@@ -970,6 +982,16 @@ export type { VoiceChangerModel } from "./voice-changer-models.js"
970
982
  // --- Node presets ---
971
983
  export { EXECUTION_DATA_KEYS, TRANSIENT_RUNTIME_KEYS, stripTransientRuntimeData } from "./node-runtime-keys.js"
972
984
 
985
+ // --- Execution node state (wire contract) ---
986
+ export {
987
+ OUTPUT_BEARING_NODE_STATUSES,
988
+ nodeStateMayCarryOutput,
989
+ } from "./node-execution-state.js"
990
+ export type {
991
+ NodeExecutionStatus,
992
+ NodeExecutionStateWire,
993
+ } from "./node-execution-state.js"
994
+
973
995
  export {
974
996
  MODEL_PARAM_NODE_TYPES,
975
997
  normalizeNodeModelParams,
@@ -996,6 +1018,9 @@ export * from "./reference-sheet/index.js"
996
1018
  // --- Reference Board (templates + provider constant) ---
997
1019
  export * from "./reference-board-templates.js"
998
1020
 
1021
+ // --- Marketplace template categories (the eight use cases) ---
1022
+ export * from "./template-categories.js"
1023
+
999
1024
  // --- Model tree (derive node targets + product-line grouping for the Models tab) ---
1000
1025
  export * from "./model-tree.js"
1001
1026
 
@@ -1064,6 +1089,9 @@ export * from "./scene3d-v2.js"
1064
1089
  export * from "./scene3d-v2-plan.js"
1065
1090
  export * from "./scene3d-v2-resources.js"
1066
1091
  export * from "./scene3d-camera-track.js"
1092
+ // --- What an authoring run says about its own answer: assumptions, summary,
1093
+ // repair count. Additive and optional on every Scene3D authoring lane. ---
1094
+ export * from "./scene3d-delivery-notes.js"
1067
1095
  // --- 3D Render Pro: one durable operation, scene + video in one result ---
1068
1096
  export * from "./pro-3d-render.js"
1069
1097
  // --- Scene3D render pricing: which frame-size tier a render settles at.
@@ -52,7 +52,6 @@ export type ModelMode =
52
52
  | "music"
53
53
  | "sfx"
54
54
  | "stt"
55
- | "voice-clone"
56
55
  | "voice-design"
57
56
  | "voice-changer"
58
57
  | "isolation"
@@ -207,7 +206,7 @@ export const MODEL_RECOMMENDATIONS: readonly ModelRecommendation[] = [
207
206
  { intent: "best cinematic video", modelIds: ["veo3", "kling-3.0", "seedance-2"], note: "VEO 3.1 Quality for premium narrative; Kling 3.0 for music-synced motion; Seedance 2 for reference-driven consistency." },
208
207
  { intent: "cheap batch video clips", modelIds: ["veo3.1", "wan-turbo", "bytedance-lite"], note: "VEO 3.1 Fast is the best price/quality balance with native audio." },
209
208
  { intent: "video with start + end frame", modelIds: ["veo3", "veo3.1", "kling-turbo", "minimax", "hailuo-standard", "seedance-2"], note: "All listed support an end frame; VEO uses imageUrls[start, end]." },
210
- { intent: "music / song generation", modelIds: ["suno-v5", "suno"], note: "Suno v5 has better vocal quality at the same price." },
209
+ { intent: "music / song generation", modelIds: ["suno-v6", "suno-v6_wild", "suno-v6_mini", "suno-v5_5"], note: "V6 is the default flagship; V6 Wild for bolder, less predictable results; V6 Mini when speed matters; v5.5 / v5 / v4 keep their own character. Same price." },
211
210
  { intent: "voice over / narration", modelIds: ["elevenlabs-v3", "elevenlabs-turbo"], note: "v3 supports [audio tags] for emotion; Turbo is cheaper for plain narration." },
212
211
  { intent: "lip-sync a portrait to audio", modelIds: ["kling-avatar-pro", "kling-avatar", "infinitalk"], note: "Pro for best mouth shape; InfiniTalk for resolution control." },
213
212
  { intent: "transcription / captions", modelIds: ["elevenlabs-stt"], note: "Word-level timestamps available." },
@@ -755,23 +754,8 @@ const IMAGE_MODELS: Record<string, ModelCatalogEntry> = {
755
754
  { identifier: "ideogram-remix:QUALITY", credits: 60, note: "best quality" },
756
755
  ],
757
756
  },
758
- "ideogram-reframe": {
759
- id: "ideogram-reframe",
760
- kind: "image",
761
- modes: ["edit"] as const,
762
- family: "Ideogram",
763
- label: "Ideogram Reframe",
764
- series: "Ideogram",
765
- description: "Outpaint / reframe to a new aspect ratio while preserving subject.",
766
- useCases: ["outpaint", "reframe"],
767
- features: ["reference-image"],
768
- aspectRatios: IDEOGRAM_RATIOS,
769
- pricing: [
770
- { identifier: "ideogram-reframe", credits: 18, note: "BALANCED default" },
771
- { identifier: "ideogram-reframe:TURBO", credits: 18, note: "fastest" },
772
- { identifier: "ideogram-reframe:QUALITY", credits: 18, note: "best quality" },
773
- ],
774
- },
757
+ // `ideogram-reframe` was retired 2026-09-15 — KIE's `ideogram/v3-reframe`
758
+ // fails every task upstream (#1331); see IDEOGRAM_PROVIDERS in model-constants.
775
759
 
776
760
  // ── Google Imagen ──
777
761
  "imagen4": {
@@ -1238,9 +1222,17 @@ const VIDEO_MODELS: Record<string, ModelCatalogEntry> = {
1238
1222
  family: "Google",
1239
1223
  label: "VEO 3.1 Quality",
1240
1224
  series: "VEO",
1241
- description: "Google VEO 3.1 Quality — premium cinematic video. 4/6/8s clips, optional end frame, native audio. Flat per-generation pricing across durations.",
1225
+ description: "Google VEO 3.1 Quality — premium cinematic video. 4/6/8s clips, optional end frame, native audio. No reference-to-video mode (Fast/Lite only). Flat per-generation pricing across durations.",
1242
1226
  useCases: ["cinematic", "premium", "narrative"],
1243
- features: ["end-frame", "audio", "reference-image"],
1227
+ // NO "reference-image": KIE serves REFERENCE_2_VIDEO on the Fast and Lite
1228
+ // SKUs only. Its own words, on a production job (2026-09-04, app-reports
1229
+ // lane G): "Reference to video only supports the Veo Fast model and Veo
1230
+ // Lite model." Until 2026-09-15 this entry claimed the feature, so the
1231
+ // editor offered the reference handles, the adapter sent
1232
+ // generationType: "REFERENCE_2_VIDEO", and the call came back 422 every
1233
+ // time — after credits were reserved. `veo3.1` (KIE `veo3_fast`) and
1234
+ // `veo3_lite` DO serve it and keep the flag.
1235
+ features: ["end-frame", "audio"],
1244
1236
  durations: [4, 6, 8],
1245
1237
  aspectRatios: VIDEO_RATIOS_HV,
1246
1238
  // 720p (default) + 1080p inline. 4K generates the base at 1080p then chains
@@ -2204,7 +2196,7 @@ const VIDEO_MODELS: Record<string, ModelCatalogEntry> = {
2204
2196
  id: "omnihuman-1-5",
2205
2197
  kind: "video",
2206
2198
  modes: ["lip-sync"] as const,
2207
- family: "ByteDance",
2199
+ family: "Bytedance",
2208
2200
  label: "OmniHuman 1.5",
2209
2201
  series: "OmniHuman",
2210
2202
  description: "Premium prompt-directed talking avatar from a still image + audio. 720p / 1080p, up to 60s. People, pets, anime.",
@@ -2468,17 +2460,8 @@ const AUDIO_MODELS: Record<string, ModelCatalogEntry> = {
2468
2460
  },
2469
2461
 
2470
2462
  // ── ElevenLabs voice utilities ──
2471
- "voice-clone": {
2472
- id: "voice-clone",
2473
- kind: "audio",
2474
- modes: ["voice-clone"] as const,
2475
- family: "ElevenLabs",
2476
- label: "Voice Clone (Instant)",
2477
- series: "ElevenLabs",
2478
- description: "Clone a voice from a short reference clip. Instant clone via direct ElevenLabs API.",
2479
- useCases: ["voice-clone", "personalization"],
2480
- pricing: [{ identifier: "voice-clone", credits: 50 }],
2481
- },
2463
+ // (voice cloning was retired platform-wide on 2026-09-15 — no catalog entry,
2464
+ // no price, no MCP tool; existing clones still resolve as TTS voice ids.)
2482
2465
  "elevenlabs-voice-design": {
2483
2466
  id: "elevenlabs-voice-design",
2484
2467
  kind: "audio",
@@ -2534,6 +2517,17 @@ const AUDIO_MODELS: Record<string, ModelCatalogEntry> = {
2534
2517
  useCases: ["dubbing", "multilingual"],
2535
2518
  pricing: [{ identifier: "elevenlabs-dubbing", credits: 40, note: "per minute of the dubbed span (min 1)" }],
2536
2519
  },
2520
+ "elevenlabs-dubbing-v2": {
2521
+ id: "elevenlabs-dubbing-v2",
2522
+ kind: "audio",
2523
+ modes: ["dubbing"] as const,
2524
+ family: "ElevenLabs",
2525
+ label: "ElevenLabs Dubbing v2",
2526
+ series: "ElevenLabs",
2527
+ description: "Translate + dub audio or a whole video into a new language — video in, dubbed video out. Async.",
2528
+ useCases: ["dubbing", "multilingual"],
2529
+ pricing: [{ identifier: "elevenlabs-dubbing-v2", credits: 1100, note: "per minute of the dubbed span (min 1)" }],
2530
+ },
2537
2531
  "elevenlabs-forced-alignment": {
2538
2532
  id: "elevenlabs-forced-alignment",
2539
2533
  kind: "audio",
@@ -2580,7 +2574,7 @@ const AUDIO_MODELS: Record<string, ModelCatalogEntry> = {
2580
2574
  label: "Suno v5",
2581
2575
  series: "Suno",
2582
2576
  dataValue: "V5",
2583
- description: "Newer Suno v5 — better vocal quality, more genres. Same price as v4.",
2577
+ description: "Suno v5 — better vocal quality than v4, more genres. Same price.",
2584
2578
  useCases: ["music", "song", "vocals", "premium"],
2585
2579
  pricing: [{ identifier: "suno-v5", credits: 30, note: "per generation" }],
2586
2580
  },
@@ -2592,11 +2586,47 @@ const AUDIO_MODELS: Record<string, ModelCatalogEntry> = {
2592
2586
  label: "Suno v5.5",
2593
2587
  series: "Suno",
2594
2588
  dataValue: "V5_5",
2595
- description: "Suno v5.5 — latest model with improved audio quality and expressiveness.",
2589
+ description: "Suno v5.5 — improved audio quality and expressiveness over v5.",
2596
2590
  useCases: ["music", "song", "vocals", "premium"],
2597
2591
  pricing: [{ identifier: "suno-v5_5", credits: 30, note: "per generation" }],
2592
+ },
2593
+ "suno-v6": {
2594
+ id: "suno-v6",
2595
+ kind: "audio",
2596
+ modes: ["music"] as const,
2597
+ family: "Suno",
2598
+ label: "Suno V6",
2599
+ series: "Suno",
2600
+ dataValue: "V6",
2601
+ description: "Suno V6 — greater musical expression with more natural vocals and richer details. The flagship and the default.",
2602
+ useCases: ["music", "song", "vocals", "premium"],
2603
+ pricing: [{ identifier: "suno-v6", credits: 30, note: "per generation" }],
2598
2604
  featured: true,
2599
2605
  },
2606
+ "suno-v6_wild": {
2607
+ id: "suno-v6_wild",
2608
+ kind: "audio",
2609
+ modes: ["music"] as const,
2610
+ family: "Suno",
2611
+ label: "Suno V6 Wild",
2612
+ series: "Suno",
2613
+ dataValue: "V6_WILD",
2614
+ description: "Suno V6 Wild — pushes creative boundaries for bolder, more distinctive musical expression; more varied, less predictable results.",
2615
+ useCases: ["music", "song", "vocals", "experimental"],
2616
+ pricing: [{ identifier: "suno-v6_wild", credits: 30, note: "per generation" }],
2617
+ },
2618
+ "suno-v6_mini": {
2619
+ id: "suno-v6_mini",
2620
+ kind: "audio",
2621
+ modes: ["music"] as const,
2622
+ family: "Suno",
2623
+ label: "Suno V6 Mini",
2624
+ series: "Suno",
2625
+ dataValue: "V6_MINI",
2626
+ description: "Suno V6 Mini — lightweight and fast, balancing quality and speed for effortless creation.",
2627
+ useCases: ["music", "song", "vocals", "fast"],
2628
+ pricing: [{ identifier: "suno-v6_mini", credits: 30, note: "per generation" }],
2629
+ },
2600
2630
  }
2601
2631
 
2602
2632
  // =============================================================================
@@ -2642,6 +2672,29 @@ export function groupByFamily(
2642
2672
  return Array.from(groups.entries()).map(([family, models]) => ({ family, models }))
2643
2673
  }
2644
2674
 
2675
+ export const MODEL_KINDS = ["image", "video", "audio"] as const satisfies readonly ModelKind[]
2676
+
2677
+ /**
2678
+ * The Image / Video / Audio envelope every model-discovery surface renders
2679
+ * (`GET /v1/models`, MCP `list_models`): each model is filed under ITS OWN
2680
+ * kind, then by vendor family within that kind — so a vendor that ships more
2681
+ * than one kind (Google: Imagen + VEO, ByteDance: Seedream + Seedance, …)
2682
+ * appears once per kind it actually ships.
2683
+ *
2684
+ * This is the ONE grouping both surfaces call. Grouping by family first and
2685
+ * letting the family's first model pick the section filed every VEO, Seedance,
2686
+ * Wan and ByteDance video model under "image" whenever the call carried no
2687
+ * `kind` filter (#1332) — an explicit `kind` pre-filtered the list and hid it.
2688
+ * Empty sections are omitted; section order is fixed image → video → audio.
2689
+ */
2690
+ export function groupByKindAndFamily(
2691
+ entries: ModelCatalogEntry[],
2692
+ ): Array<{ kind: ModelKind; families: Array<{ family: string; models: ModelCatalogEntry[] }> }> {
2693
+ return MODEL_KINDS
2694
+ .map((kind) => ({ kind, families: groupByFamily(entries.filter((m) => m.kind === kind)) }))
2695
+ .filter((section) => section.families.length > 0)
2696
+ }
2697
+
2645
2698
  export function getModel(id: string): ModelCatalogEntry | undefined {
2646
2699
  return MODEL_CATALOG[id]
2647
2700
  }
@@ -91,14 +91,20 @@ export const MAX_IMAGE_PROMPT_CHARS_BY_PROVIDER: Record<string, number> = {
91
91
  "seedream-5-lite-i2i": 3000, // docs.kie.ai/market/seedream-5-lite-image-to-image (NB: t2i sibling is 1000)
92
92
  "qwen": 3000, // docs.kie.ai/market/qwen/text-to-image
93
93
  "qwen-edit": 2000, // docs.kie.ai/market/qwen/image-edit
94
+ // Z-Image is the SHORTEST of the family — "maxLength: 1000" in its schema
95
+ // (docs.kie.ai/market/z-image/z-image, re-fetched 2026-09-15). It was listed
96
+ // below as "verified == 5000", which is how three production runs on
97
+ // 2026-09-07 sent a workflow-assembled prompt straight into KIE's
98
+ // `{"code":500,"msg":"The text length cannot exceed the maximum limit"}`.
99
+ "z-image": 1000, // docs.kie.ai/market/z-image/z-image
94
100
  // verified == 5000 default (no entry needed): imagen4(-fast/-ultra), nano-banana,
95
101
  // nano-banana-edit, flux, flux-flex, gpt-image-2, ideogram-v3/-edit/-remix,
96
- // z-image, grok, qwen-i2i, seedream-5-pro, seedream-5-pro-i2i
102
+ // grok, qwen-i2i, seedream-5-pro, seedream-5-pro-i2i
97
103
  // (docs.kie.ai/market/seedream/5-pro-text-to-image + 5-pro-image-to-image).
98
104
  // grok-i2i: doc states 390000 (78× its t2i sibling) — treated as a KIE schema
99
105
  // typo and left at the 5000 default per the sanity-cap decision.
100
106
  // UNVERIFIED (no limit stated in schema) → 5000 default: flux-kontext(-max)
101
- // gpt-image, gpt-image-i2i, flux-i2i, flux-pro-i2i, ideogram-reframe.
107
+ // gpt-image, gpt-image-i2i, flux-i2i, flux-pro-i2i.
102
108
  }
103
109
 
104
110
  /** Max assembled image-prompt length (chars) for a provider: its verified
@@ -119,10 +125,10 @@ export const VIDEO_PROMPT_MAX = 8000
119
125
 
120
126
  /**
121
127
  * Suno prompt / lyrics / content ceiling — the LARGEST any Suno version accepts
122
- * in custom mode (V4.5 / V4.5PLUS / V4.5ALL / V5 / V5.5 = 5000). NOT the route
128
+ * in custom mode (V6 family and V4.5+ / V5 / V5.5 = 5000). NOT the route
123
129
  * Zod bound — the routes bind at {@link SUNO_HARD_CEILING} and clamp to the
124
130
  * per-version cap via {@link getMaxSunoPromptChars} (3000 non-custom for every
125
- * version; in custom mode 3000 for V4/V3.5, 5000 for V4.5+/V5) before the job
131
+ * version; in custom mode 3000 for V4/V3.5, 5000 for V4.5+/V5/V6) before the job
126
132
  * is built. This constant is shared with the editor `maxLength` / counter
127
133
  * (warn-don't-block at the per-version cap). `style` and `title` have their
128
134
  * own caps ({@link getMaxSunoStyleChars} / {@link SUNO_TITLE_MAX}).
@@ -310,9 +316,11 @@ export function getMaxTtsChars(provider: string | undefined): number {
310
316
  * SUNO_TEXT_MAX} is 5000 today — the largest per-version prompt cap; the
311
317
  * route Zod bound is {@link SUNO_HARD_CEILING}.)
312
318
  * - prompt / lyrics: 3000 in non-custom mode (all versions); in custom mode
313
- * 3000 for V4/V3.5 and 5000 for V4.5 / V4.5PLUS / V4.5ALL / V5 / V5.5.
314
- * - style: 200 for V4/V3.5, 1000 for V4.5+.
315
- * - title: 80 (all versions).
319
+ * 3000 for V4/V3.5 and 5000 for V4.5 / V4.5PLUS / V4.5ALL / V5 / V5.5 and
320
+ * the whole V6 family (V6 / V6_WILD / V6_MINI).
321
+ * - style: 200 for V4/V3.5, 1000 for V4.5+ and V6.
322
+ * - title: 80 (all versions; the extend docs quote 100 for V6 — we keep the
323
+ * stricter generate cap so one value is right everywhere).
316
324
  */
317
325
  export const SUNO_TITLE_MAX = 80
318
326
 
@@ -506,7 +514,6 @@ export const MODELS_WITH_REFERENCE_IMAGE_SUPPORT = new Set([
506
514
  "flux-kontext-max",
507
515
  "ideogram-edit",
508
516
  "ideogram-remix",
509
- "ideogram-reframe",
510
517
  "qwen-i2i",
511
518
  "qwen-edit",
512
519
  "seedream-edit",
@@ -586,7 +593,6 @@ export const REF_IMAGE_MAX_LIMITS: Record<string, number> = {
586
593
  "flux-kontext-max": 1,
587
594
  "ideogram-edit": 1,
588
595
  "ideogram-remix": 1,
589
- "ideogram-reframe": 1,
590
596
  "qwen-i2i": 1,
591
597
  "qwen-edit": 1,
592
598
  "grok-i2i": 1,
@@ -660,7 +666,6 @@ export const VARIABLE_PRICING_MODELS: Record<string, "quality" | "resolution" |
660
666
  "topaz-image-upscale": "resolution",
661
667
  "ideogram-edit": "rendering-speed",
662
668
  "ideogram-remix": "rendering-speed",
663
- "ideogram-reframe": "rendering-speed",
664
669
  "ideogram-v3": "rendering-speed",
665
670
  "wan-2.7": "resolution",
666
671
  "wan-2.7-pro": "resolution",
@@ -687,7 +692,12 @@ export const RESOLUTION_2K_4K_TIERED_PROVIDERS = new Set([
687
692
  ])
688
693
 
689
694
  // Ideogram family models with TURBO/QUALITY pricing variants
690
- export const IDEOGRAM_PROVIDERS = new Set(["ideogram-edit", "ideogram-remix", "ideogram-reframe", "ideogram-v3"])
695
+ // `ideogram-reframe` (KIE `ideogram/v3-reframe`) was retired 2026-09-15: KIE no
696
+ // longer documents the model and every task — the minimal documented payload
697
+ // included — fails upstream with "[500] internal error" (#1331). Its price rows
698
+ // are removed by migration 424; re-add everywhere per the Provider Enum Sync
699
+ // table if KIE brings it back.
700
+ export const IDEOGRAM_PROVIDERS = new Set(["ideogram-edit", "ideogram-remix", "ideogram-v3"])
691
701
 
692
702
  // =====================================================================
693
703
  // Provider arrays (single source of truth for route Zod validation)
@@ -742,7 +752,6 @@ export const IMAGE_I2I_PROVIDERS = [
742
752
  "grok-2-i2i",
743
753
  "ideogram-edit",
744
754
  "ideogram-remix",
745
- "ideogram-reframe",
746
755
  "qwen-i2i",
747
756
  "qwen-edit",
748
757
  "seedream-edit",
@@ -1229,19 +1238,72 @@ export const QA_CHECK_PROVIDERS = [
1229
1238
  ] as const
1230
1239
  export type QaCheckProvider = typeof QA_CHECK_PROVIDERS[number]
1231
1240
 
1232
- /** Suno model versions */
1233
- export const SUNO_MODELS = [
1234
- "V4",
1235
- "V4_5",
1241
+ /**
1242
+ * The current Suno generation (docs.kie.ai/suno-api/generate-music, 2026-09:
1243
+ * the V6 family), listed first in every picker; V6 is the default.
1244
+ *
1245
+ * - V6 — flagship: greater musical expression, more natural vocals, richer details.
1246
+ * - V6_WILD — bolder, more distinctive, less predictable output for exploration.
1247
+ * - V6_MINI — lightweight and fast; quality/speed balance.
1248
+ *
1249
+ * This is also the set that honours `duration` ({@link SUNO_DURATION_MODELS}).
1250
+ */
1251
+ export const SUNO_ACTIVE_MODELS = ["V6", "V6_WILD", "V6_MINI"] as const
1252
+ export type SunoActiveModel = typeof SUNO_ACTIVE_MODELS[number]
1253
+
1254
+ /**
1255
+ * The earlier Suno generations. Still offered in every picker as ordinary
1256
+ * choices (users report each has its own character), still accepted by every
1257
+ * route, and confirmed generating on 2026-09-11. KIE's docs tag them
1258
+ * "Discontinued"; if KIE ever stops serving one, coerce it to
1259
+ * {@link DEFAULT_SUNO_MODEL} at the provider send seam — never remove it from
1260
+ * {@link SUNO_MODELS}, which would 400 every saved music workflow.
1261
+ */
1262
+ export const SUNO_LEGACY_MODELS = [
1263
+ "V5_5",
1264
+ "V5",
1236
1265
  "V4_5PLUS",
1237
1266
  "V4_5ALL",
1238
- "V5",
1239
- "V5_5",
1267
+ "V4_5",
1268
+ "V4",
1240
1269
  ] as const
1270
+ export type SunoLegacyModel = typeof SUNO_LEGACY_MODELS[number]
1271
+
1272
+ /**
1273
+ * Every Suno model version — the picker order, the route / MCP Zod enum and
1274
+ * the `data.model` type on Suno nodes. Current generation first (V6 = default),
1275
+ * then the earlier generations newest-first.
1276
+ */
1277
+ export const SUNO_MODELS = [...SUNO_ACTIVE_MODELS, ...SUNO_LEGACY_MODELS] as const
1241
1278
  export type SunoModel = typeof SUNO_MODELS[number]
1242
1279
 
1243
- /** Suno models that support add-instrumental / add-vocals operations */
1244
- export const SUNO_ADD_TRACK_MODELS = ["V4_5PLUS", "V5", "V5_5"] as const
1280
+ /** The Suno version used when a caller omits `model` — KIE's own default. */
1281
+ export const DEFAULT_SUNO_MODEL = "V6" satisfies SunoActiveModel
1282
+
1283
+ export function isLegacySunoModel(model: string | undefined): model is SunoLegacyModel {
1284
+ return (SUNO_LEGACY_MODELS as readonly string[]).includes(model ?? "")
1285
+ }
1286
+
1287
+ /**
1288
+ * Suno versions that HONOUR `duration` (custom mode only). Per the generate /
1289
+ * extend docs the field "will only take effect when the model is V6, V6_MINI,
1290
+ * or V6_WILD" — V5_5 was struck from that sentence when V6 shipped. The
1291
+ * provider client's send-gate (`sunoGenerate` in suno-client.ts) is the single
1292
+ * chokepoint that consults this set; the editor uses it only to decide whether
1293
+ * to SHOW the duration field.
1294
+ */
1295
+ export const SUNO_DURATION_MODELS: ReadonlySet<string> = new Set<SunoModel>(SUNO_ACTIVE_MODELS)
1296
+
1297
+ export function sunoModelHonoursDuration(model: string | undefined): boolean {
1298
+ return SUNO_DURATION_MODELS.has(model ?? "")
1299
+ }
1300
+
1301
+ /**
1302
+ * Suno models accepted by add-instrumental / add-vocals (docs enum: the V6
1303
+ * family plus V4_5PLUS / V5 / V5_5). Same current-then-earlier order as
1304
+ * {@link SUNO_MODELS}.
1305
+ */
1306
+ export const SUNO_ADD_TRACK_MODELS = [...SUNO_ACTIVE_MODELS, "V4_5PLUS", "V5", "V5_5"] as const
1245
1307
  export type SunoAddTrackModel = typeof SUNO_ADD_TRACK_MODELS[number]
1246
1308
 
1247
1309
  /** Voice design models */
@@ -1310,7 +1372,7 @@ export const I2I_STRENGTH_SUPPORT: Record<string, { min: number; max: number; st
1310
1372
 
1311
1373
  /** Models that accept a seed parameter for reproducible generation */
1312
1374
  export const SEED_SUPPORT = new Set([
1313
- "ideogram-remix", "ideogram-reframe", "ideogram-v3",
1375
+ "ideogram-remix", "ideogram-v3",
1314
1376
  "qwen", "qwen-i2i", "qwen-edit",
1315
1377
  "flux", "flux-flex", "flux-i2i", "flux-pro-i2i", "flux-kontext", "flux-kontext-max",
1316
1378
  "flux-2-klein", "kontext-multi",
@@ -1318,7 +1380,7 @@ export const SEED_SUPPORT = new Set([
1318
1380
 
1319
1381
  /** Ideogram models that support rendering_speed selection (TURBO/BALANCED/QUALITY) */
1320
1382
  export const RENDERING_SPEED_SUPPORT = new Set([
1321
- "ideogram-remix", "ideogram-reframe", "ideogram-v3",
1383
+ "ideogram-remix", "ideogram-v3",
1322
1384
  ])
1323
1385
 
1324
1386
  /** Models that accept guidance_scale for controlling prompt adherence */
@@ -1924,8 +1986,13 @@ export const VIDEO_REF_LIMITS_BY_PROVIDER: Record<
1924
1986
  "kling-3-omni": { images: 7 }, // catalog/docs: "end frame + up to 7 reference images"
1925
1987
  "grok-i2v": { images: 7 }, // backend kie/models.ts maxRefImages: 7
1926
1988
  "happyhorse-ref2v": { images: 9 }, // backend kie/models.ts maxRefImages: 9
1927
- // VEO 3.x — REFERENCE_2_VIDEO path caps refs at 3 (kie/video.ts slice(0, 3)).
1928
- "veo3": { images: 3 },
1989
+ // VEO 3.x — REFERENCE_2_VIDEO path caps refs at 3 (kie/video.ts slices to
1990
+ // this number). `veo3` (VEO 3.1 QUALITY) is deliberately ABSENT: KIE serves
1991
+ // reference-to-video on the Fast and Lite SKUs only and rejects it on
1992
+ // Quality with "Reference to video only supports the Veo Fast model and Veo
1993
+ // Lite model." (production, 2026-09-04). Absent ⇒ 0 ⇒ the handle dims, the
1994
+ // `connectedReferences` assembly strips image tokens, and kie/video.ts never
1995
+ // flips a Quality call to REFERENCE_2_VIDEO.
1929
1996
  "veo3.1": { images: 3 },
1930
1997
  "veo3_lite": { images: 3 },
1931
1998
  // NOTE: wan-i2v / hailuo-2.3[-pro] / bytedance-pro[-fast] / grok-imagine-video-1.5
@@ -2458,6 +2525,33 @@ export const VIDEO_VARIABLE_PRICING: Record<string, "duration" | "duration+audio
2458
2525
  */
2459
2526
  export const PRICING_DEFAULT_DURATION_SEC: Record<string, number> = {
2460
2527
  "minimax-h3": 6,
2528
+ // KIE renders 8s when `duration` is omitted (kie/models.ts extraParams), and
2529
+ // Seedance 2.5 prices one tier per second across 4–30s, so the 5s fallback
2530
+ // billed a 5s tier against an 8s render — and under-reserved every
2531
+ // reference-video run's output seconds by three (#1397).
2532
+ "seedance-2-5": 8,
2533
+ // Same shape: KIE renders 8s by default and the ladder is priced per second,
2534
+ // so the 5s fallback billed a 5s tier against an 8s render (caught by the
2535
+ // render-default ↔ priced-tier invariant in
2536
+ // backend/src/providers/__tests__/pricing-default-duration-sync.test.ts).
2537
+ "grok-imagine-video-1.5": 8,
2538
+ }
2539
+
2540
+ /**
2541
+ * The output seconds a request is PRICED at: the requested duration when the
2542
+ * caller gave one, else the provider's own default render length
2543
+ * ({@link PRICING_DEFAULT_DURATION_SEC}), else the historical 5s fallback.
2544
+ *
2545
+ * The ONE source for `buildVideoCreditModelIdentifier`'s tier AND for every
2546
+ * dynamic reservation that scales by output seconds (the Seedance 2 and
2547
+ * MiniMax Hailuo 3 reference-video overrides on both the route and the DAG
2548
+ * lane) — a literal `?? 5` in any of those places re-opens the gap this map
2549
+ * closes.
2550
+ */
2551
+ export function pricedOutputDurationSec(provider: string, requested: number | string | undefined): number {
2552
+ const fallback = PRICING_DEFAULT_DURATION_SEC[provider] ?? 5
2553
+ const parsed = typeof requested === "string" ? parseInt(requested, 10) : requested
2554
+ return parsed === undefined || Number.isNaN(parsed) ? fallback : parsed
2461
2555
  }
2462
2556
 
2463
2557
  /**
@@ -0,0 +1,95 @@
1
+ /**
2
+ * The wire contract for one node's entry in an execution's `nodeStates` map —
3
+ * the shape the orchestrator persists on `workflow_executions.node_states`,
4
+ * emits on the SSE execution stream, and every client reads back.
5
+ *
6
+ * Why this lives here rather than in the backend
7
+ * ----------------------------------------------
8
+ * The shape had THREE independent declarations — the orchestrator's rich
9
+ * `NodeExecutionState` (workflow-engine/types.ts), the SDK's loose one
10
+ * (`packages/client/src/resources/executions.ts`) and the editor's local copy
11
+ * (`workflow-editor/run-handlers.ts`) — and nothing tied them together. The
12
+ * field that made that expensive is `output`: it used to be an unstated
13
+ * convention that only a COMPLETED node carries one, so every consumer read it
14
+ * exclusively under `status === "completed"` and a failed node's retained
15
+ * result had nowhere to travel.
16
+ *
17
+ * This module states the rule once. The backend keeps its richer `NodeOutput`
18
+ * typing (its `NodeExecutionState` must stay ASSIGNABLE to the wire shape — a
19
+ * type-level test pins that); the SDK and the editor extend/import it.
20
+ */
21
+
22
+ /**
23
+ * The status a node reports inside an execution.
24
+ *
25
+ * `cancelled` is deliberately absent: a cancelled child job is mapped onto
26
+ * `skipped` by the reconcile lane (see `lib/reconcile/node-states.ts`), and a
27
+ * HELD job keeps `status: "running"` with the `awaitingReview` sidecar rather
28
+ * than adding a member here.
29
+ */
30
+ export type NodeExecutionStatus =
31
+ | "pending"
32
+ | "running"
33
+ | "completed"
34
+ | "failed"
35
+ | "skipped"
36
+
37
+ /**
38
+ * The statuses whose node state MAY carry `output`.
39
+ *
40
+ * `completed` is the obvious one. `failed` is here because a run can refuse
41
+ * its result and still RETAIN what it produced — the 3D-scene authoring lanes
42
+ * publish a real, renderable revision and then fail the job on the visual
43
+ * reviewer's verdict (`SCENE_QUALITY_FAILED`), so the scene and the refusal are
44
+ * both true at once. That output was billed; dropping it because the status is
45
+ * not `"completed"` is what this set exists to stop.
46
+ *
47
+ * `pending` / `running` / `skipped` never carry one: nothing has settled, or
48
+ * the node was gated out.
49
+ */
50
+ export const OUTPUT_BEARING_NODE_STATUSES: ReadonlySet<NodeExecutionStatus> =
51
+ new Set<NodeExecutionStatus>(["completed", "failed"])
52
+
53
+ /** Whether a node in this status may carry `output`. */
54
+ export function nodeStateMayCarryOutput(status: string | undefined): boolean {
55
+ return (
56
+ status !== undefined &&
57
+ OUTPUT_BEARING_NODE_STATUSES.has(status as NodeExecutionStatus)
58
+ )
59
+ }
60
+
61
+ /**
62
+ * One node's execution state, as it travels on the wire.
63
+ *
64
+ * Additive by construction — an older client ignores fields it does not know,
65
+ * which is why `output` on a failed node is safe to start sending.
66
+ *
67
+ * `TOutput` lets a declaration that knows its own richer output shape (the
68
+ * orchestrator's `NodeOutput`, the editor's) stay ASSIGNABLE to this contract
69
+ * instead of restating it; the default is what an external client sees.
70
+ */
71
+ export interface NodeExecutionStateWire<TOutput = Record<string, unknown>> {
72
+ status: NodeExecutionStatus | (string & {})
73
+ nodeType?: string
74
+ jobId?: string | null
75
+ /** Every job id of a fan-out node (one per list / loop iteration). */
76
+ jobIds?: string[]
77
+ creditsUsed?: number
78
+ error?: string | null
79
+ /** Stable code for a refusal a client may branch on — never on the text. */
80
+ errorCode?: string
81
+ /**
82
+ * What the node produced.
83
+ *
84
+ * Present for a `completed` node, and for a `failed` node whose run RETAINED
85
+ * a structured result (see {@link OUTPUT_BEARING_NODE_STATUSES}). A consumer
86
+ * that reads it must therefore gate on the FIELD, not on the status, and
87
+ * must not treat its presence as success.
88
+ */
89
+ output?: TOutput
90
+ startedAt?: string | null
91
+ completedAt?: string | null
92
+ /** The node's job is parked in `pending_review`; `status` stays "running". */
93
+ awaitingReview?: boolean
94
+ progress?: number
95
+ }