@nodaro/shared 2.8.0 → 2.11.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 +329 -53
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +631 -94
- package/dist/index.d.ts +631 -94
- package/dist/index.js +297 -53
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/catalog-projection.test.ts +14 -0
- package/src/__tests__/default-video-provider.test.ts +1 -1
- package/src/__tests__/llm-models.test.ts +3 -2
- package/src/__tests__/organizations-types.test.ts +61 -0
- package/src/__tests__/pack-sidecar-localization.test.ts +21 -0
- package/src/__tests__/parameter-node-value.test.ts +18 -1
- package/src/__tests__/producer-types.test.ts +11 -0
- package/src/__tests__/prompt-length-limits.test.ts +7 -3
- package/src/__tests__/resolve-pipeline-model.test.ts +4 -4
- 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/animals.ts +10 -0
- package/src/catalog-projection.ts +48 -0
- package/src/combine-transitions.ts +38 -0
- package/src/credit-estimators/video-utils.ts +1 -1
- package/src/entity-node-fields.ts +147 -0
- package/src/featured-entities.ts +1 -1
- package/src/furniture.ts +10 -0
- package/src/i18n/index.ts +33 -3
- package/src/i18n/transitions.ar.ts +6 -0
- package/src/i18n/transitions.de.ts +6 -0
- package/src/i18n/transitions.es.ts +6 -0
- package/src/i18n/transitions.fr.ts +6 -0
- package/src/i18n/transitions.he.ts +6 -0
- package/src/i18n/transitions.hi.ts +6 -0
- package/src/i18n/transitions.ja.ts +6 -0
- package/src/i18n/transitions.ko.ts +6 -0
- package/src/i18n/transitions.pt-BR.ts +6 -0
- package/src/i18n/transitions.ru.ts +6 -0
- package/src/i18n/transitions.zh-CN.ts +6 -0
- package/src/i18n/types.ts +12 -14
- package/src/index.ts +30 -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/parameter-node-value.ts +23 -3
- package/src/producer-types.ts +10 -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/vehicles.ts +11 -1
- package/src/video-analysis-pricing.ts +25 -36
- package/src/weapons.ts +11 -1
- package/src/workflow-export.ts +41 -0
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
|
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { z } from "zod"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Organizations — wire contract for the second tenancy axis
|
|
5
|
+
* (Organization -> Workspace -> Member).
|
|
6
|
+
*
|
|
7
|
+
* Lives in @nodaro/shared because SDK/MCP consumers need these enums, the
|
|
8
|
+
* request schemas the API validates, and the error codes it returns. This
|
|
9
|
+
* package carries the CONTRACT ONLY — no resolution logic, no presets, no
|
|
10
|
+
* vocabulary, no access rule. Those are server-side.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Selects which workspace a request LISTS from and CREATES into. It never
|
|
15
|
+
* authorizes: reading, updating, deleting or running an identified object is
|
|
16
|
+
* decided by that object's own workspace, so a forgotten or forged header can
|
|
17
|
+
* neither widen access nor move a charge.
|
|
18
|
+
*
|
|
19
|
+
* Fastify lower-cases incoming header keys, hence the second constant — read
|
|
20
|
+
* `req.headers[WORKSPACE_HEADER_LOWER]`, send `WORKSPACE_HEADER`.
|
|
21
|
+
*/
|
|
22
|
+
export const WORKSPACE_HEADER = "X-Nodaro-Workspace"
|
|
23
|
+
export const WORKSPACE_HEADER_LOWER = "x-nodaro-workspace"
|
|
24
|
+
|
|
25
|
+
export const ORG_KINDS = ["school", "team"] as const
|
|
26
|
+
export type OrgKind = (typeof ORG_KINDS)[number]
|
|
27
|
+
|
|
28
|
+
export const ORG_ROLES = ["owner", "admin", "member"] as const
|
|
29
|
+
export type OrgRole = (typeof ORG_ROLES)[number]
|
|
30
|
+
|
|
31
|
+
export const WORKSPACE_ROLES = ["admin", "member"] as const
|
|
32
|
+
export type WorkspaceRole = (typeof WORKSPACE_ROLES)[number]
|
|
33
|
+
|
|
34
|
+
export const MEMBER_STATUSES = ["active", "suspended"] as const
|
|
35
|
+
export type MemberStatus = (typeof MEMBER_STATUSES)[number]
|
|
36
|
+
|
|
37
|
+
/** `pending` = created, awaiting platform-admin approval. */
|
|
38
|
+
export const ORG_STATUSES = ["pending", "active", "suspended", "deleted"] as const
|
|
39
|
+
export type OrgStatus = (typeof ORG_STATUSES)[number]
|
|
40
|
+
|
|
41
|
+
/** An explicit per-workflow grant (works for personal workflows too). */
|
|
42
|
+
export const COLLABORATOR_ROLES = ["editor", "viewer"] as const
|
|
43
|
+
export type CollaboratorRole = (typeof COLLABORATOR_ROLES)[number]
|
|
44
|
+
|
|
45
|
+
export const WORKFLOW_VISIBILITIES = ["private", "workspace"] as const
|
|
46
|
+
export type WorkflowVisibility = (typeof WORKFLOW_VISIBILITIES)[number]
|
|
47
|
+
|
|
48
|
+
/** What an identity may do with a workflow, strongest first. */
|
|
49
|
+
export const ACCESS_LEVELS = ["own", "edit", "view", "none"] as const
|
|
50
|
+
export type AccessLevel = (typeof ACCESS_LEVELS)[number]
|
|
51
|
+
|
|
52
|
+
/** The access a setting may grant to a non-creator. */
|
|
53
|
+
export const GRANTED_ACCESS = ["view", "edit"] as const
|
|
54
|
+
export type GrantedAccess = (typeof GRANTED_ACCESS)[number]
|
|
55
|
+
|
|
56
|
+
export const SUBMISSION_STATUSES = ["submitted", "in_review", "returned", "approved"] as const
|
|
57
|
+
export type SubmissionStatus = (typeof SUBMISSION_STATUSES)[number]
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Error codes the organization endpoints add to the standard envelope
|
|
61
|
+
* (`{ error: { code, message } }`). Clients dispatch on the code, never on
|
|
62
|
+
* the message text.
|
|
63
|
+
*/
|
|
64
|
+
export const ORG_ERROR_CODES = [
|
|
65
|
+
"not_a_member",
|
|
66
|
+
"insufficient_role",
|
|
67
|
+
"org_not_active",
|
|
68
|
+
"member_suspended",
|
|
69
|
+
"workspace_archived",
|
|
70
|
+
"personal_space_disabled",
|
|
71
|
+
"token_workspace_mismatch",
|
|
72
|
+
"run_requires_authenticated_member",
|
|
73
|
+
"budget_exceeded",
|
|
74
|
+
"member_cap_exceeded",
|
|
75
|
+
"model_not_allowed",
|
|
76
|
+
"invitation_expired",
|
|
77
|
+
"invitation_revoked",
|
|
78
|
+
"email_mismatch",
|
|
79
|
+
"join_code_invalid",
|
|
80
|
+
"domain_not_allowed",
|
|
81
|
+
"already_started",
|
|
82
|
+
"collab_unavailable",
|
|
83
|
+
// Organization, workspace and membership endpoints.
|
|
84
|
+
"terms_required",
|
|
85
|
+
"not_org_member",
|
|
86
|
+
"already_a_member",
|
|
87
|
+
"owner_cannot_leave",
|
|
88
|
+
"has_active_workspaces",
|
|
89
|
+
// Invitations and join codes.
|
|
90
|
+
"invitation_not_found",
|
|
91
|
+
"invitation_accepted",
|
|
92
|
+
"bulk_invite_cap_exceeded",
|
|
93
|
+
] as const
|
|
94
|
+
export type OrgErrorCode = (typeof ORG_ERROR_CODES)[number]
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The settings every organization kind has a default for. `organizations.
|
|
98
|
+
* settings` and `workspaces.settings` store PARTIAL overrides of this shape;
|
|
99
|
+
* `resolveEffectiveSettings` (./settings.ts) produces the full one.
|
|
100
|
+
*/
|
|
101
|
+
export const PresetSettingsSchema = z.object({
|
|
102
|
+
/** What org/workspace admins may do with a member's workflow. */
|
|
103
|
+
admin_access: z.enum(GRANTED_ACCESS),
|
|
104
|
+
default_workflow_visibility: z.enum(WORKFLOW_VISIBILITIES),
|
|
105
|
+
/** What a plain member may do with a `visibility = workspace` workflow. */
|
|
106
|
+
member_access_to_shared: z.enum(GRANTED_ACCESS),
|
|
107
|
+
members_can_create_projects: z.boolean(),
|
|
108
|
+
member_caps_enabled: z.boolean(),
|
|
109
|
+
/** Whether members keep a personal (non-workspace) space at all. */
|
|
110
|
+
personal_space_enabled: z.boolean(),
|
|
111
|
+
/** Whether a workspace admin may invite NEW people into the org. */
|
|
112
|
+
workspace_admins_can_invite: z.boolean(),
|
|
113
|
+
/** Whether an editor collaborator may invite further collaborators. */
|
|
114
|
+
collaborators_can_invite: z.boolean(),
|
|
115
|
+
/**
|
|
116
|
+
* When the organization is SUSPENDED, do its content rules still bind its
|
|
117
|
+
* members?
|
|
118
|
+
*
|
|
119
|
+
* Today this governs exactly one rule — `personal_space_enabled` — and that
|
|
120
|
+
* is not an accident: every other key above governs behaviour INSIDE a
|
|
121
|
+
* workspace, and a suspended organization grants no workspace context at
|
|
122
|
+
* all, so those are already moot.
|
|
123
|
+
*
|
|
124
|
+
* The name is general because an organization is deciding a principle here,
|
|
125
|
+
* not one checkbox's fate. Default `false`, which is today's behaviour: a
|
|
126
|
+
* suspended organization stops binding, and its members work independently
|
|
127
|
+
* until it resumes. An organization whose reason for disabling the personal
|
|
128
|
+
* space is contractual — the work made here belongs to the institution —
|
|
129
|
+
* turns this on, because an unpaid invoice does not void a contract.
|
|
130
|
+
*/
|
|
131
|
+
policy_survives_suspension: z.boolean(),
|
|
132
|
+
})
|
|
133
|
+
export type PresetSettings = z.infer<typeof PresetSettingsSchema>
|
|
134
|
+
export type PresetSettingKey = keyof PresetSettings
|
|
135
|
+
export const PRESET_SETTING_KEYS = Object.freeze(
|
|
136
|
+
Object.keys(PresetSettingsSchema.shape) as readonly PresetSettingKey[],
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
/** `workspaces.settings` — per-workspace overrides only. */
|
|
140
|
+
export const WorkspaceSettingsSchema = PresetSettingsSchema.partial()
|
|
141
|
+
export type WorkspaceSettings = z.infer<typeof WorkspaceSettingsSchema>
|
|
142
|
+
|
|
143
|
+
const EMAIL_DOMAIN_PATTERN = /^[a-z0-9-]+(\.[a-z0-9-]+)+$/
|
|
144
|
+
|
|
145
|
+
/** `organizations.settings` — preset overrides plus org-only keys. */
|
|
146
|
+
export const OrgSettingsSchema = PresetSettingsSchema.partial().extend({
|
|
147
|
+
/** Lower-case domains that join codes / domain auto-join accept. Empty = any. */
|
|
148
|
+
allowed_email_domains: z.array(z.string().regex(EMAIL_DOMAIN_PATTERN)).optional(),
|
|
149
|
+
/** Per-org relabelling of the kind vocabulary (./vocabulary.ts). */
|
|
150
|
+
vocabulary_overrides: z.record(z.string(), z.string()).optional(),
|
|
151
|
+
})
|
|
152
|
+
export type OrgSettings = z.infer<typeof OrgSettingsSchema>
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
MemberStatus,
|
|
3
|
+
OrgKind,
|
|
4
|
+
OrgRole,
|
|
5
|
+
OrgSettings,
|
|
6
|
+
OrgStatus,
|
|
7
|
+
WorkspaceRole,
|
|
8
|
+
WorkspaceSettings,
|
|
9
|
+
} from "./types.js"
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* What the organization endpoints RETURN.
|
|
13
|
+
*
|
|
14
|
+
* `types.ts` carries what a client must send and the codes it must dispatch
|
|
15
|
+
* on; this carries the other half of the same wire contract — the shapes that
|
|
16
|
+
* come back. It lives here for the same reason: the SDK, the CLI, the app and
|
|
17
|
+
* any third-party integration all read these, and a shape described in three
|
|
18
|
+
* places is a shape that drifts in two of them.
|
|
19
|
+
*
|
|
20
|
+
* CONTRACT ONLY, like its sibling. There is no resolution logic here, no
|
|
21
|
+
* access rule, no vocabulary — a view names fields, it does not decide who
|
|
22
|
+
* may see them. Fields the server omits for a caller without the standing to
|
|
23
|
+
* see them are OPTIONAL here rather than nullable: absent means "not for
|
|
24
|
+
* you", `null` means "genuinely unset", and a client that cannot tell those
|
|
25
|
+
* apart will render the wrong thing.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
export interface OrganizationView {
|
|
29
|
+
id: string
|
|
30
|
+
slug: string
|
|
31
|
+
name: string
|
|
32
|
+
kind: OrgKind
|
|
33
|
+
status: OrgStatus
|
|
34
|
+
ownerUserId: string
|
|
35
|
+
settings: OrgSettings
|
|
36
|
+
termsAcceptedAt: string | null
|
|
37
|
+
createdAt: string
|
|
38
|
+
updatedAt: string
|
|
39
|
+
/** The CALLER's role. Absent on a read that did not establish membership. */
|
|
40
|
+
role?: OrgRole
|
|
41
|
+
memberStatus?: MemberStatus
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface OrgMemberView {
|
|
45
|
+
userId: string
|
|
46
|
+
role: OrgRole
|
|
47
|
+
status: MemberStatus
|
|
48
|
+
joinedAt: string
|
|
49
|
+
email: string | null
|
|
50
|
+
displayName: string | null
|
|
51
|
+
avatarUrl: string | null
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface WorkspaceView {
|
|
55
|
+
id: string
|
|
56
|
+
orgId: string
|
|
57
|
+
name: string
|
|
58
|
+
slug: string
|
|
59
|
+
description: string | null
|
|
60
|
+
settings: WorkspaceSettings
|
|
61
|
+
defaultProjectId: string | null
|
|
62
|
+
archived: boolean
|
|
63
|
+
archivedAt: string | null
|
|
64
|
+
createdAt: string
|
|
65
|
+
updatedAt: string
|
|
66
|
+
/** The CALLER's role. Absent on a read that did not establish membership. */
|
|
67
|
+
role?: WorkspaceRole
|
|
68
|
+
memberStatus?: MemberStatus
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface WorkspaceMemberView {
|
|
72
|
+
userId: string
|
|
73
|
+
role: WorkspaceRole
|
|
74
|
+
displayName: string | null
|
|
75
|
+
avatarUrl: string | null
|
|
76
|
+
addedAt: string
|
|
77
|
+
/** Workspace admins only — absent for a plain member's read. */
|
|
78
|
+
status?: MemberStatus
|
|
79
|
+
creditCap?: number | null
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Where an invitation stands. `expired` is derived from `expiresAt`, not stored. */
|
|
83
|
+
export type InvitationState = "open" | "accepted" | "revoked" | "expired"
|
|
84
|
+
|
|
85
|
+
export interface InvitationView {
|
|
86
|
+
id: string
|
|
87
|
+
orgId: string
|
|
88
|
+
workspaceId: string | null
|
|
89
|
+
email: string
|
|
90
|
+
orgRole: OrgRole
|
|
91
|
+
workspaceRole: WorkspaceRole | null
|
|
92
|
+
invitedBy: string | null
|
|
93
|
+
state: InvitationState
|
|
94
|
+
expiresAt: string
|
|
95
|
+
acceptedAt: string | null
|
|
96
|
+
revokedAt: string | null
|
|
97
|
+
createdAt: string
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* One row per address a create/resend was asked for.
|
|
102
|
+
*
|
|
103
|
+
* `link` is present whenever the address was NOT emailed — an install with no
|
|
104
|
+
* mail provider, or a delivery that failed. A client MUST surface it: the
|
|
105
|
+
* invitation exists either way, and without the link nobody can reach it.
|
|
106
|
+
*/
|
|
107
|
+
export interface InvitationDelivery {
|
|
108
|
+
email: string
|
|
109
|
+
status: "sent" | "link_only" | "failed"
|
|
110
|
+
link?: string
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* What an invitee sees BEFORE signing in — the one organization read that
|
|
115
|
+
* needs no token. `email` comes back masked, so the invitee can recognise
|
|
116
|
+
* their own address without the link disclosing it to whoever holds it.
|
|
117
|
+
*/
|
|
118
|
+
export interface InvitationPreview {
|
|
119
|
+
orgName: string
|
|
120
|
+
kind: OrgKind
|
|
121
|
+
vocabulary: Record<string, string>
|
|
122
|
+
inviterName: string | null
|
|
123
|
+
workspaceName: string | null
|
|
124
|
+
email: string
|
|
125
|
+
expiresAt: string
|
|
126
|
+
state: InvitationState
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface JoinCodeView {
|
|
130
|
+
code: string
|
|
131
|
+
enabled: boolean
|
|
132
|
+
rotatedAt: string
|
|
133
|
+
rotatedBy: string | null
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* What `GET /v1/me` reports about the caller's memberships.
|
|
138
|
+
*
|
|
139
|
+
* Deliberately a SUMMARY, not the full views above: this is the payload every
|
|
140
|
+
* client loads on every session start, and it answers one question — what am
|
|
141
|
+
* I a member of, and what may I call each thing. Names, roles, and the
|
|
142
|
+
* resolved vocabulary are here because a switcher cannot render without them;
|
|
143
|
+
* descriptions, timestamps and default projects are not, because a switcher
|
|
144
|
+
* never shows them and `GET /v1/orgs/:id` exists.
|
|
145
|
+
*
|
|
146
|
+
* The settings block is narrowed to the three keys a CLIENT can act on. The
|
|
147
|
+
* rest of an organization's settings are enforced server-side, and shipping
|
|
148
|
+
* them here would invite a client to enforce them badly.
|
|
149
|
+
*/
|
|
150
|
+
export interface OrganizationSummary {
|
|
151
|
+
id: string
|
|
152
|
+
slug: string
|
|
153
|
+
name: string
|
|
154
|
+
kind: OrgKind
|
|
155
|
+
status: OrgStatus
|
|
156
|
+
/** The caller's own role and standing — always present in this payload. */
|
|
157
|
+
role: OrgRole
|
|
158
|
+
memberStatus: MemberStatus
|
|
159
|
+
settings: {
|
|
160
|
+
personal_space_enabled: boolean
|
|
161
|
+
allowed_email_domains: string[]
|
|
162
|
+
vocabulary_overrides: Record<string, string>
|
|
163
|
+
}
|
|
164
|
+
/** Resolved labels, so no client hard-codes "Class" or "Team". */
|
|
165
|
+
vocabulary: Record<string, string>
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export interface WorkspaceSummary {
|
|
169
|
+
id: string
|
|
170
|
+
orgId: string
|
|
171
|
+
name: string
|
|
172
|
+
slug: string
|
|
173
|
+
role: WorkspaceRole
|
|
174
|
+
memberStatus: MemberStatus
|
|
175
|
+
archived: boolean
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* The organizations block on `GET /v1/me`.
|
|
180
|
+
*
|
|
181
|
+
* THREE distinct states, and a client that collapses them is wrong in a way
|
|
182
|
+
* users feel: the fields ABSENT means this install has no organizations at
|
|
183
|
+
* all; present and empty means the account belongs to none; and
|
|
184
|
+
* `organizationsUnavailable` means the lookup FAILED — in which case a
|
|
185
|
+
* client must KEEP whatever selection it already had, because telling someone
|
|
186
|
+
* their school vanished during a cache blip is worse than a stale switcher.
|
|
187
|
+
*/
|
|
188
|
+
export interface MeOrganizations {
|
|
189
|
+
organizations?: OrganizationSummary[]
|
|
190
|
+
workspaces?: WorkspaceSummary[]
|
|
191
|
+
lastWorkspaceId?: string | null
|
|
192
|
+
organizationsUnavailable?: boolean
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* One recorded action in an organization's audit log.
|
|
197
|
+
*
|
|
198
|
+
* `action` is an OPEN vocabulary and a client must not exhaust it: new
|
|
199
|
+
* actions are added as the product grows, and a switch that throws on an
|
|
200
|
+
* unknown one turns a new feature into a broken page. Render what you
|
|
201
|
+
* recognise, fall back to the raw string for the rest.
|
|
202
|
+
*
|
|
203
|
+
* `actor` is null for anything the system did on nobody's behalf.
|
|
204
|
+
*/
|
|
205
|
+
export interface OrgAuditEntry {
|
|
206
|
+
id: string
|
|
207
|
+
workspaceId: string | null
|
|
208
|
+
action: string
|
|
209
|
+
targetType: string | null
|
|
210
|
+
targetId: string | null
|
|
211
|
+
details: Record<string, unknown>
|
|
212
|
+
createdAt: string
|
|
213
|
+
actor: { userId: string; displayName: string | null; email: string | null } | null
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** A cursor-paged read. The cursor is part of the answer, not a side channel. */
|
|
217
|
+
export interface OrgPage<T> {
|
|
218
|
+
data: T[]
|
|
219
|
+
nextCursor: string | null
|
|
220
|
+
}
|
|
@@ -77,6 +77,17 @@ export const HINT_EXEMPT_PARAMETER_TYPES: ReadonlySet<string> = new Set([
|
|
|
77
77
|
"aspect-ratio",
|
|
78
78
|
])
|
|
79
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Extra person-dimension data-field names contributed by registered person
|
|
82
|
+
* packs. Content-free (field-name strings only) — populated at runtime by
|
|
83
|
+
* `@nodaro/prompts`'s `registerPersonPack`; empty on mainline (identity).
|
|
84
|
+
* shared MUST NOT import prompts, so the field list is pushed in, not pulled.
|
|
85
|
+
*/
|
|
86
|
+
let registeredPersonPackFields: readonly string[] = []
|
|
87
|
+
export function setRegisteredPersonPackFields(fields: readonly string[]): void {
|
|
88
|
+
registeredPersonPackFields = [...fields]
|
|
89
|
+
}
|
|
90
|
+
|
|
80
91
|
export function getParameterValue(
|
|
81
92
|
data: Record<string, unknown>,
|
|
82
93
|
nodeType: string,
|
|
@@ -171,11 +182,11 @@ export function getParameterValue(
|
|
|
171
182
|
return trim(data.backdrop)
|
|
172
183
|
case "held-prop":
|
|
173
184
|
return trim(data.heldProp)
|
|
174
|
-
case "person":
|
|
185
|
+
case "person": {
|
|
175
186
|
// Multi-dimension: return the first set per-dimension value (used for
|
|
176
187
|
// single-string field-mapping resolution; full hint composition goes
|
|
177
188
|
// through buildPersonHints in the executors).
|
|
178
|
-
|
|
189
|
+
const base =
|
|
179
190
|
trim(data.type) ??
|
|
180
191
|
trim(data.age) ??
|
|
181
192
|
trim(data.ethnicity) ??
|
|
@@ -210,7 +221,16 @@ export function getParameterValue(
|
|
|
210
221
|
trim(data.distinctiveFeature) ??
|
|
211
222
|
trim(data.lipState) ??
|
|
212
223
|
trim(data.eyeState)
|
|
213
|
-
)
|
|
224
|
+
if (base !== undefined) return base
|
|
225
|
+
// Fall back to any registered person-pack dimension field (G4): a
|
|
226
|
+
// deployment overlay's extra person dimensions resolve in the
|
|
227
|
+
// `{PersonLabel}` field-mapping single-string path. Empty on mainline.
|
|
228
|
+
for (const field of registeredPersonPackFields) {
|
|
229
|
+
const v = trim(data[field])
|
|
230
|
+
if (v !== undefined) return v
|
|
231
|
+
}
|
|
232
|
+
return undefined
|
|
233
|
+
}
|
|
214
234
|
case "mood":
|
|
215
235
|
return trim(data.mood)
|
|
216
236
|
case "photographer":
|
package/src/producer-types.ts
CHANGED
|
@@ -79,6 +79,16 @@ export const VIDEO_PRODUCER_TYPES: ReadonlySet<string> = new Set([
|
|
|
79
79
|
"cinematic-avatar",
|
|
80
80
|
// Assemble Narrated Video: fits N (clip, voice) blocks into one MP4 → video.
|
|
81
81
|
"assemble-narrated-video",
|
|
82
|
+
// Still to Video: one still image + one audio track → MP4 (local FFmpeg,
|
|
83
|
+
// no provider). Emits generatedVideoUrl like every other ffmpeg video node.
|
|
84
|
+
"still-to-video",
|
|
85
|
+
// Slideshow: 2-100 stills + one optional audio track → MP4 (local FFmpeg,
|
|
86
|
+
// no provider). Same contract; images arrive via the image-collage lane.
|
|
87
|
+
"slideshow",
|
|
88
|
+
// GIF to Video: animated GIF → H.264 MP4 (local FFmpeg, no provider).
|
|
89
|
+
// Emits generatedVideoUrl so it connects to any downstream video consumer
|
|
90
|
+
// (e.g. a Seedance video-reference input) by an ordinary edge.
|
|
91
|
+
"gif-to-video",
|
|
82
92
|
])
|
|
83
93
|
|
|
84
94
|
/**
|
package/src/smart-cut-windows.ts
CHANGED
|
@@ -1,19 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Smart-cut
|
|
2
|
+
* Smart-cut SEARCH WINDOWS — the shared bound + clamp for
|
|
3
3
|
* generate-video-pro's `smartCutFramesPrev` / `smartCutFramesNext`.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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.
|
|
5
|
+
* They bound how much of each side of a boundary the engine considers when
|
|
6
|
+
* it places the cut: N frames from the end of a segment and M from the start
|
|
7
|
+
* of the next. Absent → the engine's own default, byte-identical to the
|
|
8
|
+
* behavior before they were exposed. A boundary the engine cannot resolve
|
|
9
|
+
* inside the window falls back to the fixed freeze-trims; recast pins 24/24.
|
|
17
10
|
*
|
|
18
11
|
* Why a shared clamp: the canvas node (single-node Run) and the orchestrator
|
|
19
12
|
* (workflow Run) are two independent send paths into the same engine route,
|
|
@@ -23,10 +16,8 @@
|
|
|
23
16
|
* and the two paths cannot drift apart.
|
|
24
17
|
*/
|
|
25
18
|
|
|
26
|
-
/** Widest window the UI offers
|
|
27
|
-
*
|
|
28
|
-
* at 24fps, and every frame added past the real overlap only costs match
|
|
29
|
-
* time and invites a spurious pairing. */
|
|
19
|
+
/** Widest window the UI offers (the engine route itself accepts up to 48).
|
|
20
|
+
* Past this, added frames only cost search time and invite a false match. */
|
|
30
21
|
export const SMART_CUT_WINDOW_MAX = 24
|
|
31
22
|
/** Narrowest meaningful window — one frame each side. */
|
|
32
23
|
export const SMART_CUT_WINDOW_MIN = 1
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node types whose output carries Suno chaining ids (`sunoTrackId` /
|
|
3
|
+
* `sunoTaskId`) for a downstream Suno node (extend / separate / replace /
|
|
4
|
+
* add-vocals / …) to chain off.
|
|
5
|
+
*
|
|
6
|
+
* One set for the three readers — the canvas resolver, the orchestrator
|
|
7
|
+
* resolver, and the config panels' "Inherited" hint (#819). They used to keep
|
|
8
|
+
* their own copies and drifted: the canvas read ids off a `suno-separate`
|
|
9
|
+
* (whose output is stems, not a track) while the orchestrator ignored it, so
|
|
10
|
+
* the same graph resolved on one path and not the other. Structural
|
|
11
|
+
* vocabulary only — node type names, no prompt content.
|
|
12
|
+
*/
|
|
13
|
+
export const SUNO_TRACK_SOURCE_TYPES: ReadonlySet<string> = new Set([
|
|
14
|
+
"suno-generate",
|
|
15
|
+
"suno-cover",
|
|
16
|
+
"suno-extend",
|
|
17
|
+
"suno-mashup",
|
|
18
|
+
"suno-replace-section",
|
|
19
|
+
"suno-add-instrumental",
|
|
20
|
+
"suno-add-vocals",
|
|
21
|
+
"suno-convert-wav",
|
|
22
|
+
"suno-upload-extend",
|
|
23
|
+
])
|