@nodaro/shared 1.22.0 → 1.24.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 +137 -42
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +55 -5
- package/dist/index.d.ts +55 -5
- package/dist/index.js +136 -43
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/llm-models.test.ts +19 -0
- package/src/__tests__/video-analysis-catalog-sync.test.ts +47 -0
- package/src/__tests__/video-analysis-pricing.test.ts +6 -1
- package/src/index.ts +3 -0
- package/src/llm-models.ts +80 -6
- package/src/model-catalog.ts +41 -20
- package/src/video-analysis-pricing.ts +24 -16
- package/src/video-analysis.ts +28 -0
package/package.json
CHANGED
|
@@ -678,6 +678,25 @@ describe("advanced mode", () => {
|
|
|
678
678
|
}
|
|
679
679
|
})
|
|
680
680
|
|
|
681
|
+
it("no advanced-capable model declares xhigh/max — the UI copy depends on it", () => {
|
|
682
|
+
// `EFFORT_LABELS` in reasoning-effort-select.tsx labels xhigh/max "may bill
|
|
683
|
+
// one tier up" and its comment explains that the effort bump and the
|
|
684
|
+
// advanced bump can never both apply, because Advanced is Gemini-only and
|
|
685
|
+
// no Gemini model reaches xhigh/max. That was a note asking a future reader
|
|
686
|
+
// to remember; this is the check that fails instead.
|
|
687
|
+
//
|
|
688
|
+
// If this ever goes red, the two bumps CAN stack: reword the labels to say
|
|
689
|
+
// so and revisit whether a double bump is the price we want.
|
|
690
|
+
for (const m of LLM_MODELS) {
|
|
691
|
+
if (!supportsAdvancedMode(m.id)) continue
|
|
692
|
+
const levels = [...(m.reasoningEfforts ?? []), ...(m.directReasoningEfforts ?? [])]
|
|
693
|
+
expect(levels, `${m.id} can run advanced AND declares a tier-bumping effort`)
|
|
694
|
+
.not.toEqual(expect.arrayContaining(["xhigh"]))
|
|
695
|
+
expect(levels, `${m.id} can run advanced AND declares a tier-bumping effort`)
|
|
696
|
+
.not.toEqual(expect.arrayContaining(["max"]))
|
|
697
|
+
}
|
|
698
|
+
})
|
|
699
|
+
|
|
681
700
|
it("never bumps past premium", () => {
|
|
682
701
|
expect(buildLlmCreditIdentifier("llm-chat", "gemini-3.1-pro", "max", true)).toBe("llm-chat:premium")
|
|
683
702
|
})
|
|
@@ -13,7 +13,9 @@ import {
|
|
|
13
13
|
VIDEO_ANALYSIS_BUCKET_CREDITS,
|
|
14
14
|
VIDEO_ANALYSIS_DURATION_BUCKETS,
|
|
15
15
|
VIDEO_ANALYSIS_MAX_DURATION_SEC,
|
|
16
|
+
buildVideoAnalysisCreditId,
|
|
16
17
|
} from "../video-analysis-pricing.js"
|
|
18
|
+
import { DEFAULT_VIDEO_ANALYSIS_MODEL } from "../llm-models.js"
|
|
17
19
|
|
|
18
20
|
/** Every `video-analysis:*` pricing row declared anywhere in the catalog. */
|
|
19
21
|
const catalogRows = Object.values(MODEL_CATALOG)
|
|
@@ -61,3 +63,48 @@ describe("model catalog video-analysis pricing", () => {
|
|
|
61
63
|
}
|
|
62
64
|
})
|
|
63
65
|
})
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The BARE `video-analysis` node-type id — the one every colon-scoped guard misses.
|
|
69
|
+
*
|
|
70
|
+
* `getModelIdentifier` (frontend) has no video-analysis branch, so it falls through
|
|
71
|
+
* to `return nodeType` and the run-credits estimate resolves this exact id. It was
|
|
72
|
+
* seeded at 3 credits in migration 247 with the comment "= flash 600s" and then
|
|
73
|
+
* skipped by FIVE consecutive repricings (248, 259, 273, 275, 276), every one of
|
|
74
|
+
* which matched only `video-analysis:` prefixes — including this test file's own
|
|
75
|
+
* `catalogRows` filter above. A real run reserves 21-200, so the pre-run balance
|
|
76
|
+
* check was quoting 3 for a run it would then refuse.
|
|
77
|
+
*
|
|
78
|
+
* The value is the MAXIMUM of the table, not the default tier at the ceiling
|
|
79
|
+
* bucket: this test's first version pinned the latter and failed immediately,
|
|
80
|
+
* because the default (pro) tops out at 120 while `mixed` reaches 200. Reached only
|
|
81
|
+
* when BOTH model and duration are unknown, so it has to bound every row.
|
|
82
|
+
*/
|
|
83
|
+
describe("bare video-analysis node-type credit id", () => {
|
|
84
|
+
it("bounds EVERY bucketed row — an unknown-duration estimate must never UNDER-quote", () => {
|
|
85
|
+
const ceiling = Math.max(...Object.values(VIDEO_ANALYSIS_BUCKET_CREDITS))
|
|
86
|
+
for (const [id, credits] of Object.entries(VIDEO_ANALYSIS_BUCKET_CREDITS)) {
|
|
87
|
+
expect(credits, `${id} exceeds the bare-id ceiling ${ceiling}`).toBeLessThanOrEqual(ceiling)
|
|
88
|
+
}
|
|
89
|
+
// The migration writes this number; keep them in lockstep (277 wrote 200,
|
|
90
|
+
// 279 wrote 739, 280 writes 346 — every tier now dispatches one identical
|
|
91
|
+
// analyzer pass, so the whole schedule flattened and the ceiling fell with it).
|
|
92
|
+
expect(ceiling).toBe(346)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it("the bare id still bounds the default tier at the ceiling bucket", () => {
|
|
96
|
+
// Pins the reason the bare id is the table MAX rather than the intuitive
|
|
97
|
+
// "default model, longest video" value, so nobody re-derives it wrongly.
|
|
98
|
+
//
|
|
99
|
+
// Was `toBeLessThan`, on the premise that `mixed` cost strictly more than the
|
|
100
|
+
// default tier. That premise is gone: all tiers resolve to the same single
|
|
101
|
+
// pass, so every row at a given bucket is equal and the correct assertion is
|
|
102
|
+
// the INVARIANT (the bare id must never under-quote), not the old strict gap.
|
|
103
|
+
// Should tiers ever diverge again this still holds, and the ceiling check
|
|
104
|
+
// above catches any row that outgrows it.
|
|
105
|
+
const defaultAtCeiling = VIDEO_ANALYSIS_BUCKET_CREDITS[
|
|
106
|
+
buildVideoAnalysisCreditId(DEFAULT_VIDEO_ANALYSIS_MODEL, VIDEO_ANALYSIS_MAX_DURATION_SEC)
|
|
107
|
+
]!
|
|
108
|
+
expect(defaultAtCeiling).toBeLessThanOrEqual(Math.max(...Object.values(VIDEO_ANALYSIS_BUCKET_CREDITS)))
|
|
109
|
+
})
|
|
110
|
+
})
|
|
@@ -84,9 +84,14 @@ describe("video-analysis-pricing", () => {
|
|
|
84
84
|
expect(DEFAULT_VIDEO_ANALYSIS_TIER).toBe("pro")
|
|
85
85
|
expect(DEFAULT_VIDEO_ANALYSIS_MODEL).toBe("gemini-3.1-pro")
|
|
86
86
|
expect(resolveVideoAnalysisModel("pro")).toBe("gemini-3.1-pro")
|
|
87
|
-
|
|
87
|
+
// The economy tiers moved to the cheaper flash generation when they moved to
|
|
88
|
+
// the cheaper transport; the newer flash now sits behind the `smart` sentinel.
|
|
89
|
+
expect(resolveVideoAnalysisModel("fast")).toBe("gemini-3-flash")
|
|
88
90
|
expect(resolveVideoAnalysisModel("mixed")).toBe("mixed") // roll-plan sentinel passthrough
|
|
89
91
|
expect(resolveVideoAnalysisModel("mixed-fast")).toBe("mixed-fast")
|
|
92
|
+
// `smart` is a sentinel too — it names an engine plan, never a model id, so the
|
|
93
|
+
// engine behind it stays unpublished exactly as the mixed plans' does.
|
|
94
|
+
expect(resolveVideoAnalysisModel("smart")).toBe("smart")
|
|
90
95
|
// Raw passthrough keeps LEGACY stored configs running (and priced) on the
|
|
91
96
|
// exact model they were saved with — never silently re-tiered.
|
|
92
97
|
expect(resolveVideoAnalysisModel("gemini-3-flash")).toBe("gemini-3-flash")
|
package/src/index.ts
CHANGED
|
@@ -279,6 +279,9 @@ export {
|
|
|
279
279
|
resolveLlmCreditId,
|
|
280
280
|
supportsAdvancedMode,
|
|
281
281
|
availableReasoningEfforts,
|
|
282
|
+
LLM_ROUTE_DEFAULTS,
|
|
283
|
+
llmRouteDefaults,
|
|
284
|
+
type LlmRouteDefaults,
|
|
282
285
|
ADVANCED_MODE_UNAVAILABLE_REASON,
|
|
283
286
|
motionGraphicsFeature,
|
|
284
287
|
effectiveReasoningEffort,
|
package/src/llm-models.ts
CHANGED
|
@@ -212,6 +212,17 @@ export const LLM_MODELS: readonly LlmModelDef[] = [
|
|
|
212
212
|
// Google documents low/medium/high for 3.1 Pro — no `minimal` tier, so
|
|
213
213
|
// this ladder is deliberately shorter than the flash models'.
|
|
214
214
|
directReasoningEfforts: ["low", "medium", "high"],
|
|
215
|
+
// NO `reasoningEfforts` (the proxied ladder) ON PURPOSE, not an omission.
|
|
216
|
+
// That endpoint accepts `reasoning_effort: low | high` and already DEFAULTS
|
|
217
|
+
// to "high", so leaving it unset gets its deepest setting and a ladder here
|
|
218
|
+
// would unlock nothing. The two lanes are NOT equivalent at their maxima:
|
|
219
|
+
// verified 2026-07-29 on identical input, the proxy's ceiling produces
|
|
220
|
+
// roughly a quarter of the reasoning tokens that the direct lane's
|
|
221
|
+
// `thinkingLevel: HIGH` does, and it does not expose Google's deeper tiers
|
|
222
|
+
// at all. That difference is visible in fine-detail reading (small on-screen
|
|
223
|
+
// text such as a name badge), which is exactly what video-analysis casts
|
|
224
|
+
// identities from — so adding an entry here would silently move analysis to
|
|
225
|
+
// the shallower lane. Don't. Full comparison lives with the analysis engine.
|
|
215
226
|
// The ONE Gemini model routed direct-first. It is the premium/low-volume
|
|
216
227
|
// tier (video-analysis `pro`, no LLM_FEATURE_DEFAULTS entry), so the ~4×
|
|
217
228
|
// list-price premium lands on the smallest call volume — and it is where
|
|
@@ -550,6 +561,49 @@ export function effectiveReasoningEffort(
|
|
|
550
561
|
* premium stays premium). `high` is the Claude-family server default and
|
|
551
562
|
* never bumps.
|
|
552
563
|
*/
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* The sampling defaults each LLM feature's route sends when Advanced mode is
|
|
567
|
+
* OFF — and therefore the values its Advanced panel must seed the sliders with.
|
|
568
|
+
*
|
|
569
|
+
* SINGLE SOURCE because the two used to disagree: the routes hardcoded their
|
|
570
|
+
* own literals while the toggle fell back to 0.7/2048, so the panel displayed
|
|
571
|
+
* a temperature the run never used, and one arrow-key press on 3D Title's Max
|
|
572
|
+
* Tokens silently cut its budget from 3072 to 2048 on a node that emits
|
|
573
|
+
* structured JSON.
|
|
574
|
+
*
|
|
575
|
+
* `structuredOutput` marks the features whose prompt asks the model for JSON —
|
|
576
|
+
* a high temperature measurably degrades schema adherence there, so the panel
|
|
577
|
+
* warns. Absent `temperature` means the route sends none (vendor default).
|
|
578
|
+
*/
|
|
579
|
+
export interface LlmRouteDefaults {
|
|
580
|
+
temperature?: number
|
|
581
|
+
maxTokens?: number
|
|
582
|
+
structuredOutput?: true
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
export const LLM_ROUTE_DEFAULTS: Record<string, LlmRouteDefaults> = {
|
|
586
|
+
"llm-chat": { temperature: 0.7, maxTokens: 8192 },
|
|
587
|
+
"ai-writer": { temperature: 0.7, maxTokens: 8192 },
|
|
588
|
+
"prompt-helper": { temperature: 0.7, maxTokens: 8192, structuredOutput: true },
|
|
589
|
+
"generate-script": { maxTokens: 16384, structuredOutput: true },
|
|
590
|
+
"qa-check": { maxTokens: 1024, structuredOutput: true },
|
|
591
|
+
"image-critic": { maxTokens: 1024, structuredOutput: true },
|
|
592
|
+
"image-to-text": { maxTokens: 1024 },
|
|
593
|
+
"describe-to-picker": { structuredOutput: true },
|
|
594
|
+
"scene-graph-ai": { temperature: 0.3, maxTokens: 4096, structuredOutput: true },
|
|
595
|
+
"after-effects": { temperature: 0.3, maxTokens: 2048, structuredOutput: true },
|
|
596
|
+
"lottie-overlay": { temperature: 0.3, maxTokens: 2048, structuredOutput: true },
|
|
597
|
+
"motion-graphics": { temperature: 0.3, maxTokens: 2048, structuredOutput: true },
|
|
598
|
+
"motion-graphics-lottie": { temperature: 0.3, maxTokens: 8192, structuredOutput: true },
|
|
599
|
+
"3d-title": { temperature: 0.4, maxTokens: 3072, structuredOutput: true },
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/** Route defaults for a feature; `{}` for an unknown one. */
|
|
603
|
+
export function llmRouteDefaults(feature: string | undefined): LlmRouteDefaults {
|
|
604
|
+
return (feature && LLM_ROUTE_DEFAULTS[feature]) || {}
|
|
605
|
+
}
|
|
606
|
+
|
|
553
607
|
/** One step up the economy → standard → premium ladder. Premium is the ceiling. */
|
|
554
608
|
function bumpTier(tier: LlmTier): LlmTier {
|
|
555
609
|
if (tier === "economy") return "standard"
|
|
@@ -623,7 +677,7 @@ export const VIDEO_ANALYSIS_LLM_MODELS: string[] = LLM_MODELS
|
|
|
623
677
|
* VIDEO_ANALYSIS_LLM_MODELS member AND every such model is reachable by a tier,
|
|
624
678
|
* so adding a video model forces a tier decision instead of silently leaking.
|
|
625
679
|
*/
|
|
626
|
-
export const VIDEO_ANALYSIS_TIERS = { fast: "gemini-3
|
|
680
|
+
export const VIDEO_ANALYSIS_TIERS = { fast: "gemini-3-flash", pro: "gemini-3.1-pro" } as const
|
|
627
681
|
export type VideoAnalysisModelTier = keyof typeof VIDEO_ANALYSIS_TIERS
|
|
628
682
|
/**
|
|
629
683
|
* Models that PREVIOUSLY backed a tier, mapped to the tier they backed.
|
|
@@ -637,8 +691,13 @@ export type VideoAnalysisModelTier = keyof typeof VIDEO_ANALYSIS_TIERS
|
|
|
637
691
|
* two-sided decision.
|
|
638
692
|
*/
|
|
639
693
|
export const VIDEO_ANALYSIS_LEGACY_MODELS: Record<string, VideoAnalysisModelTier> = {
|
|
640
|
-
// fast tier
|
|
641
|
-
|
|
694
|
+
// Backed the fast tier from 2026-07 until 2026-07-29, when the economy tiers
|
|
695
|
+
// moved to the cheaper lane and the older, cheaper flash. It is now the engine
|
|
696
|
+
// behind the `smart` tier — but `smart` is a SENTINEL that never exposes a model
|
|
697
|
+
// id, so as far as this map is concerned it no longer backs a selectable tier and
|
|
698
|
+
// belongs here: stored raw `llmModel` values keep resolving and keep pricing
|
|
699
|
+
// under their own credit family.
|
|
700
|
+
"gemini-3.6-flash": "fast",
|
|
642
701
|
}
|
|
643
702
|
/**
|
|
644
703
|
* MIXED tiers — advanced multi-engine analysis plans whose identifier resolves
|
|
@@ -649,15 +708,30 @@ export const VIDEO_ANALYSIS_LEGACY_MODELS: Record<string, VideoAnalysisModelTier
|
|
|
649
708
|
* NOT published here (Apache irrevocability; only the wire vocabulary below is
|
|
650
709
|
* contract — the engine lives in the private analysis plugin).
|
|
651
710
|
*/
|
|
652
|
-
export const VIDEO_ANALYSIS_MIXED_TIERS = ["mixed", "mixed-fast"] as const
|
|
711
|
+
export const VIDEO_ANALYSIS_MIXED_TIERS = ["mixed", "mixed-fast", "smart"] as const
|
|
653
712
|
export type VideoAnalysisMixedTier = (typeof VIDEO_ANALYSIS_MIXED_TIERS)[number]
|
|
654
|
-
/**
|
|
655
|
-
|
|
713
|
+
/**
|
|
714
|
+
* `smart` is an ENGINE-PLAN sentinel like the mixed tiers — it names a plan, not a
|
|
715
|
+
* model — but it is the opposite kind of plan, and the distinction is the whole
|
|
716
|
+
* reason it exists as a separate tier rather than a repricing of the others.
|
|
717
|
+
*
|
|
718
|
+
* The economy tiers (`fast`, `pro`, `mixed`, `mixed-fast`) run several passes on
|
|
719
|
+
* the cheaper proxied transport and vote. That transport is 3-4x cheaper per token
|
|
720
|
+
* and additionally does no deep reasoning, which is why they cost single-digit
|
|
721
|
+
* credits — and also why they are less accurate. `smart` runs ONE pass on the
|
|
722
|
+
* native transport with everything turned up, which is where the accuracy is.
|
|
723
|
+
*
|
|
724
|
+
* As with the mixed sentinels, what the plan does internally is deliberately not
|
|
725
|
+
* published here; only the wire vocabulary is contract.
|
|
726
|
+
*/
|
|
727
|
+
/** UI/listing order — recommended (smart) first. */
|
|
728
|
+
export const VIDEO_ANALYSIS_TIER_ORDER = ["smart", "pro", "fast", "mixed", "mixed-fast"] as const
|
|
656
729
|
export type VideoAnalysisTier = (typeof VIDEO_ANALYSIS_TIER_ORDER)[number]
|
|
657
730
|
export const DEFAULT_VIDEO_ANALYSIS_TIER: VideoAnalysisTier = "pro"
|
|
658
731
|
export const DEFAULT_VIDEO_ANALYSIS_MODEL: string = VIDEO_ANALYSIS_TIERS[DEFAULT_VIDEO_ANALYSIS_TIER]
|
|
659
732
|
/** Neutral, vendor-free display labels for the UI. */
|
|
660
733
|
export const VIDEO_ANALYSIS_TIER_LABELS: Record<VideoAnalysisTier, string> = {
|
|
734
|
+
smart: "Smart",
|
|
661
735
|
fast: "Fast",
|
|
662
736
|
pro: "Pro",
|
|
663
737
|
mixed: "Mixed",
|
package/src/model-catalog.ts
CHANGED
|
@@ -1815,11 +1815,11 @@ const VIDEO_MODELS: Record<string, ModelCatalogEntry> = {
|
|
|
1815
1815
|
description: "Legacy fast-tier analysis model (pre-2026-07). Kept so stored raw-model configs keep running and keep pricing under their own identifier; new fast-tier runs use the current fast model.",
|
|
1816
1816
|
useCases: ["video-analysis", "shot-list", "fast"],
|
|
1817
1817
|
pricing: [
|
|
1818
|
-
{ identifier: "video-analysis:gemini-3-flash", credits:
|
|
1819
|
-
{ identifier: "video-analysis:gemini-3-flash:60s", credits:
|
|
1820
|
-
{ identifier: "video-analysis:gemini-3-flash:180s", credits:
|
|
1821
|
-
{ identifier: "video-analysis:gemini-3-flash:360s", credits:
|
|
1822
|
-
{ identifier: "video-analysis:gemini-3-flash:600s", credits:
|
|
1818
|
+
{ identifier: "video-analysis:gemini-3-flash", credits: 14, note: "10-min ceiling (no duration given)" },
|
|
1819
|
+
{ identifier: "video-analysis:gemini-3-flash:60s", credits: 3 },
|
|
1820
|
+
{ identifier: "video-analysis:gemini-3-flash:180s", credits: 4 },
|
|
1821
|
+
{ identifier: "video-analysis:gemini-3-flash:360s", credits: 9 },
|
|
1822
|
+
{ identifier: "video-analysis:gemini-3-flash:600s", credits: 14, note: "10-min ceiling" },
|
|
1823
1823
|
],
|
|
1824
1824
|
},
|
|
1825
1825
|
"gemini-3.6-flash-video-analysis": {
|
|
@@ -1832,11 +1832,11 @@ const VIDEO_MODELS: Record<string, ModelCatalogEntry> = {
|
|
|
1832
1832
|
description: "Analyze a video into a structured shot list (scenes, camera, audio) — fast, economy tier. Billed per duration bucket.",
|
|
1833
1833
|
useCases: ["video-analysis", "shot-list", "fast"],
|
|
1834
1834
|
pricing: [
|
|
1835
|
-
{ identifier: "video-analysis:gemini-3.6-flash", credits:
|
|
1836
|
-
{ identifier: "video-analysis:gemini-3.6-flash:60s", credits:
|
|
1837
|
-
{ identifier: "video-analysis:gemini-3.6-flash:180s", credits:
|
|
1838
|
-
{ identifier: "video-analysis:gemini-3.6-flash:360s", credits:
|
|
1839
|
-
{ identifier: "video-analysis:gemini-3.6-flash:600s", credits:
|
|
1835
|
+
{ identifier: "video-analysis:gemini-3.6-flash", credits: 38, note: "10-min ceiling (no duration given)" },
|
|
1836
|
+
{ identifier: "video-analysis:gemini-3.6-flash:60s", credits: 7 },
|
|
1837
|
+
{ identifier: "video-analysis:gemini-3.6-flash:180s", credits: 9 },
|
|
1838
|
+
{ identifier: "video-analysis:gemini-3.6-flash:360s", credits: 23 },
|
|
1839
|
+
{ identifier: "video-analysis:gemini-3.6-flash:600s", credits: 38, note: "10-min ceiling" },
|
|
1840
1840
|
],
|
|
1841
1841
|
},
|
|
1842
1842
|
"gemini-3.1-pro-video-analysis": {
|
|
@@ -1849,11 +1849,11 @@ const VIDEO_MODELS: Record<string, ModelCatalogEntry> = {
|
|
|
1849
1849
|
description: "Analyze a video into a structured shot list (scenes, camera, audio) — higher-fidelity, default tier. Billed per duration bucket.",
|
|
1850
1850
|
useCases: ["video-analysis", "shot-list", "cinematic"],
|
|
1851
1851
|
pricing: [
|
|
1852
|
-
{ identifier: "video-analysis:gemini-3.1-pro", credits:
|
|
1853
|
-
{ identifier: "video-analysis:gemini-3.1-pro:60s", credits:
|
|
1854
|
-
{ identifier: "video-analysis:gemini-3.1-pro:180s", credits:
|
|
1855
|
-
{ identifier: "video-analysis:gemini-3.1-pro:360s", credits:
|
|
1856
|
-
{ identifier: "video-analysis:gemini-3.1-pro:600s", credits:
|
|
1852
|
+
{ identifier: "video-analysis:gemini-3.1-pro", credits: 49, note: "10-min ceiling (no duration given)" },
|
|
1853
|
+
{ identifier: "video-analysis:gemini-3.1-pro:60s", credits: 9 },
|
|
1854
|
+
{ identifier: "video-analysis:gemini-3.1-pro:180s", credits: 12 },
|
|
1855
|
+
{ identifier: "video-analysis:gemini-3.1-pro:360s", credits: 30 },
|
|
1856
|
+
{ identifier: "video-analysis:gemini-3.1-pro:600s", credits: 49, note: "10-min ceiling" },
|
|
1857
1857
|
],
|
|
1858
1858
|
},
|
|
1859
1859
|
// Both mixed tiers are variants of the same advanced multi-engine analysis
|
|
@@ -1869,11 +1869,32 @@ const VIDEO_MODELS: Record<string, ModelCatalogEntry> = {
|
|
|
1869
1869
|
description: "Our most advanced analysis tier — multiple analysis engines combined into one result for maximum completeness and accuracy. Billed per duration bucket.",
|
|
1870
1870
|
useCases: ["video-analysis", "shot-list", "premium", "most-complete"],
|
|
1871
1871
|
pricing: [
|
|
1872
|
-
{ identifier: "video-analysis:mixed", credits:
|
|
1873
|
-
{ identifier: "video-analysis:mixed:60s", credits:
|
|
1874
|
-
{ identifier: "video-analysis:mixed:180s", credits:
|
|
1875
|
-
{ identifier: "video-analysis:mixed:360s", credits:
|
|
1876
|
-
{ identifier: "video-analysis:mixed:600s", credits:
|
|
1872
|
+
{ identifier: "video-analysis:mixed", credits: 63, note: "10-min ceiling (no duration given)" },
|
|
1873
|
+
{ identifier: "video-analysis:mixed:60s", credits: 11 },
|
|
1874
|
+
{ identifier: "video-analysis:mixed:180s", credits: 15 },
|
|
1875
|
+
{ identifier: "video-analysis:mixed:360s", credits: 38 },
|
|
1876
|
+
{ identifier: "video-analysis:mixed:600s", credits: 63, note: "10-min ceiling" },
|
|
1877
|
+
],
|
|
1878
|
+
},
|
|
1879
|
+
// SMART — the accuracy tier, and the only one that does not rely on voting.
|
|
1880
|
+
// Every other tier runs several cheap passes and has a grader pick between them;
|
|
1881
|
+
// this one runs a single pass with reasoning and frame sampling turned all the
|
|
1882
|
+
// way up. Priced well above them because it genuinely costs more to run.
|
|
1883
|
+
"smart-video-analysis": {
|
|
1884
|
+
id: "smart-video-analysis",
|
|
1885
|
+
kind: "video",
|
|
1886
|
+
modes: ["video-analysis"] as const,
|
|
1887
|
+
family: "Nodaro",
|
|
1888
|
+
label: "Video Analysis (Smart)",
|
|
1889
|
+
series: "Video Analysis",
|
|
1890
|
+
description: "Highest-accuracy analysis — a single deep pass that reads the footage frame by frame, finds its own shot boundaries, and identifies the cast by appearance. Best choice when the shot list will drive regeneration. Billed per duration bucket.",
|
|
1891
|
+
useCases: ["video-analysis", "shot-list", "premium", "most-accurate"],
|
|
1892
|
+
pricing: [
|
|
1893
|
+
{ identifier: "video-analysis:smart", credits: 346, note: "10-min ceiling (no duration given)" },
|
|
1894
|
+
{ identifier: "video-analysis:smart:60s", credits: 42 },
|
|
1895
|
+
{ identifier: "video-analysis:smart:180s", credits: 94 },
|
|
1896
|
+
{ identifier: "video-analysis:smart:360s", credits: 207 },
|
|
1897
|
+
{ identifier: "video-analysis:smart:600s", credits: 346, note: "10-min ceiling" },
|
|
1877
1898
|
],
|
|
1878
1899
|
},
|
|
1879
1900
|
}
|
|
@@ -50,27 +50,35 @@ export const VIDEO_ANALYSIS_WINDOW = { LEN: WINDOW_LEN, STRIDE: WINDOW_STRIDE, O
|
|
|
50
50
|
*/
|
|
51
51
|
export const VIDEO_ANALYSIS_BUCKET_CREDITS: Record<string, number> = {
|
|
52
52
|
// Legacy fast-tier model (pre-2026-07) — kept for stored raw-id configs.
|
|
53
|
-
"video-analysis:gemini-3-flash:60s":
|
|
54
|
-
"video-analysis:gemini-3-flash:180s":
|
|
55
|
-
"video-analysis:gemini-3-flash:360s":
|
|
56
|
-
"video-analysis:gemini-3-flash:600s":
|
|
53
|
+
"video-analysis:gemini-3-flash:60s": 3,
|
|
54
|
+
"video-analysis:gemini-3-flash:180s": 4,
|
|
55
|
+
"video-analysis:gemini-3-flash:360s": 9,
|
|
56
|
+
"video-analysis:gemini-3-flash:600s": 14,
|
|
57
57
|
// Current fast tier — regenerated from the private formula for its backing
|
|
58
58
|
// model; higher than the legacy fast schedule but still ≤ pro per bucket.
|
|
59
|
-
"video-analysis:gemini-3.6-flash:60s":
|
|
60
|
-
"video-analysis:gemini-3.6-flash:180s":
|
|
61
|
-
"video-analysis:gemini-3.6-flash:360s":
|
|
62
|
-
"video-analysis:gemini-3.6-flash:600s":
|
|
63
|
-
"video-analysis:gemini-3.1-pro:60s":
|
|
64
|
-
"video-analysis:gemini-3.1-pro:180s":
|
|
65
|
-
"video-analysis:gemini-3.1-pro:360s":
|
|
66
|
-
"video-analysis:gemini-3.1-pro:600s":
|
|
59
|
+
"video-analysis:gemini-3.6-flash:60s": 7,
|
|
60
|
+
"video-analysis:gemini-3.6-flash:180s": 9,
|
|
61
|
+
"video-analysis:gemini-3.6-flash:360s": 23,
|
|
62
|
+
"video-analysis:gemini-3.6-flash:600s": 38,
|
|
63
|
+
"video-analysis:gemini-3.1-pro:60s": 9,
|
|
64
|
+
"video-analysis:gemini-3.1-pro:180s": 12,
|
|
65
|
+
"video-analysis:gemini-3.1-pro:360s": 30,
|
|
66
|
+
"video-analysis:gemini-3.1-pro:600s": 49,
|
|
67
67
|
// Mixed tiers (`mixed` + `mixed-fast`) share ONE credit family — they are
|
|
68
68
|
// variants of the same engine plan (plan internals live in the private
|
|
69
69
|
// analysis plugin). Admin-tunable via model_pricing like every other row.
|
|
70
|
-
"video-analysis:mixed:60s":
|
|
71
|
-
"video-analysis:mixed:180s":
|
|
72
|
-
"video-analysis:mixed:360s":
|
|
73
|
-
"video-analysis:mixed:600s":
|
|
70
|
+
"video-analysis:mixed:60s": 11,
|
|
71
|
+
"video-analysis:mixed:180s": 15,
|
|
72
|
+
"video-analysis:mixed:360s": 38,
|
|
73
|
+
"video-analysis:mixed:600s": 63,
|
|
74
|
+
// SMART — the one native-transport plan: a single pass with reasoning and
|
|
75
|
+
// frame sampling turned all the way up. Priced well above the economy tiers
|
|
76
|
+
// because it genuinely costs more to run, and it is the only tier whose
|
|
77
|
+
// accuracy was measured against a hand-counted edit list.
|
|
78
|
+
"video-analysis:smart:60s": 42,
|
|
79
|
+
"video-analysis:smart:180s": 94,
|
|
80
|
+
"video-analysis:smart:360s": 207,
|
|
81
|
+
"video-analysis:smart:600s": 346,
|
|
74
82
|
}
|
|
75
83
|
|
|
76
84
|
/**
|
package/src/video-analysis.ts
CHANGED
|
@@ -205,6 +205,19 @@ export const entitySlotSchema = z.object({
|
|
|
205
205
|
* where this entity is clearly visible. Optional/additive: producers may
|
|
206
206
|
* omit it; consumers use it as an identity reference for recreation. */
|
|
207
207
|
refImageUrl: z.string().url().optional(),
|
|
208
|
+
/**
|
|
209
|
+
* Why `refImageUrl` is ABSENT, when the analyzer's vision pass actively
|
|
210
|
+
* refused every candidate frame rather than merely failing to produce one.
|
|
211
|
+
*
|
|
212
|
+
* Present only alongside a missing `refImageUrl`, and only for a real refusal
|
|
213
|
+
* — never for a technical failure, so its presence always carries meaning. The
|
|
214
|
+
* important case reads like "the shots bound to this slot show someone else —
|
|
215
|
+
* cast as X, but on screen: Y", which is the analyzer telling you the casting
|
|
216
|
+
* description and the footage disagree. A consumer should treat that as a
|
|
217
|
+
* reason to review the slot before spending on regeneration, since a wrong
|
|
218
|
+
* identity propagates into every regenerated shot.
|
|
219
|
+
*/
|
|
220
|
+
refRejectedReason: z.string().optional(),
|
|
208
221
|
/** NON-default looks only; present only when at least one exists. */
|
|
209
222
|
variations: z.array(slotVariationSchema).max(VIDEO_ANALYSIS_MAX_VARIATIONS).optional(),
|
|
210
223
|
})
|
|
@@ -327,6 +340,21 @@ export const videoAnalysisResultSchema = z.object({
|
|
|
327
340
|
variationId: z.string(),
|
|
328
341
|
label: z.string(),
|
|
329
342
|
})).optional(),
|
|
343
|
+
/**
|
|
344
|
+
* Analyzer diagnostics for THIS result — things the analysis noticed about
|
|
345
|
+
* itself that a reader should know before spending on regeneration.
|
|
346
|
+
*
|
|
347
|
+
* Added because there was previously no channel for them at all: the merge
|
|
348
|
+
* layer has always produced a warnings list and the auto-cast pass has always
|
|
349
|
+
* had findings, and every one of them died in a `console.warn` inside a worker
|
|
350
|
+
* the user cannot see. So a run could quietly drop a duplicated line, fold a
|
|
351
|
+
* cast look, or conclude a character is not the person their own description
|
|
352
|
+
* names, and the result looked indistinguishable from a clean one.
|
|
353
|
+
*
|
|
354
|
+
* Prose, not codes, and deliberately so — these are read by a human deciding
|
|
355
|
+
* whether to re-run, not branched on. Absent when there is nothing to report.
|
|
356
|
+
*/
|
|
357
|
+
warnings: z.array(z.string()).optional(),
|
|
330
358
|
})
|
|
331
359
|
export type VideoAnalysisResult = z.infer<typeof videoAnalysisResultSchema>
|
|
332
360
|
|