@nodaro/shared 1.14.0 → 1.16.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 +84 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -2
- package/dist/index.d.ts +19 -2
- package/dist/index.js +84 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/llm-models.test.ts +26 -11
- package/src/__tests__/video-analysis-pricing.test.ts +31 -7
- package/src/__tests__/video-analysis.test.ts +17 -0
- package/src/index.ts +1 -0
- package/src/llm-models.ts +56 -5
- package/src/model-catalog.ts +19 -2
- package/src/video-analysis-pricing.ts +7 -0
- package/src/video-analysis.ts +10 -0
package/package.json
CHANGED
|
@@ -24,8 +24,8 @@ import { PIPELINE_PINNABLE_SCRIPT_LLMS } from "../pipeline-types.js"
|
|
|
24
24
|
// LLM_MODELS data integrity
|
|
25
25
|
// ---------------------------------------------------------------------------
|
|
26
26
|
describe("LLM_MODELS data integrity", () => {
|
|
27
|
-
it("should have exactly
|
|
28
|
-
expect(LLM_MODELS).toHaveLength(
|
|
27
|
+
it("should have exactly 15 models", () => {
|
|
28
|
+
expect(LLM_MODELS).toHaveLength(15)
|
|
29
29
|
})
|
|
30
30
|
|
|
31
31
|
it("each model has all required fields", () => {
|
|
@@ -60,14 +60,14 @@ describe("LLM_MODELS data integrity", () => {
|
|
|
60
60
|
expect(new Set(ids).size).toBe(ids.length)
|
|
61
61
|
})
|
|
62
62
|
|
|
63
|
-
it("has
|
|
63
|
+
it("has 4 economy, 4 standard, 7 premium models", () => {
|
|
64
64
|
const tierCounts: Record<LlmTier, number> = { economy: 0, standard: 0, premium: 0 }
|
|
65
65
|
for (const model of LLM_MODELS) {
|
|
66
66
|
tierCounts[model.tier]++
|
|
67
67
|
}
|
|
68
|
-
expect(tierCounts.economy).toBe(
|
|
68
|
+
expect(tierCounts.economy).toBe(4)
|
|
69
69
|
expect(tierCounts.standard).toBe(4)
|
|
70
|
-
expect(tierCounts.premium).toBe(
|
|
70
|
+
expect(tierCounts.premium).toBe(7)
|
|
71
71
|
})
|
|
72
72
|
|
|
73
73
|
it("all three kieFormats are represented", () => {
|
|
@@ -108,6 +108,7 @@ describe("LLM_MODEL_IDS", () => {
|
|
|
108
108
|
it("contains all expected model ids", () => {
|
|
109
109
|
const expected = [
|
|
110
110
|
"gemini-3-flash",
|
|
111
|
+
"gemini-3.6-flash",
|
|
111
112
|
"claude-haiku-4.5",
|
|
112
113
|
"claude-sonnet-4.6",
|
|
113
114
|
"gpt-5.2",
|
|
@@ -120,6 +121,7 @@ describe("LLM_MODEL_IDS", () => {
|
|
|
120
121
|
"gpt-5.6-sol",
|
|
121
122
|
"claude-sonnet-5",
|
|
122
123
|
"claude-opus-4.8",
|
|
124
|
+
"claude-fable-5",
|
|
123
125
|
]
|
|
124
126
|
expect(LLM_MODEL_IDS).toEqual(expected)
|
|
125
127
|
})
|
|
@@ -387,8 +389,8 @@ describe("LLM_FEATURE_DEFAULTS", () => {
|
|
|
387
389
|
}
|
|
388
390
|
})
|
|
389
391
|
|
|
390
|
-
it('"prompt-helper" defaults to "gemini-3-flash" (economy)', () => {
|
|
391
|
-
expect(LLM_FEATURE_DEFAULTS["prompt-helper"]).toBe("gemini-3-flash")
|
|
392
|
+
it('"prompt-helper" defaults to "gemini-3.6-flash" (economy)', () => {
|
|
393
|
+
expect(LLM_FEATURE_DEFAULTS["prompt-helper"]).toBe("gemini-3.6-flash")
|
|
392
394
|
expect(getLlmTier(LLM_FEATURE_DEFAULTS["prompt-helper"])).toBe("economy")
|
|
393
395
|
})
|
|
394
396
|
|
|
@@ -404,12 +406,12 @@ describe("LLM_FEATURE_DEFAULTS", () => {
|
|
|
404
406
|
expect(STRUCTURED_VISION_MODELS.map((m) => m.id)).toContain("claude-opus-4.7")
|
|
405
407
|
})
|
|
406
408
|
|
|
407
|
-
it('"generate-script" defaults to "gemini-3-flash" (economy)', () => {
|
|
408
|
-
expect(LLM_FEATURE_DEFAULTS["generate-script"]).toBe("gemini-3-flash")
|
|
409
|
+
it('"generate-script" defaults to "gemini-3.6-flash" (economy)', () => {
|
|
410
|
+
expect(LLM_FEATURE_DEFAULTS["generate-script"]).toBe("gemini-3.6-flash")
|
|
409
411
|
})
|
|
410
412
|
|
|
411
|
-
it('"translate" defaults to "gemini-3-flash" (economy)', () => {
|
|
412
|
-
expect(LLM_FEATURE_DEFAULTS["translate"]).toBe("gemini-3-flash")
|
|
413
|
+
it('"translate" defaults to "gemini-3.6-flash" (economy)', () => {
|
|
414
|
+
expect(LLM_FEATURE_DEFAULTS["translate"]).toBe("gemini-3.6-flash")
|
|
413
415
|
})
|
|
414
416
|
|
|
415
417
|
it("composition features default to claude-sonnet-4.6", () => {
|
|
@@ -454,9 +456,11 @@ describe("STRUCTURED_VISION_MODELS", () => {
|
|
|
454
456
|
"claude-opus-4.7",
|
|
455
457
|
"claude-sonnet-4.6",
|
|
456
458
|
"gemini-3-flash",
|
|
459
|
+
"gemini-3.6-flash",
|
|
457
460
|
"gemini-3.1-pro",
|
|
458
461
|
"claude-sonnet-5",
|
|
459
462
|
"claude-opus-4.8",
|
|
463
|
+
"claude-fable-5",
|
|
460
464
|
// responses-format GPTs — KIE text.format json_schema live-verified
|
|
461
465
|
// 2026-07-14 (text AND vision inputs).
|
|
462
466
|
"gpt-5.4",
|
|
@@ -509,6 +513,14 @@ describe("reasoning effort registry", () => {
|
|
|
509
513
|
expect(getLlmTier("claude-sonnet-5")).toBe("standard")
|
|
510
514
|
expect(getLlmTier("claude-opus-4.8")).toBe("premium")
|
|
511
515
|
expect(getLlmTier("gpt-5.5")).toBe("premium")
|
|
516
|
+
expect(getLlmTier("gemini-3.6-flash")).toBe("economy")
|
|
517
|
+
expect(getLlmTier("claude-fable-5")).toBe("premium")
|
|
518
|
+
})
|
|
519
|
+
|
|
520
|
+
it("gemini-3.6-flash exposes the KIE low/high thinking levels; claude-fable-5 the full Claude ladder", () => {
|
|
521
|
+
expect(getLlmModel("gemini-3.6-flash")?.reasoningEfforts).toEqual(["low", "high"])
|
|
522
|
+
expect(getLlmModel("claude-fable-5")?.reasoningEfforts).toEqual(["low", "medium", "high", "xhigh", "max"])
|
|
523
|
+
expect(getLlmModel("claude-fable-5")?.directFallbackModel).toBe("claude-fable-5")
|
|
512
524
|
})
|
|
513
525
|
})
|
|
514
526
|
|
|
@@ -551,6 +563,9 @@ describe("buildLlmCreditIdentifier effort bump (xhigh/max only)", () => {
|
|
|
551
563
|
expect(buildLlmCreditIdentifier("llm-chat", "gpt-5.4", "xhigh")).toBe("llm-chat:premium")
|
|
552
564
|
// gpt-5.4 is premium anyway; the real clamp case:
|
|
553
565
|
expect(buildLlmCreditIdentifier("llm-chat", "gemini-3-flash", "max")).toBe("llm-chat:economy")
|
|
566
|
+
// gemini-3.6-flash tops out at "high" — max clamps to high, never bumps.
|
|
567
|
+
expect(effectiveReasoningEffort("gemini-3.6-flash", "max")).toBe("high")
|
|
568
|
+
expect(buildLlmCreditIdentifier("llm-chat", "gemini-3.6-flash", "max")).toBe("llm-chat:economy")
|
|
554
569
|
})
|
|
555
570
|
it("back-compat: no effort arg → identical to today for every model", () => {
|
|
556
571
|
for (const m of LLM_MODELS) {
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
} from "../video-analysis-pricing.js"
|
|
8
8
|
import {
|
|
9
9
|
VIDEO_ANALYSIS_LLM_MODELS, VIDEO_ANALYSIS_TIERS, VIDEO_ANALYSIS_TIER_ORDER,
|
|
10
|
-
VIDEO_ANALYSIS_MIXED_TIERS,
|
|
10
|
+
VIDEO_ANALYSIS_MIXED_TIERS, VIDEO_ANALYSIS_LEGACY_MODELS,
|
|
11
11
|
DEFAULT_VIDEO_ANALYSIS_TIER, DEFAULT_VIDEO_ANALYSIS_MODEL, resolveVideoAnalysisModel,
|
|
12
12
|
} from "../llm-models.js"
|
|
13
13
|
|
|
@@ -41,15 +41,28 @@ describe("video-analysis-pricing", () => {
|
|
|
41
41
|
})
|
|
42
42
|
|
|
43
43
|
it("model SSOT is capability-derived and Gemini-only today", () => {
|
|
44
|
-
expect(VIDEO_ANALYSIS_LLM_MODELS).toEqual(["gemini-3-flash", "gemini-3.1-pro"])
|
|
44
|
+
expect(VIDEO_ANALYSIS_LLM_MODELS).toEqual(["gemini-3-flash", "gemini-3.6-flash", "gemini-3.1-pro"])
|
|
45
45
|
})
|
|
46
46
|
|
|
47
|
-
it("tier layer: every model-backed tier maps to a real model AND every model is tier-reachable (no vendor leak)", () => {
|
|
47
|
+
it("tier layer: every model-backed tier maps to a real model AND every model is tier-reachable or explicitly legacy (no vendor leak)", () => {
|
|
48
48
|
// Adding a video-analysis model without a tier would silently leave it
|
|
49
|
-
// unreachable / unnamed — this fails until
|
|
49
|
+
// unreachable / unnamed — this fails until an explicit decision is made:
|
|
50
|
+
// either a tier targets it, or it's acknowledged in
|
|
51
|
+
// VIDEO_ANALYSIS_LEGACY_MODELS (kept only for stored raw-id configs).
|
|
50
52
|
const tierTargets = Object.values(VIDEO_ANALYSIS_TIERS)
|
|
53
|
+
const legacy = Object.keys(VIDEO_ANALYSIS_LEGACY_MODELS)
|
|
51
54
|
for (const m of tierTargets) expect(VIDEO_ANALYSIS_LLM_MODELS).toContain(m)
|
|
52
|
-
for (const m of VIDEO_ANALYSIS_LLM_MODELS)
|
|
55
|
+
for (const m of VIDEO_ANALYSIS_LLM_MODELS) {
|
|
56
|
+
expect([...tierTargets, ...legacy], `${m} needs a tier or a legacy entry`).toContain(m)
|
|
57
|
+
}
|
|
58
|
+
// Legacy entries are strictly the OTHER side of that decision: a legacy
|
|
59
|
+
// model is never also a tier target, must still be video-capable, and must
|
|
60
|
+
// point at a real tier (the tier it used to back — UIs reverse-map via it).
|
|
61
|
+
for (const [m, tier] of Object.entries(VIDEO_ANALYSIS_LEGACY_MODELS)) {
|
|
62
|
+
expect(tierTargets).not.toContain(m)
|
|
63
|
+
expect(VIDEO_ANALYSIS_LLM_MODELS).toContain(m)
|
|
64
|
+
expect(Object.keys(VIDEO_ANALYSIS_TIERS)).toContain(tier)
|
|
65
|
+
}
|
|
53
66
|
// TIER_ORDER = model-backed tiers + mixed roll-plan tiers, exactly.
|
|
54
67
|
expect(new Set(VIDEO_ANALYSIS_TIER_ORDER)).toEqual(
|
|
55
68
|
new Set([...Object.keys(VIDEO_ANALYSIS_TIERS), ...VIDEO_ANALYSIS_MIXED_TIERS]),
|
|
@@ -59,14 +72,25 @@ describe("video-analysis-pricing", () => {
|
|
|
59
72
|
for (const t of VIDEO_ANALYSIS_MIXED_TIERS) expect(VIDEO_ANALYSIS_LLM_MODELS).not.toContain(t)
|
|
60
73
|
})
|
|
61
74
|
|
|
75
|
+
it("fast tier never prices above pro in any bucket", () => {
|
|
76
|
+
for (const bucketSec of VIDEO_ANALYSIS_DURATION_BUCKETS) {
|
|
77
|
+
const fast = VIDEO_ANALYSIS_BUCKET_CREDITS[buildVideoAnalysisCreditId(VIDEO_ANALYSIS_TIERS.fast, bucketSec)]!
|
|
78
|
+
const pro = VIDEO_ANALYSIS_BUCKET_CREDITS[buildVideoAnalysisCreditId(VIDEO_ANALYSIS_TIERS.pro, bucketSec)]!
|
|
79
|
+
expect(fast, `fast > pro at ${bucketSec}s`).toBeLessThanOrEqual(pro)
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
|
|
62
83
|
it("resolveVideoAnalysisModel: tier → model, mixed → sentinel, raw model passthrough, default pro on empty/unknown", () => {
|
|
63
84
|
expect(DEFAULT_VIDEO_ANALYSIS_TIER).toBe("pro")
|
|
64
85
|
expect(DEFAULT_VIDEO_ANALYSIS_MODEL).toBe("gemini-3.1-pro")
|
|
65
86
|
expect(resolveVideoAnalysisModel("pro")).toBe("gemini-3.1-pro")
|
|
66
|
-
expect(resolveVideoAnalysisModel("fast")).toBe("gemini-3-flash")
|
|
87
|
+
expect(resolveVideoAnalysisModel("fast")).toBe("gemini-3.6-flash")
|
|
67
88
|
expect(resolveVideoAnalysisModel("mixed")).toBe("mixed") // roll-plan sentinel passthrough
|
|
68
89
|
expect(resolveVideoAnalysisModel("mixed-fast")).toBe("mixed-fast")
|
|
69
|
-
|
|
90
|
+
// Raw passthrough keeps LEGACY stored configs running (and priced) on the
|
|
91
|
+
// exact model they were saved with — never silently re-tiered.
|
|
92
|
+
expect(resolveVideoAnalysisModel("gemini-3-flash")).toBe("gemini-3-flash")
|
|
93
|
+
expect(resolveVideoAnalysisModel("gemini-3.6-flash")).toBe("gemini-3.6-flash")
|
|
70
94
|
expect(resolveVideoAnalysisModel(undefined)).toBe("gemini-3.1-pro") // default → pro
|
|
71
95
|
expect(resolveVideoAnalysisModel("")).toBe("gemini-3.1-pro")
|
|
72
96
|
expect(resolveVideoAnalysisModel("nonsense")).toBe("gemini-3.1-pro") // unknown → default, never throws
|
|
@@ -104,6 +104,23 @@ describe("appearance variations (cast-variations spec §4)", () => {
|
|
|
104
104
|
})
|
|
105
105
|
})
|
|
106
106
|
|
|
107
|
+
describe("variationFolds (cast-variations §4/§6 — review F5)", () => {
|
|
108
|
+
const meta = { durationSec: 10, width: 1920, height: 1080, aspectRatio: "16:9" }
|
|
109
|
+
const doc = {
|
|
110
|
+
meta,
|
|
111
|
+
slots: [slot],
|
|
112
|
+
scenes: [{ ...baseScene, sceneNumber: 1, visualResolved: "a man juggles", slotRefs: ["hero"] }],
|
|
113
|
+
}
|
|
114
|
+
it("videoAnalysisResultSchema round-trips variationFolds — strip-mode consumers keep the §6 fold note", () => {
|
|
115
|
+
const withFolds = { ...doc, variationFolds: [{ slotId: "hero", variationId: "era", label: "Era" }] }
|
|
116
|
+
expect(videoAnalysisResultSchema.parse(withFolds)).toEqual(withFolds)
|
|
117
|
+
})
|
|
118
|
+
it("absent variationFolds stays absent (no [] materialization)", () => {
|
|
119
|
+
const parsed = videoAnalysisResultSchema.parse(doc)
|
|
120
|
+
expect("variationFolds" in parsed && parsed.variationFolds !== undefined).toBe(false)
|
|
121
|
+
})
|
|
122
|
+
})
|
|
123
|
+
|
|
107
124
|
describe("binding rewrite helpers (merge consumes — spec §4)", () => {
|
|
108
125
|
it("rewriteSceneBindings renames slot keys and per-slot variation values", () => {
|
|
109
126
|
expect(rewriteSceneBindings({ "man-2": "dream", other: "era" }, { "man-2": "hero" }, { hero: { dream: "flashback" } }))
|
package/src/index.ts
CHANGED
package/src/llm-models.ts
CHANGED
|
@@ -72,6 +72,24 @@ export const LLM_MODELS: readonly LlmModelDef[] = [
|
|
|
72
72
|
supportsImages: true,
|
|
73
73
|
maxOutputTokens: 8192,
|
|
74
74
|
},
|
|
75
|
+
{
|
|
76
|
+
id: "gemini-3.6-flash",
|
|
77
|
+
displayName: "Gemini 3.6 Flash",
|
|
78
|
+
desc: "Latest fast Gemini, sharper reasoning",
|
|
79
|
+
tier: "economy",
|
|
80
|
+
kieFormat: "chat-completions",
|
|
81
|
+
// KIE serves Gemini 3.6 Flash on the OpenAI-compatible dialect under this
|
|
82
|
+
// slug (docs.kie.ai/market/gemini/gemini-3-6-flash-openai.md) — same
|
|
83
|
+
// chat-completions path shape as gemini-3-flash, different slug prefix.
|
|
84
|
+
kieSlugOrModel: "gemini-3-6-flash-openai",
|
|
85
|
+
vendor: "google",
|
|
86
|
+
structuredOutputMode: "kie-response-format",
|
|
87
|
+
supportsImages: true,
|
|
88
|
+
maxOutputTokens: 8192,
|
|
89
|
+
// KIE's 3.6 endpoint accepts `reasoning_effort: low | high` (thinking
|
|
90
|
+
// level) — exactly the chat-completions wire mapping deriveParams sends.
|
|
91
|
+
reasoningEfforts: ["low", "high"],
|
|
92
|
+
},
|
|
75
93
|
{
|
|
76
94
|
id: "claude-haiku-4.5",
|
|
77
95
|
displayName: "Claude Haiku 4.5",
|
|
@@ -240,6 +258,22 @@ export const LLM_MODELS: readonly LlmModelDef[] = [
|
|
|
240
258
|
supportsTemperature: false,
|
|
241
259
|
preferKie: true,
|
|
242
260
|
},
|
|
261
|
+
{
|
|
262
|
+
id: "claude-fable-5",
|
|
263
|
+
displayName: "Claude Fable 5",
|
|
264
|
+
desc: "Frontier Claude above Opus, hardest problems",
|
|
265
|
+
tier: "premium",
|
|
266
|
+
kieFormat: "messages",
|
|
267
|
+
kieSlugOrModel: "claude-fable-5",
|
|
268
|
+
vendor: "anthropic",
|
|
269
|
+
structuredOutputMode: "anthropic-tool",
|
|
270
|
+
supportsImages: true,
|
|
271
|
+
maxOutputTokens: 16384,
|
|
272
|
+
directFallbackModel: "claude-fable-5",
|
|
273
|
+
reasoningEfforts: ["low", "medium", "high", "xhigh", "max"],
|
|
274
|
+
supportsTemperature: false,
|
|
275
|
+
preferKie: true,
|
|
276
|
+
},
|
|
243
277
|
] as const
|
|
244
278
|
|
|
245
279
|
export const LLM_MODEL_IDS = LLM_MODELS.map((m) => m.id)
|
|
@@ -280,8 +314,8 @@ export function motionGraphicsFeature(engine?: string): LlmFeature {
|
|
|
280
314
|
/** Feature → default model when user hasn't selected one */
|
|
281
315
|
export const LLM_FEATURE_DEFAULTS: Record<LlmFeature, string> = {
|
|
282
316
|
"ai-writer": "claude-sonnet-4.6",
|
|
283
|
-
"llm-chat": "gemini-3-flash",
|
|
284
|
-
"prompt-helper": "gemini-3-flash",
|
|
317
|
+
"llm-chat": "gemini-3.6-flash",
|
|
318
|
+
"prompt-helper": "gemini-3.6-flash",
|
|
285
319
|
"scene-graph-ai": "claude-sonnet-4.6",
|
|
286
320
|
"after-effects": "claude-sonnet-4.6",
|
|
287
321
|
"motion-graphics": "claude-sonnet-4.6",
|
|
@@ -291,8 +325,8 @@ export const LLM_FEATURE_DEFAULTS: Record<LlmFeature, string> = {
|
|
|
291
325
|
"image-to-text": "claude-sonnet-4.6",
|
|
292
326
|
"describe-to-picker": "claude-opus-4.7",
|
|
293
327
|
"qa-check": "claude-sonnet-4.6",
|
|
294
|
-
"generate-script": "gemini-3-flash",
|
|
295
|
-
"translate": "gemini-3-flash",
|
|
328
|
+
"generate-script": "gemini-3.6-flash",
|
|
329
|
+
"translate": "gemini-3.6-flash",
|
|
296
330
|
"image-critic": "claude-sonnet-4.6",
|
|
297
331
|
}
|
|
298
332
|
|
|
@@ -307,6 +341,7 @@ export const LLM_FEATURE_DEFAULTS: Record<LlmFeature, string> = {
|
|
|
307
341
|
*/
|
|
308
342
|
export const LLM_MODALITY_CAPS: Record<string, { image: boolean; video: boolean; audio: boolean }> = {
|
|
309
343
|
"gemini-3-flash": { image: true, video: true, audio: true },
|
|
344
|
+
"gemini-3.6-flash": { image: true, video: true, audio: true },
|
|
310
345
|
"gemini-3.1-pro": { image: true, video: true, audio: true },
|
|
311
346
|
"claude-haiku-4.5": { image: true, video: false, audio: false },
|
|
312
347
|
"claude-sonnet-4.6": { image: true, video: false, audio: false },
|
|
@@ -319,6 +354,7 @@ export const LLM_MODALITY_CAPS: Record<string, { image: boolean; video: boolean;
|
|
|
319
354
|
"gpt-5.6-sol": { image: true, video: false, audio: false },
|
|
320
355
|
"claude-sonnet-5": { image: true, video: false, audio: false },
|
|
321
356
|
"claude-opus-4.8": { image: true, video: false, audio: false },
|
|
357
|
+
"claude-fable-5": { image: true, video: false, audio: false },
|
|
322
358
|
}
|
|
323
359
|
|
|
324
360
|
/** Capability lookup with safe default — unknown models get image-only. */
|
|
@@ -416,8 +452,23 @@ export const VIDEO_ANALYSIS_LLM_MODELS: string[] = LLM_MODELS
|
|
|
416
452
|
* VIDEO_ANALYSIS_LLM_MODELS member AND every such model is reachable by a tier,
|
|
417
453
|
* so adding a video model forces a tier decision instead of silently leaking.
|
|
418
454
|
*/
|
|
419
|
-
export const VIDEO_ANALYSIS_TIERS = { fast: "gemini-3-flash", pro: "gemini-3.1-pro" } as const
|
|
455
|
+
export const VIDEO_ANALYSIS_TIERS = { fast: "gemini-3.6-flash", pro: "gemini-3.1-pro" } as const
|
|
420
456
|
export type VideoAnalysisModelTier = keyof typeof VIDEO_ANALYSIS_TIERS
|
|
457
|
+
/**
|
|
458
|
+
* Models that PREVIOUSLY backed a tier, mapped to the tier they backed.
|
|
459
|
+
* They stay video+audio-capable (so they remain in VIDEO_ANALYSIS_LLM_MODELS):
|
|
460
|
+
* stored raw `llmModel` values keep resolving via the passthrough in
|
|
461
|
+
* `resolveVideoAnalysisModel` and keep pricing under their own credit family —
|
|
462
|
+
* but no tier reaches them for NEW runs. UIs use this map to reverse a stored
|
|
463
|
+
* raw id to the tier the user originally chose (video-configs fail-safe).
|
|
464
|
+
* The tier guard test requires every video-capable model to be a tier target
|
|
465
|
+
* OR a key here, so replacing a tier's backing model stays an explicit,
|
|
466
|
+
* two-sided decision.
|
|
467
|
+
*/
|
|
468
|
+
export const VIDEO_ANALYSIS_LEGACY_MODELS: Record<string, VideoAnalysisModelTier> = {
|
|
469
|
+
// fast tier's backing model until 2026-07 (superseded by gemini-3.6-flash)
|
|
470
|
+
"gemini-3-flash": "fast",
|
|
471
|
+
}
|
|
421
472
|
/**
|
|
422
473
|
* MIXED tiers — advanced multi-engine analysis plans whose identifier resolves
|
|
423
474
|
* to an engine-plan SENTINEL consumed by the analysis engine, never to a single
|
package/src/model-catalog.ts
CHANGED
|
@@ -1810,9 +1810,9 @@ const VIDEO_MODELS: Record<string, ModelCatalogEntry> = {
|
|
|
1810
1810
|
kind: "video",
|
|
1811
1811
|
modes: ["video-analysis"] as const,
|
|
1812
1812
|
family: "Nodaro",
|
|
1813
|
-
label: "Video Analysis (Fast)",
|
|
1813
|
+
label: "Video Analysis (Fast — legacy)",
|
|
1814
1814
|
series: "Video Analysis",
|
|
1815
|
-
description: "
|
|
1815
|
+
description: "Legacy fast-tier analysis model (pre-2026-07). Kept so stored raw-model configs keep running and pricing unchanged; new fast-tier runs use the current fast model.",
|
|
1816
1816
|
useCases: ["video-analysis", "shot-list", "fast"],
|
|
1817
1817
|
pricing: [
|
|
1818
1818
|
{ identifier: "video-analysis:gemini-3-flash", credits: 3, note: "10-min ceiling (no duration given)" },
|
|
@@ -1822,6 +1822,23 @@ const VIDEO_MODELS: Record<string, ModelCatalogEntry> = {
|
|
|
1822
1822
|
{ identifier: "video-analysis:gemini-3-flash:600s", credits: 3, note: "10-min ceiling" },
|
|
1823
1823
|
],
|
|
1824
1824
|
},
|
|
1825
|
+
"gemini-3.6-flash-video-analysis": {
|
|
1826
|
+
id: "gemini-3.6-flash-video-analysis",
|
|
1827
|
+
kind: "video",
|
|
1828
|
+
modes: ["video-analysis"] as const,
|
|
1829
|
+
family: "Nodaro",
|
|
1830
|
+
label: "Video Analysis (Fast)",
|
|
1831
|
+
series: "Video Analysis",
|
|
1832
|
+
description: "Analyze a video into a structured shot list (scenes, camera, audio) — fast, economy tier. Billed per duration bucket.",
|
|
1833
|
+
useCases: ["video-analysis", "shot-list", "fast"],
|
|
1834
|
+
pricing: [
|
|
1835
|
+
{ identifier: "video-analysis:gemini-3.6-flash", credits: 9, note: "10-min ceiling (no duration given)" },
|
|
1836
|
+
{ identifier: "video-analysis:gemini-3.6-flash:60s", credits: 2 },
|
|
1837
|
+
{ identifier: "video-analysis:gemini-3.6-flash:180s", credits: 2 },
|
|
1838
|
+
{ identifier: "video-analysis:gemini-3.6-flash:360s", credits: 5 },
|
|
1839
|
+
{ identifier: "video-analysis:gemini-3.6-flash:600s", credits: 9, note: "10-min ceiling" },
|
|
1840
|
+
],
|
|
1841
|
+
},
|
|
1825
1842
|
"gemini-3.1-pro-video-analysis": {
|
|
1826
1843
|
id: "gemini-3.1-pro-video-analysis",
|
|
1827
1844
|
kind: "video",
|
|
@@ -46,10 +46,17 @@ export const VIDEO_ANALYSIS_WINDOW = { LEN: WINDOW_LEN, STRIDE: WINDOW_STRIDE, O
|
|
|
46
46
|
* `docs/nodes/processing-video/video-analysis.md`.
|
|
47
47
|
*/
|
|
48
48
|
export const VIDEO_ANALYSIS_BUCKET_CREDITS: Record<string, number> = {
|
|
49
|
+
// Legacy fast-tier model (pre-2026-07) — kept for stored raw-id configs.
|
|
49
50
|
"video-analysis:gemini-3-flash:60s": 1,
|
|
50
51
|
"video-analysis:gemini-3-flash:180s": 1,
|
|
51
52
|
"video-analysis:gemini-3-flash:360s": 2,
|
|
52
53
|
"video-analysis:gemini-3-flash:600s": 3,
|
|
54
|
+
// Current fast tier — regenerated from the private formula for its backing
|
|
55
|
+
// model; higher than the legacy fast schedule but still ≤ pro per bucket.
|
|
56
|
+
"video-analysis:gemini-3.6-flash:60s": 2,
|
|
57
|
+
"video-analysis:gemini-3.6-flash:180s": 2,
|
|
58
|
+
"video-analysis:gemini-3.6-flash:360s": 5,
|
|
59
|
+
"video-analysis:gemini-3.6-flash:600s": 9,
|
|
53
60
|
"video-analysis:gemini-3.1-pro:60s": 2,
|
|
54
61
|
"video-analysis:gemini-3.1-pro:180s": 3,
|
|
55
62
|
"video-analysis:gemini-3.1-pro:360s": 7,
|
package/src/video-analysis.ts
CHANGED
|
@@ -128,6 +128,16 @@ export const videoAnalysisResultSchema = z.object({
|
|
|
128
128
|
}),
|
|
129
129
|
slots: z.array(entitySlotSchema),
|
|
130
130
|
scenes: z.array(analyzedSceneSchema).min(1),
|
|
131
|
+
/** CAST VARIATIONS (§4 cap handling): looks the analyzer's merge FOLDED into
|
|
132
|
+
* the default at VIDEO_ANALYSIS_MAX_VARIATIONS — recorded, never silent. In
|
|
133
|
+
* the wire schema so strip-mode consumers (the recast client's validated
|
|
134
|
+
* blueprint view) keep it: the §6 "folded into default look" note is the
|
|
135
|
+
* user's only pre-pay defense against a wrong split. */
|
|
136
|
+
variationFolds: z.array(z.object({
|
|
137
|
+
slotId: z.string(),
|
|
138
|
+
variationId: z.string(),
|
|
139
|
+
label: z.string(),
|
|
140
|
+
})).optional(),
|
|
131
141
|
})
|
|
132
142
|
export type VideoAnalysisResult = z.infer<typeof videoAnalysisResultSchema>
|
|
133
143
|
|