@nodaro/shared 2.8.0 → 2.10.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 +198 -46
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +474 -94
- package/dist/index.d.ts +474 -94
- package/dist/index.js +178 -46
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/llm-models.test.ts +3 -2
- package/src/__tests__/organizations-types.test.ts +61 -0
- package/src/__tests__/producer-types.test.ts +11 -0
- package/src/__tests__/prompt-length-limits.test.ts +7 -3
- package/src/__tests__/seedance2-continuation-ref.test.ts +2 -3
- package/src/__tests__/video-analysis-catalog-sync.test.ts +1 -1
- package/src/__tests__/video-analysis-pricing.test.ts +2 -2
- package/src/__tests__/video-mode-for-inputs.test.ts +74 -0
- package/src/combine-transitions.ts +38 -0
- package/src/credit-estimators/video-utils.ts +1 -1
- package/src/featured-entities.ts +1 -1
- package/src/i18n/types.ts +12 -14
- package/src/index.ts +11 -1
- package/src/llm-models.ts +4 -0
- package/src/model-catalog.ts +19 -0
- package/src/model-constants.ts +52 -7
- package/src/organizations/index.ts +2 -0
- package/src/organizations/types.ts +152 -0
- package/src/organizations/views.ts +220 -0
- package/src/producer-types.ts +6 -0
- package/src/smart-cut-windows.ts +8 -17
- package/src/suno-track-sources.ts +23 -0
- package/src/surround.ts +10 -90
- package/src/video-analysis-pricing.ts +25 -36
- package/src/workflow-export.ts +41 -0
package/package.json
CHANGED
|
@@ -392,10 +392,11 @@ describe("LLM_FEATURE_DEFAULTS", () => {
|
|
|
392
392
|
"translate",
|
|
393
393
|
"image-critic",
|
|
394
394
|
"pick-best-llm",
|
|
395
|
+
"workflow-copilot",
|
|
395
396
|
]
|
|
396
397
|
|
|
397
|
-
it("has entries for all
|
|
398
|
-
expect(Object.keys(LLM_FEATURE_DEFAULTS)).toHaveLength(
|
|
398
|
+
it("has entries for all 17 features", () => {
|
|
399
|
+
expect(Object.keys(LLM_FEATURE_DEFAULTS)).toHaveLength(17)
|
|
399
400
|
for (const feature of ALL_FEATURES) {
|
|
400
401
|
expect(LLM_FEATURE_DEFAULTS).toHaveProperty(feature)
|
|
401
402
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest"
|
|
2
|
+
import {
|
|
3
|
+
ACCESS_LEVELS,
|
|
4
|
+
COLLABORATOR_ROLES,
|
|
5
|
+
MEMBER_STATUSES,
|
|
6
|
+
ORG_ERROR_CODES,
|
|
7
|
+
ORG_KINDS,
|
|
8
|
+
ORG_ROLES,
|
|
9
|
+
OrgSettingsSchema,
|
|
10
|
+
PRESET_SETTING_KEYS,
|
|
11
|
+
PresetSettingsSchema,
|
|
12
|
+
WORKSPACE_ROLES,
|
|
13
|
+
WorkspaceSettingsSchema,
|
|
14
|
+
} from "../organizations/index.js"
|
|
15
|
+
|
|
16
|
+
describe("organizations wire contract — enums", () => {
|
|
17
|
+
it("every enum is non-empty and duplicate-free", () => {
|
|
18
|
+
for (const list of [ORG_KINDS, ORG_ROLES, WORKSPACE_ROLES, MEMBER_STATUSES, COLLABORATOR_ROLES, ACCESS_LEVELS, ORG_ERROR_CODES]) {
|
|
19
|
+
expect(list.length).toBeGreaterThan(0)
|
|
20
|
+
expect(new Set(list).size).toBe(list.length)
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it("error codes are snake_case identifiers", () => {
|
|
25
|
+
for (const code of ORG_ERROR_CODES) expect(code).toMatch(/^[a-z][a-z_]*[a-z]$/)
|
|
26
|
+
})
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
describe("organizations wire contract — settings request schemas", () => {
|
|
30
|
+
it("the preset shape has exactly the nine resolvable keys", () => {
|
|
31
|
+
expect([...PRESET_SETTING_KEYS].sort()).toEqual(
|
|
32
|
+
[
|
|
33
|
+
"admin_access",
|
|
34
|
+
"collaborators_can_invite",
|
|
35
|
+
"default_workflow_visibility",
|
|
36
|
+
"member_access_to_shared",
|
|
37
|
+
"member_caps_enabled",
|
|
38
|
+
"members_can_create_projects",
|
|
39
|
+
"personal_space_enabled",
|
|
40
|
+
"policy_survives_suspension",
|
|
41
|
+
"workspace_admins_can_invite",
|
|
42
|
+
].sort(),
|
|
43
|
+
)
|
|
44
|
+
expect(PresetSettingsSchema.safeParse({}).success).toBe(false)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it("workspace settings are a partial of the preset; unknown keys are stripped; bad values rejected", () => {
|
|
48
|
+
expect(WorkspaceSettingsSchema.parse({ admin_access: "edit", rogue: 1 })).toEqual({ admin_access: "edit" })
|
|
49
|
+
expect(WorkspaceSettingsSchema.safeParse({}).success).toBe(true)
|
|
50
|
+
expect(WorkspaceSettingsSchema.safeParse({ admin_access: "owner" }).success).toBe(false)
|
|
51
|
+
expect(WorkspaceSettingsSchema.safeParse({ member_caps_enabled: "yes" }).success).toBe(false)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it("org settings accept lower-case domains only and string vocabulary overrides", () => {
|
|
55
|
+
expect(OrgSettingsSchema.safeParse({ allowed_email_domains: ["school.edu", "sub.school.ac.il"] }).success).toBe(true)
|
|
56
|
+
expect(OrgSettingsSchema.safeParse({ allowed_email_domains: ["School.edu"] }).success).toBe(false)
|
|
57
|
+
expect(OrgSettingsSchema.safeParse({ allowed_email_domains: ["not a domain"] }).success).toBe(false)
|
|
58
|
+
expect(OrgSettingsSchema.safeParse({ vocabulary_overrides: { workspace: "Cohort" } }).success).toBe(true)
|
|
59
|
+
expect(OrgSettingsSchema.safeParse({ vocabulary_overrides: { workspace: 3 } }).success).toBe(false)
|
|
60
|
+
})
|
|
61
|
+
})
|
|
@@ -23,6 +23,17 @@ describe("producer-types", () => {
|
|
|
23
23
|
DYNAMIC_PRODUCER_TYPES,
|
|
24
24
|
} as const
|
|
25
25
|
|
|
26
|
+
// still-to-video (still image + audio → MP4 via local FFmpeg) has a video
|
|
27
|
+
// OUTPUT handle — absent from this set, every downstream video input would
|
|
28
|
+
// hard-reject its edges (the "cannot connect the outputs" bug class).
|
|
29
|
+
it("registers still-to-video as a video producer", () => {
|
|
30
|
+
expect(VIDEO_PRODUCER_TYPES.has("still-to-video")).toBe(true)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it("registers slideshow as a video producer (still-to-video's N-image companion)", () => {
|
|
34
|
+
expect(VIDEO_PRODUCER_TYPES.has("slideshow")).toBe(true)
|
|
35
|
+
})
|
|
36
|
+
|
|
26
37
|
it("registers voice-changer-pro as an audio producer (its default output)", () => {
|
|
27
38
|
expect(AUDIO_PRODUCER_TYPES.has("voice-changer-pro")).toBe(true)
|
|
28
39
|
})
|
|
@@ -102,9 +102,13 @@ describe("per-model prompt length limits", () => {
|
|
|
102
102
|
})
|
|
103
103
|
|
|
104
104
|
describe("Suno per-version caps", () => {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
105
|
+
// Non-custom was 500 until 2026-08-19 — six times under the provider's
|
|
106
|
+
// documented 3000, and the route truncates rather than rejects, so a
|
|
107
|
+
// 950-char recast score brief reached Suno cut to exactly 500 mid-word.
|
|
108
|
+
it("prompt: 3000 non-custom (all models); 3000 V4 custom; 5000 V4.5+/V5 custom", () => {
|
|
109
|
+
expect(getMaxSunoPromptChars("V4", false)).toBe(3000)
|
|
110
|
+
expect(getMaxSunoPromptChars("V5", false)).toBe(3000)
|
|
111
|
+
expect(getMaxSunoPromptChars("V5_5", false)).toBe(3000)
|
|
108
112
|
expect(getMaxSunoPromptChars("V4", true)).toBe(3000)
|
|
109
113
|
expect(getMaxSunoPromptChars("V4_5", true)).toBe(5000)
|
|
110
114
|
expect(getMaxSunoPromptChars("V5", true)).toBe(5000)
|
|
@@ -11,9 +11,8 @@ import {
|
|
|
11
11
|
* job dbf95612 — the original 1.0s tails made every generate-video-pro
|
|
12
12
|
* continuation segment fail deterministically). Everything this platform
|
|
13
13
|
* sends as a Seedance-2 video reference cuts SEEDANCE_2_CONTINUATION_REF_SEC
|
|
14
|
-
* seconds; do NOT "optimize" it back below the floor. The
|
|
15
|
-
* twin constants
|
|
16
|
-
* MIN_REF) are guarded by that repo's r2v-ref-floor.test.ts — keep in sync. */
|
|
14
|
+
* seconds; do NOT "optimize" it back below the floor. The plugin repo
|
|
15
|
+
* carries twin constants guarded by its own tests — keep the two in sync. */
|
|
17
16
|
describe("seedance-2 continuation-reference floor (do not regress)", () => {
|
|
18
17
|
it("the platform's continuation-ref length clears KIE's r2v minimum", () => {
|
|
19
18
|
expect(SEEDANCE_2_CONTINUATION_REF_SEC).toBeGreaterThanOrEqual(SEEDANCE_2_R2V_MIN_REF_VIDEO_SEC)
|
|
@@ -157,7 +157,7 @@ describe("bare video-analysis node-type credit id", () => {
|
|
|
157
157
|
// 24 and the schedule regenerated ~47% lower at the ceiling bucket, and 300
|
|
158
158
|
// wrote 2064: the V1 hybrid-smart reprice (task A3) moved `smart` to a
|
|
159
159
|
// multi-roll plan (native skeleton + donor rolls, always refined) and
|
|
160
|
-
// trued up every tier's judge/refine terms
|
|
160
|
+
// trued up every tier's judge/refine terms).
|
|
161
161
|
expect(ceiling).toBe(2064)
|
|
162
162
|
})
|
|
163
163
|
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
|
|
16
16
|
// The measured-rate constants and the $-derived `videoAnalysisBucketCredits`
|
|
17
17
|
// formula are PRIVATE, in @nodaroai/cloud-plugins
|
|
18
|
-
// (
|
|
18
|
+
// (in the plugin repo) — its tests (the worked-example
|
|
19
19
|
// bucket-credit values and the cross-check against VIDEO_ANALYSIS_BUCKET_CREDITS
|
|
20
20
|
// below) live in that private package's __tests__/cost.test.ts. This file
|
|
21
21
|
// covers only the NON-monetary duration-bucketing, window-batching, and
|
|
@@ -121,7 +121,7 @@ describe("video-analysis-pricing", () => {
|
|
|
121
121
|
})
|
|
122
122
|
|
|
123
123
|
// Full drift-detection against the live $-formula lives in the PRIVATE
|
|
124
|
-
// plugin repo
|
|
124
|
+
// plugin repo — the
|
|
125
125
|
// formula moved there in 2026-07 and the app-side test was deleted with it,
|
|
126
126
|
// so nothing in THIS repo can recompute these numbers. This is a lightweight
|
|
127
127
|
// shape check that the precomputed table covers every legal id.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest"
|
|
2
|
+
import {
|
|
3
|
+
VIDEO_MODE_ALIASES,
|
|
4
|
+
VIDEO_REF_LIMITS_BY_PROVIDER,
|
|
5
|
+
resolveVideoModeForInputs,
|
|
6
|
+
resolveVideoProviderForMode,
|
|
7
|
+
} from "../model-constants.js"
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* #861 — reference images wired to Grok Imagine 1 without a start frame were
|
|
11
|
+
* silently dropped: the run resolved to text-to-video, the t2v twin (`grok`)
|
|
12
|
+
* has no image parameter, and nothing said so. The mode must follow what the
|
|
13
|
+
* model can CARRY, derived from the catalog's reference caps — not a name.
|
|
14
|
+
*/
|
|
15
|
+
const I2V = "image-to-video"
|
|
16
|
+
const T2V = "text-to-video"
|
|
17
|
+
|
|
18
|
+
describe("resolveVideoModeForInputs", () => {
|
|
19
|
+
it("a start frame is image-to-video for every provider", () => {
|
|
20
|
+
for (const id of ["grok-i2v", "grok", "seedance-2", "kling", "veo3", "wan-i2v", undefined]) {
|
|
21
|
+
expect(resolveVideoModeForInputs(id, { hasStartFrame: true, hasImageRefs: false })).toBe(I2V)
|
|
22
|
+
expect(resolveVideoModeForInputs(id, { hasStartFrame: true, hasImageRefs: true })).toBe(I2V)
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it("nothing wired is text-to-video", () => {
|
|
27
|
+
expect(resolveVideoModeForInputs("grok-i2v", { hasStartFrame: false, hasImageRefs: false })).toBe(T2V)
|
|
28
|
+
expect(resolveVideoModeForInputs(undefined, { hasStartFrame: false, hasImageRefs: true })).toBe(T2V)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it("refs alone route Grok Imagine 1 to its i2v twin — the only one that can carry them (#861)", () => {
|
|
32
|
+
// Whichever member id the node stored (base/i2v/t2v all appear in old workflows).
|
|
33
|
+
for (const id of ["grok-i2v", "grok"]) {
|
|
34
|
+
expect(resolveVideoModeForInputs(id, { hasStartFrame: false, hasImageRefs: true })).toBe(I2V)
|
|
35
|
+
}
|
|
36
|
+
// The premise, pinned: i2v carries refs, t2v does not.
|
|
37
|
+
expect(VIDEO_REF_LIMITS_BY_PROVIDER["grok-i2v"]?.images).toBeGreaterThan(0)
|
|
38
|
+
expect(VIDEO_REF_LIMITS_BY_PROVIDER["grok"]).toBeUndefined()
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it("single-id models are untouched: the same id serves both modes, so there is nothing to re-route", () => {
|
|
42
|
+
// seedance-2 / gemini-omni-video / veo forward refs on their t2v path
|
|
43
|
+
// (generate-video.md mode table). kling-3-omni is i2v-only and keeps its
|
|
44
|
+
// documented orchestrator behavior (image_required on the t2v path) —
|
|
45
|
+
// whether refs alone should satisfy it is a separate question from #861.
|
|
46
|
+
for (const id of ["seedance-2", "gemini-omni-video", "veo3.1", "kling-3-omni"]) {
|
|
47
|
+
expect(resolveVideoModeForInputs(id, { hasStartFrame: false, hasImageRefs: true })).toBe(T2V)
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it("a split-id model with no ref support on either twin is unaffected (wan, happyhorse)", () => {
|
|
52
|
+
for (const id of ["wan-i2v", "wan-2.7-i2v", "happyhorse-i2v"]) {
|
|
53
|
+
expect(resolveVideoModeForInputs(id, { hasStartFrame: false, hasImageRefs: true })).toBe(T2V)
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it("every alias group is classified — a new split-id model lands in one of the two pinned outcomes", () => {
|
|
58
|
+
// Explicit per-group expectations (not a re-derivation of the formula):
|
|
59
|
+
// grok is the one group whose twins differ on ref support today.
|
|
60
|
+
const expectedByBase: Record<string, "image-to-video" | "text-to-video"> = {
|
|
61
|
+
"grok-i2v": I2V,
|
|
62
|
+
"wan-i2v": T2V,
|
|
63
|
+
"wan-2.7-i2v": T2V,
|
|
64
|
+
"happyhorse-i2v": T2V,
|
|
65
|
+
}
|
|
66
|
+
expect(VIDEO_MODE_ALIASES.map((g) => g.base).sort()).toEqual(Object.keys(expectedByBase).sort())
|
|
67
|
+
for (const g of VIDEO_MODE_ALIASES) {
|
|
68
|
+
const expected = expectedByBase[g.base]!
|
|
69
|
+
expect(resolveVideoModeForInputs(g.base, { hasStartFrame: false, hasImageRefs: true }), g.base).toBe(expected)
|
|
70
|
+
// The mode it picks resolves to the twin that can actually run it.
|
|
71
|
+
if (expected === I2V) expect(resolveVideoProviderForMode(g.base, expected)).toBe(g.i2v)
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
})
|
|
@@ -480,3 +480,41 @@ export function resolveXfadeName(id: string): string | null {
|
|
|
480
480
|
}
|
|
481
481
|
return entry.xfade
|
|
482
482
|
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Map a `transition` PARAMETER-NODE pick (the @nodaro/prompts creative
|
|
486
|
+
* catalog — prose-hint transitions for AI video prompts) onto the ffmpeg
|
|
487
|
+
* xfade vocabulary above. The two vocabularies are deliberately different
|
|
488
|
+
* (the picker describes shots, this table describes blends); only the
|
|
489
|
+
* visually-faithful subset maps — everything else falls back to `cut`.
|
|
490
|
+
*
|
|
491
|
+
* Consumed by the slideshow node (its transition TYPE comes from a wired
|
|
492
|
+
* transition parameter node; unwired defaults to cut). Kept here — not in
|
|
493
|
+
* @nodaro/prompts — because the dependency points prompts → shared, and the
|
|
494
|
+
* ids are plain strings on both sides.
|
|
495
|
+
*/
|
|
496
|
+
export const PICKER_TO_COMBINE_TRANSITION: Readonly<Record<string, string>> = {
|
|
497
|
+
"none": "cut",
|
|
498
|
+
"auto": "dissolve", // an explicitly wired "auto" wants A blend — the classic slideshow default
|
|
499
|
+
"cross-dissolve": "dissolve",
|
|
500
|
+
"fade-to-black": "dip-to-black",
|
|
501
|
+
"fade-to-white": "dip-to-white",
|
|
502
|
+
"iris": "circle-open",
|
|
503
|
+
"wipe": "wipe-left",
|
|
504
|
+
"pixelate-reform": "pixelize",
|
|
505
|
+
"dissolve-to-mist": "hblur",
|
|
506
|
+
"match-cut": "cut",
|
|
507
|
+
"smash-cut": "cut",
|
|
508
|
+
"freeze-frame-jump": "cut",
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Resolve ANY transition string a slideshow may receive — a combine id
|
|
513
|
+
* (already valid), a picker id (mapped), or anything else (→ cut).
|
|
514
|
+
*/
|
|
515
|
+
export function resolveSlideshowTransition(value: string | undefined | null): string {
|
|
516
|
+
if (!value) return "cut"
|
|
517
|
+
if (COMBINE_TRANSITION_IDS.includes(value)) return value
|
|
518
|
+
return PICKER_TO_COMBINE_TRANSITION[value] ?? "cut"
|
|
519
|
+
}
|
|
520
|
+
|
|
@@ -155,7 +155,7 @@ export function estimateLoopTrimAddonCredits(
|
|
|
155
155
|
Math.ceil(frames / VIDEO_UTIL_PRICING.FRAMES_PER_CREDIT)
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
-
/** BASE credits
|
|
158
|
+
/** BASE credits for assemble-narrated-video: 3 flat + 1 per 6
|
|
159
159
|
* blocks. 6→4, 24→7, 60→13. Single source of truth shared by the backend
|
|
160
160
|
* route/creditGuard (`backend/src/providers/video/narrated-block-fit.ts`
|
|
161
161
|
* re-exports this) and the frontend pre-run estimate
|
package/src/featured-entities.ts
CHANGED
package/src/i18n/types.ts
CHANGED
|
@@ -52,8 +52,6 @@ export interface LanguageDefinition {
|
|
|
52
52
|
readonly shortCode: string
|
|
53
53
|
/** Reading direction. */
|
|
54
54
|
readonly dir: LocaleDirection
|
|
55
|
-
/** Optional flag emoji for visual recognition. */
|
|
56
|
-
readonly flag: string
|
|
57
55
|
}
|
|
58
56
|
|
|
59
57
|
/**
|
|
@@ -62,18 +60,18 @@ export interface LanguageDefinition {
|
|
|
62
60
|
* languages, then RTL languages last.
|
|
63
61
|
*/
|
|
64
62
|
export const LANGUAGES: ReadonlyArray<LanguageDefinition> = [
|
|
65
|
-
{ id: "en", englishName: "English", nativeName: "English", shortCode: "EN", dir: "ltr"
|
|
66
|
-
{ id: "es", englishName: "Spanish", nativeName: "Español", shortCode: "ES", dir: "ltr"
|
|
67
|
-
{ id: "fr", englishName: "French", nativeName: "Français", shortCode: "FR", dir: "ltr"
|
|
68
|
-
{ id: "de", englishName: "German", nativeName: "Deutsch", shortCode: "DE", dir: "ltr"
|
|
69
|
-
{ id: "pt-BR", englishName: "Portuguese (Brazil)", nativeName: "Português", shortCode: "PT", dir: "ltr"
|
|
70
|
-
{ id: "ru", englishName: "Russian", nativeName: "Русский", shortCode: "RU", dir: "ltr"
|
|
71
|
-
{ id: "hi", englishName: "Hindi", nativeName: "हिन्दी", shortCode: "HI", dir: "ltr"
|
|
72
|
-
{ id: "ja", englishName: "Japanese", nativeName: "日本語", shortCode: "JA", dir: "ltr"
|
|
73
|
-
{ id: "ko", englishName: "Korean", nativeName: "한국어", shortCode: "KO", dir: "ltr"
|
|
74
|
-
{ id: "zh-CN", englishName: "Chinese (Simplified)", nativeName: "简体中文", shortCode: "ZH", dir: "ltr"
|
|
75
|
-
{ id: "he", englishName: "Hebrew", nativeName: "עברית", shortCode: "HE", dir: "rtl"
|
|
76
|
-
{ id: "ar", englishName: "Arabic", nativeName: "العربية", shortCode: "AR", dir: "rtl"
|
|
63
|
+
{ id: "en", englishName: "English", nativeName: "English", shortCode: "EN", dir: "ltr" },
|
|
64
|
+
{ id: "es", englishName: "Spanish", nativeName: "Español", shortCode: "ES", dir: "ltr" },
|
|
65
|
+
{ id: "fr", englishName: "French", nativeName: "Français", shortCode: "FR", dir: "ltr" },
|
|
66
|
+
{ id: "de", englishName: "German", nativeName: "Deutsch", shortCode: "DE", dir: "ltr" },
|
|
67
|
+
{ id: "pt-BR", englishName: "Portuguese (Brazil)", nativeName: "Português", shortCode: "PT", dir: "ltr" },
|
|
68
|
+
{ id: "ru", englishName: "Russian", nativeName: "Русский", shortCode: "RU", dir: "ltr" },
|
|
69
|
+
{ id: "hi", englishName: "Hindi", nativeName: "हिन्दी", shortCode: "HI", dir: "ltr" },
|
|
70
|
+
{ id: "ja", englishName: "Japanese", nativeName: "日本語", shortCode: "JA", dir: "ltr" },
|
|
71
|
+
{ id: "ko", englishName: "Korean", nativeName: "한국어", shortCode: "KO", dir: "ltr" },
|
|
72
|
+
{ id: "zh-CN", englishName: "Chinese (Simplified)", nativeName: "简体中文", shortCode: "ZH", dir: "ltr" },
|
|
73
|
+
{ id: "he", englishName: "Hebrew", nativeName: "עברית", shortCode: "HE", dir: "rtl" },
|
|
74
|
+
{ id: "ar", englishName: "Arabic", nativeName: "العربية", shortCode: "AR", dir: "rtl" },
|
|
77
75
|
] as const
|
|
78
76
|
|
|
79
77
|
export const LOCALE_IDS: ReadonlyArray<LocaleId> = LANGUAGES.map((l) => l.id)
|
package/src/index.ts
CHANGED
|
@@ -110,6 +110,7 @@ export {
|
|
|
110
110
|
VIDEO_MODE_ALIASES,
|
|
111
111
|
VIDEO_GEN_COLLAPSED_T2V_IDS,
|
|
112
112
|
resolveVideoProviderForMode,
|
|
113
|
+
resolveVideoModeForInputs,
|
|
113
114
|
isSeedance2Provider,
|
|
114
115
|
MINIMAX_H3_PROVIDERS,
|
|
115
116
|
isMinimaxH3Provider,
|
|
@@ -396,7 +397,6 @@ export {
|
|
|
396
397
|
TILT_CARRIED_FRACTION,
|
|
397
398
|
isTiltDirection,
|
|
398
399
|
defaultCarriedFraction,
|
|
399
|
-
buildSurroundFillPrompt,
|
|
400
400
|
type SurroundDirection,
|
|
401
401
|
} from "./surround.js"
|
|
402
402
|
|
|
@@ -762,6 +762,9 @@ export type {
|
|
|
762
762
|
WorkflowExportObject,
|
|
763
763
|
WorkflowExportCreature,
|
|
764
764
|
WorkflowExportLocation,
|
|
765
|
+
WorkflowMediaRef,
|
|
766
|
+
WorkflowPortability,
|
|
767
|
+
WorkflowImportReport,
|
|
765
768
|
} from "./workflow-export.js"
|
|
766
769
|
export { stripExportContent } from "./workflow-export.js"
|
|
767
770
|
|
|
@@ -860,6 +863,8 @@ export * from "./reduce-strategy-registry.js"
|
|
|
860
863
|
export {
|
|
861
864
|
COMBINE_TRANSITIONS,
|
|
862
865
|
COMBINE_TRANSITION_IDS,
|
|
866
|
+
PICKER_TO_COMBINE_TRANSITION,
|
|
867
|
+
resolveSlideshowTransition,
|
|
863
868
|
COMBINE_TRANSITION_GROUP_ORDER,
|
|
864
869
|
COMBINE_TRANSITION_GROUP_LABELS,
|
|
865
870
|
getCombineTransition,
|
|
@@ -886,6 +891,8 @@ export {
|
|
|
886
891
|
FAN_OUT_EACH_TYPES,
|
|
887
892
|
} from "./producer-types.js"
|
|
888
893
|
|
|
894
|
+
export { SUNO_TRACK_SOURCE_TYPES } from "./suno-track-sources.js"
|
|
895
|
+
|
|
889
896
|
export {
|
|
890
897
|
VOICE_CHANGER_MODELS,
|
|
891
898
|
VOICE_CHANGER_MODEL_IDS,
|
|
@@ -953,3 +960,6 @@ export * from "./smart-cut-windows.js"
|
|
|
953
960
|
|
|
954
961
|
export * from "./entity-asset-types.js"
|
|
955
962
|
export * from "./hint-graph-types.js"
|
|
963
|
+
|
|
964
|
+
// --- Organizations (second tenancy axis): wire contract only — enums, settings request schemas, error codes ---
|
|
965
|
+
export * from "./organizations/index.js"
|
package/src/llm-models.ts
CHANGED
|
@@ -529,6 +529,9 @@ export type LlmFeature =
|
|
|
529
529
|
// feature (not ai-writer, which it used to piggyback on) so the model
|
|
530
530
|
// default and the tiered credit ids are the strategy's own.
|
|
531
531
|
| "pick-best-llm"
|
|
532
|
+
// In-app Workflow Copilot turns (backend agent loop; metered, reservation
|
|
533
|
+
// ceiling under `STATIC_CREDIT_COSTS["workflow-copilot"]`).
|
|
534
|
+
| "workflow-copilot"
|
|
532
535
|
|
|
533
536
|
/** Engine-dependent LlmFeature for the motion-graphics node (design §8: every credit-id site must branch on engine). */
|
|
534
537
|
export function motionGraphicsFeature(engine?: string): LlmFeature {
|
|
@@ -553,6 +556,7 @@ export const LLM_FEATURE_DEFAULTS: Record<LlmFeature, string> = {
|
|
|
553
556
|
"translate": "gemini-3.6-flash",
|
|
554
557
|
"image-critic": "claude-sonnet-4.6",
|
|
555
558
|
"pick-best-llm": "claude-sonnet-4.6",
|
|
559
|
+
"workflow-copilot": "claude-sonnet-5",
|
|
556
560
|
}
|
|
557
561
|
|
|
558
562
|
/**
|
package/src/model-catalog.ts
CHANGED
|
@@ -886,6 +886,9 @@ const IMAGE_MODELS: Record<string, ModelCatalogEntry> = {
|
|
|
886
886
|
description:
|
|
887
887
|
"Grok Imagine Image 2.0 — expressive, high-contrast t2i. Generations chain into grok-2-segment (free named region masks) and grok-2-edit (region-targeted edits).",
|
|
888
888
|
useCases: ["stylized", "expressive", "general"],
|
|
889
|
+
// Refs auto-route to grok-2-i2i (segment-map → image-edit chain); ONE
|
|
890
|
+
// reference (REF_IMAGE_MAX_LIMITS).
|
|
891
|
+
features: ["reference-image"],
|
|
889
892
|
aspectRatios: GROK_RATIOS,
|
|
890
893
|
pricing: [{ identifier: "grok-2", credits: 10 }],
|
|
891
894
|
},
|
|
@@ -901,6 +904,22 @@ const IMAGE_MODELS: Record<string, ModelCatalogEntry> = {
|
|
|
901
904
|
useCases: ["edit", "region-edit"],
|
|
902
905
|
pricing: [{ identifier: "grok-2-edit", credits: 10 }],
|
|
903
906
|
},
|
|
907
|
+
// Auto-selected when references are attached to grok-2 (T2I_TO_I2I_VARIANT):
|
|
908
|
+
// the t2i endpoint takes no image, so the FREE segment-map mints a task id
|
|
909
|
+
// from the reference URL and image-edit consumes it. Single reference.
|
|
910
|
+
"grok-2-i2i": {
|
|
911
|
+
id: "grok-2-i2i",
|
|
912
|
+
kind: "image",
|
|
913
|
+
modes: ["i2i"] as const,
|
|
914
|
+
family: "xAI",
|
|
915
|
+
label: "Grok Imagine 2 (reference)",
|
|
916
|
+
series: "Grok",
|
|
917
|
+
description:
|
|
918
|
+
"grok-2 guided by ONE reference image via the segment-map → image-edit chain — preserves the reference's composition while applying the prompt.",
|
|
919
|
+
useCases: ["restyle", "edit", "reference"],
|
|
920
|
+
features: ["reference-image"],
|
|
921
|
+
pricing: [{ identifier: "grok-2-i2i", credits: 10 }],
|
|
922
|
+
},
|
|
904
923
|
"grok-2-segment": {
|
|
905
924
|
id: "grok-2-segment",
|
|
906
925
|
kind: "image",
|
package/src/model-constants.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { z } from "zod"
|
|
6
6
|
import { MODEL_CATALOG } from "./model-catalog.js"
|
|
7
7
|
|
|
8
|
-
/** Base USD
|
|
8
|
+
/** Base USD value of 1 Nodaro credit. Used for cost→credit conversion. */
|
|
9
9
|
export const CREDIT_BASE_USD = 0.002
|
|
10
10
|
|
|
11
11
|
/** Max characters for the (assembled) prompt accepted by the image-generation routes
|
|
@@ -232,16 +232,25 @@ export function getMaxTtsChars(provider: string | undefined): number {
|
|
|
232
232
|
* Suno per-version field caps (from docs.kie.ai/suno-api/generate-music). The old
|
|
233
233
|
* flat {@link SUNO_TEXT_MAX} (3000) was simultaneously too low for V4.5+/V5
|
|
234
234
|
* prompts (5000) and too high for `style` (1000) and `title` (80).
|
|
235
|
-
* - prompt / lyrics:
|
|
235
|
+
* - prompt / lyrics: 3000 in non-custom mode (all versions); in custom mode
|
|
236
236
|
* 3000 for V4/V3.5 and 5000 for V4.5 / V4.5PLUS / V4.5ALL / V5 / V5.5.
|
|
237
237
|
* - style: 200 for V4/V3.5, 1000 for V4.5+.
|
|
238
238
|
* - title: 80 (all versions).
|
|
239
239
|
*/
|
|
240
240
|
export const SUNO_TITLE_MAX = 80
|
|
241
241
|
|
|
242
|
-
/**
|
|
242
|
+
/**
|
|
243
|
+
* Max Suno `prompt` (= lyrics in custom mode) length for a model version.
|
|
244
|
+
*
|
|
245
|
+
* NON-CUSTOM WAS 500 UNTIL 2026-08-19 — six times under the provider's
|
|
246
|
+
* documented 3000, and the route TRUNCATES to this number instead of
|
|
247
|
+
* rejecting, so everything past it vanished without a trace. Field evidence:
|
|
248
|
+
* a 950-character recast score brief (instruments, vocal, the source's own
|
|
249
|
+
* scat syllables, the arrangement's arc) reached Suno as exactly 500
|
|
250
|
+
* characters, cut mid-word.
|
|
251
|
+
*/
|
|
243
252
|
export function getMaxSunoPromptChars(model: string | undefined, customMode: boolean): number {
|
|
244
|
-
if (!customMode) return
|
|
253
|
+
if (!customMode) return 3000
|
|
245
254
|
return model === "V4" || model === "V3_5" ? 3000 : 5000
|
|
246
255
|
}
|
|
247
256
|
|
|
@@ -364,6 +373,7 @@ export const MODELS_WITH_REFERENCE_IMAGE_SUPPORT = new Set([
|
|
|
364
373
|
"gpt-image",
|
|
365
374
|
"gpt-image-2",
|
|
366
375
|
"grok",
|
|
376
|
+
"grok-2",
|
|
367
377
|
"qwen",
|
|
368
378
|
"seedream",
|
|
369
379
|
"seedream-5-lite",
|
|
@@ -374,6 +384,7 @@ export const MODELS_WITH_REFERENCE_IMAGE_SUPPORT = new Set([
|
|
|
374
384
|
"nano-banana-edit",
|
|
375
385
|
"gpt-image-i2i",
|
|
376
386
|
"gpt-image-2-i2i",
|
|
387
|
+
"grok-2-i2i",
|
|
377
388
|
"flux-i2i",
|
|
378
389
|
"flux-pro-i2i",
|
|
379
390
|
"flux-kontext",
|
|
@@ -412,6 +423,9 @@ export const T2I_TO_I2I_VARIANT: Record<string, string> = {
|
|
|
412
423
|
"gpt-image": "gpt-image-i2i",
|
|
413
424
|
"gpt-image-2": "gpt-image-2-i2i",
|
|
414
425
|
"grok": "grok-i2i",
|
|
426
|
+
// grok-2's t2i takes NO image input; its "i2i" is the segment-map(image_url)
|
|
427
|
+
// → image-edit(task_id) chain in the KIE provider (single reference).
|
|
428
|
+
"grok-2": "grok-2-i2i",
|
|
415
429
|
"qwen": "qwen-i2i",
|
|
416
430
|
"seedream": "seedream-edit",
|
|
417
431
|
"seedream-5-lite": "seedream-5-lite-i2i",
|
|
@@ -437,6 +451,8 @@ export const REF_IMAGE_MAX_LIMITS: Record<string, number> = {
|
|
|
437
451
|
"nano-banana-2": 4,
|
|
438
452
|
"nano-banana-2-lite": 10,
|
|
439
453
|
"wan-2.7": 9,
|
|
454
|
+
// grok-2 reference chain consumes exactly one image (segment-map input).
|
|
455
|
+
"grok-2-i2i": 1,
|
|
440
456
|
// Image-to-image (multi-source array)
|
|
441
457
|
"nano-banana-edit": 8,
|
|
442
458
|
"gpt-image-i2i": 16,
|
|
@@ -592,6 +608,7 @@ export const IMAGE_I2I_PROVIDERS = [
|
|
|
592
608
|
"flux-pro-i2i",
|
|
593
609
|
"gpt-image-i2i",
|
|
594
610
|
"gpt-image-2-i2i",
|
|
611
|
+
"grok-2-i2i",
|
|
595
612
|
"ideogram-edit",
|
|
596
613
|
"ideogram-remix",
|
|
597
614
|
"ideogram-reframe",
|
|
@@ -824,6 +841,35 @@ export function resolveVideoProviderForMode(
|
|
|
824
841
|
return provider
|
|
825
842
|
}
|
|
826
843
|
|
|
844
|
+
/**
|
|
845
|
+
* Which execution mode a unified Generate Video run takes from what is wired.
|
|
846
|
+
* Shared by the frontend DAG executor (`execute-node.ts`) and the backend
|
|
847
|
+
* orchestrator (`payload-builder.ts`) so the two cannot disagree.
|
|
848
|
+
*
|
|
849
|
+
* A start frame is image-to-video, full stop. Reference images ALONE are the
|
|
850
|
+
* nuance: most models forward refs on either path, but a split-id model
|
|
851
|
+
* (VIDEO_MODE_ALIASES) can carry them on one twin only — Grok Imagine 1's
|
|
852
|
+
* text-to-video endpoint has no image parameter at all, while its i2v twin
|
|
853
|
+
* takes up to 7. Refs wired without a start frame used to resolve to t2v →
|
|
854
|
+
* `grok` → silently dropped (#861). When refs are present and ONLY the i2v
|
|
855
|
+
* twin can carry them, the run is image-to-video with the refs as its images.
|
|
856
|
+
* Derived from VIDEO_REF_LIMITS_BY_PROVIDER (= the catalog's `reference-image`
|
|
857
|
+
* feature), never from a provider name; single-id models are untouched
|
|
858
|
+
* because both twins are the same id.
|
|
859
|
+
*/
|
|
860
|
+
export function resolveVideoModeForInputs(
|
|
861
|
+
provider: string | undefined,
|
|
862
|
+
inputs: { readonly hasStartFrame: boolean; readonly hasImageRefs: boolean },
|
|
863
|
+
): "image-to-video" | "text-to-video" {
|
|
864
|
+
if (inputs.hasStartFrame) return "image-to-video"
|
|
865
|
+
if (!provider || !inputs.hasImageRefs) return "text-to-video"
|
|
866
|
+
const i2v = resolveVideoProviderForMode(provider, "image-to-video")
|
|
867
|
+
const t2v = resolveVideoProviderForMode(provider, "text-to-video")
|
|
868
|
+
if (i2v === t2v) return "text-to-video"
|
|
869
|
+
const carriesImageRefs = (id: string) => (VIDEO_REF_LIMITS_BY_PROVIDER[id]?.images ?? 0) > 0
|
|
870
|
+
return carriesImageRefs(i2v) && !carriesImageRefs(t2v) ? "image-to-video" : "text-to-video"
|
|
871
|
+
}
|
|
872
|
+
|
|
827
873
|
/**
|
|
828
874
|
* t2v twin ids hidden from the unified Generate Video picker — the i2v/base
|
|
829
875
|
* entry already represents both modes (execution remaps by image presence).
|
|
@@ -1526,9 +1572,8 @@ export const SEEDANCE_2_R2V_MIN_REF_VIDEO_SEC = 1.8
|
|
|
1526
1572
|
* credit formulas bill per continuation join. Clears the provider floor
|
|
1527
1573
|
* above with margin while staying short enough to keep the model focused on
|
|
1528
1574
|
* continuing the boundary motion instead of re-staging the whole clip.
|
|
1529
|
-
* Guarded ≥ floor by model-constants tests; the
|
|
1530
|
-
*
|
|
1531
|
-
* guarded by that repo's r2v-ref-floor.test.ts — keep the two in sync.
|
|
1575
|
+
* Guarded ≥ floor by model-constants tests; the plugin repo carries a twin
|
|
1576
|
+
* constant guarded by its own tests — keep the two in sync.
|
|
1532
1577
|
*/
|
|
1533
1578
|
export const SEEDANCE_2_CONTINUATION_REF_SEC = 2
|
|
1534
1579
|
|