@molecule/api-resource-ai-models 1.0.2 → 1.1.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/README.md CHANGED
@@ -3,7 +3,7 @@ AUTO-GENERATED — DO NOT EDIT THIS FILE.
3
3
  Generated by `mlcl sync-docs` from the package's src/index.ts JSDoc + mlcl/registry.json.
4
4
  Edits here are overwritten on the next commit (molecule's pre-commit hook regenerates).
5
5
  To change this document, edit the module-level JSDoc in src/index.ts.
6
- Generated: 2026-08-06T19:42:52.531Z
6
+ Generated: 2026-08-12T22:38:26.212Z
7
7
  -->
8
8
 
9
9
  # @molecule/api-resource-ai-models
@@ -310,6 +310,35 @@ interface ModelDefinition {
310
310
  * or its past usage silently meters as free. Omit entirely for active models.
311
311
  */
312
312
  disabled?: boolean
313
+ /**
314
+ * The id of the NEWER-generation model that replaces this one, set on the
315
+ * OLDER entry and naming its successor (e.g. `qwen3.7-max` carries
316
+ * `supersededBy: 'qwen3.8-max'`).
317
+ *
318
+ * A superseded model is NOT selectable — like {@link disabled} it is excluded
319
+ * from `MODEL_IDS`, `getAvailableModels()`, the `GET /ai/models` listing and
320
+ * the client-side picker partitions, so a user is only ever offered the
321
+ * newest generation of a family. It differs from `disabled` in *why* and in
322
+ * what it points at: a disabled model is one the provider retired (it can no
323
+ * longer answer), whereas a superseded model is usually still served upstream
324
+ * and simply has nothing to offer over its successor — and the successor id
325
+ * is a real migration target, so a saved selection can resolve forward
326
+ * (`resolveSelectableModelId`) instead of silently falling back to the
327
+ * platform default.
328
+ *
329
+ * `getModel(id)` STILL returns a superseded entry — historical usage must stay
330
+ * priceable, so NEVER delete one.
331
+ *
332
+ * Set this ONLY when the successor covers the same TIER. A cheaper or
333
+ * specialist tier with no newer equivalent is NOT superseded merely because
334
+ * its version number is lower (Google's sole pro tier `gemini-3.1-pro-preview`
335
+ * alongside the newer flash flagship; `qwen3-coder-plus`; `kimi-k2.7-code`) —
336
+ * hiding those would leave a provider with no cheap option. Mark those
337
+ * {@link deprecatedAt} at most. The invariant is enforced by
338
+ * `__tests__/lookup.test.ts`: two selectable models of the same family at
339
+ * different versions fail unless listed there as a documented exception.
340
+ */
341
+ supersededBy?: string
313
342
  }
314
343
  ```
315
344
 
@@ -424,7 +453,8 @@ function effectiveModelRegion(modelDef: ModelDefinition | undefined, requested?:
424
453
  Get models that are currently usable — filtered to only providers that are available.
425
454
 
426
455
  The caller passes in which provider IDs are active (i.e. have a bond wired).
427
- `disabled` models are excluded — they are never offered for selection.
456
+ Models that are not {@link isSelectableModel} — `disabled` or superseded by a
457
+ newer generation — are excluded; they are never offered for selection.
428
458
 
429
459
  ```typescript
430
460
  function getAvailableModels(
@@ -434,16 +464,16 @@ function getAvailableModels(
434
464
 
435
465
  - `availableProviders` — Set or array of provider IDs that have active bonds.
436
466
 
437
- **Returns:** Non-disabled models whose provider is in the available set.
467
+ **Returns:** Selectable models whose provider is in the available set.
438
468
 
439
469
  #### `getModel(id)`
440
470
 
441
471
  Look up a model definition by ID.
442
472
 
443
- Returns `disabled` models too: a saved selection or a historical usage row
444
- may reference a since-retired model, and it must stay priceable. Use
445
- {@link MODEL_IDS} / {@link getAvailableModels} (which exclude disabled
446
- models) to decide what is _selectable_.
473
+ Returns `disabled` and `supersededBy` models too: a saved selection or a
474
+ historical usage row may reference a since-retired or since-superseded model,
475
+ and it must stay priceable. Use {@link MODEL_IDS} / {@link getAvailableModels}
476
+ (or {@link isSelectableModel}) to decide what is _selectable_.
447
477
 
448
478
  ```typescript
449
479
  function getModel(id: string): ModelDefinition | undefined
@@ -465,6 +495,21 @@ function getModelsByProvider(provider: AIProviderID): readonly ModelDefinition[]
465
495
 
466
496
  **Returns:** Array of model definitions for that provider.
467
497
 
498
+ #### `isSelectableModel(model)`
499
+
500
+ Whether a model may be offered for selection: not `disabled` (retired
501
+ upstream) and not `supersededBy` a newer generation of its own family. Both
502
+ kinds stay in the catalog for pricing — this predicate is the single place
503
+ that decides _exposure_, so every listing/validation surface agrees.
504
+
505
+ ```typescript
506
+ function isSelectableModel(model: Pick<ModelDefinition, 'disabled' | 'supersededBy'>): boolean
507
+ ```
508
+
509
+ - `model` — The model definition (or the two flags from one).
510
+
511
+ **Returns:** True when the model may be listed and chosen.
512
+
468
513
  #### `list(_req, res)`
469
514
 
470
515
  Returns models whose `provider` has a bond registered under the `'ai'`
@@ -518,6 +563,22 @@ function priceMultiplierAt(modelDef: ModelDefinition | undefined, at: Date): num
518
563
 
519
564
  **Returns:** The multiplier (`1` outside peak windows or when none are declared).
520
565
 
566
+ #### `resolveSelectableModelId(id)`
567
+
568
+ Resolve a model id FORWARD to the selectable model that replaces it, following
569
+ the {@link ModelDefinition.supersededBy} chain (a saved `qwen3.7-max` →
570
+ `qwen3.8-max`). Lets a persisted selection keep the user's intent — the same
571
+ tier from the same provider — instead of falling back to the platform default
572
+ once the older generation stops being offered.
573
+
574
+ ```typescript
575
+ function resolveSelectableModelId(id: string): string | undefined
576
+ ```
577
+
578
+ - `id` — The persisted model id.
579
+
580
+ **Returns:** The selectable successor's id, the id itself when it is already selectable, or `undefined` for an unknown or `disabled` model (nothing to forward to).
581
+
521
582
  ### Constants
522
583
 
523
584
  #### `MODEL_IDS`
@@ -525,8 +586,9 @@ function priceMultiplierAt(modelDef: ModelDefinition | undefined, at: Date): num
525
586
  Set of _selectable_ model IDs for fast validation.
526
587
 
527
588
  Excludes `disabled` models so a retired model (e.g. `grok-code-fast-1`) can
528
- never be chosen for a new chat, while {@link getModel} still resolves it for
529
- historical pricing.
589
+ never be chosen for a new chat, and `supersededBy` models so an older
590
+ generation of a family (e.g. `qwen3.7-max` next to `qwen3.8-max`) is never
591
+ offered — while {@link getModel} still resolves both for historical pricing.
530
592
 
531
593
  ```typescript
532
594
  const MODEL_IDS: ReadonlySet<string>
@@ -554,6 +616,18 @@ Effort is each model's OWN native value — there is no abstract scale (see
554
616
  control) carries `thinkingConfigurable: false` and OMITS both fields —
555
617
  there is nothing to tune.
556
618
 
619
+ ONE GENERATION PER FAMILY. When a provider ships a newer generation of a
620
+ model line, the older entry gets `supersededBy: '<newer id>'` and stops being
621
+ offered — the picker never shows both `qwen3.7-max` and `qwen3.8-max`. The
622
+ entry is NEVER deleted: `getModel()` still resolves it so saved selections and
623
+ historical usage stay priceable, and a persisted id resolves forward to the
624
+ successor. Supersede only within the same TIER: a cheaper or specialist model
625
+ with no newer equivalent (`gemini-3.1-pro-preview`, `qwen3-coder-plus`,
626
+ `kimi-k2.7-code`, `grok-build-0.1`) keeps at most `deprecatedAt`, so every
627
+ provider keeps a real choice. `__tests__/lookup.test.ts` fails on any two
628
+ selectable models of one family at different versions that aren't a
629
+ documented exception.
630
+
557
631
  Sources (verified 2026-07-28; OpenAI re-verified 2026-07-31 after the
558
632
  2026-07-30 GPT-5.6 repricing — cross-check prices against models.dev with
559
633
  `npm run check:model-freshness` from the workspace root):
@@ -585,12 +659,13 @@ Sources (verified 2026-07-28; OpenAI re-verified 2026-07-31 after the
585
659
  Pro/Flash pricing; legacy deepseek-chat/-reasoner ids fully retired
586
660
  2026-07-24 — never in this catalog; the announced peak-hour 2× surcharge is
587
661
  still NOT active as of 2026-07-28, see the entries)
588
- - Moonshot: https://platform.kimi.ai/docs/models (kimi-k3 flagship 2026-07-16
662
+ - Moonshot: https://platform.kimi.ai/docs/models + DeepInfra's model API for
663
+ the US re-host (kimi-k3 flagship 2026-07-16
589
664
  — 2.8T MoE, 1M ctx, $3/$15 — NOT added: thinking is forced-on with
590
665
  reasoning_content that must be replayed through tool loops, the same
591
- constraint that keeps kimi-k2.7-code out; add BOTH once the moonshot bond
592
- supports preserved thinking + reasoning_effort low|high|max. kimi-k2.6
593
- remains the newest model the bond can run correctly.)
666
+ constraint that kept kimi-k2.7-code out. BOTH are now in the catalog: the
667
+ moonshot bond gained preserved thinking (reasoning replayed through tool
668
+ loops), so kimi-k3 is the Moonshot pick.)
594
669
  - MiniMax: https://platform.minimax.io/docs/guides/pricing-paygo (unchanged;
595
670
  minimax-m3 $0.30/$1.20 is a "permanent 50% off" list rate)
596
671
  - Alibaba: https://www.alibabacloud.com/help/en/model-studio/deep-thinking
@@ -2,10 +2,11 @@
2
2
  * `GET /ai/models` — returns the catalog of available AI models.
3
3
  *
4
4
  * Filters the central `MODELS` list to only those whose provider is currently
5
- * bonded under the `'ai'` category AND are not `disabled` (a retired model is
6
- * never listed for selection, though `getModel` still prices it). No further
7
- * projection is applied — every `ModelDefinition` field is fine to expose to
8
- * authenticated clients today.
5
+ * bonded under the `'ai'` category AND are selectable — neither `disabled` (a
6
+ * model the provider retired) nor superseded by a newer generation of the same
7
+ * family, so the picker offers exactly one generation per family. Both kinds
8
+ * stay priceable via `getModel`. No further projection is applied — every
9
+ * `ModelDefinition` field is fine to expose to authenticated clients today.
9
10
  *
10
11
  * Secure-by-default: this handler enforces authentication IN the handler
11
12
  * (`res.locals.session.userId`) and fails closed with `401` for an
@@ -1 +1 @@
1
- {"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../src/handlers/list.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAK/E;;;;;;;;;;;GAWG;AACH,wBAAsB,IAAI,CAAC,IAAI,EAAE,eAAe,EAAE,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CActF"}
1
+ {"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../src/handlers/list.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAM/E;;;;;;;;;;;GAWG;AACH,wBAAsB,IAAI,CAAC,IAAI,EAAE,eAAe,EAAE,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CActF"}
@@ -2,10 +2,11 @@
2
2
  * `GET /ai/models` — returns the catalog of available AI models.
3
3
  *
4
4
  * Filters the central `MODELS` list to only those whose provider is currently
5
- * bonded under the `'ai'` category AND are not `disabled` (a retired model is
6
- * never listed for selection, though `getModel` still prices it). No further
7
- * projection is applied — every `ModelDefinition` field is fine to expose to
8
- * authenticated clients today.
5
+ * bonded under the `'ai'` category AND are selectable — neither `disabled` (a
6
+ * model the provider retired) nor superseded by a newer generation of the same
7
+ * family, so the picker offers exactly one generation per family. Both kinds
8
+ * stay priceable via `getModel`. No further projection is applied — every
9
+ * `ModelDefinition` field is fine to expose to authenticated clients today.
9
10
  *
10
11
  * Secure-by-default: this handler enforces authentication IN the handler
11
12
  * (`res.locals.session.userId`) and fails closed with `401` for an
@@ -19,6 +20,7 @@
19
20
  */
20
21
  import { getAll } from '@molecule/api-bond';
21
22
  import { t } from '@molecule/api-i18n';
23
+ import { isSelectableModel } from '../lookup.js';
22
24
  import { MODELS } from '../models.js';
23
25
  /**
24
26
  * Returns models whose `provider` has a bond registered under the `'ai'`
@@ -42,7 +44,7 @@ export async function list(_req, res) {
42
44
  return;
43
45
  }
44
46
  const bondedProviders = new Set(getAll('ai').keys());
45
- const models = MODELS.filter((m) => bondedProviders.has(m.provider) && !m.disabled);
47
+ const models = MODELS.filter((m) => bondedProviders.has(m.provider) && isSelectableModel(m));
46
48
  const response = { models: [...models] };
47
49
  res.json(response);
48
50
  }
package/dist/lookup.d.ts CHANGED
@@ -4,21 +4,45 @@
4
4
  * @module
5
5
  */
6
6
  import type { AIProviderID, ModelDefinition } from './types.js';
7
+ /**
8
+ * Whether a model may be offered for selection: not `disabled` (retired
9
+ * upstream) and not `supersededBy` a newer generation of its own family. Both
10
+ * kinds stay in the catalog for pricing — this predicate is the single place
11
+ * that decides *exposure*, so every listing/validation surface agrees.
12
+ *
13
+ * @param model - The model definition (or the two flags from one).
14
+ * @returns True when the model may be listed and chosen.
15
+ */
16
+ export declare function isSelectableModel(model: Pick<ModelDefinition, 'disabled' | 'supersededBy'>): boolean;
17
+ /**
18
+ * Resolve a model id FORWARD to the selectable model that replaces it, following
19
+ * the {@link ModelDefinition.supersededBy} chain (a saved `qwen3.7-max` →
20
+ * `qwen3.8-max`). Lets a persisted selection keep the user's intent — the same
21
+ * tier from the same provider — instead of falling back to the platform default
22
+ * once the older generation stops being offered.
23
+ *
24
+ * @param id - The persisted model id.
25
+ * @returns The selectable successor's id, the id itself when it is already
26
+ * selectable, or `undefined` for an unknown or `disabled` model (nothing to
27
+ * forward to).
28
+ */
29
+ export declare function resolveSelectableModelId(id: string): string | undefined;
7
30
  /**
8
31
  * Set of *selectable* model IDs for fast validation.
9
32
  *
10
33
  * Excludes `disabled` models so a retired model (e.g. `grok-code-fast-1`) can
11
- * never be chosen for a new chat, while {@link getModel} still resolves it for
12
- * historical pricing.
34
+ * never be chosen for a new chat, and `supersededBy` models so an older
35
+ * generation of a family (e.g. `qwen3.7-max` next to `qwen3.8-max`) is never
36
+ * offered — while {@link getModel} still resolves both for historical pricing.
13
37
  */
14
38
  export declare const MODEL_IDS: ReadonlySet<string>;
15
39
  /**
16
40
  * Look up a model definition by ID.
17
41
  *
18
- * Returns `disabled` models too: a saved selection or a historical usage row
19
- * may reference a since-retired model, and it must stay priceable. Use
20
- * {@link MODEL_IDS} / {@link getAvailableModels} (which exclude disabled
21
- * models) to decide what is *selectable*.
42
+ * Returns `disabled` and `supersededBy` models too: a saved selection or a
43
+ * historical usage row may reference a since-retired or since-superseded model,
44
+ * and it must stay priceable. Use {@link MODEL_IDS} / {@link getAvailableModels}
45
+ * (or {@link isSelectableModel}) to decide what is *selectable*.
22
46
  *
23
47
  * @param id - The API model ID.
24
48
  * @returns The model definition, or `undefined` if not found.
@@ -35,10 +59,11 @@ export declare function getModelsByProvider(provider: AIProviderID): readonly Mo
35
59
  * Get models that are currently usable — filtered to only providers that are available.
36
60
  *
37
61
  * The caller passes in which provider IDs are active (i.e. have a bond wired).
38
- * `disabled` models are excluded — they are never offered for selection.
62
+ * Models that are not {@link isSelectableModel} — `disabled` or superseded by a
63
+ * newer generation — are excluded; they are never offered for selection.
39
64
  *
40
65
  * @param availableProviders - Set or array of provider IDs that have active bonds.
41
- * @returns Non-disabled models whose provider is in the available set.
66
+ * @returns Selectable models whose provider is in the available set.
42
67
  */
43
68
  export declare function getAvailableModels(availableProviders: ReadonlySet<AIProviderID> | readonly AIProviderID[]): readonly ModelDefinition[];
44
69
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"lookup.d.ts","sourceRoot":"","sources":["../src/lookup.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAE/D;;;;;;GAMG;AACH,eAAO,MAAM,SAAS,EAAE,WAAW,CAAC,MAAM,CAEzC,CAAA;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAEhE;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,YAAY,GAAG,SAAS,eAAe,EAAE,CAEtF;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAChC,kBAAkB,EAAE,WAAW,CAAC,YAAY,CAAC,GAAG,SAAS,YAAY,EAAE,GACtE,SAAS,eAAe,EAAE,CAI5B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,eAAe,GAAG,SAAS,EAAE,EAAE,EAAE,IAAI,GAAG,MAAM,CAYzF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,eAAe,GAAG,SAAS,EACrC,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,CAGR;AAED,2DAA2D;AAC3D,MAAM,WAAW,eAAe;IAC9B,sDAAsD;IACtD,iBAAiB,EAAE,MAAM,CAAA;IACzB,8CAA8C;IAC9C,kBAAkB,EAAE,MAAM,CAAA;IAC1B,yDAAyD;IACzD,qBAAqB,EAAE,MAAM,CAAA;IAC7B,0DAA0D;IAC1D,sBAAsB,EAAE,MAAM,CAAA;CAC/B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,eAAe,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,eAAe,CAiB/F"}
1
+ {"version":3,"file":"lookup.d.ts","sourceRoot":"","sources":["../src/lookup.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAE/D;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,IAAI,CAAC,eAAe,EAAE,UAAU,GAAG,cAAc,CAAC,GACxD,OAAO,CAET;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,wBAAwB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CASvE;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,SAAS,EAAE,WAAW,CAAC,MAAM,CAEzC,CAAA;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAEhE;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,YAAY,GAAG,SAAS,eAAe,EAAE,CAEtF;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAChC,kBAAkB,EAAE,WAAW,CAAC,YAAY,CAAC,GAAG,SAAS,YAAY,EAAE,GACtE,SAAS,eAAe,EAAE,CAI5B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,eAAe,GAAG,SAAS,EAAE,EAAE,EAAE,IAAI,GAAG,MAAM,CAYzF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,eAAe,GAAG,SAAS,EACrC,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,CAGR;AAED,2DAA2D;AAC3D,MAAM,WAAW,eAAe;IAC9B,sDAAsD;IACtD,iBAAiB,EAAE,MAAM,CAAA;IACzB,8CAA8C;IAC9C,kBAAkB,EAAE,MAAM,CAAA;IAC1B,yDAAyD;IACzD,qBAAqB,EAAE,MAAM,CAAA;IAC7B,0DAA0D;IAC1D,sBAAsB,EAAE,MAAM,CAAA;CAC/B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,eAAe,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,eAAe,CAiB/F"}
package/dist/lookup.js CHANGED
@@ -4,21 +4,57 @@
4
4
  * @module
5
5
  */
6
6
  import { MODELS } from './models.js';
7
+ /**
8
+ * Whether a model may be offered for selection: not `disabled` (retired
9
+ * upstream) and not `supersededBy` a newer generation of its own family. Both
10
+ * kinds stay in the catalog for pricing — this predicate is the single place
11
+ * that decides *exposure*, so every listing/validation surface agrees.
12
+ *
13
+ * @param model - The model definition (or the two flags from one).
14
+ * @returns True when the model may be listed and chosen.
15
+ */
16
+ export function isSelectableModel(model) {
17
+ return !model.disabled && !model.supersededBy;
18
+ }
19
+ /**
20
+ * Resolve a model id FORWARD to the selectable model that replaces it, following
21
+ * the {@link ModelDefinition.supersededBy} chain (a saved `qwen3.7-max` →
22
+ * `qwen3.8-max`). Lets a persisted selection keep the user's intent — the same
23
+ * tier from the same provider — instead of falling back to the platform default
24
+ * once the older generation stops being offered.
25
+ *
26
+ * @param id - The persisted model id.
27
+ * @returns The selectable successor's id, the id itself when it is already
28
+ * selectable, or `undefined` for an unknown or `disabled` model (nothing to
29
+ * forward to).
30
+ */
31
+ export function resolveSelectableModelId(id) {
32
+ let model = getModel(id);
33
+ // Bounded by the catalog size: a supersession cycle would otherwise spin here,
34
+ // and the invariant that forbids one is a test, not a runtime guarantee.
35
+ for (let hops = 0; model && !isSelectableModel(model) && hops <= MODELS.length; hops++) {
36
+ if (!model.supersededBy)
37
+ return undefined; // disabled — no successor declared
38
+ model = getModel(model.supersededBy);
39
+ }
40
+ return model && isSelectableModel(model) ? model.id : undefined;
41
+ }
7
42
  /**
8
43
  * Set of *selectable* model IDs for fast validation.
9
44
  *
10
45
  * Excludes `disabled` models so a retired model (e.g. `grok-code-fast-1`) can
11
- * never be chosen for a new chat, while {@link getModel} still resolves it for
12
- * historical pricing.
46
+ * never be chosen for a new chat, and `supersededBy` models so an older
47
+ * generation of a family (e.g. `qwen3.7-max` next to `qwen3.8-max`) is never
48
+ * offered — while {@link getModel} still resolves both for historical pricing.
13
49
  */
14
- export const MODEL_IDS = new Set(MODELS.filter((m) => !m.disabled).map((m) => m.id));
50
+ export const MODEL_IDS = new Set(MODELS.filter(isSelectableModel).map((m) => m.id));
15
51
  /**
16
52
  * Look up a model definition by ID.
17
53
  *
18
- * Returns `disabled` models too: a saved selection or a historical usage row
19
- * may reference a since-retired model, and it must stay priceable. Use
20
- * {@link MODEL_IDS} / {@link getAvailableModels} (which exclude disabled
21
- * models) to decide what is *selectable*.
54
+ * Returns `disabled` and `supersededBy` models too: a saved selection or a
55
+ * historical usage row may reference a since-retired or since-superseded model,
56
+ * and it must stay priceable. Use {@link MODEL_IDS} / {@link getAvailableModels}
57
+ * (or {@link isSelectableModel}) to decide what is *selectable*.
22
58
  *
23
59
  * @param id - The API model ID.
24
60
  * @returns The model definition, or `undefined` if not found.
@@ -39,14 +75,15 @@ export function getModelsByProvider(provider) {
39
75
  * Get models that are currently usable — filtered to only providers that are available.
40
76
  *
41
77
  * The caller passes in which provider IDs are active (i.e. have a bond wired).
42
- * `disabled` models are excluded — they are never offered for selection.
78
+ * Models that are not {@link isSelectableModel} — `disabled` or superseded by a
79
+ * newer generation — are excluded; they are never offered for selection.
43
80
  *
44
81
  * @param availableProviders - Set or array of provider IDs that have active bonds.
45
- * @returns Non-disabled models whose provider is in the available set.
82
+ * @returns Selectable models whose provider is in the available set.
46
83
  */
47
84
  export function getAvailableModels(availableProviders) {
48
85
  const providerSet = availableProviders instanceof Set ? availableProviders : new Set(availableProviders);
49
- return MODELS.filter((m) => providerSet.has(m.provider) && !m.disabled);
86
+ return MODELS.filter((m) => providerSet.has(m.provider) && isSelectableModel(m));
50
87
  }
51
88
  /**
52
89
  * The price multiplier in effect for a model at a given instant.
package/dist/models.d.ts CHANGED
@@ -28,6 +28,18 @@ import type { ModelDefinition } from './types.js';
28
28
  * control) carries `thinkingConfigurable: false` and OMITS both fields —
29
29
  * there is nothing to tune.
30
30
  *
31
+ * ONE GENERATION PER FAMILY. When a provider ships a newer generation of a
32
+ * model line, the older entry gets `supersededBy: '<newer id>'` and stops being
33
+ * offered — the picker never shows both `qwen3.7-max` and `qwen3.8-max`. The
34
+ * entry is NEVER deleted: `getModel()` still resolves it so saved selections and
35
+ * historical usage stay priceable, and a persisted id resolves forward to the
36
+ * successor. Supersede only within the same TIER: a cheaper or specialist model
37
+ * with no newer equivalent (`gemini-3.1-pro-preview`, `qwen3-coder-plus`,
38
+ * `kimi-k2.7-code`, `grok-build-0.1`) keeps at most `deprecatedAt`, so every
39
+ * provider keeps a real choice. `__tests__/lookup.test.ts` fails on any two
40
+ * selectable models of one family at different versions that aren't a
41
+ * documented exception.
42
+ *
31
43
  * Sources (verified 2026-07-28; OpenAI re-verified 2026-07-31 after the
32
44
  * 2026-07-30 GPT-5.6 repricing — cross-check prices against models.dev with
33
45
  * `npm run check:model-freshness` from the workspace root):
@@ -58,12 +70,13 @@ import type { ModelDefinition } from './types.js';
58
70
  * Pro/Flash pricing; legacy deepseek-chat/-reasoner ids fully retired
59
71
  * 2026-07-24 — never in this catalog; the announced peak-hour 2× surcharge is
60
72
  * still NOT active as of 2026-07-28, see the entries)
61
- * - Moonshot: https://platform.kimi.ai/docs/models (kimi-k3 flagship 2026-07-16
73
+ * - Moonshot: https://platform.kimi.ai/docs/models + DeepInfra's model API for
74
+ * the US re-host (kimi-k3 flagship 2026-07-16
62
75
  * — 2.8T MoE, 1M ctx, $3/$15 — NOT added: thinking is forced-on with
63
76
  * reasoning_content that must be replayed through tool loops, the same
64
- * constraint that keeps kimi-k2.7-code out; add BOTH once the moonshot bond
65
- * supports preserved thinking + reasoning_effort low|high|max. kimi-k2.6
66
- * remains the newest model the bond can run correctly.)
77
+ * constraint that kept kimi-k2.7-code out. BOTH are now in the catalog: the
78
+ * moonshot bond gained preserved thinking (reasoning replayed through tool
79
+ * loops), so kimi-k3 is the Moonshot pick.)
67
80
  * - MiniMax: https://platform.minimax.io/docs/guides/pricing-paygo (unchanged;
68
81
  * minimax-m3 $0.30/$1.20 is a "permanent 50% off" list rate)
69
82
  * - Alibaba: https://www.alibabacloud.com/help/en/model-studio/deep-thinking
@@ -1 +1 @@
1
- {"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAEjD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6EG;AACH,eAAO,MAAM,MAAM,EAAE,SAAS,eAAe,EAmtCnC,CAAA"}
1
+ {"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAEjD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0FG;AACH,eAAO,MAAM,MAAM,EAAE,SAAS,eAAe,EAqxCnC,CAAA"}
package/dist/models.js CHANGED
@@ -27,6 +27,18 @@
27
27
  * control) carries `thinkingConfigurable: false` and OMITS both fields —
28
28
  * there is nothing to tune.
29
29
  *
30
+ * ONE GENERATION PER FAMILY. When a provider ships a newer generation of a
31
+ * model line, the older entry gets `supersededBy: '<newer id>'` and stops being
32
+ * offered — the picker never shows both `qwen3.7-max` and `qwen3.8-max`. The
33
+ * entry is NEVER deleted: `getModel()` still resolves it so saved selections and
34
+ * historical usage stay priceable, and a persisted id resolves forward to the
35
+ * successor. Supersede only within the same TIER: a cheaper or specialist model
36
+ * with no newer equivalent (`gemini-3.1-pro-preview`, `qwen3-coder-plus`,
37
+ * `kimi-k2.7-code`, `grok-build-0.1`) keeps at most `deprecatedAt`, so every
38
+ * provider keeps a real choice. `__tests__/lookup.test.ts` fails on any two
39
+ * selectable models of one family at different versions that aren't a
40
+ * documented exception.
41
+ *
30
42
  * Sources (verified 2026-07-28; OpenAI re-verified 2026-07-31 after the
31
43
  * 2026-07-30 GPT-5.6 repricing — cross-check prices against models.dev with
32
44
  * `npm run check:model-freshness` from the workspace root):
@@ -57,12 +69,13 @@
57
69
  * Pro/Flash pricing; legacy deepseek-chat/-reasoner ids fully retired
58
70
  * 2026-07-24 — never in this catalog; the announced peak-hour 2× surcharge is
59
71
  * still NOT active as of 2026-07-28, see the entries)
60
- * - Moonshot: https://platform.kimi.ai/docs/models (kimi-k3 flagship 2026-07-16
72
+ * - Moonshot: https://platform.kimi.ai/docs/models + DeepInfra's model API for
73
+ * the US re-host (kimi-k3 flagship 2026-07-16
61
74
  * — 2.8T MoE, 1M ctx, $3/$15 — NOT added: thinking is forced-on with
62
75
  * reasoning_content that must be replayed through tool loops, the same
63
- * constraint that keeps kimi-k2.7-code out; add BOTH once the moonshot bond
64
- * supports preserved thinking + reasoning_effort low|high|max. kimi-k2.6
65
- * remains the newest model the bond can run correctly.)
76
+ * constraint that kept kimi-k2.7-code out. BOTH are now in the catalog: the
77
+ * moonshot bond gained preserved thinking (reasoning replayed through tool
78
+ * loops), so kimi-k3 is the Moonshot pick.)
66
79
  * - MiniMax: https://platform.minimax.io/docs/guides/pricing-paygo (unchanged;
67
80
  * minimax-m3 $0.30/$1.20 is a "permanent 50% off" list rate)
68
81
  * - Alibaba: https://www.alibabacloud.com/help/en/model-studio/deep-thinking
@@ -191,9 +204,11 @@ export const MODELS = [
191
204
  cacheReadPricePerMTok: 0.5,
192
205
  cacheWritePricePerMTok: 6.25,
193
206
  knowledgeCutoff: '2026-01-01',
194
- // Superseded by claude-opus-5 (same price); still served upstream and the
195
- // recommended refusal-fallback target. Selectable under "Older models".
207
+ // Superseded by claude-opus-5 (same price, same tier); still served upstream
208
+ // and the recommended refusal-fallback target, so it stays priceable and
209
+ // callable by id — it is just not OFFERED, since opus-5 is a drop-in.
196
210
  deprecatedAt: '2026-07-28',
211
+ supersededBy: 'claude-opus-5',
197
212
  },
198
213
  {
199
214
  id: 'claude-sonnet-5',
@@ -255,9 +270,12 @@ export const MODELS = [
255
270
  knowledgeCutoff: '2026-01-01',
256
271
  // Superseded by claude-opus-4-8 (launched 2026-05-28) at identical pricing;
257
272
  // still Active upstream (deprecations page 2026-08-06: retires no sooner
258
- // than 2027-04-16). Selectable under "Older models". NO fast mode —
259
- // speed:"fast" on 4.7 returns an error (pricing page, fast-mode section).
273
+ // than 2027-04-16). NO fast mode — speed:"fast" on 4.7 returns an error
274
+ // (pricing page, fast-mode section). `supersededBy` names the CURRENT
275
+ // selectable Opus (opus-5), not the also-superseded 4.8, so a saved
276
+ // selection resolves forward in one hop.
260
277
  deprecatedAt: '2026-05-28',
278
+ supersededBy: 'claude-opus-5',
261
279
  },
262
280
  {
263
281
  id: 'claude-opus-4-6',
@@ -285,8 +303,9 @@ export const MODELS = [
285
303
  cacheReadPricePerMTok: 0.5,
286
304
  cacheWritePricePerMTok: 6.25,
287
305
  knowledgeCutoff: '2025-05-01',
288
- // Superseded by claude-opus-4-8; kept selectable (Older models) + priceable.
306
+ // Superseded by the current Opus (opus-5) — kept priceable, not offered.
289
307
  deprecatedAt: '2026-06-16',
308
+ supersededBy: 'claude-opus-5',
290
309
  },
291
310
  {
292
311
  id: 'claude-sonnet-4-6',
@@ -315,8 +334,9 @@ export const MODELS = [
315
334
  cacheReadPricePerMTok: 0.3,
316
335
  cacheWritePricePerMTok: 3.75,
317
336
  knowledgeCutoff: '2025-08-01',
318
- // Superseded by claude-sonnet-5; kept selectable (Older models) + priceable.
337
+ // Superseded by claude-sonnet-5 (same tier) — kept priceable, not offered.
319
338
  deprecatedAt: '2026-07-07',
339
+ supersededBy: 'claude-sonnet-5',
320
340
  },
321
341
  {
322
342
  id: 'claude-haiku-4-5-20251001',
@@ -471,9 +491,10 @@ export const MODELS = [
471
491
  cacheReadPricePerMTok: 0.5,
472
492
  cacheWritePricePerMTok: 5,
473
493
  knowledgeCutoff: '2025-12-01',
474
- // Superseded by gpt-5.6-sol (same price); still listed as current by
475
- // OpenAI. Selectable under "Older models".
494
+ // Superseded by gpt-5.6-sol (same frontier tier, same $5/$30); still listed
495
+ // as current by OpenAI, so it stays priceable — it is just not offered.
476
496
  deprecatedAt: '2026-07-09',
497
+ supersededBy: 'gpt-5.6-sol',
477
498
  },
478
499
  {
479
500
  id: 'gpt-5.4',
@@ -501,9 +522,11 @@ export const MODELS = [
501
522
  cacheWritePricePerMTok: 2.5,
502
523
  knowledgeCutoff: '2025-08-31',
503
524
  // OpenAI still lists gpt-5.4 as current, but gpt-5.6-terra covers this
504
- // tier at the same price — moved to "Older models" (deprecatedAt is OUR
505
- // picker taxonomy, not OpenAI's deprecations page).
525
+ // balanced tier for LESS ($2/$12 vs $2.50/$15) — superseded, so the picker
526
+ // offers only the 5.6 generation (this is OUR taxonomy, not OpenAI's
527
+ // deprecations page; the model stays priceable).
506
528
  deprecatedAt: '2026-07-28',
529
+ supersededBy: 'gpt-5.6-terra',
507
530
  },
508
531
  {
509
532
  id: 'gpt-5.4-mini',
@@ -530,11 +553,12 @@ export const MODELS = [
530
553
  cacheReadPricePerMTok: 0.075,
531
554
  cacheWritePricePerMTok: 0.75,
532
555
  knowledgeCutoff: '2025-08-31',
533
- // OpenAI still lists gpt-5.4-mini as current, but like gpt-5.4 above it's
534
- // superseded in our lineup (cheap/fast tier is better served by the newer
535
- // models) — moved to "Older models" (deprecatedAt is OUR picker taxonomy,
536
- // not OpenAI's deprecations page).
556
+ // Superseded by gpt-5.6-luna, which IS the newer cheap/fast tier and is
557
+ // strictly better on every axis that made this the budget pick: $0.20/$1.20
558
+ // vs $0.75/$4.50 after the 2026-07-30 repricing, and a 1M window vs 400K.
559
+ // Hiding it therefore costs OpenAI no cheap option. Stays priceable.
537
560
  deprecatedAt: '2026-08-01',
561
+ supersededBy: 'gpt-5.6-luna',
538
562
  },
539
563
  // ---------------------------------------------------------------------------
540
564
  // Google
@@ -608,9 +632,10 @@ export const MODELS = [
608
632
  cacheReadPricePerMTok: 0.15,
609
633
  cacheWritePricePerMTok: 1.5,
610
634
  knowledgeCutoff: '2025-01-01',
611
- // Superseded by gemini-3.6-flash (2026-07-21); still served upstream.
612
- // Selectable under "Older models".
635
+ // Superseded by gemini-3.6-flash (2026-07-21) — same flash tier, same input
636
+ // price, cheaper output. Still served upstream, so it stays priceable.
613
637
  deprecatedAt: '2026-07-21',
638
+ supersededBy: 'gemini-3.6-flash',
614
639
  },
615
640
  {
616
641
  id: 'gemini-3.1-pro-preview',
@@ -640,6 +665,10 @@ export const MODELS = [
640
665
  cacheReadPricePerMTok: 0.2,
641
666
  cacheWritePricePerMTok: 2,
642
667
  knowledgeCutoff: '2025-01-01',
668
+ // NOT superseded despite the lower version number: this is Google's only
669
+ // PRO-tier id (no GA "3.5/3.6 Pro" exists), and the 3.6 flash flagship is a
670
+ // different tier. Superseding it would leave Google with no deep-reasoning
671
+ // option at all — see `ModelDefinition.supersededBy` (same-tier rule).
643
672
  },
644
673
  // ---------------------------------------------------------------------------
645
674
  // xAI (Grok)
@@ -703,9 +732,13 @@ export const MODELS = [
703
732
  cacheReadPricePerMTok: 0.2,
704
733
  cacheWritePricePerMTok: 1.25,
705
734
  knowledgeCutoff: '2025-12-01',
706
- // Superseded by grok-4.5 as the xAI pick (4.3 keeps the bigger 1M window
707
- // — the reason it stays selectable under "Older models").
735
+ // Superseded by grok-4.5: the previous version of the same general-purpose
736
+ // Grok line, not a separately-named tier. It keeps a bigger window (1M vs
737
+ // 500K) and a lower price, which is why it was previously left selectable —
738
+ // but offering two generations of one family is exactly what the picker no
739
+ // longer does, and grok-4.5 is xAI's own recommendation. Stays priceable.
708
740
  deprecatedAt: '2026-07-28',
741
+ supersededBy: 'grok-4.5',
709
742
  },
710
743
  {
711
744
  id: 'grok-build-0.1',
@@ -732,6 +765,9 @@ export const MODELS = [
732
765
  // Not published by xAI — best-effort estimate (grok-4-generation base).
733
766
  knowledgeCutoff: '2025-06-01',
734
767
  // Niche coding beta; grok-4.5 is the xAI pick — kept out of the main list.
768
+ // NOT superseded: its own family (grok-build) has no newer version, and it
769
+ // is xAI's cheapest tool-capable model, so it stays selectable under
770
+ // "Older models" (and is the deliberately-weak live selftest target).
735
771
  deprecatedAt: '2026-07-28',
736
772
  },
737
773
  {
@@ -891,9 +927,13 @@ export const MODELS = [
891
927
  // though Flash's US list price is BELOW native, its cache reads are 6.4×,
892
928
  // and the plan/execute pair defaults to one region deliberately.
893
929
  regions: ['cn', 'us'],
894
- // US = DeepInfra, verified 2026-08-01 (api.deepinfra.com/models/…V4-Flash).
930
+ // US = DeepInfra, verified 2026-08-13 against the id the bond actually
931
+ // sends: `deepseek-ai/DeepSeek-V4-Flash-0731`, the official release that
932
+ // supersedes the preview weights still served under the un-dated id
933
+ // (cents_per_input_token 0.000008, cents_per_output_token 0.000018,
934
+ // rate_per_input_token_cached 0.2 → cache read = 0.2 × input).
895
935
  regionPricing: {
896
- us: { inputPricePerMTok: 0.09, outputPricePerMTok: 0.18, cacheReadPricePerMTok: 0.018 },
936
+ us: { inputPricePerMTok: 0.08, outputPricePerMTok: 0.18, cacheReadPricePerMTok: 0.016 },
897
937
  },
898
938
  // Peak-hour surcharge NOT active (see deepseek-v4-pro) — windows removed.
899
939
  // Not published by DeepSeek — best-effort estimate.
@@ -910,6 +950,12 @@ export const MODELS = [
910
950
  // and kimi-k2.7-code (coding flagship — forced thinking, no depth knob).
911
951
  // kimi-k2.x thinking stays on/off only; the bond disables it for those by
912
952
  // default (KIMI_REASONING_EFFORT env tunes it).
953
+ // EVERY moonshot entry declares `regions` explicitly. A model that omits the
954
+ // field defaults to `['us']` (effectiveModelRegion), which would route it to
955
+ // the bare `moonshot` bond — DeepInfra when its key is set — with an id that
956
+ // host has never heard of, i.e. a 404 at dispatch. The freshness gate's
957
+ // region-re-host coverage check fails on exactly that (a us-region moonshot
958
+ // model missing from the bond's modelMap).
913
959
  // ---------------------------------------------------------------------------
914
960
  {
915
961
  id: 'kimi-k3',
@@ -936,8 +982,21 @@ export const MODELS = [
936
982
  // Automatic context cache: absolute cache-hit price ($0.30/M = 0.1× input).
937
983
  cacheReadPricePerMTok: 0.3,
938
984
  cacheWritePricePerMTok: 3,
939
- // No US re-host exists (not on DeepInfra) — pinned to native China.
940
- regions: ['cn'],
985
+ // US default = DeepInfra, verified 2026-08-13 against
986
+ // api.deepinfra.com/models/moonshotai/Kimi-K3: cents_per_input_token
987
+ // 0.000285 → $2.85/MTok, cents_per_output_token 0.001425 → $14.25/MTok,
988
+ // rate_per_input_token_cached 0.1 → cache read $0.285/MTok, and
989
+ // rate_per_input_token_cache_write null → no write premium (the omitted
990
+ // cache-write field falls back to the region's input rate). Cheaper than
991
+ // Moonshot native on every axis, which is why US leads (see the
992
+ // cheapest-default-region invariant in __tests__/lookup.test.ts).
993
+ // The host serves the full 1M context, unquantized, and returns
994
+ // reasoning_content while accepting reasoning_effort — probed live
995
+ // 2026-08-13 — so the preserved-thinking tool loop works there unchanged.
996
+ regions: ['us', 'cn'],
997
+ regionPricing: {
998
+ us: { inputPricePerMTok: 2.85, outputPricePerMTok: 14.25, cacheReadPricePerMTok: 0.285 },
999
+ },
941
1000
  // Not published — best-effort estimate.
942
1001
  knowledgeCutoff: '2026-01-01',
943
1002
  },
@@ -962,15 +1021,21 @@ export const MODELS = [
962
1021
  // Automatic context cache: absolute cache-hit price ($0.19/M = 0.2× input).
963
1022
  cacheReadPricePerMTok: 0.19,
964
1023
  cacheWritePricePerMTok: 0.95,
965
- // US default (DeepInfra bills below native here). Verified 2026-08-01.
1024
+ // US default (DeepInfra bills below native here). Re-verified 2026-08-13
1025
+ // against api.deepinfra.com/models/moonshotai/Kimi-K2.7-Code — the host
1026
+ // repriced since 2026-08-01 ($0.74/$3.50/$0.15): cents_per_input_token
1027
+ // 0.000068, cents_per_output_token 0.00034, rate_per_input_token_cached
1028
+ // 0.2 → cache read $0.136, no write premium.
966
1029
  regions: ['us', 'cn'],
967
1030
  regionPricing: {
968
- us: { inputPricePerMTok: 0.74, outputPricePerMTok: 3.5, cacheReadPricePerMTok: 0.15 },
1031
+ us: { inputPricePerMTok: 0.68, outputPricePerMTok: 3.4, cacheReadPricePerMTok: 0.136 },
969
1032
  },
970
1033
  // Not published — best-effort estimate.
971
1034
  knowledgeCutoff: '2025-10-01',
972
- // kimi-k3 is the Moonshot pick; the coding specialist stays selectable
973
- // under "Older models" for anyone who wants the cheaper tier.
1035
+ // kimi-k3 is the Moonshot pick, but this is NOT superseded: the coding
1036
+ // specialist is a distinct, much cheaper tier ($0.95/$4 vs $3/$15) with no
1037
+ // K3 equivalent, so it stays selectable under "Older models" — superseding
1038
+ // it would leave Moonshot with only the flagship.
974
1039
  deprecatedAt: '2026-07-28',
975
1040
  },
976
1041
  {
@@ -1002,8 +1067,9 @@ export const MODELS = [
1002
1067
  us: { inputPricePerMTok: 0.75, outputPricePerMTok: 3.5, cacheReadPricePerMTok: 0.15 },
1003
1068
  },
1004
1069
  knowledgeCutoff: '2025-04-01',
1005
- // Superseded by kimi-k3; moved to "Older models".
1070
+ // Superseded by kimi-k3 (same general-purpose line) — kept priceable.
1006
1071
  deprecatedAt: '2026-07-28',
1072
+ supersededBy: 'kimi-k3',
1007
1073
  },
1008
1074
  {
1009
1075
  id: 'kimi-k2.5',
@@ -1030,9 +1096,11 @@ export const MODELS = [
1030
1096
  us: { inputPricePerMTok: 0.45, outputPricePerMTok: 2.25, cacheReadPricePerMTok: 0.07 },
1031
1097
  },
1032
1098
  knowledgeCutoff: '2024-04-01',
1033
- // Superseded by kimi-k2.6 (still served upstream, no announced retirement);
1034
- // kept selectable (Older models) + priceable.
1099
+ // Two generations behind. `supersededBy` names the current selectable Kimi
1100
+ // (k3) rather than the also-superseded k2.6, so a saved selection resolves
1101
+ // forward in one hop. Still served upstream; stays priceable.
1035
1102
  deprecatedAt: '2026-04-01',
1103
+ supersededBy: 'kimi-k3',
1036
1104
  },
1037
1105
  // ---------------------------------------------------------------------------
1038
1106
  // MiniMax
@@ -1101,9 +1169,9 @@ export const MODELS = [
1101
1169
  us: { inputPricePerMTok: 0.25, outputPricePerMTok: 1, cacheReadPricePerMTok: 0.05 },
1102
1170
  },
1103
1171
  knowledgeCutoff: '2025-09-01',
1104
- // Superseded by minimax-m3 (same price, 1M ctx, multimodal); moved to
1105
- // "Older models".
1172
+ // Superseded by minimax-m3 (same price, 1M ctx, multimodal) — kept priceable.
1106
1173
  deprecatedAt: '2026-07-28',
1174
+ supersededBy: 'minimax-m3',
1107
1175
  },
1108
1176
  {
1109
1177
  id: 'minimax-m2.5',
@@ -1126,17 +1194,18 @@ export const MODELS = [
1126
1194
  // No US re-host exists (not on DeepInfra) — pinned to native China.
1127
1195
  regions: ['cn'],
1128
1196
  knowledgeCutoff: '2025-01-01',
1129
- // Superseded by minimax-m3 (legacy upstream, still served); kept selectable
1130
- // (Older models) + priceable.
1197
+ // Superseded by minimax-m3 (legacy upstream, still served) — kept priceable.
1131
1198
  deprecatedAt: '2026-03-18',
1199
+ supersededBy: 'minimax-m3',
1132
1200
  },
1133
1201
  // ---------------------------------------------------------------------------
1134
1202
  // Alibaba (Qwen)
1135
1203
  // Verified: https://www.alibabacloud.com/help/en/model-studio/deep-thinking
1136
1204
  // https://www.alibabacloud.com/help/en/model-studio/qwen-coder
1137
1205
  // https://openrouter.ai/qwen/qwen3.7-max
1138
- // qwen3.7-max (2026-05-21) is the agentic flagship — Alibaba's own Qwen-Coder
1139
- // docs now recommend the general-purpose models over Qwen-Coder. Its thinking
1206
+ // qwen3.8-max (2026-08-03) is the agentic flagship, succeeding qwen3.7-max —
1207
+ // Alibaba's own Qwen-Coder docs now recommend the general-purpose models over
1208
+ // Qwen-Coder. Their thinking
1140
1209
  // uses enable_thinking (default ON for the 3.7 series) + thinking_budget
1141
1210
  // (token cap) — a real budget param, so effort scales the budget. The
1142
1211
  // qwen3-coder models are NON-thinking (previous catalog entry was wrong).
@@ -1207,6 +1276,11 @@ export const MODELS = [
1207
1276
  regions: ['us', 'cn'],
1208
1277
  // Not published by Alibaba — best-effort estimate.
1209
1278
  knowledgeCutoff: '2026-01-01',
1279
+ // Superseded by qwen3.8-max (GA 2026-08-03): same tier and mechanism, and
1280
+ // CHEAPER at list ($2/$6 vs $2.50/$7.50). Still served upstream (the 50%-off
1281
+ // promo runs on this id), so it stays priceable — it is just not offered.
1282
+ deprecatedAt: '2026-08-03',
1283
+ supersededBy: 'qwen3.8-max',
1210
1284
  },
1211
1285
  {
1212
1286
  id: 'qwen3-coder-plus',
@@ -1236,8 +1310,11 @@ export const MODELS = [
1236
1310
  us: { inputPricePerMTok: 0.3, outputPricePerMTok: 1, cacheReadPricePerMTok: 0.1 },
1237
1311
  },
1238
1312
  knowledgeCutoff: '2025-06-01',
1239
- // Alibaba itself recommends the general-purpose models over Qwen-Coder;
1240
- // qwen3.7-max is the pick — moved to "Older models".
1313
+ // Alibaba itself recommends the general-purpose models over Qwen-Coder, so
1314
+ // this sits in "Older models" — but it is NOT superseded: it is a distinct
1315
+ // coding specialist and Alibaba's cheap tier (US $0.30/$1 vs qwen3.8-max's
1316
+ // $2/$6), with no newer coder id. Superseding it would leave Alibaba with
1317
+ // only the flagship.
1241
1318
  deprecatedAt: '2026-07-28',
1242
1319
  },
1243
1320
  // ---------------------------------------------------------------------------
@@ -1309,7 +1386,9 @@ export const MODELS = [
1309
1386
  us: { inputPricePerMTok: 0.6, outputPricePerMTok: 2.08, cacheReadPricePerMTok: 0.12 },
1310
1387
  },
1311
1388
  knowledgeCutoff: '2025-01-01',
1312
- // Superseded by glm-5.2; moved to "Older models".
1389
+ // Superseded by glm-5.2 (same line, bigger window, reasoning_effort) — kept
1390
+ // priceable.
1313
1391
  deprecatedAt: '2026-07-28',
1392
+ supersededBy: 'glm-5.2',
1314
1393
  },
1315
1394
  ];
package/dist/types.d.ts CHANGED
@@ -282,6 +282,35 @@ export interface ModelDefinition {
282
282
  * or its past usage silently meters as free. Omit entirely for active models.
283
283
  */
284
284
  disabled?: boolean;
285
+ /**
286
+ * The id of the NEWER-generation model that replaces this one, set on the
287
+ * OLDER entry and naming its successor (e.g. `qwen3.7-max` carries
288
+ * `supersededBy: 'qwen3.8-max'`).
289
+ *
290
+ * A superseded model is NOT selectable — like {@link disabled} it is excluded
291
+ * from `MODEL_IDS`, `getAvailableModels()`, the `GET /ai/models` listing and
292
+ * the client-side picker partitions, so a user is only ever offered the
293
+ * newest generation of a family. It differs from `disabled` in *why* and in
294
+ * what it points at: a disabled model is one the provider retired (it can no
295
+ * longer answer), whereas a superseded model is usually still served upstream
296
+ * and simply has nothing to offer over its successor — and the successor id
297
+ * is a real migration target, so a saved selection can resolve forward
298
+ * (`resolveSelectableModelId`) instead of silently falling back to the
299
+ * platform default.
300
+ *
301
+ * `getModel(id)` STILL returns a superseded entry — historical usage must stay
302
+ * priceable, so NEVER delete one.
303
+ *
304
+ * Set this ONLY when the successor covers the same TIER. A cheaper or
305
+ * specialist tier with no newer equivalent is NOT superseded merely because
306
+ * its version number is lower (Google's sole pro tier `gemini-3.1-pro-preview`
307
+ * alongside the newer flash flagship; `qwen3-coder-plus`; `kimi-k2.7-code`) —
308
+ * hiding those would leave a provider with no cheap option. Mark those
309
+ * {@link deprecatedAt} at most. The invariant is enforced by
310
+ * `__tests__/lookup.test.ts`: two selectable models of the same family at
311
+ * different versions fail unless listed there as a documented exception.
312
+ */
313
+ supersededBy?: string;
285
314
  }
286
315
  /**
287
316
  * The model ids the SERVER falls back to per mode/job when the user hasn't
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@molecule/api-resource-ai-models",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "AI model catalog — server-side source of truth plus an authentication-gated discovery endpoint",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",