@oh-my-pi/pi-catalog 15.11.7 → 15.11.8

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,12 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [15.11.8] - 2026-06-12
6
+
7
+ ### Fixed
8
+
9
+ - Fixed Antigravity `gemini-3.1-pro --thinking high` failing with `Cloud Code Assist API error (400): Request contains an invalid argument.` — the upstream `gemini-3.1-pro-high` deployment rejects every `streamGenerateContent` request on both CCA endpoints while discovery still advertises it. High effort now routes to `gemini-pro-agent` (the same "Gemini 3.1 Pro (High)" model, verified accepting the identical request body), and the model-cache fingerprint version was bumped (`merge-v2` → `merge-v3`) so existing fresh caches refetch discovery and pick up the corrected routing immediately.
10
+
5
11
  ## [15.11.7] - 2026-06-12
6
12
  ### Added
7
13
 
@@ -21,6 +21,15 @@ export interface EffortVariantFamily {
21
21
  * when it equals the logical id).
22
22
  */
23
23
  members: readonly string[];
24
+ /**
25
+ * Wire ids upstream no longer serves (e.g. a deployment killed while
26
+ * discovery still advertises it). Fresh collapsing never routes to them,
27
+ * and stale collapsed snapshots (bundled catalog, cache rows,
28
+ * previous-generation fallbacks) get routing/`requestModelId` entries that
29
+ * target them re-pointed through `routing`. Keep retired ids in `members`
30
+ * so the raw upstream spec is still consumed and aliased.
31
+ */
32
+ retiredMembers?: readonly string[];
24
33
  /**
25
34
  * Per-effort upstream wire id; `"off"` applies when thinking is disabled.
26
35
  * Entries whose target member is absent from the input are dropped — those
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-catalog",
4
- "version": "15.11.7",
4
+ "version": "15.11.8",
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": "Can Boluk",
@@ -34,11 +34,11 @@
34
34
  },
35
35
  "dependencies": {
36
36
  "@bufbuild/protobuf": "^2.12.0",
37
- "@oh-my-pi/pi-utils": "15.11.7",
37
+ "@oh-my-pi/pi-utils": "15.11.8",
38
38
  "zod": "4.4.3"
39
39
  },
40
40
  "devDependencies": {
41
- "@oh-my-pi/pi-ai": "15.11.7",
41
+ "@oh-my-pi/pi-ai": "15.11.8",
42
42
  "@types/bun": "^1.3.14"
43
43
  },
44
44
  "engines": {
@@ -301,7 +301,7 @@ function retainModelIds<TApi extends Api>(
301
301
  * arms calling `resolveProviderModels` with the same `staticModels` array)
302
302
  * skip the JSON+hash work after the first call.
303
303
  */
304
- const MODEL_CACHE_FINGERPRINT_VERSION = "merge-v2";
304
+ const MODEL_CACHE_FINGERPRINT_VERSION = "merge-v3";
305
305
  const kStaticFingerprint = Symbol("model-manager.staticFingerprint");
306
306
  type ModelArrayWithFingerprint = readonly Model<Api>[] & { [kStaticFingerprint]?: string };
307
307
  function fingerprintStatic<TApi extends Api>(
package/src/models.json CHANGED
@@ -16574,7 +16574,7 @@
16574
16574
  "effortRouting": {
16575
16575
  "off": "gemini-3.1-pro-low",
16576
16576
  "low": "gemini-3.1-pro-low",
16577
- "high": "gemini-3.1-pro-high"
16577
+ "high": "gemini-pro-agent"
16578
16578
  },
16579
16579
  "suppressWhenOff": true
16580
16580
  },
@@ -54417,7 +54417,7 @@
54417
54417
  "cacheRead": 0.135,
54418
54418
  "cacheWrite": 0
54419
54419
  },
54420
- "contextWindow": 163840,
54420
+ "contextWindow": 131072,
54421
54421
  "maxTokens": 16384,
54422
54422
  "thinking": {
54423
54423
  "mode": "effort",
@@ -57,6 +57,15 @@ export interface EffortVariantFamily {
57
57
  * when it equals the logical id).
58
58
  */
59
59
  members: readonly string[];
60
+ /**
61
+ * Wire ids upstream no longer serves (e.g. a deployment killed while
62
+ * discovery still advertises it). Fresh collapsing never routes to them,
63
+ * and stale collapsed snapshots (bundled catalog, cache rows,
64
+ * previous-generation fallbacks) get routing/`requestModelId` entries that
65
+ * target them re-pointed through `routing`. Keep retired ids in `members`
66
+ * so the raw upstream spec is still consumed and aliased.
67
+ */
68
+ retiredMembers?: readonly string[];
60
69
  /**
61
70
  * Per-effort upstream wire id; `"off"` applies when thinking is disabled.
62
71
  * Entries whose target member is absent from the input are dropped — those
@@ -123,12 +132,18 @@ export const ANTIGRAVITY_VARIANT_COLLAPSE_TABLE: VariantCollapseTable = {
123
132
  {
124
133
  id: "gemini-3.1-pro",
125
134
  name: "Gemini 3.1 Pro",
126
- // `gemini-pro-agent` is consumed as a member but unused in routing.
127
- members: ["gemini-3.1-pro-low", "gemini-3.1-pro-high", "gemini-pro-agent"],
135
+ // High routes to `gemini-pro-agent` the upstream `gemini-3.1-pro-high`
136
+ // deployment returns INVALID_ARGUMENT on every streamGenerateContent
137
+ // request (both CCA endpoints) while discovery still lists it;
138
+ // `gemini-pro-agent` is the same model ("Gemini 3.1 Pro (High)", same
139
+ // thinking budget/caps) and accepts the identical request body.
140
+ // `gemini-3.1-pro-high` stays a member so the dead raw id is consumed.
141
+ members: ["gemini-3.1-pro-low", "gemini-pro-agent", "gemini-3.1-pro-high"],
142
+ retiredMembers: ["gemini-3.1-pro-high"],
128
143
  routing: {
129
144
  off: "gemini-3.1-pro-low",
130
145
  [Effort.Low]: "gemini-3.1-pro-low",
131
- [Effort.High]: "gemini-3.1-pro-high",
146
+ [Effort.High]: "gemini-pro-agent",
132
147
  },
133
148
  thinking: { mode: "google-level", efforts: GEMINI_3_PRO_FAMILY_EFFORTS },
134
149
  suppressWhenOff: true,
@@ -282,6 +297,69 @@ export function isVariantCollapsedSpec(spec: VariantSpecLike): boolean {
282
297
  return table !== undefined && getAliasIndex(table).familyIds.has(spec.id);
283
298
  }
284
299
 
300
+ /**
301
+ * Re-point a stale collapsed spec whose `requestModelId` or routing still
302
+ * targets a retired wire id. Collapsed snapshots (bundled catalog, cache
303
+ * rows, previous-generation fallbacks) pass through collapsing untouched, so
304
+ * a hand-table routing fix would otherwise never reach them. Only retired
305
+ * targets are rewritten — presence-filtered routing decisions from live
306
+ * discovery stay authoritative for everything else. Per retired entry the
307
+ * table's route for that effort wins, then the off/first-live-member wire id,
308
+ * then the route is dropped (falls back to `requestModelId ?? id`). Returns
309
+ * `spec` by reference when nothing targets a retired id.
310
+ */
311
+ function reconcileRetiredRouting<TSpec extends VariantSpecLike>(
312
+ spec: TSpec,
313
+ family: EffortVariantFamily,
314
+ retired: ReadonlySet<string>,
315
+ ): TSpec {
316
+ const routing = spec.thinking?.effortRouting;
317
+ const requestRetired = spec.requestModelId !== undefined && retired.has(spec.requestModelId);
318
+ let routingRetired = false;
319
+ if (routing !== undefined) {
320
+ for (const key in routing) {
321
+ const target = routing[key as Effort | "off"];
322
+ if (target !== undefined && retired.has(target)) {
323
+ routingRetired = true;
324
+ break;
325
+ }
326
+ }
327
+ }
328
+ if (!requestRetired && !routingRetired) return spec;
329
+
330
+ const offTarget = family.routing.off;
331
+ const fallbackWireId =
332
+ offTarget !== undefined && !retired.has(offTarget) ? offTarget : family.members.find(id => !retired.has(id));
333
+ const next: TSpec = { ...spec };
334
+ if (routingRetired && routing !== undefined) {
335
+ const nextRouting: Partial<Record<Effort | "off", string>> = {};
336
+ for (const key in routing) {
337
+ const effortKey = key as Effort | "off";
338
+ const target = routing[effortKey];
339
+ if (target === undefined) continue;
340
+ if (!retired.has(target)) {
341
+ nextRouting[effortKey] = target;
342
+ continue;
343
+ }
344
+ const tableTarget = family.routing[effortKey];
345
+ if (tableTarget !== undefined && !retired.has(tableTarget)) {
346
+ nextRouting[effortKey] = tableTarget;
347
+ } else if (fallbackWireId !== undefined) {
348
+ nextRouting[effortKey] = fallbackWireId;
349
+ }
350
+ }
351
+ next.thinking = { ...(spec.thinking as ThinkingConfig), effortRouting: nextRouting };
352
+ }
353
+ if (requestRetired) {
354
+ if (fallbackWireId !== undefined && fallbackWireId !== spec.id) {
355
+ next.requestModelId = fallbackWireId;
356
+ } else {
357
+ delete next.requestModelId;
358
+ }
359
+ }
360
+ return next;
361
+ }
362
+
285
363
  /**
286
364
  * Collapse every family in `table` found in `specs`. Non-member specs pass
287
365
  * through verbatim (by reference), order preserved; the collapsed spec
@@ -302,13 +380,26 @@ export function collapseEffortVariants<TSpec extends VariantSpecLike>(
302
380
  const familyIdBySpecId = new Map<string, string>();
303
381
 
304
382
  for (const family of table.families) {
383
+ const retired =
384
+ family.retiredMembers !== undefined && family.retiredMembers.length > 0
385
+ ? new Set(family.retiredMembers)
386
+ : undefined;
305
387
  const existing = byId.get(family.id);
306
388
  const existingCollapsed =
307
389
  existing !== undefined &&
308
390
  (existing.requestModelId !== undefined || existing.thinking?.effortRouting !== undefined);
391
+ const reconciled =
392
+ existing !== undefined && existingCollapsed && retired !== undefined
393
+ ? reconcileRetiredRouting(existing, family, retired)
394
+ : existing;
309
395
  const rawPresent = family.members.filter(id => byId.has(id) && !(id === family.id && existingCollapsed));
310
396
  if (rawPresent.length === 0) {
311
397
  // Inert (no members) or already collapsed (pass-through) — idempotence.
398
+ // A stale collapsed entry still gets retired routing re-pointed.
399
+ if (reconciled !== undefined && reconciled !== existing) {
400
+ familyIdBySpecId.set(family.id, family.id);
401
+ replacement.set(family.id, reconciled);
402
+ }
312
403
  continue;
313
404
  }
314
405
 
@@ -317,8 +408,8 @@ export function collapseEffortVariants<TSpec extends VariantSpecLike>(
317
408
 
318
409
  if (existingCollapsed) {
319
410
  // Mixed input: the collapsed entry (live truth) wins; stale raw
320
- // members are deduped away.
321
- replacement.set(family.id, existing);
411
+ // members are deduped away. Retired targets are re-pointed first.
412
+ replacement.set(family.id, reconciled as TSpec);
322
413
  continue;
323
414
  }
324
415
 
@@ -329,7 +420,7 @@ export function collapseEffortVariants<TSpec extends VariantSpecLike>(
329
420
  let hasEffortRoute = false;
330
421
  for (const effortKey in family.routing) {
331
422
  const target = family.routing[effortKey as Effort | "off"];
332
- if (target !== undefined && presentSet.has(target)) {
423
+ if (target !== undefined && presentSet.has(target) && !retired?.has(target)) {
333
424
  routing[effortKey as Effort | "off"] = target;
334
425
  hasRouting = true;
335
426
  if (effortKey !== "off") hasEffortRoute = true;
@@ -356,12 +447,14 @@ export function collapseEffortVariants<TSpec extends VariantSpecLike>(
356
447
  contextWindow: Math.max(...memberSpecs.map(spec => spec.contextWindow)),
357
448
  maxTokens: Math.max(...memberSpecs.map(spec => spec.maxTokens)),
358
449
  };
359
- // The default wire id is the priority member; omit when it equals the
360
- // logical id (bare/thinking pairs) — `resolveWireModelId` falls back.
361
- if (rawPresent[0] === family.id) {
450
+ // The default wire id is the highest-priority live member; omit when it
451
+ // equals the logical id (bare/thinking pairs) — `resolveWireModelId`
452
+ // falls back. Retired members never become the default.
453
+ const defaultWireId = rawPresent.find(id => !retired?.has(id)) ?? rawPresent[0];
454
+ if (defaultWireId === family.id) {
362
455
  delete collapsed.requestModelId;
363
456
  } else {
364
- collapsed.requestModelId = rawPresent[0] as string;
457
+ collapsed.requestModelId = defaultWireId as string;
365
458
  }
366
459
  if (reasoning) {
367
460
  collapsed.thinking = thinking;