@nodaro/shared 1.9.0 → 1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nodaro/shared",
3
- "version": "1.9.0",
3
+ "version": "1.10.0",
4
4
  "description": "Shared types, model catalog, wire contracts, and structural vocabularies for the Nodaro platform and SDK.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -17,6 +17,8 @@ describe("buildCreditModelIdentifier", () => {
17
17
  "seedream-edit",
18
18
  "seedream-5-lite",
19
19
  "seedream-5-lite-i2i",
20
+ "seedream-5-pro",
21
+ "seedream-5-pro-i2i",
20
22
  ]
21
23
 
22
24
  it.each(highQualityProviders)(
@@ -16,7 +16,9 @@ describe("imageReferenceLimit", () => {
16
16
  "flux-2-max": 8,
17
17
  "gpt-image-2": 16, // → gpt-image-2-i2i
18
18
  "seedream-5-lite": 16, // → seedream-5-lite-i2i
19
+ "seedream-5-pro": 16, // → seedream-5-pro-i2i
19
20
  "nano-banana-2": 4,
21
+ "nano-banana-2-lite": 10, // schema maxItems (image_urls)
20
22
  flux: 4, // → flux-pro-i2i
21
23
  "flux-2-klein": 1, // product cap (schema array slices at 5)
22
24
  "flux-2-pro": 4, // product cap (BFL schema accepts 8)
@@ -430,20 +430,27 @@ describe("STRUCTURED_VISION_MODELS", () => {
430
430
  "gemini-3.1-pro",
431
431
  "claude-sonnet-5",
432
432
  "claude-opus-4.8",
433
+ // responses-format GPTs — KIE text.format json_schema live-verified
434
+ // 2026-07-14 (text AND vision inputs).
435
+ "gpt-5.4",
436
+ "gpt-5.5",
437
+ "gpt-5.6-luna",
438
+ "gpt-5.6-terra",
439
+ "gpt-5.6-sol",
433
440
  ].sort(),
434
441
  )
435
442
  })
436
443
 
437
- it("includes Anthropic (forced-tool) AND Gemini (response_format) vendors", () => {
444
+ it("includes Anthropic (forced-tool), Gemini (response_format), and OpenAI (responses text.format) vendors", () => {
438
445
  const vendors = new Set(STRUCTURED_VISION_MODELS.map((m) => m.vendor))
439
446
  expect(vendors).toContain("anthropic")
440
447
  expect(vendors).toContain("google")
448
+ expect(vendors).toContain("openai")
441
449
  })
442
450
 
443
- it("excludes GPT models — no native structured mode (parse+retry only)", () => {
451
+ it("excludes chat-completions GPT models — no native structured mode there (parse+retry only)", () => {
444
452
  const ids = STRUCTURED_VISION_MODELS.map((m) => m.id)
445
453
  expect(ids).not.toContain("gpt-5.2")
446
- expect(ids).not.toContain("gpt-5.4")
447
454
  })
448
455
 
449
456
  it("every member is vision-capable and has a structuredOutputMode", () => {
@@ -28,6 +28,7 @@ describe("per-model prompt length limits", () => {
28
28
  describe("getMaxImagePromptChars", () => {
29
29
  it("returns the verified higher caps (provider supports more than 5000)", () => {
30
30
  expect(getMaxImagePromptChars("nano-banana-2")).toBe(20000)
31
+ expect(getMaxImagePromptChars("nano-banana-2-lite")).toBe(20000)
31
32
  expect(getMaxImagePromptChars("nano-banana-pro")).toBe(20000)
32
33
  expect(getMaxImagePromptChars("gpt-image-2-i2i")).toBe(20000)
33
34
  })
@@ -44,6 +45,8 @@ describe("per-model prompt length limits", () => {
44
45
  it("falls back to IMAGE_PROMPT_MAX for verified-default + unknown providers", () => {
45
46
  expect(getMaxImagePromptChars("flux")).toBe(IMAGE_PROMPT_MAX)
46
47
  expect(getMaxImagePromptChars("imagen4")).toBe(IMAGE_PROMPT_MAX)
48
+ expect(getMaxImagePromptChars("seedream-5-pro")).toBe(IMAGE_PROMPT_MAX) // verified 5000 in KIE schema
49
+ expect(getMaxImagePromptChars("seedream-5-pro-i2i")).toBe(IMAGE_PROMPT_MAX) // verified 5000 in KIE schema
47
50
  expect(getMaxImagePromptChars("grok-i2i")).toBe(IMAGE_PROMPT_MAX) // 390000 doc = sanity-capped
48
51
  expect(getMaxImagePromptChars("flux-kontext")).toBe(IMAGE_PROMPT_MAX) // UNVERIFIED
49
52
  expect(getMaxImagePromptChars(undefined)).toBe(IMAGE_PROMPT_MAX)
@@ -0,0 +1,24 @@
1
+ import { describe, it, expect } from "vitest"
2
+ import {
3
+ SEEDANCE_2_CONTINUATION_REF_SEC,
4
+ SEEDANCE_2_R2V_MIN_REF_VIDEO_SEC,
5
+ SEEDANCE_2_EXTEND_STITCH,
6
+ } from "../model-constants.js"
7
+
8
+ /** KIE hard-rejects r2v reference videos below the floor: "the parameter
9
+ * video duration (seconds) specified in the request must be greater than or
10
+ * equal to 1.8 for model dreamina-seedance-2-0-fast in r2v" (2026-07-13,
11
+ * job dbf95612 — the original 1.0s tails made every generate-video-pro
12
+ * continuation segment fail deterministically). Everything this platform
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 private-plugin
15
+ * twin constants (nodaro-cloud-plugins chain.ts TAIL_SEC / bridge-math.ts
16
+ * MIN_REF) are guarded by that repo's r2v-ref-floor.test.ts — keep in sync. */
17
+ describe("seedance-2 continuation-reference floor (do not regress)", () => {
18
+ it("the platform's continuation-ref length clears KIE's r2v minimum", () => {
19
+ expect(SEEDANCE_2_CONTINUATION_REF_SEC).toBeGreaterThanOrEqual(SEEDANCE_2_R2V_MIN_REF_VIDEO_SEC)
20
+ })
21
+ it("the extend node's tail IS the shared continuation-ref length (single source)", () => {
22
+ expect(SEEDANCE_2_EXTEND_STITCH.referenceTailSeconds).toBe(SEEDANCE_2_CONTINUATION_REF_SEC)
23
+ })
24
+ })
@@ -5,13 +5,16 @@ import {
5
5
  pickVideoAnalysisBucket, buildVideoAnalysisCreditId, bucketSecondsFromCreditId,
6
6
  videoAnalysisNumWindows,
7
7
  } from "../video-analysis-pricing.js"
8
- import { VIDEO_ANALYSIS_LLM_MODELS } from "../llm-models.js"
8
+ import {
9
+ VIDEO_ANALYSIS_LLM_MODELS, VIDEO_ANALYSIS_TIERS, VIDEO_ANALYSIS_TIER_ORDER,
10
+ DEFAULT_VIDEO_ANALYSIS_TIER, DEFAULT_VIDEO_ANALYSIS_MODEL, resolveVideoAnalysisModel,
11
+ } from "../llm-models.js"
9
12
 
10
13
  // The measured-rate constants and the $-derived `videoAnalysisBucketCredits`
11
- // formula moved to backend/src/lib/pricing/video-analysis-cost.ts (S5) — its
12
- // tests (including the worked-example bucket-credit values and the
13
- // cross-check against VIDEO_ANALYSIS_BUCKET_CREDITS below) live in
14
- // backend/src/lib/pricing/__tests__/video-analysis-cost.test.ts. This file
14
+ // formula are PRIVATE, in @nodaroai/cloud-plugins
15
+ // (src/plugins/video-analysis/cost.ts) — its tests (the worked-example
16
+ // bucket-credit values and the cross-check against VIDEO_ANALYSIS_BUCKET_CREDITS
17
+ // below) live in that private package's __tests__/cost.test.ts. This file
15
18
  // covers only the NON-monetary duration-bucketing, window-batching, and
16
19
  // credit-id-construction logic that stays in the published package.
17
20
 
@@ -40,6 +43,26 @@ describe("video-analysis-pricing", () => {
40
43
  expect(VIDEO_ANALYSIS_LLM_MODELS).toEqual(["gemini-3-flash", "gemini-3.1-pro"])
41
44
  })
42
45
 
46
+ it("tier layer: every tier maps to a real model AND every model is tier-reachable (no vendor leak)", () => {
47
+ // Adding a video-analysis model without a tier would silently leave it
48
+ // unreachable / unnamed — this fails until a tier decision is made.
49
+ const tierTargets = Object.values(VIDEO_ANALYSIS_TIERS)
50
+ for (const m of tierTargets) expect(VIDEO_ANALYSIS_LLM_MODELS).toContain(m)
51
+ for (const m of VIDEO_ANALYSIS_LLM_MODELS) expect(tierTargets).toContain(m)
52
+ expect(new Set(VIDEO_ANALYSIS_TIER_ORDER)).toEqual(new Set(Object.keys(VIDEO_ANALYSIS_TIERS)))
53
+ })
54
+
55
+ it("resolveVideoAnalysisModel: tier → model, raw model passthrough, default pro on empty/unknown", () => {
56
+ expect(DEFAULT_VIDEO_ANALYSIS_TIER).toBe("pro")
57
+ expect(DEFAULT_VIDEO_ANALYSIS_MODEL).toBe("gemini-3.1-pro")
58
+ expect(resolveVideoAnalysisModel("pro")).toBe("gemini-3.1-pro")
59
+ expect(resolveVideoAnalysisModel("fast")).toBe("gemini-3-flash")
60
+ expect(resolveVideoAnalysisModel("gemini-3-flash")).toBe("gemini-3-flash") // raw passthrough
61
+ expect(resolveVideoAnalysisModel(undefined)).toBe("gemini-3.1-pro") // default → pro
62
+ expect(resolveVideoAnalysisModel("")).toBe("gemini-3.1-pro")
63
+ expect(resolveVideoAnalysisModel("nonsense")).toBe("gemini-3.1-pro") // unknown → default, never throws
64
+ })
65
+
43
66
  // Full drift-detection against the live $-formula lives in
44
67
  // backend/src/lib/pricing/__tests__/video-analysis-cost.test.ts (this
45
68
  // package cannot see the formula post-S5). This is a lightweight shape
@@ -7,7 +7,7 @@ import {
7
7
  } from "../video-analysis.js"
8
8
 
9
9
  const slot: EntitySlot = { slotId: "hero", label: "Protagonist", source: "wired-character", role: "person", description: "tan man, mustache, black tee" }
10
- const baseScene = { startSec: 0, endSec: 4, label: "Hook", shotType: "Medium Close-Up", camera: "slow push-in", visual: "{slot:hero} juggles a ball", audio: { mode: "speech" as const, content: "As a kid…", voice: "male, warm" } }
10
+ const baseScene = { startSec: 0, endSec: 4, label: "Hook", shotType: "Medium Close-Up", camera: "slow push-in", visual: "{slot:hero} juggles a ball", audio: [{ mode: "speech" as const, content: "As a kid…", voice: "male, warm" }] }
11
11
 
12
12
  describe("windowAnalysisSchema", () => {
13
13
  it("accepts a zero-scene window (quiet footage is a VALID result)", () => {
package/src/index.ts CHANGED
@@ -19,6 +19,7 @@ export {
19
19
  MAX_IMAGE_PROMPT_CHARS_BY_PROVIDER,
20
20
  getMaxImagePromptChars,
21
21
  PROMPT_HARD_CEILING,
22
+ LLM_TEXT_INPUT_MAX,
22
23
  MAX_VIDEO_PROMPT_CHARS_BY_PROVIDER,
23
24
  getMaxVideoPromptChars,
24
25
  NEGATIVE_PROMPT_MAX,
@@ -95,6 +96,8 @@ export {
95
96
  SEEDANCE_2_PROVIDERS,
96
97
  SEEDANCE_2_REF_LIMITS,
97
98
  SEEDANCE_2_EXTEND_STITCH,
99
+ SEEDANCE_2_R2V_MIN_REF_VIDEO_SEC,
100
+ SEEDANCE_2_CONTINUATION_REF_SEC,
98
101
  NATIVE_ADAPTIVE_ASPECT,
99
102
  VIDEO_REF_LIMITS_BY_PROVIDER,
100
103
  VIDEO_PROVIDERS_REQUIRING_IMAGE,
@@ -250,6 +253,14 @@ export {
250
253
  LLM_MODEL_IDS,
251
254
  STRUCTURED_VISION_MODELS,
252
255
  VIDEO_ANALYSIS_LLM_MODELS,
256
+ VIDEO_ANALYSIS_TIERS,
257
+ type VideoAnalysisTier,
258
+ VIDEO_ANALYSIS_TIER_ORDER,
259
+ DEFAULT_VIDEO_ANALYSIS_TIER,
260
+ DEFAULT_VIDEO_ANALYSIS_MODEL,
261
+ VIDEO_ANALYSIS_TIER_LABELS,
262
+ isVideoAnalysisTier,
263
+ resolveVideoAnalysisModel,
253
264
  LLM_FEATURE_DEFAULTS,
254
265
  LLM_MODALITY_CAPS,
255
266
  LLM_REASONING_EFFORTS,
package/src/llm-models.ts CHANGED
@@ -37,14 +37,18 @@ export interface LlmModelDef {
37
37
  maxOutputTokens: number
38
38
  /**
39
39
  * How this model can be forced into schema-valid structured output.
40
- * - "anthropic-tool" direct Anthropic SDK forced tool_choice (guaranteed).
41
- * - "kie-response-format" → KIE chat-completions `response_format: json_schema`
42
- * (verified enforced for Gemini via KIE).
43
- * - undefined no native structured mode (GPT-via-KIE doesn't honor
44
- * response_format); callers fall back to parse + retry.
40
+ * - "anthropic-tool" forced tool_choice on the Claude wire (direct SDK
41
+ * guaranteed; KIE's proxy re-serializes the tool call
42
+ * as a `<tool_calls>` text tag — decoded in llm-client).
43
+ * - "kie-response-format" KIE chat-completions `response_format: json_schema`
44
+ * (verified enforced for Gemini via KIE).
45
+ * - "responses-json-schema" → KIE codex/v1/responses `text.format: json_schema`
46
+ * (live-verified 2026-07-14 for gpt-5.4/5.5 and the
47
+ * whole GPT-5.6 family, text AND vision inputs).
48
+ * - undefined → no native mode; callers fall back to parse + retry.
45
49
  * Capability-driven so `llmCompleteStructured` never hardcodes provider ids.
46
50
  */
47
- structuredOutputMode?: "anthropic-tool" | "kie-response-format"
51
+ structuredOutputMode?: "anthropic-tool" | "kie-response-format" | "responses-json-schema"
48
52
  /** If set, fallback to direct Anthropic SDK with this model ID when KIE.ai fails */
49
53
  directFallbackModel?: string
50
54
  /** Effort levels this model accepts (ascending). Absent/empty = no effort lever, picker hidden. */
@@ -142,6 +146,7 @@ export const LLM_MODELS: readonly LlmModelDef[] = [
142
146
  kieFormat: "responses",
143
147
  kieSlugOrModel: "gpt-5-4",
144
148
  vendor: "openai",
149
+ structuredOutputMode: "responses-json-schema",
145
150
  supportsImages: true,
146
151
  maxOutputTokens: 16384,
147
152
  reasoningEfforts: ["low", "medium", "high"],
@@ -154,6 +159,7 @@ export const LLM_MODELS: readonly LlmModelDef[] = [
154
159
  kieFormat: "responses",
155
160
  kieSlugOrModel: "gpt-5-5",
156
161
  vendor: "openai",
162
+ structuredOutputMode: "responses-json-schema",
157
163
  supportsImages: true,
158
164
  maxOutputTokens: 16384,
159
165
  reasoningEfforts: ["none", "low", "medium", "high", "xhigh", "max"],
@@ -167,6 +173,7 @@ export const LLM_MODELS: readonly LlmModelDef[] = [
167
173
  kieFormat: "responses",
168
174
  kieSlugOrModel: "gpt-5-6-luna",
169
175
  vendor: "openai",
176
+ structuredOutputMode: "responses-json-schema",
170
177
  supportsImages: true,
171
178
  maxOutputTokens: 16384,
172
179
  reasoningEfforts: ["none", "low", "medium", "high", "xhigh", "max"],
@@ -180,6 +187,7 @@ export const LLM_MODELS: readonly LlmModelDef[] = [
180
187
  kieFormat: "responses",
181
188
  kieSlugOrModel: "gpt-5-6-terra",
182
189
  vendor: "openai",
190
+ structuredOutputMode: "responses-json-schema",
183
191
  supportsImages: true,
184
192
  maxOutputTokens: 16384,
185
193
  reasoningEfforts: ["none", "low", "medium", "high", "xhigh", "max"],
@@ -193,6 +201,7 @@ export const LLM_MODELS: readonly LlmModelDef[] = [
193
201
  kieFormat: "responses",
194
202
  kieSlugOrModel: "gpt-5-6-sol",
195
203
  vendor: "openai",
204
+ structuredOutputMode: "responses-json-schema",
196
205
  supportsImages: true,
197
206
  maxOutputTokens: 16384,
198
207
  reasoningEfforts: ["none", "low", "medium", "high", "xhigh", "max"],
@@ -238,9 +247,10 @@ export const LLM_MODEL_IDS = LLM_MODELS.map((m) => m.id)
238
247
  /** Vision models that can return GUARANTEED structured output — the
239
248
  * describe-to-picker analyzer forces a schema over an image, so its model
240
249
  * pickers AND the backend route gate offer exactly these: Anthropic (forced
241
- * tool) + Gemini (KIE `response_format`). GPT-via-KIE has no native structured
242
- * mode (parse+retry only), so it's excluded. Single source of truth so the
243
- * picker, the config panel, and the route gate can't drift. */
250
+ * tool), Gemini (KIE `response_format`), and GPT responses-format models
251
+ * (KIE `text.format` json_schema vision+schema live-verified 2026-07-14).
252
+ * Single source of truth so the picker, the config panel, and the route gate
253
+ * can't drift. */
244
254
  export const STRUCTURED_VISION_MODELS = LLM_MODELS.filter(
245
255
  (m) => m.supportsImages && m.structuredOutputMode != null,
246
256
  )
@@ -376,3 +386,36 @@ export function resolveLlmCreditId(feature: string, body: unknown): string {
376
386
  export const VIDEO_ANALYSIS_LLM_MODELS: string[] = LLM_MODELS
377
387
  .filter((m) => getLlmModalityCaps(m.id).video && getLlmModalityCaps(m.id).audio)
378
388
  .map((m) => m.id)
389
+
390
+ /**
391
+ * Video-analysis quality TIERS — the ONLY analyzer identifiers exposed to users
392
+ * (API, UI, docs). Each maps to an internal analysis model; the underlying
393
+ * vendor/model name is never surfaced. Default is `pro`. The guard test
394
+ * (video-analysis-pricing.test.ts) asserts every tier target is a real
395
+ * VIDEO_ANALYSIS_LLM_MODELS member AND every such model is reachable by a tier,
396
+ * so adding a video model forces a tier decision instead of silently leaking.
397
+ */
398
+ export const VIDEO_ANALYSIS_TIERS = { fast: "gemini-3-flash", pro: "gemini-3.1-pro" } as const
399
+ export type VideoAnalysisTier = keyof typeof VIDEO_ANALYSIS_TIERS
400
+ /** UI/listing order — recommended (pro) first. */
401
+ export const VIDEO_ANALYSIS_TIER_ORDER = ["pro", "fast"] as const
402
+ export const DEFAULT_VIDEO_ANALYSIS_TIER: VideoAnalysisTier = "pro"
403
+ export const DEFAULT_VIDEO_ANALYSIS_MODEL: string = VIDEO_ANALYSIS_TIERS[DEFAULT_VIDEO_ANALYSIS_TIER]
404
+ /** Neutral, vendor-free display labels for the UI. */
405
+ export const VIDEO_ANALYSIS_TIER_LABELS: Record<VideoAnalysisTier, string> = { fast: "Fast", pro: "Pro" }
406
+
407
+ export function isVideoAnalysisTier(v: string): v is VideoAnalysisTier {
408
+ return Object.prototype.hasOwnProperty.call(VIDEO_ANALYSIS_TIERS, v)
409
+ }
410
+
411
+ /**
412
+ * Resolve a user-supplied tier (`"fast"`/`"pro"`) OR a raw internal model id to
413
+ * the internal analysis model id. Empty/unknown → the default tier's model.
414
+ * Real model ids pass through (back-compat for existing stored `llmModel`
415
+ * values); anything else falls back to the default rather than erroring.
416
+ */
417
+ export function resolveVideoAnalysisModel(input?: string | null): string {
418
+ if (input && isVideoAnalysisTier(input)) return VIDEO_ANALYSIS_TIERS[input]
419
+ if (input && VIDEO_ANALYSIS_LLM_MODELS.includes(input)) return input
420
+ return DEFAULT_VIDEO_ANALYSIS_MODEL
421
+ }
@@ -146,7 +146,7 @@ export const MODEL_RECOMMENDATIONS: readonly ModelRecommendation[] = [
146
146
  { intent: "best for typography / logos / text-heavy", modelIds: ["nano-banana-pro", "gpt-image-2"], note: "Nano Banana Pro for diagrams / complex text; GPT Image 2 for logos and short copy." },
147
147
  { intent: "cheapest realistic image", modelIds: ["z-image", "qwen", "imagen4-fast"], note: "Z-Image is the cheapest at 1 credit. Qwen / Imagen4 Fast for slightly higher quality." },
148
148
  { intent: "highest fidelity image", modelIds: ["nano-banana-pro", "imagen4-ultra", "flux-flex"], note: "Pick by family preference; all three are premium tiers." },
149
- { intent: "image edit / restyle", modelIds: ["flux-kontext", "ideogram-remix", "seedream-5-lite-i2i"], note: "Flux Kontext preserves identity; Ideogram Remix is character-aware; Seedream 5 Lite for instruction-based edits." },
149
+ { intent: "image edit / restyle", modelIds: ["flux-kontext", "ideogram-remix", "seedream-5-pro-i2i"], note: "Flux Kontext preserves identity; Ideogram Remix is character-aware; Seedream 5 Pro for instruction-based edits (5 Lite is the budget option)." },
150
150
  { intent: "highest-resolution image (4K / 8K)", modelIds: ["topaz-image-upscale", "nano-banana-pro", "gpt-image-2"], note: "Generate at native then Topaz upscale for 8K." },
151
151
  { intent: "background removal / cutout", modelIds: ["recraft-remove-bg"], note: "1 credit, no prompt needed." },
152
152
  // video
@@ -165,6 +165,11 @@ export const MODEL_RECOMMENDATIONS: readonly ModelRecommendation[] = [
165
165
  // can consume them. Keep in sync when adding a new ratio set.)
166
166
  // =============================================================================
167
167
  const NANO_BANANA_RATIOS = ["1:1", "16:9", "9:16", "4:3", "3:4", "3:2", "2:3", "4:5", "5:4", "21:9"] as const
168
+ // Nano Banana 2 Lite only — the model's full documented enum incl. `auto`
169
+ // (native default) and extreme banner ratios (docs.kie.ai/market/google/
170
+ // nano-banana-2-lite). Kept separate so the wider set can't leak to the
171
+ // rest of the family.
172
+ const NANO_BANANA_2_LITE_RATIOS = ["auto", "1:1", "16:9", "9:16", "4:3", "3:4", "3:2", "2:3", "4:5", "5:4", "21:9", "4:1", "1:4", "8:1", "1:8"] as const
168
173
  // Note: these arrays are synchronized with the frontend's
169
174
  // `model-options.ts` constants of the same names. The frontend now imports
170
175
  // them via the `getAspectRatioOptions(modelId)` helper; if you update one
@@ -233,6 +238,22 @@ const IMAGE_MODELS: Record<string, ModelCatalogEntry> = {
233
238
  { identifier: "nano-banana-2:4K", credits: 5, note: "4K" },
234
239
  ],
235
240
  },
241
+ "nano-banana-2-lite": {
242
+ id: "nano-banana-2-lite",
243
+ kind: "image",
244
+ // Same single-id shape as nano-banana-2: optional input images on the one
245
+ // endpoint (via image_urls, up to 10), so reachable for both t2i and i2i.
246
+ modes: ["t2i", "i2i"] as const,
247
+ family: "Google",
248
+ label: "Nano Banana 2 Lite",
249
+ series: "Nano Banana",
250
+ description: "Lightweight Nano Banana 2 (Gemini 3.1 Flash-Lite) — fast, low-cost 1K generation and editing.",
251
+ useCases: ["realistic", "drafts", "iteration"],
252
+ features: ["reference-image"],
253
+ aspectRatios: NANO_BANANA_2_LITE_RATIOS,
254
+ // 1K only — no resolution lever, flat pricing.
255
+ pricing: [{ identifier: "nano-banana-2-lite", credits: 2, note: "1K flat" }],
256
+ },
236
257
  "nano-banana-pro": {
237
258
  id: "nano-banana-pro",
238
259
  kind: "image",
@@ -691,6 +712,40 @@ const IMAGE_MODELS: Record<string, ModelCatalogEntry> = {
691
712
  { identifier: "seedream-5-lite-i2i:high", credits: 5, note: "high quality" },
692
713
  ],
693
714
  },
715
+ "seedream-5-pro": {
716
+ id: "seedream-5-pro",
717
+ kind: "image",
718
+ modes: ["t2i"] as const,
719
+ family: "Bytedance",
720
+ label: "Seedream 5 Pro",
721
+ series: "Seedream",
722
+ description: "Flagship Seedream 5 Pro — strongest instruction following and visual reasoning. Basic = 1K, high = 2K.",
723
+ useCases: ["realistic", "instruction"],
724
+ features: ["reference-image"],
725
+ aspectRatios: SEEDREAM_RATIOS,
726
+ qualities: ["basic", "high"],
727
+ pricing: [
728
+ { identifier: "seedream-5-pro", credits: 3, note: "basic / 1K default" },
729
+ { identifier: "seedream-5-pro:high", credits: 6, note: "high / 2K" },
730
+ ],
731
+ },
732
+ "seedream-5-pro-i2i": {
733
+ id: "seedream-5-pro-i2i",
734
+ kind: "image",
735
+ modes: ["i2i"] as const,
736
+ family: "Bytedance",
737
+ label: "Seedream 5 Pro (I2I)",
738
+ series: "Seedream",
739
+ description: "Image-to-image with Seedream 5 Pro — multi-reference instruction edits. Basic = 1K, high = 2K.",
740
+ useCases: ["edit", "instruction"],
741
+ features: ["reference-image"],
742
+ aspectRatios: SEEDREAM_RATIOS,
743
+ qualities: ["basic", "high"],
744
+ pricing: [
745
+ { identifier: "seedream-5-pro-i2i", credits: 3, note: "basic / 1K default" },
746
+ { identifier: "seedream-5-pro-i2i:high", credits: 6, note: "high / 2K" },
747
+ ],
748
+ },
694
749
 
695
750
  // ── Alibaba Qwen ──
696
751
  "qwen": {
@@ -1754,10 +1809,10 @@ const VIDEO_MODELS: Record<string, ModelCatalogEntry> = {
1754
1809
  id: "gemini-3-flash-video-analysis",
1755
1810
  kind: "video",
1756
1811
  modes: ["video-analysis"] as const,
1757
- family: "Google",
1758
- label: "Video Analysis (Gemini 3 Flash)",
1759
- series: "Gemini",
1760
- description: "Analyze a video into a structured shot list (scenes, camera, audio) — fast Gemini tier. Billed per duration bucket.",
1812
+ family: "Nodaro",
1813
+ label: "Video Analysis (Fast)",
1814
+ series: "Video Analysis",
1815
+ description: "Analyze a video into a structured shot list (scenes, camera, audio) — fast, economy tier. Billed per duration bucket.",
1761
1816
  useCases: ["video-analysis", "shot-list", "fast"],
1762
1817
  pricing: [
1763
1818
  { identifier: "video-analysis:gemini-3-flash", credits: 3, note: "10-min ceiling (no duration given)" },
@@ -1771,10 +1826,10 @@ const VIDEO_MODELS: Record<string, ModelCatalogEntry> = {
1771
1826
  id: "gemini-3.1-pro-video-analysis",
1772
1827
  kind: "video",
1773
1828
  modes: ["video-analysis"] as const,
1774
- family: "Google",
1775
- label: "Video Analysis (Gemini 3.1 Pro)",
1776
- series: "Gemini",
1777
- description: "Analyze a video into a structured shot list (scenes, camera, audio) — high-fidelity Gemini tier. Billed per duration bucket.",
1829
+ family: "Nodaro",
1830
+ label: "Video Analysis (Pro)",
1831
+ series: "Video Analysis",
1832
+ description: "Analyze a video into a structured shot list (scenes, camera, audio) — higher-fidelity, default tier. Billed per duration bucket.",
1778
1833
  useCases: ["video-analysis", "shot-list", "cinematic"],
1779
1834
  pricing: [
1780
1835
  { identifier: "video-analysis:gemini-3.1-pro", credits: 11, note: "10-min ceiling (no duration given)" },
@@ -37,6 +37,7 @@ export const IMAGE_PROMPT_MAX = 5000
37
37
  export const MAX_IMAGE_PROMPT_CHARS_BY_PROVIDER: Record<string, number> = {
38
38
  // ── higher than the 5000 default ──
39
39
  "nano-banana-2": 20000, // docs.kie.ai/market/google/nano-banana-2
40
+ "nano-banana-2-lite": 20000, // docs.kie.ai/market/google/nano-banana-2-lite
40
41
  "nano-banana-pro": 20000, // docs.kie.ai/market/google/pro-image-to-image
41
42
  "gpt-image-2-i2i": 20000, // docs.kie.ai/market/gpt/gpt-image-2-image-to-image
42
43
  // ── lower than the 5000 default (over-send risk if left at default) ──
@@ -48,7 +49,8 @@ export const MAX_IMAGE_PROMPT_CHARS_BY_PROVIDER: Record<string, number> = {
48
49
  "qwen-edit": 2000, // docs.kie.ai/market/qwen/image-edit
49
50
  // verified == 5000 default (no entry needed): imagen4(-fast/-ultra), nano-banana,
50
51
  // nano-banana-edit, flux, flux-flex, gpt-image-2, ideogram-v3/-edit/-remix,
51
- // z-image, grok, qwen-i2i.
52
+ // z-image, grok, qwen-i2i, seedream-5-pro, seedream-5-pro-i2i
53
+ // (docs.kie.ai/market/seedream/5-pro-text-to-image + 5-pro-image-to-image).
52
54
  // grok-i2i: doc states 390000 (78× its t2i sibling) — treated as a KIE schema
53
55
  // typo and left at the 5000 default per the sanity-cap decision.
54
56
  // UNVERIFIED (no limit stated in schema) → 5000 default: flux-kontext(-max)
@@ -93,6 +95,18 @@ export const SUNO_TEXT_MAX = 5000
93
95
  */
94
96
  export const PROMPT_HARD_CEILING = 20000
95
97
 
98
+ /**
99
+ * Ceiling for LLM TEXT-generation node inputs (the "Generate Text"/llm-chat node
100
+ * + AI Writer + Generate Script `systemPrompt`/`userInput`/`prompt` fields).
101
+ * Distinct from PROMPT_HARD_CEILING (an image/video PROMPT budget): these inputs
102
+ * go straight into an LLM whose context is huge (Claude 200K / GPT 128K+ /
103
+ * Gemini 1M tokens), so the old flat 10000 was a false blocker on pasting a
104
+ * document to summarize or rewrite. 100000 chars (~25K input tokens) covers
105
+ * long-form inputs while still bounding abuse; the OUTPUT stays capped by each
106
+ * route's maxTokens.
107
+ */
108
+ export const LLM_TEXT_INPUT_MAX = 100_000
109
+
96
110
  /**
97
111
  * Per-provider maximum VIDEO prompt length (chars), VERIFIED against each model's
98
112
  * official docs.kie.ai schema (2026-06). Absent → {@link VIDEO_PROMPT_MAX} (8000)
@@ -333,6 +347,7 @@ export const MODELS_WITH_REFERENCE_IMAGE_SUPPORT = new Set([
333
347
  "nano-banana",
334
348
  "nano-banana-pro",
335
349
  "nano-banana-2",
350
+ "nano-banana-2-lite",
336
351
  // T2I providers that auto-route to their i2i sibling when refs are attached
337
352
  "gpt-image",
338
353
  "gpt-image-2",
@@ -340,6 +355,7 @@ export const MODELS_WITH_REFERENCE_IMAGE_SUPPORT = new Set([
340
355
  "qwen",
341
356
  "seedream",
342
357
  "seedream-5-lite",
358
+ "seedream-5-pro",
343
359
  "flux",
344
360
  "flux-flex",
345
361
  // Image editing / image-to-image (reference = source image)
@@ -357,6 +373,7 @@ export const MODELS_WITH_REFERENCE_IMAGE_SUPPORT = new Set([
357
373
  "qwen-edit",
358
374
  "seedream-edit",
359
375
  "seedream-5-lite-i2i",
376
+ "seedream-5-pro-i2i",
360
377
  "grok-i2i",
361
378
  // Upscale / background ops (source acts as the reference)
362
379
  "recraft-remove-bg",
@@ -386,6 +403,7 @@ export const T2I_TO_I2I_VARIANT: Record<string, string> = {
386
403
  "qwen": "qwen-i2i",
387
404
  "seedream": "seedream-edit",
388
405
  "seedream-5-lite": "seedream-5-lite-i2i",
406
+ "seedream-5-pro": "seedream-5-pro-i2i",
389
407
  "flux": "flux-pro-i2i",
390
408
  "flux-flex": "flux-i2i",
391
409
  }
@@ -405,6 +423,7 @@ export const REF_IMAGE_MAX_LIMITS: Record<string, number> = {
405
423
  "nano-banana": 8,
406
424
  "nano-banana-pro": 8,
407
425
  "nano-banana-2": 4,
426
+ "nano-banana-2-lite": 10,
408
427
  "wan-2.7": 9,
409
428
  // Image-to-image (multi-source array)
410
429
  "nano-banana-edit": 8,
@@ -414,6 +433,7 @@ export const REF_IMAGE_MAX_LIMITS: Record<string, number> = {
414
433
  "flux-pro-i2i": 4,
415
434
  "seedream-edit": 16,
416
435
  "seedream-5-lite-i2i": 16,
436
+ "seedream-5-pro-i2i": 16,
417
437
  // Single-source i2i (one input image)
418
438
  "flux-kontext": 1,
419
439
  "flux-kontext-max": 1,
@@ -484,6 +504,8 @@ export const VARIABLE_PRICING_MODELS: Record<string, "quality" | "resolution" |
484
504
  "seedream-edit": "quality",
485
505
  "seedream-5-lite": "quality",
486
506
  "seedream-5-lite-i2i": "quality",
507
+ "seedream-5-pro": "quality",
508
+ "seedream-5-pro-i2i": "quality",
487
509
  "topaz-image-upscale": "resolution",
488
510
  "ideogram-edit": "rendering-speed",
489
511
  "ideogram-remix": "rendering-speed",
@@ -495,7 +517,7 @@ export const VARIABLE_PRICING_MODELS: Record<string, "quality" | "resolution" |
495
517
 
496
518
 
497
519
  // Models where quality=high triggers composite credit identifier
498
- export const HIGH_QUALITY_PROVIDERS = new Set(["gpt-image", "gpt-image-i2i", "seedream", "seedream-edit", "seedream-5-lite", "seedream-5-lite-i2i"])
520
+ export const HIGH_QUALITY_PROVIDERS = new Set(["gpt-image", "gpt-image-i2i", "seedream", "seedream-edit", "seedream-5-lite", "seedream-5-lite-i2i", "seedream-5-pro", "seedream-5-pro-i2i"])
499
521
 
500
522
  // Models where resolution=2K triggers composite credit identifier
501
523
  export const TWO_K_RESOLUTION_PROVIDERS = new Set(["flux", "flux-pro-i2i", "flux-flex", "flux-i2i"])
@@ -522,6 +544,7 @@ export const IMAGE_GEN_PROVIDERS = [
522
544
  "flux",
523
545
  "nano-banana-pro",
524
546
  "nano-banana-2",
547
+ "nano-banana-2-lite",
525
548
  "grok",
526
549
  "gpt-image",
527
550
  "gpt-image-2",
@@ -532,6 +555,7 @@ export const IMAGE_GEN_PROVIDERS = [
532
555
  "qwen",
533
556
  "seedream",
534
557
  "seedream-5-lite",
558
+ "seedream-5-pro",
535
559
  "flux-flex",
536
560
  "flux-kontext",
537
561
  "flux-kontext-max",
@@ -548,6 +572,7 @@ export const IMAGE_GEN_PROVIDERS = [
548
572
  export const IMAGE_I2I_PROVIDERS = [
549
573
  "nano-banana",
550
574
  "nano-banana-2",
575
+ "nano-banana-2-lite",
551
576
  "nano-banana-pro",
552
577
  "grok-i2i",
553
578
  "flux-i2i",
@@ -561,6 +586,7 @@ export const IMAGE_I2I_PROVIDERS = [
561
586
  "qwen-edit",
562
587
  "seedream-edit",
563
588
  "seedream-5-lite-i2i",
589
+ "seedream-5-pro-i2i",
564
590
  "flux-kontext",
565
591
  "flux-kontext-max",
566
592
  // Replicate Open (uncensored) — multi-image Kontext via Replicate
@@ -1003,10 +1029,12 @@ export const IMAGE_MASK_MODE: Record<ImageGenProvider, ImageMaskMode> = {
1003
1029
  "nano-banana": "prompt",
1004
1030
  "nano-banana-pro": "prompt",
1005
1031
  "nano-banana-2": "prompt",
1032
+ "nano-banana-2-lite": "prompt",
1006
1033
  "gpt-image": "prompt",
1007
1034
  "gpt-image-2": "prompt",
1008
1035
  "seedream": "prompt",
1009
1036
  "seedream-5-lite": "prompt",
1037
+ "seedream-5-pro": "prompt",
1010
1038
  "qwen": "prompt",
1011
1039
  "flux-kontext": "prompt",
1012
1040
  "flux-kontext-max": "prompt",
@@ -1129,6 +1157,29 @@ export const SEEDANCE_2_REF_LIMITS = {
1129
1157
  audio: 3,
1130
1158
  } as const
1131
1159
 
1160
+ /**
1161
+ * KIE r2v REFERENCE-VIDEO MINIMUM (seconds) for the Seedance 2.0 family — the
1162
+ * provider hard-rejects shorter reference clips with a 400 BEFORE generation:
1163
+ * "the parameter video duration (seconds) specified in the request must be
1164
+ * greater than or equal to 1.8 for model dreamina-seedance-2-0-fast in r2v"
1165
+ * (2026-07-13, job dbf95612 — every 1.0s continuation tail failed
1166
+ * deterministically). Mirror of the reference-AUDIO maximum below.
1167
+ */
1168
+ export const SEEDANCE_2_R2V_MIN_REF_VIDEO_SEC = 1.8
1169
+
1170
+ /**
1171
+ * The continuation-reference length (seconds) every Seedance-2 chaining
1172
+ * feature actually cuts — extend-node tails, generate-video-pro segment
1173
+ * tails, edit-video-pro refOut/refIn brackets — AND the length the gvp/evp
1174
+ * credit formulas bill per continuation join. Clears the provider floor
1175
+ * above with margin while staying short enough to keep the model focused on
1176
+ * continuing the boundary motion instead of re-staging the whole clip.
1177
+ * Guarded ≥ floor by model-constants tests; the private-plugin twin
1178
+ * (nodaro-cloud-plugins chain.ts TAIL_SEC / bridge-math.ts MIN_REF) is
1179
+ * guarded by that repo's r2v-ref-floor.test.ts — keep the two in sync.
1180
+ */
1181
+ export const SEEDANCE_2_CONTINUATION_REF_SEC = 2
1182
+
1132
1183
  /**
1133
1184
  * Trim-stitch parameters for the seedance-2-extend provider (spike-validated
1134
1185
  * 2026-06-11):
@@ -1147,10 +1198,11 @@ export const SEEDANCE_2_EXTEND_STITCH = {
1147
1198
  trimHeadFrames: 3,
1148
1199
  /** Boundary audio fade length (seconds), timeline-preserving. */
1149
1200
  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,
1201
+ /** Seconds of the source's TAIL passed as the @video_1 reference — short
1202
+ * keeps the model focused on continuing the boundary motion; the value is
1203
+ * floored by SEEDANCE_2_R2V_MIN_REF_VIDEO_SEC (the original spike's 1s
1204
+ * tail is now provider-rejected). */
1205
+ referenceTailSeconds: SEEDANCE_2_CONTINUATION_REF_SEC,
1154
1206
  } as const
1155
1207
 
1156
1208
  /**
@@ -174,6 +174,8 @@ const QUALITY_MAP: Record<string, QualityMapping> = {
174
174
  "seedream-edit": { field: "quality", values: { low: "basic", mid: "basic", high: "high" } },
175
175
  "seedream-5-lite": { field: "quality", values: { low: "basic", mid: "basic", high: "high" } },
176
176
  "seedream-5-lite-i2i": { field: "quality", values: { low: "basic", mid: "basic", high: "high" } },
177
+ "seedream-5-pro": { field: "quality", values: { low: "basic", mid: "basic", high: "high" } },
178
+ "seedream-5-pro-i2i": { field: "quality", values: { low: "basic", mid: "basic", high: "high" } },
177
179
  // Video gen — resolution-style (720p/1080p)
178
180
  "veo3": { field: "resolution", values: { low: "720p", mid: "1080p", high: "1080p" } },
179
181
  "veo3.1": { field: "resolution", values: { low: "720p", mid: "1080p", high: "1080p" } },
@@ -248,6 +250,7 @@ const GENERATE_IMAGE_PROVIDER_TO_MODEL: Record<string, string> = {
248
250
  "nano-banana": "gemini-2.5-flash-image",
249
251
  "nano-banana-pro": "gemini-2.5-flash-image",
250
252
  "nano-banana-2": "gemini-2.5-flash-image",
253
+ "nano-banana-2-lite": "gemini-3.1-flash-lite-image",
251
254
  }
252
255
 
253
256
  export function deriveLinkedFields(