@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 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.3",
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.3",
39
- "@oh-my-pi/pi-utils": "18.1.3"
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.3",
43
+ "@oh-my-pi/pi-ai": "18.1.4",
44
44
  "@types/bun": "^1.3.14"
45
45
  },
46
46
  "engines": {
@@ -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 { CompiledVariantFamily } from "./types";
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<string, { families: EffortVariantFamily[]; providerAliases?: Record<string, string> }> = {};
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
- tables[compiled.provider].families.push(compiledFamily(compiled));
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 (claimed) {
411
- const forward = claimed.forward;
412
- if (
413
- forward.has(spec.id.toLowerCase()) ||
414
- forward.has(baseId.toLowerCase()) ||
415
- claimed.familyIds.has(spec.id) ||
416
- claimed.familyIds.has(baseId)
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).familyIds.has(spec.id);
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
- for (const family of table.families) {
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
- * `forward` so bare-id and reverse lookups never see it.
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
- const dynamicAliasIndexes = new Map<string, VariantAliasIndex>();
935
- const kAliasIndex = Symbol("compat-collapse.aliasIndex");
1053
+ constructor(templates: readonly VariantFamilyTemplate[] = []) {
1054
+ this.#templates = templates;
1055
+ }
936
1056
 
937
- function createAliasIndex(): VariantAliasIndex {
938
- return new VariantAliasIndex();
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
- function addVariantAlias(index: VariantAliasIndex, from: string, to: string): boolean {
942
- if (from === to || index.forward.has(from.toLowerCase())) return false;
943
- index.forward.set(from.toLowerCase(), to);
944
- const sources = index.reverse.get(to);
945
- if (sources) {
946
- sources.push(from);
947
- } else {
948
- index.reverse.set(to, [from]);
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 ??= createAliasIndex();
968
- registered = addVariantAlias(index, source, spec.id) || 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 ??= createAliasIndex();
972
- registered = addVariantAlias(index, spec.requestModelId, spec.id) || registered;
1130
+ index ??= new VariantAliasIndex();
1131
+ registered = index.add(spec.requestModelId, spec.id) || registered;
973
1132
  }
974
- if (registered) index?.familyIds.add(spec.id);
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).forward.get(normalizedModelId) : undefined) ??
984
- dynamicAliasIndexes.get(providerId)?.forward.get(normalizedModelId)
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 = createAliasIndex();
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).reverse.get(modelId) : undefined;
1061
- const dynamicSources = dynamicAliasIndexes.get(providerId)?.reverse.get(modelId);
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 "google-antigravity" "gemini-3.7-flash" "gemini-3.7-flash-tiered"
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
 
@@ -18,7 +18,7 @@ provider "google-antigravity" {
18
18
  thinking-mode "budget"
19
19
  thinking-suppress-when-off #true
20
20
  }
21
- revision ">=3.6 <3.8" {
21
+ revision ">=3.6" {
22
22
  thinking-mode "google-level"
23
23
  thinking-requires-effort #true
24
24
  }
@@ -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
- variant-family "google-antigravity" "gemini-3.6-flash" name="Gemini 3.6 Flash" {
40
- members "gemini-3.6-flash-low" "gemini-3.6-flash-medium" "gemini-3.6-flash-high" "gemini-3.6-flash-tiered"
41
- route "minimal" "gemini-3.6-flash-low"
42
- route "low" "gemini-3.6-flash-low"
43
- route "medium" "gemini-3.6-flash-medium"
44
- route "high" "gemini-3.6-flash-high"
45
- mode "google-level"
46
- efforts "minimal" "low" "medium" "high"
47
- requires-effort #true
48
- }
49
- variant-family "google-antigravity" "gemini-3.7-flash" name="Gemini 3.7 Flash" {
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-3.6-flash" name="Gemini 3.6 Flash" {
147
- members "gemini-3.6-flash-low" "gemini-3.6-flash-medium" "gemini-3.6-flash-high" "gemini-3.6-flash-tiered"
148
- route "minimal" "gemini-3.6-flash-low"
149
- route "low" "gemini-3.6-flash-low"
150
- route "medium" "gemini-3.6-flash-medium"
151
- route "high" "gemini-3.6-flash-high"
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