@nodaro/shared 2.2.1 → 2.5.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.
@@ -605,8 +605,13 @@ export const IMAGE_I2I_PROVIDERS = [
605
605
  "kontext-multi",
606
606
  // BFL Flux 2 Pro — runs through Replicate with safety_tolerance=5 (max for Pro)
607
607
  "flux-2-pro",
608
+ // BFL FLUX Fill Pro — dedicated masked inpainting via Replicate (white = edit area)
609
+ "flux-fill",
608
610
  // BFL Flux 2 Max — runs through Replicate with safety_tolerance=5, up to 8 refs
609
611
  "flux-2-max",
612
+ // BFL FLUX Fill Pro — dedicated inpainting via Replicate (image + mask + prompt,
613
+ // white = edit area). Second mask-capable i2i provider alongside ideogram-edit.
614
+ "flux-fill",
610
615
  ] as const
611
616
 
612
617
  /** Image editing providers (upscale, remove bg, etc.) */
@@ -1039,7 +1044,7 @@ export type VoiceDesignModel = typeof VOICE_DESIGN_MODELS[number]
1039
1044
  export const DEFAULT_VOICE_DESIGN_MODEL: VoiceDesignModel = "eleven_ttv_v3"
1040
1045
 
1041
1046
  /** I2I providers that support mask-based inpainting */
1042
- export const I2I_MASK_SUPPORT = new Set(["ideogram-edit"])
1047
+ export const I2I_MASK_SUPPORT = new Set(["ideogram-edit", "flux-fill"])
1043
1048
 
1044
1049
  /**
1045
1050
  * Mask edit tier per image-gen provider (single source of truth for inpaint).
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Workflow-node parameter normalization.
3
+ *
4
+ * The config panels already prevent an impossible provider/parameter pair from
5
+ * being *selected*: their dropdowns are provider-aware and a fail-safe effect
6
+ * snaps stale values when the provider changes. But both of those are React
7
+ * effects — they only run when that specific node's panel or hover strip is
8
+ * mounted. A node written straight into workflow JSON by a non-UI author (an
9
+ * agent, an import, a template) is never seen by either, so its invalid pair
10
+ * survives all the way to the provider call.
11
+ *
12
+ * That is the 2026-08-09 incident: one `generate-image` node carrying
13
+ * `gpt-image` + `16:9` (GPT Image 1.5 renders 1:1 / 3:2 / 2:3 only) reached KIE,
14
+ * got rejected, and aborted a run whose five sibling nodes had already produced
15
+ * — and billed for — their images.
16
+ *
17
+ * This module is the write-boundary guard: it heals the node instead of
18
+ * rejecting it, so the user sees a valid value on the canvas rather than a
19
+ * failed run. It reads exclusively from `MODEL_CATALOG`, so a model added later
20
+ * is covered without touching this file.
21
+ */
22
+
23
+ import { normalizeModelInput, type ModelInputAdjustment } from "./model-catalog.js"
24
+
25
+ /**
26
+ * Node types whose `data` carries catalog-governed model parameters under the
27
+ * shape this module understands (`provider` + aspectRatio/resolution/quality).
28
+ *
29
+ * IMAGE ONLY, deliberately. The video nodes route several of these fields
30
+ * through mode-dependent defaults (`"adaptive"` for Seedance/Hailuo, duration
31
+ * composites tied to pricing) that this flat normalizer would flatten wrongly;
32
+ * they get their own pass once those defaults are catalog-derived too.
33
+ */
34
+ export const MODEL_PARAM_NODE_TYPES: ReadonlySet<string> = new Set([
35
+ "generate-image",
36
+ "image-to-image",
37
+ ])
38
+
39
+ export interface NodeParamAdjustment extends ModelInputAdjustment {
40
+ nodeId: string
41
+ provider: string
42
+ }
43
+
44
+ export interface NormalizedNodes<T> {
45
+ nodes: T[]
46
+ /** Empty when nothing needed correcting. */
47
+ adjustments: NodeParamAdjustment[]
48
+ }
49
+
50
+ interface NodeLike {
51
+ id?: unknown
52
+ type?: unknown
53
+ data?: unknown
54
+ }
55
+
56
+ /**
57
+ * Return `nodes` with every catalog-governed image parameter coerced into a
58
+ * combination its provider actually accepts, plus the list of what changed.
59
+ *
60
+ * Immutable: nodes that need no correction are returned by reference, and a
61
+ * corrected node is a fresh object (never a mutation of the caller's input).
62
+ *
63
+ * Multi-provider nodes (`data.providers` holding 2+ entries) are SKIPPED: the
64
+ * valid set there is the intersection across every selected provider, and when
65
+ * a stored value falls outside it there is no single defensible replacement —
66
+ * picking one provider's default would silently misconfigure the others. Those
67
+ * nodes are still guarded interactively by the panel's intersection dropdown.
68
+ */
69
+ export function normalizeNodeModelParams<T extends NodeLike>(
70
+ nodes: readonly T[],
71
+ ): NormalizedNodes<T> {
72
+ const adjustments: NodeParamAdjustment[] = []
73
+ const out = nodes.map((node) => {
74
+ const type = typeof node.type === "string" ? node.type : ""
75
+ if (!MODEL_PARAM_NODE_TYPES.has(type)) return node
76
+
77
+ const data = node.data
78
+ if (!data || typeof data !== "object" || Array.isArray(data)) return node
79
+ const d = data as Record<string, unknown>
80
+
81
+ const multi = Array.isArray(d.providers) ? (d.providers as unknown[]) : []
82
+ if (multi.length > 1) return node
83
+
84
+ const provider =
85
+ typeof d.provider === "string"
86
+ ? d.provider
87
+ : typeof multi[0] === "string"
88
+ ? (multi[0] as string)
89
+ : undefined
90
+ if (!provider) return node
91
+
92
+ const normalized = normalizeModelInput(provider, {
93
+ aspectRatio: typeof d.aspectRatio === "string" ? d.aspectRatio : undefined,
94
+ resolution: typeof d.resolution === "string" ? d.resolution : undefined,
95
+ quality: typeof d.quality === "string" ? d.quality : undefined,
96
+ })
97
+ if (normalized.adjustments.length === 0) return node
98
+
99
+ const nodeId = typeof node.id === "string" ? node.id : "(unknown node)"
100
+ for (const adj of normalized.adjustments) {
101
+ adjustments.push({ ...adj, nodeId, provider })
102
+ }
103
+
104
+ // Only the three governed keys are rewritten; everything else on the node
105
+ // is passed through untouched. A dropped lever is written as `undefined`
106
+ // rather than deleted so the shape stays stable for downstream readers.
107
+ return {
108
+ ...node,
109
+ data: {
110
+ ...d,
111
+ aspectRatio: normalized.aspectRatio,
112
+ resolution: normalized.resolution,
113
+ quality: normalized.quality,
114
+ },
115
+ }
116
+ })
117
+
118
+ return { nodes: out, adjustments }
119
+ }
120
+
121
+ /** One-line-per-change summary, for surfacing back to an agent or a log. */
122
+ export function describeNodeAdjustments(adjustments: readonly NodeParamAdjustment[]): string[] {
123
+ return adjustments.map(
124
+ (a) => `${a.nodeId} (${a.provider}): ${a.field} "${a.from}" → ${a.to === undefined ? "removed" : `"${a.to}"`} — ${a.reason}`,
125
+ )
126
+ }
@@ -65,6 +65,7 @@ export function validateModeActivation(
65
65
  // Tier → max parallel pipelines (Architecture §5.4)
66
66
  export const TIER_PIPELINE_PARALLELISM: Record<string, number> = {
67
67
  free: 0,
68
+ payg: 1, // derived tier — pipeline entitlements copy basic's
68
69
  basic: 1,
69
70
  standard: 2,
70
71
  pro: 3,
@@ -80,6 +81,7 @@ export const TIER_PIPELINE_PARALLELISM: Record<string, number> = {
80
81
  // the user paid for, instead of the intended two-thirds.
81
82
  export const TIER_MAX_PIPELINE_COST_CREDITS: Record<string, number> = {
82
83
  free: 0,
84
+ payg: 3000, // derived tier — copies basic
83
85
  basic: 3000,
84
86
  standard: 8000,
85
87
  pro: 20000,