@nodaro/shared 3.10.0 → 3.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +2242 -86
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1779 -32
- package/dist/index.d.ts +1779 -32
- package/dist/index.js +2041 -87
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/caption-styles.test.ts +207 -0
- package/src/__tests__/edl-multicam.test.ts +304 -0
- package/src/__tests__/edl.test.ts +822 -0
- package/src/__tests__/fan-out-rows.test.ts +208 -0
- package/src/__tests__/instagram-scrape.test.ts +66 -0
- package/src/__tests__/llm-models.test.ts +48 -11
- package/src/__tests__/meta-ads-scrape.test.ts +284 -0
- package/src/__tests__/node-runtime-keys.test.ts +15 -0
- package/src/__tests__/parameter-node-value.test.ts +13 -1
- package/src/__tests__/presentation-utils.test.ts +67 -0
- package/src/__tests__/producer-types.test.ts +19 -0
- package/src/__tests__/schedule-rules.test.ts +265 -0
- package/src/__tests__/speaker-layouts.test.ts +203 -0
- package/src/__tests__/transcribe-capabilities.test.ts +104 -0
- package/src/__tests__/transcribe-preflight.test.ts +60 -0
- package/src/__tests__/trigger-feeds.test.ts +39 -0
- package/src/__tests__/video-analysis.test.ts +15 -0
- package/src/__tests__/video-duration-auto.test.ts +65 -0
- package/src/__tests__/video-duration.test.ts +56 -0
- package/src/__tests__/video-frame-fit.test.ts +189 -0
- package/src/__tests__/video-link.test.ts +137 -0
- package/src/__tests__/workflow-export-strip.test.ts +59 -1
- package/src/caption-styles.ts +240 -0
- package/src/catalog-projection.ts +3 -0
- package/src/character-motion-metadata.ts +19 -0
- package/src/credit-identifiers.ts +31 -0
- package/src/edit-plan-contract.ts +96 -0
- package/src/edl-multicam.ts +185 -0
- package/src/edl.ts +747 -0
- package/src/entity-image-handle.ts +24 -1
- package/src/fan-out-rows.ts +213 -0
- package/src/i18n/character-motion.ar.ts +126 -75
- package/src/i18n/character-motion.de.ts +126 -75
- package/src/i18n/character-motion.es.ts +126 -75
- package/src/i18n/character-motion.fr.ts +126 -75
- package/src/i18n/character-motion.he.ts +126 -75
- package/src/i18n/character-motion.hi.ts +126 -75
- package/src/i18n/character-motion.ja.ts +126 -75
- package/src/i18n/character-motion.ko.ts +126 -75
- package/src/i18n/character-motion.pt-BR.ts +126 -75
- package/src/i18n/character-motion.ru.ts +126 -75
- package/src/i18n/character-motion.zh-CN.ts +126 -75
- package/src/index.ts +211 -3
- package/src/instagram-scrape.ts +204 -0
- package/src/llm-models.ts +80 -3
- package/src/meta-ads-scrape.ts +463 -0
- package/src/model-catalog.ts +48 -5
- package/src/model-constants.ts +148 -5
- package/src/node-mappable-fields.ts +2 -0
- package/src/node-runtime-keys.ts +28 -0
- package/src/parameter-node-value.ts +31 -5
- package/src/presentation-utils.ts +49 -0
- package/src/producer-types.ts +20 -0
- package/src/schedule-rules.ts +484 -0
- package/src/speaker-layouts.ts +220 -0
- package/src/transcribe-preflight.ts +101 -0
- package/src/trigger-feeds.ts +59 -0
- package/src/trigger-node-types.ts +20 -0
- package/src/video-analysis.ts +15 -0
- package/src/video-duration-auto.ts +18 -0
- package/src/video-duration.ts +32 -0
- package/src/video-frame-fit.ts +228 -0
- package/src/video-link.ts +167 -0
- package/src/video-output-canvas.ts +119 -0
- package/src/workflow-export.ts +37 -1
package/src/index.ts
CHANGED
|
@@ -71,6 +71,10 @@ export {
|
|
|
71
71
|
TEXT_TO_VIDEO_PROVIDERS,
|
|
72
72
|
VIDEO_GEN_PROVIDERS,
|
|
73
73
|
VIDEO_TO_VIDEO_PROVIDERS,
|
|
74
|
+
SEEDANCE_VIDEO_EDIT_PROVIDERS,
|
|
75
|
+
isSeedanceVideoEditProvider,
|
|
76
|
+
VIDEO_TO_VIDEO_NODE_PROVIDERS,
|
|
77
|
+
SEEDANCE_VIDEO_EDIT_SHAPE,
|
|
74
78
|
FACE_SWAP_PROVIDERS,
|
|
75
79
|
VIDEO_UPSCALE_PROVIDERS,
|
|
76
80
|
EXTEND_VIDEO_PROVIDERS,
|
|
@@ -85,6 +89,12 @@ export {
|
|
|
85
89
|
TEXT_TO_AUDIO_PROVIDERS,
|
|
86
90
|
MUSIC_PROVIDERS,
|
|
87
91
|
TRANSCRIBE_PROVIDERS,
|
|
92
|
+
TRANSCRIBE_LANES,
|
|
93
|
+
TRANSCRIBE_PROVIDER_CAPABILITIES,
|
|
94
|
+
transcribeProvidersWithWordTimestamps,
|
|
95
|
+
transcribeLaneSupportsWordTimestamps,
|
|
96
|
+
DEFAULT_TRANSCRIBE_PROVIDER,
|
|
97
|
+
DEFAULT_TRANSCRIBE_NODE_PROVIDER,
|
|
88
98
|
SCRIPT_PROVIDERS,
|
|
89
99
|
AI_WRITER_PROVIDERS,
|
|
90
100
|
QA_CHECK_PROVIDERS,
|
|
@@ -168,6 +178,8 @@ export {
|
|
|
168
178
|
applyDefaultVideoSelection,
|
|
169
179
|
PRICING_DEFAULT_DURATION_SEC,
|
|
170
180
|
pricedOutputDurationSec,
|
|
181
|
+
supportsAutoVideoDuration,
|
|
182
|
+
maxVideoDurationSec,
|
|
171
183
|
PRICING_DEFAULT_RESOLUTION,
|
|
172
184
|
} from "./model-constants.js"
|
|
173
185
|
|
|
@@ -196,6 +208,8 @@ export type {
|
|
|
196
208
|
VideoGenProvider,
|
|
197
209
|
VideoModeAlias,
|
|
198
210
|
VideoToVideoProvider,
|
|
211
|
+
SeedanceVideoEditProvider,
|
|
212
|
+
VideoToVideoNodeProvider,
|
|
199
213
|
VideoUpscaleProvider,
|
|
200
214
|
ExtendVideoProvider,
|
|
201
215
|
FaceSwapProvider,
|
|
@@ -205,6 +219,7 @@ export type {
|
|
|
205
219
|
TextToAudioProvider,
|
|
206
220
|
MusicProvider,
|
|
207
221
|
TranscribeProvider,
|
|
222
|
+
TranscribeLane,
|
|
208
223
|
ScriptProvider,
|
|
209
224
|
AiWriterProvider,
|
|
210
225
|
QaCheckProvider,
|
|
@@ -251,6 +266,7 @@ export {
|
|
|
251
266
|
resolveImageGenCreditIdentifier,
|
|
252
267
|
resolveNormalizedImageGen,
|
|
253
268
|
buildVideoCreditModelIdentifier,
|
|
269
|
+
seedanceVideoEditCreditId,
|
|
254
270
|
pricedVideoSelection,
|
|
255
271
|
buildMotionCreditModelIdentifier,
|
|
256
272
|
sunoCreditType,
|
|
@@ -261,7 +277,7 @@ export {
|
|
|
261
277
|
export type { NormalizedImageGen, PricedVideoSelection } from "./credit-identifiers.js"
|
|
262
278
|
|
|
263
279
|
export * from "./credit-estimators/index.js"
|
|
264
|
-
export { extractVideoDurationFromNode } from "./video-duration.js"
|
|
280
|
+
export { extractVideoDurationFromNode, editPlanSourceDurationSec } from "./video-duration.js"
|
|
265
281
|
|
|
266
282
|
export {
|
|
267
283
|
resolveTopazUpscale,
|
|
@@ -284,6 +300,7 @@ export {
|
|
|
284
300
|
getNodeResult,
|
|
285
301
|
getNodeLabel,
|
|
286
302
|
getInputFieldSchema,
|
|
303
|
+
mergeNodeInputOverrides,
|
|
287
304
|
flattenItems,
|
|
288
305
|
migrateToItems,
|
|
289
306
|
validateNoNestedGroups,
|
|
@@ -357,6 +374,8 @@ export {
|
|
|
357
374
|
ADVANCED_MODE_UNAVAILABLE_REASON,
|
|
358
375
|
motionGraphicsFeature,
|
|
359
376
|
effectiveReasoningEffort,
|
|
377
|
+
REASONING_OUTPUT_FLOOR,
|
|
378
|
+
reasoningOutputFloor,
|
|
360
379
|
type LlmTier,
|
|
361
380
|
type LlmFeature,
|
|
362
381
|
type KieApiFormat,
|
|
@@ -426,6 +445,18 @@ export type {
|
|
|
426
445
|
|
|
427
446
|
export { REPEATABLE_NODE_TYPES, REPEAT_PLACEHOLDER, PROVIDER_PLACEHOLDER_PREFIX, encodeProviderItem, decodeProviderItem, getEffectiveRepeatCount, expandItemsWithRepeat } from "./repeat-types.js"
|
|
428
447
|
|
|
448
|
+
export {
|
|
449
|
+
NON_PROMPT_TEXT_LANES,
|
|
450
|
+
fanOutTextFeedsPrompt,
|
|
451
|
+
isFanOutUrlItem,
|
|
452
|
+
compactWithRows,
|
|
453
|
+
liveRowColumn,
|
|
454
|
+
resolveListFanOut,
|
|
455
|
+
planFanOut,
|
|
456
|
+
alignedFieldList,
|
|
457
|
+
} from "./fan-out-rows.js"
|
|
458
|
+
export type { FanOutCandidate, ListFanOut, FanOutPlan } from "./fan-out-rows.js"
|
|
459
|
+
|
|
429
460
|
export { settledWithLimit } from "./settled-with-limit.js"
|
|
430
461
|
|
|
431
462
|
|
|
@@ -611,6 +642,39 @@ export {
|
|
|
611
642
|
} from "./node-default-mappings.js"
|
|
612
643
|
|
|
613
644
|
export { NODE_MAPPABLE_FIELDS, SUNO_FIELD_HANDLE_FIELDS, fieldKeyFromHandle } from "./node-mappable-fields.js"
|
|
645
|
+
export {
|
|
646
|
+
SCHEDULE_TRIGGER_NODE_TYPE,
|
|
647
|
+
WEBHOOK_TRIGGER_NODE_TYPE,
|
|
648
|
+
TELEGRAM_TRIGGER_NODE_TYPE,
|
|
649
|
+
PROJECTED_TRIGGER_NODE_TYPES,
|
|
650
|
+
isProjectedTriggerNodeType,
|
|
651
|
+
} from "./trigger-node-types.js"
|
|
652
|
+
|
|
653
|
+
export { buildFeedMaps, nodeFeedsAnything, type FeedNode, type FeedEdge, type FeedMaps } from "./trigger-feeds.js"
|
|
654
|
+
|
|
655
|
+
export {
|
|
656
|
+
SCHEDULE_RULE_KINDS,
|
|
657
|
+
SCHEDULE_EVERY_LIMITS,
|
|
658
|
+
isCronExpression,
|
|
659
|
+
isValidTimezone,
|
|
660
|
+
normalizeScheduleRule,
|
|
661
|
+
normalizeScheduleRules,
|
|
662
|
+
legacyScheduleToRules,
|
|
663
|
+
localTimeIn,
|
|
664
|
+
localMinuteKey,
|
|
665
|
+
timezoneOffsetMinutes,
|
|
666
|
+
matchesCronField,
|
|
667
|
+
matchesCron,
|
|
668
|
+
ruleMatches,
|
|
669
|
+
scheduleMatchesAt,
|
|
670
|
+
scheduleOccurrences,
|
|
671
|
+
nextScheduleRuns,
|
|
672
|
+
previewHorizonMs,
|
|
673
|
+
type ScheduleRuleKind,
|
|
674
|
+
type ScheduleRule,
|
|
675
|
+
type ScheduleSpec,
|
|
676
|
+
type LocalTime,
|
|
677
|
+
} from "./schedule-rules.js"
|
|
614
678
|
|
|
615
679
|
|
|
616
680
|
export {
|
|
@@ -622,6 +686,101 @@ export {
|
|
|
622
686
|
type ScraperActorId,
|
|
623
687
|
} from "./scraper-actors.js"
|
|
624
688
|
|
|
689
|
+
export {
|
|
690
|
+
META_ADS_SCRAPE_NODE_TYPE,
|
|
691
|
+
META_ADS_SCRAPE_MODES,
|
|
692
|
+
META_ADS_SCRAPE_PERIODS,
|
|
693
|
+
META_ADS_SCRAPE_STATUSES,
|
|
694
|
+
META_ADS_PLATFORMS,
|
|
695
|
+
isMetaAdsPlatform,
|
|
696
|
+
type MetaAdsPlatform,
|
|
697
|
+
META_ADS_FORMATS,
|
|
698
|
+
isMetaAdsFormat,
|
|
699
|
+
classifyCreativeFormat,
|
|
700
|
+
clampMetaAdsFeaturedIndex,
|
|
701
|
+
featuredMetaAdOutputs,
|
|
702
|
+
type FeaturedMetaAdOutputs,
|
|
703
|
+
type MetaAdsFormat,
|
|
704
|
+
type MetaAdsCreativeFormat,
|
|
705
|
+
META_ADS_SCRAPE_COUNT_OPTIONS,
|
|
706
|
+
META_ADS_SCRAPE_DEFAULT_COUNT,
|
|
707
|
+
META_ADS_SCRAPE_MAX_COUNT,
|
|
708
|
+
META_ADS_SCRAPE_MAX_SOURCES,
|
|
709
|
+
META_ADS_SCRAPE_MAX_QUERY_LENGTH,
|
|
710
|
+
META_ADS_SCRAPE_DEFAULT_COUNTRY,
|
|
711
|
+
META_ADS_SCRAPE_TIERS,
|
|
712
|
+
META_ADS_SCRAPE_CREDIT_COSTS,
|
|
713
|
+
META_ADS_SCRAPE_FALLBACK_CREDIT_ID,
|
|
714
|
+
splitMetaAdsPageUrls,
|
|
715
|
+
isMetaAdsScrapeMode,
|
|
716
|
+
isMetaAdsScrapeCount,
|
|
717
|
+
metaAdsScrapeTier,
|
|
718
|
+
buildMetaAdsScrapeCreditId,
|
|
719
|
+
resolveMetaAdsScrapeCreditId,
|
|
720
|
+
META_ADS_NODE_MODES,
|
|
721
|
+
META_ADS_ADVERTISER_MAX_RESULTS,
|
|
722
|
+
META_ADS_ANALYSIS_TIERS,
|
|
723
|
+
META_ADS_ANALYSIS_CREDITS_PER_AD,
|
|
724
|
+
META_ADS_ANALYSIS_CREDIT_ID,
|
|
725
|
+
META_ADS_ANALYSIS_FOCUS_MAX,
|
|
726
|
+
metaAdsAnalysisCreditId,
|
|
727
|
+
metaAdsAnalysisTier,
|
|
728
|
+
metaAdsAnalysisTierFrom,
|
|
729
|
+
adCreativeAnalysisFrom,
|
|
730
|
+
metaAdsScrapeCreditIdFromNode,
|
|
731
|
+
type MetaAdsAnalysisTier,
|
|
732
|
+
type AdCreativeAnalysis,
|
|
733
|
+
type MetaAdsNodeQuoteFields,
|
|
734
|
+
metaAdsNodeMode,
|
|
735
|
+
isFacebookPageUrl,
|
|
736
|
+
isMetaCdnImageUrl,
|
|
737
|
+
splitMetaAdsAdvertiserNames,
|
|
738
|
+
metaAdsAdvertisersFrom,
|
|
739
|
+
metaAdsScrapeSources,
|
|
740
|
+
metaAdsScrapeWireSources,
|
|
741
|
+
type MetaAdsNodeMode,
|
|
742
|
+
type MetaAdsAdvertiser,
|
|
743
|
+
type MetaAdsNodeSourceFields,
|
|
744
|
+
type MetaAdsWireSources,
|
|
745
|
+
type MetaAdsScrapeMode,
|
|
746
|
+
type MetaAdsScrapePeriod,
|
|
747
|
+
type MetaAdsScrapeStatus,
|
|
748
|
+
type MetaAdsScrapeTier,
|
|
749
|
+
} from "./meta-ads-scrape.js"
|
|
750
|
+
|
|
751
|
+
export {
|
|
752
|
+
INSTAGRAM_SCRAPE_NODE_TYPE,
|
|
753
|
+
INSTAGRAM_SCRAPE_MODES,
|
|
754
|
+
INSTAGRAM_SCRAPE_PERIODS,
|
|
755
|
+
INSTAGRAM_SCRAPE_DEFAULT_COUNT,
|
|
756
|
+
INSTAGRAM_SCRAPE_MAX_COUNT,
|
|
757
|
+
INSTAGRAM_SCRAPE_MAX_SOURCES,
|
|
758
|
+
INSTAGRAM_SCRAPE_MAX_TARGET_LENGTH,
|
|
759
|
+
INSTAGRAM_SCRAPE_TIERS,
|
|
760
|
+
INSTAGRAM_SCRAPE_CREDIT_COSTS,
|
|
761
|
+
INSTAGRAM_SCRAPE_FALLBACK_CREDIT_ID,
|
|
762
|
+
INSTAGRAM_ANALYSIS_CREDIT_ID,
|
|
763
|
+
isInstagramScrapeMode,
|
|
764
|
+
instagramScrapeMode,
|
|
765
|
+
isInstagramScrapeCount,
|
|
766
|
+
instagramScrapeTier,
|
|
767
|
+
instagramAnalysisTierFrom,
|
|
768
|
+
instagramAnalysisCreditId,
|
|
769
|
+
buildInstagramScrapeCreditId,
|
|
770
|
+
resolveInstagramScrapeCreditId,
|
|
771
|
+
instagramScrapeSources,
|
|
772
|
+
instagramScrapeCreditIdFromNode,
|
|
773
|
+
splitInstagramTargets,
|
|
774
|
+
clampInstagramFeaturedIndex,
|
|
775
|
+
featuredInstagramOutputs,
|
|
776
|
+
type InstagramScrapeMode,
|
|
777
|
+
type InstagramScrapePeriod,
|
|
778
|
+
type InstagramScrapeTier,
|
|
779
|
+
type InstagramFormat,
|
|
780
|
+
type FeaturedInstagramOutputs,
|
|
781
|
+
type InstagramNodeQuoteFields,
|
|
782
|
+
} from "./instagram-scrape.js"
|
|
783
|
+
|
|
625
784
|
export { VARIABLES_HANDLE_ID, buildConditionVariables } from "./condition-variables.js"
|
|
626
785
|
|
|
627
786
|
export { extractAllGeneratedResults, extractGeneratedJsonAsList, spreadJsonArrayIfSingleton } from "./generated-results.js"
|
|
@@ -782,8 +941,27 @@ export {
|
|
|
782
941
|
KINETIC_CAPTION_STYLES,
|
|
783
942
|
ALL_CAPTION_STYLES,
|
|
784
943
|
isKineticCaptionStyle,
|
|
944
|
+
CAPTION_LOOK_IDS,
|
|
945
|
+
CAPTION_LOOKS,
|
|
946
|
+
DEFAULT_CAPTION_LOOK,
|
|
947
|
+
DEFAULT_SUBTITLE_LOOK,
|
|
948
|
+
KINETIC_ONLY_CAPTION_LEVER_KEYS,
|
|
949
|
+
captionRoutesToRemotion,
|
|
950
|
+
CAPTION_MAX_WORDS_PER_LINE_MIN,
|
|
951
|
+
CAPTION_MAX_WORDS_PER_LINE_MAX,
|
|
952
|
+
CAPTION_LEVER_BOUNDS,
|
|
953
|
+
normalizeCaptionNumericLevers,
|
|
954
|
+
autoStrokeWidth,
|
|
955
|
+
resolveCaptionLook,
|
|
956
|
+
resolveCaptionLevers,
|
|
785
957
|
} from "./caption-styles.js"
|
|
786
|
-
export type { StaticCaptionStyle, KineticCaptionStyle, CaptionStyle } from "./caption-styles.js"
|
|
958
|
+
export type { StaticCaptionStyle, KineticCaptionStyle, CaptionStyle, CaptionLookId, CaptionLookLevers, KineticOnlyCaptionLeverKey } from "./caption-styles.js"
|
|
959
|
+
|
|
960
|
+
export {
|
|
961
|
+
transcribeWordTimestampsRefusal,
|
|
962
|
+
findWordlessTranscriptFeeds,
|
|
963
|
+
} from "./transcribe-preflight.js"
|
|
964
|
+
export type { PreflightGraphNode, PreflightGraphEdge, WordlessTranscriptFeed } from "./transcribe-preflight.js"
|
|
787
965
|
|
|
788
966
|
// Sound parameter-node dimensions (music + voice pickers + backend hints)
|
|
789
967
|
|
|
@@ -822,7 +1000,7 @@ export type {
|
|
|
822
1000
|
WorkflowImportSkippedAsset,
|
|
823
1001
|
WorkflowAssetKind,
|
|
824
1002
|
} from "./workflow-export.js"
|
|
825
|
-
export { stripExportContent } from "./workflow-export.js"
|
|
1003
|
+
export { stripExportContent, stripUnownedRefs } from "./workflow-export.js"
|
|
826
1004
|
|
|
827
1005
|
export { validateSubWorkflowRoutes } from "./sub-workflow-validation.js"
|
|
828
1006
|
|
|
@@ -972,6 +1150,22 @@ export {
|
|
|
972
1150
|
|
|
973
1151
|
export { SUNO_TRACK_SOURCE_TYPES } from "./suno-track-sources.js"
|
|
974
1152
|
|
|
1153
|
+
// --- Video URL node + social-video import (host allowlist, node output rule) ---
|
|
1154
|
+
export {
|
|
1155
|
+
SOCIAL_VIDEO_HOSTS,
|
|
1156
|
+
YOUTUBE_HOSTS,
|
|
1157
|
+
INSTAGRAM_HOSTS,
|
|
1158
|
+
VIDEO_LINK_TOLERANT_CONSUMER_TYPES,
|
|
1159
|
+
hostnameMatchesAllowlist,
|
|
1160
|
+
hasUrlParserHazard,
|
|
1161
|
+
isSocialVideoUrl,
|
|
1162
|
+
detectVideoLinkPlatform,
|
|
1163
|
+
videoLinkDownloadedFile,
|
|
1164
|
+
resolveVideoLinkOutput,
|
|
1165
|
+
videoLinkNeedsDownload,
|
|
1166
|
+
} from "./video-link.js"
|
|
1167
|
+
export type { VideoLinkPlatform, VideoLinkNodeFields } from "./video-link.js"
|
|
1168
|
+
|
|
975
1169
|
export {
|
|
976
1170
|
VOICE_CHANGER_MODELS,
|
|
977
1171
|
VOICE_CHANGER_MODEL_IDS,
|
|
@@ -1109,3 +1303,17 @@ export * from "./scene3d-v2-edit.js"
|
|
|
1109
1303
|
export { STUDIO_DEPENDENT_FRAMES_CAPABILITY, SequenceExecutionRequiredError, requiresSequenceExecution, assertCanvasExecutionAllowed } from "./sequence-execution"
|
|
1110
1304
|
export * from "./scene3d-authoring-engine.js"
|
|
1111
1305
|
export * from "./scene3d-input-assets.js"
|
|
1306
|
+
|
|
1307
|
+
// --- Start/end frame fit: measured output canvases + the pure fit math ---
|
|
1308
|
+
export * from "./video-output-canvas.js"
|
|
1309
|
+
export * from "./video-frame-fit.js"
|
|
1310
|
+
export type { CharacterMotionMetadata } from "./character-motion-metadata.js"
|
|
1311
|
+
|
|
1312
|
+
// --- EDL: the edit decision list contract (podcast editing primitives).
|
|
1313
|
+
// Types + validators + pure remap/duration functions; structural only. ---
|
|
1314
|
+
export * from "./edl.js"
|
|
1315
|
+
export * from "./speaker-layouts.js"
|
|
1316
|
+
export * from "./edl-multicam.js"
|
|
1317
|
+
export * from "./edit-plan-contract.js"
|
|
1318
|
+
|
|
1319
|
+
export { VIDEO_DURATION_AUTO, isAutoVideoDuration } from "./video-duration-auto.js"
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Instagram scraper node — shared vocabulary + credit identifiers.
|
|
3
|
+
*
|
|
4
|
+
* Pulls PUBLIC Instagram posts (feed images, carousels, reels) by profile or
|
|
5
|
+
* by hashtag and emits a normalized JSON array. Same shape of contract as the
|
|
6
|
+
* Meta Ads node: everything the backend guard/reservation, the frontend credit
|
|
7
|
+
* badge and the docs formula must agree on lives here.
|
|
8
|
+
*
|
|
9
|
+
* Pricing: 1 credit per REQUESTED post, rounded UP to a fixed tier of
|
|
10
|
+
* `count × sources` (a profile / hashtag is one source, up to 5). Optional
|
|
11
|
+
* per-post AI analysis folds into the same identifier, priced by the model's
|
|
12
|
+
* tier — reusing the Meta analysis per-item values so the two nodes stay in
|
|
13
|
+
* lockstep.
|
|
14
|
+
*/
|
|
15
|
+
import {
|
|
16
|
+
META_ADS_ANALYSIS_CREDITS_PER_AD,
|
|
17
|
+
META_ADS_ANALYSIS_TIERS,
|
|
18
|
+
metaAdsAnalysisTier,
|
|
19
|
+
type MetaAdsAnalysisTier,
|
|
20
|
+
} from "./meta-ads-scrape.js"
|
|
21
|
+
import { classifyCreativeFormat, type MetaAdsFormat } from "./meta-ads-scrape.js"
|
|
22
|
+
|
|
23
|
+
export const INSTAGRAM_SCRAPE_NODE_TYPE = "instagram-scrape" as const
|
|
24
|
+
|
|
25
|
+
export const INSTAGRAM_SCRAPE_MODES = ["profile", "hashtag"] as const
|
|
26
|
+
export type InstagramScrapeMode = (typeof INSTAGRAM_SCRAPE_MODES)[number]
|
|
27
|
+
|
|
28
|
+
export function isInstagramScrapeMode(value: unknown): value is InstagramScrapeMode {
|
|
29
|
+
return typeof value === "string" && (INSTAGRAM_SCRAPE_MODES as readonly string[]).includes(value)
|
|
30
|
+
}
|
|
31
|
+
export function instagramScrapeMode(value: unknown): InstagramScrapeMode {
|
|
32
|
+
return isInstagramScrapeMode(value) ? value : "profile"
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Same window vocabulary as Meta Ads; the Instagram actor honours it server-side (`onlyPostsNewerThan`). */
|
|
36
|
+
export const INSTAGRAM_SCRAPE_PERIODS = ["24h", "7d", "30d", "all"] as const
|
|
37
|
+
export type InstagramScrapePeriod = (typeof INSTAGRAM_SCRAPE_PERIODS)[number]
|
|
38
|
+
|
|
39
|
+
export const INSTAGRAM_SCRAPE_DEFAULT_COUNT = 20
|
|
40
|
+
export const INSTAGRAM_SCRAPE_MAX_COUNT = 100
|
|
41
|
+
export const INSTAGRAM_SCRAPE_MAX_SOURCES = 5
|
|
42
|
+
/** Instagram usernames / hashtags are short; cap a single target well under a URL. */
|
|
43
|
+
export const INSTAGRAM_SCRAPE_MAX_TARGET_LENGTH = 200
|
|
44
|
+
|
|
45
|
+
/** Requested-total buckets — identical shape to Meta (the pricing model is the same). */
|
|
46
|
+
export const INSTAGRAM_SCRAPE_TIERS = [10, 20, 50, 100, 200, 500] as const
|
|
47
|
+
export type InstagramScrapeTier = (typeof INSTAGRAM_SCRAPE_TIERS)[number]
|
|
48
|
+
|
|
49
|
+
export function instagramScrapeTier(requestedTotal: number): InstagramScrapeTier {
|
|
50
|
+
for (const tier of INSTAGRAM_SCRAPE_TIERS) if (requestedTotal <= tier) return tier
|
|
51
|
+
return INSTAGRAM_SCRAPE_TIERS[INSTAGRAM_SCRAPE_TIERS.length - 1]
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** The analysis tier a request / node asks for, or null when analysis is off. */
|
|
55
|
+
export function instagramAnalysisTierFrom(data: { readonly analyze?: unknown; readonly analysisModel?: unknown }): MetaAdsAnalysisTier | null {
|
|
56
|
+
return data.analyze === true ? metaAdsAnalysisTier(data.analysisModel) : null
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function analysisSuffix(tier: MetaAdsAnalysisTier): string {
|
|
60
|
+
return tier === "standard" ? ":analysis" : `:analysis:${tier}`
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Per-post settlement SKU for a tier (the bare id is the standard tier). */
|
|
64
|
+
export const INSTAGRAM_ANALYSIS_CREDIT_ID = "instagram-analysis" as const
|
|
65
|
+
export function instagramAnalysisCreditId(tier: MetaAdsAnalysisTier): string {
|
|
66
|
+
return tier === "standard" ? INSTAGRAM_ANALYSIS_CREDIT_ID : `${INSTAGRAM_ANALYSIS_CREDIT_ID}:${tier}`
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface InstagramScrapeCreditInput {
|
|
70
|
+
count: number
|
|
71
|
+
sources: number
|
|
72
|
+
analysis?: MetaAdsAnalysisTier | null
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function buildInstagramScrapeCreditId(input: InstagramScrapeCreditInput): string {
|
|
76
|
+
const sources = Math.min(Math.max(Math.trunc(input.sources) || 1, 1), INSTAGRAM_SCRAPE_MAX_SOURCES)
|
|
77
|
+
const count = Math.min(Math.max(Math.trunc(input.count) || 1, 1), INSTAGRAM_SCRAPE_MAX_COUNT)
|
|
78
|
+
const base = `${INSTAGRAM_SCRAPE_NODE_TYPE}:${instagramScrapeTier(count * sources)}`
|
|
79
|
+
return input.analysis ? `${base}${analysisSuffix(input.analysis)}` : base
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Cost per SKU — mirror of the backend `STATIC_CREDIT_COSTS` rows / migration,
|
|
84
|
+
* for the frontend badge / estimator. 1 credit per requested post at every
|
|
85
|
+
* tier, plus the analysis multiples.
|
|
86
|
+
*/
|
|
87
|
+
export const INSTAGRAM_SCRAPE_CREDIT_COSTS: Record<string, number> = (() => {
|
|
88
|
+
const table: Record<string, number> = { [INSTAGRAM_SCRAPE_NODE_TYPE]: 20 }
|
|
89
|
+
for (const tier of META_ADS_ANALYSIS_TIERS) table[instagramAnalysisCreditId(tier)] = META_ADS_ANALYSIS_CREDITS_PER_AD[tier]
|
|
90
|
+
for (const t of INSTAGRAM_SCRAPE_TIERS) {
|
|
91
|
+
table[`${INSTAGRAM_SCRAPE_NODE_TYPE}:${t}`] = t
|
|
92
|
+
for (const tier of META_ADS_ANALYSIS_TIERS) {
|
|
93
|
+
table[`${INSTAGRAM_SCRAPE_NODE_TYPE}:${t}${analysisSuffix(tier)}`] = t * (1 + META_ADS_ANALYSIS_CREDITS_PER_AD[tier])
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return table
|
|
97
|
+
})()
|
|
98
|
+
|
|
99
|
+
export const INSTAGRAM_SCRAPE_FALLBACK_CREDIT_ID = "instagram-scrape:20"
|
|
100
|
+
|
|
101
|
+
export function isInstagramScrapeCount(value: unknown): value is number {
|
|
102
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= INSTAGRAM_SCRAPE_MAX_COUNT
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Targets (profile usernames/URLs or hashtags) are typed one per line; the
|
|
107
|
+
* route wants an array. Unlike page urls a target can be a bare username /
|
|
108
|
+
* `#tag`, so split on lines / commas only (never whitespace), strip a leading
|
|
109
|
+
* `@` or `#`, dedupe, cap at MAX_SOURCES.
|
|
110
|
+
*/
|
|
111
|
+
export function splitInstagramTargets(value: unknown): string[] {
|
|
112
|
+
const raw = Array.isArray(value)
|
|
113
|
+
? value.filter((v): v is string => typeof v === "string")
|
|
114
|
+
: typeof value === "string"
|
|
115
|
+
? value.split(/[\n,]+/)
|
|
116
|
+
: []
|
|
117
|
+
const seen = new Set<string>()
|
|
118
|
+
const out: string[] = []
|
|
119
|
+
for (const item of raw) {
|
|
120
|
+
const t = item.trim().replace(/^[@#]+/, "").trim()
|
|
121
|
+
if (t.length < 1 || t.length > INSTAGRAM_SCRAPE_MAX_TARGET_LENGTH) continue
|
|
122
|
+
const key = t.toLowerCase()
|
|
123
|
+
if (seen.has(key)) continue
|
|
124
|
+
seen.add(key)
|
|
125
|
+
out.push(t)
|
|
126
|
+
if (out.length >= INSTAGRAM_SCRAPE_MAX_SOURCES) break
|
|
127
|
+
}
|
|
128
|
+
return out
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** The featured post index, clamped. */
|
|
132
|
+
export function clampInstagramFeaturedIndex(stored: unknown, count: number): number {
|
|
133
|
+
if (count <= 0) return 0
|
|
134
|
+
const n = typeof stored === "number" && Number.isFinite(stored) ? Math.trunc(stored) : 0
|
|
135
|
+
return Math.min(Math.max(n, 0), count - 1)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export interface FeaturedInstagramOutputs {
|
|
139
|
+
readonly text?: string
|
|
140
|
+
readonly imageUrl?: string
|
|
141
|
+
readonly videoUrl?: string
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function urlStrings(value: unknown): string[] {
|
|
145
|
+
return Array.isArray(value) ? value.filter((v): v is string => typeof v === "string" && v.trim().length > 0) : []
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** The featured post's caption (`text`), first image / cover (`image`), and first video (`video`). */
|
|
149
|
+
export function featuredInstagramOutputs(json: unknown, featuredIndex: unknown): FeaturedInstagramOutputs {
|
|
150
|
+
if (!Array.isArray(json) || json.length === 0) return {}
|
|
151
|
+
const post = json[clampInstagramFeaturedIndex(featuredIndex, json.length)]
|
|
152
|
+
if (!post || typeof post !== "object") return {}
|
|
153
|
+
const p = post as Record<string, unknown>
|
|
154
|
+
const text = typeof p.caption === "string" ? p.caption.trim() : ""
|
|
155
|
+
const imageUrl = urlStrings(p.images)[0] ?? urlStrings(p.videoPreviews)[0]
|
|
156
|
+
const videoUrl = urlStrings(p.videos)[0]
|
|
157
|
+
return {
|
|
158
|
+
...(text ? { text } : {}),
|
|
159
|
+
...(imageUrl ? { imageUrl } : {}),
|
|
160
|
+
...(videoUrl ? { videoUrl } : {}),
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** The node-data fields a quote reads. */
|
|
165
|
+
export interface InstagramNodeQuoteFields {
|
|
166
|
+
readonly [key: string]: unknown
|
|
167
|
+
readonly mode?: unknown
|
|
168
|
+
readonly targets?: unknown
|
|
169
|
+
readonly count?: unknown
|
|
170
|
+
readonly analyze?: unknown
|
|
171
|
+
readonly analysisModel?: unknown
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Billable source count: number of targets (min 1). */
|
|
175
|
+
export function instagramScrapeSources(data: InstagramNodeQuoteFields): number {
|
|
176
|
+
return Math.max(1, Math.min(splitInstagramTargets(data.targets).length, INSTAGRAM_SCRAPE_MAX_SOURCES))
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** The ONE credit identifier for a node's current settings. */
|
|
180
|
+
export function instagramScrapeCreditIdFromNode(data: InstagramNodeQuoteFields): string {
|
|
181
|
+
const count = typeof data.count === "number" ? data.count : INSTAGRAM_SCRAPE_DEFAULT_COUNT
|
|
182
|
+
return buildInstagramScrapeCreditId({ count, sources: instagramScrapeSources(data), analysis: instagramAnalysisTierFrom(data) })
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Resolve the credit identifier from an UNVALIDATED request body (the guard
|
|
187
|
+
* runs before Zod). Lands on the SAME tier the reservation computes.
|
|
188
|
+
*/
|
|
189
|
+
export function resolveInstagramScrapeCreditId(body: unknown): string {
|
|
190
|
+
const raw = body as { count?: unknown; targets?: unknown; analyze?: unknown; analysisModel?: unknown } | null | undefined
|
|
191
|
+
if (!raw || typeof raw !== "object") return INSTAGRAM_SCRAPE_FALLBACK_CREDIT_ID
|
|
192
|
+
const count = raw.count === undefined ? INSTAGRAM_SCRAPE_DEFAULT_COUNT : raw.count
|
|
193
|
+
if (!isInstagramScrapeCount(count)) return INSTAGRAM_SCRAPE_FALLBACK_CREDIT_ID
|
|
194
|
+
// Same splitter the handler uses for `sources` (dedupes, caps at MAX), so the
|
|
195
|
+
// pre-Zod guard and the post-Zod reservation always land on the same tier —
|
|
196
|
+
// a duplicate target bills once, not per copy.
|
|
197
|
+
const sources = splitInstagramTargets(raw.targets).length
|
|
198
|
+
if (sources < 1) return INSTAGRAM_SCRAPE_FALLBACK_CREDIT_ID
|
|
199
|
+
return buildInstagramScrapeCreditId({ count, sources, analysis: instagramAnalysisTierFrom(raw) })
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Re-export the shared creative-format vocabulary so the node imports one place.
|
|
203
|
+
export { classifyCreativeFormat }
|
|
204
|
+
export type InstagramFormat = MetaAdsFormat
|
package/src/llm-models.ts
CHANGED
|
@@ -118,12 +118,37 @@ export interface LlmModelDef {
|
|
|
118
118
|
*
|
|
119
119
|
* Consumers MUST give such a model output headroom regardless of the
|
|
120
120
|
* requested effort, or reasoning silently eats a small legacy cap and the
|
|
121
|
-
* answer truncates with `stop_reason: max_tokens` — a paid-for empty reply
|
|
122
|
-
*
|
|
123
|
-
*
|
|
121
|
+
* answer truncates with `stop_reason: max_tokens` — a paid-for empty reply
|
|
122
|
+
* (since #1588 the client fails such a call rather than return the
|
|
123
|
+
* fragment, but the floor is what keeps it from happening). `deriveParams`
|
|
124
|
+
* (llm-client.ts) floors to {@link reasoningOutputFloor}; the film
|
|
125
|
+
* pipeline's `callLLM` — Anthropic SDK only, so only its Claude members
|
|
126
|
+
* matter — floors at the default. Keep the flag in sync with the vendor's
|
|
124
127
|
* documented default rather than inferring it from the model name.
|
|
125
128
|
*/
|
|
126
129
|
thinkingDefaultOn?: true
|
|
130
|
+
/**
|
|
131
|
+
* The output-token cap a REASONING call on this model is floored to — the
|
|
132
|
+
* room its thinking shares with the answer (`thinkingDefaultOn`, or an
|
|
133
|
+
* xhigh/max effort). Absent = {@link REASONING_OUTPUT_FLOOR}; read it through
|
|
134
|
+
* {@link reasoningOutputFloor}, never directly.
|
|
135
|
+
*
|
|
136
|
+
* Declare it ONLY where a lane serving this model is not known to accept the
|
|
137
|
+
* default: the floor rides every lane the model can be served on (KIE AND its
|
|
138
|
+
* direct fallback), so it has to sit at the intersection of what they take —
|
|
139
|
+
* the rule `maxOutputTokens` already follows for the Gemini flash entries.
|
|
140
|
+
* Never below `maxOutputTokens` (a floor under the default cap is not a
|
|
141
|
+
* floor — guarded by a registry test).
|
|
142
|
+
*/
|
|
143
|
+
reasoningOutputFloor?: number
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** The reasoning floor for a model that declares no lane limit of its own. */
|
|
147
|
+
export const REASONING_OUTPUT_FLOOR = 32768
|
|
148
|
+
|
|
149
|
+
/** The output cap a reasoning call on `model` is floored to (see `LlmModelDef.reasoningOutputFloor`). */
|
|
150
|
+
export function reasoningOutputFloor(model: LlmModelDef): number {
|
|
151
|
+
return model.reasoningOutputFloor ?? REASONING_OUTPUT_FLOOR
|
|
127
152
|
}
|
|
128
153
|
|
|
129
154
|
export const LLM_MODELS: readonly LlmModelDef[] = [
|
|
@@ -145,6 +170,11 @@ export const LLM_MODELS: readonly LlmModelDef[] = [
|
|
|
145
170
|
// No `reasoningEfforts` at all on the KIE lane, but the vendor API accepts
|
|
146
171
|
// the full minimal→high ladder (`none` maps to Google's `minimal`).
|
|
147
172
|
directReasoningEfforts: ["none", "low", "medium", "high"],
|
|
173
|
+
// Reasons with no thinking param sent — Google's Gemini 3 default (dynamic
|
|
174
|
+
// thinking; `minimal` is its floor, never off), measured on 3.6 in #1588.
|
|
175
|
+
// Floored at the KIE-safe 8192, the same intersection as `maxOutputTokens`.
|
|
176
|
+
thinkingDefaultOn: true,
|
|
177
|
+
reasoningOutputFloor: 8192,
|
|
148
178
|
},
|
|
149
179
|
{
|
|
150
180
|
id: "gemini-3.6-flash",
|
|
@@ -177,6 +207,15 @@ export const LLM_MODELS: readonly LlmModelDef[] = [
|
|
|
177
207
|
// Gemini entry — the lane with the lower unit cost wins by default and
|
|
178
208
|
// direct is the reliability fallback only.
|
|
179
209
|
directGeminiModel: "gemini-3.6-flash",
|
|
210
|
+
// Reasons with NO thinking param sent — measured, issue #1588: a Generate
|
|
211
|
+
// Text node capped at 1,100 tokens fell back to the direct lane (KIE 500),
|
|
212
|
+
// spent ~1,060 of them reasoning, and returned 120 characters cut mid-URL.
|
|
213
|
+
// On the same input the KIE runs used ~500 output tokens in all, so only
|
|
214
|
+
// the fallback runs broke — every other run of a 5-minute schedule.
|
|
215
|
+
// Floored at 8192, NOT the default 32768: the floor rides the KIE endpoint
|
|
216
|
+
// too, and 8192 is all it is known to take (see `maxOutputTokens`).
|
|
217
|
+
thinkingDefaultOn: true,
|
|
218
|
+
reasoningOutputFloor: 8192,
|
|
180
219
|
},
|
|
181
220
|
{
|
|
182
221
|
id: "gemini-3.7-flash",
|
|
@@ -205,6 +244,10 @@ export const LLM_MODELS: readonly LlmModelDef[] = [
|
|
|
205
244
|
// Assumed parity with 3.6 pending a live probe on the direct lane.
|
|
206
245
|
directReasoningEfforts: ["none", "low", "medium", "high"],
|
|
207
246
|
directGeminiModel: "gemini-3.7-flash",
|
|
247
|
+
// Gemini 3 default: reasons with no thinking param sent (measured on 3.6,
|
|
248
|
+
// #1588). KIE-safe floor, same intersection as `maxOutputTokens`.
|
|
249
|
+
thinkingDefaultOn: true,
|
|
250
|
+
reasoningOutputFloor: 8192,
|
|
208
251
|
},
|
|
209
252
|
{
|
|
210
253
|
id: "gemini-3.8-flash",
|
|
@@ -245,6 +288,11 @@ export const LLM_MODELS: readonly LlmModelDef[] = [
|
|
|
245
288
|
// KIE-first (no `preferDirect`) — 3.7's posture exactly: the cheap lane
|
|
246
289
|
// serves the A/B, direct is Advanced mode + the reliability fallback.
|
|
247
290
|
directGeminiModel: "gemini-3.8-flash",
|
|
291
|
+
// Gemini 3 default: reasons with no thinking param sent (measured on 3.6,
|
|
292
|
+
// #1588). Floored at its own 16384, inside the 20000 its KIE endpoint was
|
|
293
|
+
// measured to honour.
|
|
294
|
+
thinkingDefaultOn: true,
|
|
295
|
+
reasoningOutputFloor: 16384,
|
|
248
296
|
},
|
|
249
297
|
{
|
|
250
298
|
id: "claude-haiku-4.5",
|
|
@@ -318,6 +366,11 @@ export const LLM_MODELS: readonly LlmModelDef[] = [
|
|
|
318
366
|
// `additionalProperties` (KIE's `response_format` silently DROPS
|
|
319
367
|
// record/map-shaped fields — see the z.record rule in backend/CLAUDE.md).
|
|
320
368
|
preferDirect: true,
|
|
369
|
+
// Reasons with no thinking param sent on both lanes — the proxied endpoint
|
|
370
|
+
// DEFAULTS to "high" (above), and the direct lane reasons harder still.
|
|
371
|
+
// Floored at its own 16384: its KIE fallback is not known to take more.
|
|
372
|
+
thinkingDefaultOn: true,
|
|
373
|
+
reasoningOutputFloor: 16384,
|
|
321
374
|
},
|
|
322
375
|
{
|
|
323
376
|
id: "claude-opus-4.7",
|
|
@@ -413,6 +466,22 @@ export const LLM_MODELS: readonly LlmModelDef[] = [
|
|
|
413
466
|
maxOutputTokens: 16384,
|
|
414
467
|
reasoningEfforts: ["none", "low", "medium", "high", "xhigh", "max"],
|
|
415
468
|
supportsTemperature: false,
|
|
469
|
+
// SERVED COLLAPSED, on the astra precedent (2026-09-17). The 2026-07-14
|
|
470
|
+
// verification that KIE's non-stream responses endpoint serves the GPT-5.6
|
|
471
|
+
// family reliably no longer holds: a live call — a recast continuity
|
|
472
|
+
// review, ~3k tokens, `reasoning.effort: high`, `text.format: json_schema`
|
|
473
|
+
// — came back `500 {"error":{"type":"server_error"}}` / "Server exception,
|
|
474
|
+
// please try again later". Same endpoint family, same dialect and the same
|
|
475
|
+
// signature astra was measured on (12 calls: non-stream 2/6, streaming
|
|
476
|
+
// 5/6; a schema-less non-stream call 500'd too, so the lane is the trigger
|
|
477
|
+
// and not the schema). ONE sighting here rather than a fresh 12-call probe
|
|
478
|
+
// — the precedent is strong and the flag is cheap to reverse.
|
|
479
|
+
//
|
|
480
|
+
// THE COST: SSE does not reliably carry `credits_consumed`, so this model's
|
|
481
|
+
// provider cost becomes the rate-table estimate instead of the billed
|
|
482
|
+
// figure. That is the price of a lane that answers, and it is the same
|
|
483
|
+
// trade astra already makes.
|
|
484
|
+
kieCollapseStream: true,
|
|
416
485
|
},
|
|
417
486
|
{
|
|
418
487
|
id: "gpt-6-astra",
|
|
@@ -649,6 +718,10 @@ export type LlmFeature =
|
|
|
649
718
|
// zero-cost `3d-scene-ops` identifier instead.
|
|
650
719
|
| "3d-scene"
|
|
651
720
|
| "image-to-text"
|
|
721
|
+
// Per-ad creative analysis on the social scraper nodes (Meta Ads first):
|
|
722
|
+
// one structured vision call per ad, priced per REQUESTED ad by tier and
|
|
723
|
+
// folded into the scrape's own identifier (packages/shared/meta-ads-scrape).
|
|
724
|
+
| "meta-ads-analysis"
|
|
652
725
|
| "describe-to-picker"
|
|
653
726
|
| "qa-check"
|
|
654
727
|
| "generate-script"
|
|
@@ -680,6 +753,10 @@ export const LLM_FEATURE_DEFAULTS: Record<LlmFeature, string> = {
|
|
|
680
753
|
"3d-title": "claude-sonnet-4.6",
|
|
681
754
|
"3d-scene": "claude-sonnet-4.6",
|
|
682
755
|
"image-to-text": "claude-sonnet-4.6",
|
|
756
|
+
// Economy on purpose: the analysis reads ONE frame + the copy per ad and
|
|
757
|
+
// runs once per returned ad — volume, not depth. Must stay an image-capable
|
|
758
|
+
// structured-output model (STRUCTURED_VISION_MODELS); a registry test pins it.
|
|
759
|
+
"meta-ads-analysis": "gemini-3.6-flash",
|
|
683
760
|
"describe-to-picker": "claude-opus-5",
|
|
684
761
|
"qa-check": "gemini-3.6-flash",
|
|
685
762
|
"generate-script": "gemini-3.6-flash",
|