@nodaro/shared 2.7.0 → 2.8.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 +83 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +30 -2
- package/dist/index.d.ts +30 -2
- package/dist/index.js +80 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/llm-models.test.ts +134 -8
- package/src/index.ts +6 -0
- package/src/llm-models.ts +111 -2
package/package.json
CHANGED
|
@@ -6,12 +6,18 @@ import {
|
|
|
6
6
|
LLM_REASONING_EFFORTS,
|
|
7
7
|
getLlmModel,
|
|
8
8
|
getLlmTier,
|
|
9
|
+
getLlmModalityCaps,
|
|
10
|
+
VIDEO_ANALYSIS_LLM_MODELS,
|
|
9
11
|
buildLlmCreditIdentifier,
|
|
10
12
|
resolveLlmCreditId,
|
|
11
13
|
motionGraphicsFeature,
|
|
12
14
|
effectiveReasoningEffort,
|
|
13
15
|
supportsAdvancedMode,
|
|
14
16
|
availableReasoningEfforts,
|
|
17
|
+
LLM_VENDOR_ORDER,
|
|
18
|
+
LLM_VENDOR_LABELS,
|
|
19
|
+
groupLlmModelsByVendor,
|
|
20
|
+
orderedLlmModels,
|
|
15
21
|
} from "../llm-models.js"
|
|
16
22
|
import type { LlmModelDef, LlmTier, LlmFeature } from "../llm-models.js"
|
|
17
23
|
import { PIPELINE_PINNABLE_SCRIPT_LLMS } from "../pipeline-types.js"
|
|
@@ -28,6 +34,7 @@ import { PIPELINE_PINNABLE_SCRIPT_LLMS } from "../pipeline-types.js"
|
|
|
28
34
|
const EXPECTED_MODEL_IDS = [
|
|
29
35
|
"gemini-3-flash",
|
|
30
36
|
"gemini-3.6-flash",
|
|
37
|
+
"gemini-3.7-flash",
|
|
31
38
|
"claude-haiku-4.5",
|
|
32
39
|
"claude-sonnet-4.6",
|
|
33
40
|
"gpt-5.2",
|
|
@@ -38,6 +45,7 @@ const EXPECTED_MODEL_IDS = [
|
|
|
38
45
|
"gpt-5.6-luna",
|
|
39
46
|
"gpt-5.6-terra",
|
|
40
47
|
"gpt-5.6-sol",
|
|
48
|
+
"grok-4.6",
|
|
41
49
|
"claude-sonnet-5",
|
|
42
50
|
"claude-opus-4.8",
|
|
43
51
|
"claude-opus-5",
|
|
@@ -86,13 +94,13 @@ describe("LLM_MODELS data integrity", () => {
|
|
|
86
94
|
expect(new Set(ids).size).toBe(ids.length)
|
|
87
95
|
})
|
|
88
96
|
|
|
89
|
-
it("has
|
|
97
|
+
it("has 5 economy, 5 standard, 8 premium models", () => {
|
|
90
98
|
const tierCounts: Record<LlmTier, number> = { economy: 0, standard: 0, premium: 0 }
|
|
91
99
|
for (const model of LLM_MODELS) {
|
|
92
100
|
tierCounts[model.tier]++
|
|
93
101
|
}
|
|
94
|
-
expect(tierCounts.economy).toBe(
|
|
95
|
-
expect(tierCounts.standard).toBe(
|
|
102
|
+
expect(tierCounts.economy).toBe(5)
|
|
103
|
+
expect(tierCounts.standard).toBe(5)
|
|
96
104
|
expect(tierCounts.premium).toBe(8)
|
|
97
105
|
})
|
|
98
106
|
|
|
@@ -103,11 +111,12 @@ describe("LLM_MODELS data integrity", () => {
|
|
|
103
111
|
expect(formats).toContain("responses")
|
|
104
112
|
})
|
|
105
113
|
|
|
106
|
-
it("all
|
|
114
|
+
it("all four vendors are represented", () => {
|
|
107
115
|
const vendors = new Set(LLM_MODELS.map((m) => m.vendor))
|
|
108
116
|
expect(vendors).toContain("anthropic")
|
|
109
117
|
expect(vendors).toContain("google")
|
|
110
118
|
expect(vendors).toContain("openai")
|
|
119
|
+
expect(vendors).toContain("xai")
|
|
111
120
|
})
|
|
112
121
|
|
|
113
122
|
it("all models support images", () => {
|
|
@@ -472,6 +481,7 @@ describe("STRUCTURED_VISION_MODELS", () => {
|
|
|
472
481
|
"claude-sonnet-4.6",
|
|
473
482
|
"gemini-3-flash",
|
|
474
483
|
"gemini-3.6-flash",
|
|
484
|
+
"gemini-3.7-flash",
|
|
475
485
|
"gemini-3.1-pro",
|
|
476
486
|
"claude-sonnet-5",
|
|
477
487
|
"claude-opus-4.8",
|
|
@@ -484,15 +494,18 @@ describe("STRUCTURED_VISION_MODELS", () => {
|
|
|
484
494
|
"gpt-5.6-luna",
|
|
485
495
|
"gpt-5.6-terra",
|
|
486
496
|
"gpt-5.6-sol",
|
|
497
|
+
// responses-format Grok — vision + text.format live-verified 2026-08-18.
|
|
498
|
+
"grok-4.6",
|
|
487
499
|
].sort(),
|
|
488
500
|
)
|
|
489
501
|
})
|
|
490
502
|
|
|
491
|
-
it("includes Anthropic (forced-tool), Gemini (response_format), and OpenAI (responses text.format) vendors", () => {
|
|
503
|
+
it("includes Anthropic (forced-tool), Gemini (response_format), and OpenAI/xAI (responses text.format) vendors", () => {
|
|
492
504
|
const vendors = new Set(STRUCTURED_VISION_MODELS.map((m) => m.vendor))
|
|
493
505
|
expect(vendors).toContain("anthropic")
|
|
494
506
|
expect(vendors).toContain("google")
|
|
495
507
|
expect(vendors).toContain("openai")
|
|
508
|
+
expect(vendors).toContain("xai")
|
|
496
509
|
})
|
|
497
510
|
|
|
498
511
|
it("excludes chat-completions GPT models — no native structured mode there (parse+retry only)", () => {
|
|
@@ -509,9 +522,9 @@ describe("STRUCTURED_VISION_MODELS", () => {
|
|
|
509
522
|
})
|
|
510
523
|
|
|
511
524
|
// ---------------------------------------------------------------------------
|
|
512
|
-
// Reasoning effort registry (GPT-5.6 / Claude Sonnet 5 / Claude Opus 4.8
|
|
513
|
-
// grok-4.5
|
|
514
|
-
//
|
|
525
|
+
// Reasoning effort registry (GPT-5.6 / Claude Sonnet 5 / Claude Opus 4.8 /
|
|
526
|
+
// Grok 4.6 — grok-4.5 was deferred on 2026-07-13 because its chat endpoint
|
|
527
|
+
// wasn't live; grok-4.6 is its activation, live-verified 2026-08-18.)
|
|
515
528
|
// ---------------------------------------------------------------------------
|
|
516
529
|
describe("reasoning effort registry", () => {
|
|
517
530
|
it("every reasoningEfforts list is a subset of the superset, in ascending order", () => {
|
|
@@ -526,6 +539,7 @@ describe("reasoning effort registry", () => {
|
|
|
526
539
|
expect(getLlmTier("gpt-5.6-luna")).toBe("economy")
|
|
527
540
|
expect(getLlmTier("gpt-5.6-terra")).toBe("standard")
|
|
528
541
|
expect(getLlmTier("gpt-5.6-sol")).toBe("premium")
|
|
542
|
+
expect(getLlmTier("grok-4.6")).toBe("standard")
|
|
529
543
|
expect(getLlmTier("claude-sonnet-5")).toBe("standard")
|
|
530
544
|
expect(getLlmTier("claude-opus-4.8")).toBe("premium")
|
|
531
545
|
expect(getLlmTier("gpt-5.5")).toBe("premium")
|
|
@@ -557,6 +571,62 @@ describe("reasoning effort registry", () => {
|
|
|
557
571
|
expect(getLlmModel("claude-fable-5")?.reasoningEfforts).toEqual(["low", "medium", "high", "xhigh", "max"])
|
|
558
572
|
expect(getLlmModel("claude-fable-5")?.directFallbackModel).toBe("claude-fable-5")
|
|
559
573
|
})
|
|
574
|
+
|
|
575
|
+
it("grok-4.6: responses format on the grok family path, KIE's documented effort enum, no sampling params, reasons by default", () => {
|
|
576
|
+
const m = getLlmModel("grok-4.6")
|
|
577
|
+
expect(m?.vendor).toBe("xai")
|
|
578
|
+
expect(m?.kieFormat).toBe("responses")
|
|
579
|
+
expect(m?.kieSlugOrModel).toBe("grok-4-6")
|
|
580
|
+
expect(m?.structuredOutputMode).toBe("responses-json-schema")
|
|
581
|
+
// KIE's enum is low..xhigh with NO `none` — the endpoint reasons
|
|
582
|
+
// unconditionally (thinkingDefaultOn), so `none` would be a lie.
|
|
583
|
+
expect(m?.reasoningEfforts).toEqual(["low", "medium", "high", "xhigh"])
|
|
584
|
+
// Live-probed 2026-08-18: temperature is silently ignored — never send it.
|
|
585
|
+
expect(m?.supportsTemperature).toBe(false)
|
|
586
|
+
// Load-bearing: consumers floor max_tokens off this flag (a trivial probe
|
|
587
|
+
// spent 169/170 output tokens on reasoning with no reasoning param sent).
|
|
588
|
+
expect(m?.thinkingDefaultOn).toBe(true)
|
|
589
|
+
// KIE-only: no direct lane on either vendor SDK.
|
|
590
|
+
expect(m?.directFallbackModel).toBeUndefined()
|
|
591
|
+
expect(m?.directGeminiModel).toBeUndefined()
|
|
592
|
+
})
|
|
593
|
+
|
|
594
|
+
it("grok-4.6 resolves from its dash-form wire slug", () => {
|
|
595
|
+
expect(getLlmModel("grok-4-6")?.id).toBe("grok-4.6")
|
|
596
|
+
})
|
|
597
|
+
})
|
|
598
|
+
|
|
599
|
+
// ---------------------------------------------------------------------------
|
|
600
|
+
// gemini-3.7-flash exposure (both lanes live-verified 2026-08-18: KIE
|
|
601
|
+
// chat-completions with reasoning_effort + enforced response_format, and the
|
|
602
|
+
// direct Google id resolving on generativelanguage)
|
|
603
|
+
// ---------------------------------------------------------------------------
|
|
604
|
+
describe("gemini-3.7-flash exposure", () => {
|
|
605
|
+
it("KIE-first chat-completions with a direct lane — 3.6-flash's proven shape", () => {
|
|
606
|
+
const m = getLlmModel("gemini-3.7-flash")
|
|
607
|
+
expect(m?.tier).toBe("economy")
|
|
608
|
+
expect(m?.vendor).toBe("google")
|
|
609
|
+
expect(m?.kieFormat).toBe("chat-completions")
|
|
610
|
+
expect(m?.kieSlugOrModel).toBe("gemini-3-7-flash-openai")
|
|
611
|
+
// KIE-first on purpose (no preferDirect): the cheap lane serves Generate
|
|
612
|
+
// Text; the direct lane is Advanced mode + the reliability fallback.
|
|
613
|
+
expect(m?.preferDirect).toBeUndefined()
|
|
614
|
+
expect(m?.directGeminiModel).toBe("gemini-3.7-flash")
|
|
615
|
+
expect(m?.reasoningEfforts).toEqual(["low", "high"])
|
|
616
|
+
expect(m?.structuredOutputMode).toBe("kie-response-format")
|
|
617
|
+
expect(supportsAdvancedMode("gemini-3.7-flash")).toBe(true)
|
|
618
|
+
expect(availableReasoningEfforts("gemini-3.7-flash", true)).toEqual(["none", "low", "medium", "high"])
|
|
619
|
+
})
|
|
620
|
+
|
|
621
|
+
it("stays OUT of video-analysis — image-only modality caps by decision, not omission", () => {
|
|
622
|
+
// Full video+audio caps would auto-enroll it in VIDEO_ANALYSIS_LLM_MODELS
|
|
623
|
+
// and force a VA tier + pricing decision that is deliberately deferred
|
|
624
|
+
// while the smart-family A/B routes this model internally (2026-08-18).
|
|
625
|
+
// If this test goes red, someone flipped the caps — that flip is only
|
|
626
|
+
// valid TOGETHER with the VA-side tier/pricing decision.
|
|
627
|
+
expect(getLlmModalityCaps("gemini-3.7-flash")).toEqual({ image: true, video: false, audio: false })
|
|
628
|
+
expect(VIDEO_ANALYSIS_LLM_MODELS).not.toContain("gemini-3.7-flash")
|
|
629
|
+
})
|
|
560
630
|
})
|
|
561
631
|
|
|
562
632
|
describe("effectiveReasoningEffort", () => {
|
|
@@ -584,6 +654,7 @@ describe("buildLlmCreditIdentifier effort bump (xhigh/max only)", () => {
|
|
|
584
654
|
})
|
|
585
655
|
it("standard + xhigh → premium", () => {
|
|
586
656
|
expect(buildLlmCreditIdentifier("llm-chat", "gpt-5.6-terra", "xhigh")).toBe("llm-chat:premium")
|
|
657
|
+
expect(buildLlmCreditIdentifier("llm-chat", "grok-4.6", "xhigh")).toBe("llm-chat:premium")
|
|
587
658
|
})
|
|
588
659
|
it("premium + max stays premium", () => {
|
|
589
660
|
expect(buildLlmCreditIdentifier("llm-chat", "gpt-5.6-sol", "max")).toBe("llm-chat:premium")
|
|
@@ -767,3 +838,58 @@ describe("lane-aware reasoning efforts", () => {
|
|
|
767
838
|
expect(bad).toEqual([])
|
|
768
839
|
})
|
|
769
840
|
})
|
|
841
|
+
|
|
842
|
+
// ---------------------------------------------------------------------------
|
|
843
|
+
// Vendor grouping — the ONE ordering every LLM model menu renders
|
|
844
|
+
// ---------------------------------------------------------------------------
|
|
845
|
+
describe("groupLlmModelsByVendor / orderedLlmModels", () => {
|
|
846
|
+
it("every vendor in the union has an order slot AND a label (totality)", () => {
|
|
847
|
+
// A new vendor added to LlmVendor without a menu decision would silently
|
|
848
|
+
// sort its models to the end of every picker unlabeled — fail here instead.
|
|
849
|
+
const vendorsInUse = new Set(LLM_MODELS.map((m) => m.vendor))
|
|
850
|
+
for (const v of vendorsInUse) {
|
|
851
|
+
expect(LLM_VENDOR_ORDER, `vendor "${v}" missing from LLM_VENDOR_ORDER`).toContain(v)
|
|
852
|
+
expect(LLM_VENDOR_LABELS[v], `vendor "${v}" missing from LLM_VENDOR_LABELS`).toBeTruthy()
|
|
853
|
+
}
|
|
854
|
+
for (const v of LLM_VENDOR_ORDER) {
|
|
855
|
+
expect(LLM_VENDOR_LABELS[v]).toBeTruthy()
|
|
856
|
+
}
|
|
857
|
+
})
|
|
858
|
+
|
|
859
|
+
it("covers every model exactly once, in vendor order", () => {
|
|
860
|
+
const groups = groupLlmModelsByVendor()
|
|
861
|
+
const flattened = groups.flatMap((g) => g.models.map((m) => m.id))
|
|
862
|
+
expect(flattened.sort()).toEqual(LLM_MODELS.map((m) => m.id).sort())
|
|
863
|
+
expect(new Set(flattened).size).toBe(flattened.length)
|
|
864
|
+
const groupVendors = groups.map((g) => g.vendor)
|
|
865
|
+
expect(groupVendors).toEqual(LLM_VENDOR_ORDER.filter((v) => groupVendors.includes(v)))
|
|
866
|
+
})
|
|
867
|
+
|
|
868
|
+
it("inside each group models are tier-ordered economy → standard → premium", () => {
|
|
869
|
+
const rank = { economy: 0, standard: 1, premium: 2 } as const
|
|
870
|
+
for (const g of groupLlmModelsByVendor()) {
|
|
871
|
+
const ranks = g.models.map((m) => rank[m.tier])
|
|
872
|
+
expect(ranks, g.vendor).toEqual([...ranks].sort((a, b) => a - b))
|
|
873
|
+
}
|
|
874
|
+
})
|
|
875
|
+
|
|
876
|
+
it("groups carry their display label and omit empty groups after a filter", () => {
|
|
877
|
+
const groups = groupLlmModelsByVendor(LLM_MODELS.filter((m) => m.vendor === "xai"))
|
|
878
|
+
expect(groups).toHaveLength(1)
|
|
879
|
+
expect(groups[0].label).toBe("xAI")
|
|
880
|
+
expect(groups[0].models.map((m) => m.id)).toEqual(["grok-4.6"])
|
|
881
|
+
})
|
|
882
|
+
|
|
883
|
+
it("does not mutate the registry (registry order is load-bearing for LLM_MODEL_IDS)", () => {
|
|
884
|
+
const before = LLM_MODELS.map((m) => m.id)
|
|
885
|
+
groupLlmModelsByVendor()
|
|
886
|
+
orderedLlmModels()
|
|
887
|
+
expect(LLM_MODELS.map((m) => m.id)).toEqual(before)
|
|
888
|
+
})
|
|
889
|
+
|
|
890
|
+
it("orderedLlmModels is the flattened grouping", () => {
|
|
891
|
+
expect(orderedLlmModels().map((m) => m.id)).toEqual(
|
|
892
|
+
groupLlmModelsByVendor().flatMap((g) => g.models.map((m) => m.id)),
|
|
893
|
+
)
|
|
894
|
+
})
|
|
895
|
+
})
|
package/src/index.ts
CHANGED
|
@@ -281,6 +281,12 @@ export {
|
|
|
281
281
|
LLM_MODELS,
|
|
282
282
|
LLM_MODEL_IDS,
|
|
283
283
|
STRUCTURED_VISION_MODELS,
|
|
284
|
+
LLM_VENDOR_ORDER,
|
|
285
|
+
LLM_VENDOR_LABELS,
|
|
286
|
+
groupLlmModelsByVendor,
|
|
287
|
+
orderedLlmModels,
|
|
288
|
+
type LlmVendor,
|
|
289
|
+
type LlmModelGroup,
|
|
284
290
|
VIDEO_ANALYSIS_LLM_MODELS,
|
|
285
291
|
VIDEO_ANALYSIS_TIERS,
|
|
286
292
|
VIDEO_ANALYSIS_LEGACY_MODELS,
|
package/src/llm-models.ts
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
export type LlmTier = "economy" | "standard" | "premium"
|
|
17
17
|
export type KieApiFormat = "chat-completions" | "messages" | "responses"
|
|
18
|
+
export type LlmVendor = "anthropic" | "google" | "openai" | "xai"
|
|
18
19
|
|
|
19
20
|
export const LLM_REASONING_EFFORTS = ["none", "low", "medium", "high", "xhigh", "max"] as const
|
|
20
21
|
export type LlmReasoningEffort = (typeof LLM_REASONING_EFFORTS)[number]
|
|
@@ -32,7 +33,7 @@ export interface LlmModelDef {
|
|
|
32
33
|
* For messages: the model id sent in the body (e.g. "claude-haiku-4-5-v1messages").
|
|
33
34
|
* For responses: the model id sent in the body (e.g. "gpt-5-4"). */
|
|
34
35
|
kieSlugOrModel: string
|
|
35
|
-
vendor:
|
|
36
|
+
vendor: LlmVendor
|
|
36
37
|
supportsImages: boolean
|
|
37
38
|
maxOutputTokens: number
|
|
38
39
|
/**
|
|
@@ -159,6 +160,30 @@ export const LLM_MODELS: readonly LlmModelDef[] = [
|
|
|
159
160
|
// direct is the reliability fallback only.
|
|
160
161
|
directGeminiModel: "gemini-3.6-flash",
|
|
161
162
|
},
|
|
163
|
+
{
|
|
164
|
+
id: "gemini-3.7-flash",
|
|
165
|
+
displayName: "Gemini 3.7 Flash",
|
|
166
|
+
desc: "Newest fast Gemini, agentic-tuned",
|
|
167
|
+
tier: "economy",
|
|
168
|
+
kieFormat: "chat-completions",
|
|
169
|
+
// KIE serves it on the OpenAI-compatible dialect under this slug
|
|
170
|
+
// (docs.kie.ai/market/gemini/gemini-3-7-flash-openai.md) — same
|
|
171
|
+
// chat-completions path shape as gemini-3.6-flash.
|
|
172
|
+
kieSlugOrModel: "gemini-3-7-flash-openai",
|
|
173
|
+
vendor: "google",
|
|
174
|
+
structuredOutputMode: "kie-response-format",
|
|
175
|
+
supportsImages: true,
|
|
176
|
+
// Google's own cap is 65,536, but the field feeds BOTH lanes and the KIE
|
|
177
|
+
// flash endpoints cap at 8192 (the measured 3.6 posture) — stay at the
|
|
178
|
+
// KIE-safe intersection, same reasoning as `reasoningEfforts` below.
|
|
179
|
+
maxOutputTokens: 8192,
|
|
180
|
+
// KIE's 3.7 endpoint enumerates reasoning_effort low | high (verified
|
|
181
|
+
// against its OpenAPI spec 2026-08-18), identical to 3.6.
|
|
182
|
+
reasoningEfforts: ["low", "high"],
|
|
183
|
+
// Assumed parity with 3.6 pending a live probe on the direct lane.
|
|
184
|
+
directReasoningEfforts: ["none", "low", "medium", "high"],
|
|
185
|
+
directGeminiModel: "gemini-3.7-flash",
|
|
186
|
+
},
|
|
162
187
|
{
|
|
163
188
|
id: "claude-haiku-4.5",
|
|
164
189
|
displayName: "Claude Haiku 4.5",
|
|
@@ -321,7 +346,35 @@ export const LLM_MODELS: readonly LlmModelDef[] = [
|
|
|
321
346
|
reasoningEfforts: ["none", "low", "medium", "high", "xhigh", "max"],
|
|
322
347
|
supportsTemperature: false,
|
|
323
348
|
},
|
|
324
|
-
|
|
349
|
+
{
|
|
350
|
+
id: "grok-4.6",
|
|
351
|
+
displayName: "Grok 4.6",
|
|
352
|
+
desc: "xAI flagship, strong reasoning",
|
|
353
|
+
tier: "standard",
|
|
354
|
+
kieFormat: "responses",
|
|
355
|
+
// KIE serves Grok on the responses dialect under its own family path —
|
|
356
|
+
// grok/v1/responses, NOT codex/v1/responses (llm-client derives the path
|
|
357
|
+
// from `vendor`). Live-verified end-to-end 2026-08-18: array `input`,
|
|
358
|
+
// `developer` system role, `input_image` URL vision, `text.format`
|
|
359
|
+
// json_schema enforcement, SSE `response.output_text.delta` stream, and
|
|
360
|
+
// `credits_consumed` actual-cost capture. (grok-4.5 was deferred 2026-07-13
|
|
361
|
+
// because none of this was live; 4.6 is its activation.)
|
|
362
|
+
kieSlugOrModel: "grok-4-6",
|
|
363
|
+
vendor: "xai",
|
|
364
|
+
structuredOutputMode: "responses-json-schema",
|
|
365
|
+
supportsImages: true,
|
|
366
|
+
maxOutputTokens: 16384,
|
|
367
|
+
// KIE's documented enum, each level live-verified (echoed back) 2026-08-18.
|
|
368
|
+
// No `none`: the endpoint reasons unconditionally (see thinkingDefaultOn).
|
|
369
|
+
reasoningEfforts: ["low", "medium", "high", "xhigh"],
|
|
370
|
+
// Live-probed 2026-08-18: `temperature` is silently IGNORED (request echo
|
|
371
|
+
// stays at the 0.7 default), so never send it — same treatment as GPT-5.5+.
|
|
372
|
+
supportsTemperature: false,
|
|
373
|
+
// Reasons with NO reasoning param sent (effort defaults to "low" server-side
|
|
374
|
+
// — a trivial probe spent 169 of 170 output tokens on reasoning), so every
|
|
375
|
+
// call needs output headroom, not just xhigh.
|
|
376
|
+
thinkingDefaultOn: true,
|
|
377
|
+
},
|
|
325
378
|
{
|
|
326
379
|
id: "claude-sonnet-5",
|
|
327
380
|
displayName: "Claude Sonnet 5",
|
|
@@ -407,6 +460,55 @@ export const STRUCTURED_VISION_MODELS = LLM_MODELS.filter(
|
|
|
407
460
|
(m) => m.supportsImages && m.structuredOutputMode != null,
|
|
408
461
|
)
|
|
409
462
|
|
|
463
|
+
/**
|
|
464
|
+
* Vendor presentation order + labels for model pickers. Every LlmVendor MUST
|
|
465
|
+
* appear in the order list (guarded by a registry test) so a new vendor can't
|
|
466
|
+
* ship with its models silently sorted to the end of every menu unlabeled.
|
|
467
|
+
* Alphabetical on purpose: stable, and no vendor-preference fights.
|
|
468
|
+
*/
|
|
469
|
+
export const LLM_VENDOR_ORDER: readonly LlmVendor[] = ["anthropic", "google", "openai", "xai"]
|
|
470
|
+
export const LLM_VENDOR_LABELS: Record<LlmVendor, string> = {
|
|
471
|
+
anthropic: "Anthropic",
|
|
472
|
+
google: "Google",
|
|
473
|
+
openai: "OpenAI",
|
|
474
|
+
xai: "xAI",
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const TIER_RANK: Record<LlmTier, number> = { economy: 0, standard: 1, premium: 2 }
|
|
478
|
+
|
|
479
|
+
export interface LlmModelGroup {
|
|
480
|
+
vendor: LlmVendor
|
|
481
|
+
/** Display heading for the group (LLM_VENDOR_LABELS[vendor]). */
|
|
482
|
+
label: string
|
|
483
|
+
models: LlmModelDef[]
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* The ONE ordering every LLM model menu renders: grouped by vendor (in
|
|
488
|
+
* LLM_VENDOR_ORDER), and inside each group sorted economy → standard → premium
|
|
489
|
+
* (registry order breaks ties, which keeps family generations adjacent).
|
|
490
|
+
* A flat registry-order dump was genuinely hard to scan at 17 models — every
|
|
491
|
+
* picker (config panel, quick strips, quick toolbar) derives from this so the
|
|
492
|
+
* menus can't drift apart. Groups with no models (after `filter`) are omitted.
|
|
493
|
+
*/
|
|
494
|
+
export function groupLlmModelsByVendor(models: readonly LlmModelDef[] = LLM_MODELS): LlmModelGroup[] {
|
|
495
|
+
const groups: LlmModelGroup[] = []
|
|
496
|
+
for (const vendor of LLM_VENDOR_ORDER) {
|
|
497
|
+
const members = models
|
|
498
|
+
.filter((m) => m.vendor === vendor)
|
|
499
|
+
.sort((a, b) => TIER_RANK[a.tier] - TIER_RANK[b.tier])
|
|
500
|
+
if (members.length > 0) groups.push({ vendor, label: LLM_VENDOR_LABELS[vendor], models: members })
|
|
501
|
+
}
|
|
502
|
+
return groups
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/** {@link groupLlmModelsByVendor} flattened — for menus that can't render
|
|
506
|
+
* group headers (e.g. the compact node quick strips) but should still read
|
|
507
|
+
* vendor-clustered and tier-ordered. */
|
|
508
|
+
export function orderedLlmModels(models: readonly LlmModelDef[] = LLM_MODELS): LlmModelDef[] {
|
|
509
|
+
return groupLlmModelsByVendor(models).flatMap((g) => g.models)
|
|
510
|
+
}
|
|
511
|
+
|
|
410
512
|
export type LlmFeature =
|
|
411
513
|
| "ai-writer"
|
|
412
514
|
| "llm-chat"
|
|
@@ -465,6 +567,12 @@ export const LLM_FEATURE_DEFAULTS: Record<LlmFeature, string> = {
|
|
|
465
567
|
export const LLM_MODALITY_CAPS: Record<string, { image: boolean; video: boolean; audio: boolean }> = {
|
|
466
568
|
"gemini-3-flash": { image: true, video: true, audio: true },
|
|
467
569
|
"gemini-3.6-flash": { image: true, video: true, audio: true },
|
|
570
|
+
// gemini-3.7-flash is IMAGE-ONLY by DECISION, not omission: full video+audio
|
|
571
|
+
// caps would auto-enroll it in VIDEO_ANALYSIS_LLM_MODELS (derived below) and
|
|
572
|
+
// force a video-analysis tier + pricing decision that is deliberately
|
|
573
|
+
// deferred while the smart-family A/B routes this model internally (#747).
|
|
574
|
+
// Flip these two flags ONLY together with that VA-side decision.
|
|
575
|
+
"gemini-3.7-flash": { image: true, video: false, audio: false },
|
|
468
576
|
"gemini-3.1-pro": { image: true, video: true, audio: true },
|
|
469
577
|
"claude-haiku-4.5": { image: true, video: false, audio: false },
|
|
470
578
|
"claude-sonnet-4.6": { image: true, video: false, audio: false },
|
|
@@ -475,6 +583,7 @@ export const LLM_MODALITY_CAPS: Record<string, { image: boolean; video: boolean;
|
|
|
475
583
|
"gpt-5.6-luna": { image: true, video: false, audio: false },
|
|
476
584
|
"gpt-5.6-terra": { image: true, video: false, audio: false },
|
|
477
585
|
"gpt-5.6-sol": { image: true, video: false, audio: false },
|
|
586
|
+
"grok-4.6": { image: true, video: false, audio: false },
|
|
478
587
|
"claude-sonnet-5": { image: true, video: false, audio: false },
|
|
479
588
|
"claude-opus-4.8": { image: true, video: false, audio: false },
|
|
480
589
|
"claude-opus-5": { image: true, video: false, audio: false },
|