@nodaro/shared 2.23.0 → 2.24.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": "2.23.0",
3
+ "version": "2.24.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",
@@ -35,6 +35,7 @@ const EXPECTED_MODEL_IDS = [
35
35
  "gemini-3-flash",
36
36
  "gemini-3.6-flash",
37
37
  "gemini-3.7-flash",
38
+ "gemini-3.8-flash",
38
39
  "claude-haiku-4.5",
39
40
  "claude-sonnet-4.6",
40
41
  "gpt-5.2",
@@ -45,6 +46,7 @@ const EXPECTED_MODEL_IDS = [
45
46
  "gpt-5.6-luna",
46
47
  "gpt-5.6-terra",
47
48
  "gpt-5.6-sol",
49
+ "gpt-6-astra",
48
50
  "grok-4.6",
49
51
  "claude-sonnet-5",
50
52
  "claude-opus-4.8",
@@ -94,14 +96,14 @@ describe("LLM_MODELS data integrity", () => {
94
96
  expect(new Set(ids).size).toBe(ids.length)
95
97
  })
96
98
 
97
- it("has 5 economy, 5 standard, 8 premium models", () => {
99
+ it("has 6 economy, 5 standard, 9 premium models", () => {
98
100
  const tierCounts: Record<LlmTier, number> = { economy: 0, standard: 0, premium: 0 }
99
101
  for (const model of LLM_MODELS) {
100
102
  tierCounts[model.tier]++
101
103
  }
102
- expect(tierCounts.economy).toBe(5)
104
+ expect(tierCounts.economy).toBe(6)
103
105
  expect(tierCounts.standard).toBe(5)
104
- expect(tierCounts.premium).toBe(8)
106
+ expect(tierCounts.premium).toBe(9)
105
107
  })
106
108
 
107
109
  it("all three kieFormats are represented", () => {
@@ -483,6 +485,7 @@ describe("STRUCTURED_VISION_MODELS", () => {
483
485
  "gemini-3-flash",
484
486
  "gemini-3.6-flash",
485
487
  "gemini-3.7-flash",
488
+ "gemini-3.8-flash",
486
489
  "gemini-3.1-pro",
487
490
  "claude-sonnet-5",
488
491
  "claude-opus-4.8",
@@ -495,6 +498,9 @@ describe("STRUCTURED_VISION_MODELS", () => {
495
498
  "gpt-5.6-luna",
496
499
  "gpt-5.6-terra",
497
500
  "gpt-5.6-sol",
501
+ // responses-format GPT-6 — text.format json_schema enforced (server
502
+ // echoed strict:true), live-verified 2026-09-06.
503
+ "gpt-6-astra",
498
504
  // responses-format Grok — vision + text.format live-verified 2026-08-18.
499
505
  "grok-4.6",
500
506
  ].sort(),
@@ -540,6 +546,8 @@ describe("reasoning effort registry", () => {
540
546
  expect(getLlmTier("gpt-5.6-luna")).toBe("economy")
541
547
  expect(getLlmTier("gpt-5.6-terra")).toBe("standard")
542
548
  expect(getLlmTier("gpt-5.6-sol")).toBe("premium")
549
+ expect(getLlmTier("gpt-6-astra")).toBe("premium")
550
+ expect(getLlmTier("gemini-3.8-flash")).toBe("economy")
543
551
  expect(getLlmTier("grok-4.6")).toBe("standard")
544
552
  expect(getLlmTier("claude-sonnet-5")).toBe("standard")
545
553
  expect(getLlmTier("claude-opus-4.8")).toBe("premium")
@@ -630,6 +638,117 @@ describe("gemini-3.7-flash exposure", () => {
630
638
  })
631
639
  })
632
640
 
641
+ // ---------------------------------------------------------------------------
642
+ // gemini-3.8-flash exposure (KIE lane live-verified 2026-09-06: enforced
643
+ // response_format json_schema, reasoning_tokens in usage, and a 20k max_tokens
644
+ // request honored for 14,892 completion tokens with finish_reason "stop")
645
+ // ---------------------------------------------------------------------------
646
+ describe("gemini-3.8-flash exposure", () => {
647
+ it("KIE-first chat-completions with a direct lane — 3.7-flash's proven shape", () => {
648
+ const m = getLlmModel("gemini-3.8-flash")
649
+ expect(m?.tier).toBe("economy")
650
+ expect(m?.vendor).toBe("google")
651
+ expect(m?.kieFormat).toBe("chat-completions")
652
+ expect(m?.kieSlugOrModel).toBe("gemini-3-8-flash-openai")
653
+ // KIE-first on purpose (no preferDirect): the cheap lane serves the A/B,
654
+ // the direct lane is Advanced mode + the reliability fallback.
655
+ expect(m?.preferDirect).toBeUndefined()
656
+ expect(m?.directGeminiModel).toBe("gemini-3.8-flash")
657
+ expect(m?.reasoningEfforts).toEqual(["low", "high"])
658
+ expect(m?.structuredOutputMode).toBe("kie-response-format")
659
+ expect(supportsAdvancedMode("gemini-3.8-flash")).toBe(true)
660
+ expect(availableReasoningEfforts("gemini-3.8-flash")).toEqual(["low", "high"])
661
+ expect(availableReasoningEfforts("gemini-3.8-flash", true)).toEqual(["none", "low", "medium", "high"])
662
+ })
663
+
664
+ it("carries a 16384 output cap — measured, not inherited from its 8192 siblings", () => {
665
+ // 3.6 / 3.7 sit at 8192 as the KIE-safe intersection because nobody
666
+ // measured their endpoints past it. 3.8's WAS measured (2026-09-06:
667
+ // max_tokens 20000 honored, 14,892 completion tokens, finish_reason
668
+ // "stop"), so it is not pinned to its siblings' unmeasured floor.
669
+ expect(getLlmModel("gemini-3.8-flash")?.maxOutputTokens).toBe(16384)
670
+ })
671
+
672
+ it("resolves by canonical id and by its KIE slug (cost reconciliation reads whatever the wire used)", () => {
673
+ expect(getLlmModel("gemini-3.8-flash")?.id).toBe("gemini-3.8-flash")
674
+ expect(getLlmModel("gemini-3-8-flash-openai")?.id).toBe("gemini-3.8-flash")
675
+ })
676
+
677
+ it("stays OUT of video-analysis — image-only modality caps by decision, not omission", () => {
678
+ // Google's own 3.8 lane understands video (agentic video via the
679
+ // Interactions API), so full caps here would auto-enroll it in
680
+ // VIDEO_ANALYSIS_LLM_MODELS and force a VA tier + pricing decision that is
681
+ // deliberately deferred until the 3.8-vs-3.7 analysis A/B concludes.
682
+ // If this goes red, someone flipped the caps — only valid TOGETHER with
683
+ // that VA-side decision.
684
+ expect(getLlmModalityCaps("gemini-3.8-flash")).toEqual({ image: true, video: false, audio: false })
685
+ expect(VIDEO_ANALYSIS_LLM_MODELS).not.toContain("gemini-3.8-flash")
686
+ })
687
+ })
688
+
689
+ // ---------------------------------------------------------------------------
690
+ // gpt-6-astra exposure (KIE responses lane live-verified 2026-09-06 on the
691
+ // streaming path: enforced text.format json_schema with strict:true, the
692
+ // documented effort enum, temperature silently ignored, reasoning on by default)
693
+ // ---------------------------------------------------------------------------
694
+ describe("gpt-6-astra exposure", () => {
695
+ it("responses-format OpenAI premium — the GPT-5.6 family's proven shape", () => {
696
+ const m = getLlmModel("gpt-6-astra")
697
+ expect(m?.tier).toBe("premium")
698
+ expect(m?.vendor).toBe("openai")
699
+ expect(m?.kieFormat).toBe("responses")
700
+ expect(m?.structuredOutputMode).toBe("responses-json-schema")
701
+ expect(m?.maxOutputTokens).toBe(16384)
702
+ expect(m?.reasoningEfforts).toEqual(["low", "medium", "high", "xhigh"])
703
+ // Temperature is accepted-and-ignored by this endpoint (echo stayed at 1.0
704
+ // for a sent 0.2), so the registry must say "don't send it".
705
+ expect(m?.supportsTemperature).toBe(false)
706
+ // Reasons with NO reasoning param sent (server echoed effort "medium") —
707
+ // consumers floor max_tokens off this flag or the answer truncates.
708
+ expect(m?.thinkingDefaultOn).toBe(true)
709
+ // No direct lane: OpenAI models route through KIE only, so Advanced mode
710
+ // (a Gemini-lane capability) must stay off.
711
+ expect(supportsAdvancedMode("gpt-6-astra")).toBe(false)
712
+ })
713
+
714
+ it("resolves from its own slug — the slug is the body `model`, the path comes from vendor", () => {
715
+ // llm-client derives codex/v1/responses from vendor "openai" (the same
716
+ // derivation that keeps Grok on grok/v1/responses), so kieSlugOrModel is
717
+ // sent as the request body's `model` and doubles as a lookup alias.
718
+ const m = getLlmModel("gpt-6-astra")
719
+ expect(m?.kieSlugOrModel).toBe("gpt-6-astra")
720
+ expect(getLlmModel(m!.kieSlugOrModel)?.id).toBe("gpt-6-astra")
721
+ })
722
+
723
+ it("is an accepted structured-vision analyzer", () => {
724
+ expect(STRUCTURED_VISION_MODELS.map((m) => m.id)).toContain("gpt-6-astra")
725
+ expect(STRUCTURED_VISION_MODELS.map((m) => m.id)).toContain("gemini-3.8-flash")
726
+ })
727
+
728
+ it("is image-only — KIE's GPT-6 doc lists text + image + file inputs, no video/audio", () => {
729
+ expect(getLlmModalityCaps("gpt-6-astra")).toEqual({ image: true, video: false, audio: false })
730
+ expect(VIDEO_ANALYSIS_LLM_MODELS).not.toContain("gpt-6-astra")
731
+ })
732
+
733
+ it("bills premium, and xhigh cannot bump past it", () => {
734
+ expect(buildLlmCreditIdentifier("llm-chat", "gpt-6-astra")).toBe("llm-chat:premium")
735
+ expect(buildLlmCreditIdentifier("llm-chat", "gpt-6-astra", "xhigh")).toBe("llm-chat:premium")
736
+ })
737
+
738
+ it("is the ONLY model declaring kieCollapseStream — the condition is per model, not per lane", () => {
739
+ // KIE's non-stream responses endpoint 500s ~2 calls in 3 for astra
740
+ // (measured 2026-09-06: 2/6 non-stream vs 5/6 streaming) while serving
741
+ // gpt-5.4/5.5/5.6 fine, so llm-client collapses the streaming wire for
742
+ // THIS model only. If a second model ever legitimately needs it, add it
743
+ // here deliberately — a flag that spreads by copy-paste would quietly move
744
+ // every affected model's provider cost onto the rate-table estimate,
745
+ // because SSE does not reliably carry `credits_consumed`.
746
+ expect(getLlmModel("gpt-6-astra")?.kieCollapseStream).toBe(true)
747
+ const others = LLM_MODELS.filter((m) => m.id !== "gpt-6-astra")
748
+ expect(others.filter((m) => m.kieCollapseStream !== undefined).map((m) => m.id)).toEqual([])
749
+ })
750
+ })
751
+
633
752
  describe("effectiveReasoningEffort", () => {
634
753
  it("passes through a supported level", () => {
635
754
  expect(effectiveReasoningEffort("claude-sonnet-5", "max")).toBe("max")
@@ -164,7 +164,7 @@ describe("bare video-analysis node-type credit id", () => {
164
164
  // that prompt again, moving the pin 8_482 -> 8_706, and 357 wrote 2076:
165
165
  // the transition vocabulary v2 doctrine (twelve named values; absent =
166
166
  // nothing asserted) grew it once more, moving the pin 8_706 -> 9_082.
167
- expect(ceiling).toBe(2076)
167
+ expect(ceiling).toBe(2081)
168
168
  })
169
169
 
170
170
  it("the bare id still bounds the default tier at the ceiling bucket", () => {
@@ -180,10 +180,10 @@ describe("video-audit-pricing", () => {
180
180
  it("videoAuditCreditsForBucket: table lookup by resolved bucket + family, snapping a raw duration onto the ladder", () => {
181
181
  expect(videoAuditCreditsForBucket(60, false)).toBe(215)
182
182
  expect(videoAuditCreditsForBucket(60, true)).toBe(396)
183
- expect(videoAuditCreditsForBucket(180, false)).toBe(290)
184
- expect(videoAuditCreditsForBucket(360, false)).toBe(663)
185
- expect(videoAuditCreditsForBucket(600, false)).toBe(1073)
186
- expect(videoAuditCreditsForBucket(600, true)).toBe(1921)
183
+ expect(videoAuditCreditsForBucket(180, false)).toBe(291)
184
+ expect(videoAuditCreditsForBucket(360, false)).toBe(664)
185
+ expect(videoAuditCreditsForBucket(600, false)).toBe(1075)
186
+ expect(videoAuditCreditsForBucket(600, true)).toBe(1924)
187
187
  // Not just exact ladder values — a raw duration snaps up to its bucket.
188
188
  expect(videoAuditCreditsForBucket(70, false)).toBe(VIDEO_AUDIT_BUCKET_CREDITS["video-audit:180s"])
189
189
  expect(videoAuditCreditsForBucket(9999, true)).toBe(VIDEO_AUDIT_BUCKET_CREDITS["video-audit:auto:600s"])
@@ -224,8 +224,8 @@ describe("video-audit-pricing", () => {
224
224
  // 600s bucket here, same convention as VIDEO_ANALYSIS_BUCKET_CREDITS).
225
225
  expect(Object.keys(VIDEO_AUDIT_BUCKET_CREDITS)).toHaveLength(8)
226
226
  // Bare-id values quoted in the task/catalog must equal each family's 600s ceiling.
227
- expect(VIDEO_AUDIT_BUCKET_CREDITS["video-audit:600s"]).toBe(1073)
228
- expect(VIDEO_AUDIT_BUCKET_CREDITS["video-audit:auto:600s"]).toBe(1921)
227
+ expect(VIDEO_AUDIT_BUCKET_CREDITS["video-audit:600s"]).toBe(1075)
228
+ expect(VIDEO_AUDIT_BUCKET_CREDITS["video-audit:auto:600s"]).toBe(1924)
229
229
  })
230
230
 
231
231
  it("auto family = base family + the gemini-3-flash (legacy fast tier) row at the same bucket, exactly — single-source, never hand-added", () => {
package/src/index.ts CHANGED
@@ -1051,3 +1051,12 @@ export {
1051
1051
  } from "./entity-node-fields.js"
1052
1052
  export { ENTITY_NODE_KINDS } from "./entity-node-fields.js"
1053
1053
  export type { EntityNodeKind } from "./entity-node-fields.js"
1054
+
1055
+ // --- wire contract of /v1/studio/productions — types only ---
1056
+ // The studio production CODEC (the reader/writer of `settings.studio`, the
1057
+ // plan format, the catalogs and the reducers) lives in the FSL-licensed
1058
+ // `@nodaro/studio-production`. What crosses into this Apache package is the
1059
+ // envelope those routes return and the bodies they take, so the SDK can be
1060
+ // typed against it — the document's own sub-objects stay named JSON here and
1061
+ // are narrowed, and pinned, by the package that owns them.
1062
+ export type * from "./studio-production-wire.js"
package/src/llm-models.ts CHANGED
@@ -94,6 +94,22 @@ export interface LlmModelDef {
94
94
  supportsTemperature?: false
95
95
  /** Claude-only: KIE is the preferred routing, direct Anthropic the fallback. */
96
96
  preferKie?: true
97
+ /**
98
+ * KIE's NON-streaming endpoint for this model is unreliable (measured);
99
+ * `llmComplete` serves it by opening the streaming wire and collapsing it to
100
+ * one response. Streaming responses do not reliably carry
101
+ * `credits_consumed`, so provider cost on this path comes from the rate
102
+ * table (`backend/src/lib/pricing/llm-cost.ts`) rather than the real charge.
103
+ *
104
+ * Declared per model — a per-model condition on KIE's side, like the
105
+ * Claude-lane one behind `callKieMessagesCollapsed`. The flag says WHICH
106
+ * model is affected, and `llm-client` reads it instead of matching model
107
+ * names. Today only the `responses` dispatcher in `callKie` honours it (the
108
+ * one format where the condition has been measured); a chat-completions or
109
+ * messages model that needs the same treatment must also teach its `callKie`
110
+ * case to read the flag — declaring it alone changes nothing there.
111
+ */
112
+ kieCollapseStream?: true
97
113
  /**
98
114
  * true = the model reasons even when NO thinking parameter is sent, so its
99
115
  * reasoning tokens share the `max_tokens` budget on EVERY call — not just
@@ -133,7 +149,9 @@ export const LLM_MODELS: readonly LlmModelDef[] = [
133
149
  {
134
150
  id: "gemini-3.6-flash",
135
151
  displayName: "Gemini 3.6 Flash",
136
- desc: "Latest fast Gemini, sharper reasoning",
152
+ // Demoted 2026-09-06 alongside 3.7: superlatives belong to the CURRENT top
153
+ // model of a family only, and 3.8 now holds that slot.
154
+ desc: "Fast Gemini, sharper reasoning",
137
155
  tier: "economy",
138
156
  kieFormat: "chat-completions",
139
157
  // KIE serves Gemini 3.6 Flash on the OpenAI-compatible dialect under this
@@ -163,7 +181,11 @@ export const LLM_MODELS: readonly LlmModelDef[] = [
163
181
  {
164
182
  id: "gemini-3.7-flash",
165
183
  displayName: "Gemini 3.7 Flash",
166
- desc: "Newest fast Gemini, agentic-tuned",
184
+ // Demoted 2026-09-06 when 3.8 registered below: superlatives belong to the
185
+ // CURRENT top model of a family only (same rule the Opus entries follow) —
186
+ // `desc` renders in every picker, so leaving "Newest" on the older flash
187
+ // steers the A/B's traffic backwards.
188
+ desc: "Fast Gemini, agentic-tuned",
167
189
  tier: "economy",
168
190
  kieFormat: "chat-completions",
169
191
  // KIE serves it on the OpenAI-compatible dialect under this slug
@@ -184,6 +206,46 @@ export const LLM_MODELS: readonly LlmModelDef[] = [
184
206
  directReasoningEfforts: ["none", "low", "medium", "high"],
185
207
  directGeminiModel: "gemini-3.7-flash",
186
208
  },
209
+ {
210
+ id: "gemini-3.8-flash",
211
+ displayName: "Gemini 3.8 Flash",
212
+ // Inherits "Newest" from 3.7 (demoted above). Deliberately does NOT name
213
+ // agentic video: that capability exists only on the direct Google lane
214
+ // (Interactions API, not wired here) and the modality caps below withhold
215
+ // video — a picker desc must not promise a lever this entry cannot expose.
216
+ desc: "Newest fast Gemini, agentic-tuned",
217
+ tier: "economy",
218
+ kieFormat: "chat-completions",
219
+ // KIE serves it on the OpenAI-compatible dialect under this slug
220
+ // (docs.kie.ai/market/gemini/gemini-3-8-flash-openai.md) — identical
221
+ // chat-completions path shape to gemini-3.7-flash / gemini-3.6-flash,
222
+ // different slug prefix.
223
+ kieSlugOrModel: "gemini-3-8-flash-openai",
224
+ vendor: "google",
225
+ // Live-verified 2026-09-06 on the KIE lane: `response_format: json_schema`
226
+ // is ENFORCED (the reply came back exact-schema valid, credits_consumed
227
+ // present) — not merely accepted-and-ignored.
228
+ structuredOutputMode: "kie-response-format",
229
+ supportsImages: true,
230
+ // 16384 here, NOT the 8192 its 3.6 / 3.7 siblings sit at. Those two stay at
231
+ // the KIE-safe intersection because nobody measured their endpoints past
232
+ // it; 3.8's WAS measured — live-verified 2026-09-06, `max_tokens: 20000`
233
+ // was honored for 14,892 completion tokens with finish_reason "stop" (no
234
+ // truncation), so the KIE lane is not the binding constraint. 16384 keeps
235
+ // it level with the rest of the registry's ceiling rather than at the cap.
236
+ maxOutputTokens: 16384,
237
+ // KIE's 3.8 endpoint enumerates reasoning_effort low | high (its doc enum),
238
+ // and usage carries completion_tokens_details.reasoning_tokens — same
239
+ // KIE-safe intersection as 3.6 / 3.7, because ONE field feeds both lanes
240
+ // and this model is KIE-first.
241
+ reasoningEfforts: ["low", "high"],
242
+ // Google's own API accepts the full minimal→high ladder here (`none` maps
243
+ // to `minimal`), same as 3.7. Advanced mode is what unlocks it.
244
+ directReasoningEfforts: ["none", "low", "medium", "high"],
245
+ // KIE-first (no `preferDirect`) — 3.7's posture exactly: the cheap lane
246
+ // serves the A/B, direct is Advanced mode + the reliability fallback.
247
+ directGeminiModel: "gemini-3.8-flash",
248
+ },
187
249
  {
188
250
  id: "claude-haiku-4.5",
189
251
  displayName: "Claude Haiku 4.5",
@@ -280,7 +342,9 @@ export const LLM_MODELS: readonly LlmModelDef[] = [
280
342
  {
281
343
  id: "gpt-5.4",
282
344
  displayName: "GPT-5.4",
283
- desc: "Latest GPT, premium quality",
345
+ // Demoted 2026-09-06: two GPT generations have shipped above it, so
346
+ // "Latest" steered the picker at the oldest premium GPT in the registry.
347
+ desc: "Older GPT, premium quality",
284
348
  tier: "premium",
285
349
  kieFormat: "responses",
286
350
  kieSlugOrModel: "gpt-5-4",
@@ -335,7 +399,11 @@ export const LLM_MODELS: readonly LlmModelDef[] = [
335
399
  {
336
400
  id: "gpt-5.6-sol",
337
401
  displayName: "GPT-5.6 Sol",
338
- desc: "Flagship GPT-5.6, deepest reasoning",
402
+ // Demoted 2026-09-06 when gpt-6-astra registered below — "Flagship /
403
+ // deepest" is the current top GPT's copy only. Worded to stay distinct
404
+ // from gpt-5.5's "Previous flagship GPT, deep reasoning": all three read
405
+ // premium on the invoice, so a stale superlative mis-steers silently.
406
+ desc: "Previous flagship GPT-5.6, deep reasoning",
339
407
  tier: "premium",
340
408
  kieFormat: "responses",
341
409
  kieSlugOrModel: "gpt-5-6-sol",
@@ -346,6 +414,61 @@ export const LLM_MODELS: readonly LlmModelDef[] = [
346
414
  reasoningEfforts: ["none", "low", "medium", "high", "xhigh", "max"],
347
415
  supportsTemperature: false,
348
416
  },
417
+ {
418
+ id: "gpt-6-astra",
419
+ displayName: "GPT-6 Astra",
420
+ desc: "Flagship GPT-6, deepest reasoning",
421
+ tier: "premium",
422
+ kieFormat: "responses",
423
+ // KIE serves GPT-6 on the responses dialect under the OpenAI family path —
424
+ // codex/v1/responses, which llm-client DERIVES from `vendor` (the same
425
+ // derivation that keeps Grok on grok/v1/responses). So this field is the
426
+ // body `model` only, not a path segment. Live-verified 2026-09-06 on the
427
+ // streaming path (array `input`, SSE deltas), which is the ONLY path we
428
+ // serve it on — see `kieCollapseStream` below. The non-stream reply does
429
+ // carry `credits_consumed` (and cached_tokens/cache_write_tokens in usage)
430
+ // on the ~1 call in 3 that returns one, but we no longer take that reply,
431
+ // so provider cost here is the rate-table estimate.
432
+ // KIE's codex/v1/responses injects its own Codex-agent `instructions`
433
+ // prompt ABOVE the caller's `developer` message — observed in the
434
+ // 2026-07-14 live probe of that endpoint, and unchanged for GPT-6. Budget
435
+ // for it when a developer message has to dominate.
436
+ kieSlugOrModel: "gpt-6-astra",
437
+ vendor: "openai",
438
+ // Live-verified 2026-09-06: `text.format: json_schema` is passed through
439
+ // and ENFORCED — the server echoed `strict: true` and returned
440
+ // schema-valid JSON.
441
+ structuredOutputMode: "responses-json-schema",
442
+ // text + image + file inputs per KIE's doc. No video/audio — see the
443
+ // image-only LLM_MODALITY_CAPS row below.
444
+ supportsImages: true,
445
+ maxOutputTokens: 16384,
446
+ // KIE's documented enum for this endpoint. No `none` on purpose: the
447
+ // endpoint reasons unconditionally (see thinkingDefaultOn below), so a
448
+ // "none" level would be a lie the wire silently overrides.
449
+ reasoningEfforts: ["low", "medium", "high", "xhigh"],
450
+ // Live-probed 2026-09-06: `temperature: 0.2` was sent and the request echo
451
+ // stayed at 1.0 — silently IGNORED, so never send it. Same treatment as the
452
+ // GPT-5.6 family and grok-4.6.
453
+ supportsTemperature: false,
454
+ // With NO reasoning param sent, the server echoed effort "medium" — it
455
+ // reasons by default (2026-09-06), so reasoning tokens share `max_tokens`
456
+ // on EVERY call, not just effort-bearing ones. Consumers must floor output
457
+ // headroom off this flag or a premium answer truncates into a paid-for
458
+ // empty reply.
459
+ thinkingDefaultOn: true,
460
+ // Measured 2026-09-06, 12 identical requests (developer + user message,
461
+ // reasoning.effort low, text.format json_schema): `stream: false`
462
+ // succeeded 2/6 in 4–5 s and 500'd 4/6 with
463
+ // {"error":{"type":"server_error"}} after 34, 34, 35 and 64 s, while the
464
+ // same body with `stream: true` succeeded 5/6 in the same 4–5 s. An
465
+ // earlier non-stream probe timed out at 90 s with 0 bytes, and a
466
+ // non-stream call WITHOUT a schema 500'd after 65 s — the schema is not
467
+ // the trigger, the non-stream lane is. The very same endpoint serves
468
+ // gpt-5.4/5.5/5.6 non-stream reliably (live-verified 2026-07-14), which is
469
+ // why this is per-model and not a lane-wide flag.
470
+ kieCollapseStream: true,
471
+ },
349
472
  {
350
473
  id: "grok-4.6",
351
474
  displayName: "Grok 4.6",
@@ -577,6 +700,15 @@ export const LLM_MODALITY_CAPS: Record<string, { image: boolean; video: boolean;
577
700
  // deferred while the smart-family A/B routes this model internally (#747).
578
701
  // Flip these two flags ONLY together with that VA-side decision.
579
702
  "gemini-3.7-flash": { image: true, video: false, audio: false },
703
+ // gemini-3.8-flash is IMAGE-ONLY by DECISION, not omission — 3.7's rationale
704
+ // exactly: full video+audio caps would auto-enroll it in
705
+ // VIDEO_ANALYSIS_LLM_MODELS (derived below) and force a video-analysis tier +
706
+ // pricing decision that stays deferred until the 3.8-vs-3.7 analysis A/B
707
+ // concludes. Google's own 3.8 lane DOES understand video (agentic video, via
708
+ // the Interactions API we don't wire) — which is precisely why withholding it
709
+ // has to be a decision rather than a gap. Flip these two flags ONLY together
710
+ // with that VA-side decision.
711
+ "gemini-3.8-flash": { image: true, video: false, audio: false },
580
712
  "gemini-3.1-pro": { image: true, video: true, audio: true },
581
713
  "claude-haiku-4.5": { image: true, video: false, audio: false },
582
714
  "claude-sonnet-4.6": { image: true, video: false, audio: false },
@@ -587,6 +719,8 @@ export const LLM_MODALITY_CAPS: Record<string, { image: boolean; video: boolean;
587
719
  "gpt-5.6-luna": { image: true, video: false, audio: false },
588
720
  "gpt-5.6-terra": { image: true, video: false, audio: false },
589
721
  "gpt-5.6-sol": { image: true, video: false, audio: false },
722
+ // KIE's GPT-6 doc lists text + image + file inputs only — no video, no audio.
723
+ "gpt-6-astra": { image: true, video: false, audio: false },
590
724
  "grok-4.6": { image: true, video: false, audio: false },
591
725
  "claude-sonnet-5": { image: true, video: false, audio: false },
592
726
  "claude-opus-4.8": { image: true, video: false, audio: false },
@@ -2176,11 +2176,11 @@ const VIDEO_MODELS: Record<string, ModelCatalogEntry> = {
2176
2176
  description: "Legacy fast-tier analysis model (pre-2026-07). Kept so stored raw-model configs keep running and keep pricing under their own identifier; new fast-tier runs use the current fast model.",
2177
2177
  useCases: ["video-analysis", "shot-list", "fast"],
2178
2178
  pricing: [
2179
- { identifier: "video-analysis:gemini-3-flash", credits: 848, note: "10-min ceiling (no duration given)" },
2179
+ { identifier: "video-analysis:gemini-3-flash", credits: 849, note: "10-min ceiling (no duration given)" },
2180
2180
  { identifier: "video-analysis:gemini-3-flash:60s", credits: 181 },
2181
- { identifier: "video-analysis:gemini-3-flash:180s", credits: 185 },
2182
- { identifier: "video-analysis:gemini-3-flash:360s", credits: 515 },
2183
- { identifier: "video-analysis:gemini-3-flash:600s", credits: 848, note: "10-min ceiling" },
2181
+ { identifier: "video-analysis:gemini-3-flash:180s", credits: 186 },
2182
+ { identifier: "video-analysis:gemini-3-flash:360s", credits: 516 },
2183
+ { identifier: "video-analysis:gemini-3-flash:600s", credits: 849, note: "10-min ceiling" },
2184
2184
  ],
2185
2185
  },
2186
2186
  "gemini-3.6-flash-video-analysis": {
@@ -2193,11 +2193,11 @@ const VIDEO_MODELS: Record<string, ModelCatalogEntry> = {
2193
2193
  description: "Analyze a video into a structured shot list (scenes, camera, audio) — fast, economy tier. Billed per duration bucket.",
2194
2194
  useCases: ["video-analysis", "shot-list", "fast"],
2195
2195
  pricing: [
2196
- { identifier: "video-analysis:gemini-3.6-flash", credits: 992, note: "10-min ceiling (no duration given)" },
2197
- { identifier: "video-analysis:gemini-3.6-flash:60s", credits: 204 },
2196
+ { identifier: "video-analysis:gemini-3.6-flash", credits: 995, note: "10-min ceiling (no duration given)" },
2197
+ { identifier: "video-analysis:gemini-3.6-flash:60s", credits: 205 },
2198
2198
  { identifier: "video-analysis:gemini-3.6-flash:180s", credits: 219 },
2199
- { identifier: "video-analysis:gemini-3.6-flash:360s", credits: 602 },
2200
- { identifier: "video-analysis:gemini-3.6-flash:600s", credits: 992, note: "10-min ceiling" },
2199
+ { identifier: "video-analysis:gemini-3.6-flash:360s", credits: 603 },
2200
+ { identifier: "video-analysis:gemini-3.6-flash:600s", credits: 995, note: "10-min ceiling" },
2201
2201
  ],
2202
2202
  },
2203
2203
  "gemini-3.1-pro-video-analysis": {
@@ -2210,11 +2210,11 @@ const VIDEO_MODELS: Record<string, ModelCatalogEntry> = {
2210
2210
  description: "Analyze a video into a structured shot list (scenes, camera, audio) — higher-fidelity, default tier. Billed per duration bucket.",
2211
2211
  useCases: ["video-analysis", "shot-list", "cinematic"],
2212
2212
  pricing: [
2213
- { identifier: "video-analysis:gemini-3.1-pro", credits: 1056, note: "10-min ceiling (no duration given)" },
2214
- { identifier: "video-analysis:gemini-3.1-pro:60s", credits: 216 },
2215
- { identifier: "video-analysis:gemini-3.1-pro:180s", credits: 232 },
2216
- { identifier: "video-analysis:gemini-3.1-pro:360s", credits: 640 },
2217
- { identifier: "video-analysis:gemini-3.1-pro:600s", credits: 1056, note: "10-min ceiling" },
2213
+ { identifier: "video-analysis:gemini-3.1-pro", credits: 1059, note: "10-min ceiling (no duration given)" },
2214
+ { identifier: "video-analysis:gemini-3.1-pro:60s", credits: 217 },
2215
+ { identifier: "video-analysis:gemini-3.1-pro:180s", credits: 233 },
2216
+ { identifier: "video-analysis:gemini-3.1-pro:360s", credits: 642 },
2217
+ { identifier: "video-analysis:gemini-3.1-pro:600s", credits: 1059, note: "10-min ceiling" },
2218
2218
  ],
2219
2219
  },
2220
2220
  // Both mixed tiers are variants of the same advanced multi-engine analysis
@@ -2230,11 +2230,11 @@ const VIDEO_MODELS: Record<string, ModelCatalogEntry> = {
2230
2230
  description: "Our most advanced analysis tier — multiple analysis engines combined into one result for maximum completeness and accuracy. Billed per duration bucket.",
2231
2231
  useCases: ["video-analysis", "shot-list", "premium", "most-complete"],
2232
2232
  pricing: [
2233
- { identifier: "video-analysis:mixed", credits: 1177, note: "10-min ceiling (no duration given)" },
2233
+ { identifier: "video-analysis:mixed", credits: 1181, note: "10-min ceiling (no duration given)" },
2234
2234
  { identifier: "video-analysis:mixed:60s", credits: 270 },
2235
- { identifier: "video-analysis:mixed:180s", credits: 291 },
2236
- { identifier: "video-analysis:mixed:360s", credits: 729 },
2237
- { identifier: "video-analysis:mixed:600s", credits: 1177, note: "10-min ceiling" },
2235
+ { identifier: "video-analysis:mixed:180s", credits: 292 },
2236
+ { identifier: "video-analysis:mixed:360s", credits: 731 },
2237
+ { identifier: "video-analysis:mixed:600s", credits: 1181, note: "10-min ceiling" },
2238
2238
  ],
2239
2239
  },
2240
2240
  // SMART — the accuracy tier. Since the 2026-08-03 hybrid re-plan it is a
@@ -2252,11 +2252,11 @@ const VIDEO_MODELS: Record<string, ModelCatalogEntry> = {
2252
2252
  description: "Highest-accuracy analysis — a hybrid pass that blends a native skeleton read with several donor analysis rolls, then always refines the merged result to find shot boundaries and identify the cast by appearance. Best choice when the shot list will drive regeneration. Billed per duration bucket.",
2253
2253
  useCases: ["video-analysis", "shot-list", "premium", "most-accurate"],
2254
2254
  pricing: [
2255
- { identifier: "video-analysis:smart", credits: 2076, note: "10-min ceiling (no duration given)" },
2256
- { identifier: "video-analysis:smart:60s", credits: 413 },
2257
- { identifier: "video-analysis:smart:180s", credits: 503 },
2258
- { identifier: "video-analysis:smart:360s", credits: 1267 },
2259
- { identifier: "video-analysis:smart:600s", credits: 2076, note: "10-min ceiling" },
2255
+ { identifier: "video-analysis:smart", credits: 2081, note: "10-min ceiling (no duration given)" },
2256
+ { identifier: "video-analysis:smart:60s", credits: 414 },
2257
+ { identifier: "video-analysis:smart:180s", credits: 504 },
2258
+ { identifier: "video-analysis:smart:360s", credits: 1270 },
2259
+ { identifier: "video-analysis:smart:600s", credits: 2081, note: "10-min ceiling" },
2260
2260
  ],
2261
2261
  },
2262
2262
  // ── AI Audit (`video-audit`) ── a SEPARATE node from video-analysis (own
@@ -2280,11 +2280,11 @@ const VIDEO_MODELS: Record<string, ModelCatalogEntry> = {
2280
2280
  description: "Re-watches a clip against a wired analysis, applies video-verified corrections under guards, and returns a disclosed report of what changed. Billed per duration bucket.",
2281
2281
  useCases: ["video-audit", "qa", "shot-list", "verification"],
2282
2282
  pricing: [
2283
- { identifier: "video-audit", credits: 1073, note: "10-min ceiling (no duration given)" },
2283
+ { identifier: "video-audit", credits: 1075, note: "10-min ceiling (no duration given)" },
2284
2284
  { identifier: "video-audit:60s", credits: 215 },
2285
- { identifier: "video-audit:180s", credits: 290 },
2286
- { identifier: "video-audit:360s", credits: 663 },
2287
- { identifier: "video-audit:600s", credits: 1073, note: "10-min ceiling" },
2285
+ { identifier: "video-audit:180s", credits: 291 },
2286
+ { identifier: "video-audit:360s", credits: 664 },
2287
+ { identifier: "video-audit:600s", credits: 1075, note: "10-min ceiling" },
2288
2288
  ],
2289
2289
  },
2290
2290
  "video-audit-auto": {
@@ -2296,11 +2296,11 @@ const VIDEO_MODELS: Record<string, ModelCatalogEntry> = {
2296
2296
  description: "Same video-verified audit as AI Audit, but auto-runs a fast analysis first when none is wired in, then applies corrections under guards and returns a disclosed report. Billed per duration bucket.",
2297
2297
  useCases: ["video-audit", "qa", "shot-list", "verification", "auto-analysis"],
2298
2298
  pricing: [
2299
- { identifier: "video-audit:auto", credits: 1921, note: "10-min ceiling (no duration given)" },
2299
+ { identifier: "video-audit:auto", credits: 1924, note: "10-min ceiling (no duration given)" },
2300
2300
  { identifier: "video-audit:auto:60s", credits: 396 },
2301
- { identifier: "video-audit:auto:180s", credits: 475 },
2302
- { identifier: "video-audit:auto:360s", credits: 1178 },
2303
- { identifier: "video-audit:auto:600s", credits: 1921, note: "10-min ceiling" },
2301
+ { identifier: "video-audit:auto:180s", credits: 477 },
2302
+ { identifier: "video-audit:auto:360s", credits: 1180 },
2303
+ { identifier: "video-audit:auto:600s", credits: 1924, note: "10-min ceiling" },
2304
2304
  ],
2305
2305
  },
2306
2306
  }