@nodaro/shared 2.23.0 → 2.26.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.
@@ -2176,11 +2176,11 @@ const VIDEO_MODELS: Record<string, ModelCatalogEntry> = {
2176
2176
  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.",
2177
2177
  useCases: ["video-analysis", "shot-list", "fast"],
2178
2178
  pricing: [
2179
- { identifier: "video-analysis:gemini-3-flash", credits: 848, note: "10-min ceiling (no duration given)" },
2179
+ { identifier: "video-analysis:gemini-3-flash", credits: 849, note: "10-min ceiling (no duration given)" },
2180
2180
  { identifier: "video-analysis:gemini-3-flash:60s", credits: 181 },
2181
- { identifier: "video-analysis:gemini-3-flash:180s", credits: 185 },
2182
- { identifier: "video-analysis:gemini-3-flash:360s", credits: 515 },
2183
- { identifier: "video-analysis:gemini-3-flash:600s", credits: 848, note: "10-min ceiling" },
2181
+ { identifier: "video-analysis:gemini-3-flash:180s", credits: 186 },
2182
+ { identifier: "video-analysis:gemini-3-flash:360s", credits: 516 },
2183
+ { identifier: "video-analysis:gemini-3-flash:600s", credits: 849, note: "10-min ceiling" },
2184
2184
  ],
2185
2185
  },
2186
2186
  "gemini-3.6-flash-video-analysis": {
@@ -2193,11 +2193,11 @@ const VIDEO_MODELS: Record<string, ModelCatalogEntry> = {
2193
2193
  description: "Analyze a video into a structured shot list (scenes, camera, audio) — fast, economy tier. Billed per duration bucket.",
2194
2194
  useCases: ["video-analysis", "shot-list", "fast"],
2195
2195
  pricing: [
2196
- { identifier: "video-analysis:gemini-3.6-flash", credits: 992, note: "10-min ceiling (no duration given)" },
2197
- { identifier: "video-analysis:gemini-3.6-flash:60s", credits: 204 },
2196
+ { identifier: "video-analysis:gemini-3.6-flash", credits: 995, note: "10-min ceiling (no duration given)" },
2197
+ { identifier: "video-analysis:gemini-3.6-flash:60s", credits: 205 },
2198
2198
  { identifier: "video-analysis:gemini-3.6-flash:180s", credits: 219 },
2199
- { identifier: "video-analysis:gemini-3.6-flash:360s", credits: 602 },
2200
- { identifier: "video-analysis:gemini-3.6-flash:600s", credits: 992, note: "10-min ceiling" },
2199
+ { identifier: "video-analysis:gemini-3.6-flash:360s", credits: 603 },
2200
+ { identifier: "video-analysis:gemini-3.6-flash:600s", credits: 995, note: "10-min ceiling" },
2201
2201
  ],
2202
2202
  },
2203
2203
  "gemini-3.1-pro-video-analysis": {
@@ -2210,11 +2210,11 @@ const VIDEO_MODELS: Record<string, ModelCatalogEntry> = {
2210
2210
  description: "Analyze a video into a structured shot list (scenes, camera, audio) — higher-fidelity, default tier. Billed per duration bucket.",
2211
2211
  useCases: ["video-analysis", "shot-list", "cinematic"],
2212
2212
  pricing: [
2213
- { identifier: "video-analysis:gemini-3.1-pro", credits: 1056, note: "10-min ceiling (no duration given)" },
2214
- { identifier: "video-analysis:gemini-3.1-pro:60s", credits: 216 },
2215
- { identifier: "video-analysis:gemini-3.1-pro:180s", credits: 232 },
2216
- { identifier: "video-analysis:gemini-3.1-pro:360s", credits: 640 },
2217
- { identifier: "video-analysis:gemini-3.1-pro:600s", credits: 1056, note: "10-min ceiling" },
2213
+ { identifier: "video-analysis:gemini-3.1-pro", credits: 1059, note: "10-min ceiling (no duration given)" },
2214
+ { identifier: "video-analysis:gemini-3.1-pro:60s", credits: 217 },
2215
+ { identifier: "video-analysis:gemini-3.1-pro:180s", credits: 233 },
2216
+ { identifier: "video-analysis:gemini-3.1-pro:360s", credits: 642 },
2217
+ { identifier: "video-analysis:gemini-3.1-pro:600s", credits: 1059, note: "10-min ceiling" },
2218
2218
  ],
2219
2219
  },
2220
2220
  // Both mixed tiers are variants of the same advanced multi-engine analysis
@@ -2230,11 +2230,11 @@ const VIDEO_MODELS: Record<string, ModelCatalogEntry> = {
2230
2230
  description: "Our most advanced analysis tier — multiple analysis engines combined into one result for maximum completeness and accuracy. Billed per duration bucket.",
2231
2231
  useCases: ["video-analysis", "shot-list", "premium", "most-complete"],
2232
2232
  pricing: [
2233
- { identifier: "video-analysis:mixed", credits: 1177, note: "10-min ceiling (no duration given)" },
2233
+ { identifier: "video-analysis:mixed", credits: 1181, note: "10-min ceiling (no duration given)" },
2234
2234
  { identifier: "video-analysis:mixed:60s", credits: 270 },
2235
- { identifier: "video-analysis:mixed:180s", credits: 291 },
2236
- { identifier: "video-analysis:mixed:360s", credits: 729 },
2237
- { identifier: "video-analysis:mixed:600s", credits: 1177, note: "10-min ceiling" },
2235
+ { identifier: "video-analysis:mixed:180s", credits: 292 },
2236
+ { identifier: "video-analysis:mixed:360s", credits: 731 },
2237
+ { identifier: "video-analysis:mixed:600s", credits: 1181, note: "10-min ceiling" },
2238
2238
  ],
2239
2239
  },
2240
2240
  // SMART — the accuracy tier. Since the 2026-08-03 hybrid re-plan it is a
@@ -2252,11 +2252,11 @@ const VIDEO_MODELS: Record<string, ModelCatalogEntry> = {
2252
2252
  description: "Highest-accuracy analysis — a hybrid pass that blends a native skeleton read with several donor analysis rolls, then always refines the merged result to find shot boundaries and identify the cast by appearance. Best choice when the shot list will drive regeneration. Billed per duration bucket.",
2253
2253
  useCases: ["video-analysis", "shot-list", "premium", "most-accurate"],
2254
2254
  pricing: [
2255
- { identifier: "video-analysis:smart", credits: 2076, note: "10-min ceiling (no duration given)" },
2256
- { identifier: "video-analysis:smart:60s", credits: 413 },
2257
- { identifier: "video-analysis:smart:180s", credits: 503 },
2258
- { identifier: "video-analysis:smart:360s", credits: 1267 },
2259
- { identifier: "video-analysis:smart:600s", credits: 2076, note: "10-min ceiling" },
2255
+ { identifier: "video-analysis:smart", credits: 2081, note: "10-min ceiling (no duration given)" },
2256
+ { identifier: "video-analysis:smart:60s", credits: 414 },
2257
+ { identifier: "video-analysis:smart:180s", credits: 504 },
2258
+ { identifier: "video-analysis:smart:360s", credits: 1270 },
2259
+ { identifier: "video-analysis:smart:600s", credits: 2081, note: "10-min ceiling" },
2260
2260
  ],
2261
2261
  },
2262
2262
  // ── AI Audit (`video-audit`) ── a SEPARATE node from video-analysis (own
@@ -2280,11 +2280,11 @@ const VIDEO_MODELS: Record<string, ModelCatalogEntry> = {
2280
2280
  description: "Re-watches a clip against a wired analysis, applies video-verified corrections under guards, and returns a disclosed report of what changed. Billed per duration bucket.",
2281
2281
  useCases: ["video-audit", "qa", "shot-list", "verification"],
2282
2282
  pricing: [
2283
- { identifier: "video-audit", credits: 1073, note: "10-min ceiling (no duration given)" },
2283
+ { identifier: "video-audit", credits: 1075, note: "10-min ceiling (no duration given)" },
2284
2284
  { identifier: "video-audit:60s", credits: 215 },
2285
- { identifier: "video-audit:180s", credits: 290 },
2286
- { identifier: "video-audit:360s", credits: 663 },
2287
- { identifier: "video-audit:600s", credits: 1073, note: "10-min ceiling" },
2285
+ { identifier: "video-audit:180s", credits: 291 },
2286
+ { identifier: "video-audit:360s", credits: 664 },
2287
+ { identifier: "video-audit:600s", credits: 1075, note: "10-min ceiling" },
2288
2288
  ],
2289
2289
  },
2290
2290
  "video-audit-auto": {
@@ -2296,11 +2296,11 @@ const VIDEO_MODELS: Record<string, ModelCatalogEntry> = {
2296
2296
  description: "Same video-verified audit as AI Audit, but auto-runs a fast analysis first when none is wired in, then applies corrections under guards and returns a disclosed report. Billed per duration bucket.",
2297
2297
  useCases: ["video-audit", "qa", "shot-list", "verification", "auto-analysis"],
2298
2298
  pricing: [
2299
- { identifier: "video-audit:auto", credits: 1921, note: "10-min ceiling (no duration given)" },
2299
+ { identifier: "video-audit:auto", credits: 1924, note: "10-min ceiling (no duration given)" },
2300
2300
  { identifier: "video-audit:auto:60s", credits: 396 },
2301
- { identifier: "video-audit:auto:180s", credits: 475 },
2302
- { identifier: "video-audit:auto:360s", credits: 1178 },
2303
- { identifier: "video-audit:auto:600s", credits: 1921, note: "10-min ceiling" },
2301
+ { identifier: "video-audit:auto:180s", credits: 477 },
2302
+ { identifier: "video-audit:auto:360s", credits: 1180 },
2303
+ { identifier: "video-audit:auto:600s", credits: 1924, note: "10-min ceiling" },
2304
2304
  ],
2305
2305
  },
2306
2306
  }
@@ -0,0 +1,124 @@
1
+ /**
2
+ * The parts of `settings.studio` that must not leave the owner's account.
3
+ *
4
+ * A shared production is read by anyone with the link. Three things inside the
5
+ * document are the OWNER'S working state and nobody else's business:
6
+ *
7
+ * - `trash` — the recycle bin, which holds every shot, still and clip they
8
+ * deleted, with prompts and urls intact. A share viewer receiving the bin is
9
+ * the sharpest of the three: it hands out work the owner explicitly threw away.
10
+ * - the in-flight job markers — `pendingClips` / `pendingStills` per shot, and
11
+ * `pendingMusic` / `pendingDraft` on the document. A viewer cannot land any
12
+ * of them and does not own them; all they carry across is job ids.
13
+ * - `freecutDraftUrl` — an unsaved editor draft.
14
+ *
15
+ * They do NOT all live at the same level, and that is the whole reason this
16
+ * file exists rather than one array: the writer puts `trash` and
17
+ * `freecutDraftUrl` on `settings.studio` itself, and puts the per-shot markers
18
+ * on the `settings.studio.shots[]` entry. A strip that walked only the top
19
+ * level would pass its own test and still hand a share viewer every marker in
20
+ * the production.
21
+ *
22
+ * It lives in `@nodaro/shared` because two independent readers need the SAME
23
+ * list: the public share read (which is the reason the list exists) and the
24
+ * production writer's own bundle projection. A second copy of a list like this
25
+ * does not stay equal — it goes one key stale and the stale side is the one
26
+ * that publishes.
27
+ *
28
+ * This is a plain JSON walker on purpose. `settings` is a free-form column that
29
+ * a client owns end to end; the projection reads the keys it must drop and
30
+ * nothing else, so it never needs — and must never grow — a dependency on
31
+ * whatever writes the rest of the document.
32
+ */
33
+
34
+ /**
35
+ * `settings.studio`'s OWN transient keys.
36
+ *
37
+ * The per-shot pending lists are on this list as well as the shot one on
38
+ * purpose: nothing writes them here today, and a stray one from an older
39
+ * client — or from a client that is not the studio editor at all — still must
40
+ * not ride out to a viewer.
41
+ */
42
+ export const STUDIO_TRANSIENT_KEYS = [
43
+ "trash",
44
+ "pendingStills",
45
+ "pendingClips",
46
+ // The two markers that genuinely DO live at this level: a soundtrack render
47
+ // and a story-planning run in flight. Same rule as the per-shot pair — they
48
+ // name jobs on the owner's account and nobody else can land them.
49
+ "pendingMusic",
50
+ "pendingDraft",
51
+ "freecutDraftUrl",
52
+ ] as const
53
+
54
+ /**
55
+ * ...and a SHOT entry's, which is where the per-shot markers actually are.
56
+ *
57
+ * `pendingClip` (singular) is the pre-concurrent-markers shape; the editor's
58
+ * reader still migrates it on parse, so a row can still be carrying one and it
59
+ * is still in-flight state.
60
+ */
61
+ export const STUDIO_SHOT_TRANSIENT_KEYS = ["pendingClips", "pendingClip", "pendingStills"] as const
62
+
63
+ function withoutKeys(
64
+ source: Record<string, unknown>,
65
+ drop: ReadonlyArray<string>,
66
+ ): Record<string, unknown> {
67
+ const kept: Record<string, unknown> = {}
68
+ for (const [key, value] of Object.entries(source)) {
69
+ if (drop.includes(key)) continue
70
+ kept[key] = value
71
+ }
72
+ return kept
73
+ }
74
+
75
+ /**
76
+ * `settings.studio.shots` with every shot's in-flight markers removed.
77
+ *
78
+ * Returns the SAME array when no shot carried one, so an idle production's
79
+ * share read allocates nothing. Anything that is not a shot-shaped object rides
80
+ * through untouched: this runs on whatever is in the column, and a projection
81
+ * that threw on an unexpected row would take the share read down with it.
82
+ */
83
+ function stripShots(value: unknown): unknown {
84
+ if (!Array.isArray(value)) return value
85
+ let changed = false
86
+ const out = value.map((entry) => {
87
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) return entry
88
+ const shot = entry as Record<string, unknown>
89
+ if (!STUDIO_SHOT_TRANSIENT_KEYS.some((key) => key in shot)) return entry
90
+ changed = true
91
+ return withoutKeys(shot, STUDIO_SHOT_TRANSIENT_KEYS)
92
+ })
93
+ return changed ? out : value
94
+ }
95
+
96
+ /**
97
+ * A production's `settings` with the owner's working state removed.
98
+ *
99
+ * Copy-on-write, and structurally: it rebuilds the objects without those keys
100
+ * rather than deleting from the caller's, so the stored row is untouched. A
101
+ * `settings` with no `studio` comes back unchanged — this is a studio concern,
102
+ * and a workflow that is not a production has nothing here to strip.
103
+ *
104
+ * Takes and returns `unknown` because the column is free-form and every caller
105
+ * already holds it as whatever its own layer calls JSON; narrowing here would
106
+ * only move the cast one line up.
107
+ */
108
+ export function stripStudioTransientSettings(settings: unknown): unknown {
109
+ if (!settings || typeof settings !== "object") return settings
110
+ const studio = (settings as { studio?: unknown }).studio
111
+ if (!studio || typeof studio !== "object" || Array.isArray(studio)) return settings
112
+
113
+ const source = studio as Record<string, unknown>
114
+ const kept = withoutKeys(source, STUDIO_TRANSIENT_KEYS)
115
+ const shots = stripShots(source.shots)
116
+ if (source.shots !== undefined) kept.shots = shots
117
+
118
+ // Nothing to drop at either level — hand back the original object so an
119
+ // ordinary share read allocates nothing.
120
+ if (Object.keys(kept).length === Object.keys(source).length && shots === source.shots) {
121
+ return settings
122
+ }
123
+ return { ...(settings as Record<string, unknown>), studio: kept }
124
+ }
@@ -105,39 +105,46 @@ export const VIDEO_ANALYSIS_WINDOW = { LEN: WINDOW_LEN, STRIDE: WINDOW_STRIDE, O
105
105
  // analysis rows and 6 of 8 audit rows tick up by 1–5 credits (~0.1–0.6%); unlike
106
106
  // the 8_482 → 8_706 round, the legacy `gemini-3-flash` family moves too.
107
107
  // Output of the plugin's `scripts/gen-va-buckets.mjs`, pasted verbatim.
108
+ //
109
+ // REGENERATED 2026-09-06 — the zoom-through doctrine round (cloud-plugins
110
+ // 0.221.0 → 0.221.7): the analyser's system prompt grew across the day's
111
+ // releases, moving the plugin's system-prompt token pin 9_082 → 9_434. 17 of
112
+ // 20 analysis rows and 6 of 8 audit rows tick up by 1–5 credits (~0.1–0.5%);
113
+ // the legacy `gemini-3-flash` family moves too.
114
+ // Output of the plugin's `scripts/gen-va-buckets.mjs`, pasted verbatim.
108
115
  export const VIDEO_ANALYSIS_BUCKET_CREDITS: Record<string, number> = {
109
116
  // Legacy fast-tier model (pre-2026-07) — kept for stored raw-id configs.
110
117
  "video-analysis:gemini-3-flash:60s": 181,
111
- "video-analysis:gemini-3-flash:180s": 185,
112
- "video-analysis:gemini-3-flash:360s": 515,
113
- "video-analysis:gemini-3-flash:600s": 848,
118
+ "video-analysis:gemini-3-flash:180s": 186,
119
+ "video-analysis:gemini-3-flash:360s": 516,
120
+ "video-analysis:gemini-3-flash:600s": 849,
114
121
  // Current fast tier — regenerated from the private formula for its backing
115
122
  // model; higher than the legacy fast schedule but still ≤ pro per bucket.
116
- "video-analysis:gemini-3.6-flash:60s": 204,
123
+ "video-analysis:gemini-3.6-flash:60s": 205,
117
124
  "video-analysis:gemini-3.6-flash:180s": 219,
118
- "video-analysis:gemini-3.6-flash:360s": 602,
119
- "video-analysis:gemini-3.6-flash:600s": 992,
120
- "video-analysis:gemini-3.1-pro:60s": 216,
121
- "video-analysis:gemini-3.1-pro:180s": 232,
122
- "video-analysis:gemini-3.1-pro:360s": 640,
123
- "video-analysis:gemini-3.1-pro:600s": 1056,
125
+ "video-analysis:gemini-3.6-flash:360s": 603,
126
+ "video-analysis:gemini-3.6-flash:600s": 995,
127
+ "video-analysis:gemini-3.1-pro:60s": 217,
128
+ "video-analysis:gemini-3.1-pro:180s": 233,
129
+ "video-analysis:gemini-3.1-pro:360s": 642,
130
+ "video-analysis:gemini-3.1-pro:600s": 1059,
124
131
  // Mixed tiers (`mixed` + `mixed-fast`) share ONE credit family — they are
125
132
  // variants of the same engine plan (plan internals live in the private
126
133
  // analysis plugin). Admin-tunable via model_pricing like every other row.
127
134
  "video-analysis:mixed:60s": 270,
128
- "video-analysis:mixed:180s": 291,
129
- "video-analysis:mixed:360s": 729,
130
- "video-analysis:mixed:600s": 1177,
135
+ "video-analysis:mixed:180s": 292,
136
+ "video-analysis:mixed:360s": 731,
137
+ "video-analysis:mixed:600s": 1181,
131
138
  // SMART — the accuracy tier, and since the 2026-08-03 re-plan a multi-roll
132
139
  // plan like the others, always refined (`selectionMode` does not apply
133
140
  // here — smart always refines; it never offers a cheaper "choose" path).
134
141
  // Priced above the economy tiers because it genuinely costs more to run;
135
142
  // the only tier whose accuracy is validated against a hand-counted edit
136
143
  // list, re-validated at the current plan before this schedule shipped.
137
- "video-analysis:smart:60s": 413,
138
- "video-analysis:smart:180s": 503,
139
- "video-analysis:smart:360s": 1267,
140
- "video-analysis:smart:600s": 2076,
144
+ "video-analysis:smart:60s": 414,
145
+ "video-analysis:smart:180s": 504,
146
+ "video-analysis:smart:360s": 1270,
147
+ "video-analysis:smart:600s": 2081,
141
148
  }
142
149
 
143
150
  /**
@@ -208,13 +215,13 @@ export function videoAnalysisNumWindows(bucketSec: number): number {
208
215
  */
209
216
  export const VIDEO_AUDIT_BUCKET_CREDITS: Record<string, number> = {
210
217
  "video-audit:60s": 215,
211
- "video-audit:180s": 290,
212
- "video-audit:360s": 663,
213
- "video-audit:600s": 1073,
218
+ "video-audit:180s": 291,
219
+ "video-audit:360s": 664,
220
+ "video-audit:600s": 1075,
214
221
  "video-audit:auto:60s": 396,
215
- "video-audit:auto:180s": 475,
216
- "video-audit:auto:360s": 1178,
217
- "video-audit:auto:600s": 1921,
222
+ "video-audit:auto:180s": 477,
223
+ "video-audit:auto:360s": 1180,
224
+ "video-audit:auto:600s": 1924,
218
225
  }
219
226
 
220
227
  /**