@nodaro/shared 2.1.0 → 2.2.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 +455 -77
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +307 -29
- package/dist/index.d.ts +307 -29
- package/dist/index.js +431 -78
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/gvp-anchor-mode.test.ts +65 -0
- package/src/__tests__/gvp-supported-providers.test.ts +234 -15
- package/src/__tests__/minimax-h3-catalog.test.ts +116 -0
- package/src/__tests__/minimax-h3-credit-id.test.ts +62 -0
- package/src/__tests__/reference-roles.test.ts +22 -1
- package/src/__tests__/seedance-2-5-catalog.test.ts +128 -0
- package/src/__tests__/video-analysis-catalog-sync.test.ts +69 -3
- package/src/__tests__/video-analysis-pricing.test.ts +105 -0
- package/src/__tests__/video-analysis.test.ts +13 -0
- package/src/credit-identifiers.ts +27 -3
- package/src/film-pricing.ts +1 -0
- package/src/index.ts +25 -0
- package/src/llm-models.ts +8 -5
- package/src/model-catalog.ts +170 -38
- package/src/model-constants.ts +453 -16
- package/src/node-default-mappings.ts +2 -0
- package/src/node-mappable-fields.ts +1 -1
- package/src/pipeline-types.ts +1 -0
- package/src/provider-directive-defaults.ts +4 -0
- package/src/reference-roles.ts +4 -1
- package/src/types.ts +11 -1
- package/src/video-analysis-pricing.ts +145 -25
- package/src/video-analysis.ts +17 -1
package/src/reference-roles.ts
CHANGED
|
@@ -11,7 +11,10 @@ import { DEFAULT_LABEL_BY_SOURCE, type ReferenceSource } from "./types.js"
|
|
|
11
11
|
export const REFERENCE_ROLE_PRESETS: Record<ReferenceSource, readonly string[]> = {
|
|
12
12
|
"wired-character": ["ref-only", "person", "face", "clothes", "hair", "pose", "expression", "style"],
|
|
13
13
|
"wired-face": ["face", "person", "expression", "style"],
|
|
14
|
-
"
|
|
14
|
+
// "location" sits second, mirroring wired-character's ref-only/person order —
|
|
15
|
+
// it is this source's default. "background" keeps its place for the genuine
|
|
16
|
+
// backdrop case (see DEFAULT_LABEL_BY_SOURCE for why it stopped being default).
|
|
17
|
+
"wired-location": ["ref-only", "location", "background", "atmosphere", "as-is", "empty background", "layout", "lighting", "style"],
|
|
15
18
|
"wired-object": ["object", "shape", "material", "color", "texture", "style"],
|
|
16
19
|
"wired-creature": ["creature", "anatomy", "markings", "pose", "color", "style"],
|
|
17
20
|
"wired-image": ["object", "person", "face", "clothes", "background", "style", "pose", "texture"],
|
package/src/types.ts
CHANGED
|
@@ -224,7 +224,17 @@ export const DEFAULT_LABEL_BY_SOURCE: Record<ReferenceSource, string> = {
|
|
|
224
224
|
// subject ("match anatomy, markings…"), not a prop. See CREATURE_LABELS in
|
|
225
225
|
// prompt-builder.ts.
|
|
226
226
|
"wired-creature": "creature",
|
|
227
|
-
|
|
227
|
+
// A PLACE, NOT A BACKDROP (2026-08-05). This defaulted to "background", which
|
|
228
|
+
// `roleToPhrase` renders as "the background from reference image B" — and the
|
|
229
|
+
// image models read that as "paste this behind the subject". Measured on
|
|
230
|
+
// gpt-image-2 (character + location, 4 draws per arm): with "background" every
|
|
231
|
+
// draw was a cut-out composite — an indoor-lit subject over a stock beach, no
|
|
232
|
+
// cast shadow, ignoring the asked-for action. Changing ONLY this word put the
|
|
233
|
+
// subject inside the scene with ground contact and a matching sun. "background"
|
|
234
|
+
// remains a curated pick in REFERENCE_ROLE_PRESETS for the genuine backdrop
|
|
235
|
+
// case; it is no longer what every location silently gets. Nodes that stored a
|
|
236
|
+
// role explicitly are untouched — `resolveDefaultRole` prefers the explicit value.
|
|
237
|
+
"wired-location": "location",
|
|
228
238
|
}
|
|
229
239
|
|
|
230
240
|
// ---------------------------------------------------------------------------
|
|
@@ -29,6 +29,10 @@
|
|
|
29
29
|
* against it and fails on drift. There is deliberately NO app-side formula to
|
|
30
30
|
* check against — it was moved private in 2026-07 and the old backend test
|
|
31
31
|
* went with it.
|
|
32
|
+
*
|
|
33
|
+
* `VIDEO_AUDIT_BUCKET_CREDITS` further down is the sibling table for the
|
|
34
|
+
* `video-audit` node ("AI Audit") — same module, same bucket ladder, same
|
|
35
|
+
* generator-authoritative pattern, its own two credit families.
|
|
32
36
|
*/
|
|
33
37
|
|
|
34
38
|
export const VIDEO_ANALYSIS_DURATION_BUCKETS = [60, 180, 360, 600] as const
|
|
@@ -67,38 +71,65 @@ export const VIDEO_ANALYSIS_WINDOW = { LEN: WINDOW_LEN, STRIDE: WINDOW_STRIDE, O
|
|
|
67
71
|
// Net effect: smart drops 27–47% per bucket — the 24 fps token spend was also
|
|
68
72
|
// partly paying for media tokens the provider clamped and never counted — and
|
|
69
73
|
// the economy rows tick up 3–6% from the prompt-token true-up.
|
|
74
|
+
//
|
|
75
|
+
// REGENERATED 2026-08-03 — V1 hybrid-smart reprice (task A3), from the
|
|
76
|
+
// plugin's own generator (`scripts/gen-va-buckets.mjs`) at
|
|
77
|
+
// nodaroai/nodaro-cloud-plugins commit eef077d (branch fix/va-cost-trueup).
|
|
78
|
+
// This is the V1 true-up of task P6's provisional judge/refine/frame-judge
|
|
79
|
+
// constants, re-derived from a 2026-08-03 staging measurement and approved by
|
|
80
|
+
// Tal (the constants themselves, like the rest of the $-derived formula, stay
|
|
81
|
+
// private in the plugin repo — never in this public package). `smart` is now
|
|
82
|
+
// a HYBRID plan — one native 6fps skeleton pass plus 2 fast + 2 pro donor
|
|
83
|
+
// rolls, always refined (`selectionMode` does not apply to `smart`; it always
|
|
84
|
+
// refines) — and every multi-roll tier now carries its own explicit
|
|
85
|
+
// judge/refine terms instead of an implicit share of a single-pass budget.
|
|
86
|
+
// This is the full, honest reprice Tal approved, including the economy tiers
|
|
87
|
+
// (fast 33->185 @180s ends a below-cost combine exposure that existed at the
|
|
88
|
+
// old price). Net effect, per bucket (every row rises):
|
|
89
|
+
//
|
|
90
|
+
// gemini-3-flash 60s 24->180 180s 33->185 360s 86-> 514 600s 143-> 846
|
|
91
|
+
// gemini-3.6-flash 60s 65->203 180s 92->218 360s 237-> 598 600s 395-> 986
|
|
92
|
+
// gemini-3.1-pro 60s 87->215 180s 116->231 360s 305-> 636 600s 509->1050
|
|
93
|
+
// mixed 60s 110->228 180s 149->249 360s 390-> 684 600s 651->1129
|
|
94
|
+
// mixed (2026-08-04) 60s 228->268 180s 249->289 360s 684-> 724 600s 1129->1169 (+40 flat: continuity pass joined the family)
|
|
95
|
+
// smart 60s 333->410 180s 470->500 360s 1135->1259 600s 1868->2064
|
|
96
|
+
//
|
|
97
|
+
// Values are pasted verbatim from the plugin generator's output — never hand
|
|
98
|
+
// computed. The plugin's cost test cross-checks every row, sentinels included.
|
|
70
99
|
export const VIDEO_ANALYSIS_BUCKET_CREDITS: Record<string, number> = {
|
|
71
100
|
// Legacy fast-tier model (pre-2026-07) — kept for stored raw-id configs.
|
|
72
|
-
"video-analysis:gemini-3-flash:60s":
|
|
73
|
-
"video-analysis:gemini-3-flash:180s":
|
|
74
|
-
"video-analysis:gemini-3-flash:360s":
|
|
75
|
-
"video-analysis:gemini-3-flash:600s":
|
|
101
|
+
"video-analysis:gemini-3-flash:60s": 180,
|
|
102
|
+
"video-analysis:gemini-3-flash:180s": 185,
|
|
103
|
+
"video-analysis:gemini-3-flash:360s": 514,
|
|
104
|
+
"video-analysis:gemini-3-flash:600s": 846,
|
|
76
105
|
// Current fast tier — regenerated from the private formula for its backing
|
|
77
106
|
// model; higher than the legacy fast schedule but still ≤ pro per bucket.
|
|
78
|
-
"video-analysis:gemini-3.6-flash:60s":
|
|
79
|
-
"video-analysis:gemini-3.6-flash:180s":
|
|
80
|
-
"video-analysis:gemini-3.6-flash:360s":
|
|
81
|
-
"video-analysis:gemini-3.6-flash:600s":
|
|
82
|
-
"video-analysis:gemini-3.1-pro:60s":
|
|
83
|
-
"video-analysis:gemini-3.1-pro:180s":
|
|
84
|
-
"video-analysis:gemini-3.1-pro:360s":
|
|
85
|
-
"video-analysis:gemini-3.1-pro:600s":
|
|
107
|
+
"video-analysis:gemini-3.6-flash:60s": 203,
|
|
108
|
+
"video-analysis:gemini-3.6-flash:180s": 218,
|
|
109
|
+
"video-analysis:gemini-3.6-flash:360s": 598,
|
|
110
|
+
"video-analysis:gemini-3.6-flash:600s": 986,
|
|
111
|
+
"video-analysis:gemini-3.1-pro:60s": 215,
|
|
112
|
+
"video-analysis:gemini-3.1-pro:180s": 231,
|
|
113
|
+
"video-analysis:gemini-3.1-pro:360s": 636,
|
|
114
|
+
"video-analysis:gemini-3.1-pro:600s": 1050,
|
|
86
115
|
// Mixed tiers (`mixed` + `mixed-fast`) share ONE credit family — they are
|
|
87
116
|
// variants of the same engine plan (plan internals live in the private
|
|
88
117
|
// analysis plugin). Admin-tunable via model_pricing like every other row.
|
|
89
|
-
"video-analysis:mixed:60s":
|
|
90
|
-
"video-analysis:mixed:180s":
|
|
91
|
-
"video-analysis:mixed:360s":
|
|
92
|
-
"video-analysis:mixed:600s":
|
|
93
|
-
// SMART — the
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
//
|
|
97
|
-
// the
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
"video-analysis:smart:
|
|
101
|
-
"video-analysis:smart:
|
|
118
|
+
"video-analysis:mixed:60s": 268,
|
|
119
|
+
"video-analysis:mixed:180s": 289,
|
|
120
|
+
"video-analysis:mixed:360s": 724,
|
|
121
|
+
"video-analysis:mixed:600s": 1169,
|
|
122
|
+
// SMART — the accuracy tier, and since the 2026-08-03 hybrid re-plan a
|
|
123
|
+
// multi-roll plan like the others: one native 6fps skeleton pass plus 2
|
|
124
|
+
// fast + 2 pro donor rolls, always refined (`selectionMode` does not apply
|
|
125
|
+
// here — smart always refines; it never offers a cheaper "choose" path).
|
|
126
|
+
// Priced above the economy tiers because it genuinely costs more to run;
|
|
127
|
+
// the only tier whose accuracy is validated against a hand-counted edit
|
|
128
|
+
// list, re-validated at the current plan before this schedule shipped.
|
|
129
|
+
"video-analysis:smart:60s": 410,
|
|
130
|
+
"video-analysis:smart:180s": 500,
|
|
131
|
+
"video-analysis:smart:360s": 1259,
|
|
132
|
+
"video-analysis:smart:600s": 2064,
|
|
102
133
|
}
|
|
103
134
|
|
|
104
135
|
/**
|
|
@@ -132,3 +163,92 @@ export function bucketSecondsFromCreditId(creditId: string): number | null {
|
|
|
132
163
|
export function videoAnalysisNumWindows(bucketSec: number): number {
|
|
133
164
|
return bucketSec <= VIDEO_ANALYSIS_WINDOW.SINGLE_MAX ? 1 : 1 + Math.ceil((bucketSec - WINDOW_LEN) / WINDOW_STRIDE)
|
|
134
165
|
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Precomputed credit cost for the `video-audit` node ("AI Audit") — the same
|
|
169
|
+
* pattern as `VIDEO_ANALYSIS_BUCKET_CREDITS` above: the OUTPUT of the private
|
|
170
|
+
* `videoAuditBucketCredits` formula in `@nodaroai/cloud-plugins`
|
|
171
|
+
* (`src/plugins/video-analysis/cost.ts`), a plain lookup table never a
|
|
172
|
+
* formula, cross-checked against that package's own cost test. Shares the
|
|
173
|
+
* SAME duration-bucket ladder as video-analysis (`VIDEO_ANALYSIS_DURATION_BUCKETS`
|
|
174
|
+
* / `pickVideoAnalysisBucket`) — the audit re-watches the same clip, so it
|
|
175
|
+
* buckets identically.
|
|
176
|
+
*
|
|
177
|
+
* Two credit families, selected by whether an analysis was wired into the
|
|
178
|
+
* node (see `buildVideoAuditCreditId`):
|
|
179
|
+
* - `video-audit:<bucket>s` — an analysis was provided; the audit re-watches
|
|
180
|
+
* the clip against it directly (native watcher roll + reasoner pass, at a
|
|
181
|
+
* safety margin — internals stay private in the plugin).
|
|
182
|
+
* - `video-audit:auto:<bucket>s` — no analysis wired; the node auto-runs a
|
|
183
|
+
* fast-tier analysis first. Exactly the base family's price PLUS the
|
|
184
|
+
* `gemini-3-flash` (legacy fast tier) row from `VIDEO_ANALYSIS_BUCKET_CREDITS`
|
|
185
|
+
* at the SAME bucket — summed in the plugin's generator/formula, never
|
|
186
|
+
* hand-added here (see the single-source assertion in
|
|
187
|
+
* video-analysis-pricing.test.ts).
|
|
188
|
+
*
|
|
189
|
+
* Bare ids (`video-audit`, `video-audit:auto` — they live in
|
|
190
|
+
* `model-catalog.ts`'s pricing rows, not as keys in this table) equal their
|
|
191
|
+
* family's 600s ceiling, the same "unknown duration → ceiling" convention
|
|
192
|
+
* `VIDEO_ANALYSIS_BUCKET_CREDITS`'s bare per-model catalog rows use.
|
|
193
|
+
* `buildVideoAuditCreditId` never returns a bare id itself — an
|
|
194
|
+
* unknown/invalid duration resolves to the bucketed 600s id, matching
|
|
195
|
+
* `buildVideoAnalysisCreditId`.
|
|
196
|
+
*
|
|
197
|
+
* Values pasted verbatim from the plugin generator's output
|
|
198
|
+
* (`scripts/gen-va-buckets.mjs`) at `@nodaroai/cloud-plugins` v0.102.0 —
|
|
199
|
+
* never hand computed. The plugin's cost test cross-checks every row.
|
|
200
|
+
*/
|
|
201
|
+
export const VIDEO_AUDIT_BUCKET_CREDITS: Record<string, number> = {
|
|
202
|
+
"video-audit:60s": 213,
|
|
203
|
+
"video-audit:180s": 289,
|
|
204
|
+
"video-audit:360s": 659,
|
|
205
|
+
"video-audit:600s": 1066,
|
|
206
|
+
"video-audit:auto:60s": 393,
|
|
207
|
+
"video-audit:auto:180s": 474,
|
|
208
|
+
"video-audit:auto:360s": 1173,
|
|
209
|
+
"video-audit:auto:600s": 1912,
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Composite credit-id builder for `video-audit`. Mirrors
|
|
214
|
+
* `buildVideoAnalysisCreditId`'s bucket selection and unknown/invalid-duration
|
|
215
|
+
* ceiling fallback exactly (same ladder, same rounding — `durationSec <=
|
|
216
|
+
* bucket`, clamped to the 600s max), but selects the FAMILY by whether an
|
|
217
|
+
* analysis was already provided instead of by model: `analysisProvided:
|
|
218
|
+
* true` prices under the cheaper `video-audit` family (re-audits an existing
|
|
219
|
+
* analysis); `false` prices under `video-audit:auto` (the node auto-runs a
|
|
220
|
+
* fast analysis first, hence that family's built-in fast-tier addition).
|
|
221
|
+
*/
|
|
222
|
+
export function buildVideoAuditCreditId(args: { analysisProvided: boolean; durationSec?: number }): string {
|
|
223
|
+
const { analysisProvided, durationSec } = args
|
|
224
|
+
const bucket = durationSec !== undefined && durationSec > 0
|
|
225
|
+
? pickVideoAnalysisBucket(Math.min(durationSec, VIDEO_ANALYSIS_MAX_DURATION_SEC))
|
|
226
|
+
: VIDEO_ANALYSIS_MAX_DURATION_SEC // no/invalid duration → ceiling composite, same convention as buildVideoAnalysisCreditId
|
|
227
|
+
const family = analysisProvided ? "video-audit" : "video-audit:auto"
|
|
228
|
+
return `${family}:${bucket}s`
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Table lookup for a resolved bucket + family. Snaps `bucketSec` onto the
|
|
233
|
+
* ladder via `pickVideoAnalysisBucket` first, so a raw duration (not just an
|
|
234
|
+
* exact 60/180/360/600) is safe to pass — mirrors `buildVideoAuditCreditId`'s
|
|
235
|
+
* own rounding rather than requiring the caller to pre-round.
|
|
236
|
+
*/
|
|
237
|
+
export function videoAuditCreditsForBucket(bucketSec: number, auto: boolean): number {
|
|
238
|
+
const bucket = pickVideoAnalysisBucket(bucketSec)
|
|
239
|
+
return VIDEO_AUDIT_BUCKET_CREDITS[`video-audit${auto ? ":auto" : ""}:${bucket}s`]!
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Parses a `video-audit:*` credit id back to its bucket seconds — both
|
|
244
|
+
* families (`video-audit:<n>s` and `video-audit:auto:<n>s`); null on
|
|
245
|
+
* anything else (a bare id with no bucket, a `video-analysis:*` id, or
|
|
246
|
+
* garbage). Deliberately a SEPARATE parser from `bucketSecondsFromCreditId`
|
|
247
|
+
* (anchored to the `video-analysis:` prefix and a mandatory model segment) —
|
|
248
|
+
* mirrors the plugin-local audit parser so app code can never mis-parse an
|
|
249
|
+
* audit id with the VA-anchored parser, or vice versa.
|
|
250
|
+
*/
|
|
251
|
+
export function bucketSecondsFromAuditCreditId(id: string): number | null {
|
|
252
|
+
const m = /^video-audit(?::auto)?:(\d+)s$/.exec(id)
|
|
253
|
+
return m ? Number(m[1]) : null
|
|
254
|
+
}
|
package/src/video-analysis.ts
CHANGED
|
@@ -491,8 +491,24 @@ export function renderAnalyzedScene(scene: { visual: string }, slots: EntitySlot
|
|
|
491
491
|
return scene.visual.replace(SLOT_TOKEN_RE, (_whole, id: string) => castMap?.[id] ?? byId.get(id)?.description ?? id)
|
|
492
492
|
}
|
|
493
493
|
|
|
494
|
+
/**
|
|
495
|
+
* Float slack for the oversize comparison.
|
|
496
|
+
*
|
|
497
|
+
* Scene boundaries arrive as decimal seconds and the duration is derived by
|
|
498
|
+
* SUBTRACTING two of them, which does not land on the cap exactly: a real job
|
|
499
|
+
* produced a 12.67 → 20.67 scene whose computed length is 8.000000000000002,
|
|
500
|
+
* so a scene sitting precisely ON the 8s cap was flagged oversized and carried
|
|
501
|
+
* a `oversized: true` defect marker downstream for no reason.
|
|
502
|
+
*
|
|
503
|
+
* A microsecond is orders of magnitude below any boundary precision the
|
|
504
|
+
* analyzer can actually resolve (windows land on ~0.01s at best), so this can
|
|
505
|
+
* never mask a genuinely oversized scene — the smallest real overshoot is
|
|
506
|
+
* still ~10000x larger than the tolerance.
|
|
507
|
+
*/
|
|
508
|
+
const OVERSIZE_TOLERANCE_SEC = 1e-6
|
|
509
|
+
|
|
494
510
|
export function isOversizedScene(startSec: number, endSec: number): boolean {
|
|
495
|
-
return endSec - startSec > VIDEO_ANALYSIS_MAX_SCENE_SEC
|
|
511
|
+
return endSec - startSec > VIDEO_ANALYSIS_MAX_SCENE_SEC + OVERSIZE_TOLERANCE_SEC
|
|
496
512
|
}
|
|
497
513
|
|
|
498
514
|
const STANDARD_RATIOS: Array<[string, number]> = [
|