@nodaro/shared 1.18.0 → 1.19.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 +25 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +58 -1
- package/dist/index.d.ts +58 -1
- package/dist/index.js +22 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/llm-models.test.ts +46 -21
- package/src/__tests__/smart-cut-windows.test.ts +43 -0
- package/src/index.ts +3 -0
- package/src/llm-models.ts +22 -1
- package/src/smart-cut-windows.ts +50 -0
package/package.json
CHANGED
|
@@ -20,12 +20,36 @@ import { PIPELINE_PINNABLE_SCRIPT_LLMS } from "../pipeline-types.js"
|
|
|
20
20
|
// the NON-monetary model registry (ids, capabilities, tiers, feature
|
|
21
21
|
// defaults) that stays in the published package.
|
|
22
22
|
|
|
23
|
+
/** The registry's expected membership, in order — the single list both the
|
|
24
|
+
* count guard and the id-order guard below assert against, so a model
|
|
25
|
+
* addition can never leave one of them stale. */
|
|
26
|
+
const EXPECTED_MODEL_IDS = [
|
|
27
|
+
"gemini-3-flash",
|
|
28
|
+
"gemini-3.6-flash",
|
|
29
|
+
"claude-haiku-4.5",
|
|
30
|
+
"claude-sonnet-4.6",
|
|
31
|
+
"gpt-5.2",
|
|
32
|
+
"gemini-3.1-pro",
|
|
33
|
+
"claude-opus-4.7",
|
|
34
|
+
"gpt-5.4",
|
|
35
|
+
"gpt-5.5",
|
|
36
|
+
"gpt-5.6-luna",
|
|
37
|
+
"gpt-5.6-terra",
|
|
38
|
+
"gpt-5.6-sol",
|
|
39
|
+
"claude-sonnet-5",
|
|
40
|
+
"claude-opus-4.8",
|
|
41
|
+
"claude-opus-5",
|
|
42
|
+
"claude-fable-5",
|
|
43
|
+
]
|
|
44
|
+
|
|
23
45
|
// ---------------------------------------------------------------------------
|
|
24
46
|
// LLM_MODELS data integrity
|
|
25
47
|
// ---------------------------------------------------------------------------
|
|
26
48
|
describe("LLM_MODELS data integrity", () => {
|
|
27
|
-
it("
|
|
28
|
-
|
|
49
|
+
it("model count matches the enumerated id list", () => {
|
|
50
|
+
// Bound to the id list asserted below rather than a hand-typed number, so
|
|
51
|
+
// the count and the title can never disagree after a model addition.
|
|
52
|
+
expect(LLM_MODELS).toHaveLength(EXPECTED_MODEL_IDS.length)
|
|
29
53
|
})
|
|
30
54
|
|
|
31
55
|
it("each model has all required fields", () => {
|
|
@@ -106,25 +130,7 @@ describe("LLM_MODEL_IDS", () => {
|
|
|
106
130
|
})
|
|
107
131
|
|
|
108
132
|
it("contains all expected model ids", () => {
|
|
109
|
-
|
|
110
|
-
"gemini-3-flash",
|
|
111
|
-
"gemini-3.6-flash",
|
|
112
|
-
"claude-haiku-4.5",
|
|
113
|
-
"claude-sonnet-4.6",
|
|
114
|
-
"gpt-5.2",
|
|
115
|
-
"gemini-3.1-pro",
|
|
116
|
-
"claude-opus-4.7",
|
|
117
|
-
"gpt-5.4",
|
|
118
|
-
"gpt-5.5",
|
|
119
|
-
"gpt-5.6-luna",
|
|
120
|
-
"gpt-5.6-terra",
|
|
121
|
-
"gpt-5.6-sol",
|
|
122
|
-
"claude-sonnet-5",
|
|
123
|
-
"claude-opus-4.8",
|
|
124
|
-
"claude-opus-5",
|
|
125
|
-
"claude-fable-5",
|
|
126
|
-
]
|
|
127
|
-
expect(LLM_MODEL_IDS).toEqual(expected)
|
|
133
|
+
expect(LLM_MODEL_IDS).toEqual(EXPECTED_MODEL_IDS)
|
|
128
134
|
})
|
|
129
135
|
})
|
|
130
136
|
|
|
@@ -522,6 +528,25 @@ describe("reasoning effort registry", () => {
|
|
|
522
528
|
expect(getLlmTier("gpt-5.5")).toBe("premium")
|
|
523
529
|
expect(getLlmTier("gemini-3.6-flash")).toBe("economy")
|
|
524
530
|
expect(getLlmTier("claude-fable-5")).toBe("premium")
|
|
531
|
+
expect(getLlmTier("claude-opus-5")).toBe("premium")
|
|
532
|
+
})
|
|
533
|
+
|
|
534
|
+
it("claude-opus-5 carries the full ladder, its own direct fallback, and thinkingDefaultOn", () => {
|
|
535
|
+
const m = getLlmModel("claude-opus-5")
|
|
536
|
+
expect(m?.reasoningEfforts).toEqual(["low", "medium", "high", "xhigh", "max"])
|
|
537
|
+
expect(m?.directFallbackModel).toBe("claude-opus-5")
|
|
538
|
+
// Load-bearing: consumers floor max_tokens off this flag, because Opus 5
|
|
539
|
+
// reasons even with NO thinking param sent. Dropping it silently
|
|
540
|
+
// reintroduces truncated answers at Effort=Auto on a premium model.
|
|
541
|
+
expect(m?.thinkingDefaultOn).toBe(true)
|
|
542
|
+
})
|
|
543
|
+
|
|
544
|
+
it("thinkingDefaultOn is set only where the vendor default actually reasons", () => {
|
|
545
|
+
// Opus 4.8 / 4.7 and Sonnet 5 do NOT reason when `thinking` is omitted —
|
|
546
|
+
// flagging them would inflate every call's cap for no reason.
|
|
547
|
+
for (const id of ["claude-opus-4.8", "claude-opus-4.7", "claude-sonnet-5", "claude-sonnet-4.6"]) {
|
|
548
|
+
expect(getLlmModel(id)?.thinkingDefaultOn, id).toBeUndefined()
|
|
549
|
+
}
|
|
525
550
|
})
|
|
526
551
|
|
|
527
552
|
it("gemini-3.6-flash exposes the KIE low/high thinking levels; claude-fable-5 the full Claude ladder", () => {
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest"
|
|
2
|
+
import {
|
|
3
|
+
clampSmartCutWindow,
|
|
4
|
+
SMART_CUT_WINDOW_MIN,
|
|
5
|
+
SMART_CUT_WINDOW_MAX,
|
|
6
|
+
SMART_CUT_WINDOW_DEFAULT,
|
|
7
|
+
} from "../smart-cut-windows.js"
|
|
8
|
+
|
|
9
|
+
describe("smart-cut search windows", () => {
|
|
10
|
+
it("keeps in-range integers untouched", () => {
|
|
11
|
+
for (const n of [1, 4, 8, 12, 24]) expect(clampSmartCutWindow(n)).toBe(n)
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it("clamps to the product bounds instead of erroring", () => {
|
|
15
|
+
expect(clampSmartCutWindow(999)).toBe(SMART_CUT_WINDOW_MAX)
|
|
16
|
+
expect(clampSmartCutWindow(25)).toBe(SMART_CUT_WINDOW_MAX)
|
|
17
|
+
expect(clampSmartCutWindow(0)).toBe(SMART_CUT_WINDOW_MIN)
|
|
18
|
+
expect(clampSmartCutWindow(-5)).toBe(SMART_CUT_WINDOW_MIN)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it("rounds fractional input (a number <input> can emit one)", () => {
|
|
22
|
+
expect(clampSmartCutWindow(8.4)).toBe(8)
|
|
23
|
+
expect(clampSmartCutWindow(8.6)).toBe(9)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('returns undefined — "use the engine default" — for anything non-numeric', () => {
|
|
27
|
+
// Load-bearing: undefined means the field is OMITTED from the request, so
|
|
28
|
+
// the engine applies its own 8/8. Coercing junk to a NUMBER instead would
|
|
29
|
+
// silently run a search width the user never chose.
|
|
30
|
+
for (const v of [undefined, null, NaN, Infinity, "8", "", {}, []]) {
|
|
31
|
+
expect(clampSmartCutWindow(v), String(v)).toBeUndefined()
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it("bounds are coherent and the default sits inside them", () => {
|
|
36
|
+
expect(SMART_CUT_WINDOW_MIN).toBeLessThan(SMART_CUT_WINDOW_MAX)
|
|
37
|
+
expect(SMART_CUT_WINDOW_DEFAULT).toBeGreaterThanOrEqual(SMART_CUT_WINDOW_MIN)
|
|
38
|
+
expect(SMART_CUT_WINDOW_DEFAULT).toBeLessThanOrEqual(SMART_CUT_WINDOW_MAX)
|
|
39
|
+
// The engine route accepts up to 48; the product cap must stay within it
|
|
40
|
+
// or a clamped value would still 400 at the boundary we were protecting.
|
|
41
|
+
expect(SMART_CUT_WINDOW_MAX).toBeLessThanOrEqual(48)
|
|
42
|
+
})
|
|
43
|
+
})
|
package/src/index.ts
CHANGED
|
@@ -889,5 +889,8 @@ export * from "./video-analysis.js"
|
|
|
889
889
|
// --- Video-analysis pricing (duration buckets + structural credit formula) ---
|
|
890
890
|
export * from "./video-analysis-pricing.js"
|
|
891
891
|
|
|
892
|
+
// --- Smart-cut best-pair search windows (shared bound + clamp) ---
|
|
893
|
+
export * from "./smart-cut-windows.js"
|
|
894
|
+
|
|
892
895
|
export * from "./entity-asset-types.js"
|
|
893
896
|
export * from "./hint-graph-types.js"
|
package/src/llm-models.ts
CHANGED
|
@@ -57,6 +57,20 @@ export interface LlmModelDef {
|
|
|
57
57
|
supportsTemperature?: false
|
|
58
58
|
/** Claude-only: KIE is the preferred routing, direct Anthropic the fallback. */
|
|
59
59
|
preferKie?: true
|
|
60
|
+
/**
|
|
61
|
+
* true = the model reasons even when NO thinking parameter is sent, so its
|
|
62
|
+
* reasoning tokens share the `max_tokens` budget on EVERY call — not just
|
|
63
|
+
* effort-bearing ones. Claude Opus 5 flipped this default (on Opus 4.8/4.7
|
|
64
|
+
* and Sonnet 5, omitting `thinking` means no thinking at all).
|
|
65
|
+
*
|
|
66
|
+
* Consumers MUST give such a model output headroom regardless of the
|
|
67
|
+
* requested effort, or reasoning silently eats a small legacy cap and the
|
|
68
|
+
* answer truncates with `stop_reason: max_tokens` — a paid-for empty reply,
|
|
69
|
+
* not an error. `deriveParams` (llm-client.ts) and the film-pipeline's
|
|
70
|
+
* `callLLM` both floor on this flag; keep it in sync with the vendor's
|
|
71
|
+
* documented default rather than inferring it from the model name.
|
|
72
|
+
*/
|
|
73
|
+
thinkingDefaultOn?: true
|
|
60
74
|
}
|
|
61
75
|
|
|
62
76
|
export const LLM_MODELS: readonly LlmModelDef[] = [
|
|
@@ -143,7 +157,11 @@ export const LLM_MODELS: readonly LlmModelDef[] = [
|
|
|
143
157
|
{
|
|
144
158
|
id: "claude-opus-4.7",
|
|
145
159
|
displayName: "Claude Opus 4.7",
|
|
146
|
-
desc
|
|
160
|
+
// Superlatives belong to the CURRENT top model only — `desc` renders in every
|
|
161
|
+
// model picker, so leaving "highest quality" on an older Opus steers
|
|
162
|
+
// quality-critical work backwards (all three Opus entries bill premium, so
|
|
163
|
+
// the mis-steer is invisible on the invoice).
|
|
164
|
+
desc: "Older Opus, complex tasks",
|
|
147
165
|
tier: "premium",
|
|
148
166
|
kieFormat: "messages",
|
|
149
167
|
kieSlugOrModel: "claude-opus-4-7",
|
|
@@ -276,6 +294,9 @@ export const LLM_MODELS: readonly LlmModelDef[] = [
|
|
|
276
294
|
reasoningEfforts: ["low", "medium", "high", "xhigh", "max"],
|
|
277
295
|
supportsTemperature: false,
|
|
278
296
|
preferKie: true,
|
|
297
|
+
// Opus 5 reasons with NO thinking param sent (vendor default flipped from
|
|
298
|
+
// Opus 4.8/4.7) — so every call needs output headroom, not just xhigh/max.
|
|
299
|
+
thinkingDefaultOn: true,
|
|
279
300
|
},
|
|
280
301
|
{
|
|
281
302
|
id: "claude-fable-5",
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smart-cut best-pair SEARCH WINDOWS — the shared bound + clamp for
|
|
3
|
+
* generate-video-pro's `smartCutFramesPrev` / `smartCutFramesNext`.
|
|
4
|
+
*
|
|
5
|
+
* What they do: the best-pair matcher (the mode the node calls "legacy-8x8")
|
|
6
|
+
* PSNR-compares the last N frames of a segment against the first M of the
|
|
7
|
+
* next, ends the previous clip ON the best match and starts the next right
|
|
8
|
+
* AFTER its twin — so the duplicated frame plays once and motion stays
|
|
9
|
+
* continuous. N and M are those windows. Absent → the engine's own 8/8
|
|
10
|
+
* default, which is byte-identical to the behavior before they were
|
|
11
|
+
* exposed.
|
|
12
|
+
*
|
|
13
|
+
* Why wider helps: a continuation can re-enact a longer stretch of the
|
|
14
|
+
* previous tail than 8 frames covers, and a match outside the window is
|
|
15
|
+
* simply never found — the boundary silently falls back to the fixed
|
|
16
|
+
* freeze-trims. recast pins 24/24 for exactly this reason.
|
|
17
|
+
*
|
|
18
|
+
* Why a shared clamp: the canvas node (single-node Run) and the orchestrator
|
|
19
|
+
* (workflow Run) are two independent send paths into the same engine route,
|
|
20
|
+
* whose Zod schema rejects out-of-range values. Deriving both from this one
|
|
21
|
+
* function means a stale or hand-edited node value degrades to a legal
|
|
22
|
+
* request instead of 400-ing an entire multi-segment run at finalize time —
|
|
23
|
+
* and the two paths cannot drift apart.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/** Widest window the UI offers. The engine route itself accepts up to 48;
|
|
27
|
+
* 24 is the product cap — it already covers a full second of re-enactment
|
|
28
|
+
* at 24fps, and every frame added past the real overlap only costs match
|
|
29
|
+
* time and invites a spurious pairing. */
|
|
30
|
+
export const SMART_CUT_WINDOW_MAX = 24
|
|
31
|
+
/** Narrowest meaningful window — one frame each side. */
|
|
32
|
+
export const SMART_CUT_WINDOW_MIN = 1
|
|
33
|
+
/** The engine's default when the field is absent. Documented here so the UI
|
|
34
|
+
* can show it as the placeholder without hardcoding a second copy. */
|
|
35
|
+
export const SMART_CUT_WINDOW_DEFAULT = 8
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Narrow an arbitrary node value to a legal window, or `undefined` to mean
|
|
39
|
+
* "let the engine use its default". Non-numbers, NaN, and non-integers all
|
|
40
|
+
* collapse to `undefined` rather than to a guessed number: a malformed value
|
|
41
|
+
* should fall back to the proven default, not silently pick a different
|
|
42
|
+
* search width than the user asked for.
|
|
43
|
+
*/
|
|
44
|
+
export function clampSmartCutWindow(value: unknown): number | undefined {
|
|
45
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return undefined
|
|
46
|
+
const n = Math.round(value)
|
|
47
|
+
if (n < SMART_CUT_WINDOW_MIN) return SMART_CUT_WINDOW_MIN
|
|
48
|
+
if (n > SMART_CUT_WINDOW_MAX) return SMART_CUT_WINDOW_MAX
|
|
49
|
+
return n
|
|
50
|
+
}
|