@nodaro/shared 1.4.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.
- package/LICENSE +202 -0
- package/README.md +32 -0
- package/dist/index.cjs +12506 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +12269 -0
- package/dist/index.d.ts +12269 -0
- package/dist/index.js +11831 -0
- package/dist/index.js.map +1 -0
- package/package.json +54 -0
- package/src/__tests__/__snapshots__/prompt-builder-segments.test.ts.snap +65 -0
- package/src/__tests__/ai-avatar-pricing.test.ts +321 -0
- package/src/__tests__/apply-video-negative-prompt.test.ts +76 -0
- package/src/__tests__/attach-columns.test.ts +28 -0
- package/src/__tests__/audio-crossfade-curves.test.ts +33 -0
- package/src/__tests__/build-model-menu.test.ts +58 -0
- package/src/__tests__/character-aspect-defaults.test.ts +142 -0
- package/src/__tests__/character-mention-slug.test.ts +199 -0
- package/src/__tests__/character-reference-photo-kinds.test.ts +32 -0
- package/src/__tests__/character-usage-mode.test.ts +147 -0
- package/src/__tests__/character-variant-assets.test.ts +142 -0
- package/src/__tests__/character-voice.test.ts +130 -0
- package/src/__tests__/cinematic-avatar-pricing.test.ts +119 -0
- package/src/__tests__/combine-transitions.test.ts +137 -0
- package/src/__tests__/component-types.test.ts +87 -0
- package/src/__tests__/condition-variables.test.ts +98 -0
- package/src/__tests__/credit-identifiers.test.ts +811 -0
- package/src/__tests__/default-video-provider.test.ts +63 -0
- package/src/__tests__/entity-image-handle.test.ts +43 -0
- package/src/__tests__/entity-variants.test.ts +33 -0
- package/src/__tests__/fal-lip-sync-sets.test.ts +31 -0
- package/src/__tests__/featured-entities.test.ts +33 -0
- package/src/__tests__/film-pricing.test.ts +64 -0
- package/src/__tests__/filter-condition.test.ts +285 -0
- package/src/__tests__/freecut-protocol.test.ts +35 -0
- package/src/__tests__/generate-text-items.test.ts +19 -0
- package/src/__tests__/generated-results.test.ts +50 -0
- package/src/__tests__/graph-utils.test.ts +618 -0
- package/src/__tests__/group-aggregation.test.ts +83 -0
- package/src/__tests__/i18n-locale-completeness.test.ts +156 -0
- package/src/__tests__/image-critic-node.test.ts +86 -0
- package/src/__tests__/image-mask-mode.test.ts +16 -0
- package/src/__tests__/image-ref-limit.test.ts +78 -0
- package/src/__tests__/inpaint-region.test.ts +24 -0
- package/src/__tests__/input-field-map-parity.test.ts +30 -0
- package/src/__tests__/json-evaluator.test.ts +289 -0
- package/src/__tests__/json-path.test.ts +64 -0
- package/src/__tests__/lip-sync-pricing.test.ts +142 -0
- package/src/__tests__/list-merge.test.ts +96 -0
- package/src/__tests__/list-sort.test.ts +134 -0
- package/src/__tests__/llm-models.test.ts +445 -0
- package/src/__tests__/location-mention-slug.test.ts +246 -0
- package/src/__tests__/locations-coverage-critic-schema.test.ts +92 -0
- package/src/__tests__/loop-delimiter.test.ts +39 -0
- package/src/__tests__/lottie-slots.test.ts +245 -0
- package/src/__tests__/model-catalog-flux2-resolutions.test.ts +21 -0
- package/src/__tests__/model-constants-location.test.ts +20 -0
- package/src/__tests__/model-constants.test.ts +122 -0
- package/src/__tests__/model-tree.test.ts +86 -0
- package/src/__tests__/no-internal-imports.test.ts +25 -0
- package/src/__tests__/node-default-mappings.test.ts +175 -0
- package/src/__tests__/node-mappable-fields.test.ts +7 -0
- package/src/__tests__/node-preset-export.test.ts +46 -0
- package/src/__tests__/node-preset-extract.test.ts +166 -0
- package/src/__tests__/node-refs-canonical.test.ts +54 -0
- package/src/__tests__/node-refs-combine.test.ts +54 -0
- package/src/__tests__/node-refs.test.ts +187 -0
- package/src/__tests__/node-runtime-keys.test.ts +99 -0
- package/src/__tests__/object-aspect-defaults.test.ts +59 -0
- package/src/__tests__/parameter-node-value.test.ts +137 -0
- package/src/__tests__/pinterest-url.test.ts +35 -0
- package/src/__tests__/pipeline-chat.test.ts +110 -0
- package/src/__tests__/pipeline-validation.test.ts +98 -0
- package/src/__tests__/presentation-utils.test.ts +1137 -0
- package/src/__tests__/producer-types.test.ts +47 -0
- package/src/__tests__/prompt-length-limits.test.ts +156 -0
- package/src/__tests__/provider-directive-defaults.test.ts +46 -0
- package/src/__tests__/reduce-strategy-registry.test.ts +43 -0
- package/src/__tests__/reference-board-templates.test.ts +33 -0
- package/src/__tests__/reference-roles.test.ts +36 -0
- package/src/__tests__/resolve-field-mappings.test.ts +110 -0
- package/src/__tests__/resolve-pipeline-model.test.ts +76 -0
- package/src/__tests__/scene-node-types.test.ts +80 -0
- package/src/__tests__/scraper-actors.test.ts +67 -0
- package/src/__tests__/seedance-2-catalog.test.ts +18 -0
- package/src/__tests__/seedance-2-credit-id.test.ts +23 -0
- package/src/__tests__/seedance-aspects.test.ts +52 -0
- package/src/__tests__/selector.test.ts +1151 -0
- package/src/__tests__/settled-with-limit.test.ts +214 -0
- package/src/__tests__/social-media-specs.test.ts +129 -0
- package/src/__tests__/sub-workflow-validation.test.ts +88 -0
- package/src/__tests__/switchx-pricing.test.ts +28 -0
- package/src/__tests__/to-connected-references.test.ts +153 -0
- package/src/__tests__/utilities.test.ts +541 -0
- package/src/__tests__/video-analysis-pricing.test.ts +58 -0
- package/src/__tests__/video-analysis.test.ts +63 -0
- package/src/__tests__/video-audio-capability.test.ts +204 -0
- package/src/__tests__/video-gen-providers.test.ts +28 -0
- package/src/__tests__/video-mode-aliases.test.ts +150 -0
- package/src/__tests__/video-ref-limits.test.ts +72 -0
- package/src/__tests__/video-utils.test.ts +303 -0
- package/src/__tests__/voice-changer-models.test.ts +33 -0
- package/src/__tests__/workflow-export-strip.test.ts +46 -0
- package/src/ai-avatar-pricing.ts +213 -0
- package/src/ancestor-refs.ts +48 -0
- package/src/animals.ts +231 -0
- package/src/audio-crossfade-curves.ts +73 -0
- package/src/audio-fx-presets.ts +64 -0
- package/src/avatar-payload-validators.ts +214 -0
- package/src/caption-styles.ts +31 -0
- package/src/character-aspect-defaults.ts +102 -0
- package/src/character-facets.ts +37 -0
- package/src/character-mention-slug.ts +166 -0
- package/src/character-usage-mode.ts +109 -0
- package/src/character-variant-assets.ts +121 -0
- package/src/character-voice.ts +151 -0
- package/src/cinematic-avatar-pricing.ts +111 -0
- package/src/clone-utils.ts +46 -0
- package/src/combine-transitions.ts +482 -0
- package/src/community.ts +112 -0
- package/src/component-types.ts +127 -0
- package/src/condition-variables.ts +65 -0
- package/src/credit-estimators/index.ts +15 -0
- package/src/credit-estimators/video-utils.ts +165 -0
- package/src/credit-identifiers.ts +304 -0
- package/src/entity-approval-types.ts +31 -0
- package/src/entity-aspect-defaults.ts +82 -0
- package/src/entity-asset-types.ts +296 -0
- package/src/entity-image-handle.ts +36 -0
- package/src/extra-refs.ts +139 -0
- package/src/featured-entities.ts +136 -0
- package/src/film-pricing.ts +117 -0
- package/src/filter-condition.ts +250 -0
- package/src/flux2-pricing.ts +18 -0
- package/src/freecut-protocol.ts +52 -0
- package/src/furniture.ts +167 -0
- package/src/generate-text-items.ts +15 -0
- package/src/generated-results.ts +63 -0
- package/src/group-aggregation.ts +75 -0
- package/src/hint-graph-types.ts +18 -0
- package/src/i18n/action-fx.ar.ts +89 -0
- package/src/i18n/action-fx.de.ts +89 -0
- package/src/i18n/action-fx.es.ts +89 -0
- package/src/i18n/action-fx.fr.ts +89 -0
- package/src/i18n/action-fx.he.ts +89 -0
- package/src/i18n/action-fx.hi.ts +89 -0
- package/src/i18n/action-fx.ja.ts +89 -0
- package/src/i18n/action-fx.ko.ts +89 -0
- package/src/i18n/action-fx.pt-BR.ts +89 -0
- package/src/i18n/action-fx.ru.ts +89 -0
- package/src/i18n/action-fx.zh-CN.ts +89 -0
- package/src/i18n/aesthetic.ar.ts +170 -0
- package/src/i18n/aesthetic.de.ts +48 -0
- package/src/i18n/aesthetic.es.ts +169 -0
- package/src/i18n/aesthetic.fr.ts +52 -0
- package/src/i18n/aesthetic.he.ts +169 -0
- package/src/i18n/aesthetic.hi.ts +55 -0
- package/src/i18n/aesthetic.ja.ts +165 -0
- package/src/i18n/aesthetic.ko.ts +169 -0
- package/src/i18n/aesthetic.pt-BR.ts +148 -0
- package/src/i18n/aesthetic.ru.ts +155 -0
- package/src/i18n/aesthetic.zh-CN.ts +172 -0
- package/src/i18n/animals.ar.ts +145 -0
- package/src/i18n/animals.de.ts +144 -0
- package/src/i18n/animals.es.ts +158 -0
- package/src/i18n/animals.fr.ts +144 -0
- package/src/i18n/animals.he.ts +155 -0
- package/src/i18n/animals.hi.ts +157 -0
- package/src/i18n/animals.ja.ts +159 -0
- package/src/i18n/animals.ko.ts +159 -0
- package/src/i18n/animals.pt-BR.ts +157 -0
- package/src/i18n/animals.ru.ts +155 -0
- package/src/i18n/animals.zh-CN.ts +155 -0
- package/src/i18n/atmosphere.ar.ts +49 -0
- package/src/i18n/atmosphere.de.ts +47 -0
- package/src/i18n/atmosphere.es.ts +50 -0
- package/src/i18n/atmosphere.fr.ts +47 -0
- package/src/i18n/atmosphere.he.ts +47 -0
- package/src/i18n/atmosphere.hi.ts +49 -0
- package/src/i18n/atmosphere.ja.ts +49 -0
- package/src/i18n/atmosphere.ko.ts +49 -0
- package/src/i18n/atmosphere.pt-BR.ts +49 -0
- package/src/i18n/atmosphere.ru.ts +47 -0
- package/src/i18n/atmosphere.zh-CN.ts +152 -0
- package/src/i18n/backdrop.ar.ts +53 -0
- package/src/i18n/backdrop.de.ts +46 -0
- package/src/i18n/backdrop.es.ts +54 -0
- package/src/i18n/backdrop.fr.ts +52 -0
- package/src/i18n/backdrop.he.ts +51 -0
- package/src/i18n/backdrop.hi.ts +59 -0
- package/src/i18n/backdrop.ja.ts +61 -0
- package/src/i18n/backdrop.ko.ts +59 -0
- package/src/i18n/backdrop.pt-BR.ts +59 -0
- package/src/i18n/backdrop.ru.ts +57 -0
- package/src/i18n/backdrop.zh-CN.ts +57 -0
- package/src/i18n/camera-format.ar.ts +40 -0
- package/src/i18n/camera-format.de.ts +37 -0
- package/src/i18n/camera-format.es.ts +41 -0
- package/src/i18n/camera-format.fr.ts +37 -0
- package/src/i18n/camera-format.he.ts +37 -0
- package/src/i18n/camera-format.hi.ts +42 -0
- package/src/i18n/camera-format.ja.ts +37 -0
- package/src/i18n/camera-format.ko.ts +37 -0
- package/src/i18n/camera-format.pt-BR.ts +46 -0
- package/src/i18n/camera-format.ru.ts +42 -0
- package/src/i18n/camera-format.zh-CN.ts +42 -0
- package/src/i18n/camera-motions.ar.ts +86 -0
- package/src/i18n/camera-motions.de.ts +74 -0
- package/src/i18n/camera-motions.es.ts +86 -0
- package/src/i18n/camera-motions.fr.ts +86 -0
- package/src/i18n/camera-motions.he.ts +86 -0
- package/src/i18n/camera-motions.hi.ts +99 -0
- package/src/i18n/camera-motions.ja.ts +99 -0
- package/src/i18n/camera-motions.ko.ts +99 -0
- package/src/i18n/camera-motions.pt-BR.ts +99 -0
- package/src/i18n/camera-motions.ru.ts +99 -0
- package/src/i18n/camera-motions.zh-CN.ts +99 -0
- package/src/i18n/character-fx.ar.ts +72 -0
- package/src/i18n/character-fx.de.ts +72 -0
- package/src/i18n/character-fx.es.ts +72 -0
- package/src/i18n/character-fx.fr.ts +72 -0
- package/src/i18n/character-fx.he.ts +72 -0
- package/src/i18n/character-fx.hi.ts +72 -0
- package/src/i18n/character-fx.ja.ts +72 -0
- package/src/i18n/character-fx.ko.ts +72 -0
- package/src/i18n/character-fx.pt-BR.ts +72 -0
- package/src/i18n/character-fx.ru.ts +72 -0
- package/src/i18n/character-fx.zh-CN.ts +72 -0
- package/src/i18n/color-look.ar.ts +50 -0
- package/src/i18n/color-look.de.ts +47 -0
- package/src/i18n/color-look.es.ts +50 -0
- package/src/i18n/color-look.fr.ts +50 -0
- package/src/i18n/color-look.he.ts +49 -0
- package/src/i18n/color-look.hi.ts +52 -0
- package/src/i18n/color-look.ja.ts +54 -0
- package/src/i18n/color-look.ko.ts +54 -0
- package/src/i18n/color-look.pt-BR.ts +54 -0
- package/src/i18n/color-look.ru.ts +52 -0
- package/src/i18n/color-look.zh-CN.ts +53 -0
- package/src/i18n/composition-effects.ar.ts +26 -0
- package/src/i18n/composition-effects.de.ts +25 -0
- package/src/i18n/composition-effects.es.ts +27 -0
- package/src/i18n/composition-effects.fr.ts +25 -0
- package/src/i18n/composition-effects.he.ts +25 -0
- package/src/i18n/composition-effects.hi.ts +27 -0
- package/src/i18n/composition-effects.ja.ts +27 -0
- package/src/i18n/composition-effects.ko.ts +27 -0
- package/src/i18n/composition-effects.pt-BR.ts +25 -0
- package/src/i18n/composition-effects.ru.ts +82 -0
- package/src/i18n/composition-effects.zh-CN.ts +25 -0
- package/src/i18n/era.ar.ts +37 -0
- package/src/i18n/era.de.ts +34 -0
- package/src/i18n/era.es.ts +37 -0
- package/src/i18n/era.fr.ts +37 -0
- package/src/i18n/era.he.ts +36 -0
- package/src/i18n/era.hi.ts +39 -0
- package/src/i18n/era.ja.ts +41 -0
- package/src/i18n/era.ko.ts +39 -0
- package/src/i18n/era.pt-BR.ts +41 -0
- package/src/i18n/era.ru.ts +121 -0
- package/src/i18n/era.zh-CN.ts +39 -0
- package/src/i18n/exposure-settings.ar.ts +28 -0
- package/src/i18n/exposure-settings.de.ts +26 -0
- package/src/i18n/exposure-settings.es.ts +28 -0
- package/src/i18n/exposure-settings.fr.ts +29 -0
- package/src/i18n/exposure-settings.he.ts +28 -0
- package/src/i18n/exposure-settings.hi.ts +31 -0
- package/src/i18n/exposure-settings.ja.ts +31 -0
- package/src/i18n/exposure-settings.ko.ts +31 -0
- package/src/i18n/exposure-settings.pt-BR.ts +31 -0
- package/src/i18n/exposure-settings.ru.ts +31 -0
- package/src/i18n/exposure-settings.zh-CN.ts +31 -0
- package/src/i18n/framing.ar.ts +78 -0
- package/src/i18n/framing.de.ts +73 -0
- package/src/i18n/framing.es.ts +78 -0
- package/src/i18n/framing.fr.ts +78 -0
- package/src/i18n/framing.he.ts +77 -0
- package/src/i18n/framing.hi.ts +82 -0
- package/src/i18n/framing.ja.ts +84 -0
- package/src/i18n/framing.ko.ts +82 -0
- package/src/i18n/framing.pt-BR.ts +84 -0
- package/src/i18n/framing.ru.ts +82 -0
- package/src/i18n/framing.zh-CN.ts +82 -0
- package/src/i18n/furniture.ar.ts +92 -0
- package/src/i18n/furniture.de.ts +93 -0
- package/src/i18n/furniture.es.ts +101 -0
- package/src/i18n/furniture.fr.ts +93 -0
- package/src/i18n/furniture.he.ts +101 -0
- package/src/i18n/furniture.hi.ts +101 -0
- package/src/i18n/furniture.ja.ts +101 -0
- package/src/i18n/furniture.ko.ts +101 -0
- package/src/i18n/furniture.pt-BR.ts +101 -0
- package/src/i18n/furniture.ru.ts +101 -0
- package/src/i18n/furniture.zh-CN.ts +101 -0
- package/src/i18n/held-prop.ar.ts +74 -0
- package/src/i18n/held-prop.de.ts +65 -0
- package/src/i18n/held-prop.es.ts +83 -0
- package/src/i18n/held-prop.fr.ts +74 -0
- package/src/i18n/held-prop.he.ts +73 -0
- package/src/i18n/held-prop.hi.ts +82 -0
- package/src/i18n/held-prop.ja.ts +84 -0
- package/src/i18n/held-prop.ko.ts +84 -0
- package/src/i18n/held-prop.pt-BR.ts +84 -0
- package/src/i18n/held-prop.ru.ts +82 -0
- package/src/i18n/held-prop.zh-CN.ts +82 -0
- package/src/i18n/index.ts +208 -0
- package/src/i18n/instrumentation.ar.ts +195 -0
- package/src/i18n/instrumentation.de.ts +195 -0
- package/src/i18n/instrumentation.es.ts +195 -0
- package/src/i18n/instrumentation.fr.ts +195 -0
- package/src/i18n/instrumentation.he.ts +195 -0
- package/src/i18n/instrumentation.hi.ts +195 -0
- package/src/i18n/instrumentation.ja.ts +195 -0
- package/src/i18n/instrumentation.ko.ts +195 -0
- package/src/i18n/instrumentation.pt-BR.ts +195 -0
- package/src/i18n/instrumentation.ru.ts +195 -0
- package/src/i18n/instrumentation.zh-CN.ts +195 -0
- package/src/i18n/lens.ar.ts +24 -0
- package/src/i18n/lens.de.ts +24 -0
- package/src/i18n/lens.es.ts +24 -0
- package/src/i18n/lens.fr.ts +24 -0
- package/src/i18n/lens.he.ts +24 -0
- package/src/i18n/lens.hi.ts +24 -0
- package/src/i18n/lens.ja.ts +24 -0
- package/src/i18n/lens.ko.ts +24 -0
- package/src/i18n/lens.pt-BR.ts +24 -0
- package/src/i18n/lens.ru.ts +25 -0
- package/src/i18n/lens.zh-CN.ts +24 -0
- package/src/i18n/lighting.ar.ts +86 -0
- package/src/i18n/lighting.de.ts +84 -0
- package/src/i18n/lighting.es.ts +89 -0
- package/src/i18n/lighting.fr.ts +84 -0
- package/src/i18n/lighting.he.ts +88 -0
- package/src/i18n/lighting.hi.ts +90 -0
- package/src/i18n/lighting.ja.ts +90 -0
- package/src/i18n/lighting.ko.ts +90 -0
- package/src/i18n/lighting.pt-BR.ts +90 -0
- package/src/i18n/lighting.ru.ts +88 -0
- package/src/i18n/lighting.zh-CN.ts +92 -0
- package/src/i18n/loop-subject.ar.ts +39 -0
- package/src/i18n/loop-subject.de.ts +39 -0
- package/src/i18n/loop-subject.es.ts +39 -0
- package/src/i18n/loop-subject.fr.ts +39 -0
- package/src/i18n/loop-subject.he.ts +39 -0
- package/src/i18n/loop-subject.hi.ts +39 -0
- package/src/i18n/loop-subject.ja.ts +39 -0
- package/src/i18n/loop-subject.ko.ts +39 -0
- package/src/i18n/loop-subject.pt-BR.ts +39 -0
- package/src/i18n/loop-subject.ru.ts +39 -0
- package/src/i18n/loop-subject.zh-CN.ts +39 -0
- package/src/i18n/materials.ar.ts +80 -0
- package/src/i18n/materials.de.ts +80 -0
- package/src/i18n/materials.es.ts +87 -0
- package/src/i18n/materials.fr.ts +80 -0
- package/src/i18n/materials.he.ts +86 -0
- package/src/i18n/materials.hi.ts +86 -0
- package/src/i18n/materials.ja.ts +88 -0
- package/src/i18n/materials.ko.ts +88 -0
- package/src/i18n/materials.pt-BR.ts +88 -0
- package/src/i18n/materials.ru.ts +86 -0
- package/src/i18n/materials.zh-CN.ts +86 -0
- package/src/i18n/mood.ar.ts +61 -0
- package/src/i18n/mood.de.ts +61 -0
- package/src/i18n/mood.es.ts +66 -0
- package/src/i18n/mood.fr.ts +61 -0
- package/src/i18n/mood.he.ts +64 -0
- package/src/i18n/mood.hi.ts +66 -0
- package/src/i18n/mood.ja.ts +66 -0
- package/src/i18n/mood.ko.ts +66 -0
- package/src/i18n/mood.pt-BR.ts +64 -0
- package/src/i18n/mood.ru.ts +64 -0
- package/src/i18n/mood.zh-CN.ts +64 -0
- package/src/i18n/music-genre.ar.ts +302 -0
- package/src/i18n/music-genre.de.ts +302 -0
- package/src/i18n/music-genre.es.ts +302 -0
- package/src/i18n/music-genre.fr.ts +302 -0
- package/src/i18n/music-genre.he.ts +302 -0
- package/src/i18n/music-genre.hi.ts +302 -0
- package/src/i18n/music-genre.ja.ts +302 -0
- package/src/i18n/music-genre.ko.ts +302 -0
- package/src/i18n/music-genre.pt-BR.ts +302 -0
- package/src/i18n/music-genre.ru.ts +302 -0
- package/src/i18n/music-genre.zh-CN.ts +302 -0
- package/src/i18n/music-mood.ar.ts +75 -0
- package/src/i18n/music-mood.de.ts +75 -0
- package/src/i18n/music-mood.es.ts +75 -0
- package/src/i18n/music-mood.fr.ts +75 -0
- package/src/i18n/music-mood.he.ts +75 -0
- package/src/i18n/music-mood.hi.ts +75 -0
- package/src/i18n/music-mood.ja.ts +75 -0
- package/src/i18n/music-mood.ko.ts +75 -0
- package/src/i18n/music-mood.pt-BR.ts +75 -0
- package/src/i18n/music-mood.ru.ts +75 -0
- package/src/i18n/music-mood.zh-CN.ts +75 -0
- package/src/i18n/person.ar.ts +638 -0
- package/src/i18n/person.de.ts +629 -0
- package/src/i18n/person.es.ts +645 -0
- package/src/i18n/person.fr.ts +621 -0
- package/src/i18n/person.he.ts +656 -0
- package/src/i18n/person.hi.ts +654 -0
- package/src/i18n/person.ja.ts +662 -0
- package/src/i18n/person.ko.ts +662 -0
- package/src/i18n/person.pt-BR.ts +656 -0
- package/src/i18n/person.ru.ts +656 -0
- package/src/i18n/person.zh-CN.ts +656 -0
- package/src/i18n/photo-genre.ar.ts +58 -0
- package/src/i18n/photo-genre.de.ts +52 -0
- package/src/i18n/photo-genre.es.ts +64 -0
- package/src/i18n/photo-genre.fr.ts +58 -0
- package/src/i18n/photo-genre.he.ts +62 -0
- package/src/i18n/photo-genre.hi.ts +63 -0
- package/src/i18n/photo-genre.ja.ts +65 -0
- package/src/i18n/photo-genre.ko.ts +63 -0
- package/src/i18n/photo-genre.pt-BR.ts +65 -0
- package/src/i18n/photo-genre.ru.ts +63 -0
- package/src/i18n/photo-genre.zh-CN.ts +63 -0
- package/src/i18n/photographer.ar.ts +75 -0
- package/src/i18n/photographer.de.ts +74 -0
- package/src/i18n/photographer.es.ts +77 -0
- package/src/i18n/photographer.fr.ts +77 -0
- package/src/i18n/photographer.he.ts +78 -0
- package/src/i18n/photographer.hi.ts +80 -0
- package/src/i18n/photographer.ja.ts +84 -0
- package/src/i18n/photographer.ko.ts +84 -0
- package/src/i18n/photographer.pt-BR.ts +83 -0
- package/src/i18n/photographer.ru.ts +78 -0
- package/src/i18n/photographer.zh-CN.ts +78 -0
- package/src/i18n/pose.ar.ts +95 -0
- package/src/i18n/pose.de.ts +96 -0
- package/src/i18n/pose.es.ts +104 -0
- package/src/i18n/pose.fr.ts +96 -0
- package/src/i18n/pose.he.ts +104 -0
- package/src/i18n/pose.hi.ts +104 -0
- package/src/i18n/pose.ja.ts +104 -0
- package/src/i18n/pose.ko.ts +104 -0
- package/src/i18n/pose.pt-BR.ts +104 -0
- package/src/i18n/pose.ru.ts +104 -0
- package/src/i18n/pose.zh-CN.ts +104 -0
- package/src/i18n/post-process-effects.ar.ts +25 -0
- package/src/i18n/post-process-effects.de.ts +24 -0
- package/src/i18n/post-process-effects.es.ts +26 -0
- package/src/i18n/post-process-effects.fr.ts +24 -0
- package/src/i18n/post-process-effects.he.ts +24 -0
- package/src/i18n/post-process-effects.hi.ts +26 -0
- package/src/i18n/post-process-effects.ja.ts +26 -0
- package/src/i18n/post-process-effects.ko.ts +26 -0
- package/src/i18n/post-process-effects.pt-BR.ts +24 -0
- package/src/i18n/post-process-effects.ru.ts +24 -0
- package/src/i18n/post-process-effects.zh-CN.ts +24 -0
- package/src/i18n/render-quality.ar.ts +34 -0
- package/src/i18n/render-quality.de.ts +30 -0
- package/src/i18n/render-quality.es.ts +35 -0
- package/src/i18n/render-quality.fr.ts +34 -0
- package/src/i18n/render-quality.he.ts +33 -0
- package/src/i18n/render-quality.hi.ts +39 -0
- package/src/i18n/render-quality.ja.ts +39 -0
- package/src/i18n/render-quality.ko.ts +39 -0
- package/src/i18n/render-quality.pt-BR.ts +37 -0
- package/src/i18n/render-quality.ru.ts +37 -0
- package/src/i18n/render-quality.zh-CN.ts +37 -0
- package/src/i18n/seasons.ar.ts +10 -0
- package/src/i18n/seasons.de.ts +10 -0
- package/src/i18n/seasons.es.ts +10 -0
- package/src/i18n/seasons.fr.ts +10 -0
- package/src/i18n/seasons.he.ts +10 -0
- package/src/i18n/seasons.hi.ts +10 -0
- package/src/i18n/seasons.ja.ts +10 -0
- package/src/i18n/seasons.ko.ts +10 -0
- package/src/i18n/seasons.pt-BR.ts +10 -0
- package/src/i18n/seasons.ru.ts +10 -0
- package/src/i18n/seasons.zh-CN.ts +10 -0
- package/src/i18n/setting.ar.ts +73 -0
- package/src/i18n/setting.de.ts +73 -0
- package/src/i18n/setting.es.ts +77 -0
- package/src/i18n/setting.fr.ts +73 -0
- package/src/i18n/setting.he.ts +76 -0
- package/src/i18n/setting.hi.ts +76 -0
- package/src/i18n/setting.ja.ts +78 -0
- package/src/i18n/setting.ko.ts +76 -0
- package/src/i18n/setting.pt-BR.ts +78 -0
- package/src/i18n/setting.ru.ts +76 -0
- package/src/i18n/setting.zh-CN.ts +76 -0
- package/src/i18n/style.ar.ts +55 -0
- package/src/i18n/style.de.ts +54 -0
- package/src/i18n/style.es.ts +54 -0
- package/src/i18n/style.fr.ts +54 -0
- package/src/i18n/style.he.ts +54 -0
- package/src/i18n/style.hi.ts +54 -0
- package/src/i18n/style.ja.ts +54 -0
- package/src/i18n/style.ko.ts +54 -0
- package/src/i18n/style.pt-BR.ts +56 -0
- package/src/i18n/style.ru.ts +54 -0
- package/src/i18n/style.zh-CN.ts +54 -0
- package/src/i18n/styling.ar.ts +295 -0
- package/src/i18n/styling.de.ts +285 -0
- package/src/i18n/styling.es.ts +317 -0
- package/src/i18n/styling.fr.ts +285 -0
- package/src/i18n/styling.he.ts +307 -0
- package/src/i18n/styling.hi.ts +323 -0
- package/src/i18n/styling.ja.ts +317 -0
- package/src/i18n/styling.ko.ts +311 -0
- package/src/i18n/styling.pt-BR.ts +301 -0
- package/src/i18n/styling.ru.ts +301 -0
- package/src/i18n/styling.zh-CN.ts +307 -0
- package/src/i18n/temporal.ar.ts +31 -0
- package/src/i18n/temporal.de.ts +24 -0
- package/src/i18n/temporal.es.ts +27 -0
- package/src/i18n/temporal.fr.ts +28 -0
- package/src/i18n/temporal.he.ts +31 -0
- package/src/i18n/temporal.hi.ts +31 -0
- package/src/i18n/temporal.ja.ts +31 -0
- package/src/i18n/temporal.ko.ts +31 -0
- package/src/i18n/temporal.pt-BR.ts +31 -0
- package/src/i18n/temporal.ru.ts +31 -0
- package/src/i18n/temporal.zh-CN.ts +31 -0
- package/src/i18n/transitions.ar.ts +97 -0
- package/src/i18n/transitions.de.ts +97 -0
- package/src/i18n/transitions.es.ts +97 -0
- package/src/i18n/transitions.fr.ts +97 -0
- package/src/i18n/transitions.he.ts +97 -0
- package/src/i18n/transitions.hi.ts +97 -0
- package/src/i18n/transitions.ja.ts +97 -0
- package/src/i18n/transitions.ko.ts +97 -0
- package/src/i18n/transitions.pt-BR.ts +97 -0
- package/src/i18n/transitions.ru.ts +97 -0
- package/src/i18n/transitions.zh-CN.ts +97 -0
- package/src/i18n/types.ts +158 -0
- package/src/i18n/vehicles.ar.ts +137 -0
- package/src/i18n/vehicles.de.ts +125 -0
- package/src/i18n/vehicles.es.ts +138 -0
- package/src/i18n/vehicles.fr.ts +125 -0
- package/src/i18n/vehicles.he.ts +136 -0
- package/src/i18n/vehicles.hi.ts +138 -0
- package/src/i18n/vehicles.ja.ts +138 -0
- package/src/i18n/vehicles.ko.ts +138 -0
- package/src/i18n/vehicles.pt-BR.ts +136 -0
- package/src/i18n/vehicles.ru.ts +136 -0
- package/src/i18n/vehicles.zh-CN.ts +136 -0
- package/src/i18n/voice-character.ar.ts +126 -0
- package/src/i18n/voice-character.de.ts +126 -0
- package/src/i18n/voice-character.es.ts +126 -0
- package/src/i18n/voice-character.fr.ts +126 -0
- package/src/i18n/voice-character.he.ts +126 -0
- package/src/i18n/voice-character.hi.ts +126 -0
- package/src/i18n/voice-character.ja.ts +126 -0
- package/src/i18n/voice-character.ko.ts +126 -0
- package/src/i18n/voice-character.pt-BR.ts +126 -0
- package/src/i18n/voice-character.ru.ts +126 -0
- package/src/i18n/voice-character.zh-CN.ts +126 -0
- package/src/i18n/voice-delivery.ar.ts +77 -0
- package/src/i18n/voice-delivery.de.ts +77 -0
- package/src/i18n/voice-delivery.es.ts +77 -0
- package/src/i18n/voice-delivery.fr.ts +77 -0
- package/src/i18n/voice-delivery.he.ts +77 -0
- package/src/i18n/voice-delivery.hi.ts +77 -0
- package/src/i18n/voice-delivery.ja.ts +77 -0
- package/src/i18n/voice-delivery.ko.ts +77 -0
- package/src/i18n/voice-delivery.pt-BR.ts +77 -0
- package/src/i18n/voice-delivery.ru.ts +77 -0
- package/src/i18n/voice-delivery.zh-CN.ts +77 -0
- package/src/i18n/weapons.ar.ts +115 -0
- package/src/i18n/weapons.de.ts +114 -0
- package/src/i18n/weapons.es.ts +116 -0
- package/src/i18n/weapons.fr.ts +103 -0
- package/src/i18n/weapons.he.ts +114 -0
- package/src/i18n/weapons.hi.ts +116 -0
- package/src/i18n/weapons.ja.ts +116 -0
- package/src/i18n/weapons.ko.ts +116 -0
- package/src/i18n/weapons.pt-BR.ts +114 -0
- package/src/i18n/weapons.ru.ts +114 -0
- package/src/i18n/weapons.zh-CN.ts +114 -0
- package/src/image-critic-node.ts +61 -0
- package/src/image-critic-types.ts +40 -0
- package/src/index.ts +866 -0
- package/src/inject-upstream.ts +15 -0
- package/src/inpaint-region.ts +51 -0
- package/src/json-evaluator.ts +732 -0
- package/src/json-path.ts +52 -0
- package/src/lip-sync-pricing.ts +90 -0
- package/src/list-merge.ts +40 -0
- package/src/list-sort.ts +122 -0
- package/src/list-source-resolver.ts +50 -0
- package/src/llm-models.ts +245 -0
- package/src/location-mention-slug.ts +291 -0
- package/src/location-preset-catalog-map.ts +128 -0
- package/src/loop-delimiter.ts +73 -0
- package/src/lora-routing.ts +95 -0
- package/src/lottie-overlay-catalog.ts +175 -0
- package/src/lottie-slots.ts +309 -0
- package/src/model-catalog.ts +2386 -0
- package/src/model-constants.ts +1910 -0
- package/src/model-tree.ts +120 -0
- package/src/monetization.ts +15 -0
- package/src/multi-pick.ts +42 -0
- package/src/node-default-mappings.ts +262 -0
- package/src/node-mappable-fields.ts +70 -0
- package/src/node-preset-export.ts +56 -0
- package/src/node-preset-extract.ts +147 -0
- package/src/node-refs.ts +253 -0
- package/src/node-runtime-keys.ts +117 -0
- package/src/object-aspect-defaults.ts +72 -0
- package/src/object-picker-types.ts +22 -0
- package/src/parameter-node-value.ts +284 -0
- package/src/pinterest-url.ts +28 -0
- package/src/pipeline-chat.ts +72 -0
- package/src/pipeline-defaults.ts +318 -0
- package/src/pipeline-events.ts +162 -0
- package/src/pipeline-state-types.ts +196 -0
- package/src/pipeline-types.ts +647 -0
- package/src/pipeline-validation.ts +87 -0
- package/src/presentation-types.ts +23 -0
- package/src/presentation-utils.ts +397 -0
- package/src/producer-types.ts +188 -0
- package/src/progress-curve.ts +99 -0
- package/src/provider-directive-defaults.ts +69 -0
- package/src/reduce-strategy-registry.ts +121 -0
- package/src/reference-board-templates.ts +81 -0
- package/src/reference-roles.ts +153 -0
- package/src/reference-sheet/__tests__/board-to-column.test.ts +20 -0
- package/src/reference-sheet/__tests__/catalog.test.ts +50 -0
- package/src/reference-sheet/__tests__/cost.test.ts +38 -0
- package/src/reference-sheet/__tests__/no-drift.test.ts +28 -0
- package/src/reference-sheet/__tests__/panel-plan.test.ts +61 -0
- package/src/reference-sheet/__tests__/panel-prompts.test.ts +17 -0
- package/src/reference-sheet/__tests__/plan-generation.test.ts +72 -0
- package/src/reference-sheet/__tests__/presets.test.ts +54 -0
- package/src/reference-sheet/__tests__/types.test.ts +24 -0
- package/src/reference-sheet/catalog.ts +144 -0
- package/src/reference-sheet/cost.ts +37 -0
- package/src/reference-sheet/credit-id.ts +7 -0
- package/src/reference-sheet/index.ts +8 -0
- package/src/reference-sheet/panel-plan.ts +65 -0
- package/src/reference-sheet/panel-prompts.ts +14 -0
- package/src/reference-sheet/plan-generation.ts +79 -0
- package/src/reference-sheet/presets.ts +77 -0
- package/src/reference-sheet/types.ts +92 -0
- package/src/repeat-types.ts +97 -0
- package/src/resolve-field-mappings.ts +119 -0
- package/src/route-filter.ts +80 -0
- package/src/scene-helper-types.ts +249 -0
- package/src/scene-node-types.ts +281 -0
- package/src/scraper-actors.ts +57 -0
- package/src/scraper-output-schemas.ts +25 -0
- package/src/seasons.ts +45 -0
- package/src/selector.ts +864 -0
- package/src/settled-with-limit.ts +42 -0
- package/src/shot-element.ts +43 -0
- package/src/social-media-specs.ts +121 -0
- package/src/social-post.ts +24 -0
- package/src/sub-workflow-validation.ts +78 -0
- package/src/supported-fonts.ts +40 -0
- package/src/surround.ts +113 -0
- package/src/switchx-pricing.ts +33 -0
- package/src/text-separators.ts +59 -0
- package/src/to-connected-references.ts +77 -0
- package/src/types.ts +272 -0
- package/src/variant-job-id.ts +17 -0
- package/src/vehicles.ts +213 -0
- package/src/video-analysis-pricing.ts +74 -0
- package/src/video-analysis.ts +127 -0
- package/src/video-duration.ts +25 -0
- package/src/voice-changer-models.ts +36 -0
- package/src/voice-matcher-types.ts +37 -0
- package/src/voices.ts +92 -0
- package/src/weapons.ts +190 -0
- package/src/workflow-export.ts +153 -0
|
@@ -0,0 +1,2386 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model catalog — single source of truth for what each Nodaro-exposed model
|
|
3
|
+
* is, who made it, what it's good for, and which knobs it accepts.
|
|
4
|
+
*
|
|
5
|
+
* Consumed by:
|
|
6
|
+
* - Backend MCP `list_models` tool (renders the nested per-kind / per-family
|
|
7
|
+
* response so Claude can pick the right model for a user's intent)
|
|
8
|
+
* - (future) Frontend config panels — today they read parallel registries
|
|
9
|
+
* in `frontend/src/components/editor/config-panels/model-options.ts`; a
|
|
10
|
+
* follow-up will migrate them to import from here.
|
|
11
|
+
*
|
|
12
|
+
* Authoring rules:
|
|
13
|
+
* - Keep `description` to one line (≤120 chars). It's what Claude reads to
|
|
14
|
+
* decide which model to use.
|
|
15
|
+
* - `useCases` is a small set of short tags (1-3 words) — verbs/nouns Claude
|
|
16
|
+
* can match against user intent ("typography", "character", "fast",
|
|
17
|
+
* "cinematic"). Stay consistent across entries: don't write "text" in one
|
|
18
|
+
* and "typography" in another.
|
|
19
|
+
* - `aspectRatios` / `resolutions` / `qualities` only appear when the model
|
|
20
|
+
* actually exposes that lever (e.g., Nano Banana base has no `resolution`).
|
|
21
|
+
* - `durations` is in seconds.
|
|
22
|
+
* - `pricing[0]` is ALWAYS the default variant (the bare id without a
|
|
23
|
+
* composite suffix like `:4K` or `:high`).
|
|
24
|
+
* - Costs are sourced from `STATIC_CREDIT_COSTS` (see backend
|
|
25
|
+
* `src/billing/credits.ts`). Keep them in sync if the table changes.
|
|
26
|
+
* - After editing `MODEL_CATALOG` or `MODEL_RECOMMENDATIONS`, run
|
|
27
|
+
* `npm run gen:skills` (from `backend/`) to refresh the public
|
|
28
|
+
* `docs/choosing-models.md` guide. CI (`gen:skills:check`) fails on drift.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
export type ModelKind = "image" | "video" | "audio"
|
|
32
|
+
|
|
33
|
+
export type ModelMode =
|
|
34
|
+
// image
|
|
35
|
+
| "t2i"
|
|
36
|
+
| "i2i"
|
|
37
|
+
| "edit"
|
|
38
|
+
| "upscale"
|
|
39
|
+
| "remove-bg"
|
|
40
|
+
// video
|
|
41
|
+
| "i2v"
|
|
42
|
+
| "t2v"
|
|
43
|
+
| "v2v"
|
|
44
|
+
| "extend"
|
|
45
|
+
| "motion-transfer"
|
|
46
|
+
| "lip-sync"
|
|
47
|
+
| "video-upscale"
|
|
48
|
+
// audio
|
|
49
|
+
| "tts"
|
|
50
|
+
| "music"
|
|
51
|
+
| "sfx"
|
|
52
|
+
| "stt"
|
|
53
|
+
| "voice-clone"
|
|
54
|
+
| "voice-design"
|
|
55
|
+
| "voice-changer"
|
|
56
|
+
| "isolation"
|
|
57
|
+
| "dubbing"
|
|
58
|
+
| "forced-alignment"
|
|
59
|
+
// video analysis
|
|
60
|
+
| "video-analysis"
|
|
61
|
+
|
|
62
|
+
export interface PriceVariant {
|
|
63
|
+
/** Composite identifier as it appears in `STATIC_CREDIT_COSTS`. */
|
|
64
|
+
identifier: string
|
|
65
|
+
/** Credits charged at reservation. */
|
|
66
|
+
credits: number
|
|
67
|
+
/** Short human-readable note describing this variant ("1K default", "4K", "with audio"). */
|
|
68
|
+
note?: string
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface ModelCatalogEntry {
|
|
72
|
+
id: string
|
|
73
|
+
kind: ModelKind
|
|
74
|
+
/**
|
|
75
|
+
* Operations this model supports. Many video models (minimax, veo3,
|
|
76
|
+
* seedance) support BOTH i2v AND t2v under the same id — the route handler
|
|
77
|
+
* picks which KIE endpoint to hit based on whether an image was supplied.
|
|
78
|
+
* For those, list both modes here so MCP filters and frontend pickers see
|
|
79
|
+
* the model in either context.
|
|
80
|
+
*/
|
|
81
|
+
modes: readonly ModelMode[]
|
|
82
|
+
/** Vendor / lab that produced the model. Used for UI grouping. */
|
|
83
|
+
family: string
|
|
84
|
+
/** Display name (NOT the id — the id is the API enum value). */
|
|
85
|
+
label: string
|
|
86
|
+
/**
|
|
87
|
+
* Model LINE for the add-node Models tab — the brand-agnostic product line a
|
|
88
|
+
* user thinks in ("Nano Banana", "Flux", "VEO", "Kling", "ElevenLabs").
|
|
89
|
+
* Distinct from `family` (the lab: Google covers Nano Banana + Imagen + VEO).
|
|
90
|
+
* REQUIRED for every node-creatable model; a completeness guard in
|
|
91
|
+
* `@nodaro/shared` (model-tree.test.ts) fails if one is missing.
|
|
92
|
+
*/
|
|
93
|
+
series?: string
|
|
94
|
+
/**
|
|
95
|
+
* The value written into the creating node's data field when this model is
|
|
96
|
+
* picked from the Models tab, for models whose catalog id differs from the
|
|
97
|
+
* stored value. Only `suno-generate` diverges today (id `suno-v5` → `data.model`
|
|
98
|
+
* "V5"). Omit when the id IS the stored value (the common case).
|
|
99
|
+
*/
|
|
100
|
+
dataValue?: string
|
|
101
|
+
/** One-liner. Visible to Claude in MCP `list_models` output. */
|
|
102
|
+
description: string
|
|
103
|
+
/** Short tags Claude matches against user intent. */
|
|
104
|
+
useCases: readonly string[]
|
|
105
|
+
/** Capabilities flags: "reference-image", "end-frame", "audio", etc. */
|
|
106
|
+
features?: readonly string[]
|
|
107
|
+
aspectRatios?: readonly string[]
|
|
108
|
+
resolutions?: readonly string[]
|
|
109
|
+
qualities?: readonly string[]
|
|
110
|
+
durations?: readonly number[]
|
|
111
|
+
pricing: readonly PriceVariant[]
|
|
112
|
+
/** Editorial highlight — "best in tier". Surfaces in MCP output as a ⭐. */
|
|
113
|
+
featured?: boolean
|
|
114
|
+
/**
|
|
115
|
+
* Per-model overrides for the global value label decorations (e.g. Hailuo
|
|
116
|
+
* 2.3 Pro labels its 1080P resolution as "1080P (6s max)" because the
|
|
117
|
+
* model caps long durations at lower resolution). Falls back to
|
|
118
|
+
* `MODEL_VALUE_LABELS` for values not listed here.
|
|
119
|
+
*/
|
|
120
|
+
valueLabels?: Record<string, string>
|
|
121
|
+
/**
|
|
122
|
+
* Hide from MCP `list_models` output and tool model enums. Used for legacy
|
|
123
|
+
* model versions superseded by newer entries — they remain in the catalog
|
|
124
|
+
* (and accessible via direct API / frontend pickers for back-compat) but
|
|
125
|
+
* stop showing up in Claude.ai's model picker, which keeps the surface
|
|
126
|
+
* tight and steers the agent to the current generation.
|
|
127
|
+
*
|
|
128
|
+
* Frontend pickers ignore this flag.
|
|
129
|
+
*/
|
|
130
|
+
mcpHidden?: boolean
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Editorial recommendations — short "best for X" picks Claude can echo back
|
|
135
|
+
* to the user when they don't know which model to use. Surfaces as a "Quick
|
|
136
|
+
* recommendations" footer in MCP `list_models` output.
|
|
137
|
+
*/
|
|
138
|
+
export interface ModelRecommendation {
|
|
139
|
+
intent: string
|
|
140
|
+
modelIds: readonly string[]
|
|
141
|
+
note: string
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export const MODEL_RECOMMENDATIONS: readonly ModelRecommendation[] = [
|
|
145
|
+
// image
|
|
146
|
+
{ intent: "best for typography / logos / text-heavy", modelIds: ["nano-banana-pro", "gpt-image-2"], note: "Nano Banana Pro for diagrams / complex text; GPT Image 2 for logos and short copy." },
|
|
147
|
+
{ intent: "cheapest realistic image", modelIds: ["z-image", "qwen", "imagen4-fast"], note: "Z-Image is the cheapest at 1 credit. Qwen / Imagen4 Fast for slightly higher quality." },
|
|
148
|
+
{ intent: "highest fidelity image", modelIds: ["nano-banana-pro", "imagen4-ultra", "flux-flex"], note: "Pick by family preference; all three are premium tiers." },
|
|
149
|
+
{ intent: "image edit / restyle", modelIds: ["flux-kontext", "ideogram-remix", "seedream-5-lite-i2i"], note: "Flux Kontext preserves identity; Ideogram Remix is character-aware; Seedream 5 Lite for instruction-based edits." },
|
|
150
|
+
{ intent: "highest-resolution image (4K / 8K)", modelIds: ["topaz-image-upscale", "nano-banana-pro", "gpt-image-2"], note: "Generate at native then Topaz upscale for 8K." },
|
|
151
|
+
{ intent: "background removal / cutout", modelIds: ["recraft-remove-bg"], note: "1 credit, no prompt needed." },
|
|
152
|
+
// video
|
|
153
|
+
{ 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." },
|
|
154
|
+
{ 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." },
|
|
155
|
+
{ 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]." },
|
|
156
|
+
{ intent: "music / song generation", modelIds: ["suno-v5", "suno"], note: "Suno v5 has better vocal quality at the same price." },
|
|
157
|
+
{ intent: "voice over / narration", modelIds: ["elevenlabs-v3", "elevenlabs-turbo"], note: "v3 supports [audio tags] for emotion; Turbo is cheaper for plain narration." },
|
|
158
|
+
{ intent: "lip-sync a portrait to audio", modelIds: ["kling-avatar-pro", "kling-avatar", "infinitalk"], note: "Pro for best mouth shape; InfiniTalk for resolution control." },
|
|
159
|
+
{ intent: "transcription / captions", modelIds: ["elevenlabs-stt"], note: "Word-level timestamps available." },
|
|
160
|
+
{ intent: "motion transfer (drive a subject by another video)", modelIds: ["motion-transfer", "kling-3.0-motion"], note: "Kling 2.6 base is cheap; Kling 3.0 is premium." },
|
|
161
|
+
] as const
|
|
162
|
+
|
|
163
|
+
// =============================================================================
|
|
164
|
+
// Aspect ratio presets (mirrored from frontend's model-options.ts so backend
|
|
165
|
+
// can consume them. Keep in sync when adding a new ratio set.)
|
|
166
|
+
// =============================================================================
|
|
167
|
+
const NANO_BANANA_RATIOS = ["1:1", "16:9", "9:16", "4:3", "3:4", "3:2", "2:3", "4:5", "5:4", "21:9"] as const
|
|
168
|
+
// Note: these arrays are synchronized with the frontend's
|
|
169
|
+
// `model-options.ts` constants of the same names. The frontend now imports
|
|
170
|
+
// them via the `getAspectRatioOptions(modelId)` helper; if you update one
|
|
171
|
+
// you no longer need to touch the other.
|
|
172
|
+
const FLUX_RATIOS = ["1:1", "16:9", "9:16", "4:3", "3:4", "3:2", "2:3"] as const
|
|
173
|
+
const KONTEXT_RATIOS = ["1:1", "16:9", "9:16", "4:3", "3:4", "21:9"] as const
|
|
174
|
+
const GROK_RATIOS = ["1:1", "16:9", "9:16", "3:2", "2:3"] as const
|
|
175
|
+
const GPT_IMAGE_RATIOS = ["1:1", "3:2", "2:3"] as const
|
|
176
|
+
const GPT_IMAGE_2_RATIOS = ["auto", "1:1", "16:9", "9:16", "4:3", "3:4"] as const
|
|
177
|
+
const IMAGEN4_RATIOS = ["1:1", "16:9", "9:16", "4:3", "3:4"] as const
|
|
178
|
+
const IDEOGRAM_RATIOS = ["1:1", "16:9", "9:16", "4:3", "3:4"] as const
|
|
179
|
+
const SEEDREAM_RATIOS = ["1:1", "16:9", "9:16", "4:3", "3:4", "3:2", "2:3", "21:9"] as const
|
|
180
|
+
const Z_IMAGE_RATIOS = ["1:1", "16:9", "9:16", "4:3", "3:4"] as const
|
|
181
|
+
|
|
182
|
+
const VIDEO_RATIOS_HV = ["16:9", "9:16"] as const
|
|
183
|
+
const VIDEO_RATIOS_HVS = ["16:9", "9:16", "1:1"] as const
|
|
184
|
+
const WAN_27_IMAGE_RATIOS = ["1:1", "16:9", "9:16", "4:3", "3:4", "21:9", "8:1", "1:8"] as const
|
|
185
|
+
const VIDEO_RATIOS_HVS345 = ["16:9", "9:16", "1:1", "4:3", "3:4"] as const
|
|
186
|
+
// Seedance 2.x only — KIE accepts the full fixed-ratio set incl. 21:9
|
|
187
|
+
// (docs.kie.ai/market/bytedance/seedance-2). Kept separate from HVS so the
|
|
188
|
+
// wider set can't leak to models that don't support it.
|
|
189
|
+
const VIDEO_RATIOS_SEEDANCE_2 = ["16:9", "9:16", "1:1", "4:3", "3:4", "21:9", "adaptive"] as const
|
|
190
|
+
|
|
191
|
+
// =============================================================================
|
|
192
|
+
// IMAGE MODELS
|
|
193
|
+
// =============================================================================
|
|
194
|
+
const IMAGE_MODELS: Record<string, ModelCatalogEntry> = {
|
|
195
|
+
// ── Google Nano Banana ──
|
|
196
|
+
"nano-banana": {
|
|
197
|
+
id: "nano-banana",
|
|
198
|
+
kind: "image",
|
|
199
|
+
// Accepts a reference image via image_input — works as both t2i (no
|
|
200
|
+
// ref) and i2i (with ref) under the same id, so list in both modes.
|
|
201
|
+
modes: ["t2i", "i2i"] as const,
|
|
202
|
+
family: "Google",
|
|
203
|
+
label: "Nano Banana",
|
|
204
|
+
series: "Nano Banana",
|
|
205
|
+
description: "Budget-friendly realistic generation. Wide aspect-ratio support up to 21:9.",
|
|
206
|
+
useCases: ["realistic", "general", "fast"],
|
|
207
|
+
features: ["reference-image"],
|
|
208
|
+
aspectRatios: NANO_BANANA_RATIOS,
|
|
209
|
+
pricing: [{ identifier: "nano-banana", credits: 1, note: "1K" }],
|
|
210
|
+
mcpHidden: true,
|
|
211
|
+
},
|
|
212
|
+
"nano-banana-2": {
|
|
213
|
+
id: "nano-banana-2",
|
|
214
|
+
kind: "image",
|
|
215
|
+
// Accepts reference images via image_input — same as the rest of the
|
|
216
|
+
// Nano Banana family — so reachable for both t2i and i2i.
|
|
217
|
+
modes: ["t2i", "i2i"] as const,
|
|
218
|
+
family: "Google",
|
|
219
|
+
label: "Nano Banana 2",
|
|
220
|
+
series: "Nano Banana",
|
|
221
|
+
description: "Newer Nano Banana with native resolution control (1K/2K/4K) and Google Search context.",
|
|
222
|
+
useCases: ["realistic", "high-res", "factual"],
|
|
223
|
+
features: ["reference-image", "google-search"],
|
|
224
|
+
aspectRatios: NANO_BANANA_RATIOS,
|
|
225
|
+
resolutions: ["1K", "2K", "4K"],
|
|
226
|
+
pricing: [
|
|
227
|
+
{ identifier: "nano-banana-2", credits: 4, note: "1K default" },
|
|
228
|
+
{ identifier: "nano-banana-2:2K", credits: 5, note: "2K" },
|
|
229
|
+
{ identifier: "nano-banana-2:4K", credits: 5, note: "4K" },
|
|
230
|
+
],
|
|
231
|
+
},
|
|
232
|
+
"nano-banana-pro": {
|
|
233
|
+
id: "nano-banana-pro",
|
|
234
|
+
kind: "image",
|
|
235
|
+
modes: ["t2i", "i2i"] as const,
|
|
236
|
+
family: "Google",
|
|
237
|
+
label: "Nano Banana Pro",
|
|
238
|
+
series: "Nano Banana",
|
|
239
|
+
description: "Top-tier Nano Banana — best for text rendering, diagrams, and complex compositions.",
|
|
240
|
+
useCases: ["typography", "diagrams", "premium", "text-heavy"],
|
|
241
|
+
features: ["reference-image"],
|
|
242
|
+
aspectRatios: NANO_BANANA_RATIOS,
|
|
243
|
+
resolutions: ["1K", "2K", "4K"],
|
|
244
|
+
pricing: [
|
|
245
|
+
{ identifier: "nano-banana-pro", credits: 5, note: "1K / 2K" },
|
|
246
|
+
{ identifier: "nano-banana-pro:4K", credits: 6, note: "4K" },
|
|
247
|
+
],
|
|
248
|
+
featured: true,
|
|
249
|
+
},
|
|
250
|
+
"nano-banana-edit": {
|
|
251
|
+
id: "nano-banana-edit",
|
|
252
|
+
kind: "image",
|
|
253
|
+
modes: ["edit"] as const,
|
|
254
|
+
family: "Google",
|
|
255
|
+
label: "Nano Banana Edit",
|
|
256
|
+
series: "Nano Banana",
|
|
257
|
+
description: "Image-to-image edits via Google's Nano Banana family. Good general-purpose editor.",
|
|
258
|
+
useCases: ["edit", "remix", "general"],
|
|
259
|
+
features: ["reference-image"],
|
|
260
|
+
aspectRatios: NANO_BANANA_RATIOS,
|
|
261
|
+
pricing: [{ identifier: "nano-banana-edit", credits: 2 }],
|
|
262
|
+
},
|
|
263
|
+
|
|
264
|
+
// ── Black Forest Labs (Flux) ──
|
|
265
|
+
"flux": {
|
|
266
|
+
id: "flux",
|
|
267
|
+
kind: "image",
|
|
268
|
+
modes: ["t2i"] as const,
|
|
269
|
+
family: "Black Forest Labs",
|
|
270
|
+
label: "Flux 2 Pro",
|
|
271
|
+
series: "Flux",
|
|
272
|
+
description: "Flux 2 Pro text-to-image. Strong realism, fast. Resolution lever to 2K.",
|
|
273
|
+
useCases: ["realistic", "general"],
|
|
274
|
+
features: ["reference-image"],
|
|
275
|
+
aspectRatios: FLUX_RATIOS,
|
|
276
|
+
resolutions: ["1K", "2K"],
|
|
277
|
+
pricing: [
|
|
278
|
+
{ identifier: "flux", credits: 2, note: "1K default" },
|
|
279
|
+
{ identifier: "flux:2K", credits: 2, note: "2K" },
|
|
280
|
+
],
|
|
281
|
+
},
|
|
282
|
+
"flux-flex": {
|
|
283
|
+
id: "flux-flex",
|
|
284
|
+
kind: "image",
|
|
285
|
+
modes: ["t2i"] as const,
|
|
286
|
+
family: "Black Forest Labs",
|
|
287
|
+
label: "Flux 2 Flex",
|
|
288
|
+
series: "Flux",
|
|
289
|
+
description: "Flux 2 Flex — premium fidelity, more flexible composition. Pricier than Pro.",
|
|
290
|
+
useCases: ["premium", "fidelity", "realistic"],
|
|
291
|
+
features: ["reference-image"],
|
|
292
|
+
aspectRatios: FLUX_RATIOS,
|
|
293
|
+
resolutions: ["1K", "2K"],
|
|
294
|
+
pricing: [
|
|
295
|
+
{ identifier: "flux-flex", credits: 4, note: "1K default" },
|
|
296
|
+
{ identifier: "flux-flex:2K", credits: 6, note: "2K" },
|
|
297
|
+
],
|
|
298
|
+
},
|
|
299
|
+
"flux-i2i": {
|
|
300
|
+
id: "flux-i2i",
|
|
301
|
+
kind: "image",
|
|
302
|
+
modes: ["i2i"] as const,
|
|
303
|
+
family: "Black Forest Labs",
|
|
304
|
+
label: "Flux 2 Flex (I2I)",
|
|
305
|
+
series: "Flux",
|
|
306
|
+
description: "Image-to-image with Flux Flex. Honors source image structure while applying prompt.",
|
|
307
|
+
useCases: ["edit", "restyle", "transform"],
|
|
308
|
+
features: ["reference-image"],
|
|
309
|
+
aspectRatios: FLUX_RATIOS,
|
|
310
|
+
resolutions: ["1K", "2K"],
|
|
311
|
+
pricing: [
|
|
312
|
+
{ identifier: "flux-i2i", credits: 4, note: "1K default" },
|
|
313
|
+
{ identifier: "flux-i2i:2K", credits: 6, note: "2K" },
|
|
314
|
+
],
|
|
315
|
+
},
|
|
316
|
+
"flux-pro-i2i": {
|
|
317
|
+
id: "flux-pro-i2i",
|
|
318
|
+
kind: "image",
|
|
319
|
+
modes: ["i2i"] as const,
|
|
320
|
+
family: "Black Forest Labs",
|
|
321
|
+
label: "Flux 2 Pro (I2I)",
|
|
322
|
+
series: "Flux",
|
|
323
|
+
description: "Image-to-image with Flux Pro. Cheaper than Flex variant, good general edits.",
|
|
324
|
+
useCases: ["edit", "restyle"],
|
|
325
|
+
features: ["reference-image"],
|
|
326
|
+
aspectRatios: FLUX_RATIOS,
|
|
327
|
+
resolutions: ["1K", "2K"],
|
|
328
|
+
pricing: [
|
|
329
|
+
{ identifier: "flux-pro-i2i", credits: 2, note: "1K default" },
|
|
330
|
+
{ identifier: "flux-pro-i2i:2K", credits: 2, note: "2K" },
|
|
331
|
+
],
|
|
332
|
+
},
|
|
333
|
+
"flux-kontext": {
|
|
334
|
+
id: "flux-kontext",
|
|
335
|
+
kind: "image",
|
|
336
|
+
// Kontext supports both pure t2i (no input image) and edit (with input).
|
|
337
|
+
modes: ["t2i", "edit"] as const,
|
|
338
|
+
family: "Black Forest Labs",
|
|
339
|
+
label: "Flux Kontext Pro",
|
|
340
|
+
series: "Flux",
|
|
341
|
+
description: "Context-aware editing and style transfer. Strong at preserving subject identity through edits.",
|
|
342
|
+
useCases: ["edit", "style-transfer", "preserve-identity"],
|
|
343
|
+
features: ["reference-image"],
|
|
344
|
+
aspectRatios: KONTEXT_RATIOS,
|
|
345
|
+
pricing: [{ identifier: "flux-kontext", credits: 2 }],
|
|
346
|
+
},
|
|
347
|
+
"flux-kontext-max": {
|
|
348
|
+
id: "flux-kontext-max",
|
|
349
|
+
kind: "image",
|
|
350
|
+
modes: ["t2i", "edit"] as const,
|
|
351
|
+
family: "Black Forest Labs",
|
|
352
|
+
label: "Flux Kontext Max",
|
|
353
|
+
series: "Flux",
|
|
354
|
+
description: "Premium Kontext — highest fidelity context-aware edits.",
|
|
355
|
+
useCases: ["edit", "style-transfer", "premium"],
|
|
356
|
+
features: ["reference-image"],
|
|
357
|
+
aspectRatios: KONTEXT_RATIOS,
|
|
358
|
+
pricing: [{ identifier: "flux-kontext-max", credits: 4 }],
|
|
359
|
+
},
|
|
360
|
+
// ── Replicate "Open" (uncensored) — run direct via Replicate, not KIE ──
|
|
361
|
+
"flux-2-klein": {
|
|
362
|
+
id: "flux-2-klein",
|
|
363
|
+
kind: "image",
|
|
364
|
+
modes: ["t2i"] as const,
|
|
365
|
+
family: "Black Forest Labs",
|
|
366
|
+
label: "Flux 2 Klein (Open)",
|
|
367
|
+
series: "Flux",
|
|
368
|
+
description: "Open Flux 2 9B from BFL — fast, no safety filter. Runs direct on Replicate.",
|
|
369
|
+
useCases: ["realistic", "fast", "unfiltered"],
|
|
370
|
+
features: ["reference-image"],
|
|
371
|
+
aspectRatios: FLUX_RATIOS,
|
|
372
|
+
resolutions: ["0.5 MP", "1 MP", "2 MP", "4 MP"],
|
|
373
|
+
pricing: [
|
|
374
|
+
{ identifier: "flux-2-klein", credits: 1 }, // 1MP 0ref (default)
|
|
375
|
+
{ identifier: "flux-2-klein:1MP:0ref", credits: 1 }, // min
|
|
376
|
+
{ identifier: "flux-2-klein:4MP:8ref", credits: 11 }, // max
|
|
377
|
+
],
|
|
378
|
+
},
|
|
379
|
+
"kontext-multi": {
|
|
380
|
+
id: "kontext-multi",
|
|
381
|
+
kind: "image",
|
|
382
|
+
modes: ["i2i", "edit"] as const,
|
|
383
|
+
family: "Black Forest Labs",
|
|
384
|
+
label: "Kontext Multi (Open)",
|
|
385
|
+
series: "Flux",
|
|
386
|
+
description: "Multi-image Flux Kontext Pro via Replicate — up to 4 input images, no safety filter.",
|
|
387
|
+
useCases: ["edit", "multi-reference", "unfiltered"],
|
|
388
|
+
features: ["reference-image"],
|
|
389
|
+
aspectRatios: KONTEXT_RATIOS,
|
|
390
|
+
pricing: [{ identifier: "kontext-multi", credits: 3 }],
|
|
391
|
+
},
|
|
392
|
+
"flux-2-pro": {
|
|
393
|
+
id: "flux-2-pro",
|
|
394
|
+
kind: "image",
|
|
395
|
+
modes: ["t2i", "i2i"] as const,
|
|
396
|
+
family: "Black Forest Labs",
|
|
397
|
+
label: "Flux 2 Pro (Safety Tolerance)",
|
|
398
|
+
series: "Flux",
|
|
399
|
+
description: "BFL Flux 2 Pro flagship via Replicate — exposes the `safety_tolerance` lever pinned to 5 (max for Pro). Accepts up to 4 reference images.",
|
|
400
|
+
useCases: ["realistic", "high-quality", "unfiltered", "reference"],
|
|
401
|
+
features: ["reference-image"],
|
|
402
|
+
aspectRatios: FLUX_RATIOS,
|
|
403
|
+
resolutions: ["0.5 MP", "1 MP", "2 MP", "4 MP"],
|
|
404
|
+
pricing: [
|
|
405
|
+
{ identifier: "flux-2-pro", credits: 3 }, // 2MP 0ref (default)
|
|
406
|
+
{ identifier: "flux-2-pro:0.5MP:0ref", credits: 2 }, // min
|
|
407
|
+
{ identifier: "flux-2-pro:4MP:8ref", credits: 28 }, // max
|
|
408
|
+
],
|
|
409
|
+
},
|
|
410
|
+
"flux-2-max": {
|
|
411
|
+
id: "flux-2-max",
|
|
412
|
+
kind: "image",
|
|
413
|
+
modes: ["t2i", "i2i"] as const,
|
|
414
|
+
family: "Black Forest Labs",
|
|
415
|
+
label: "Flux 2 Max (Safety Tolerance)",
|
|
416
|
+
series: "Flux",
|
|
417
|
+
description: "BFL Flux 2 Max — even larger sibling of Pro via Replicate, safety_tolerance=5, up to 8 reference images. Variable pricing by MP and ref count.",
|
|
418
|
+
useCases: ["realistic", "high-quality", "unfiltered", "multi-reference"],
|
|
419
|
+
features: ["reference-image"],
|
|
420
|
+
aspectRatios: FLUX_RATIOS,
|
|
421
|
+
resolutions: ["0.5 MP", "1 MP", "2 MP", "4 MP"],
|
|
422
|
+
pricing: [
|
|
423
|
+
{ identifier: "flux-2-max", credits: 7 }, // 2MP 0ref (default)
|
|
424
|
+
{ identifier: "flux-2-max:0.5MP:0ref", credits: 2 }, // min
|
|
425
|
+
{ identifier: "flux-2-max:4MP:8ref", credits: 62 }, // max
|
|
426
|
+
],
|
|
427
|
+
},
|
|
428
|
+
|
|
429
|
+
// ── OpenAI (GPT Image) ──
|
|
430
|
+
"gpt-image": {
|
|
431
|
+
id: "gpt-image",
|
|
432
|
+
kind: "image",
|
|
433
|
+
modes: ["t2i"] as const,
|
|
434
|
+
family: "OpenAI",
|
|
435
|
+
label: "GPT Image 1.5",
|
|
436
|
+
series: "GPT Image",
|
|
437
|
+
description: "Best for text rendering, typography, logos, and graphic design. Limited aspect ratios.",
|
|
438
|
+
useCases: ["typography", "logo", "graphic-design", "text-heavy"],
|
|
439
|
+
features: ["reference-image"],
|
|
440
|
+
aspectRatios: GPT_IMAGE_RATIOS,
|
|
441
|
+
qualities: ["medium", "high"],
|
|
442
|
+
pricing: [
|
|
443
|
+
{ identifier: "gpt-image", credits: 4, note: "medium default" },
|
|
444
|
+
{ identifier: "gpt-image:high", credits: 6, note: "high quality" },
|
|
445
|
+
],
|
|
446
|
+
valueLabels: { "3:2": "3:2 (Landscape)", "2:3": "2:3 (Portrait)" },
|
|
447
|
+
mcpHidden: true,
|
|
448
|
+
},
|
|
449
|
+
"gpt-image-i2i": {
|
|
450
|
+
id: "gpt-image-i2i",
|
|
451
|
+
kind: "image",
|
|
452
|
+
modes: ["i2i"] as const,
|
|
453
|
+
family: "OpenAI",
|
|
454
|
+
label: "GPT Image 1.5 (I2I)",
|
|
455
|
+
series: "GPT Image",
|
|
456
|
+
description: "Image-to-image with GPT Image 1.5. Good text-aware edits.",
|
|
457
|
+
useCases: ["edit", "typography", "remix"],
|
|
458
|
+
features: ["reference-image"],
|
|
459
|
+
aspectRatios: GPT_IMAGE_RATIOS,
|
|
460
|
+
qualities: ["medium", "high"],
|
|
461
|
+
pricing: [
|
|
462
|
+
{ identifier: "gpt-image-i2i", credits: 4, note: "medium default" },
|
|
463
|
+
{ identifier: "gpt-image-i2i:high", credits: 6, note: "high quality" },
|
|
464
|
+
],
|
|
465
|
+
valueLabels: { "3:2": "3:2 (Landscape)", "2:3": "2:3 (Portrait)" },
|
|
466
|
+
mcpHidden: true,
|
|
467
|
+
},
|
|
468
|
+
"gpt-image-2": {
|
|
469
|
+
id: "gpt-image-2",
|
|
470
|
+
kind: "image",
|
|
471
|
+
modes: ["t2i"] as const,
|
|
472
|
+
family: "OpenAI",
|
|
473
|
+
label: "GPT Image 2",
|
|
474
|
+
series: "GPT Image",
|
|
475
|
+
description: "Next-gen GPT Image — broader aspect ratios, resolution-based pricing (1K/2K/4K).",
|
|
476
|
+
useCases: ["typography", "high-res", "general"],
|
|
477
|
+
features: ["reference-image"],
|
|
478
|
+
aspectRatios: GPT_IMAGE_2_RATIOS,
|
|
479
|
+
resolutions: ["1K", "2K", "4K"],
|
|
480
|
+
pricing: [
|
|
481
|
+
{ identifier: "gpt-image-2", credits: 1, note: "1K default" },
|
|
482
|
+
{ identifier: "gpt-image-2:2K", credits: 3, note: "2K" },
|
|
483
|
+
{ identifier: "gpt-image-2:4K", credits: 6, note: "4K" },
|
|
484
|
+
],
|
|
485
|
+
},
|
|
486
|
+
"gpt-image-2-i2i": {
|
|
487
|
+
id: "gpt-image-2-i2i",
|
|
488
|
+
kind: "image",
|
|
489
|
+
modes: ["i2i"] as const,
|
|
490
|
+
family: "OpenAI",
|
|
491
|
+
label: "GPT Image 2 (I2I)",
|
|
492
|
+
series: "GPT Image",
|
|
493
|
+
description: "Image-to-image with GPT Image 2.",
|
|
494
|
+
useCases: ["edit", "high-res"],
|
|
495
|
+
features: ["reference-image"],
|
|
496
|
+
aspectRatios: GPT_IMAGE_2_RATIOS,
|
|
497
|
+
resolutions: ["1K", "2K", "4K"],
|
|
498
|
+
pricing: [
|
|
499
|
+
{ identifier: "gpt-image-2-i2i", credits: 1, note: "1K default" },
|
|
500
|
+
{ identifier: "gpt-image-2-i2i:2K", credits: 3, note: "2K" },
|
|
501
|
+
{ identifier: "gpt-image-2-i2i:4K", credits: 6, note: "4K" },
|
|
502
|
+
],
|
|
503
|
+
},
|
|
504
|
+
|
|
505
|
+
// ── Ideogram ──
|
|
506
|
+
// Ideogram models use a `rendering_speed` (TURBO / BALANCED / QUALITY)
|
|
507
|
+
// dimension that's distinct from the `quality` lever — frontend exposes
|
|
508
|
+
// it via a separate dropdown — so we don't model it under `qualities`.
|
|
509
|
+
// The composite pricing identifiers (`ideogram-v3:TURBO`, etc.) capture
|
|
510
|
+
// the per-tier credit cost without claiming Claude can pick it from
|
|
511
|
+
// the MCP tool's `quality` enum.
|
|
512
|
+
"ideogram-v3": {
|
|
513
|
+
id: "ideogram-v3",
|
|
514
|
+
kind: "image",
|
|
515
|
+
modes: ["t2i"] as const,
|
|
516
|
+
family: "Ideogram",
|
|
517
|
+
label: "Ideogram V3",
|
|
518
|
+
series: "Ideogram",
|
|
519
|
+
description: "Strong typography and stylized illustration. Speed/quality tiered (TURBO/BALANCED/QUALITY).",
|
|
520
|
+
useCases: ["typography", "illustration", "stylized"],
|
|
521
|
+
aspectRatios: IDEOGRAM_RATIOS,
|
|
522
|
+
pricing: [
|
|
523
|
+
{ identifier: "ideogram-v3", credits: 2, note: "BALANCED default" },
|
|
524
|
+
{ identifier: "ideogram-v3:TURBO", credits: 1, note: "fastest" },
|
|
525
|
+
{ identifier: "ideogram-v3:QUALITY", credits: 3, note: "best quality" },
|
|
526
|
+
],
|
|
527
|
+
},
|
|
528
|
+
"ideogram-edit": {
|
|
529
|
+
id: "ideogram-edit",
|
|
530
|
+
kind: "image",
|
|
531
|
+
modes: ["edit"] as const,
|
|
532
|
+
family: "Ideogram",
|
|
533
|
+
label: "Ideogram Edit",
|
|
534
|
+
series: "Ideogram",
|
|
535
|
+
description: "Inpainting / mask-based editing with Ideogram. Pair with a mask URL.",
|
|
536
|
+
useCases: ["edit", "inpaint", "typography"],
|
|
537
|
+
features: ["reference-image", "mask"],
|
|
538
|
+
// No aspectRatios — Ideogram Edit takes output dimensions from the input
|
|
539
|
+
// image + mask. Backend strips aspect_ratio for this provider.
|
|
540
|
+
pricing: [
|
|
541
|
+
{ identifier: "ideogram-edit", credits: 5, note: "BALANCED default" },
|
|
542
|
+
{ identifier: "ideogram-edit:TURBO", credits: 3, note: "fastest" },
|
|
543
|
+
{ identifier: "ideogram-edit:QUALITY", credits: 6, note: "best quality" },
|
|
544
|
+
],
|
|
545
|
+
},
|
|
546
|
+
"ideogram-remix": {
|
|
547
|
+
id: "ideogram-remix",
|
|
548
|
+
kind: "image",
|
|
549
|
+
modes: ["i2i"] as const,
|
|
550
|
+
family: "Ideogram",
|
|
551
|
+
label: "Ideogram Remix",
|
|
552
|
+
series: "Ideogram",
|
|
553
|
+
description: "Ideogram remix — character-aware restyling driven by reference images.",
|
|
554
|
+
useCases: ["remix", "character", "stylized"],
|
|
555
|
+
features: ["reference-image"],
|
|
556
|
+
aspectRatios: IDEOGRAM_RATIOS,
|
|
557
|
+
pricing: [
|
|
558
|
+
{ identifier: "ideogram-remix", credits: 5, note: "BALANCED default" },
|
|
559
|
+
{ identifier: "ideogram-remix:TURBO", credits: 3, note: "fastest" },
|
|
560
|
+
{ identifier: "ideogram-remix:QUALITY", credits: 6, note: "best quality" },
|
|
561
|
+
],
|
|
562
|
+
},
|
|
563
|
+
"ideogram-reframe": {
|
|
564
|
+
id: "ideogram-reframe",
|
|
565
|
+
kind: "image",
|
|
566
|
+
modes: ["edit"] as const,
|
|
567
|
+
family: "Ideogram",
|
|
568
|
+
label: "Ideogram Reframe",
|
|
569
|
+
series: "Ideogram",
|
|
570
|
+
description: "Outpaint / reframe to a new aspect ratio while preserving subject.",
|
|
571
|
+
useCases: ["outpaint", "reframe"],
|
|
572
|
+
features: ["reference-image"],
|
|
573
|
+
aspectRatios: IDEOGRAM_RATIOS,
|
|
574
|
+
pricing: [
|
|
575
|
+
{ identifier: "ideogram-reframe", credits: 2, note: "BALANCED default" },
|
|
576
|
+
{ identifier: "ideogram-reframe:TURBO", credits: 1, note: "fastest" },
|
|
577
|
+
{ identifier: "ideogram-reframe:QUALITY", credits: 3, note: "best quality" },
|
|
578
|
+
],
|
|
579
|
+
},
|
|
580
|
+
|
|
581
|
+
// ── Google Imagen ──
|
|
582
|
+
"imagen4": {
|
|
583
|
+
id: "imagen4",
|
|
584
|
+
kind: "image",
|
|
585
|
+
modes: ["t2i"] as const,
|
|
586
|
+
family: "Google",
|
|
587
|
+
label: "Imagen 4",
|
|
588
|
+
series: "Imagen",
|
|
589
|
+
description: "Google's Imagen 4 — strong photographic quality and prompt fidelity.",
|
|
590
|
+
useCases: ["photographic", "realistic"],
|
|
591
|
+
aspectRatios: IMAGEN4_RATIOS,
|
|
592
|
+
pricing: [{ identifier: "imagen4", credits: 2 }],
|
|
593
|
+
},
|
|
594
|
+
"imagen4-fast": {
|
|
595
|
+
id: "imagen4-fast",
|
|
596
|
+
kind: "image",
|
|
597
|
+
modes: ["t2i"] as const,
|
|
598
|
+
family: "Google",
|
|
599
|
+
label: "Imagen 4 Fast",
|
|
600
|
+
series: "Imagen",
|
|
601
|
+
description: "Cheaper / quicker Imagen 4 tier.",
|
|
602
|
+
useCases: ["fast", "realistic", "general"],
|
|
603
|
+
aspectRatios: IMAGEN4_RATIOS,
|
|
604
|
+
pricing: [{ identifier: "imagen4-fast", credits: 1 }],
|
|
605
|
+
},
|
|
606
|
+
"imagen4-ultra": {
|
|
607
|
+
id: "imagen4-ultra",
|
|
608
|
+
kind: "image",
|
|
609
|
+
modes: ["t2i"] as const,
|
|
610
|
+
family: "Google",
|
|
611
|
+
label: "Imagen 4 Ultra",
|
|
612
|
+
series: "Imagen",
|
|
613
|
+
description: "Premium Imagen 4 — highest fidelity, slower / more credits.",
|
|
614
|
+
useCases: ["premium", "photographic"],
|
|
615
|
+
aspectRatios: IMAGEN4_RATIOS,
|
|
616
|
+
pricing: [{ identifier: "imagen4-ultra", credits: 3 }],
|
|
617
|
+
},
|
|
618
|
+
|
|
619
|
+
// ── Bytedance Seedream ──
|
|
620
|
+
"seedream": {
|
|
621
|
+
id: "seedream",
|
|
622
|
+
kind: "image",
|
|
623
|
+
modes: ["t2i"] as const,
|
|
624
|
+
family: "Bytedance",
|
|
625
|
+
label: "Seedream 4.5",
|
|
626
|
+
series: "Seedream",
|
|
627
|
+
description: "Bytedance's Seedream 4.5 — precise control, high resolution at 4K via :high.",
|
|
628
|
+
useCases: ["realistic", "high-res", "control"],
|
|
629
|
+
features: ["reference-image"],
|
|
630
|
+
aspectRatios: SEEDREAM_RATIOS,
|
|
631
|
+
qualities: ["basic", "high"],
|
|
632
|
+
pricing: [
|
|
633
|
+
{ identifier: "seedream", credits: 2, note: "basic / 2K default" },
|
|
634
|
+
{ identifier: "seedream:high", credits: 4, note: "high / 4K" },
|
|
635
|
+
],
|
|
636
|
+
mcpHidden: true,
|
|
637
|
+
},
|
|
638
|
+
"seedream-edit": {
|
|
639
|
+
id: "seedream-edit",
|
|
640
|
+
kind: "image",
|
|
641
|
+
modes: ["edit"] as const,
|
|
642
|
+
family: "Bytedance",
|
|
643
|
+
label: "Seedream 4.5 Edit",
|
|
644
|
+
series: "Seedream",
|
|
645
|
+
description: "Image editing / transforms via Seedream 4.5.",
|
|
646
|
+
useCases: ["edit", "transform"],
|
|
647
|
+
features: ["reference-image"],
|
|
648
|
+
aspectRatios: SEEDREAM_RATIOS,
|
|
649
|
+
qualities: ["basic", "high"],
|
|
650
|
+
pricing: [
|
|
651
|
+
{ identifier: "seedream-edit", credits: 2, note: "basic default" },
|
|
652
|
+
{ identifier: "seedream-edit:high", credits: 4, note: "high quality" },
|
|
653
|
+
],
|
|
654
|
+
mcpHidden: true,
|
|
655
|
+
},
|
|
656
|
+
"seedream-5-lite": {
|
|
657
|
+
id: "seedream-5-lite",
|
|
658
|
+
kind: "image",
|
|
659
|
+
modes: ["t2i"] as const,
|
|
660
|
+
family: "Bytedance",
|
|
661
|
+
label: "Seedream 5 Lite",
|
|
662
|
+
series: "Seedream",
|
|
663
|
+
description: "Newer Seedream 5 Lite — instruction-based generation, visual reasoning.",
|
|
664
|
+
useCases: ["realistic", "instruction"],
|
|
665
|
+
features: ["reference-image"],
|
|
666
|
+
aspectRatios: SEEDREAM_RATIOS,
|
|
667
|
+
qualities: ["basic", "high"],
|
|
668
|
+
pricing: [
|
|
669
|
+
{ identifier: "seedream-5-lite", credits: 2, note: "basic default" },
|
|
670
|
+
{ identifier: "seedream-5-lite:high", credits: 5, note: "high quality" },
|
|
671
|
+
],
|
|
672
|
+
},
|
|
673
|
+
"seedream-5-lite-i2i": {
|
|
674
|
+
id: "seedream-5-lite-i2i",
|
|
675
|
+
kind: "image",
|
|
676
|
+
modes: ["i2i"] as const,
|
|
677
|
+
family: "Bytedance",
|
|
678
|
+
label: "Seedream 5 Lite (I2I)",
|
|
679
|
+
series: "Seedream",
|
|
680
|
+
description: "Image-to-image with Seedream 5 Lite.",
|
|
681
|
+
useCases: ["edit", "instruction"],
|
|
682
|
+
features: ["reference-image"],
|
|
683
|
+
aspectRatios: SEEDREAM_RATIOS,
|
|
684
|
+
qualities: ["basic", "high"],
|
|
685
|
+
pricing: [
|
|
686
|
+
{ identifier: "seedream-5-lite-i2i", credits: 2, note: "basic default" },
|
|
687
|
+
{ identifier: "seedream-5-lite-i2i:high", credits: 5, note: "high quality" },
|
|
688
|
+
],
|
|
689
|
+
},
|
|
690
|
+
|
|
691
|
+
// ── Alibaba Qwen ──
|
|
692
|
+
"qwen": {
|
|
693
|
+
id: "qwen",
|
|
694
|
+
kind: "image",
|
|
695
|
+
modes: ["t2i"] as const,
|
|
696
|
+
family: "Alibaba",
|
|
697
|
+
label: "Qwen",
|
|
698
|
+
series: "Qwen",
|
|
699
|
+
description: "Cheap, fast, decent quality. Native negative-prompt support.",
|
|
700
|
+
useCases: ["fast", "cheap", "general"],
|
|
701
|
+
features: ["reference-image"],
|
|
702
|
+
aspectRatios: IDEOGRAM_RATIOS,
|
|
703
|
+
pricing: [{ identifier: "qwen", credits: 1 }],
|
|
704
|
+
},
|
|
705
|
+
"qwen-i2i": {
|
|
706
|
+
id: "qwen-i2i",
|
|
707
|
+
kind: "image",
|
|
708
|
+
modes: ["i2i"] as const,
|
|
709
|
+
family: "Alibaba",
|
|
710
|
+
label: "Qwen (I2I)",
|
|
711
|
+
series: "Qwen",
|
|
712
|
+
description: "Image-to-image with Qwen.",
|
|
713
|
+
useCases: ["edit", "fast", "cheap"],
|
|
714
|
+
features: ["reference-image"],
|
|
715
|
+
aspectRatios: IDEOGRAM_RATIOS,
|
|
716
|
+
pricing: [{ identifier: "qwen-i2i", credits: 1 }],
|
|
717
|
+
},
|
|
718
|
+
"qwen-edit": {
|
|
719
|
+
id: "qwen-edit",
|
|
720
|
+
kind: "image",
|
|
721
|
+
modes: ["edit"] as const,
|
|
722
|
+
family: "Alibaba",
|
|
723
|
+
label: "Qwen Edit",
|
|
724
|
+
series: "Qwen",
|
|
725
|
+
description: "Qwen image edit endpoint with native negative prompt.",
|
|
726
|
+
useCases: ["edit", "cheap"],
|
|
727
|
+
features: ["reference-image"],
|
|
728
|
+
aspectRatios: IDEOGRAM_RATIOS,
|
|
729
|
+
pricing: [{ identifier: "qwen-edit", credits: 2 }],
|
|
730
|
+
},
|
|
731
|
+
|
|
732
|
+
// ── Tongyi-MAI Z-Image ──
|
|
733
|
+
"z-image": {
|
|
734
|
+
id: "z-image",
|
|
735
|
+
kind: "image",
|
|
736
|
+
modes: ["t2i"] as const,
|
|
737
|
+
family: "Tongyi-MAI",
|
|
738
|
+
label: "Z-Image",
|
|
739
|
+
series: "Z-Image",
|
|
740
|
+
description: "Cheapest model in catalog. Fast, stylized output. Limited aspect ratios.",
|
|
741
|
+
useCases: ["cheap", "fast", "stylized"],
|
|
742
|
+
aspectRatios: Z_IMAGE_RATIOS,
|
|
743
|
+
pricing: [{ identifier: "z-image", credits: 1 }],
|
|
744
|
+
featured: true,
|
|
745
|
+
},
|
|
746
|
+
|
|
747
|
+
// ── xAI Grok ──
|
|
748
|
+
"grok": {
|
|
749
|
+
id: "grok",
|
|
750
|
+
// Grok serves both t2i (image) and t2v (video) under the same id —
|
|
751
|
+
// backend route resolves which by entry point. We classify the catalog
|
|
752
|
+
// entry by primary kind (image) but list both modes so MCP filters and
|
|
753
|
+
// frontend pickers find it under either context.
|
|
754
|
+
kind: "image",
|
|
755
|
+
modes: ["t2i", "t2v"] as const,
|
|
756
|
+
family: "xAI",
|
|
757
|
+
label: "Grok Imagine",
|
|
758
|
+
series: "Grok",
|
|
759
|
+
description: "Expressive, high-contrast output. Supports both image and video.",
|
|
760
|
+
useCases: ["stylized", "expressive", "general"],
|
|
761
|
+
features: ["reference-image"],
|
|
762
|
+
aspectRatios: GROK_RATIOS,
|
|
763
|
+
pricing: [{ identifier: "grok", credits: 1 }],
|
|
764
|
+
},
|
|
765
|
+
"grok-i2i": {
|
|
766
|
+
id: "grok-i2i",
|
|
767
|
+
kind: "image",
|
|
768
|
+
modes: ["i2i"] as const,
|
|
769
|
+
family: "xAI",
|
|
770
|
+
label: "Grok Imagine (I2I)",
|
|
771
|
+
series: "Grok",
|
|
772
|
+
description: "Image-to-image with Grok.",
|
|
773
|
+
useCases: ["edit", "stylized"],
|
|
774
|
+
features: ["reference-image"],
|
|
775
|
+
pricing: [{ identifier: "grok-i2i", credits: 1 }],
|
|
776
|
+
},
|
|
777
|
+
"grok-upscale": {
|
|
778
|
+
id: "grok-upscale",
|
|
779
|
+
kind: "image",
|
|
780
|
+
modes: ["upscale"] as const,
|
|
781
|
+
family: "xAI",
|
|
782
|
+
label: "Grok Upscale",
|
|
783
|
+
series: "Grok",
|
|
784
|
+
description: "Upscale a previously-generated Grok image. Requires the prior task id.",
|
|
785
|
+
useCases: ["upscale"],
|
|
786
|
+
pricing: [{ identifier: "grok-upscale", credits: 3 }],
|
|
787
|
+
},
|
|
788
|
+
|
|
789
|
+
// ── Utilities ──
|
|
790
|
+
"recraft-remove-bg": {
|
|
791
|
+
id: "recraft-remove-bg",
|
|
792
|
+
kind: "image",
|
|
793
|
+
modes: ["remove-bg"] as const,
|
|
794
|
+
family: "Recraft",
|
|
795
|
+
label: "Recraft Remove BG",
|
|
796
|
+
series: "Recraft",
|
|
797
|
+
description: "Remove image background. Cheap utility.",
|
|
798
|
+
useCases: ["background-removal", "utility"],
|
|
799
|
+
features: ["reference-image"],
|
|
800
|
+
pricing: [{ identifier: "recraft-remove-bg", credits: 1 }],
|
|
801
|
+
},
|
|
802
|
+
"recraft-upscale": {
|
|
803
|
+
id: "recraft-upscale",
|
|
804
|
+
kind: "image",
|
|
805
|
+
modes: ["upscale"] as const,
|
|
806
|
+
family: "Recraft",
|
|
807
|
+
label: "Recraft Crisp Upscale",
|
|
808
|
+
series: "Recraft",
|
|
809
|
+
description: "Light-weight image upscale (Recraft Crisp).",
|
|
810
|
+
useCases: ["upscale", "utility"],
|
|
811
|
+
features: ["reference-image"],
|
|
812
|
+
pricing: [{ identifier: "recraft-upscale", credits: 1 }],
|
|
813
|
+
},
|
|
814
|
+
"topaz-image-upscale": {
|
|
815
|
+
id: "topaz-image-upscale",
|
|
816
|
+
kind: "image",
|
|
817
|
+
modes: ["upscale"] as const,
|
|
818
|
+
family: "Topaz",
|
|
819
|
+
label: "Topaz Image Upscale",
|
|
820
|
+
series: "Topaz",
|
|
821
|
+
description: "High-quality image upscale up to 8K. Best for production-ready output.",
|
|
822
|
+
useCases: ["upscale", "high-res", "premium"],
|
|
823
|
+
features: ["reference-image"],
|
|
824
|
+
resolutions: ["2K", "4K", "8K"],
|
|
825
|
+
pricing: [
|
|
826
|
+
{ identifier: "topaz-image-upscale", credits: 3, note: "2K default" },
|
|
827
|
+
{ identifier: "topaz-image-upscale:4K", credits: 5, note: "4K" },
|
|
828
|
+
{ identifier: "topaz-image-upscale:8K", credits: 10, note: "8K" },
|
|
829
|
+
],
|
|
830
|
+
},
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// =============================================================================
|
|
834
|
+
// VIDEO MODELS
|
|
835
|
+
// =============================================================================
|
|
836
|
+
const VIDEO_MODELS: Record<string, ModelCatalogEntry> = {
|
|
837
|
+
// ── Hailuo / MiniMax ──
|
|
838
|
+
"minimax": {
|
|
839
|
+
id: "minimax",
|
|
840
|
+
kind: "video",
|
|
841
|
+
modes: ["i2v", "t2v"] as const,
|
|
842
|
+
family: "MiniMax",
|
|
843
|
+
label: "Hailuo 02 I2V Pro",
|
|
844
|
+
series: "Hailuo",
|
|
845
|
+
description: "Hailuo 02 Pro — strong photoreal motion, fixed 5-second clips. Supports end frame.",
|
|
846
|
+
useCases: ["realistic", "motion", "narrative"],
|
|
847
|
+
features: ["end-frame"],
|
|
848
|
+
durations: [5],
|
|
849
|
+
pricing: [{ identifier: "minimax", credits: 15, note: "5s, 1080p" }],
|
|
850
|
+
},
|
|
851
|
+
"hailuo-2.3-pro": {
|
|
852
|
+
id: "hailuo-2.3-pro",
|
|
853
|
+
kind: "video",
|
|
854
|
+
modes: ["i2v"] as const,
|
|
855
|
+
family: "MiniMax",
|
|
856
|
+
valueLabels: { "1080P": "1080P (6s max)" },
|
|
857
|
+
label: "Hailuo 2.3 Pro",
|
|
858
|
+
series: "Hailuo",
|
|
859
|
+
description: "Hailuo 2.3 Pro — newer Hailuo with 768P / 1080P resolutions.",
|
|
860
|
+
useCases: ["realistic", "motion"],
|
|
861
|
+
durations: [6, 10],
|
|
862
|
+
resolutions: ["768P", "1080P"],
|
|
863
|
+
pricing: [
|
|
864
|
+
{ identifier: "hailuo-2.3-pro", credits: 20, note: "10s default" },
|
|
865
|
+
{ identifier: "hailuo-2.3-pro:6s", credits: 13, note: "6s" },
|
|
866
|
+
{ identifier: "hailuo-2.3-pro:10s", credits: 20, note: "10s" },
|
|
867
|
+
],
|
|
868
|
+
},
|
|
869
|
+
"hailuo-2.3": {
|
|
870
|
+
id: "hailuo-2.3",
|
|
871
|
+
kind: "video",
|
|
872
|
+
modes: ["i2v"] as const,
|
|
873
|
+
family: "MiniMax",
|
|
874
|
+
resolutions: ["768P", "1080P"],
|
|
875
|
+
valueLabels: { "1080P": "1080P (6s max)" },
|
|
876
|
+
label: "Hailuo 2.3 Standard",
|
|
877
|
+
series: "Hailuo",
|
|
878
|
+
description: "Cheaper Hailuo 2.3 tier — good baseline quality.",
|
|
879
|
+
useCases: ["realistic", "cheap"],
|
|
880
|
+
durations: [6, 10],
|
|
881
|
+
pricing: [
|
|
882
|
+
{ identifier: "hailuo-2.3", credits: 8, note: "6s default" },
|
|
883
|
+
{ identifier: "hailuo-2.3:6s", credits: 8, note: "6s" },
|
|
884
|
+
{ identifier: "hailuo-2.3:10s", credits: 13, note: "10s" },
|
|
885
|
+
],
|
|
886
|
+
},
|
|
887
|
+
"hailuo-standard": {
|
|
888
|
+
id: "hailuo-standard",
|
|
889
|
+
kind: "video",
|
|
890
|
+
modes: ["i2v", "t2v"] as const,
|
|
891
|
+
family: "MiniMax",
|
|
892
|
+
label: "Hailuo 02 Standard",
|
|
893
|
+
series: "Hailuo",
|
|
894
|
+
description: "Hailuo 02 Standard — economical option with end-frame support.",
|
|
895
|
+
useCases: ["cheap", "motion"],
|
|
896
|
+
features: ["end-frame"],
|
|
897
|
+
durations: [6, 10],
|
|
898
|
+
resolutions: ["512P", "768P"],
|
|
899
|
+
pricing: [
|
|
900
|
+
{ identifier: "hailuo-standard", credits: 8, note: "6s default" },
|
|
901
|
+
{ identifier: "hailuo-standard:6s", credits: 8, note: "6s" },
|
|
902
|
+
{ identifier: "hailuo-standard:10s", credits: 13, note: "10s" },
|
|
903
|
+
],
|
|
904
|
+
},
|
|
905
|
+
|
|
906
|
+
// ── Google VEO ──
|
|
907
|
+
"veo3": {
|
|
908
|
+
id: "veo3",
|
|
909
|
+
kind: "video",
|
|
910
|
+
modes: ["i2v", "t2v"] as const,
|
|
911
|
+
family: "Google",
|
|
912
|
+
label: "VEO 3.1 Quality",
|
|
913
|
+
series: "VEO",
|
|
914
|
+
description: "Google VEO 3.1 Quality — premium cinematic video. 4/6/8s clips, optional end frame, native audio. Flat per-generation pricing across durations.",
|
|
915
|
+
useCases: ["cinematic", "premium", "narrative"],
|
|
916
|
+
features: ["end-frame", "audio", "reference-image"],
|
|
917
|
+
durations: [4, 6, 8],
|
|
918
|
+
aspectRatios: VIDEO_RATIOS_HV,
|
|
919
|
+
// 720p (default) + 1080p inline. 4K generates the base at 1080p then chains
|
|
920
|
+
// KIE's /api/v1/veo/get-4k-video endpoint (worker handleImageToVideo / handleTextToVideo).
|
|
921
|
+
resolutions: ["720p", "1080p", "4k"],
|
|
922
|
+
pricing: [
|
|
923
|
+
{ identifier: "veo3", credits: 63, note: "4/6/8s with audio (flat per-generation)" },
|
|
924
|
+
{ identifier: "veo3:4k", credits: 93, note: "4K (base 1080p → get-4k-video)" },
|
|
925
|
+
],
|
|
926
|
+
featured: true,
|
|
927
|
+
},
|
|
928
|
+
"veo3.1": {
|
|
929
|
+
id: "veo3.1",
|
|
930
|
+
kind: "video",
|
|
931
|
+
modes: ["i2v", "t2v"] as const,
|
|
932
|
+
family: "Google",
|
|
933
|
+
label: "VEO 3.1 Fast",
|
|
934
|
+
series: "VEO",
|
|
935
|
+
description: "VEO 3.1 Fast — cheaper VEO 3.1 tier, 4/6/8s with audio. Good balance for most uses. Flat per-generation pricing across durations.",
|
|
936
|
+
useCases: ["cinematic", "fast", "general"],
|
|
937
|
+
features: ["end-frame", "audio", "reference-image"],
|
|
938
|
+
durations: [4, 6, 8],
|
|
939
|
+
aspectRatios: VIDEO_RATIOS_HV,
|
|
940
|
+
resolutions: ["720p", "1080p", "4k"],
|
|
941
|
+
pricing: [
|
|
942
|
+
{ identifier: "veo3.1", credits: 15, note: "4/6/8s with audio @ 720p (flat)" },
|
|
943
|
+
{ identifier: "veo3.1:1080p", credits: 17, note: "4/6/8s with audio @ 1080p (flat)" },
|
|
944
|
+
{ identifier: "veo3.1:4k", credits: 45, note: "4K (base 1080p → get-4k-video)" },
|
|
945
|
+
],
|
|
946
|
+
featured: true,
|
|
947
|
+
},
|
|
948
|
+
"veo3_lite": {
|
|
949
|
+
id: "veo3_lite",
|
|
950
|
+
kind: "video",
|
|
951
|
+
modes: ["i2v", "t2v"] as const,
|
|
952
|
+
family: "Google",
|
|
953
|
+
label: "VEO 3.1 Lite",
|
|
954
|
+
series: "VEO",
|
|
955
|
+
description: "VEO 3.1 Lite — most cost-effective VEO tier for high-volume generation. 4/6/8s with audio, supports first+last frame.",
|
|
956
|
+
useCases: ["cinematic", "cheap", "high-volume"],
|
|
957
|
+
features: ["end-frame", "audio", "reference-image"],
|
|
958
|
+
durations: [4, 6, 8],
|
|
959
|
+
aspectRatios: VIDEO_RATIOS_HV,
|
|
960
|
+
resolutions: ["720p", "1080p", "4k"],
|
|
961
|
+
pricing: [
|
|
962
|
+
{ identifier: "veo3_lite", credits: 8, note: "4/6/8s with audio @ 720p (flat)" },
|
|
963
|
+
{ identifier: "veo3_lite:1080p", credits: 9, note: "4/6/8s with audio @ 1080p (flat)" },
|
|
964
|
+
{ identifier: "veo3_lite:4k", credits: 38, note: "4K (base 1080p → get-4k-video)" },
|
|
965
|
+
],
|
|
966
|
+
featured: false,
|
|
967
|
+
},
|
|
968
|
+
"gemini-omni-video": {
|
|
969
|
+
id: "gemini-omni-video",
|
|
970
|
+
kind: "video",
|
|
971
|
+
modes: ["i2v", "t2v"] as const, // V2V is served via the i2v handle, not a catalog mode
|
|
972
|
+
family: "Google",
|
|
973
|
+
label: "Gemini Omni",
|
|
974
|
+
series: "Gemini",
|
|
975
|
+
description: "Google multimodal video with native audio; text/image-to-video + video-edit.",
|
|
976
|
+
useCases: ["cinematic", "narrative"],
|
|
977
|
+
features: ["audio", "reference-image"],
|
|
978
|
+
aspectRatios: VIDEO_RATIOS_HV,
|
|
979
|
+
resolutions: ["720p", "1080p", "4k"],
|
|
980
|
+
durations: [4, 6, 8, 10],
|
|
981
|
+
pricing: [
|
|
982
|
+
{ identifier: "gemini-omni-video", credits: 23, note: "default — 720p/1080p 4s" },
|
|
983
|
+
{ identifier: "gemini-omni-video:4", credits: 23, note: "720p/1080p 4s" },
|
|
984
|
+
{ identifier: "gemini-omni-video:6", credits: 30, note: "720p/1080p 6s" },
|
|
985
|
+
{ identifier: "gemini-omni-video:8", credits: 38, note: "720p/1080p 8s" },
|
|
986
|
+
{ identifier: "gemini-omni-video:10", credits: 45, note: "720p/1080p 10s" },
|
|
987
|
+
{ identifier: "gemini-omni-video:4k:4", credits: 53, note: "4K 4s" },
|
|
988
|
+
{ identifier: "gemini-omni-video:4k:6", credits: 60, note: "4K 6s" },
|
|
989
|
+
{ identifier: "gemini-omni-video:4k:8", credits: 68, note: "4K 8s" },
|
|
990
|
+
{ identifier: "gemini-omni-video:4k:10", credits: 75, note: "4K 10s" },
|
|
991
|
+
{ identifier: "gemini-omni-video:vref", credits: 60, note: "video-edit 720p/1080p" },
|
|
992
|
+
{ identifier: "gemini-omni-video:4k:vref", credits: 90, note: "video-edit 4K" },
|
|
993
|
+
],
|
|
994
|
+
},
|
|
995
|
+
|
|
996
|
+
// ── Kling ──
|
|
997
|
+
"kling": {
|
|
998
|
+
id: "kling",
|
|
999
|
+
kind: "video",
|
|
1000
|
+
modes: ["i2v", "t2v"] as const,
|
|
1001
|
+
family: "Kuaishou",
|
|
1002
|
+
label: "Kling 2.6",
|
|
1003
|
+
series: "Kling",
|
|
1004
|
+
description: "Kling 2.6 I2V — strong motion realism. 5s/10s, optional native audio.",
|
|
1005
|
+
useCases: ["realistic", "motion", "dance"],
|
|
1006
|
+
features: ["audio"],
|
|
1007
|
+
aspectRatios: VIDEO_RATIOS_HVS,
|
|
1008
|
+
durations: [5, 10],
|
|
1009
|
+
pricing: [
|
|
1010
|
+
{ identifier: "kling", credits: 28, note: "10s no audio default" },
|
|
1011
|
+
{ identifier: "kling:5s", credits: 14, note: "5s no audio" },
|
|
1012
|
+
{ identifier: "kling:10s", credits: 28, note: "10s no audio" },
|
|
1013
|
+
{ identifier: "kling:5s:audio", credits: 28, note: "5s with audio" },
|
|
1014
|
+
{ identifier: "kling:10s:audio", credits: 55, note: "10s with audio" },
|
|
1015
|
+
],
|
|
1016
|
+
},
|
|
1017
|
+
"kling-turbo": {
|
|
1018
|
+
id: "kling-turbo",
|
|
1019
|
+
kind: "video",
|
|
1020
|
+
modes: ["i2v", "t2v"] as const,
|
|
1021
|
+
family: "Kuaishou",
|
|
1022
|
+
label: "Kling 2.5 Turbo Pro",
|
|
1023
|
+
series: "Kling",
|
|
1024
|
+
description: "Faster Kling — good quality at lower cost. Supports end frame.",
|
|
1025
|
+
useCases: ["fast", "motion", "cheap"],
|
|
1026
|
+
features: ["end-frame"],
|
|
1027
|
+
durations: [5, 10],
|
|
1028
|
+
pricing: [
|
|
1029
|
+
{ identifier: "kling-turbo", credits: 11, note: "5s default" },
|
|
1030
|
+
{ identifier: "kling-turbo:5s", credits: 11 },
|
|
1031
|
+
{ identifier: "kling-turbo:10s", credits: 21 },
|
|
1032
|
+
],
|
|
1033
|
+
},
|
|
1034
|
+
"kling-3.0": {
|
|
1035
|
+
id: "kling-3.0",
|
|
1036
|
+
kind: "video",
|
|
1037
|
+
modes: ["i2v", "t2v"] as const,
|
|
1038
|
+
family: "Kuaishou",
|
|
1039
|
+
label: "Kling 3.0",
|
|
1040
|
+
series: "Kling",
|
|
1041
|
+
description: "Premium Kling 3.0 — variable 3-15s duration, native audio, 720P/1080P.",
|
|
1042
|
+
useCases: ["premium", "narrative", "cinematic"],
|
|
1043
|
+
features: ["end-frame", "audio"],
|
|
1044
|
+
durations: [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
|
|
1045
|
+
resolutions: ["720P", "1080P"],
|
|
1046
|
+
pricing: [
|
|
1047
|
+
{ identifier: "kling-3.0", credits: 50, note: "5s 1080p with audio" },
|
|
1048
|
+
{ identifier: "kling-3.0:5s", credits: 34, note: "5s 1080p no audio" },
|
|
1049
|
+
{ identifier: "kling-3.0:10s", credits: 68, note: "10s 1080p no audio" },
|
|
1050
|
+
{ identifier: "kling-3.0:15s", credits: 102, note: "15s 1080p no audio" },
|
|
1051
|
+
{ identifier: "kling-3.0:5s:audio", credits: 50, note: "5s 1080p with audio" },
|
|
1052
|
+
{ identifier: "kling-3.0:10s:audio", credits: 100, note: "10s 1080p with audio" },
|
|
1053
|
+
{ identifier: "kling-3.0:15s:audio", credits: 150, note: "15s 1080p with audio" },
|
|
1054
|
+
],
|
|
1055
|
+
featured: true,
|
|
1056
|
+
},
|
|
1057
|
+
"kling-master": {
|
|
1058
|
+
id: "kling-master",
|
|
1059
|
+
kind: "video",
|
|
1060
|
+
modes: ["i2v"] as const,
|
|
1061
|
+
family: "Kuaishou",
|
|
1062
|
+
label: "Kling 2.1 Master",
|
|
1063
|
+
series: "Kling",
|
|
1064
|
+
description: "Master tier I2V — strong cinematic quality.",
|
|
1065
|
+
useCases: ["cinematic", "premium"],
|
|
1066
|
+
durations: [5, 10],
|
|
1067
|
+
pricing: [
|
|
1068
|
+
{ identifier: "kling-master", credits: 40, note: "5s default" },
|
|
1069
|
+
{ identifier: "kling-master:5s", credits: 40 },
|
|
1070
|
+
{ identifier: "kling-master:10s", credits: 80 },
|
|
1071
|
+
],
|
|
1072
|
+
},
|
|
1073
|
+
"kling-3-omni": {
|
|
1074
|
+
id: "kling-3-omni",
|
|
1075
|
+
kind: "video",
|
|
1076
|
+
modes: ["i2v"] as const,
|
|
1077
|
+
family: "Kuaishou",
|
|
1078
|
+
label: "Kling 3 Omni",
|
|
1079
|
+
series: "Kling",
|
|
1080
|
+
description: "Kling 3 Omni via Replicate — 3-15s, 720p/1080p, end frame + reference images, native audio.",
|
|
1081
|
+
useCases: ["motion", "cinematic", "reference"],
|
|
1082
|
+
features: ["end-frame", "reference-image"],
|
|
1083
|
+
durations: [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
|
|
1084
|
+
resolutions: ["720p", "1080p"],
|
|
1085
|
+
aspectRatios: VIDEO_RATIOS_HVS,
|
|
1086
|
+
pricing: [
|
|
1087
|
+
{ identifier: "kling-3-omni", credits: 25, note: "5s 720p default" },
|
|
1088
|
+
{ identifier: "kling-3-omni:5s", credits: 25 },
|
|
1089
|
+
{ identifier: "kling-3-omni:10s", credits: 50 },
|
|
1090
|
+
{ identifier: "kling-3-omni:15s", credits: 75 },
|
|
1091
|
+
],
|
|
1092
|
+
},
|
|
1093
|
+
|
|
1094
|
+
// ── xAI Grok video ──
|
|
1095
|
+
"grok-i2v": {
|
|
1096
|
+
id: "grok-i2v",
|
|
1097
|
+
kind: "video",
|
|
1098
|
+
modes: ["i2v"] as const,
|
|
1099
|
+
features: ["reference-image"],
|
|
1100
|
+
family: "xAI",
|
|
1101
|
+
label: "Grok Imagine (I2V)",
|
|
1102
|
+
series: "Grok",
|
|
1103
|
+
description: "Grok image-to-video — stylized motion. Up to 15s.",
|
|
1104
|
+
useCases: ["stylized", "motion"],
|
|
1105
|
+
durations: [6, 10],
|
|
1106
|
+
resolutions: ["480p", "720p"],
|
|
1107
|
+
pricing: [
|
|
1108
|
+
{ identifier: "grok-i2v", credits: 5, note: "6s default" },
|
|
1109
|
+
{ identifier: "grok-i2v:6s", credits: 5 },
|
|
1110
|
+
{ identifier: "grok-i2v:10s", credits: 8 },
|
|
1111
|
+
{ identifier: "grok-i2v:15s", credits: 10 },
|
|
1112
|
+
],
|
|
1113
|
+
},
|
|
1114
|
+
// xAI Grok Imagine Video 1.5 — KIE image-to-video (image required). True
|
|
1115
|
+
// per-second billing split 480p/720p. modes:["i2v"] is the honest capability;
|
|
1116
|
+
// it's also surfaced in the text-to-video dropdown for the unified node, where
|
|
1117
|
+
// the t2v route returns a clean "requires an input image" 400.
|
|
1118
|
+
"grok-imagine-video-1.5": {
|
|
1119
|
+
id: "grok-imagine-video-1.5",
|
|
1120
|
+
kind: "video",
|
|
1121
|
+
modes: ["i2v"] as const,
|
|
1122
|
+
// No `reference-image` (locked by video-reference-features.test.ts, 2026-06-28
|
|
1123
|
+
// audit): KIE's grok-imagine endpoint accepts a single image_url (the i2v
|
|
1124
|
+
// start frame) only — there is no reference transport, so advertising the
|
|
1125
|
+
// feature let the editor attach refs that were silently dropped at the
|
|
1126
|
+
// provider. Also kept OUT of VIDEO_REF_LIMITS_BY_PROVIDER for the same reason.
|
|
1127
|
+
family: "xAI",
|
|
1128
|
+
label: "Grok Imagine Video 1.5",
|
|
1129
|
+
series: "Grok",
|
|
1130
|
+
description: "Grok Imagine 1.5 image-to-video — 1–15s, 480p/720p, per-second pricing. Requires an input image.",
|
|
1131
|
+
useCases: ["stylized", "motion"],
|
|
1132
|
+
aspectRatios: ["16:9", "9:16", "1:1", "4:3", "3:4"],
|
|
1133
|
+
durations: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
|
|
1134
|
+
resolutions: ["480p", "720p"],
|
|
1135
|
+
pricing: [
|
|
1136
|
+
{ identifier: "grok-imagine-video-1.5", credits: 30, note: "default 8s 480p — see :Ns:res variants" },
|
|
1137
|
+
{ identifier: "grok-imagine-video-1.5:8s:480p", credits: 30, note: "8s 480p" },
|
|
1138
|
+
{ identifier: "grok-imagine-video-1.5:8s:720p", credits: 51, note: "8s 720p" },
|
|
1139
|
+
{ identifier: "grok-imagine-video-1.5:15s:480p", credits: 55, note: "15s 480p (max)" },
|
|
1140
|
+
{ identifier: "grok-imagine-video-1.5:15s:720p", credits: 95, note: "15s 720p (max)" },
|
|
1141
|
+
],
|
|
1142
|
+
},
|
|
1143
|
+
|
|
1144
|
+
// ── Bytedance Seedance ──
|
|
1145
|
+
"seedance": {
|
|
1146
|
+
id: "seedance",
|
|
1147
|
+
kind: "video",
|
|
1148
|
+
modes: ["i2v", "t2v"] as const,
|
|
1149
|
+
family: "Bytedance",
|
|
1150
|
+
label: "Seedance 1.5 Pro",
|
|
1151
|
+
series: "Seedance",
|
|
1152
|
+
description: "Bytedance Seedance 1.5 Pro — flexible duration (4/8/12s) with end-frame support.",
|
|
1153
|
+
useCases: ["motion", "narrative"],
|
|
1154
|
+
features: ["end-frame"],
|
|
1155
|
+
durations: [4, 8, 12],
|
|
1156
|
+
resolutions: ["480p", "720p", "1080p"],
|
|
1157
|
+
pricing: [
|
|
1158
|
+
{ identifier: "seedance", credits: 7, note: "8s default" },
|
|
1159
|
+
{ identifier: "seedance:4s", credits: 4 },
|
|
1160
|
+
{ identifier: "seedance:8s", credits: 7 },
|
|
1161
|
+
{ identifier: "seedance:12s", credits: 15 },
|
|
1162
|
+
],
|
|
1163
|
+
mcpHidden: true,
|
|
1164
|
+
},
|
|
1165
|
+
"seedance-2": {
|
|
1166
|
+
id: "seedance-2",
|
|
1167
|
+
kind: "video",
|
|
1168
|
+
modes: ["i2v", "t2v"] as const,
|
|
1169
|
+
family: "Bytedance",
|
|
1170
|
+
label: "Seedance 2",
|
|
1171
|
+
series: "Seedance",
|
|
1172
|
+
description: "Seedance 2 — premium tier with native audio. Per-second pricing by resolution.",
|
|
1173
|
+
useCases: ["premium", "narrative"],
|
|
1174
|
+
features: ["end-frame", "audio", "reference-image"],
|
|
1175
|
+
aspectRatios: VIDEO_RATIOS_SEEDANCE_2,
|
|
1176
|
+
durations: [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
|
|
1177
|
+
resolutions: ["480p", "720p", "1080p", "4k"],
|
|
1178
|
+
pricing: [
|
|
1179
|
+
{ identifier: "seedance-2", credits: 38, note: "default — see :NsR variants for exact" },
|
|
1180
|
+
{ identifier: "seedance-2:8s:480p", credits: 38, note: "8s 480p" },
|
|
1181
|
+
{ identifier: "seedance-2:8s:720p", credits: 82, note: "8s 720p" },
|
|
1182
|
+
{ identifier: "seedance-2:8s:1080p", credits: 204, note: "8s 1080p" },
|
|
1183
|
+
{ identifier: "seedance-2:8s:480p-ref", credits: 23, note: "8s 480p with reference" },
|
|
1184
|
+
{ identifier: "seedance-2:8s:720p-ref", credits: 50, note: "8s 720p with reference" },
|
|
1185
|
+
{ identifier: "seedance-2:8s:1080p-ref", credits: 124, note: "8s 1080p with reference" },
|
|
1186
|
+
{ identifier: "seedance-2:8s:4k", credits: 416, note: "8s 4k" },
|
|
1187
|
+
{ identifier: "seedance-2:8s:4k-ref", credits: 256, note: "8s 4k with reference" },
|
|
1188
|
+
],
|
|
1189
|
+
},
|
|
1190
|
+
"seedance-2-fast": {
|
|
1191
|
+
id: "seedance-2-fast",
|
|
1192
|
+
kind: "video",
|
|
1193
|
+
modes: ["i2v", "t2v"] as const,
|
|
1194
|
+
family: "Bytedance",
|
|
1195
|
+
label: "Seedance 2 Fast",
|
|
1196
|
+
series: "Seedance",
|
|
1197
|
+
description: "Cheaper / quicker Seedance 2 tier.",
|
|
1198
|
+
useCases: ["fast", "motion"],
|
|
1199
|
+
features: ["end-frame", "audio", "reference-image"],
|
|
1200
|
+
aspectRatios: VIDEO_RATIOS_SEEDANCE_2,
|
|
1201
|
+
durations: [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
|
|
1202
|
+
resolutions: ["480p", "720p"],
|
|
1203
|
+
pricing: [
|
|
1204
|
+
{ identifier: "seedance-2-fast", credits: 31, note: "default — see :NsR variants" },
|
|
1205
|
+
{ identifier: "seedance-2-fast:8s:480p", credits: 31 },
|
|
1206
|
+
{ identifier: "seedance-2-fast:8s:720p", credits: 66 },
|
|
1207
|
+
{ identifier: "seedance-2-fast:8s:480p-ref", credits: 18 },
|
|
1208
|
+
{ identifier: "seedance-2-fast:8s:720p-ref", credits: 40 },
|
|
1209
|
+
],
|
|
1210
|
+
},
|
|
1211
|
+
"seedance-2-mini": {
|
|
1212
|
+
id: "seedance-2-mini",
|
|
1213
|
+
kind: "video",
|
|
1214
|
+
modes: ["i2v", "t2v"] as const,
|
|
1215
|
+
family: "Bytedance",
|
|
1216
|
+
label: "Seedance 2 Mini",
|
|
1217
|
+
series: "Seedance",
|
|
1218
|
+
description: "Budget Seedance 2 tier — 480p/720p only, per-second pricing by resolution.",
|
|
1219
|
+
useCases: ["fast", "motion"],
|
|
1220
|
+
features: ["end-frame", "audio", "reference-image"],
|
|
1221
|
+
aspectRatios: VIDEO_RATIOS_SEEDANCE_2,
|
|
1222
|
+
durations: [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
|
|
1223
|
+
resolutions: ["480p", "720p"],
|
|
1224
|
+
pricing: [
|
|
1225
|
+
{ identifier: "seedance-2-mini", credits: 19, note: "default — see :NsR variants" },
|
|
1226
|
+
{ identifier: "seedance-2-mini:8s:480p", credits: 19 },
|
|
1227
|
+
{ identifier: "seedance-2-mini:8s:720p", credits: 41 },
|
|
1228
|
+
{ identifier: "seedance-2-mini:8s:480p-ref", credits: 12 },
|
|
1229
|
+
{ identifier: "seedance-2-mini:8s:720p-ref", credits: 25 },
|
|
1230
|
+
],
|
|
1231
|
+
},
|
|
1232
|
+
|
|
1233
|
+
// ── Wan ──
|
|
1234
|
+
"wan-i2v": {
|
|
1235
|
+
id: "wan-i2v",
|
|
1236
|
+
kind: "video",
|
|
1237
|
+
modes: ["i2v"] as const,
|
|
1238
|
+
family: "Alibaba",
|
|
1239
|
+
label: "Wan 2.6 I2V",
|
|
1240
|
+
series: "Wan",
|
|
1241
|
+
description: "Wan 2.6 image-to-video — 5/10/15s at 720p/1080p.",
|
|
1242
|
+
useCases: ["motion", "narrative"],
|
|
1243
|
+
durations: [5, 10, 15],
|
|
1244
|
+
resolutions: ["720p", "1080p"],
|
|
1245
|
+
pricing: [
|
|
1246
|
+
{ identifier: "wan-i2v", credits: 18, note: "5s 720p default" },
|
|
1247
|
+
{ identifier: "wan-i2v:5s", credits: 18 },
|
|
1248
|
+
{ identifier: "wan-i2v:10s", credits: 35 },
|
|
1249
|
+
{ identifier: "wan-i2v:15s", credits: 53 },
|
|
1250
|
+
],
|
|
1251
|
+
},
|
|
1252
|
+
"wan-turbo": {
|
|
1253
|
+
id: "wan-turbo",
|
|
1254
|
+
kind: "video",
|
|
1255
|
+
modes: ["i2v", "t2v"] as const,
|
|
1256
|
+
family: "Alibaba",
|
|
1257
|
+
label: "Wan 2.2 Turbo",
|
|
1258
|
+
series: "Wan",
|
|
1259
|
+
description: "Cheap, fast Wan turbo — 5s. Serves both i2v and t2v under one id.",
|
|
1260
|
+
useCases: ["cheap", "fast"],
|
|
1261
|
+
aspectRatios: VIDEO_RATIOS_HVS,
|
|
1262
|
+
durations: [5],
|
|
1263
|
+
resolutions: ["480p", "720p"],
|
|
1264
|
+
// Different KIE endpoints (i2v vs t2v) → different costs under
|
|
1265
|
+
// composite ids. Route picks endpoint based on whether image was supplied.
|
|
1266
|
+
pricing: [
|
|
1267
|
+
{ identifier: "wan-turbo", credits: 10, note: "i2v 5s 480p" },
|
|
1268
|
+
{ identifier: "wan-turbo-t2v", credits: 20, note: "t2v 5s 720p" },
|
|
1269
|
+
],
|
|
1270
|
+
},
|
|
1271
|
+
"wan": {
|
|
1272
|
+
id: "wan",
|
|
1273
|
+
kind: "video",
|
|
1274
|
+
// "wan" id covers v2v AND t2v under different KIE endpoints; the
|
|
1275
|
+
// pricing variants tag which mode each cost belongs to.
|
|
1276
|
+
modes: ["v2v", "t2v"] as const,
|
|
1277
|
+
family: "Alibaba",
|
|
1278
|
+
label: "Wan 2.6",
|
|
1279
|
+
series: "Wan",
|
|
1280
|
+
description: "Wan 2.6 — text-to-video and video-to-video under a single id.",
|
|
1281
|
+
useCases: ["v2v", "t2v", "restyle"],
|
|
1282
|
+
durations: [5],
|
|
1283
|
+
resolutions: ["720p", "1080p"],
|
|
1284
|
+
pricing: [
|
|
1285
|
+
{ identifier: "wan", credits: 18, note: "v2v 5s 720p" },
|
|
1286
|
+
{ identifier: "wan-t2v", credits: 27, note: "t2v 5s 1080p" },
|
|
1287
|
+
],
|
|
1288
|
+
},
|
|
1289
|
+
"wan-flash": {
|
|
1290
|
+
id: "wan-flash",
|
|
1291
|
+
kind: "video",
|
|
1292
|
+
modes: ["v2v"] as const,
|
|
1293
|
+
family: "Alibaba",
|
|
1294
|
+
label: "Wan Flash V2V",
|
|
1295
|
+
series: "Wan",
|
|
1296
|
+
description: "Faster Wan V2V variant.",
|
|
1297
|
+
useCases: ["v2v", "fast"],
|
|
1298
|
+
pricing: [{ identifier: "wan-flash", credits: 13 }],
|
|
1299
|
+
},
|
|
1300
|
+
"wan-videoedit": {
|
|
1301
|
+
id: "wan-videoedit",
|
|
1302
|
+
kind: "video",
|
|
1303
|
+
modes: ["v2v"] as const,
|
|
1304
|
+
family: "Alibaba",
|
|
1305
|
+
label: "Wan 2.7 VideoEdit",
|
|
1306
|
+
series: "Wan",
|
|
1307
|
+
description: "Guided video editing with optional reference image, audio control, and prompt expansion.",
|
|
1308
|
+
useCases: ["v2v", "restyle", "reference"],
|
|
1309
|
+
resolutions: ["720p", "1080p"],
|
|
1310
|
+
pricing: [{ identifier: "wan-videoedit", credits: 32 }],
|
|
1311
|
+
},
|
|
1312
|
+
// ── Wan 2.7 ──
|
|
1313
|
+
"wan-2.7": {
|
|
1314
|
+
id: "wan-2.7",
|
|
1315
|
+
kind: "image",
|
|
1316
|
+
modes: ["t2i"] as const,
|
|
1317
|
+
family: "Alibaba",
|
|
1318
|
+
label: "Wan 2.7",
|
|
1319
|
+
series: "Wan",
|
|
1320
|
+
description: "Wan 2.7 text-to-image — 1K/2K/4K, up to 9 optional style/character reference images.",
|
|
1321
|
+
useCases: ["photorealistic", "stylized", "reference"],
|
|
1322
|
+
features: ["reference-image"] as const,
|
|
1323
|
+
aspectRatios: WAN_27_IMAGE_RATIOS,
|
|
1324
|
+
resolutions: ["1K", "2K", "4K"],
|
|
1325
|
+
pricing: [
|
|
1326
|
+
{ identifier: "wan-2.7", credits: 2, note: "1K base" },
|
|
1327
|
+
{ identifier: "wan-2.7:2K", credits: 4, note: "2K" },
|
|
1328
|
+
{ identifier: "wan-2.7:4K", credits: 8, note: "4K" },
|
|
1329
|
+
],
|
|
1330
|
+
},
|
|
1331
|
+
"wan-2.7-pro": {
|
|
1332
|
+
id: "wan-2.7-pro",
|
|
1333
|
+
kind: "image",
|
|
1334
|
+
modes: ["t2i"] as const,
|
|
1335
|
+
family: "Alibaba",
|
|
1336
|
+
label: "Wan 2.7 Pro",
|
|
1337
|
+
series: "Wan",
|
|
1338
|
+
description: "Wan 2.7 Pro text-to-image — higher quality, 1K/2K/4K, no image input.",
|
|
1339
|
+
useCases: ["photorealistic", "premium"],
|
|
1340
|
+
aspectRatios: WAN_27_IMAGE_RATIOS,
|
|
1341
|
+
resolutions: ["1K", "2K", "4K"],
|
|
1342
|
+
pricing: [
|
|
1343
|
+
{ identifier: "wan-2.7-pro", credits: 3, note: "1K base" },
|
|
1344
|
+
{ identifier: "wan-2.7-pro:2K", credits: 6, note: "2K" },
|
|
1345
|
+
{ identifier: "wan-2.7-pro:4K", credits: 12, note: "4K" },
|
|
1346
|
+
],
|
|
1347
|
+
},
|
|
1348
|
+
"wan-2.7-i2v": {
|
|
1349
|
+
id: "wan-2.7-i2v",
|
|
1350
|
+
kind: "video",
|
|
1351
|
+
modes: ["i2v"] as const,
|
|
1352
|
+
family: "Alibaba",
|
|
1353
|
+
label: "Wan 2.7 I2V",
|
|
1354
|
+
series: "Wan",
|
|
1355
|
+
description: "Wan 2.7 image-to-video — 2–15s at 720p/1080p, supports start+end frame.",
|
|
1356
|
+
useCases: ["motion", "narrative"],
|
|
1357
|
+
durations: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
|
|
1358
|
+
resolutions: ["720p", "1080p"],
|
|
1359
|
+
features: ["end-frame"],
|
|
1360
|
+
pricing: [
|
|
1361
|
+
{ identifier: "wan-2.7-i2v", credits: 19, note: "5s 720p default" },
|
|
1362
|
+
],
|
|
1363
|
+
},
|
|
1364
|
+
"wan-2.7-t2v": {
|
|
1365
|
+
id: "wan-2.7-t2v",
|
|
1366
|
+
kind: "video",
|
|
1367
|
+
modes: ["t2v"] as const,
|
|
1368
|
+
family: "Alibaba",
|
|
1369
|
+
label: "Wan 2.7 T2V",
|
|
1370
|
+
series: "Wan",
|
|
1371
|
+
description: "Wan 2.7 text-to-video — 2–15s at 720p/1080p.",
|
|
1372
|
+
useCases: ["motion", "narrative"],
|
|
1373
|
+
aspectRatios: VIDEO_RATIOS_HVS345,
|
|
1374
|
+
durations: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
|
|
1375
|
+
resolutions: ["720p", "1080p"],
|
|
1376
|
+
pricing: [
|
|
1377
|
+
{ identifier: "wan-2.7-t2v", credits: 19, note: "5s 720p default" },
|
|
1378
|
+
],
|
|
1379
|
+
},
|
|
1380
|
+
// ── HappyHorse ──
|
|
1381
|
+
"happyhorse": {
|
|
1382
|
+
id: "happyhorse",
|
|
1383
|
+
kind: "video",
|
|
1384
|
+
modes: ["t2v"] as const,
|
|
1385
|
+
family: "HappyHorse",
|
|
1386
|
+
label: "HappyHorse",
|
|
1387
|
+
series: "HappyHorse",
|
|
1388
|
+
description: "HappyHorse text-to-video — 3–15s at 720p/1080p.",
|
|
1389
|
+
useCases: ["motion", "creative"],
|
|
1390
|
+
aspectRatios: VIDEO_RATIOS_HVS345,
|
|
1391
|
+
durations: [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
|
|
1392
|
+
resolutions: ["720p", "1080p"],
|
|
1393
|
+
pricing: [
|
|
1394
|
+
{ identifier: "happyhorse", credits: 13, note: "5s 720p default" },
|
|
1395
|
+
],
|
|
1396
|
+
},
|
|
1397
|
+
"happyhorse-i2v": {
|
|
1398
|
+
id: "happyhorse-i2v",
|
|
1399
|
+
kind: "video",
|
|
1400
|
+
modes: ["i2v"] as const,
|
|
1401
|
+
family: "HappyHorse",
|
|
1402
|
+
label: "HappyHorse I2V",
|
|
1403
|
+
series: "HappyHorse",
|
|
1404
|
+
description: "HappyHorse image-to-video — 3–15s at 720p/1080p, aspect ratio inferred from input image.",
|
|
1405
|
+
useCases: ["motion", "creative"],
|
|
1406
|
+
durations: [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
|
|
1407
|
+
resolutions: ["720p", "1080p"],
|
|
1408
|
+
pricing: [
|
|
1409
|
+
{ identifier: "happyhorse-i2v", credits: 13, note: "5s 720p default" },
|
|
1410
|
+
],
|
|
1411
|
+
},
|
|
1412
|
+
"happyhorse-ref2v": {
|
|
1413
|
+
id: "happyhorse-ref2v",
|
|
1414
|
+
kind: "video",
|
|
1415
|
+
modes: ["i2v"] as const,
|
|
1416
|
+
family: "HappyHorse",
|
|
1417
|
+
label: "HappyHorse Ref2V",
|
|
1418
|
+
series: "HappyHorse",
|
|
1419
|
+
description: "HappyHorse reference-to-video — 1–9 reference images, 3–15s at 720p/1080p.",
|
|
1420
|
+
useCases: ["motion", "reference"],
|
|
1421
|
+
aspectRatios: VIDEO_RATIOS_HVS345,
|
|
1422
|
+
durations: [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
|
|
1423
|
+
resolutions: ["720p", "1080p"],
|
|
1424
|
+
features: ["reference-image"],
|
|
1425
|
+
pricing: [
|
|
1426
|
+
{ identifier: "happyhorse-ref2v", credits: 15, note: "5s 720p default" },
|
|
1427
|
+
],
|
|
1428
|
+
},
|
|
1429
|
+
"happyhorse-edit": {
|
|
1430
|
+
id: "happyhorse-edit",
|
|
1431
|
+
kind: "video",
|
|
1432
|
+
modes: ["v2v"] as const,
|
|
1433
|
+
family: "HappyHorse",
|
|
1434
|
+
label: "HappyHorse Edit",
|
|
1435
|
+
series: "HappyHorse",
|
|
1436
|
+
description: "HappyHorse video-edit — video-to-video transformation, up to 60s input, 720p/1080p output.",
|
|
1437
|
+
useCases: ["v2v", "restyle"],
|
|
1438
|
+
resolutions: ["720p", "1080p"],
|
|
1439
|
+
pricing: [
|
|
1440
|
+
{ identifier: "happyhorse-edit", credits: 20, note: "720p default" },
|
|
1441
|
+
],
|
|
1442
|
+
},
|
|
1443
|
+
// ── Bytedance video lite/pro ──
|
|
1444
|
+
"bytedance-lite": {
|
|
1445
|
+
id: "bytedance-lite",
|
|
1446
|
+
kind: "video",
|
|
1447
|
+
modes: ["i2v", "t2v"] as const,
|
|
1448
|
+
family: "Bytedance",
|
|
1449
|
+
label: "Bytedance Lite I2V",
|
|
1450
|
+
series: "Bytedance",
|
|
1451
|
+
description: "Cheapest Bytedance video tier with end-frame support.",
|
|
1452
|
+
useCases: ["cheap", "motion"],
|
|
1453
|
+
features: ["end-frame"],
|
|
1454
|
+
aspectRatios: VIDEO_RATIOS_HVS,
|
|
1455
|
+
durations: [5, 10],
|
|
1456
|
+
resolutions: ["480p", "720p", "1080p"],
|
|
1457
|
+
pricing: [{ identifier: "bytedance-lite", credits: 6 }],
|
|
1458
|
+
},
|
|
1459
|
+
"bytedance-pro": {
|
|
1460
|
+
id: "bytedance-pro",
|
|
1461
|
+
kind: "video",
|
|
1462
|
+
modes: ["i2v", "t2v"] as const,
|
|
1463
|
+
family: "Bytedance",
|
|
1464
|
+
label: "Bytedance Pro I2V",
|
|
1465
|
+
series: "Bytedance",
|
|
1466
|
+
description: "Pro Bytedance video tier — better quality.",
|
|
1467
|
+
useCases: ["motion", "narrative"],
|
|
1468
|
+
aspectRatios: VIDEO_RATIOS_HVS,
|
|
1469
|
+
durations: [5, 10],
|
|
1470
|
+
resolutions: ["480p", "720p", "1080p"],
|
|
1471
|
+
pricing: [{ identifier: "bytedance-pro", credits: 18 }],
|
|
1472
|
+
},
|
|
1473
|
+
"bytedance-pro-fast": {
|
|
1474
|
+
id: "bytedance-pro-fast",
|
|
1475
|
+
kind: "video",
|
|
1476
|
+
modes: ["i2v"] as const,
|
|
1477
|
+
family: "Bytedance",
|
|
1478
|
+
label: "Bytedance Pro Fast I2V",
|
|
1479
|
+
series: "Bytedance",
|
|
1480
|
+
description: "Faster Bytedance Pro variant.",
|
|
1481
|
+
useCases: ["fast", "motion"],
|
|
1482
|
+
durations: [5, 10],
|
|
1483
|
+
resolutions: ["720p", "1080p"],
|
|
1484
|
+
pricing: [{ identifier: "bytedance-pro-fast", credits: 9 }],
|
|
1485
|
+
},
|
|
1486
|
+
|
|
1487
|
+
// ── OpenAI Sora ──
|
|
1488
|
+
// (Sora2 / Sora2-Pro are listed in KIE models but not in STATIC_CREDIT_COSTS;
|
|
1489
|
+
// they're priced at runtime via the per-second tracker, so we omit pricing
|
|
1490
|
+
// arrays with composite entries — `list_models` will fall back to whatever
|
|
1491
|
+
// base entry exists.)
|
|
1492
|
+
|
|
1493
|
+
// ── Runway ──
|
|
1494
|
+
"runway-kie": {
|
|
1495
|
+
id: "runway-kie",
|
|
1496
|
+
kind: "video",
|
|
1497
|
+
modes: ["i2v", "t2v"] as const,
|
|
1498
|
+
family: "Runway",
|
|
1499
|
+
label: "Runway (via KIE)",
|
|
1500
|
+
series: "Runway",
|
|
1501
|
+
description: "Runway Gen-3 routed through KIE. 5/10s at 720p/1080p.",
|
|
1502
|
+
useCases: ["motion", "narrative"],
|
|
1503
|
+
durations: [5, 10],
|
|
1504
|
+
resolutions: ["720p", "1080p"],
|
|
1505
|
+
pricing: [{ identifier: "runway-kie", credits: 3, note: "5s 720p" }],
|
|
1506
|
+
},
|
|
1507
|
+
"runway-aleph": {
|
|
1508
|
+
id: "runway-aleph",
|
|
1509
|
+
kind: "video",
|
|
1510
|
+
modes: ["v2v"] as const,
|
|
1511
|
+
family: "Runway",
|
|
1512
|
+
label: "Runway Aleph V2V",
|
|
1513
|
+
series: "Runway",
|
|
1514
|
+
description: "Runway Aleph — video-to-video conversion.",
|
|
1515
|
+
useCases: ["v2v", "restyle"],
|
|
1516
|
+
pricing: [{ identifier: "runway-aleph", credits: 35, note: "5s clip" }],
|
|
1517
|
+
},
|
|
1518
|
+
|
|
1519
|
+
// ── Extend / Upscale ──
|
|
1520
|
+
"veo-extend": {
|
|
1521
|
+
id: "veo-extend",
|
|
1522
|
+
kind: "video",
|
|
1523
|
+
modes: ["extend"] as const,
|
|
1524
|
+
family: "Google",
|
|
1525
|
+
label: "VEO Extend",
|
|
1526
|
+
series: "VEO",
|
|
1527
|
+
description: "Extend an existing VEO 3.1 clip by another segment.",
|
|
1528
|
+
useCases: ["extend"],
|
|
1529
|
+
pricing: [
|
|
1530
|
+
{ identifier: "veo-extend", credits: 19, note: "VEO 3.1 Fast default" },
|
|
1531
|
+
{ identifier: "veo-extend:quality", credits: 79, note: "VEO 3.1 Quality" },
|
|
1532
|
+
],
|
|
1533
|
+
},
|
|
1534
|
+
"runway-extend": {
|
|
1535
|
+
id: "runway-extend",
|
|
1536
|
+
kind: "video",
|
|
1537
|
+
modes: ["extend"] as const,
|
|
1538
|
+
family: "Runway",
|
|
1539
|
+
label: "Runway Extend",
|
|
1540
|
+
series: "Runway",
|
|
1541
|
+
description: "Extend a Runway video by another clip.",
|
|
1542
|
+
useCases: ["extend"],
|
|
1543
|
+
pricing: [{ identifier: "runway-extend", credits: 32 }],
|
|
1544
|
+
},
|
|
1545
|
+
"seedance-2-extend": {
|
|
1546
|
+
id: "seedance-2-extend",
|
|
1547
|
+
kind: "video",
|
|
1548
|
+
modes: ["extend"] as const,
|
|
1549
|
+
family: "Bytedance",
|
|
1550
|
+
label: "Seedance 2 Extend",
|
|
1551
|
+
series: "Seedance",
|
|
1552
|
+
description: "Extend ANY video: generates the continuation (audio included) and trim-stitches it into one seamless clip.",
|
|
1553
|
+
useCases: ["extend"],
|
|
1554
|
+
durations: [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
|
|
1555
|
+
resolutions: ["480p", "720p", "1080p"],
|
|
1556
|
+
pricing: [
|
|
1557
|
+
{ identifier: "seedance-2-extend", credits: 53, note: "default 8s 720p (ref-mode 50 + 3 stitch) — see :NsR variants" },
|
|
1558
|
+
{ identifier: "seedance-2-extend:8s:480p", credits: 26, note: "8s 480p (23+3)" },
|
|
1559
|
+
{ identifier: "seedance-2-extend:8s:720p", credits: 53, note: "8s 720p (50+3)" },
|
|
1560
|
+
{ identifier: "seedance-2-extend:8s:1080p", credits: 78, note: "8s 1080p (75+3)" },
|
|
1561
|
+
],
|
|
1562
|
+
},
|
|
1563
|
+
"veo-1080p": {
|
|
1564
|
+
id: "veo-1080p",
|
|
1565
|
+
kind: "video",
|
|
1566
|
+
modes: ["video-upscale"] as const,
|
|
1567
|
+
family: "Google",
|
|
1568
|
+
label: "VEO 1080p Upscale",
|
|
1569
|
+
series: "VEO",
|
|
1570
|
+
description: "Upscale VEO output to 1080p.",
|
|
1571
|
+
useCases: ["upscale"],
|
|
1572
|
+
pricing: [{ identifier: "veo-1080p", credits: 2 }],
|
|
1573
|
+
},
|
|
1574
|
+
"veo-4k": {
|
|
1575
|
+
id: "veo-4k",
|
|
1576
|
+
kind: "video",
|
|
1577
|
+
modes: ["video-upscale"] as const,
|
|
1578
|
+
family: "Google",
|
|
1579
|
+
label: "VEO 4K Upscale",
|
|
1580
|
+
series: "VEO",
|
|
1581
|
+
description: "Upscale VEO output to 4K.",
|
|
1582
|
+
useCases: ["upscale", "high-res"],
|
|
1583
|
+
pricing: [{ identifier: "veo-4k", credits: 38 }],
|
|
1584
|
+
},
|
|
1585
|
+
"topaz-video": {
|
|
1586
|
+
id: "topaz-video",
|
|
1587
|
+
kind: "video",
|
|
1588
|
+
modes: ["video-upscale"] as const,
|
|
1589
|
+
family: "Topaz",
|
|
1590
|
+
label: "Topaz Video Upscale",
|
|
1591
|
+
series: "Topaz",
|
|
1592
|
+
description: "High-quality video upscale and enhancement.",
|
|
1593
|
+
useCases: ["upscale", "premium"],
|
|
1594
|
+
pricing: [{ identifier: "topaz-video", credits: 19, note: "~5s clip" }],
|
|
1595
|
+
},
|
|
1596
|
+
|
|
1597
|
+
// ── Motion transfer ──
|
|
1598
|
+
"motion-transfer": {
|
|
1599
|
+
id: "motion-transfer",
|
|
1600
|
+
kind: "video",
|
|
1601
|
+
modes: ["motion-transfer"] as const,
|
|
1602
|
+
family: "Kuaishou",
|
|
1603
|
+
label: "Kling 2.6 Motion Transfer",
|
|
1604
|
+
series: "Kling",
|
|
1605
|
+
description: "Transfer the motion from a driving video onto a still subject. Kling 2.6 base.",
|
|
1606
|
+
useCases: ["motion-transfer"],
|
|
1607
|
+
durations: [5, 10, 15, 30],
|
|
1608
|
+
resolutions: ["720p", "1080p"],
|
|
1609
|
+
pricing: [
|
|
1610
|
+
{ identifier: "motion-transfer", credits: 15, note: "10s 720p default" },
|
|
1611
|
+
{ identifier: "motion-transfer:5s", credits: 8 },
|
|
1612
|
+
{ identifier: "motion-transfer:10s", credits: 15 },
|
|
1613
|
+
{ identifier: "motion-transfer:15s", credits: 23 },
|
|
1614
|
+
{ identifier: "motion-transfer:30s", credits: 45 },
|
|
1615
|
+
{ identifier: "motion-transfer:1080p:10s", credits: 23 },
|
|
1616
|
+
],
|
|
1617
|
+
},
|
|
1618
|
+
"kling-3.0-motion": {
|
|
1619
|
+
id: "kling-3.0-motion",
|
|
1620
|
+
kind: "video",
|
|
1621
|
+
modes: ["motion-transfer"] as const,
|
|
1622
|
+
family: "Kuaishou",
|
|
1623
|
+
label: "Kling 3.0 Motion Transfer",
|
|
1624
|
+
series: "Kling",
|
|
1625
|
+
description: "Premium motion transfer via Kling 3.0.",
|
|
1626
|
+
useCases: ["motion-transfer", "premium"],
|
|
1627
|
+
durations: [5, 10, 15, 30],
|
|
1628
|
+
resolutions: ["720p", "1080p"],
|
|
1629
|
+
pricing: [
|
|
1630
|
+
{ identifier: "kling-3.0-motion", credits: 30, note: "10s 720p default" },
|
|
1631
|
+
{ identifier: "kling-3.0-motion:5s", credits: 15 },
|
|
1632
|
+
{ identifier: "kling-3.0-motion:10s", credits: 30 },
|
|
1633
|
+
{ identifier: "kling-3.0-motion:1080p:10s", credits: 50 },
|
|
1634
|
+
],
|
|
1635
|
+
},
|
|
1636
|
+
|
|
1637
|
+
// ── Lip sync / talking-head ──
|
|
1638
|
+
"kling-avatar": {
|
|
1639
|
+
id: "kling-avatar",
|
|
1640
|
+
kind: "video",
|
|
1641
|
+
modes: ["lip-sync"] as const,
|
|
1642
|
+
family: "Kuaishou",
|
|
1643
|
+
label: "Kling Avatar Standard",
|
|
1644
|
+
series: "Kling",
|
|
1645
|
+
description: "Lip-sync a still portrait to driving audio. Standard quality.",
|
|
1646
|
+
useCases: ["lip-sync", "talking-head"],
|
|
1647
|
+
pricing: [{ identifier: "kling-avatar", credits: 28, note: "~14s default" }],
|
|
1648
|
+
},
|
|
1649
|
+
"kling-avatar-pro": {
|
|
1650
|
+
id: "kling-avatar-pro",
|
|
1651
|
+
kind: "video",
|
|
1652
|
+
modes: ["lip-sync"] as const,
|
|
1653
|
+
family: "Kuaishou",
|
|
1654
|
+
label: "Kling Avatar Pro",
|
|
1655
|
+
series: "Kling",
|
|
1656
|
+
description: "Premium lip-sync — better mouth shape and timing.",
|
|
1657
|
+
useCases: ["lip-sync", "talking-head", "premium"],
|
|
1658
|
+
pricing: [{ identifier: "kling-avatar-pro", credits: 56 }],
|
|
1659
|
+
},
|
|
1660
|
+
"infinitalk": {
|
|
1661
|
+
id: "infinitalk",
|
|
1662
|
+
kind: "video",
|
|
1663
|
+
modes: ["lip-sync"] as const,
|
|
1664
|
+
family: "InfiniTalk",
|
|
1665
|
+
label: "InfiniTalk",
|
|
1666
|
+
series: "InfiniTalk",
|
|
1667
|
+
description: "Audio-driven talking-head from a still image. 480p / 720p.",
|
|
1668
|
+
useCases: ["lip-sync", "talking-head"],
|
|
1669
|
+
resolutions: ["480p", "720p"],
|
|
1670
|
+
pricing: [
|
|
1671
|
+
{ identifier: "infinitalk", credits: 42, note: "720p default" },
|
|
1672
|
+
{ identifier: "infinitalk:480p", credits: 11 },
|
|
1673
|
+
{ identifier: "infinitalk:720p", credits: 42 },
|
|
1674
|
+
],
|
|
1675
|
+
},
|
|
1676
|
+
"omnihuman-1-5": {
|
|
1677
|
+
id: "omnihuman-1-5",
|
|
1678
|
+
kind: "video",
|
|
1679
|
+
modes: ["lip-sync"] as const,
|
|
1680
|
+
family: "ByteDance",
|
|
1681
|
+
label: "OmniHuman 1.5",
|
|
1682
|
+
series: "OmniHuman",
|
|
1683
|
+
description: "Premium prompt-directed talking avatar from a still image + audio. 720p / 1080p, up to 60s. People, pets, anime.",
|
|
1684
|
+
useCases: ["lip-sync", "talking-head", "premium"],
|
|
1685
|
+
resolutions: ["720p", "1080p"],
|
|
1686
|
+
pricing: [
|
|
1687
|
+
{ identifier: "omnihuman-1-5", credits: 405, note: "60s ceiling (no duration given)" },
|
|
1688
|
+
{ identifier: "omnihuman-1-5:15s", credits: 102 },
|
|
1689
|
+
{ identifier: "omnihuman-1-5:30s", credits: 203 },
|
|
1690
|
+
{ identifier: "omnihuman-1-5:60s", credits: 405 },
|
|
1691
|
+
],
|
|
1692
|
+
},
|
|
1693
|
+
"hailuo-avatar": {
|
|
1694
|
+
id: "hailuo-avatar",
|
|
1695
|
+
kind: "video",
|
|
1696
|
+
modes: ["lip-sync"] as const,
|
|
1697
|
+
family: "MiniMax",
|
|
1698
|
+
label: "Hailuo Avatar",
|
|
1699
|
+
series: "Hailuo",
|
|
1700
|
+
description: "MiniMax avatar lip-sync.",
|
|
1701
|
+
useCases: ["lip-sync", "talking-head"],
|
|
1702
|
+
pricing: [{ identifier: "hailuo-avatar", credits: 19 }],
|
|
1703
|
+
// Not wired in any provider / not in LIP_SYNC_PROVIDERS — hide from MCP
|
|
1704
|
+
// list_models so the lip_sync tool can't advertise a model its route
|
|
1705
|
+
// rejects with a 400. Re-expose when the provider lands.
|
|
1706
|
+
mcpHidden: true,
|
|
1707
|
+
},
|
|
1708
|
+
"sync-lipsync-v3": {
|
|
1709
|
+
id: "sync-lipsync-v3",
|
|
1710
|
+
kind: "video",
|
|
1711
|
+
modes: ["lip-sync"] as const,
|
|
1712
|
+
family: "Sync",
|
|
1713
|
+
label: "Sync Lipsync v3",
|
|
1714
|
+
series: "Sync",
|
|
1715
|
+
description: "Dub existing footage — re-syncs lips to a new audio track. Video input, billed per second.",
|
|
1716
|
+
useCases: ["lip-sync", "dubbing", "video-to-video"],
|
|
1717
|
+
pricing: [
|
|
1718
|
+
{ identifier: "sync-lipsync-v3", credits: 2000, note: "5-min ceiling (no duration given)" },
|
|
1719
|
+
{ identifier: "sync-lipsync-v3:15s", credits: 100 },
|
|
1720
|
+
{ identifier: "sync-lipsync-v3:30s", credits: 200 },
|
|
1721
|
+
{ identifier: "sync-lipsync-v3:60s", credits: 400 },
|
|
1722
|
+
{ identifier: "sync-lipsync-v3:120s", credits: 800 },
|
|
1723
|
+
{ identifier: "sync-lipsync-v3:300s", credits: 2000, note: "5-min ceiling" },
|
|
1724
|
+
],
|
|
1725
|
+
},
|
|
1726
|
+
"volcengine-lipsync": {
|
|
1727
|
+
id: "volcengine-lipsync",
|
|
1728
|
+
kind: "video",
|
|
1729
|
+
modes: ["lip-sync"] as const,
|
|
1730
|
+
family: "Volcengine",
|
|
1731
|
+
label: "Volcengine Lip Sync",
|
|
1732
|
+
series: "Volcengine",
|
|
1733
|
+
description: "Video-to-video AI dubbing — re-syncs lips to a new vocal track. Multi-speaker (scene detection + speaker ID) in basic mode. Video input, billed per second.",
|
|
1734
|
+
useCases: ["lip-sync", "dubbing", "video-to-video"],
|
|
1735
|
+
pricing: [
|
|
1736
|
+
{ identifier: "volcengine-lipsync", credits: 600, note: "5-min ceiling (no duration given)" },
|
|
1737
|
+
{ identifier: "volcengine-lipsync:15s", credits: 30 },
|
|
1738
|
+
{ identifier: "volcengine-lipsync:30s", credits: 60 },
|
|
1739
|
+
{ identifier: "volcengine-lipsync:60s", credits: 120 },
|
|
1740
|
+
{ identifier: "volcengine-lipsync:120s", credits: 240 },
|
|
1741
|
+
{ identifier: "volcengine-lipsync:300s", credits: 600, note: "5-min ceiling" },
|
|
1742
|
+
],
|
|
1743
|
+
},
|
|
1744
|
+
// ── Gemini video analysis ── (Gemini-only; billed per duration bucket — the
|
|
1745
|
+
// structural credit formula lives in video-analysis-pricing.ts. Composite id
|
|
1746
|
+
// shape `video-analysis:<model>:<bucket>s` mirrors volcengine-lipsync: bare
|
|
1747
|
+
// row = per-model namespace root at the 10-min ceiling, plus one row per bucket.)
|
|
1748
|
+
"gemini-3-flash-video-analysis": {
|
|
1749
|
+
id: "gemini-3-flash-video-analysis",
|
|
1750
|
+
kind: "video",
|
|
1751
|
+
modes: ["video-analysis"] as const,
|
|
1752
|
+
family: "Google",
|
|
1753
|
+
label: "Video Analysis (Gemini 3 Flash)",
|
|
1754
|
+
series: "Gemini",
|
|
1755
|
+
description: "Analyze a video into a structured shot list (scenes, camera, audio) — fast Gemini tier. Billed per duration bucket.",
|
|
1756
|
+
useCases: ["video-analysis", "shot-list", "fast"],
|
|
1757
|
+
pricing: [
|
|
1758
|
+
{ identifier: "video-analysis:gemini-3-flash", credits: 3, note: "10-min ceiling (no duration given)" },
|
|
1759
|
+
{ identifier: "video-analysis:gemini-3-flash:60s", credits: 1 },
|
|
1760
|
+
{ identifier: "video-analysis:gemini-3-flash:180s", credits: 1 },
|
|
1761
|
+
{ identifier: "video-analysis:gemini-3-flash:360s", credits: 2 },
|
|
1762
|
+
{ identifier: "video-analysis:gemini-3-flash:600s", credits: 3, note: "10-min ceiling" },
|
|
1763
|
+
],
|
|
1764
|
+
},
|
|
1765
|
+
"gemini-3.1-pro-video-analysis": {
|
|
1766
|
+
id: "gemini-3.1-pro-video-analysis",
|
|
1767
|
+
kind: "video",
|
|
1768
|
+
modes: ["video-analysis"] as const,
|
|
1769
|
+
family: "Google",
|
|
1770
|
+
label: "Video Analysis (Gemini 3.1 Pro)",
|
|
1771
|
+
series: "Gemini",
|
|
1772
|
+
description: "Analyze a video into a structured shot list (scenes, camera, audio) — high-fidelity Gemini tier. Billed per duration bucket.",
|
|
1773
|
+
useCases: ["video-analysis", "shot-list", "cinematic"],
|
|
1774
|
+
pricing: [
|
|
1775
|
+
{ identifier: "video-analysis:gemini-3.1-pro", credits: 11, note: "10-min ceiling (no duration given)" },
|
|
1776
|
+
{ identifier: "video-analysis:gemini-3.1-pro:60s", credits: 2 },
|
|
1777
|
+
{ identifier: "video-analysis:gemini-3.1-pro:180s", credits: 3 },
|
|
1778
|
+
{ identifier: "video-analysis:gemini-3.1-pro:360s", credits: 7 },
|
|
1779
|
+
{ identifier: "video-analysis:gemini-3.1-pro:600s", credits: 11, note: "10-min ceiling" },
|
|
1780
|
+
],
|
|
1781
|
+
},
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
// =============================================================================
|
|
1785
|
+
// AUDIO MODELS
|
|
1786
|
+
// =============================================================================
|
|
1787
|
+
const AUDIO_MODELS: Record<string, ModelCatalogEntry> = {
|
|
1788
|
+
// ── ElevenLabs TTS ──
|
|
1789
|
+
"elevenlabs-v3": {
|
|
1790
|
+
id: "elevenlabs-v3",
|
|
1791
|
+
kind: "audio",
|
|
1792
|
+
modes: ["tts"] as const,
|
|
1793
|
+
family: "ElevenLabs",
|
|
1794
|
+
label: "ElevenLabs v3",
|
|
1795
|
+
series: "ElevenLabs",
|
|
1796
|
+
description: "Latest ElevenLabs TTS — supports [audio tags] for emotion / pacing. Direct API.",
|
|
1797
|
+
useCases: ["tts", "voice-over", "narration", "expressive"],
|
|
1798
|
+
features: ["audio-tags", "voice-cloning"],
|
|
1799
|
+
pricing: [{ identifier: "elevenlabs-v3", credits: 3 }],
|
|
1800
|
+
featured: true,
|
|
1801
|
+
},
|
|
1802
|
+
"elevenlabs-turbo": {
|
|
1803
|
+
id: "elevenlabs-turbo",
|
|
1804
|
+
kind: "audio",
|
|
1805
|
+
modes: ["tts"] as const,
|
|
1806
|
+
family: "ElevenLabs",
|
|
1807
|
+
label: "ElevenLabs Turbo v2.5",
|
|
1808
|
+
series: "ElevenLabs",
|
|
1809
|
+
description: "Fast, cheap ElevenLabs TTS via the direct ElevenLabs API. Good for narration.",
|
|
1810
|
+
useCases: ["tts", "narration", "fast"],
|
|
1811
|
+
pricing: [{ identifier: "elevenlabs-turbo", credits: 2, note: "per 1K chars" }],
|
|
1812
|
+
},
|
|
1813
|
+
"elevenlabs-multilingual": {
|
|
1814
|
+
id: "elevenlabs-multilingual",
|
|
1815
|
+
kind: "audio",
|
|
1816
|
+
modes: ["tts"] as const,
|
|
1817
|
+
family: "ElevenLabs",
|
|
1818
|
+
label: "ElevenLabs Multilingual v2",
|
|
1819
|
+
series: "ElevenLabs",
|
|
1820
|
+
description: "Multi-language ElevenLabs TTS via the direct ElevenLabs API.",
|
|
1821
|
+
useCases: ["tts", "multilingual"],
|
|
1822
|
+
pricing: [{ identifier: "elevenlabs-multilingual", credits: 3, note: "per 1K chars" }],
|
|
1823
|
+
},
|
|
1824
|
+
"elevenlabs-dialogue": {
|
|
1825
|
+
id: "elevenlabs-dialogue",
|
|
1826
|
+
kind: "audio",
|
|
1827
|
+
modes: ["tts"] as const,
|
|
1828
|
+
family: "ElevenLabs",
|
|
1829
|
+
label: "ElevenLabs Dialogue v3",
|
|
1830
|
+
series: "ElevenLabs",
|
|
1831
|
+
description: "Multi-speaker dialogue TTS — give it a script, it voices each role.",
|
|
1832
|
+
useCases: ["tts", "dialogue", "multi-speaker"],
|
|
1833
|
+
pricing: [{ identifier: "elevenlabs-dialogue", credits: 4, note: "per 1K chars" }],
|
|
1834
|
+
// Driven only via the dialogue/character-voice path (multi-speaker script
|
|
1835
|
+
// shape), NOT the single-text generate_speech verb. Hide from MCP
|
|
1836
|
+
// list_models so generate_speech (TTS_PROVIDERS) can't advertise it and
|
|
1837
|
+
// then 400. Re-expose if a dialogue-capable MCP verb is added.
|
|
1838
|
+
mcpHidden: true,
|
|
1839
|
+
},
|
|
1840
|
+
|
|
1841
|
+
// ── ElevenLabs voice utilities ──
|
|
1842
|
+
"voice-clone": {
|
|
1843
|
+
id: "voice-clone",
|
|
1844
|
+
kind: "audio",
|
|
1845
|
+
modes: ["voice-clone"] as const,
|
|
1846
|
+
family: "ElevenLabs",
|
|
1847
|
+
label: "Voice Clone (Instant)",
|
|
1848
|
+
series: "ElevenLabs",
|
|
1849
|
+
description: "Clone a voice from a short reference clip. Instant clone via direct ElevenLabs API.",
|
|
1850
|
+
useCases: ["voice-clone", "personalization"],
|
|
1851
|
+
pricing: [{ identifier: "voice-clone", credits: 5 }],
|
|
1852
|
+
},
|
|
1853
|
+
"elevenlabs-voice-design": {
|
|
1854
|
+
id: "elevenlabs-voice-design",
|
|
1855
|
+
kind: "audio",
|
|
1856
|
+
modes: ["voice-design"] as const,
|
|
1857
|
+
family: "ElevenLabs",
|
|
1858
|
+
label: "ElevenLabs Voice Design",
|
|
1859
|
+
series: "ElevenLabs",
|
|
1860
|
+
description: "Design a synthetic voice from a description (no reference clip needed).",
|
|
1861
|
+
useCases: ["voice-design", "synthetic"],
|
|
1862
|
+
pricing: [{ identifier: "elevenlabs-voice-design", credits: 5 }],
|
|
1863
|
+
},
|
|
1864
|
+
"elevenlabs-voice-changer": {
|
|
1865
|
+
id: "elevenlabs-voice-changer",
|
|
1866
|
+
kind: "audio",
|
|
1867
|
+
modes: ["voice-changer"] as const,
|
|
1868
|
+
family: "ElevenLabs",
|
|
1869
|
+
label: "ElevenLabs Voice Changer",
|
|
1870
|
+
series: "ElevenLabs",
|
|
1871
|
+
description: "Speech-to-speech: convert one voice to another while preserving prosody.",
|
|
1872
|
+
useCases: ["voice-conversion", "dubbing"],
|
|
1873
|
+
pricing: [{ identifier: "elevenlabs-voice-changer", credits: 4 }],
|
|
1874
|
+
},
|
|
1875
|
+
"elevenlabs-stt": {
|
|
1876
|
+
id: "elevenlabs-stt",
|
|
1877
|
+
kind: "audio",
|
|
1878
|
+
modes: ["stt"] as const,
|
|
1879
|
+
family: "ElevenLabs",
|
|
1880
|
+
label: "ElevenLabs STT",
|
|
1881
|
+
series: "ElevenLabs",
|
|
1882
|
+
description: "Speech-to-text — transcribe audio with timestamps.",
|
|
1883
|
+
useCases: ["transcription", "stt"],
|
|
1884
|
+
pricing: [{ identifier: "elevenlabs-stt", credits: 3 }],
|
|
1885
|
+
},
|
|
1886
|
+
"elevenlabs-isolation": {
|
|
1887
|
+
id: "elevenlabs-isolation",
|
|
1888
|
+
kind: "audio",
|
|
1889
|
+
modes: ["isolation"] as const,
|
|
1890
|
+
family: "ElevenLabs",
|
|
1891
|
+
label: "ElevenLabs Voice Isolation",
|
|
1892
|
+
series: "ElevenLabs",
|
|
1893
|
+
description: "Strip background noise / music from a vocal track.",
|
|
1894
|
+
useCases: ["cleanup", "isolation"],
|
|
1895
|
+
pricing: [{ identifier: "elevenlabs-isolation", credits: 8, note: "variable per second" }],
|
|
1896
|
+
},
|
|
1897
|
+
"elevenlabs-dubbing": {
|
|
1898
|
+
id: "elevenlabs-dubbing",
|
|
1899
|
+
kind: "audio",
|
|
1900
|
+
modes: ["dubbing"] as const,
|
|
1901
|
+
family: "ElevenLabs",
|
|
1902
|
+
label: "ElevenLabs Dubbing",
|
|
1903
|
+
series: "ElevenLabs",
|
|
1904
|
+
description: "Translate + dub a video into a new language. Async.",
|
|
1905
|
+
useCases: ["dubbing", "multilingual"],
|
|
1906
|
+
pricing: [{ identifier: "elevenlabs-dubbing", credits: 8 }],
|
|
1907
|
+
},
|
|
1908
|
+
"elevenlabs-forced-alignment": {
|
|
1909
|
+
id: "elevenlabs-forced-alignment",
|
|
1910
|
+
kind: "audio",
|
|
1911
|
+
modes: ["forced-alignment"] as const,
|
|
1912
|
+
family: "ElevenLabs",
|
|
1913
|
+
label: "ElevenLabs Forced Alignment",
|
|
1914
|
+
series: "ElevenLabs",
|
|
1915
|
+
description: "Align an existing transcript to audio with word-level timestamps.",
|
|
1916
|
+
useCases: ["alignment", "captions"],
|
|
1917
|
+
pricing: [{ identifier: "elevenlabs-forced-alignment", credits: 3 }],
|
|
1918
|
+
},
|
|
1919
|
+
|
|
1920
|
+
// ── ElevenLabs SFX ──
|
|
1921
|
+
"elevenlabs-sfx": {
|
|
1922
|
+
id: "elevenlabs-sfx",
|
|
1923
|
+
kind: "audio",
|
|
1924
|
+
modes: ["sfx"] as const,
|
|
1925
|
+
family: "ElevenLabs",
|
|
1926
|
+
label: "ElevenLabs Sound Effects",
|
|
1927
|
+
series: "ElevenLabs",
|
|
1928
|
+
description: "Generate short sound effects from a text prompt.",
|
|
1929
|
+
useCases: ["sfx", "ambient"],
|
|
1930
|
+
pricing: [{ identifier: "elevenlabs-sfx", credits: 1, note: "~5s clip" }],
|
|
1931
|
+
},
|
|
1932
|
+
|
|
1933
|
+
// ── Suno music ──
|
|
1934
|
+
"suno": {
|
|
1935
|
+
id: "suno",
|
|
1936
|
+
kind: "audio",
|
|
1937
|
+
modes: ["music"] as const,
|
|
1938
|
+
family: "Suno",
|
|
1939
|
+
label: "Suno v4",
|
|
1940
|
+
series: "Suno",
|
|
1941
|
+
dataValue: "V4",
|
|
1942
|
+
description: "Suno v4 music generation — full songs with vocals, multiple genres.",
|
|
1943
|
+
useCases: ["music", "song", "vocals"],
|
|
1944
|
+
pricing: [{ identifier: "suno", credits: 3, note: "per generation" }],
|
|
1945
|
+
},
|
|
1946
|
+
"suno-v5": {
|
|
1947
|
+
id: "suno-v5",
|
|
1948
|
+
kind: "audio",
|
|
1949
|
+
modes: ["music"] as const,
|
|
1950
|
+
family: "Suno",
|
|
1951
|
+
label: "Suno v5",
|
|
1952
|
+
series: "Suno",
|
|
1953
|
+
dataValue: "V5",
|
|
1954
|
+
description: "Newer Suno v5 — better vocal quality, more genres. Same price as v4.",
|
|
1955
|
+
useCases: ["music", "song", "vocals", "premium"],
|
|
1956
|
+
pricing: [{ identifier: "suno-v5", credits: 3, note: "per generation" }],
|
|
1957
|
+
},
|
|
1958
|
+
"suno-v5_5": {
|
|
1959
|
+
id: "suno-v5_5",
|
|
1960
|
+
kind: "audio",
|
|
1961
|
+
modes: ["music"] as const,
|
|
1962
|
+
family: "Suno",
|
|
1963
|
+
label: "Suno v5.5",
|
|
1964
|
+
series: "Suno",
|
|
1965
|
+
dataValue: "V5_5",
|
|
1966
|
+
description: "Suno v5.5 — latest model with improved audio quality and expressiveness.",
|
|
1967
|
+
useCases: ["music", "song", "vocals", "premium"],
|
|
1968
|
+
pricing: [{ identifier: "suno-v5_5", credits: 3, note: "per generation" }],
|
|
1969
|
+
featured: true,
|
|
1970
|
+
},
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
// =============================================================================
|
|
1974
|
+
// COMBINED CATALOG
|
|
1975
|
+
// =============================================================================
|
|
1976
|
+
export const MODEL_CATALOG: Record<string, ModelCatalogEntry> = {
|
|
1977
|
+
...IMAGE_MODELS,
|
|
1978
|
+
...VIDEO_MODELS,
|
|
1979
|
+
...AUDIO_MODELS,
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1982
|
+
/**
|
|
1983
|
+
* Lookup helpers for MCP and the future frontend migration.
|
|
1984
|
+
*/
|
|
1985
|
+
export function listModels(filter?: {
|
|
1986
|
+
kind?: ModelKind
|
|
1987
|
+
mode?: ModelMode
|
|
1988
|
+
family?: string
|
|
1989
|
+
}): ModelCatalogEntry[] {
|
|
1990
|
+
const all = Object.values(MODEL_CATALOG)
|
|
1991
|
+
if (!filter) return all
|
|
1992
|
+
return all.filter((m) => {
|
|
1993
|
+
if (filter.kind && m.kind !== filter.kind) return false
|
|
1994
|
+
if (filter.mode && !m.modes.includes(filter.mode)) return false
|
|
1995
|
+
if (filter.family && m.family.toLowerCase() !== filter.family.toLowerCase()) return false
|
|
1996
|
+
return true
|
|
1997
|
+
})
|
|
1998
|
+
}
|
|
1999
|
+
|
|
2000
|
+
/**
|
|
2001
|
+
* Group entries by family, returning a stable order.
|
|
2002
|
+
* Returns Array<{ family: string; models: ModelCatalogEntry[] }>.
|
|
2003
|
+
*/
|
|
2004
|
+
export function groupByFamily(
|
|
2005
|
+
entries: ModelCatalogEntry[],
|
|
2006
|
+
): Array<{ family: string; models: ModelCatalogEntry[] }> {
|
|
2007
|
+
const groups = new Map<string, ModelCatalogEntry[]>()
|
|
2008
|
+
for (const m of entries) {
|
|
2009
|
+
const list = groups.get(m.family)
|
|
2010
|
+
if (list) list.push(m)
|
|
2011
|
+
else groups.set(m.family, [m])
|
|
2012
|
+
}
|
|
2013
|
+
return Array.from(groups.entries()).map(([family, models]) => ({ family, models }))
|
|
2014
|
+
}
|
|
2015
|
+
|
|
2016
|
+
export function getModel(id: string): ModelCatalogEntry | undefined {
|
|
2017
|
+
return MODEL_CATALOG[id]
|
|
2018
|
+
}
|
|
2019
|
+
|
|
2020
|
+
// =============================================================================
|
|
2021
|
+
// Per-model input validation
|
|
2022
|
+
// =============================================================================
|
|
2023
|
+
export type ValidationField =
|
|
2024
|
+
| "aspectRatio"
|
|
2025
|
+
| "resolution"
|
|
2026
|
+
| "quality"
|
|
2027
|
+
| "duration"
|
|
2028
|
+
|
|
2029
|
+
export interface ModelValidationIssue {
|
|
2030
|
+
field: ValidationField
|
|
2031
|
+
message: string
|
|
2032
|
+
allowed: readonly (string | number)[] | null
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
/**
|
|
2036
|
+
* Validate that the user's lever values are supported by the chosen model.
|
|
2037
|
+
*
|
|
2038
|
+
* Returns null on success or the FIRST issue found. We surface a single issue
|
|
2039
|
+
* rather than collecting all so the agent doesn't get a wall of complaints —
|
|
2040
|
+
* fix one thing, retry. Order: aspectRatio → resolution → quality → duration.
|
|
2041
|
+
*
|
|
2042
|
+
* Semantics:
|
|
2043
|
+
* - If the field is undefined, it's never an issue.
|
|
2044
|
+
* - If the catalog entry doesn't list a corresponding lever (e.g. base
|
|
2045
|
+
* nano-banana has no `resolutions`), passing that field IS an issue —
|
|
2046
|
+
* silently dropping it has caused two bugs already.
|
|
2047
|
+
* - If the value is in the allow list, it's valid.
|
|
2048
|
+
*
|
|
2049
|
+
* Unknown model ids are NOT flagged here — the route handler / Zod model
|
|
2050
|
+
* enum is the right gate for that. We just skip validation.
|
|
2051
|
+
*/
|
|
2052
|
+
export function validateModelInput(
|
|
2053
|
+
modelId: string,
|
|
2054
|
+
input: {
|
|
2055
|
+
aspectRatio?: string
|
|
2056
|
+
resolution?: string
|
|
2057
|
+
quality?: string
|
|
2058
|
+
duration?: number
|
|
2059
|
+
},
|
|
2060
|
+
): ModelValidationIssue | null {
|
|
2061
|
+
const m = MODEL_CATALOG[modelId]
|
|
2062
|
+
if (!m) return null
|
|
2063
|
+
|
|
2064
|
+
if (input.aspectRatio !== undefined) {
|
|
2065
|
+
if (!m.aspectRatios) {
|
|
2066
|
+
return {
|
|
2067
|
+
field: "aspectRatio",
|
|
2068
|
+
message: `Model "${modelId}" does not have an aspect_ratio lever — omit it.`,
|
|
2069
|
+
allowed: null,
|
|
2070
|
+
}
|
|
2071
|
+
}
|
|
2072
|
+
if (!m.aspectRatios.includes(input.aspectRatio)) {
|
|
2073
|
+
return {
|
|
2074
|
+
field: "aspectRatio",
|
|
2075
|
+
message: `Model "${modelId}" does not support aspect_ratio "${input.aspectRatio}". Supported: ${m.aspectRatios.join(", ")}.`,
|
|
2076
|
+
allowed: m.aspectRatios,
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
}
|
|
2080
|
+
|
|
2081
|
+
if (input.resolution !== undefined) {
|
|
2082
|
+
if (!m.resolutions) {
|
|
2083
|
+
return {
|
|
2084
|
+
field: "resolution",
|
|
2085
|
+
message: `Model "${modelId}" does not have a resolution lever — omit it.`,
|
|
2086
|
+
allowed: null,
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
if (!m.resolutions.includes(input.resolution)) {
|
|
2090
|
+
return {
|
|
2091
|
+
field: "resolution",
|
|
2092
|
+
message: `Model "${modelId}" does not support resolution "${input.resolution}". Supported: ${m.resolutions.join(", ")}.`,
|
|
2093
|
+
allowed: m.resolutions,
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
if (input.quality !== undefined) {
|
|
2099
|
+
if (!m.qualities) {
|
|
2100
|
+
return {
|
|
2101
|
+
field: "quality",
|
|
2102
|
+
message: `Model "${modelId}" does not have a quality lever — omit it.`,
|
|
2103
|
+
allowed: null,
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
if (!m.qualities.includes(input.quality)) {
|
|
2107
|
+
return {
|
|
2108
|
+
field: "quality",
|
|
2109
|
+
message: `Model "${modelId}" does not support quality "${input.quality}". Supported: ${m.qualities.join(", ")}.`,
|
|
2110
|
+
allowed: m.qualities,
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2115
|
+
if (input.duration !== undefined) {
|
|
2116
|
+
if (!m.durations) {
|
|
2117
|
+
return {
|
|
2118
|
+
field: "duration",
|
|
2119
|
+
message: `Model "${modelId}" does not have a duration lever — omit it.`,
|
|
2120
|
+
allowed: null,
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
if (!m.durations.includes(input.duration)) {
|
|
2124
|
+
return {
|
|
2125
|
+
field: "duration",
|
|
2126
|
+
message: `Model "${modelId}" does not support duration ${input.duration}s. Supported: ${m.durations.join(", ")}s.`,
|
|
2127
|
+
allowed: m.durations,
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
|
|
2132
|
+
return null
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
// =============================================================================
|
|
2136
|
+
// Frontend picker helpers — return `{value, label}[]` shapes that the
|
|
2137
|
+
// existing config-panel components expect, derived from the catalog so we
|
|
2138
|
+
// don't duplicate registries between backend and frontend.
|
|
2139
|
+
// =============================================================================
|
|
2140
|
+
|
|
2141
|
+
/**
|
|
2142
|
+
* Global value-label decorations. Per-model overrides live on
|
|
2143
|
+
* `ModelCatalogEntry.valueLabels` (e.g. Hailuo's "1080P (6s max)").
|
|
2144
|
+
*
|
|
2145
|
+
* Add entries here when a value should render with a richer human label.
|
|
2146
|
+
* Plain values (3:2, 4:3, 720p, etc.) can stay undecorated — the value
|
|
2147
|
+
* itself is the label and we fall back to `value` automatically.
|
|
2148
|
+
*/
|
|
2149
|
+
const MODEL_VALUE_LABELS: Record<string, string> = {
|
|
2150
|
+
// aspect ratios
|
|
2151
|
+
"1:1": "1:1 (Square)",
|
|
2152
|
+
"16:9": "16:9 (Landscape)",
|
|
2153
|
+
"9:16": "9:16 (Portrait)",
|
|
2154
|
+
"21:9": "21:9 (Ultra-wide)",
|
|
2155
|
+
"9:21": "9:21 (Tall ultra-wide)",
|
|
2156
|
+
"auto": "Auto",
|
|
2157
|
+
"adaptive": "Adaptive",
|
|
2158
|
+
// image resolutions
|
|
2159
|
+
"1K": "1K (Standard)",
|
|
2160
|
+
"2K": "2K (High)",
|
|
2161
|
+
"4K": "4K (Ultra)",
|
|
2162
|
+
"4k": "4K",
|
|
2163
|
+
"8K": "8K (Ultra)",
|
|
2164
|
+
// qualities
|
|
2165
|
+
"medium": "Medium (Balanced)",
|
|
2166
|
+
"high": "High (Detailed)",
|
|
2167
|
+
"basic": "Basic (2K)",
|
|
2168
|
+
"TURBO": "Turbo (fast)",
|
|
2169
|
+
"BALANCED": "Balanced",
|
|
2170
|
+
"QUALITY": "Quality (best)",
|
|
2171
|
+
}
|
|
2172
|
+
|
|
2173
|
+
function decorateLabel(model: ModelCatalogEntry, value: string): string {
|
|
2174
|
+
return model.valueLabels?.[value] ?? MODEL_VALUE_LABELS[value] ?? value
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
export interface LabeledOption {
|
|
2178
|
+
value: string
|
|
2179
|
+
label: string
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2182
|
+
/** Return `{value, label}[]` for the model's aspect ratios, or `null` if none. */
|
|
2183
|
+
export function getAspectRatioOptions(modelId: string): LabeledOption[] | null {
|
|
2184
|
+
const m = MODEL_CATALOG[modelId]
|
|
2185
|
+
if (!m?.aspectRatios) return null
|
|
2186
|
+
return m.aspectRatios.map((v) => ({ value: v, label: decorateLabel(m, v) }))
|
|
2187
|
+
}
|
|
2188
|
+
|
|
2189
|
+
/** Return `{value, label}[]` for the model's resolution lever, or `null` if none. */
|
|
2190
|
+
export function getResolutionOptions(modelId: string): LabeledOption[] | null {
|
|
2191
|
+
const m = MODEL_CATALOG[modelId]
|
|
2192
|
+
if (!m?.resolutions) return null
|
|
2193
|
+
return m.resolutions.map((v) => ({ value: v, label: decorateLabel(m, v) }))
|
|
2194
|
+
}
|
|
2195
|
+
|
|
2196
|
+
/** Return `{value, label}[]` for the model's quality lever, or `null` if none. */
|
|
2197
|
+
export function getQualityOptions(modelId: string): LabeledOption[] | null {
|
|
2198
|
+
const m = MODEL_CATALOG[modelId]
|
|
2199
|
+
if (!m?.qualities) return null
|
|
2200
|
+
return m.qualities.map((v) => ({ value: v, label: decorateLabel(m, v) }))
|
|
2201
|
+
}
|
|
2202
|
+
|
|
2203
|
+
/** Return the model's allowed durations (in seconds), or `null` if none. */
|
|
2204
|
+
export function getDurationsForModel(modelId: string): number[] | null {
|
|
2205
|
+
const m = MODEL_CATALOG[modelId]
|
|
2206
|
+
if (!m?.durations) return null
|
|
2207
|
+
return [...m.durations]
|
|
2208
|
+
}
|
|
2209
|
+
|
|
2210
|
+
/**
|
|
2211
|
+
* Min/max credits across all pricing variants — used by the frontend's
|
|
2212
|
+
* "5–8 cr" range badge in the model dropdown. `null` for models with a
|
|
2213
|
+
* single price point.
|
|
2214
|
+
*/
|
|
2215
|
+
export function getCreditRange(modelId: string): { min: number; max: number } | null {
|
|
2216
|
+
const m = MODEL_CATALOG[modelId]
|
|
2217
|
+
if (!m || m.pricing.length < 2) return null
|
|
2218
|
+
let min = Infinity
|
|
2219
|
+
let max = -Infinity
|
|
2220
|
+
for (const p of m.pricing) {
|
|
2221
|
+
if (p.credits < min) min = p.credits
|
|
2222
|
+
if (p.credits > max) max = p.credits
|
|
2223
|
+
}
|
|
2224
|
+
return { min, max }
|
|
2225
|
+
}
|
|
2226
|
+
|
|
2227
|
+
/** Whether the model declares a given capability flag in its `features` array. */
|
|
2228
|
+
export function hasFeature(modelId: string, feature: string): boolean {
|
|
2229
|
+
return MODEL_CATALOG[modelId]?.features?.includes(feature) ?? false
|
|
2230
|
+
}
|
|
2231
|
+
|
|
2232
|
+
/**
|
|
2233
|
+
* All ids in the catalog whose `features` includes the given flag. Used by
|
|
2234
|
+
* frontend to build PROVIDERS_WITH_END_FRAME / PROVIDERS_WITH_REFERENCES
|
|
2235
|
+
* lists derived from the catalog.
|
|
2236
|
+
*/
|
|
2237
|
+
export function modelsWithFeature(feature: string): string[] {
|
|
2238
|
+
return Object.values(MODEL_CATALOG)
|
|
2239
|
+
.filter((m) => m.features?.includes(feature))
|
|
2240
|
+
.map((m) => m.id)
|
|
2241
|
+
}
|
|
2242
|
+
|
|
2243
|
+
/**
|
|
2244
|
+
* Map of `{modelId: durations[]}` for every catalog entry whose `modes`
|
|
2245
|
+
* includes the given mode. Used to derive KIE_VIDEO_DURATIONS (mode i2v)
|
|
2246
|
+
* and KIE_T2V_DURATIONS (mode t2v) on the frontend.
|
|
2247
|
+
*/
|
|
2248
|
+
export function durationsByMode(mode: ModelMode): Record<string, number[]> {
|
|
2249
|
+
const out: Record<string, number[]> = {}
|
|
2250
|
+
for (const m of Object.values(MODEL_CATALOG)) {
|
|
2251
|
+
if (!m.modes.includes(mode)) continue
|
|
2252
|
+
if (!m.durations) continue
|
|
2253
|
+
out[m.id] = [...m.durations]
|
|
2254
|
+
}
|
|
2255
|
+
return out
|
|
2256
|
+
}
|
|
2257
|
+
|
|
2258
|
+
/**
|
|
2259
|
+
* Map of `{modelId: LabeledOption[]}` for every catalog entry of the given
|
|
2260
|
+
* kind that exposes a resolution lever. Used to derive
|
|
2261
|
+
* IMAGE_RESOLUTION_OPTIONS / VIDEO_RESOLUTION_OPTIONS on the frontend.
|
|
2262
|
+
*/
|
|
2263
|
+
export function resolutionOptionsByKind(kind: ModelKind): Record<string, LabeledOption[]> {
|
|
2264
|
+
const out: Record<string, LabeledOption[]> = {}
|
|
2265
|
+
for (const m of Object.values(MODEL_CATALOG)) {
|
|
2266
|
+
if (m.kind !== kind) continue
|
|
2267
|
+
if (!m.resolutions) continue
|
|
2268
|
+
out[m.id] = m.resolutions.map((v) => ({ value: v, label: decorateLabel(m, v) }))
|
|
2269
|
+
}
|
|
2270
|
+
return out
|
|
2271
|
+
}
|
|
2272
|
+
|
|
2273
|
+
/**
|
|
2274
|
+
* Map of `{modelId: LabeledOption[]}` for every catalog entry of the given
|
|
2275
|
+
* kind that exposes an aspect_ratio lever.
|
|
2276
|
+
*/
|
|
2277
|
+
export function aspectRatioOptionsByKind(kind: ModelKind): Record<string, LabeledOption[]> {
|
|
2278
|
+
const out: Record<string, LabeledOption[]> = {}
|
|
2279
|
+
for (const m of Object.values(MODEL_CATALOG)) {
|
|
2280
|
+
if (m.kind !== kind) continue
|
|
2281
|
+
if (!m.aspectRatios) continue
|
|
2282
|
+
out[m.id] = m.aspectRatios.map((v) => ({ value: v, label: decorateLabel(m, v) }))
|
|
2283
|
+
}
|
|
2284
|
+
return out
|
|
2285
|
+
}
|
|
2286
|
+
|
|
2287
|
+
/** Same shape as `aspectRatioOptionsByKind` but for `qualities`. */
|
|
2288
|
+
export function qualityOptionsByKind(kind: ModelKind): Record<string, LabeledOption[]> {
|
|
2289
|
+
const out: Record<string, LabeledOption[]> = {}
|
|
2290
|
+
for (const m of Object.values(MODEL_CATALOG)) {
|
|
2291
|
+
if (m.kind !== kind) continue
|
|
2292
|
+
if (!m.qualities) continue
|
|
2293
|
+
out[m.id] = m.qualities.map((v) => ({ value: v, label: decorateLabel(m, v) }))
|
|
2294
|
+
}
|
|
2295
|
+
return out
|
|
2296
|
+
}
|
|
2297
|
+
|
|
2298
|
+
/** One shaped model row for a picker — capabilities derived from the catalog. */
|
|
2299
|
+
export interface ModelMenuOption {
|
|
2300
|
+
/** Catalog id (the API enum value). */
|
|
2301
|
+
id: string
|
|
2302
|
+
/** Human-readable display name from the catalog. */
|
|
2303
|
+
label: string
|
|
2304
|
+
/** Resolution levers, `[]` when the model exposes none. */
|
|
2305
|
+
resolutions: LabeledOption[]
|
|
2306
|
+
/** Aspect-ratio levers, `[]` when the model exposes none. */
|
|
2307
|
+
aspectRatios: LabeledOption[]
|
|
2308
|
+
/** Duration levers in seconds (union of i2v+t2v modes); `[]` for image / fixed-duration models. */
|
|
2309
|
+
durations: { value: number; label: string }[]
|
|
2310
|
+
}
|
|
2311
|
+
|
|
2312
|
+
/**
|
|
2313
|
+
* Build shaped picker rows for one catalog `kind` from a caller-supplied id
|
|
2314
|
+
* allowlist (the curation — which ids, in what order, stays the caller's). Every
|
|
2315
|
+
* CAPABILITY is derived from the catalog accessors, never hardcoded, so the menu
|
|
2316
|
+
* can't drift as the catalog evolves. Ids absent from the catalog or of the
|
|
2317
|
+
* wrong kind are skipped (so a stale allowlist entry drops out cleanly). Pure;
|
|
2318
|
+
* returns fresh arrays (never aliases the accessor maps).
|
|
2319
|
+
*/
|
|
2320
|
+
export function buildModelMenu(
|
|
2321
|
+
kind: ModelKind,
|
|
2322
|
+
allowlist: readonly string[],
|
|
2323
|
+
): ModelMenuOption[] {
|
|
2324
|
+
const resByModel = resolutionOptionsByKind(kind)
|
|
2325
|
+
const arByModel = aspectRatioOptionsByKind(kind)
|
|
2326
|
+
const i2v = durationsByMode("i2v")
|
|
2327
|
+
const t2v = durationsByMode("t2v")
|
|
2328
|
+
const out: ModelMenuOption[] = []
|
|
2329
|
+
for (const id of allowlist) {
|
|
2330
|
+
const entry = MODEL_CATALOG[id]
|
|
2331
|
+
if (!entry || entry.kind !== kind) continue
|
|
2332
|
+
const durations = Array.from(new Set([...(i2v[id] ?? []), ...(t2v[id] ?? [])]))
|
|
2333
|
+
.sort((a, b) => a - b)
|
|
2334
|
+
.map((n) => ({ value: n, label: `${n}s` }))
|
|
2335
|
+
out.push({
|
|
2336
|
+
id: entry.id,
|
|
2337
|
+
label: entry.label,
|
|
2338
|
+
resolutions: [...(resByModel[id] ?? [])],
|
|
2339
|
+
aspectRatios: [...(arByModel[id] ?? [])],
|
|
2340
|
+
durations,
|
|
2341
|
+
})
|
|
2342
|
+
}
|
|
2343
|
+
return out
|
|
2344
|
+
}
|
|
2345
|
+
|
|
2346
|
+
/** All `{modelId: {min, max}}` entries that have variable pricing (>1 variant). */
|
|
2347
|
+
export function creditRangesAll(): Record<string, { min: number; max: number }> {
|
|
2348
|
+
const out: Record<string, { min: number; max: number }> = {}
|
|
2349
|
+
for (const m of Object.values(MODEL_CATALOG)) {
|
|
2350
|
+
const range = getCreditRange(m.id)
|
|
2351
|
+
if (range) out[m.id] = range
|
|
2352
|
+
}
|
|
2353
|
+
return out
|
|
2354
|
+
}
|
|
2355
|
+
|
|
2356
|
+
/**
|
|
2357
|
+
* Tuple of model ids for a given kind + one or more modes. Built for use as
|
|
2358
|
+
* a Zod `z.enum()` argument so MCP tool input schemas stay in sync with the
|
|
2359
|
+
* catalog without manual maintenance.
|
|
2360
|
+
*
|
|
2361
|
+
* Filters out `mcpHidden: true` entries by default — pass `includeHidden`
|
|
2362
|
+
* to surface legacy versions (used internally by validators that still need
|
|
2363
|
+
* to recognize older ids forwarded by direct API callers).
|
|
2364
|
+
*
|
|
2365
|
+
* Pass `kind: null` to skip the kind filter — useful for cross-kind ids
|
|
2366
|
+
* like "grok" (catalog kind=image, modes include both t2i and t2v).
|
|
2367
|
+
*/
|
|
2368
|
+
export function modelIdsByKindMode(
|
|
2369
|
+
kind: ModelKind | null,
|
|
2370
|
+
modes: readonly ModelMode[],
|
|
2371
|
+
opts: { includeHidden?: boolean } = {},
|
|
2372
|
+
): [string, ...string[]] {
|
|
2373
|
+
const includeHidden = opts.includeHidden === true
|
|
2374
|
+
const ids = Object.values(MODEL_CATALOG)
|
|
2375
|
+
.filter((m) => (kind === null ? true : m.kind === kind))
|
|
2376
|
+
.filter((m) => m.modes.some((md) => modes.includes(md)))
|
|
2377
|
+
.filter((m) => includeHidden || !m.mcpHidden)
|
|
2378
|
+
.map((m) => m.id)
|
|
2379
|
+
.sort()
|
|
2380
|
+
if (ids.length === 0) {
|
|
2381
|
+
throw new Error(
|
|
2382
|
+
`modelIdsByKindMode: no models match kind=${kind ?? "*"} modes=[${modes.join(",")}]`,
|
|
2383
|
+
)
|
|
2384
|
+
}
|
|
2385
|
+
return ids as [string, ...string[]]
|
|
2386
|
+
}
|