@nodaro/shared 1.6.0 → 1.9.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 +192 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +33 -6
- package/dist/index.d.ts +33 -6
- package/dist/index.js +189 -28
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/llm-models.test.ts +91 -6
- package/src/index.ts +5 -0
- package/src/llm-models.ts +139 -6
- package/src/model-catalog.ts +18 -13
- package/src/model-constants.ts +33 -2
- package/src/node-mappable-fields.ts +6 -0
- package/src/producer-types.ts +10 -0
- package/src/selector.ts +10 -1
package/package.json
CHANGED
|
@@ -3,11 +3,13 @@ import {
|
|
|
3
3
|
LLM_MODEL_IDS,
|
|
4
4
|
LLM_FEATURE_DEFAULTS,
|
|
5
5
|
STRUCTURED_VISION_MODELS,
|
|
6
|
+
LLM_REASONING_EFFORTS,
|
|
6
7
|
getLlmModel,
|
|
7
8
|
getLlmTier,
|
|
8
9
|
buildLlmCreditIdentifier,
|
|
9
10
|
resolveLlmCreditId,
|
|
10
11
|
motionGraphicsFeature,
|
|
12
|
+
effectiveReasoningEffort,
|
|
11
13
|
} from "../llm-models.js"
|
|
12
14
|
import type { LlmModelDef, LlmTier, LlmFeature } from "../llm-models.js"
|
|
13
15
|
|
|
@@ -21,8 +23,8 @@ import type { LlmModelDef, LlmTier, LlmFeature } from "../llm-models.js"
|
|
|
21
23
|
// LLM_MODELS data integrity
|
|
22
24
|
// ---------------------------------------------------------------------------
|
|
23
25
|
describe("LLM_MODELS data integrity", () => {
|
|
24
|
-
it("should have exactly
|
|
25
|
-
expect(LLM_MODELS).toHaveLength(
|
|
26
|
+
it("should have exactly 13 models", () => {
|
|
27
|
+
expect(LLM_MODELS).toHaveLength(13)
|
|
26
28
|
})
|
|
27
29
|
|
|
28
30
|
it("each model has all required fields", () => {
|
|
@@ -57,14 +59,14 @@ describe("LLM_MODELS data integrity", () => {
|
|
|
57
59
|
expect(new Set(ids).size).toBe(ids.length)
|
|
58
60
|
})
|
|
59
61
|
|
|
60
|
-
it("has
|
|
62
|
+
it("has 3 economy, 4 standard, 6 premium models", () => {
|
|
61
63
|
const tierCounts: Record<LlmTier, number> = { economy: 0, standard: 0, premium: 0 }
|
|
62
64
|
for (const model of LLM_MODELS) {
|
|
63
65
|
tierCounts[model.tier]++
|
|
64
66
|
}
|
|
65
|
-
expect(tierCounts.economy).toBe(
|
|
66
|
-
expect(tierCounts.standard).toBe(
|
|
67
|
-
expect(tierCounts.premium).toBe(
|
|
67
|
+
expect(tierCounts.economy).toBe(3)
|
|
68
|
+
expect(tierCounts.standard).toBe(4)
|
|
69
|
+
expect(tierCounts.premium).toBe(6)
|
|
68
70
|
})
|
|
69
71
|
|
|
70
72
|
it("all three kieFormats are represented", () => {
|
|
@@ -111,6 +113,12 @@ describe("LLM_MODEL_IDS", () => {
|
|
|
111
113
|
"gemini-3.1-pro",
|
|
112
114
|
"claude-opus-4.7",
|
|
113
115
|
"gpt-5.4",
|
|
116
|
+
"gpt-5.5",
|
|
117
|
+
"gpt-5.6-luna",
|
|
118
|
+
"gpt-5.6-terra",
|
|
119
|
+
"gpt-5.6-sol",
|
|
120
|
+
"claude-sonnet-5",
|
|
121
|
+
"claude-opus-4.8",
|
|
114
122
|
]
|
|
115
123
|
expect(LLM_MODEL_IDS).toEqual(expected)
|
|
116
124
|
})
|
|
@@ -420,6 +428,8 @@ describe("STRUCTURED_VISION_MODELS", () => {
|
|
|
420
428
|
"claude-sonnet-4.6",
|
|
421
429
|
"gemini-3-flash",
|
|
422
430
|
"gemini-3.1-pro",
|
|
431
|
+
"claude-sonnet-5",
|
|
432
|
+
"claude-opus-4.8",
|
|
423
433
|
].sort(),
|
|
424
434
|
)
|
|
425
435
|
})
|
|
@@ -443,3 +453,78 @@ describe("STRUCTURED_VISION_MODELS", () => {
|
|
|
443
453
|
}
|
|
444
454
|
})
|
|
445
455
|
})
|
|
456
|
+
|
|
457
|
+
// ---------------------------------------------------------------------------
|
|
458
|
+
// Reasoning effort registry (GPT-5.6 / Claude Sonnet 5 / Claude Opus 4.8)
|
|
459
|
+
// grok-4.5 is DEFERRED — its chat endpoint is not live on the provider yet
|
|
460
|
+
// (2026-07-13); its tests are intentionally omitted here.
|
|
461
|
+
// ---------------------------------------------------------------------------
|
|
462
|
+
describe("reasoning effort registry", () => {
|
|
463
|
+
it("every reasoningEfforts list is a subset of the superset, in ascending order", () => {
|
|
464
|
+
const rank = Object.fromEntries(LLM_REASONING_EFFORTS.map((e, i) => [e, i]))
|
|
465
|
+
for (const m of LLM_MODELS) {
|
|
466
|
+
for (const e of m.reasoningEfforts ?? []) expect(LLM_REASONING_EFFORTS).toContain(e)
|
|
467
|
+
const ranks = (m.reasoningEfforts ?? []).map((e) => rank[e])
|
|
468
|
+
expect([...ranks].sort((a, b) => a - b)).toEqual(ranks)
|
|
469
|
+
}
|
|
470
|
+
})
|
|
471
|
+
it("new models exist with expected tiers", () => {
|
|
472
|
+
expect(getLlmTier("gpt-5.6-luna")).toBe("economy")
|
|
473
|
+
expect(getLlmTier("gpt-5.6-terra")).toBe("standard")
|
|
474
|
+
expect(getLlmTier("gpt-5.6-sol")).toBe("premium")
|
|
475
|
+
expect(getLlmTier("claude-sonnet-5")).toBe("standard")
|
|
476
|
+
expect(getLlmTier("claude-opus-4.8")).toBe("premium")
|
|
477
|
+
expect(getLlmTier("gpt-5.5")).toBe("premium")
|
|
478
|
+
})
|
|
479
|
+
})
|
|
480
|
+
|
|
481
|
+
describe("effectiveReasoningEffort", () => {
|
|
482
|
+
it("passes through a supported level", () => {
|
|
483
|
+
expect(effectiveReasoningEffort("claude-sonnet-5", "max")).toBe("max")
|
|
484
|
+
})
|
|
485
|
+
it("clamps down to the highest supported level ≤ requested", () => {
|
|
486
|
+
expect(effectiveReasoningEffort("gpt-5.4", "xhigh")).toBe("high")
|
|
487
|
+
})
|
|
488
|
+
it("returns undefined when the model has no levels", () => {
|
|
489
|
+
expect(effectiveReasoningEffort("gemini-3-flash", "high")).toBeUndefined()
|
|
490
|
+
})
|
|
491
|
+
it("returns undefined for none on Claude (below its lowest level)", () => {
|
|
492
|
+
expect(effectiveReasoningEffort("claude-sonnet-5", "none")).toBeUndefined()
|
|
493
|
+
})
|
|
494
|
+
it("returns undefined for undefined/garbage input", () => {
|
|
495
|
+
expect(effectiveReasoningEffort("claude-sonnet-5", undefined)).toBeUndefined()
|
|
496
|
+
expect(effectiveReasoningEffort("claude-sonnet-5", "turbo")).toBeUndefined()
|
|
497
|
+
})
|
|
498
|
+
})
|
|
499
|
+
|
|
500
|
+
describe("buildLlmCreditIdentifier effort bump (xhigh/max only)", () => {
|
|
501
|
+
it("economy + max → standard (bare feature)", () => {
|
|
502
|
+
expect(buildLlmCreditIdentifier("llm-chat", "gpt-5.6-luna", "max")).toBe("llm-chat")
|
|
503
|
+
})
|
|
504
|
+
it("standard + xhigh → premium", () => {
|
|
505
|
+
expect(buildLlmCreditIdentifier("llm-chat", "gpt-5.6-terra", "xhigh")).toBe("llm-chat:premium")
|
|
506
|
+
})
|
|
507
|
+
it("premium + max stays premium", () => {
|
|
508
|
+
expect(buildLlmCreditIdentifier("llm-chat", "gpt-5.6-sol", "max")).toBe("llm-chat:premium")
|
|
509
|
+
})
|
|
510
|
+
it("high never bumps", () => {
|
|
511
|
+
expect(buildLlmCreditIdentifier("llm-chat", "claude-sonnet-5", "high")).toBe("llm-chat")
|
|
512
|
+
})
|
|
513
|
+
it("clamp on a partial-list standard model never bumps (sonnet-4.6 @ xhigh → high)", () => {
|
|
514
|
+
expect(buildLlmCreditIdentifier("llm-chat", "claude-sonnet-4.6", "xhigh")).toBe("llm-chat")
|
|
515
|
+
})
|
|
516
|
+
it("bump uses the CLAMPED effort (xhigh on a low/medium/high model clamps to high → no bump)", () => {
|
|
517
|
+
expect(buildLlmCreditIdentifier("llm-chat", "gpt-5.4", "xhigh")).toBe("llm-chat:premium")
|
|
518
|
+
// gpt-5.4 is premium anyway; the real clamp case:
|
|
519
|
+
expect(buildLlmCreditIdentifier("llm-chat", "gemini-3-flash", "max")).toBe("llm-chat:economy")
|
|
520
|
+
})
|
|
521
|
+
it("back-compat: no effort arg → identical to today for every model", () => {
|
|
522
|
+
for (const m of LLM_MODELS) {
|
|
523
|
+
const before = m.tier === "standard" ? "x" : `x:${m.tier}`
|
|
524
|
+
expect(buildLlmCreditIdentifier("x", m.id)).toBe(before)
|
|
525
|
+
}
|
|
526
|
+
})
|
|
527
|
+
it("resolveLlmCreditId reads reasoningEffort from the raw body", () => {
|
|
528
|
+
expect(resolveLlmCreditId("llm-chat", { llmModel: "gpt-5.6-terra", reasoningEffort: "max" })).toBe("llm-chat:premium")
|
|
529
|
+
})
|
|
530
|
+
})
|
package/src/index.ts
CHANGED
|
@@ -92,6 +92,7 @@ export {
|
|
|
92
92
|
SEED_SUPPORT,
|
|
93
93
|
RENDERING_SPEED_SUPPORT,
|
|
94
94
|
GUIDANCE_SCALE_SUPPORT,
|
|
95
|
+
SEEDANCE_2_PROVIDERS,
|
|
95
96
|
SEEDANCE_2_REF_LIMITS,
|
|
96
97
|
SEEDANCE_2_EXTEND_STITCH,
|
|
97
98
|
NATIVE_ADAPTIVE_ASPECT,
|
|
@@ -251,16 +252,20 @@ export {
|
|
|
251
252
|
VIDEO_ANALYSIS_LLM_MODELS,
|
|
252
253
|
LLM_FEATURE_DEFAULTS,
|
|
253
254
|
LLM_MODALITY_CAPS,
|
|
255
|
+
LLM_REASONING_EFFORTS,
|
|
256
|
+
EFFORT_TIER_BUMP,
|
|
254
257
|
getLlmModel,
|
|
255
258
|
getLlmTier,
|
|
256
259
|
getLlmModalityCaps,
|
|
257
260
|
buildLlmCreditIdentifier,
|
|
258
261
|
resolveLlmCreditId,
|
|
259
262
|
motionGraphicsFeature,
|
|
263
|
+
effectiveReasoningEffort,
|
|
260
264
|
type LlmTier,
|
|
261
265
|
type LlmFeature,
|
|
262
266
|
type KieApiFormat,
|
|
263
267
|
type LlmModelDef,
|
|
268
|
+
type LlmReasoningEffort,
|
|
264
269
|
} from "./llm-models.js"
|
|
265
270
|
|
|
266
271
|
export {
|
package/src/llm-models.ts
CHANGED
|
@@ -16,6 +16,12 @@
|
|
|
16
16
|
export type LlmTier = "economy" | "standard" | "premium"
|
|
17
17
|
export type KieApiFormat = "chat-completions" | "messages" | "responses"
|
|
18
18
|
|
|
19
|
+
export const LLM_REASONING_EFFORTS = ["none", "low", "medium", "high", "xhigh", "max"] as const
|
|
20
|
+
export type LlmReasoningEffort = (typeof LLM_REASONING_EFFORTS)[number]
|
|
21
|
+
/** Levels that bill one tier up. `high` is the Claude-family server default — it never bumps. */
|
|
22
|
+
export const EFFORT_TIER_BUMP: ReadonlySet<LlmReasoningEffort> = new Set(["xhigh", "max"])
|
|
23
|
+
const EFFORT_RANK: Record<LlmReasoningEffort, number> = { none: 0, low: 1, medium: 2, high: 3, xhigh: 4, max: 5 }
|
|
24
|
+
|
|
19
25
|
export interface LlmModelDef {
|
|
20
26
|
id: string
|
|
21
27
|
displayName: string
|
|
@@ -41,6 +47,12 @@ export interface LlmModelDef {
|
|
|
41
47
|
structuredOutputMode?: "anthropic-tool" | "kie-response-format"
|
|
42
48
|
/** If set, fallback to direct Anthropic SDK with this model ID when KIE.ai fails */
|
|
43
49
|
directFallbackModel?: string
|
|
50
|
+
/** Effort levels this model accepts (ascending). Absent/empty = no effort lever, picker hidden. */
|
|
51
|
+
reasoningEfforts?: readonly LlmReasoningEffort[]
|
|
52
|
+
/** false = model rejects `temperature` (Claude 5-era, GPT-5.6). Absent = accepts. */
|
|
53
|
+
supportsTemperature?: false
|
|
54
|
+
/** Claude-only: KIE is the preferred routing, direct Anthropic the fallback. */
|
|
55
|
+
preferKie?: true
|
|
44
56
|
}
|
|
45
57
|
|
|
46
58
|
export const LLM_MODELS: readonly LlmModelDef[] = [
|
|
@@ -81,6 +93,7 @@ export const LLM_MODELS: readonly LlmModelDef[] = [
|
|
|
81
93
|
supportsImages: true,
|
|
82
94
|
maxOutputTokens: 16384,
|
|
83
95
|
directFallbackModel: "claude-sonnet-4-6",
|
|
96
|
+
reasoningEfforts: ["low", "medium", "high", "max"],
|
|
84
97
|
},
|
|
85
98
|
{
|
|
86
99
|
id: "gpt-5.2",
|
|
@@ -117,6 +130,9 @@ export const LLM_MODELS: readonly LlmModelDef[] = [
|
|
|
117
130
|
supportsImages: true,
|
|
118
131
|
maxOutputTokens: 16384,
|
|
119
132
|
directFallbackModel: "claude-opus-4-7",
|
|
133
|
+
reasoningEfforts: ["low", "medium", "high", "xhigh", "max"],
|
|
134
|
+
supportsTemperature: false,
|
|
135
|
+
preferKie: true,
|
|
120
136
|
},
|
|
121
137
|
{
|
|
122
138
|
id: "gpt-5.4",
|
|
@@ -128,6 +144,92 @@ export const LLM_MODELS: readonly LlmModelDef[] = [
|
|
|
128
144
|
vendor: "openai",
|
|
129
145
|
supportsImages: true,
|
|
130
146
|
maxOutputTokens: 16384,
|
|
147
|
+
reasoningEfforts: ["low", "medium", "high"],
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
id: "gpt-5.5",
|
|
151
|
+
displayName: "GPT-5.5",
|
|
152
|
+
desc: "Previous flagship GPT, deep reasoning",
|
|
153
|
+
tier: "premium",
|
|
154
|
+
kieFormat: "responses",
|
|
155
|
+
kieSlugOrModel: "gpt-5-5",
|
|
156
|
+
vendor: "openai",
|
|
157
|
+
supportsImages: true,
|
|
158
|
+
maxOutputTokens: 16384,
|
|
159
|
+
reasoningEfforts: ["none", "low", "medium", "high", "xhigh", "max"],
|
|
160
|
+
supportsTemperature: false,
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
id: "gpt-5.6-luna",
|
|
164
|
+
displayName: "GPT-5.6 Luna",
|
|
165
|
+
desc: "Fastest GPT-5.6, high-volume workloads",
|
|
166
|
+
tier: "economy",
|
|
167
|
+
kieFormat: "responses",
|
|
168
|
+
kieSlugOrModel: "gpt-5-6-luna",
|
|
169
|
+
vendor: "openai",
|
|
170
|
+
supportsImages: true,
|
|
171
|
+
maxOutputTokens: 16384,
|
|
172
|
+
reasoningEfforts: ["none", "low", "medium", "high", "xhigh", "max"],
|
|
173
|
+
supportsTemperature: false,
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
id: "gpt-5.6-terra",
|
|
177
|
+
displayName: "GPT-5.6 Terra",
|
|
178
|
+
desc: "Balanced GPT-5.6 for production work",
|
|
179
|
+
tier: "standard",
|
|
180
|
+
kieFormat: "responses",
|
|
181
|
+
kieSlugOrModel: "gpt-5-6-terra",
|
|
182
|
+
vendor: "openai",
|
|
183
|
+
supportsImages: true,
|
|
184
|
+
maxOutputTokens: 16384,
|
|
185
|
+
reasoningEfforts: ["none", "low", "medium", "high", "xhigh", "max"],
|
|
186
|
+
supportsTemperature: false,
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
id: "gpt-5.6-sol",
|
|
190
|
+
displayName: "GPT-5.6 Sol",
|
|
191
|
+
desc: "Flagship GPT-5.6, deepest reasoning",
|
|
192
|
+
tier: "premium",
|
|
193
|
+
kieFormat: "responses",
|
|
194
|
+
kieSlugOrModel: "gpt-5-6-sol",
|
|
195
|
+
vendor: "openai",
|
|
196
|
+
supportsImages: true,
|
|
197
|
+
maxOutputTokens: 16384,
|
|
198
|
+
reasoningEfforts: ["none", "low", "medium", "high", "xhigh", "max"],
|
|
199
|
+
supportsTemperature: false,
|
|
200
|
+
},
|
|
201
|
+
// grok-4.5 deferred — KIE chat endpoint not yet live (2026-07-13); add entry + rate row + docs when it activates.
|
|
202
|
+
{
|
|
203
|
+
id: "claude-sonnet-5",
|
|
204
|
+
displayName: "Claude Sonnet 5",
|
|
205
|
+
desc: "Near-Opus quality at Sonnet cost",
|
|
206
|
+
tier: "standard",
|
|
207
|
+
kieFormat: "messages",
|
|
208
|
+
kieSlugOrModel: "claude-sonnet-5",
|
|
209
|
+
vendor: "anthropic",
|
|
210
|
+
structuredOutputMode: "anthropic-tool",
|
|
211
|
+
supportsImages: true,
|
|
212
|
+
maxOutputTokens: 16384,
|
|
213
|
+
directFallbackModel: "claude-sonnet-5",
|
|
214
|
+
reasoningEfforts: ["low", "medium", "high", "xhigh", "max"],
|
|
215
|
+
supportsTemperature: false,
|
|
216
|
+
preferKie: true,
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
id: "claude-opus-4.8",
|
|
220
|
+
displayName: "Claude Opus 4.8",
|
|
221
|
+
desc: "Most capable Claude, long-horizon work",
|
|
222
|
+
tier: "premium",
|
|
223
|
+
kieFormat: "messages",
|
|
224
|
+
kieSlugOrModel: "claude-opus-4-8",
|
|
225
|
+
vendor: "anthropic",
|
|
226
|
+
structuredOutputMode: "anthropic-tool",
|
|
227
|
+
supportsImages: true,
|
|
228
|
+
maxOutputTokens: 16384,
|
|
229
|
+
directFallbackModel: "claude-opus-4-8",
|
|
230
|
+
reasoningEfforts: ["low", "medium", "high", "xhigh", "max"],
|
|
231
|
+
supportsTemperature: false,
|
|
232
|
+
preferKie: true,
|
|
131
233
|
},
|
|
132
234
|
] as const
|
|
133
235
|
|
|
@@ -201,6 +303,12 @@ export const LLM_MODALITY_CAPS: Record<string, { image: boolean; video: boolean;
|
|
|
201
303
|
"claude-opus-4.7": { image: true, video: false, audio: false },
|
|
202
304
|
"gpt-5.2": { image: true, video: false, audio: false },
|
|
203
305
|
"gpt-5.4": { image: true, video: false, audio: false },
|
|
306
|
+
"gpt-5.5": { image: true, video: false, audio: false },
|
|
307
|
+
"gpt-5.6-luna": { image: true, video: false, audio: false },
|
|
308
|
+
"gpt-5.6-terra": { image: true, video: false, audio: false },
|
|
309
|
+
"gpt-5.6-sol": { image: true, video: false, audio: false },
|
|
310
|
+
"claude-sonnet-5": { image: true, video: false, audio: false },
|
|
311
|
+
"claude-opus-4.8": { image: true, video: false, audio: false },
|
|
204
312
|
}
|
|
205
313
|
|
|
206
314
|
/** Capability lookup with safe default — unknown models get image-only. */
|
|
@@ -217,26 +325,51 @@ export function getLlmTier(id: string): LlmTier {
|
|
|
217
325
|
return getLlmModel(id)?.tier ?? "standard"
|
|
218
326
|
}
|
|
219
327
|
|
|
328
|
+
/** Highest level the model supports that is ≤ the requested level; undefined = treat as Auto. */
|
|
329
|
+
export function effectiveReasoningEffort(
|
|
330
|
+
modelId: string | undefined,
|
|
331
|
+
requested?: string,
|
|
332
|
+
): LlmReasoningEffort | undefined {
|
|
333
|
+
if (!requested || !(requested in EFFORT_RANK)) return undefined
|
|
334
|
+
const levels = getLlmModel(modelId ?? "")?.reasoningEfforts
|
|
335
|
+
if (!levels || levels.length === 0) return undefined
|
|
336
|
+
const req = requested as LlmReasoningEffort
|
|
337
|
+
let best: LlmReasoningEffort | undefined
|
|
338
|
+
for (const l of levels) {
|
|
339
|
+
if (EFFORT_RANK[l] <= EFFORT_RANK[req] && (best === undefined || EFFORT_RANK[l] > EFFORT_RANK[best])) best = l
|
|
340
|
+
}
|
|
341
|
+
return best
|
|
342
|
+
}
|
|
343
|
+
|
|
220
344
|
/**
|
|
221
345
|
* Build a composite credit identifier for an LLM feature.
|
|
222
346
|
* - economy tier → "ai-writer:economy"
|
|
223
347
|
* - standard tier → "ai-writer" (no suffix — backward compatible)
|
|
224
348
|
* - premium tier → "ai-writer:premium"
|
|
349
|
+
* A reasoning effort of "xhigh" or "max" (after clamping to what the model
|
|
350
|
+
* actually supports) bills one tier up (economy→standard, standard→premium;
|
|
351
|
+
* premium stays premium). `high` is the Claude-family server default and
|
|
352
|
+
* never bumps.
|
|
225
353
|
*/
|
|
226
|
-
export function buildLlmCreditIdentifier(feature: string, modelId?: string): string {
|
|
354
|
+
export function buildLlmCreditIdentifier(feature: string, modelId?: string, reasoningEffort?: string): string {
|
|
227
355
|
if (!modelId) return feature
|
|
228
|
-
|
|
356
|
+
let tier = getLlmTier(modelId)
|
|
357
|
+
const eff = effectiveReasoningEffort(modelId, reasoningEffort)
|
|
358
|
+
if (eff !== undefined && EFFORT_TIER_BUMP.has(eff)) {
|
|
359
|
+
if (tier === "economy") tier = "standard"
|
|
360
|
+
else if (tier === "standard") tier = "premium"
|
|
361
|
+
}
|
|
229
362
|
if (tier === "standard") return feature
|
|
230
363
|
return `${feature}:${tier}`
|
|
231
364
|
}
|
|
232
365
|
|
|
233
366
|
/**
|
|
234
|
-
* Resolve llmModel from raw body for creditGuard preHandler
|
|
235
|
-
* Returns the credit identifier for the given feature
|
|
367
|
+
* Resolve llmModel (+ reasoningEffort) from raw body for creditGuard preHandler
|
|
368
|
+
* (before Zod parsing). Returns the credit identifier for the given feature.
|
|
236
369
|
*/
|
|
237
370
|
export function resolveLlmCreditId(feature: string, body: unknown): string {
|
|
238
|
-
const
|
|
239
|
-
return buildLlmCreditIdentifier(feature, llmModel)
|
|
371
|
+
const b = body as Record<string, unknown> | undefined
|
|
372
|
+
return buildLlmCreditIdentifier(feature, b?.llmModel as string | undefined, b?.reasoningEffort as string | undefined)
|
|
240
373
|
}
|
|
241
374
|
|
|
242
375
|
/** Models capable of video-analysis: capability-derived, never hand-listed (route-enum-sync convention). */
|
package/src/model-catalog.ts
CHANGED
|
@@ -187,6 +187,10 @@ const VIDEO_RATIOS_HVS345 = ["16:9", "9:16", "1:1", "4:3", "3:4"] as const
|
|
|
187
187
|
// (docs.kie.ai/market/bytedance/seedance-2). Kept separate from HVS so the
|
|
188
188
|
// wider set can't leak to models that don't support it.
|
|
189
189
|
const VIDEO_RATIOS_SEEDANCE_2 = ["16:9", "9:16", "1:1", "4:3", "3:4", "21:9", "adaptive"] as const
|
|
190
|
+
// HappyHorse 1.1 (t2v + ref2v) — the model's full 9-ratio set incl. 4:5/5:4
|
|
191
|
+
// portrait-social and 21:9/9:21 cinematic (docs.kie.ai/market/happyhorse-1-1).
|
|
192
|
+
// Kept separate so the wider set can't leak to models that don't support it.
|
|
193
|
+
const VIDEO_RATIOS_HAPPYHORSE_11 = ["16:9", "9:16", "1:1", "4:3", "3:4", "4:5", "5:4", "21:9", "9:21"] as const
|
|
190
194
|
|
|
191
195
|
// =============================================================================
|
|
192
196
|
// IMAGE MODELS
|
|
@@ -1377,21 +1381,22 @@ const VIDEO_MODELS: Record<string, ModelCatalogEntry> = {
|
|
|
1377
1381
|
{ identifier: "wan-2.7-t2v", credits: 19, note: "5s 720p default" },
|
|
1378
1382
|
],
|
|
1379
1383
|
},
|
|
1380
|
-
// ── HappyHorse
|
|
1384
|
+
// ── HappyHorse (1.1 — ids kept version-less; repointed in place when KIE
|
|
1385
|
+
// delisted 1.0, same param surface, so saved workflows keep working) ──
|
|
1381
1386
|
"happyhorse": {
|
|
1382
1387
|
id: "happyhorse",
|
|
1383
1388
|
kind: "video",
|
|
1384
1389
|
modes: ["t2v"] as const,
|
|
1385
1390
|
family: "HappyHorse",
|
|
1386
|
-
label: "HappyHorse",
|
|
1391
|
+
label: "HappyHorse 1.1",
|
|
1387
1392
|
series: "HappyHorse",
|
|
1388
|
-
description: "HappyHorse text-to-video — 3–15s at 720p/1080p.",
|
|
1393
|
+
description: "HappyHorse 1.1 text-to-video — 3–15s at 720p/1080p, 9 aspect ratios incl. 21:9/9:21, per-second pricing.",
|
|
1389
1394
|
useCases: ["motion", "creative"],
|
|
1390
|
-
aspectRatios:
|
|
1395
|
+
aspectRatios: VIDEO_RATIOS_HAPPYHORSE_11,
|
|
1391
1396
|
durations: [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
|
|
1392
1397
|
resolutions: ["720p", "1080p"],
|
|
1393
1398
|
pricing: [
|
|
1394
|
-
{ identifier: "happyhorse", credits:
|
|
1399
|
+
{ identifier: "happyhorse", credits: 29, note: "5s 720p default — per-second: ~5.7 cr/s @720p, ~7.3 cr/s @1080p" },
|
|
1395
1400
|
],
|
|
1396
1401
|
},
|
|
1397
1402
|
"happyhorse-i2v": {
|
|
@@ -1399,14 +1404,14 @@ const VIDEO_MODELS: Record<string, ModelCatalogEntry> = {
|
|
|
1399
1404
|
kind: "video",
|
|
1400
1405
|
modes: ["i2v"] as const,
|
|
1401
1406
|
family: "HappyHorse",
|
|
1402
|
-
label: "HappyHorse I2V",
|
|
1407
|
+
label: "HappyHorse 1.1 I2V",
|
|
1403
1408
|
series: "HappyHorse",
|
|
1404
|
-
description: "HappyHorse image-to-video — 3–15s at 720p/1080p, aspect ratio inferred from input image.",
|
|
1409
|
+
description: "HappyHorse 1.1 image-to-video — 3–15s at 720p/1080p, aspect ratio inferred from input image, per-second pricing.",
|
|
1405
1410
|
useCases: ["motion", "creative"],
|
|
1406
1411
|
durations: [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
|
|
1407
1412
|
resolutions: ["720p", "1080p"],
|
|
1408
1413
|
pricing: [
|
|
1409
|
-
{ identifier: "happyhorse-i2v", credits:
|
|
1414
|
+
{ identifier: "happyhorse-i2v", credits: 29, note: "5s 720p default — per-second: ~5.7 cr/s @720p, ~7.3 cr/s @1080p" },
|
|
1410
1415
|
],
|
|
1411
1416
|
},
|
|
1412
1417
|
"happyhorse-ref2v": {
|
|
@@ -1414,16 +1419,16 @@ const VIDEO_MODELS: Record<string, ModelCatalogEntry> = {
|
|
|
1414
1419
|
kind: "video",
|
|
1415
1420
|
modes: ["i2v"] as const,
|
|
1416
1421
|
family: "HappyHorse",
|
|
1417
|
-
label: "HappyHorse Ref2V",
|
|
1422
|
+
label: "HappyHorse 1.1 Ref2V",
|
|
1418
1423
|
series: "HappyHorse",
|
|
1419
|
-
description: "HappyHorse reference-to-video — 1–9 reference images, 3–15s at 720p/1080p.",
|
|
1424
|
+
description: "HappyHorse 1.1 reference-to-video — 1–9 reference images, 3–15s at 720p/1080p, per-second pricing.",
|
|
1420
1425
|
useCases: ["motion", "reference"],
|
|
1421
|
-
aspectRatios:
|
|
1426
|
+
aspectRatios: VIDEO_RATIOS_HAPPYHORSE_11,
|
|
1422
1427
|
durations: [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
|
|
1423
1428
|
resolutions: ["720p", "1080p"],
|
|
1424
1429
|
features: ["reference-image"],
|
|
1425
1430
|
pricing: [
|
|
1426
|
-
{ identifier: "happyhorse-ref2v", credits:
|
|
1431
|
+
{ identifier: "happyhorse-ref2v", credits: 29, note: "5s 720p default — per-second: ~5.7 cr/s @720p, ~7.3 cr/s @1080p" },
|
|
1427
1432
|
],
|
|
1428
1433
|
},
|
|
1429
1434
|
"happyhorse-edit": {
|
|
@@ -1437,7 +1442,7 @@ const VIDEO_MODELS: Record<string, ModelCatalogEntry> = {
|
|
|
1437
1442
|
useCases: ["v2v", "restyle"],
|
|
1438
1443
|
resolutions: ["720p", "1080p"],
|
|
1439
1444
|
pricing: [
|
|
1440
|
-
{ identifier: "happyhorse-edit", credits:
|
|
1445
|
+
{ identifier: "happyhorse-edit", credits: 35, note: "720p default (5s-equivalent flat rate)" },
|
|
1441
1446
|
],
|
|
1442
1447
|
},
|
|
1443
1448
|
// ── Bytedance video lite/pro ──
|
package/src/model-constants.ts
CHANGED
|
@@ -1077,6 +1077,9 @@ export const DURATION_PRICED_PROVIDERS = new Set([
|
|
|
1077
1077
|
"seedance-2-fast",
|
|
1078
1078
|
"seedance-2-mini",
|
|
1079
1079
|
"grok-imagine-video-1.5",
|
|
1080
|
+
"happyhorse",
|
|
1081
|
+
"happyhorse-i2v",
|
|
1082
|
+
"happyhorse-ref2v",
|
|
1080
1083
|
])
|
|
1081
1084
|
|
|
1082
1085
|
/**
|
|
@@ -1136,12 +1139,18 @@ export const SEEDANCE_2_REF_LIMITS = {
|
|
|
1136
1139
|
* boundary clicks without shifting sync (combineVideos cut+crossfade path).
|
|
1137
1140
|
*/
|
|
1138
1141
|
export const SEEDANCE_2_EXTEND_STITCH = {
|
|
1139
|
-
/** Frames dropped from the END of the source clip
|
|
1142
|
+
/** Frames dropped from the END of the source clip (smart-cut FALLBACK —
|
|
1143
|
+
* used only when the PSNR boundary matcher finds no genuine match). */
|
|
1140
1144
|
trimTailFrames: 4,
|
|
1141
|
-
/** Frames dropped from the START of the generated extension
|
|
1145
|
+
/** Frames dropped from the START of the generated extension (smart-cut
|
|
1146
|
+
* fallback, see above). */
|
|
1142
1147
|
trimHeadFrames: 3,
|
|
1143
1148
|
/** Boundary audio fade length (seconds), timeline-preserving. */
|
|
1144
1149
|
audioFadeSec: 0.15,
|
|
1150
|
+
/** Seconds of the source's TAIL passed as the @video_1 reference —
|
|
1151
|
+
* spike-validated: a short tail keeps the model focused on continuing
|
|
1152
|
+
* the boundary motion instead of re-staging the whole clip. */
|
|
1153
|
+
referenceTailSeconds: 1,
|
|
1145
1154
|
} as const
|
|
1146
1155
|
|
|
1147
1156
|
/**
|
|
@@ -1243,6 +1252,13 @@ export const RESOLUTION_DURATION_PRICING: Record<string, readonly string[]> = {
|
|
|
1243
1252
|
// seedance-2-extend always uses a video ref, so pricing has no -ref
|
|
1244
1253
|
// dimension — duration tier + ":res" only (rates = seedance-2 -ref + stitch).
|
|
1245
1254
|
"seedance-2-extend": ["480p", "720p", "1080p"],
|
|
1255
|
+
// HappyHorse 1.1 — per-second billing at two published resolution rates
|
|
1256
|
+
// (kie.ai/happyhorse-1-1: 22.5 cr/s @720p, 29 cr/s @1080p, uniform across
|
|
1257
|
+
// t2v/i2v/ref2v). 720p first = billing default; the KIE-side default is
|
|
1258
|
+
// pinned to 720p via extraParams in backend kie/models.ts so render matches.
|
|
1259
|
+
"happyhorse": ["720p", "1080p"],
|
|
1260
|
+
"happyhorse-i2v": ["720p", "1080p"],
|
|
1261
|
+
"happyhorse-ref2v": ["720p", "1080p"],
|
|
1246
1262
|
}
|
|
1247
1263
|
|
|
1248
1264
|
/**
|
|
@@ -1489,6 +1505,13 @@ export const VIDEO_VARIABLE_PRICING: Record<string, "duration" | "duration+audio
|
|
|
1489
1505
|
"grok-imagine-video-1.5": "duration+resolution",
|
|
1490
1506
|
}
|
|
1491
1507
|
|
|
1508
|
+
/** HappyHorse 1.1 per-second tiers — one per allowed duration (3–15s), shared
|
|
1509
|
+
* by all three modes (t2v/i2v/ref2v) which bill at identical published rates. */
|
|
1510
|
+
const HAPPYHORSE_DURATION_TIERS: Array<{ maxSeconds: number; suffix: string }> = Array.from(
|
|
1511
|
+
{ length: 13 },
|
|
1512
|
+
(_, i) => ({ maxSeconds: i + 3, suffix: `${i + 3}s` }),
|
|
1513
|
+
)
|
|
1514
|
+
|
|
1492
1515
|
/**
|
|
1493
1516
|
* Duration tier breakpoints for variable-priced video models.
|
|
1494
1517
|
* Maps provider → array of { maxSeconds, suffix } in ascending order.
|
|
@@ -1588,6 +1611,14 @@ export const VIDEO_DURATION_TIERS: Record<string, Array<{ maxSeconds: number; su
|
|
|
1588
1611
|
{ maxSeconds: 14, suffix: "14s" },
|
|
1589
1612
|
{ maxSeconds: 15, suffix: "15s" },
|
|
1590
1613
|
],
|
|
1614
|
+
// HappyHorse 1.1 (t2v / i2v / ref2v) — true per-second billing (KIE 22.5 cr/s
|
|
1615
|
+
// @720p, 29 cr/s @1080p, published on kie.ai/happyhorse-1-1). One tier per
|
|
1616
|
+
// allowed second (3–15s) so the composite identifier maps 1:1 to the seeded
|
|
1617
|
+
// price, combined with RESOLUTION_DURATION_PRICING for the ":720p"/":1080p"
|
|
1618
|
+
// suffix (e.g. "happyhorse-i2v:5s:720p").
|
|
1619
|
+
"happyhorse": HAPPYHORSE_DURATION_TIERS,
|
|
1620
|
+
"happyhorse-i2v": HAPPYHORSE_DURATION_TIERS,
|
|
1621
|
+
"happyhorse-ref2v": HAPPYHORSE_DURATION_TIERS,
|
|
1591
1622
|
}
|
|
1592
1623
|
|
|
1593
1624
|
/**
|
|
@@ -15,6 +15,12 @@ export const NODE_MAPPABLE_FIELDS: Readonly<Record<string, readonly string[]>> =
|
|
|
15
15
|
// for back-compat with un-migrated workflow JSON). Mirrors text-to-video so
|
|
16
16
|
// fieldMappings/{} injection AND missing-ref detection work on the live node.
|
|
17
17
|
"generate-video": ["prompt", "negativePrompt"],
|
|
18
|
+
// Trimmed multi-segment stitch variant of generate-video — prompt only, no
|
|
19
|
+
// negativePrompt field on the node.
|
|
20
|
+
"generate-video-pro": ["prompt"],
|
|
21
|
+
// Span-replace sibling of generate-video-pro — prompt only, no
|
|
22
|
+
// negativePrompt field on the node.
|
|
23
|
+
"edit-video-pro": ["prompt"],
|
|
18
24
|
"video-analysis": ["analysisFocus", "youtubeUrl"],
|
|
19
25
|
"video-to-video": ["prompt"],
|
|
20
26
|
"text-to-speech": ["directText"],
|
package/src/producer-types.ts
CHANGED
|
@@ -30,6 +30,16 @@ export const VIDEO_PRODUCER_TYPES: ReadonlySet<string> = new Set([
|
|
|
30
30
|
// through to the imageUrl/videoUrl/audioUrl/text default and downstream consumers
|
|
31
31
|
// could silently misroute the output.
|
|
32
32
|
"generate-video",
|
|
33
|
+
// Generate Video Pro — Seedance-2-family multi-segment stitch variant of
|
|
34
|
+
// generate-video (same "emits videoUrl" contract; a trimmed provider +
|
|
35
|
+
// handle set). Must mirror generate-video here or its output can't connect
|
|
36
|
+
// downstream (the recurring "cannot connect the outputs" bug class).
|
|
37
|
+
"generate-video-pro",
|
|
38
|
+
// Edit Video Pro — Seedance-2-family span-replace sibling of generate-
|
|
39
|
+
// video-pro (same "emits videoUrl" contract; source video + prompt in,
|
|
40
|
+
// ONE video out). Must mirror generate-video-pro here or its output can't
|
|
41
|
+
// connect downstream (the recurring "cannot connect the outputs" bug class).
|
|
42
|
+
"edit-video-pro",
|
|
33
43
|
"upload-video",
|
|
34
44
|
"youtube-video",
|
|
35
45
|
"combine-videos",
|
package/src/selector.ts
CHANGED
|
@@ -706,7 +706,16 @@ export function applyRangeIndices(
|
|
|
706
706
|
return result
|
|
707
707
|
}
|
|
708
708
|
|
|
709
|
-
/** mulberry32 PRNG — deterministic given a seed (any 32-bit signed integer).
|
|
709
|
+
/** mulberry32 PRNG — deterministic given a seed (any 32-bit signed integer).
|
|
710
|
+
*
|
|
711
|
+
* TWIN WARNING: backend/src/providers/video/audio-fx.ts carries its own
|
|
712
|
+
* private mulberry32 (cosmetically different — `>>> 0` seed masking — but the
|
|
713
|
+
* same sequence). The two are deliberately INDEPENDENT and both frozen: the
|
|
714
|
+
* audio-fx copy seeds every reverb impulse response that the committed
|
|
715
|
+
* characterization goldens (and every customer's rendered reverb waveform)
|
|
716
|
+
* are pinned to. Do NOT "deduplicate" them into one shared export or "sync"
|
|
717
|
+
* one to match the other — a change to either sequence is a silent behavior
|
|
718
|
+
* change in its domain. */
|
|
710
719
|
function mulberry32(seed: number): () => number {
|
|
711
720
|
let a = seed | 0
|
|
712
721
|
return () => {
|