@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
package/src/json-path.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Evaluate a dot-notation path against a JSON value.
|
|
3
|
+
*
|
|
4
|
+
* Auto-iterates arrays at any depth: if the current value is an array,
|
|
5
|
+
* the evaluator iterates and applies the remaining path to each element,
|
|
6
|
+
* flattening results. This lets simple paths like "caption" work on a
|
|
7
|
+
* top-level array and "pages.markdown" work on a nested array without
|
|
8
|
+
* any wildcard syntax.
|
|
9
|
+
*
|
|
10
|
+
* Returns an array of values. Missing properties are skipped (empty for
|
|
11
|
+
* that branch). Null/undefined leaf values are included — callers coerce.
|
|
12
|
+
* Empty path returns `[value]` (a single-element array wrapping the
|
|
13
|
+
* value) when the value is scalar/object, or the array elements flat
|
|
14
|
+
* when the root is an array.
|
|
15
|
+
*/
|
|
16
|
+
export function evaluateJsonPath(value: unknown, path: string): unknown[] {
|
|
17
|
+
const segments = path.length === 0 ? [] : path.split(".")
|
|
18
|
+
return walk(value, segments, 0)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Coerce an array of unknown values (typically from evaluateJsonPath) to
|
|
23
|
+
* strings, skipping null/undefined. Primitives are stringified; objects
|
|
24
|
+
* are JSON-encoded.
|
|
25
|
+
*/
|
|
26
|
+
export function stringifyPathResults(raw: unknown[]): string[] {
|
|
27
|
+
const out: string[] = []
|
|
28
|
+
for (const v of raw) {
|
|
29
|
+
if (v === null || v === undefined) continue
|
|
30
|
+
if (typeof v === "string") out.push(v)
|
|
31
|
+
else if (typeof v === "number" || typeof v === "boolean") out.push(String(v))
|
|
32
|
+
else out.push(JSON.stringify(v))
|
|
33
|
+
}
|
|
34
|
+
return out
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function walk(value: unknown, segments: string[], offset: number): unknown[] {
|
|
38
|
+
if (Array.isArray(value)) {
|
|
39
|
+
const results: unknown[] = []
|
|
40
|
+
for (const item of value) {
|
|
41
|
+
for (const v of walk(item, segments, offset)) results.push(v)
|
|
42
|
+
}
|
|
43
|
+
return results
|
|
44
|
+
}
|
|
45
|
+
if (offset >= segments.length) return [value]
|
|
46
|
+
if (value !== null && typeof value === "object") {
|
|
47
|
+
const head = segments[offset]
|
|
48
|
+
if (!Object.hasOwn(value as object, head)) return []
|
|
49
|
+
return walk((value as Record<string, unknown>)[head], segments, offset + 1)
|
|
50
|
+
}
|
|
51
|
+
return []
|
|
52
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lip-sync per-second pricing helpers.
|
|
3
|
+
*
|
|
4
|
+
* KIE.ai Kling AI Avatar 2.0 (May 2026) raised the max input audio from 15s
|
|
5
|
+
* to 5 minutes and bills per-second. We bucket the requested audio duration
|
|
6
|
+
* into discrete tiers and use composite credit identifiers
|
|
7
|
+
* (e.g. `kling-avatar:60s`) so credit reservation scales with input length.
|
|
8
|
+
*
|
|
9
|
+
* InfiniTalk still enforces a 15s upstream cap.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export const LIP_SYNC_DURATION_BUCKETS = [15, 30, 60, 120, 300] as const
|
|
13
|
+
|
|
14
|
+
export type LipSyncDurationBucket = typeof LIP_SYNC_DURATION_BUCKETS[number]
|
|
15
|
+
|
|
16
|
+
/** Round a measured audio duration up to the next supported bucket. */
|
|
17
|
+
export function pickLipSyncBucket(seconds: number): LipSyncDurationBucket {
|
|
18
|
+
if (!Number.isFinite(seconds) || seconds <= 0) return LIP_SYNC_DURATION_BUCKETS[0]
|
|
19
|
+
for (const b of LIP_SYNC_DURATION_BUCKETS) {
|
|
20
|
+
if (seconds <= b) return b
|
|
21
|
+
}
|
|
22
|
+
return 300
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Upper audio-duration limit per lip-sync provider, in seconds.
|
|
27
|
+
* Anything longer must be trimmed (server-side) before sending upstream.
|
|
28
|
+
*/
|
|
29
|
+
export const LIP_SYNC_MAX_AUDIO_SECONDS: Record<string, number> = {
|
|
30
|
+
"kling-avatar": 300,
|
|
31
|
+
"kling-avatar-pro": 300,
|
|
32
|
+
"infinitalk": 15,
|
|
33
|
+
// OmniHuman 1.5 — KIE hard limit is < 60s; audio is auto-trimmed to this cap.
|
|
34
|
+
"omnihuman-1-5": 60,
|
|
35
|
+
// HeyGen Lipsync Precision + Sync Lipsync 2 Pro — billed per output second.
|
|
36
|
+
// 5-min ceiling for credit bucketing (mirrors kling-avatar). MUST be set:
|
|
37
|
+
// the default fallback below is 15s, which would clamp every request to the
|
|
38
|
+
// 15s bucket and silently under-charge long clips.
|
|
39
|
+
"heygen-lipsync-precision": 300,
|
|
40
|
+
"lipsync-2-pro": 300,
|
|
41
|
+
// Sync Lipsync v3 (fal.ai) — per-second dubbing, 5-min ceiling for bucketing.
|
|
42
|
+
"sync-lipsync-v3": 300,
|
|
43
|
+
// Volcengine (KIE) — video-to-video dubbing, per-second, 5-min ceiling.
|
|
44
|
+
"volcengine-lipsync": 300,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function getLipSyncMaxAudioSeconds(provider: string): number {
|
|
48
|
+
return LIP_SYNC_MAX_AUDIO_SECONDS[provider] ?? 15
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Providers whose credit cost varies with audio duration. */
|
|
52
|
+
const PER_SECOND_LIP_SYNC_PROVIDERS = new Set([
|
|
53
|
+
"kling-avatar",
|
|
54
|
+
"kling-avatar-pro",
|
|
55
|
+
// OmniHuman 1.5 — /sec, bucketed by audio length (60s cap).
|
|
56
|
+
"omnihuman-1-5",
|
|
57
|
+
"heygen-lipsync-precision",
|
|
58
|
+
"lipsync-2-pro",
|
|
59
|
+
// Sync Lipsync v3 (fal.ai) — billed per output second (/min).
|
|
60
|
+
"sync-lipsync-v3",
|
|
61
|
+
// Volcengine (KIE) — video-to-video dubbing, billed per output second.
|
|
62
|
+
"volcengine-lipsync",
|
|
63
|
+
])
|
|
64
|
+
|
|
65
|
+
export function isPerSecondLipSyncProvider(provider: string): boolean {
|
|
66
|
+
return PER_SECOND_LIP_SYNC_PROVIDERS.has(provider)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Build the credit identifier for a lip-sync request.
|
|
71
|
+
*
|
|
72
|
+
* For kling-avatar / kling-avatar-pro, returns `<provider>:<bucket>s`.
|
|
73
|
+
* For other providers, returns the bare provider name (caller appends
|
|
74
|
+
* any extra dimensions like resolution).
|
|
75
|
+
*
|
|
76
|
+
* If `audioDurationSec` is missing, defaults to the provider's max cap so
|
|
77
|
+
* credit reservation covers the worst case and any overage is refunded
|
|
78
|
+
* after the worker reconciles the actual KIE cost.
|
|
79
|
+
*/
|
|
80
|
+
export function buildLipSyncCreditId(
|
|
81
|
+
provider: string,
|
|
82
|
+
audioDurationSec: number | undefined,
|
|
83
|
+
): string {
|
|
84
|
+
if (!isPerSecondLipSyncProvider(provider)) return provider
|
|
85
|
+
const cap = getLipSyncMaxAudioSeconds(provider)
|
|
86
|
+
const requested = audioDurationSec === undefined || audioDurationSec <= 0
|
|
87
|
+
? cap
|
|
88
|
+
: Math.min(audioDurationSec, cap)
|
|
89
|
+
return `${provider}:${pickLipSyncBucket(requested)}s`
|
|
90
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { tryParseJson } from "./filter-condition.js"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Element-wise merge across upstream lists with modulo-wrap.
|
|
5
|
+
*
|
|
6
|
+
* Used by the `merge-lists` node's "zip" mode. Each output position i takes
|
|
7
|
+
* `lists[j][i % lists[j].length]` from every input; JSON objects are spread
|
|
8
|
+
* into a single object (later sources override earlier ones on key conflicts),
|
|
9
|
+
* non-object items are stringified and concatenated.
|
|
10
|
+
*
|
|
11
|
+
* Length semantics mirror the DAG's existing fan-out behavior: the result
|
|
12
|
+
* length equals `max(len)` across non-empty inputs. Shorter inputs cycle.
|
|
13
|
+
* This means a single-item upstream (e.g. a JSON-process node emitting one
|
|
14
|
+
* object, or a list with one row) is automatically injected into every
|
|
15
|
+
* element of a longer list.
|
|
16
|
+
*/
|
|
17
|
+
export function zipMergeLists(lists: ReadonlyArray<ReadonlyArray<string>>): string[] {
|
|
18
|
+
const nonEmpty = lists.filter((l) => l.length > 0)
|
|
19
|
+
if (nonEmpty.length === 0) return []
|
|
20
|
+
if (nonEmpty.length === 1) return [...nonEmpty[0]]
|
|
21
|
+
|
|
22
|
+
const maxLen = Math.max(...nonEmpty.map((l) => l.length))
|
|
23
|
+
const out: string[] = []
|
|
24
|
+
for (let i = 0; i < maxLen; i++) {
|
|
25
|
+
const merged: Record<string, unknown> = {}
|
|
26
|
+
const rawItems: string[] = []
|
|
27
|
+
let hasObject = false
|
|
28
|
+
for (const list of nonEmpty) {
|
|
29
|
+
const item = list[i % list.length]
|
|
30
|
+
rawItems.push(item)
|
|
31
|
+
const parsed = tryParseJson(item)
|
|
32
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
33
|
+
hasObject = true
|
|
34
|
+
Object.assign(merged, parsed as Record<string, unknown>)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
out.push(hasObject ? JSON.stringify(merged) : rawItems.join(""))
|
|
38
|
+
}
|
|
39
|
+
return out
|
|
40
|
+
}
|
package/src/list-sort.ts
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/** Pure sort helper shared by the frontend DAG executor and the backend
|
|
2
|
+
* orchestrator. Contract: reorder stringified list items (as produced by
|
|
3
|
+
* `collectUpstreamListItems` peers) by an optional dot-path field, with
|
|
4
|
+
* Auto/Text/Number/Date comparison and asc/desc direction. Items whose
|
|
5
|
+
* key is missing, empty, or uncoerceable under the chosen type are always
|
|
6
|
+
* appended last — regardless of direction. */
|
|
7
|
+
|
|
8
|
+
import { evaluateJsonPath } from "./json-path.js"
|
|
9
|
+
import { tryParseJson } from "./filter-condition.js"
|
|
10
|
+
|
|
11
|
+
export type SortType = "auto" | "text" | "number" | "date"
|
|
12
|
+
export type SortDirection = "asc" | "desc"
|
|
13
|
+
|
|
14
|
+
export interface SortListOptions {
|
|
15
|
+
field: string
|
|
16
|
+
sortType: SortType
|
|
17
|
+
direction: SortDirection
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Natural numeric-aware, case-insensitive locale compare. Declared once so
|
|
21
|
+
// both the Text branch and the Auto fallback reuse the same Collator.
|
|
22
|
+
const textCollator = new Intl.Collator(undefined, {
|
|
23
|
+
sensitivity: "base",
|
|
24
|
+
numeric: true,
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
interface KeyedItem {
|
|
28
|
+
item: string
|
|
29
|
+
key: number | string
|
|
30
|
+
keyKind: "number" | "text" | "invalid"
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function extractRawKey(item: string, field: string): unknown {
|
|
34
|
+
if (field === "") return item
|
|
35
|
+
const parsed = tryParseJson(item)
|
|
36
|
+
const matches = evaluateJsonPath(parsed, field)
|
|
37
|
+
return matches.length > 0 ? matches[0] : undefined
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function isMissing(v: unknown): boolean {
|
|
41
|
+
if (v === undefined || v === null) return true
|
|
42
|
+
if (typeof v === "string" && v.trim() === "") return true
|
|
43
|
+
return false
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function toNumberKey(v: unknown): number | undefined {
|
|
47
|
+
if (typeof v === "number") return Number.isFinite(v) ? v : undefined
|
|
48
|
+
if (typeof v === "boolean") return v ? 1 : 0
|
|
49
|
+
if (typeof v === "string") {
|
|
50
|
+
const trimmed = v.trim()
|
|
51
|
+
if (trimmed === "") return undefined
|
|
52
|
+
const n = Number(trimmed)
|
|
53
|
+
return Number.isFinite(n) ? n : undefined
|
|
54
|
+
}
|
|
55
|
+
return undefined
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function toDateKey(v: unknown): number | undefined {
|
|
59
|
+
if (v instanceof Date) {
|
|
60
|
+
const t = v.getTime()
|
|
61
|
+
return Number.isFinite(t) ? t : undefined
|
|
62
|
+
}
|
|
63
|
+
if (typeof v === "number") return Number.isFinite(v) ? v : undefined
|
|
64
|
+
if (typeof v === "string") {
|
|
65
|
+
const t = Date.parse(v)
|
|
66
|
+
return Number.isFinite(t) ? t : undefined
|
|
67
|
+
}
|
|
68
|
+
return undefined
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function coerceKey(raw: unknown, sortType: SortType): Pick<KeyedItem, "key" | "keyKind"> {
|
|
72
|
+
if (isMissing(raw)) return { key: "", keyKind: "invalid" }
|
|
73
|
+
|
|
74
|
+
if (sortType === "number") {
|
|
75
|
+
const n = toNumberKey(raw)
|
|
76
|
+
return n === undefined
|
|
77
|
+
? { key: "", keyKind: "invalid" }
|
|
78
|
+
: { key: n, keyKind: "number" }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (sortType === "date") {
|
|
82
|
+
const d = toDateKey(raw)
|
|
83
|
+
return d === undefined
|
|
84
|
+
? { key: "", keyKind: "invalid" }
|
|
85
|
+
: { key: d, keyKind: "number" }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (sortType === "text") {
|
|
89
|
+
return { key: typeof raw === "string" ? raw : JSON.stringify(raw), keyKind: "text" }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// auto: try number → date → text.
|
|
93
|
+
const asNum = toNumberKey(raw)
|
|
94
|
+
if (asNum !== undefined) return { key: asNum, keyKind: "number" }
|
|
95
|
+
const asDate = toDateKey(raw)
|
|
96
|
+
if (asDate !== undefined) return { key: asDate, keyKind: "number" }
|
|
97
|
+
return { key: typeof raw === "string" ? raw : JSON.stringify(raw), keyKind: "text" }
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function sortListItems(items: readonly string[], opts: SortListOptions): string[] {
|
|
101
|
+
const { field, sortType, direction } = opts
|
|
102
|
+
const keyed: KeyedItem[] = items.map((item) => {
|
|
103
|
+
const raw = extractRawKey(item, field)
|
|
104
|
+
const { key, keyKind } = coerceKey(raw, sortType)
|
|
105
|
+
return { item, key, keyKind }
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
const sortable = keyed.filter((k) => k.keyKind !== "invalid")
|
|
109
|
+
const invalid = keyed.filter((k) => k.keyKind === "invalid")
|
|
110
|
+
|
|
111
|
+
const factor = direction === "desc" ? -1 : 1
|
|
112
|
+
// Array.prototype.sort is guaranteed stable since ES2019 — equal keys
|
|
113
|
+
// preserve their relative input order.
|
|
114
|
+
sortable.sort((a, b) => {
|
|
115
|
+
if (a.keyKind === "number" && b.keyKind === "number") {
|
|
116
|
+
return ((a.key as number) - (b.key as number)) * factor
|
|
117
|
+
}
|
|
118
|
+
return textCollator.compare(String(a.key), String(b.key)) * factor
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
return [...sortable.map((k) => k.item), ...invalid.map((k) => k.item)]
|
|
122
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Follow a list node's `in` edge chain to its effective upstream source.
|
|
3
|
+
*
|
|
4
|
+
* When a `list` node has no manual rows but an incoming connection, the UI
|
|
5
|
+
* renders its upstream's items (see list-node.tsx's `connectedItems ??
|
|
6
|
+
* staticItems`). Without this helper, downstream readers would see an empty
|
|
7
|
+
* list because `extractSourceNodeOutput` for `list` only reads `data.rows`.
|
|
8
|
+
*
|
|
9
|
+
* Shared between the frontend DAG executor (`node-input-resolver.ts`) and
|
|
10
|
+
* the backend orchestrator (`inline-executor.ts`) so both paths resolve
|
|
11
|
+
* list-connected-mode identically.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
type EdgeShape = {
|
|
15
|
+
source: string
|
|
16
|
+
target: string
|
|
17
|
+
sourceHandle?: string | null
|
|
18
|
+
targetHandle?: string | null
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
type NodeShape = {
|
|
22
|
+
id: string
|
|
23
|
+
type?: string
|
|
24
|
+
data: Record<string, unknown>
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function hasAnyRow(rows: string[][]): boolean {
|
|
28
|
+
return rows.some((r) => r.some((v) => v?.trim()))
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function resolveSourceThroughConnectedList<
|
|
32
|
+
E extends EdgeShape,
|
|
33
|
+
N extends NodeShape,
|
|
34
|
+
>(edge: E, nodes: ReadonlyArray<N>, edges: ReadonlyArray<E>): E {
|
|
35
|
+
const visited = new Set<string>()
|
|
36
|
+
let current = edge
|
|
37
|
+
while (true) {
|
|
38
|
+
const src = nodes.find((n) => n.id === current.source)
|
|
39
|
+
if (!src || src.type !== "list") return current
|
|
40
|
+
if (visited.has(src.id)) return current
|
|
41
|
+
visited.add(src.id)
|
|
42
|
+
const rows = (src.data.rows as string[][] | undefined) ?? []
|
|
43
|
+
if (hasAnyRow(rows)) return current
|
|
44
|
+
const inEdge = edges.find(
|
|
45
|
+
(e) => e.target === src.id && e.targetHandle === "in",
|
|
46
|
+
)
|
|
47
|
+
if (!inEdge) return current
|
|
48
|
+
current = inEdge
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLM Model Registry — shared between frontend and backend.
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for available chat/LLM models routed through KIE.ai,
|
|
5
|
+
* with direct Anthropic SDK fallback for Claude models. This is the
|
|
6
|
+
* NON-monetary side of the registry (model ids, capabilities, tiers,
|
|
7
|
+
* feature defaults). The provider-$ per-token rate table and the
|
|
8
|
+
* `calculateLlmCost` formula derived from it live in
|
|
9
|
+
* `backend/src/lib/pricing/llm-cost.ts` (core, not ee/ — internal LLM cost
|
|
10
|
+
* logging needs them regardless of edition). They were moved out of this
|
|
11
|
+
* package (published Apache-2.0 on npm — an irrevocable grant) per the
|
|
12
|
+
* 2026-07-06 public-flip IP audit, S5: "Keep the model-id enum; strip prices
|
|
13
|
+
* + measurement notes."
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export type LlmTier = "economy" | "standard" | "premium"
|
|
17
|
+
export type KieApiFormat = "chat-completions" | "messages" | "responses"
|
|
18
|
+
|
|
19
|
+
export interface LlmModelDef {
|
|
20
|
+
id: string
|
|
21
|
+
displayName: string
|
|
22
|
+
desc: string
|
|
23
|
+
tier: LlmTier
|
|
24
|
+
kieFormat: KieApiFormat
|
|
25
|
+
/** For chat-completions: the slug prefix (e.g. "gemini-3-flash").
|
|
26
|
+
* For messages: the model id sent in the body (e.g. "claude-haiku-4-5-v1messages").
|
|
27
|
+
* For responses: the model id sent in the body (e.g. "gpt-5-4"). */
|
|
28
|
+
kieSlugOrModel: string
|
|
29
|
+
vendor: "anthropic" | "google" | "openai"
|
|
30
|
+
supportsImages: boolean
|
|
31
|
+
maxOutputTokens: number
|
|
32
|
+
/**
|
|
33
|
+
* How this model can be forced into schema-valid structured output.
|
|
34
|
+
* - "anthropic-tool" → direct Anthropic SDK forced tool_choice (guaranteed).
|
|
35
|
+
* - "kie-response-format" → KIE chat-completions `response_format: json_schema`
|
|
36
|
+
* (verified enforced for Gemini via KIE).
|
|
37
|
+
* - undefined → no native structured mode (GPT-via-KIE doesn't honor
|
|
38
|
+
* response_format); callers fall back to parse + retry.
|
|
39
|
+
* Capability-driven so `llmCompleteStructured` never hardcodes provider ids.
|
|
40
|
+
*/
|
|
41
|
+
structuredOutputMode?: "anthropic-tool" | "kie-response-format"
|
|
42
|
+
/** If set, fallback to direct Anthropic SDK with this model ID when KIE.ai fails */
|
|
43
|
+
directFallbackModel?: string
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const LLM_MODELS: readonly LlmModelDef[] = [
|
|
47
|
+
{
|
|
48
|
+
id: "gemini-3-flash",
|
|
49
|
+
displayName: "Gemini 3 Flash",
|
|
50
|
+
desc: "Fast and cheap, good for simple tasks",
|
|
51
|
+
tier: "economy",
|
|
52
|
+
kieFormat: "chat-completions",
|
|
53
|
+
kieSlugOrModel: "gemini-3-flash",
|
|
54
|
+
vendor: "google",
|
|
55
|
+
structuredOutputMode: "kie-response-format",
|
|
56
|
+
supportsImages: true,
|
|
57
|
+
maxOutputTokens: 8192,
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: "claude-haiku-4.5",
|
|
61
|
+
displayName: "Claude Haiku 4.5",
|
|
62
|
+
desc: "Fast economy, good reasoning",
|
|
63
|
+
tier: "economy",
|
|
64
|
+
kieFormat: "messages",
|
|
65
|
+
kieSlugOrModel: "claude-haiku-4-5",
|
|
66
|
+
vendor: "anthropic",
|
|
67
|
+
structuredOutputMode: "anthropic-tool",
|
|
68
|
+
supportsImages: true,
|
|
69
|
+
maxOutputTokens: 8192,
|
|
70
|
+
directFallbackModel: "claude-haiku-4-5-20251001",
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: "claude-sonnet-4.6",
|
|
74
|
+
displayName: "Claude Sonnet 4.6",
|
|
75
|
+
desc: "Balanced quality and speed",
|
|
76
|
+
tier: "standard",
|
|
77
|
+
kieFormat: "messages",
|
|
78
|
+
kieSlugOrModel: "claude-sonnet-4-6",
|
|
79
|
+
vendor: "anthropic",
|
|
80
|
+
structuredOutputMode: "anthropic-tool",
|
|
81
|
+
supportsImages: true,
|
|
82
|
+
maxOutputTokens: 16384,
|
|
83
|
+
directFallbackModel: "claude-sonnet-4-6",
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
id: "gpt-5.2",
|
|
87
|
+
displayName: "GPT-5.2",
|
|
88
|
+
desc: "Strong general purpose",
|
|
89
|
+
tier: "standard",
|
|
90
|
+
kieFormat: "chat-completions",
|
|
91
|
+
kieSlugOrModel: "gpt-5-2",
|
|
92
|
+
vendor: "openai",
|
|
93
|
+
supportsImages: true,
|
|
94
|
+
maxOutputTokens: 16384,
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
id: "gemini-3.1-pro",
|
|
98
|
+
displayName: "Gemini 3.1 Pro",
|
|
99
|
+
desc: "Advanced reasoning, large context",
|
|
100
|
+
tier: "premium",
|
|
101
|
+
kieFormat: "chat-completions",
|
|
102
|
+
kieSlugOrModel: "gemini-3.1-pro",
|
|
103
|
+
vendor: "google",
|
|
104
|
+
structuredOutputMode: "kie-response-format",
|
|
105
|
+
supportsImages: true,
|
|
106
|
+
maxOutputTokens: 16384,
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
id: "claude-opus-4.7",
|
|
110
|
+
displayName: "Claude Opus 4.7",
|
|
111
|
+
desc: "Highest quality, complex tasks",
|
|
112
|
+
tier: "premium",
|
|
113
|
+
kieFormat: "messages",
|
|
114
|
+
kieSlugOrModel: "claude-opus-4-7",
|
|
115
|
+
vendor: "anthropic",
|
|
116
|
+
structuredOutputMode: "anthropic-tool",
|
|
117
|
+
supportsImages: true,
|
|
118
|
+
maxOutputTokens: 16384,
|
|
119
|
+
directFallbackModel: "claude-opus-4-7",
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
id: "gpt-5.4",
|
|
123
|
+
displayName: "GPT-5.4",
|
|
124
|
+
desc: "Latest GPT, premium quality",
|
|
125
|
+
tier: "premium",
|
|
126
|
+
kieFormat: "responses",
|
|
127
|
+
kieSlugOrModel: "gpt-5-4",
|
|
128
|
+
vendor: "openai",
|
|
129
|
+
supportsImages: true,
|
|
130
|
+
maxOutputTokens: 16384,
|
|
131
|
+
},
|
|
132
|
+
] as const
|
|
133
|
+
|
|
134
|
+
export const LLM_MODEL_IDS = LLM_MODELS.map((m) => m.id)
|
|
135
|
+
|
|
136
|
+
/** Vision models that can return GUARANTEED structured output — the
|
|
137
|
+
* describe-to-picker analyzer forces a schema over an image, so its model
|
|
138
|
+
* pickers AND the backend route gate offer exactly these: Anthropic (forced
|
|
139
|
+
* tool) + Gemini (KIE `response_format`). GPT-via-KIE has no native structured
|
|
140
|
+
* mode (parse+retry only), so it's excluded. Single source of truth so the
|
|
141
|
+
* picker, the config panel, and the route gate can't drift. */
|
|
142
|
+
export const STRUCTURED_VISION_MODELS = LLM_MODELS.filter(
|
|
143
|
+
(m) => m.supportsImages && m.structuredOutputMode != null,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
export type LlmFeature =
|
|
147
|
+
| "ai-writer"
|
|
148
|
+
| "llm-chat"
|
|
149
|
+
| "prompt-helper"
|
|
150
|
+
| "scene-graph-ai"
|
|
151
|
+
| "after-effects"
|
|
152
|
+
| "motion-graphics"
|
|
153
|
+
| "motion-graphics-lottie"
|
|
154
|
+
| "lottie-overlay"
|
|
155
|
+
| "3d-title"
|
|
156
|
+
| "image-to-text"
|
|
157
|
+
| "describe-to-picker"
|
|
158
|
+
| "qa-check"
|
|
159
|
+
| "generate-script"
|
|
160
|
+
| "translate"
|
|
161
|
+
| "image-critic"
|
|
162
|
+
|
|
163
|
+
/** Engine-dependent LlmFeature for the motion-graphics node (design §8: every credit-id site must branch on engine). */
|
|
164
|
+
export function motionGraphicsFeature(engine?: string): LlmFeature {
|
|
165
|
+
return engine === "lottie" ? "motion-graphics-lottie" : "motion-graphics"
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Feature → default model when user hasn't selected one */
|
|
169
|
+
export const LLM_FEATURE_DEFAULTS: Record<LlmFeature, string> = {
|
|
170
|
+
"ai-writer": "claude-sonnet-4.6",
|
|
171
|
+
"llm-chat": "gemini-3-flash",
|
|
172
|
+
"prompt-helper": "gemini-3-flash",
|
|
173
|
+
"scene-graph-ai": "claude-sonnet-4.6",
|
|
174
|
+
"after-effects": "claude-sonnet-4.6",
|
|
175
|
+
"motion-graphics": "claude-sonnet-4.6",
|
|
176
|
+
"motion-graphics-lottie": "claude-sonnet-4.6",
|
|
177
|
+
"lottie-overlay": "claude-sonnet-4.6",
|
|
178
|
+
"3d-title": "claude-sonnet-4.6",
|
|
179
|
+
"image-to-text": "claude-sonnet-4.6",
|
|
180
|
+
"describe-to-picker": "claude-opus-4.7",
|
|
181
|
+
"qa-check": "claude-sonnet-4.6",
|
|
182
|
+
"generate-script": "gemini-3-flash",
|
|
183
|
+
"translate": "gemini-3-flash",
|
|
184
|
+
"image-critic": "claude-sonnet-4.6",
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Per-model multimodal input capabilities. Drives both frontend UI gating
|
|
189
|
+
* and backend route-level filtering for the LLM Chat node references.
|
|
190
|
+
*
|
|
191
|
+
* As of 2026-05: Claude Messages API supports text + image only (no audio,
|
|
192
|
+
* no video). Gemini 2/3 family supports image + video + audio natively.
|
|
193
|
+
* GPT-5.x via KIE chat-completions / responses supports image only — audio
|
|
194
|
+
* input requires a separate audio-capable model we don't route to today.
|
|
195
|
+
*/
|
|
196
|
+
export const LLM_MODALITY_CAPS: Record<string, { image: boolean; video: boolean; audio: boolean }> = {
|
|
197
|
+
"gemini-3-flash": { image: true, video: true, audio: true },
|
|
198
|
+
"gemini-3.1-pro": { image: true, video: true, audio: true },
|
|
199
|
+
"claude-haiku-4.5": { image: true, video: false, audio: false },
|
|
200
|
+
"claude-sonnet-4.6": { image: true, video: false, audio: false },
|
|
201
|
+
"claude-opus-4.7": { image: true, video: false, audio: false },
|
|
202
|
+
"gpt-5.2": { image: true, video: false, audio: false },
|
|
203
|
+
"gpt-5.4": { image: true, video: false, audio: false },
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Capability lookup with safe default — unknown models get image-only. */
|
|
207
|
+
export function getLlmModalityCaps(modelId: string | undefined): { image: boolean; video: boolean; audio: boolean } {
|
|
208
|
+
if (!modelId) return { image: true, video: false, audio: false }
|
|
209
|
+
return LLM_MODALITY_CAPS[modelId] ?? { image: true, video: false, audio: false }
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function getLlmModel(id: string): LlmModelDef | undefined {
|
|
213
|
+
return LLM_MODELS.find((m) => m.id === id)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function getLlmTier(id: string): LlmTier {
|
|
217
|
+
return getLlmModel(id)?.tier ?? "standard"
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Build a composite credit identifier for an LLM feature.
|
|
222
|
+
* - economy tier → "ai-writer:economy"
|
|
223
|
+
* - standard tier → "ai-writer" (no suffix — backward compatible)
|
|
224
|
+
* - premium tier → "ai-writer:premium"
|
|
225
|
+
*/
|
|
226
|
+
export function buildLlmCreditIdentifier(feature: string, modelId?: string): string {
|
|
227
|
+
if (!modelId) return feature
|
|
228
|
+
const tier = getLlmTier(modelId)
|
|
229
|
+
if (tier === "standard") return feature
|
|
230
|
+
return `${feature}:${tier}`
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Resolve llmModel from raw body for creditGuard preHandler (before Zod parsing).
|
|
235
|
+
* Returns the credit identifier for the given feature + optional model.
|
|
236
|
+
*/
|
|
237
|
+
export function resolveLlmCreditId(feature: string, body: unknown): string {
|
|
238
|
+
const llmModel = (body as Record<string, unknown>)?.llmModel as string | undefined
|
|
239
|
+
return buildLlmCreditIdentifier(feature, llmModel)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** Models capable of video-analysis: capability-derived, never hand-listed (route-enum-sync convention). */
|
|
243
|
+
export const VIDEO_ANALYSIS_LLM_MODELS: string[] = LLM_MODELS
|
|
244
|
+
.filter((m) => getLlmModalityCaps(m.id).video && getLlmModalityCaps(m.id).audio)
|
|
245
|
+
.map((m) => m.id)
|