@oh-my-pi/pi-catalog 18.1.3 → 18.1.4
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/CHANGELOG.md +10 -0
- package/dist/types/compat/collapse.d.ts +20 -0
- package/dist/types/compat/types.d.ts +12 -0
- package/package.json +4 -4
- package/src/compat/collapse.ts +206 -51
- package/src/compat/rules/README.md +3 -2
- package/src/compat/rules/providers/google-antigravity.kdl +1 -1
- package/src/compat/rules/taxonomy/_collapse.kdl +17 -37
- package/src/compat/rules.json +22 -88
- package/src/compat/types.ts +13 -0
- package/src/models.json +2562 -1312
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [18.1.4] - 2026-09-02
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Enabled Cursor tool schema projection for supported models
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Antigravity and Gemini CLI now collapse every Gemini Flash generation from 3.6 on (`gemini-3.8-flash-low/-medium/-high` and the `-tiered` alias, and future revisions) into one routed `gemini-<rev>-flash` entry via a revision-templated `variant-family`, instead of surfacing raw per-level ids until a per-revision rule lands.
|
|
14
|
+
|
|
5
15
|
## [18.1.3] - 2026-09-02
|
|
6
16
|
|
|
7
17
|
### Added
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Effort } from "../effort.js";
|
|
2
2
|
import type { Api, Model, ModelSpec, Provider, ThinkingConfig } from "../types.js";
|
|
3
|
+
import { type RevisionTerm } from "./revision.js";
|
|
3
4
|
/**
|
|
4
5
|
* Structural bound for collapse inputs: both raw `ModelSpec`s and built
|
|
5
6
|
* `Model`s qualify. (`Model.compat` is the resolved record, not the sparse
|
|
@@ -65,8 +66,27 @@ export interface EffortVariantFamily {
|
|
|
65
66
|
/** Retired/recycled selector ids that alias to this family without being members. */
|
|
66
67
|
extraAliases?: readonly string[];
|
|
67
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* A reviewed family whose ids carry {@link REVISION_PLACEHOLDER}: one
|
|
71
|
+
* `gemini-{rev}-flash` entry stands for every Flash generation the provider
|
|
72
|
+
* serves with the same sibling layout. Concrete families with the same
|
|
73
|
+
* instantiated id take precedence.
|
|
74
|
+
*/
|
|
75
|
+
export interface VariantFamilyTemplate {
|
|
76
|
+
/** Family with placeholders still in every id and the display name. */
|
|
77
|
+
family: EffortVariantFamily;
|
|
78
|
+
/** Revisions the template applies to; unbounded when absent. */
|
|
79
|
+
revision?: readonly RevisionTerm[];
|
|
80
|
+
/**
|
|
81
|
+
* Matches the logical id, any member, or any extra alias (case-insensitive);
|
|
82
|
+
* one alternative per id, each capturing the revision.
|
|
83
|
+
*/
|
|
84
|
+
pattern: RegExp;
|
|
85
|
+
}
|
|
68
86
|
export interface VariantCollapseTable {
|
|
69
87
|
families: readonly EffortVariantFamily[];
|
|
88
|
+
/** Revision-templated families, instantiated against live ids. */
|
|
89
|
+
templates?: readonly VariantFamilyTemplate[];
|
|
70
90
|
/**
|
|
71
91
|
* Provider-scoped selector aliases: short native-CLI names and dotted
|
|
72
92
|
* upstream spellings → logical model id. Unlike family members and
|
|
@@ -80,6 +80,13 @@ export interface CompiledEffortFamily {
|
|
|
80
80
|
}
|
|
81
81
|
/** Per-effort routing tier keys (`"off"` = thinking disabled). */
|
|
82
82
|
export type VariantTier = Effort | "off";
|
|
83
|
+
/**
|
|
84
|
+
* Revision placeholder in a templated `variant-family` (`gemini-{rev}-flash`).
|
|
85
|
+
* A family whose id carries it is instantiated once per live revision that
|
|
86
|
+
* discovery advertises, so new generations of a lineage collapse without a
|
|
87
|
+
* new reviewed entry.
|
|
88
|
+
*/
|
|
89
|
+
export declare const REVISION_PLACEHOLDER = "{rev}";
|
|
83
90
|
/**
|
|
84
91
|
* One reviewed provider-scoped variant family: a logical model whose provider
|
|
85
92
|
* serves per-effort/thinking sibling wire ids, with explicit routing, ladder,
|
|
@@ -89,6 +96,11 @@ export interface CompiledVariantFamily {
|
|
|
89
96
|
provider: string;
|
|
90
97
|
id: string;
|
|
91
98
|
name: string;
|
|
99
|
+
/**
|
|
100
|
+
* Revision constraint (`">=3.6"`) gating instantiation of a
|
|
101
|
+
* {@link REVISION_PLACEHOLDER} template; absent on concrete families.
|
|
102
|
+
*/
|
|
103
|
+
revision?: string;
|
|
92
104
|
/** Member wire ids in priority order. */
|
|
93
105
|
members: string[];
|
|
94
106
|
/** Preferred default wire id when live. */
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-catalog",
|
|
4
|
-
"version": "18.1.
|
|
4
|
+
"version": "18.1.4",
|
|
5
5
|
"description": "Model catalog for omp: bundled model database, provider discovery descriptors, model identity, classification, and equivalence",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Stencil Labs, Inc.",
|
|
@@ -35,12 +35,12 @@
|
|
|
35
35
|
"gen:proto": "bun scripts/generate-protocols.ts"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@oh-my-pi/omptype": "18.1.
|
|
39
|
-
"@oh-my-pi/pi-utils": "18.1.
|
|
38
|
+
"@oh-my-pi/omptype": "18.1.4",
|
|
39
|
+
"@oh-my-pi/pi-utils": "18.1.4"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@bgotink/kdl": "0.4.0",
|
|
43
|
-
"@oh-my-pi/pi-ai": "18.1.
|
|
43
|
+
"@oh-my-pi/pi-ai": "18.1.4",
|
|
44
44
|
"@types/bun": "^1.3.14"
|
|
45
45
|
},
|
|
46
46
|
"engines": {
|
package/src/compat/collapse.ts
CHANGED
|
@@ -13,7 +13,9 @@
|
|
|
13
13
|
* Families come from two sources:
|
|
14
14
|
* - Reviewed `variant-family` nodes in `compat/rules/taxonomy/_collapse.kdl`
|
|
15
15
|
* (compiled into `rules.json`) for providers whose routing needs curation
|
|
16
|
-
* (Antigravity tier triplets, single-member renames, recycled ids).
|
|
16
|
+
* (Antigravity tier triplets, single-member renames, recycled ids). A
|
|
17
|
+
* family whose ids carry `{rev}` is a template, instantiated for every
|
|
18
|
+
* revision the input advertises (`gemini-{rev}-flash` → `gemini-3.8-flash`).
|
|
17
19
|
* - `deriveThinkingPairFamilies`: the global automatic rule — any live
|
|
18
20
|
* `X` + `X-thinking` pair (trailing or infix token) collapses into `X`,
|
|
19
21
|
* routing thinking-enabled requests to `X-thinking`. Gated on identical
|
|
@@ -36,8 +38,9 @@ import { buildModel } from "../build";
|
|
|
36
38
|
import { Effort, THINKING_EFFORTS } from "../effort";
|
|
37
39
|
import type { Api, Model, ModelSpec, Provider, ThinkingConfig } from "../types";
|
|
38
40
|
import { resolveModelPolicy } from "./resolve";
|
|
41
|
+
import { parseRevision, parseRevisionConstraint, type RevisionTerm, revisionSatisfies } from "./revision";
|
|
39
42
|
import { collapseVariantId, collapseVocabulary, stripThinkingVariantSuffix } from "./taxonomy";
|
|
40
|
-
import type
|
|
43
|
+
import { type CompiledVariantFamily, REVISION_PLACEHOLDER } from "./types";
|
|
41
44
|
|
|
42
45
|
const VARIANT_ROUTING_KEYS: readonly (Effort | "off")[] = ["off", ...THINKING_EFFORTS];
|
|
43
46
|
const DEFAULT_PAIR_EFFORTS: readonly Effort[] = [Effort.Minimal, Effort.Low, Effort.Medium, Effort.High];
|
|
@@ -107,8 +110,28 @@ export interface EffortVariantFamily {
|
|
|
107
110
|
extraAliases?: readonly string[];
|
|
108
111
|
}
|
|
109
112
|
|
|
113
|
+
/**
|
|
114
|
+
* A reviewed family whose ids carry {@link REVISION_PLACEHOLDER}: one
|
|
115
|
+
* `gemini-{rev}-flash` entry stands for every Flash generation the provider
|
|
116
|
+
* serves with the same sibling layout. Concrete families with the same
|
|
117
|
+
* instantiated id take precedence.
|
|
118
|
+
*/
|
|
119
|
+
export interface VariantFamilyTemplate {
|
|
120
|
+
/** Family with placeholders still in every id and the display name. */
|
|
121
|
+
family: EffortVariantFamily;
|
|
122
|
+
/** Revisions the template applies to; unbounded when absent. */
|
|
123
|
+
revision?: readonly RevisionTerm[];
|
|
124
|
+
/**
|
|
125
|
+
* Matches the logical id, any member, or any extra alias (case-insensitive);
|
|
126
|
+
* one alternative per id, each capturing the revision.
|
|
127
|
+
*/
|
|
128
|
+
pattern: RegExp;
|
|
129
|
+
}
|
|
130
|
+
|
|
110
131
|
export interface VariantCollapseTable {
|
|
111
132
|
families: readonly EffortVariantFamily[];
|
|
133
|
+
/** Revision-templated families, instantiated against live ids. */
|
|
134
|
+
templates?: readonly VariantFamilyTemplate[];
|
|
112
135
|
/**
|
|
113
136
|
* Provider-scoped selector aliases: short native-CLI names and dotted
|
|
114
137
|
* upstream spellings → logical model id. Unlike family members and
|
|
@@ -209,13 +232,104 @@ function compiledFamily(compiled: CompiledVariantFamily): EffortVariantFamily {
|
|
|
209
232
|
return family;
|
|
210
233
|
}
|
|
211
234
|
|
|
235
|
+
const REVISION_CAPTURE = String.raw`(\d+(?:\.\d+){0,2})`;
|
|
236
|
+
|
|
237
|
+
/** One compiled `{rev}` family → runtime template with its id matcher. */
|
|
238
|
+
function compiledTemplate(compiled: CompiledVariantFamily): VariantFamilyTemplate {
|
|
239
|
+
const family = compiledFamily(compiled);
|
|
240
|
+
const alternatives = [family.id, ...family.members, ...(family.extraAliases ?? [])].map(id =>
|
|
241
|
+
id
|
|
242
|
+
.split(REVISION_PLACEHOLDER)
|
|
243
|
+
.map(part => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))
|
|
244
|
+
.join(REVISION_CAPTURE),
|
|
245
|
+
);
|
|
246
|
+
const template: VariantFamilyTemplate = {
|
|
247
|
+
family,
|
|
248
|
+
pattern: new RegExp(`^(?:${alternatives.join("|")})$`, "i"),
|
|
249
|
+
};
|
|
250
|
+
if (compiled.revision !== undefined) {
|
|
251
|
+
const terms = parseRevisionConstraint(compiled.revision);
|
|
252
|
+
if (terms !== undefined) template.revision = terms;
|
|
253
|
+
}
|
|
254
|
+
return template;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** Substitute the placeholder throughout one templated family. */
|
|
258
|
+
function instantiateTemplate(template: VariantFamilyTemplate, rev: string): EffortVariantFamily | undefined {
|
|
259
|
+
if (template.revision !== undefined) {
|
|
260
|
+
const parsed = parseRevision(rev);
|
|
261
|
+
if (parsed === undefined || !revisionSatisfies(parsed, template.revision)) return undefined;
|
|
262
|
+
}
|
|
263
|
+
const fill = (id: string): string => id.replaceAll(REVISION_PLACEHOLDER, rev);
|
|
264
|
+
const source = template.family;
|
|
265
|
+
const routing: Partial<Record<Effort | "off", string>> = {};
|
|
266
|
+
for (const effort of VARIANT_ROUTING_KEYS) {
|
|
267
|
+
const target = source.routing[effort];
|
|
268
|
+
if (target !== undefined) routing[effort] = fill(target);
|
|
269
|
+
}
|
|
270
|
+
const family: EffortVariantFamily = {
|
|
271
|
+
...source,
|
|
272
|
+
id: fill(source.id),
|
|
273
|
+
name: fill(source.name),
|
|
274
|
+
members: source.members.map(fill),
|
|
275
|
+
routing,
|
|
276
|
+
};
|
|
277
|
+
if (source.defaultMember !== undefined) family.defaultMember = fill(source.defaultMember);
|
|
278
|
+
if (source.retiredMembers !== undefined) family.retiredMembers = source.retiredMembers.map(fill);
|
|
279
|
+
if (source.extraAliases !== undefined) family.extraAliases = source.extraAliases.map(fill);
|
|
280
|
+
return family;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* The family a template yields for `id` (logical id, member, or alias), or
|
|
285
|
+
* undefined when no template matches or the revision is out of range.
|
|
286
|
+
*/
|
|
287
|
+
function templateFamilyFor(templates: readonly VariantFamilyTemplate[], id: string): EffortVariantFamily | undefined {
|
|
288
|
+
for (const template of templates) {
|
|
289
|
+
const match = template.pattern.exec(id);
|
|
290
|
+
if (match === null) continue;
|
|
291
|
+
let rev: string | undefined;
|
|
292
|
+
for (let group = 1; group < match.length && rev === undefined; group++) rev = match[group];
|
|
293
|
+
if (rev === undefined) continue;
|
|
294
|
+
const family = instantiateTemplate(template, rev);
|
|
295
|
+
if (family !== undefined) return family;
|
|
296
|
+
}
|
|
297
|
+
return undefined;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Concrete families for every revision present in `ids`, one per
|
|
302
|
+
* instantiated id, skipping ids a concrete family in `table` already owns.
|
|
303
|
+
*/
|
|
304
|
+
function instantiateTemplates(table: VariantCollapseTable, ids: Iterable<string>): EffortVariantFamily[] {
|
|
305
|
+
const templates = table.templates;
|
|
306
|
+
if (templates === undefined || templates.length === 0) return [];
|
|
307
|
+
const seen = new Set<string>();
|
|
308
|
+
for (const family of table.families) seen.add(family.id);
|
|
309
|
+
const out: EffortVariantFamily[] = [];
|
|
310
|
+
for (const id of ids) {
|
|
311
|
+
const family = templateFamilyFor(templates, id);
|
|
312
|
+
if (family === undefined || seen.has(family.id)) continue;
|
|
313
|
+
seen.add(family.id);
|
|
314
|
+
out.push(family);
|
|
315
|
+
}
|
|
316
|
+
return out;
|
|
317
|
+
}
|
|
318
|
+
|
|
212
319
|
/** Provider id → reviewed collapse table, built once from the compiled vocabulary. */
|
|
213
320
|
function buildCompiledTables(): Readonly<Record<string, VariantCollapseTable>> {
|
|
214
321
|
const { variantFamilies, providerAliases } = collapseVocabulary();
|
|
215
|
-
const tables: Record<
|
|
322
|
+
const tables: Record<
|
|
323
|
+
string,
|
|
324
|
+
{ families: EffortVariantFamily[]; templates?: VariantFamilyTemplate[]; providerAliases?: Record<string, string> }
|
|
325
|
+
> = {};
|
|
216
326
|
for (const compiled of variantFamilies) {
|
|
217
|
-
tables[compiled.provider] ??= { families: [] };
|
|
218
|
-
|
|
327
|
+
const table = (tables[compiled.provider] ??= { families: [] });
|
|
328
|
+
if (compiled.id.includes(REVISION_PLACEHOLDER)) {
|
|
329
|
+
(table.templates ??= []).push(compiledTemplate(compiled));
|
|
330
|
+
} else {
|
|
331
|
+
table.families.push(compiledFamily(compiled));
|
|
332
|
+
}
|
|
219
333
|
}
|
|
220
334
|
for (const [provider, aliases] of Object.entries(providerAliases)) {
|
|
221
335
|
tables[provider] ??= { families: [] };
|
|
@@ -407,16 +521,14 @@ export function deriveThinkingPairFamilies<TSpec extends VariantSpecLike>(
|
|
|
407
521
|
if (baseId === undefined || baseId === spec.id) continue;
|
|
408
522
|
const base = byId.get(baseId);
|
|
409
523
|
if (!base) continue;
|
|
410
|
-
if (
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
claimed.
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
continue;
|
|
419
|
-
}
|
|
524
|
+
if (
|
|
525
|
+
claimed &&
|
|
526
|
+
(claimed.resolve(spec.id) !== undefined ||
|
|
527
|
+
claimed.resolve(baseId) !== undefined ||
|
|
528
|
+
claimed.isFamily(spec.id) ||
|
|
529
|
+
claimed.isFamily(baseId))
|
|
530
|
+
) {
|
|
531
|
+
continue;
|
|
420
532
|
}
|
|
421
533
|
if (spec.api !== base.api) continue;
|
|
422
534
|
const specPriced = spec.cost.input !== 0 || spec.cost.output !== 0;
|
|
@@ -483,7 +595,7 @@ export function isCollapsedVariantSpec(spec: VariantSpecLike): boolean {
|
|
|
483
595
|
return false;
|
|
484
596
|
}
|
|
485
597
|
const table = reviewedCollapseTable(spec.provider);
|
|
486
|
-
return table !== undefined && getAliasIndex(table).
|
|
598
|
+
return table !== undefined && getAliasIndex(table).isFamily(spec.id);
|
|
487
599
|
}
|
|
488
600
|
|
|
489
601
|
/**
|
|
@@ -641,7 +753,9 @@ function collapseWithTable<TSpec extends VariantSpecLike>(
|
|
|
641
753
|
/** spec ids that belong to a touched family (members + logical id). */
|
|
642
754
|
const familyIdBySpecId = new Map<string, string>();
|
|
643
755
|
|
|
644
|
-
|
|
756
|
+
const instantiated = instantiateTemplates(table, byId.keys());
|
|
757
|
+
const families = instantiated.length === 0 ? table.families : [...table.families, ...instantiated];
|
|
758
|
+
for (const family of families) {
|
|
645
759
|
const retired =
|
|
646
760
|
family.retiredMembers !== undefined && family.retiredMembers.length > 0
|
|
647
761
|
? new Set(family.retiredMembers)
|
|
@@ -917,39 +1031,84 @@ function projectModelSpec<TApi extends Api>(model: Model<TApi>): ModelSpec<TApi>
|
|
|
917
1031
|
return { ...spec, compat: compatConfig };
|
|
918
1032
|
}
|
|
919
1033
|
|
|
1034
|
+
/**
|
|
1035
|
+
* Alias index over a collapse table (or the live families observed at
|
|
1036
|
+
* runtime). Templated families are materialized on first lookup of any id
|
|
1037
|
+
* they match, so a revision discovery has never advertised still resolves.
|
|
1038
|
+
*/
|
|
920
1039
|
class VariantAliasIndex {
|
|
921
1040
|
/** lowercased retired id → replacement model id. */
|
|
922
|
-
readonly forward = new Map<string, string>();
|
|
1041
|
+
readonly #forward = new Map<string, string>();
|
|
923
1042
|
/**
|
|
924
1043
|
* lowercased provider-scoped alias → replacement model id. Kept apart from
|
|
925
|
-
*
|
|
1044
|
+
* `#forward` so bare-id and reverse lookups never see it.
|
|
926
1045
|
*/
|
|
927
1046
|
readonly providerScoped = new Map<string, string>();
|
|
928
1047
|
/** replacement model id → retired ids that resolve to it. */
|
|
929
|
-
readonly reverse = new Map<string, string[]>();
|
|
1048
|
+
readonly #reverse = new Map<string, string[]>();
|
|
930
1049
|
/** Collapsed logical ids declared by the table or observed at runtime. */
|
|
931
|
-
readonly familyIds = new Set<string>();
|
|
932
|
-
|
|
1050
|
+
readonly #familyIds = new Set<string>();
|
|
1051
|
+
readonly #templates: readonly VariantFamilyTemplate[];
|
|
933
1052
|
|
|
934
|
-
|
|
935
|
-
|
|
1053
|
+
constructor(templates: readonly VariantFamilyTemplate[] = []) {
|
|
1054
|
+
this.#templates = templates;
|
|
1055
|
+
}
|
|
936
1056
|
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
1057
|
+
/** Register `from` → `to`; false when `from` is already claimed. */
|
|
1058
|
+
add(from: string, to: string): boolean {
|
|
1059
|
+
if (from === to || this.#forward.has(from.toLowerCase())) return false;
|
|
1060
|
+
this.#forward.set(from.toLowerCase(), to);
|
|
1061
|
+
const sources = this.#reverse.get(to);
|
|
1062
|
+
if (sources) {
|
|
1063
|
+
sources.push(from);
|
|
1064
|
+
} else {
|
|
1065
|
+
this.#reverse.set(to, [from]);
|
|
1066
|
+
}
|
|
1067
|
+
return true;
|
|
1068
|
+
}
|
|
940
1069
|
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
1070
|
+
/** Register a family: its logical id plus every member and extra alias. */
|
|
1071
|
+
addFamily(family: EffortVariantFamily): void {
|
|
1072
|
+
this.#familyIds.add(family.id);
|
|
1073
|
+
for (const member of family.members) this.add(member, family.id);
|
|
1074
|
+
for (const alias of family.extraAliases ?? []) this.add(alias, family.id);
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
markFamily(id: string): void {
|
|
1078
|
+
this.#familyIds.add(id);
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
/** Replacement model id for a retired/member id (case-insensitive). */
|
|
1082
|
+
resolve(id: string): string | undefined {
|
|
1083
|
+
const lower = id.toLowerCase();
|
|
1084
|
+
this.#learn(lower);
|
|
1085
|
+
return this.#forward.get(lower);
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
/** Whether `id` is a collapsed logical id of this index. */
|
|
1089
|
+
isFamily(id: string): boolean {
|
|
1090
|
+
this.#learn(id);
|
|
1091
|
+
return this.#familyIds.has(id);
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
/** Retired ids that resolve to logical `id`. */
|
|
1095
|
+
sourcesOf(id: string): readonly string[] | undefined {
|
|
1096
|
+
this.#learn(id);
|
|
1097
|
+
return this.#reverse.get(id);
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
/** Materialize the template family that owns `id`, once. */
|
|
1101
|
+
#learn(id: string): void {
|
|
1102
|
+
if (this.#templates.length === 0) return;
|
|
1103
|
+
const family = templateFamilyFor(this.#templates, id);
|
|
1104
|
+
if (family === undefined || this.#familyIds.has(family.id)) return;
|
|
1105
|
+
this.addFamily(family);
|
|
949
1106
|
}
|
|
950
|
-
return true;
|
|
951
1107
|
}
|
|
952
1108
|
|
|
1109
|
+
const dynamicAliasIndexes = new Map<string, VariantAliasIndex>();
|
|
1110
|
+
const kAliasIndex = Symbol("compat-collapse.aliasIndex");
|
|
1111
|
+
|
|
953
1112
|
/**
|
|
954
1113
|
* Persist aliases embedded in collapsed routing so generated catalog rows and
|
|
955
1114
|
* newly discovered families expose the same selector migrations as reviewed tables.
|
|
@@ -964,14 +1123,14 @@ function registerCollapsedVariantAliases(provider: Provider, specs: readonly Var
|
|
|
964
1123
|
for (const effort of VARIANT_ROUTING_KEYS) {
|
|
965
1124
|
const source = routing[effort];
|
|
966
1125
|
if (!source || source === spec.id) continue;
|
|
967
|
-
index ??=
|
|
968
|
-
registered =
|
|
1126
|
+
index ??= new VariantAliasIndex();
|
|
1127
|
+
registered = index.add(source, spec.id) || registered;
|
|
969
1128
|
}
|
|
970
1129
|
if (spec.requestModelId && spec.requestModelId !== spec.id) {
|
|
971
|
-
index ??=
|
|
972
|
-
registered =
|
|
1130
|
+
index ??= new VariantAliasIndex();
|
|
1131
|
+
registered = index.add(spec.requestModelId, spec.id) || registered;
|
|
973
1132
|
}
|
|
974
|
-
if (registered) index?.
|
|
1133
|
+
if (registered) index?.markFamily(spec.id);
|
|
975
1134
|
}
|
|
976
1135
|
if (index) dynamicAliasIndexes.set(providerId, index);
|
|
977
1136
|
}
|
|
@@ -980,20 +1139,16 @@ function resolveRegisteredVariantAlias(provider: Provider, normalizedModelId: st
|
|
|
980
1139
|
const providerId = provider.toLowerCase();
|
|
981
1140
|
const table = reviewedCollapseTable(provider) ?? reviewedCollapseTable(providerId);
|
|
982
1141
|
return (
|
|
983
|
-
(table ? getAliasIndex(table).
|
|
984
|
-
dynamicAliasIndexes.get(providerId)?.
|
|
1142
|
+
(table ? getAliasIndex(table).resolve(normalizedModelId) : undefined) ??
|
|
1143
|
+
dynamicAliasIndexes.get(providerId)?.resolve(normalizedModelId)
|
|
985
1144
|
);
|
|
986
1145
|
}
|
|
987
1146
|
|
|
988
1147
|
function getAliasIndex(table: VariantCollapseTable): VariantAliasIndex {
|
|
989
1148
|
const cached: unknown = Reflect.get(table, kAliasIndex);
|
|
990
1149
|
if (cached instanceof VariantAliasIndex) return cached;
|
|
991
|
-
const index =
|
|
992
|
-
for (const family of table.families)
|
|
993
|
-
index.familyIds.add(family.id);
|
|
994
|
-
for (const member of family.members) addVariantAlias(index, member, family.id);
|
|
995
|
-
for (const alias of family.extraAliases ?? []) addVariantAlias(index, alias, family.id);
|
|
996
|
-
}
|
|
1150
|
+
const index = new VariantAliasIndex(table.templates);
|
|
1151
|
+
for (const family of table.families) index.addFamily(family);
|
|
997
1152
|
for (const alias in table.providerAliases) {
|
|
998
1153
|
const target = table.providerAliases[alias];
|
|
999
1154
|
if (target !== undefined && alias !== target) index.providerScoped.set(alias.toLowerCase(), target);
|
|
@@ -1057,8 +1212,8 @@ export function resolveBareVariantSelector(modelId: string): BareVariantAliasHit
|
|
|
1057
1212
|
export function getVariantAliasSources(provider: Provider, modelId: string): readonly string[] {
|
|
1058
1213
|
const providerId = provider.toLowerCase();
|
|
1059
1214
|
const table = reviewedCollapseTable(provider) ?? reviewedCollapseTable(providerId);
|
|
1060
|
-
const staticSources = table ? getAliasIndex(table).
|
|
1061
|
-
const dynamicSources = dynamicAliasIndexes.get(providerId)?.
|
|
1215
|
+
const staticSources = table ? getAliasIndex(table).sourcesOf(modelId) : undefined;
|
|
1216
|
+
const dynamicSources = dynamicAliasIndexes.get(providerId)?.sourcesOf(modelId);
|
|
1062
1217
|
if (!staticSources) return dynamicSources ?? [];
|
|
1063
1218
|
if (!dynamicSources) return staticSources;
|
|
1064
1219
|
return [...new Set([...staticSources, ...dynamicSources])];
|
|
@@ -116,13 +116,14 @@ collapse {
|
|
|
116
116
|
effort-suffix "-minimal" tier="minimal"
|
|
117
117
|
effort-suffix "-max" tier="max" except-bare-prefix="qwen"
|
|
118
118
|
effort-lane-suffix "-fast" "cursor" bare-prefix="cursor-grok"
|
|
119
|
-
effort-family "
|
|
119
|
+
effort-family "cursor" "gpt-5.6-luna"
|
|
120
120
|
routing-variant-suffix "-wm" "openai-codex" "openai-codex-device"
|
|
121
121
|
variant-family "devin" "claude-opus-5" name="Claude Opus 5" { /* reviewed routing */ }
|
|
122
|
+
variant-family "google-antigravity" "gemini-{rev}-flash" name="Gemini {rev} Flash" revision=">=3.6" { /* per-revision template */ }
|
|
122
123
|
provider-alias "devin" "opus" "claude-opus-5"
|
|
123
124
|
}
|
|
124
125
|
```
|
|
125
|
-
`variant-family` declares one reviewed provider-scoped collapsed family: positional provider and logical id, `name=` display name, and a body of `members "a" "b" …` (wire ids in priority order), `route "<tier>" "<wire-id>"` per effort tier (`off` included), and optional `mode`, `efforts`, `default-level`, `default-member`, `retired-members`, `effort-budget "<tier>" <n>`, `requires-effort`, `suppress-when-off`, `no-thinking`, `preserve-absent-effort-routes`, and `extra-aliases`. `provider-alias` maps one provider-scoped selector spelling onto a logical id without making it a family member.
|
|
126
|
+
`variant-family` declares one reviewed provider-scoped collapsed family: positional provider and logical id, `name=` display name, and a body of `members "a" "b" …` (wire ids in priority order), `route "<tier>" "<wire-id>"` per effort tier (`off` included), and optional `mode`, `efforts`, `default-level`, `default-member`, `retired-members`, `effort-budget "<tier>" <n>`, `requires-effort`, `suppress-when-off`, `no-thinking`, `preserve-absent-effort-routes`, and `extra-aliases`. A `{rev}` placeholder in the logical id makes the node a **template**: it is instantiated once per revision found in live ids (`gemini-{rev}-flash` matches `gemini-3.8-flash-low` → family `gemini-3.8-flash`), every wire id in the body and the `name=` carry the same placeholder, and an optional `revision=` constraint (`">=3.6"`) bounds the generations it applies to. A concrete family with the same instantiated id wins over the template. `provider-alias` maps one provider-scoped selector spelling onto a logical id without making it a family member.
|
|
126
127
|
|
|
127
128
|
`thinking-suffix` accepts one non-empty suffix and no properties. `pair-token` declares bounded (possibly infix) tokens naming the thinking sibling of a live bare twin (`sonar-reasoning-pro` beside `sonar-pro`); it drives thinking-pair derivation only — never identity suffix collapse — and negated `no-`/`non-` forms never match. `effort-suffix` additionally requires `tier` with one of the effort values above, and may have `except-bare-prefix`. `routing-variant-suffix` takes one non-empty suffix followed by one or more provider IDs: a wire identifier carrying the suffix on one of those providers is a **routing variant** of its plain identifier — discovery derives base-model metadata from the plain bundled SKU while keeping the suffixed wire identifier for requests; routing variants never participate in effort collapse. `effort-lane-suffix` takes one non-empty lane suffix followed by one or more provider IDs, plus an optional `bare-prefix` gate: on a declared provider, an identifier ending in the lane suffix collapses the effort suffix wedged before the lane token while keeping the lane on the logical id. `effort-family` takes a provider, the canonical logical id, and zero or more exact aliases that fold onto it.
|
|
128
129
|
|
|
@@ -24,11 +24,6 @@ collapse {
|
|
|
24
24
|
effort-family "cursor" "gpt-5.6-luna"
|
|
25
25
|
effort-family "cursor" "gpt-5.6-sol"
|
|
26
26
|
effort-family "cursor" "gpt-5.6-terra"
|
|
27
|
-
// Cloud Code Assist advertises `-tiered` aliases beside effort-suffixed
|
|
28
|
-
// Gemini Flash siblings. Fold each alias into the canonical routed family;
|
|
29
|
-
// its minimal/off tier is served by the `-low` SKU.
|
|
30
|
-
effort-family "google-antigravity" "gemini-3.6-flash" "gemini-3.6-flash-tiered"
|
|
31
|
-
effort-family "google-antigravity" "gemini-3.7-flash" "gemini-3.7-flash-tiered"
|
|
32
27
|
// `-fast` is a parallel SKU lane. It stays in the logical identifier while
|
|
33
28
|
// the effort token immediately before it selects the wire sibling.
|
|
34
29
|
effort-lane-suffix "-fast" "cursor"
|
|
@@ -36,22 +31,17 @@ collapse {
|
|
|
36
31
|
// Reviewed per-provider variant families: logical models served as
|
|
37
32
|
// per-effort/thinking sibling wire ids, with explicit routing, ladders,
|
|
38
33
|
// budgets, and wire facts. Ported verbatim from the legacy hand tables.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
members "gemini-3.7-flash-low" "gemini-3.7-flash-medium" "gemini-3.7-flash-high" "gemini-3.7-flash-tiered"
|
|
51
|
-
route "minimal" "gemini-3.7-flash-low"
|
|
52
|
-
route "low" "gemini-3.7-flash-low"
|
|
53
|
-
route "medium" "gemini-3.7-flash-medium"
|
|
54
|
-
route "high" "gemini-3.7-flash-high"
|
|
34
|
+
//
|
|
35
|
+
// Cloud Code Assist serves every Gemini Flash generation from 3.6 on as
|
|
36
|
+
// `-low`/`-medium`/`-high` thinking-level SKUs plus a `-tiered` alias. One
|
|
37
|
+
// template per provider covers each revision discovery advertises; the
|
|
38
|
+
// minimal tier is served by the `-low` SKU (Gemini 3.7+ rejects MINIMAL).
|
|
39
|
+
variant-family "google-antigravity" "gemini-{rev}-flash" name="Gemini {rev} Flash" revision=">=3.6" {
|
|
40
|
+
members "gemini-{rev}-flash-low" "gemini-{rev}-flash-medium" "gemini-{rev}-flash-high" "gemini-{rev}-flash-tiered"
|
|
41
|
+
route "minimal" "gemini-{rev}-flash-low"
|
|
42
|
+
route "low" "gemini-{rev}-flash-low"
|
|
43
|
+
route "medium" "gemini-{rev}-flash-medium"
|
|
44
|
+
route "high" "gemini-{rev}-flash-high"
|
|
55
45
|
mode "google-level"
|
|
56
46
|
efforts "minimal" "low" "medium" "high"
|
|
57
47
|
requires-effort #true
|
|
@@ -143,22 +133,12 @@ collapse {
|
|
|
143
133
|
efforts "minimal" "low" "medium" "high"
|
|
144
134
|
preserve-absent-effort-routes #true
|
|
145
135
|
}
|
|
146
|
-
variant-family "google-gemini-cli" "gemini-
|
|
147
|
-
members "gemini-
|
|
148
|
-
route "minimal" "gemini-
|
|
149
|
-
route "low" "gemini-
|
|
150
|
-
route "medium" "gemini-
|
|
151
|
-
route "high" "gemini-
|
|
152
|
-
mode "google-level"
|
|
153
|
-
efforts "minimal" "low" "medium" "high"
|
|
154
|
-
requires-effort #true
|
|
155
|
-
}
|
|
156
|
-
variant-family "google-gemini-cli" "gemini-3.7-flash" name="Gemini 3.7 Flash" {
|
|
157
|
-
members "gemini-3.7-flash-low" "gemini-3.7-flash-medium" "gemini-3.7-flash-high" "gemini-3.7-flash-tiered"
|
|
158
|
-
route "minimal" "gemini-3.7-flash-low"
|
|
159
|
-
route "low" "gemini-3.7-flash-low"
|
|
160
|
-
route "medium" "gemini-3.7-flash-medium"
|
|
161
|
-
route "high" "gemini-3.7-flash-high"
|
|
136
|
+
variant-family "google-gemini-cli" "gemini-{rev}-flash" name="Gemini {rev} Flash" revision=">=3.6" {
|
|
137
|
+
members "gemini-{rev}-flash-low" "gemini-{rev}-flash-medium" "gemini-{rev}-flash-high" "gemini-{rev}-flash-tiered"
|
|
138
|
+
route "minimal" "gemini-{rev}-flash-low"
|
|
139
|
+
route "low" "gemini-{rev}-flash-low"
|
|
140
|
+
route "medium" "gemini-{rev}-flash-medium"
|
|
141
|
+
route "high" "gemini-{rev}-flash-high"
|
|
162
142
|
mode "google-level"
|
|
163
143
|
efforts "minimal" "low" "medium" "high"
|
|
164
144
|
requires-effort #true
|