@nodaro/shared 1.23.0 → 1.24.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nodaro/shared",
3
- "version": "1.23.0",
3
+ "version": "1.24.1",
4
4
  "description": "Shared types, model catalog, wire contracts, and structural vocabularies for the Nodaro platform and SDK.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -86,17 +86,25 @@ describe("bare video-analysis node-type credit id", () => {
86
86
  for (const [id, credits] of Object.entries(VIDEO_ANALYSIS_BUCKET_CREDITS)) {
87
87
  expect(credits, `${id} exceeds the bare-id ceiling ${ceiling}`).toBeLessThanOrEqual(ceiling)
88
88
  }
89
- // The migration writes this number; keep them in lockstep (277 wrote 200, 278
90
- // writes 739 after the measured reprice).
91
- expect(ceiling).toBe(739)
89
+ // The migration writes this number; keep them in lockstep (277 wrote 200,
90
+ // 279 wrote 739, 283 wrote 346, 284 writes 350 — `smart` owns the ceiling and
91
+ // gained the continuity pass).
92
+ expect(ceiling).toBe(350)
92
93
  })
93
94
 
94
- it("the default tier at the ceiling bucket is NOT a safe fallback — mixed costs more", () => {
95
- // Pins the reason the bare id is the table max rather than the intuitive
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
96
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.
97
105
  const defaultAtCeiling = VIDEO_ANALYSIS_BUCKET_CREDITS[
98
106
  buildVideoAnalysisCreditId(DEFAULT_VIDEO_ANALYSIS_MODEL, VIDEO_ANALYSIS_MAX_DURATION_SEC)
99
107
  ]!
100
- expect(defaultAtCeiling).toBeLessThan(Math.max(...Object.values(VIDEO_ANALYSIS_BUCKET_CREDITS)))
108
+ expect(defaultAtCeiling).toBeLessThanOrEqual(Math.max(...Object.values(VIDEO_ANALYSIS_BUCKET_CREDITS)))
101
109
  })
102
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
- expect(resolveVideoAnalysisModel("fast")).toBe("gemini-3.6-flash")
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/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
@@ -666,7 +677,7 @@ export const VIDEO_ANALYSIS_LLM_MODELS: string[] = LLM_MODELS
666
677
  * VIDEO_ANALYSIS_LLM_MODELS member AND every such model is reachable by a tier,
667
678
  * so adding a video model forces a tier decision instead of silently leaking.
668
679
  */
669
- export const VIDEO_ANALYSIS_TIERS = { fast: "gemini-3.6-flash", pro: "gemini-3.1-pro" } as const
680
+ export const VIDEO_ANALYSIS_TIERS = { fast: "gemini-3-flash", pro: "gemini-3.1-pro" } as const
670
681
  export type VideoAnalysisModelTier = keyof typeof VIDEO_ANALYSIS_TIERS
671
682
  /**
672
683
  * Models that PREVIOUSLY backed a tier, mapped to the tier they backed.
@@ -680,8 +691,13 @@ export type VideoAnalysisModelTier = keyof typeof VIDEO_ANALYSIS_TIERS
680
691
  * two-sided decision.
681
692
  */
682
693
  export const VIDEO_ANALYSIS_LEGACY_MODELS: Record<string, VideoAnalysisModelTier> = {
683
- // fast tier's backing model until 2026-07 (superseded by gemini-3.6-flash)
684
- "gemini-3-flash": "fast",
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",
685
701
  }
686
702
  /**
687
703
  * MIXED tiers — advanced multi-engine analysis plans whose identifier resolves
@@ -692,15 +708,30 @@ export const VIDEO_ANALYSIS_LEGACY_MODELS: Record<string, VideoAnalysisModelTier
692
708
  * NOT published here (Apache irrevocability; only the wire vocabulary below is
693
709
  * contract — the engine lives in the private analysis plugin).
694
710
  */
695
- export const VIDEO_ANALYSIS_MIXED_TIERS = ["mixed", "mixed-fast"] as const
711
+ export const VIDEO_ANALYSIS_MIXED_TIERS = ["mixed", "mixed-fast", "smart"] as const
696
712
  export type VideoAnalysisMixedTier = (typeof VIDEO_ANALYSIS_MIXED_TIERS)[number]
697
- /** UI/listing order — recommended (pro) first. */
698
- export const VIDEO_ANALYSIS_TIER_ORDER = ["pro", "fast", "mixed", "mixed-fast"] as const
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
699
729
  export type VideoAnalysisTier = (typeof VIDEO_ANALYSIS_TIER_ORDER)[number]
700
730
  export const DEFAULT_VIDEO_ANALYSIS_TIER: VideoAnalysisTier = "pro"
701
731
  export const DEFAULT_VIDEO_ANALYSIS_MODEL: string = VIDEO_ANALYSIS_TIERS[DEFAULT_VIDEO_ANALYSIS_TIER]
702
732
  /** Neutral, vendor-free display labels for the UI. */
703
733
  export const VIDEO_ANALYSIS_TIER_LABELS: Record<VideoAnalysisTier, string> = {
734
+ smart: "Smart",
704
735
  fast: "Fast",
705
736
  pro: "Pro",
706
737
  mixed: "Mixed",
@@ -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: 112, note: "10-min ceiling (no duration given)" },
1819
- { identifier: "video-analysis:gemini-3-flash:60s", credits: 21 },
1820
- { identifier: "video-analysis:gemini-3-flash:180s", credits: 24 },
1821
- { identifier: "video-analysis:gemini-3-flash:360s", credits: 68 },
1822
- { identifier: "video-analysis:gemini-3-flash:600s", credits: 112, note: "10-min ceiling" },
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: 291, note: "10-min ceiling (no duration given)" },
1836
- { identifier: "video-analysis:gemini-3.6-flash:60s", credits: 54 },
1837
- { identifier: "video-analysis:gemini-3.6-flash:180s", credits: 63 },
1838
- { identifier: "video-analysis:gemini-3.6-flash:360s", credits: 175 },
1839
- { identifier: "video-analysis:gemini-3.6-flash:600s", credits: 291, note: "10-min ceiling" },
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: 448, note: "10-min ceiling (no duration given)" },
1853
- { identifier: "video-analysis:gemini-3.1-pro:60s", credits: 84 },
1854
- { identifier: "video-analysis:gemini-3.1-pro:180s", credits: 96 },
1855
- { identifier: "video-analysis:gemini-3.1-pro:360s", credits: 269 },
1856
- { identifier: "video-analysis:gemini-3.1-pro:600s", credits: 448, note: "10-min ceiling" },
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: 739, note: "10-min ceiling (no duration given)" },
1873
- { identifier: "video-analysis:mixed:60s", credits: 137 },
1874
- { identifier: "video-analysis:mixed:180s", credits: 158 },
1875
- { identifier: "video-analysis:mixed:360s", credits: 443 },
1876
- { identifier: "video-analysis:mixed:600s", credits: 739, note: "10-min ceiling" },
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: 350, note: "10-min ceiling (no duration given)" },
1894
+ { identifier: "video-analysis:smart:60s", credits: 46 },
1895
+ { identifier: "video-analysis:smart:180s", credits: 98 },
1896
+ { identifier: "video-analysis:smart:360s", credits: 211 },
1897
+ { identifier: "video-analysis:smart:600s", credits: 350, 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": 21,
54
- "video-analysis:gemini-3-flash:180s": 24,
55
- "video-analysis:gemini-3-flash:360s": 68,
56
- "video-analysis:gemini-3-flash:600s": 112,
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": 54,
60
- "video-analysis:gemini-3.6-flash:180s": 63,
61
- "video-analysis:gemini-3.6-flash:360s": 175,
62
- "video-analysis:gemini-3.6-flash:600s": 291,
63
- "video-analysis:gemini-3.1-pro:60s": 84,
64
- "video-analysis:gemini-3.1-pro:180s": 96,
65
- "video-analysis:gemini-3.1-pro:360s": 269,
66
- "video-analysis:gemini-3.1-pro:600s": 448,
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": 137,
71
- "video-analysis:mixed:180s": 158,
72
- "video-analysis:mixed:360s": 443,
73
- "video-analysis:mixed:600s": 739,
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": 46,
79
+ "video-analysis:smart:180s": 98,
80
+ "video-analysis:smart:360s": 211,
81
+ "video-analysis:smart:600s": 350,
74
82
  }
75
83
 
76
84
  /**
@@ -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