@oh-my-pi/pi-catalog 18.1.2 → 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 +28 -0
- package/dist/types/compat/collapse.d.ts +20 -0
- package/dist/types/compat/types.d.ts +12 -0
- package/dist/types/provider-models/descriptors.d.ts +1 -1
- package/dist/types/provider-models/openai-compat.d.ts +5 -0
- package/dist/types/provider-models/special.d.ts +2 -1
- package/dist/types/types.d.ts +3 -1
- package/package.json +4 -4
- package/src/build.ts +8 -1
- package/src/compat/axes.ts +5 -0
- package/src/compat/collapse.ts +206 -51
- package/src/compat/resolve.ts +1 -1
- package/src/compat/rules/README.md +3 -2
- package/src/compat/rules/classes/gemini.kdl +17 -2
- package/src/compat/rules/classes/glm.kdl +4 -1
- package/src/compat/rules/providers/cursor.kdl +5 -0
- package/src/compat/rules/providers/google-antigravity.kdl +1 -1
- package/src/compat/rules/providers/zai.kdl +5 -1
- package/src/compat/rules/runtime/behavior.kdl +5 -0
- package/src/compat/rules/taxonomy/_collapse.kdl +17 -37
- package/src/compat/rules.json +103 -113
- package/src/compat/types.ts +13 -0
- package/src/models.json +3109 -1643
- package/src/provider-models/google.ts +6 -1
- package/src/provider-models/openai-compat.ts +44 -3
- package/src/provider-models/special.ts +4 -1
- package/src/types.ts +8 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,34 @@
|
|
|
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
|
+
|
|
15
|
+
## [18.1.3] - 2026-09-02
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- Added support for Claude Fable 5.1
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- Updated pricing and context limits for various Claude models
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- Claude Sonnet 5 no longer advertises unsupported mid-conversation system messages.
|
|
28
|
+
- Custom GLM 5.2 models on `alibaba-coding-plan` (and other blanket-GLM hosts) no longer crash startup with `AmbiguousOverlapError` ([#10553](https://github.com/can1357/oh-my-pi/issues/10553)).
|
|
29
|
+
- Gemini 3.7 Flash no longer offers the `minimal` thinking effort on direct google-level hosts (`google`, `google-vertex`, `opencode-zen`), which reject `thinkingLevel: MINIMAL` with a 400; budget and reasoning-effort resellers keep the tier ([#10543](https://github.com/can1357/oh-my-pi/issues/10543)).
|
|
30
|
+
- Fixed Alibaba Token Plan discovery for `qwen3.8-flash` to include its context limits, reasoning support, and image input.
|
|
31
|
+
- Z.AI GLM-5.3-Flash now uses the native API instead of failing through the unsupported Anthropic-compatible route ([#10539](https://github.com/can1357/oh-my-pi/issues/10539)).
|
|
32
|
+
|
|
5
33
|
## [18.1.2] - 2026-09-01
|
|
6
34
|
|
|
7
35
|
### 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. */
|
|
@@ -502,7 +502,7 @@ export declare const CATALOG_PROVIDERS: readonly [{
|
|
|
502
502
|
readonly id: "zai";
|
|
503
503
|
readonly defaultModel: "glm-5.3";
|
|
504
504
|
readonly envVars: readonly ["ZAI_API_KEY"];
|
|
505
|
-
readonly createModelManagerOptions: (config: ModelManagerConfig) => import("../index.js").ModelManagerOptions<"anthropic-messages", unknown>;
|
|
505
|
+
readonly createModelManagerOptions: (config: ModelManagerConfig) => import("../index.js").ModelManagerOptions<"anthropic-messages" | "openai-completions", unknown>;
|
|
506
506
|
readonly catalogDiscovery: {
|
|
507
507
|
readonly label: "zAI";
|
|
508
508
|
};
|
|
@@ -673,6 +673,11 @@ export interface ModelsDevProviderDescriptor {
|
|
|
673
673
|
}
|
|
674
674
|
/** Generic mapper that converts models.dev data using provider descriptors. */
|
|
675
675
|
export declare function mapModelsDevToModels(data: Record<string, unknown>, descriptors: readonly ModelsDevProviderDescriptor[]): ModelSpec<Api>[];
|
|
676
|
+
/** Resolves the transport and endpoint for one Z.AI model catalog entry. */
|
|
677
|
+
export declare function resolveZaiApi(modelId: string): {
|
|
678
|
+
api: "anthropic-messages" | "openai-completions";
|
|
679
|
+
baseUrl: string;
|
|
680
|
+
};
|
|
676
681
|
/** All provider descriptors for models.dev data mapping in generate-models.ts. */
|
|
677
682
|
export declare const MODELS_DEV_PROVIDER_DESCRIPTORS: readonly ModelsDevProviderDescriptor[];
|
|
678
683
|
/** Providers whose bundled catalog can receive additive models.dev updates at runtime. */
|
|
@@ -70,4 +70,5 @@ export declare const DEVIN_STATIC_MODELS: readonly ModelSpec<"devin-agent">[];
|
|
|
70
70
|
export declare function devinModelManagerOptions(config?: DevinModelManagerConfig): ModelManagerOptions<"devin-agent">;
|
|
71
71
|
export interface ZaiModelManagerConfig {
|
|
72
72
|
}
|
|
73
|
-
|
|
73
|
+
/** Creates model-manager options for Z.AI's mixed native and Anthropic transports. */
|
|
74
|
+
export declare function zaiModelManagerOptions(_config?: ZaiModelManagerConfig): ModelManagerOptions<"anthropic-messages" | "openai-completions">;
|
package/dist/types/types.d.ts
CHANGED
|
@@ -879,6 +879,8 @@ export interface Model<TApi extends Api = Api> {
|
|
|
879
879
|
* inferring it from the transport API.
|
|
880
880
|
*/
|
|
881
881
|
requiresGlyphTokenization?: boolean;
|
|
882
|
+
/** Whether this model requires Cursor's tool-schema combiner projection. */
|
|
883
|
+
requiresCursorToolSchemaProjection?: boolean;
|
|
882
884
|
/**
|
|
883
885
|
* Model id to send on the wire when it differs from `id`. Used by catalog
|
|
884
886
|
* variants that present one upstream model under several local entries —
|
|
@@ -1033,7 +1035,7 @@ export interface Model<TApi extends Api = Api> {
|
|
|
1033
1035
|
* vocabulary of `buildModel`. Identical to `Model` except `compat` carries the
|
|
1034
1036
|
* sparse override shape and nothing is resolved yet.
|
|
1035
1037
|
*/
|
|
1036
|
-
export interface ModelSpec<TApi extends Api = Api> extends Omit<Model<TApi>, "compat" | "identity" | "compatConfig" | "requiresGlyphTokenization" | "supportsComputerUseConfig"> {
|
|
1038
|
+
export interface ModelSpec<TApi extends Api = Api> extends Omit<Model<TApi>, "compat" | "identity" | "compatConfig" | "requiresGlyphTokenization" | "requiresCursorToolSchemaProjection" | "supportsComputerUseConfig"> {
|
|
1037
1039
|
/** Sparse compatibility overrides; resolved into `Model.compat` by `buildModel`. */
|
|
1038
1040
|
compat?: CompatConfigOf<TApi>;
|
|
1039
1041
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-catalog",
|
|
4
|
-
"version": "18.1.
|
|
4
|
+
"version": "18.1.4",
|
|
5
5
|
"description": "Model catalog for omp: bundled model database, provider discovery descriptors, model identity, classification, and equivalence",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Stencil Labs, Inc.",
|
|
@@ -35,12 +35,12 @@
|
|
|
35
35
|
"gen:proto": "bun scripts/generate-protocols.ts"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@oh-my-pi/omptype": "18.1.
|
|
39
|
-
"@oh-my-pi/pi-utils": "18.1.
|
|
38
|
+
"@oh-my-pi/omptype": "18.1.4",
|
|
39
|
+
"@oh-my-pi/pi-utils": "18.1.4"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@bgotink/kdl": "0.4.0",
|
|
43
|
-
"@oh-my-pi/pi-ai": "18.1.
|
|
43
|
+
"@oh-my-pi/pi-ai": "18.1.4",
|
|
44
44
|
"@types/bun": "^1.3.14"
|
|
45
45
|
},
|
|
46
46
|
"engines": {
|
package/src/build.ts
CHANGED
|
@@ -32,7 +32,8 @@ function isInputModalities(value: unknown): value is ("text" | "image")[] {
|
|
|
32
32
|
* Applies resolved catalog-data axes onto the model: reviewed metadata
|
|
33
33
|
* corrections (`cost-patch`, `limits-patch`, `long-context-cost`,
|
|
34
34
|
* `context-window-floor`) overwrite upstream values; selection metadata
|
|
35
|
-
* (`priority`, `apply-patch-tool-type`, `service-tier-cost
|
|
35
|
+
* (`priority`, `apply-patch-tool-type`, `service-tier-cost`,
|
|
36
|
+
* `requires-cursor-tool-schema-projection`) is rule-owned;
|
|
36
37
|
* `context-promotion-target` fills only when the spec left it unset.
|
|
37
38
|
*/
|
|
38
39
|
function applyCatalogAssignments<TApi extends Api>(model: Model<TApi>, catalog: Record<string, unknown>): void {
|
|
@@ -51,6 +52,12 @@ function applyCatalogAssignments<TApi extends Api>(model: Model<TApi>, catalog:
|
|
|
51
52
|
if (applyPatchToolType === "freeform" || applyPatchToolType === "function") {
|
|
52
53
|
model.applyPatchToolType = applyPatchToolType;
|
|
53
54
|
}
|
|
55
|
+
const requiresCursorToolSchemaProjection = catalog.requiresCursorToolSchemaProjection;
|
|
56
|
+
if (requiresCursorToolSchemaProjection === true) {
|
|
57
|
+
model.requiresCursorToolSchemaProjection = true;
|
|
58
|
+
} else {
|
|
59
|
+
delete model.requiresCursorToolSchemaProjection;
|
|
60
|
+
}
|
|
54
61
|
const contextPromotionTarget = catalog.contextPromotionTarget;
|
|
55
62
|
if (typeof contextPromotionTarget === "string" && model.contextPromotionTarget === undefined) {
|
|
56
63
|
model.contextPromotionTarget = contextPromotionTarget;
|
package/src/compat/axes.ts
CHANGED
|
@@ -262,6 +262,11 @@ export const AXES: Readonly<Record<string, AxisDef>> = {
|
|
|
262
262
|
"limits-patch": { key: "limitsPatch", set: "catalog", shape: "object" },
|
|
263
263
|
"long-context-cost": { key: "longContext", set: "catalog", shape: "object" },
|
|
264
264
|
"long-usage-limit-fallback": { key: "longUsageLimitFallback", set: "catalog", shape: "scalar" },
|
|
265
|
+
"requires-cursor-tool-schema-projection": {
|
|
266
|
+
key: "requiresCursorToolSchemaProjection",
|
|
267
|
+
set: "catalog",
|
|
268
|
+
shape: "scalar",
|
|
269
|
+
},
|
|
265
270
|
priority: { key: "priority", set: "catalog", shape: "scalar" },
|
|
266
271
|
"service-tier-cost": { key: "serviceTierCost", set: "catalog", shape: "object" },
|
|
267
272
|
};
|
package/src/compat/collapse.ts
CHANGED
|
@@ -13,7 +13,9 @@
|
|
|
13
13
|
* Families come from two sources:
|
|
14
14
|
* - Reviewed `variant-family` nodes in `compat/rules/taxonomy/_collapse.kdl`
|
|
15
15
|
* (compiled into `rules.json`) for providers whose routing needs curation
|
|
16
|
-
* (Antigravity tier triplets, single-member renames, recycled ids).
|
|
16
|
+
* (Antigravity tier triplets, single-member renames, recycled ids). A
|
|
17
|
+
* family whose ids carry `{rev}` is a template, instantiated for every
|
|
18
|
+
* revision the input advertises (`gemini-{rev}-flash` → `gemini-3.8-flash`).
|
|
17
19
|
* - `deriveThinkingPairFamilies`: the global automatic rule — any live
|
|
18
20
|
* `X` + `X-thinking` pair (trailing or infix token) collapses into `X`,
|
|
19
21
|
* routing thinking-enabled requests to `X-thinking`. Gated on identical
|
|
@@ -36,8 +38,9 @@ import { buildModel } from "../build";
|
|
|
36
38
|
import { Effort, THINKING_EFFORTS } from "../effort";
|
|
37
39
|
import type { Api, Model, ModelSpec, Provider, ThinkingConfig } from "../types";
|
|
38
40
|
import { resolveModelPolicy } from "./resolve";
|
|
41
|
+
import { parseRevision, parseRevisionConstraint, type RevisionTerm, revisionSatisfies } from "./revision";
|
|
39
42
|
import { collapseVariantId, collapseVocabulary, stripThinkingVariantSuffix } from "./taxonomy";
|
|
40
|
-
import type
|
|
43
|
+
import { type CompiledVariantFamily, REVISION_PLACEHOLDER } from "./types";
|
|
41
44
|
|
|
42
45
|
const VARIANT_ROUTING_KEYS: readonly (Effort | "off")[] = ["off", ...THINKING_EFFORTS];
|
|
43
46
|
const DEFAULT_PAIR_EFFORTS: readonly Effort[] = [Effort.Minimal, Effort.Low, Effort.Medium, Effort.High];
|
|
@@ -107,8 +110,28 @@ export interface EffortVariantFamily {
|
|
|
107
110
|
extraAliases?: readonly string[];
|
|
108
111
|
}
|
|
109
112
|
|
|
113
|
+
/**
|
|
114
|
+
* A reviewed family whose ids carry {@link REVISION_PLACEHOLDER}: one
|
|
115
|
+
* `gemini-{rev}-flash` entry stands for every Flash generation the provider
|
|
116
|
+
* serves with the same sibling layout. Concrete families with the same
|
|
117
|
+
* instantiated id take precedence.
|
|
118
|
+
*/
|
|
119
|
+
export interface VariantFamilyTemplate {
|
|
120
|
+
/** Family with placeholders still in every id and the display name. */
|
|
121
|
+
family: EffortVariantFamily;
|
|
122
|
+
/** Revisions the template applies to; unbounded when absent. */
|
|
123
|
+
revision?: readonly RevisionTerm[];
|
|
124
|
+
/**
|
|
125
|
+
* Matches the logical id, any member, or any extra alias (case-insensitive);
|
|
126
|
+
* one alternative per id, each capturing the revision.
|
|
127
|
+
*/
|
|
128
|
+
pattern: RegExp;
|
|
129
|
+
}
|
|
130
|
+
|
|
110
131
|
export interface VariantCollapseTable {
|
|
111
132
|
families: readonly EffortVariantFamily[];
|
|
133
|
+
/** Revision-templated families, instantiated against live ids. */
|
|
134
|
+
templates?: readonly VariantFamilyTemplate[];
|
|
112
135
|
/**
|
|
113
136
|
* Provider-scoped selector aliases: short native-CLI names and dotted
|
|
114
137
|
* upstream spellings → logical model id. Unlike family members and
|
|
@@ -209,13 +232,104 @@ function compiledFamily(compiled: CompiledVariantFamily): EffortVariantFamily {
|
|
|
209
232
|
return family;
|
|
210
233
|
}
|
|
211
234
|
|
|
235
|
+
const REVISION_CAPTURE = String.raw`(\d+(?:\.\d+){0,2})`;
|
|
236
|
+
|
|
237
|
+
/** One compiled `{rev}` family → runtime template with its id matcher. */
|
|
238
|
+
function compiledTemplate(compiled: CompiledVariantFamily): VariantFamilyTemplate {
|
|
239
|
+
const family = compiledFamily(compiled);
|
|
240
|
+
const alternatives = [family.id, ...family.members, ...(family.extraAliases ?? [])].map(id =>
|
|
241
|
+
id
|
|
242
|
+
.split(REVISION_PLACEHOLDER)
|
|
243
|
+
.map(part => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))
|
|
244
|
+
.join(REVISION_CAPTURE),
|
|
245
|
+
);
|
|
246
|
+
const template: VariantFamilyTemplate = {
|
|
247
|
+
family,
|
|
248
|
+
pattern: new RegExp(`^(?:${alternatives.join("|")})$`, "i"),
|
|
249
|
+
};
|
|
250
|
+
if (compiled.revision !== undefined) {
|
|
251
|
+
const terms = parseRevisionConstraint(compiled.revision);
|
|
252
|
+
if (terms !== undefined) template.revision = terms;
|
|
253
|
+
}
|
|
254
|
+
return template;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** Substitute the placeholder throughout one templated family. */
|
|
258
|
+
function instantiateTemplate(template: VariantFamilyTemplate, rev: string): EffortVariantFamily | undefined {
|
|
259
|
+
if (template.revision !== undefined) {
|
|
260
|
+
const parsed = parseRevision(rev);
|
|
261
|
+
if (parsed === undefined || !revisionSatisfies(parsed, template.revision)) return undefined;
|
|
262
|
+
}
|
|
263
|
+
const fill = (id: string): string => id.replaceAll(REVISION_PLACEHOLDER, rev);
|
|
264
|
+
const source = template.family;
|
|
265
|
+
const routing: Partial<Record<Effort | "off", string>> = {};
|
|
266
|
+
for (const effort of VARIANT_ROUTING_KEYS) {
|
|
267
|
+
const target = source.routing[effort];
|
|
268
|
+
if (target !== undefined) routing[effort] = fill(target);
|
|
269
|
+
}
|
|
270
|
+
const family: EffortVariantFamily = {
|
|
271
|
+
...source,
|
|
272
|
+
id: fill(source.id),
|
|
273
|
+
name: fill(source.name),
|
|
274
|
+
members: source.members.map(fill),
|
|
275
|
+
routing,
|
|
276
|
+
};
|
|
277
|
+
if (source.defaultMember !== undefined) family.defaultMember = fill(source.defaultMember);
|
|
278
|
+
if (source.retiredMembers !== undefined) family.retiredMembers = source.retiredMembers.map(fill);
|
|
279
|
+
if (source.extraAliases !== undefined) family.extraAliases = source.extraAliases.map(fill);
|
|
280
|
+
return family;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* The family a template yields for `id` (logical id, member, or alias), or
|
|
285
|
+
* undefined when no template matches or the revision is out of range.
|
|
286
|
+
*/
|
|
287
|
+
function templateFamilyFor(templates: readonly VariantFamilyTemplate[], id: string): EffortVariantFamily | undefined {
|
|
288
|
+
for (const template of templates) {
|
|
289
|
+
const match = template.pattern.exec(id);
|
|
290
|
+
if (match === null) continue;
|
|
291
|
+
let rev: string | undefined;
|
|
292
|
+
for (let group = 1; group < match.length && rev === undefined; group++) rev = match[group];
|
|
293
|
+
if (rev === undefined) continue;
|
|
294
|
+
const family = instantiateTemplate(template, rev);
|
|
295
|
+
if (family !== undefined) return family;
|
|
296
|
+
}
|
|
297
|
+
return undefined;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Concrete families for every revision present in `ids`, one per
|
|
302
|
+
* instantiated id, skipping ids a concrete family in `table` already owns.
|
|
303
|
+
*/
|
|
304
|
+
function instantiateTemplates(table: VariantCollapseTable, ids: Iterable<string>): EffortVariantFamily[] {
|
|
305
|
+
const templates = table.templates;
|
|
306
|
+
if (templates === undefined || templates.length === 0) return [];
|
|
307
|
+
const seen = new Set<string>();
|
|
308
|
+
for (const family of table.families) seen.add(family.id);
|
|
309
|
+
const out: EffortVariantFamily[] = [];
|
|
310
|
+
for (const id of ids) {
|
|
311
|
+
const family = templateFamilyFor(templates, id);
|
|
312
|
+
if (family === undefined || seen.has(family.id)) continue;
|
|
313
|
+
seen.add(family.id);
|
|
314
|
+
out.push(family);
|
|
315
|
+
}
|
|
316
|
+
return out;
|
|
317
|
+
}
|
|
318
|
+
|
|
212
319
|
/** Provider id → reviewed collapse table, built once from the compiled vocabulary. */
|
|
213
320
|
function buildCompiledTables(): Readonly<Record<string, VariantCollapseTable>> {
|
|
214
321
|
const { variantFamilies, providerAliases } = collapseVocabulary();
|
|
215
|
-
const tables: Record<
|
|
322
|
+
const tables: Record<
|
|
323
|
+
string,
|
|
324
|
+
{ families: EffortVariantFamily[]; templates?: VariantFamilyTemplate[]; providerAliases?: Record<string, string> }
|
|
325
|
+
> = {};
|
|
216
326
|
for (const compiled of variantFamilies) {
|
|
217
|
-
tables[compiled.provider] ??= { families: [] };
|
|
218
|
-
|
|
327
|
+
const table = (tables[compiled.provider] ??= { families: [] });
|
|
328
|
+
if (compiled.id.includes(REVISION_PLACEHOLDER)) {
|
|
329
|
+
(table.templates ??= []).push(compiledTemplate(compiled));
|
|
330
|
+
} else {
|
|
331
|
+
table.families.push(compiledFamily(compiled));
|
|
332
|
+
}
|
|
219
333
|
}
|
|
220
334
|
for (const [provider, aliases] of Object.entries(providerAliases)) {
|
|
221
335
|
tables[provider] ??= { families: [] };
|
|
@@ -407,16 +521,14 @@ export function deriveThinkingPairFamilies<TSpec extends VariantSpecLike>(
|
|
|
407
521
|
if (baseId === undefined || baseId === spec.id) continue;
|
|
408
522
|
const base = byId.get(baseId);
|
|
409
523
|
if (!base) continue;
|
|
410
|
-
if (
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
claimed.
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
continue;
|
|
419
|
-
}
|
|
524
|
+
if (
|
|
525
|
+
claimed &&
|
|
526
|
+
(claimed.resolve(spec.id) !== undefined ||
|
|
527
|
+
claimed.resolve(baseId) !== undefined ||
|
|
528
|
+
claimed.isFamily(spec.id) ||
|
|
529
|
+
claimed.isFamily(baseId))
|
|
530
|
+
) {
|
|
531
|
+
continue;
|
|
420
532
|
}
|
|
421
533
|
if (spec.api !== base.api) continue;
|
|
422
534
|
const specPriced = spec.cost.input !== 0 || spec.cost.output !== 0;
|
|
@@ -483,7 +595,7 @@ export function isCollapsedVariantSpec(spec: VariantSpecLike): boolean {
|
|
|
483
595
|
return false;
|
|
484
596
|
}
|
|
485
597
|
const table = reviewedCollapseTable(spec.provider);
|
|
486
|
-
return table !== undefined && getAliasIndex(table).
|
|
598
|
+
return table !== undefined && getAliasIndex(table).isFamily(spec.id);
|
|
487
599
|
}
|
|
488
600
|
|
|
489
601
|
/**
|
|
@@ -641,7 +753,9 @@ function collapseWithTable<TSpec extends VariantSpecLike>(
|
|
|
641
753
|
/** spec ids that belong to a touched family (members + logical id). */
|
|
642
754
|
const familyIdBySpecId = new Map<string, string>();
|
|
643
755
|
|
|
644
|
-
|
|
756
|
+
const instantiated = instantiateTemplates(table, byId.keys());
|
|
757
|
+
const families = instantiated.length === 0 ? table.families : [...table.families, ...instantiated];
|
|
758
|
+
for (const family of families) {
|
|
645
759
|
const retired =
|
|
646
760
|
family.retiredMembers !== undefined && family.retiredMembers.length > 0
|
|
647
761
|
? new Set(family.retiredMembers)
|
|
@@ -917,39 +1031,84 @@ function projectModelSpec<TApi extends Api>(model: Model<TApi>): ModelSpec<TApi>
|
|
|
917
1031
|
return { ...spec, compat: compatConfig };
|
|
918
1032
|
}
|
|
919
1033
|
|
|
1034
|
+
/**
|
|
1035
|
+
* Alias index over a collapse table (or the live families observed at
|
|
1036
|
+
* runtime). Templated families are materialized on first lookup of any id
|
|
1037
|
+
* they match, so a revision discovery has never advertised still resolves.
|
|
1038
|
+
*/
|
|
920
1039
|
class VariantAliasIndex {
|
|
921
1040
|
/** lowercased retired id → replacement model id. */
|
|
922
|
-
readonly forward = new Map<string, string>();
|
|
1041
|
+
readonly #forward = new Map<string, string>();
|
|
923
1042
|
/**
|
|
924
1043
|
* lowercased provider-scoped alias → replacement model id. Kept apart from
|
|
925
|
-
*
|
|
1044
|
+
* `#forward` so bare-id and reverse lookups never see it.
|
|
926
1045
|
*/
|
|
927
1046
|
readonly providerScoped = new Map<string, string>();
|
|
928
1047
|
/** replacement model id → retired ids that resolve to it. */
|
|
929
|
-
readonly reverse = new Map<string, string[]>();
|
|
1048
|
+
readonly #reverse = new Map<string, string[]>();
|
|
930
1049
|
/** Collapsed logical ids declared by the table or observed at runtime. */
|
|
931
|
-
readonly familyIds = new Set<string>();
|
|
932
|
-
|
|
1050
|
+
readonly #familyIds = new Set<string>();
|
|
1051
|
+
readonly #templates: readonly VariantFamilyTemplate[];
|
|
933
1052
|
|
|
934
|
-
|
|
935
|
-
|
|
1053
|
+
constructor(templates: readonly VariantFamilyTemplate[] = []) {
|
|
1054
|
+
this.#templates = templates;
|
|
1055
|
+
}
|
|
936
1056
|
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
1057
|
+
/** Register `from` → `to`; false when `from` is already claimed. */
|
|
1058
|
+
add(from: string, to: string): boolean {
|
|
1059
|
+
if (from === to || this.#forward.has(from.toLowerCase())) return false;
|
|
1060
|
+
this.#forward.set(from.toLowerCase(), to);
|
|
1061
|
+
const sources = this.#reverse.get(to);
|
|
1062
|
+
if (sources) {
|
|
1063
|
+
sources.push(from);
|
|
1064
|
+
} else {
|
|
1065
|
+
this.#reverse.set(to, [from]);
|
|
1066
|
+
}
|
|
1067
|
+
return true;
|
|
1068
|
+
}
|
|
940
1069
|
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
1070
|
+
/** Register a family: its logical id plus every member and extra alias. */
|
|
1071
|
+
addFamily(family: EffortVariantFamily): void {
|
|
1072
|
+
this.#familyIds.add(family.id);
|
|
1073
|
+
for (const member of family.members) this.add(member, family.id);
|
|
1074
|
+
for (const alias of family.extraAliases ?? []) this.add(alias, family.id);
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
markFamily(id: string): void {
|
|
1078
|
+
this.#familyIds.add(id);
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
/** Replacement model id for a retired/member id (case-insensitive). */
|
|
1082
|
+
resolve(id: string): string | undefined {
|
|
1083
|
+
const lower = id.toLowerCase();
|
|
1084
|
+
this.#learn(lower);
|
|
1085
|
+
return this.#forward.get(lower);
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
/** Whether `id` is a collapsed logical id of this index. */
|
|
1089
|
+
isFamily(id: string): boolean {
|
|
1090
|
+
this.#learn(id);
|
|
1091
|
+
return this.#familyIds.has(id);
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
/** Retired ids that resolve to logical `id`. */
|
|
1095
|
+
sourcesOf(id: string): readonly string[] | undefined {
|
|
1096
|
+
this.#learn(id);
|
|
1097
|
+
return this.#reverse.get(id);
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
/** Materialize the template family that owns `id`, once. */
|
|
1101
|
+
#learn(id: string): void {
|
|
1102
|
+
if (this.#templates.length === 0) return;
|
|
1103
|
+
const family = templateFamilyFor(this.#templates, id);
|
|
1104
|
+
if (family === undefined || this.#familyIds.has(family.id)) return;
|
|
1105
|
+
this.addFamily(family);
|
|
949
1106
|
}
|
|
950
|
-
return true;
|
|
951
1107
|
}
|
|
952
1108
|
|
|
1109
|
+
const dynamicAliasIndexes = new Map<string, VariantAliasIndex>();
|
|
1110
|
+
const kAliasIndex = Symbol("compat-collapse.aliasIndex");
|
|
1111
|
+
|
|
953
1112
|
/**
|
|
954
1113
|
* Persist aliases embedded in collapsed routing so generated catalog rows and
|
|
955
1114
|
* newly discovered families expose the same selector migrations as reviewed tables.
|
|
@@ -964,14 +1123,14 @@ function registerCollapsedVariantAliases(provider: Provider, specs: readonly Var
|
|
|
964
1123
|
for (const effort of VARIANT_ROUTING_KEYS) {
|
|
965
1124
|
const source = routing[effort];
|
|
966
1125
|
if (!source || source === spec.id) continue;
|
|
967
|
-
index ??=
|
|
968
|
-
registered =
|
|
1126
|
+
index ??= new VariantAliasIndex();
|
|
1127
|
+
registered = index.add(source, spec.id) || registered;
|
|
969
1128
|
}
|
|
970
1129
|
if (spec.requestModelId && spec.requestModelId !== spec.id) {
|
|
971
|
-
index ??=
|
|
972
|
-
registered =
|
|
1130
|
+
index ??= new VariantAliasIndex();
|
|
1131
|
+
registered = index.add(spec.requestModelId, spec.id) || registered;
|
|
973
1132
|
}
|
|
974
|
-
if (registered) index?.
|
|
1133
|
+
if (registered) index?.markFamily(spec.id);
|
|
975
1134
|
}
|
|
976
1135
|
if (index) dynamicAliasIndexes.set(providerId, index);
|
|
977
1136
|
}
|
|
@@ -980,20 +1139,16 @@ function resolveRegisteredVariantAlias(provider: Provider, normalizedModelId: st
|
|
|
980
1139
|
const providerId = provider.toLowerCase();
|
|
981
1140
|
const table = reviewedCollapseTable(provider) ?? reviewedCollapseTable(providerId);
|
|
982
1141
|
return (
|
|
983
|
-
(table ? getAliasIndex(table).
|
|
984
|
-
dynamicAliasIndexes.get(providerId)?.
|
|
1142
|
+
(table ? getAliasIndex(table).resolve(normalizedModelId) : undefined) ??
|
|
1143
|
+
dynamicAliasIndexes.get(providerId)?.resolve(normalizedModelId)
|
|
985
1144
|
);
|
|
986
1145
|
}
|
|
987
1146
|
|
|
988
1147
|
function getAliasIndex(table: VariantCollapseTable): VariantAliasIndex {
|
|
989
1148
|
const cached: unknown = Reflect.get(table, kAliasIndex);
|
|
990
1149
|
if (cached instanceof VariantAliasIndex) return cached;
|
|
991
|
-
const index =
|
|
992
|
-
for (const family of table.families)
|
|
993
|
-
index.familyIds.add(family.id);
|
|
994
|
-
for (const member of family.members) addVariantAlias(index, member, family.id);
|
|
995
|
-
for (const alias of family.extraAliases ?? []) addVariantAlias(index, alias, family.id);
|
|
996
|
-
}
|
|
1150
|
+
const index = new VariantAliasIndex(table.templates);
|
|
1151
|
+
for (const family of table.families) index.addFamily(family);
|
|
997
1152
|
for (const alias in table.providerAliases) {
|
|
998
1153
|
const target = table.providerAliases[alias];
|
|
999
1154
|
if (target !== undefined && alias !== target) index.providerScoped.set(alias.toLowerCase(), target);
|
|
@@ -1057,8 +1212,8 @@ export function resolveBareVariantSelector(modelId: string): BareVariantAliasHit
|
|
|
1057
1212
|
export function getVariantAliasSources(provider: Provider, modelId: string): readonly string[] {
|
|
1058
1213
|
const providerId = provider.toLowerCase();
|
|
1059
1214
|
const table = reviewedCollapseTable(provider) ?? reviewedCollapseTable(providerId);
|
|
1060
|
-
const staticSources = table ? getAliasIndex(table).
|
|
1061
|
-
const dynamicSources = dynamicAliasIndexes.get(providerId)?.
|
|
1215
|
+
const staticSources = table ? getAliasIndex(table).sourcesOf(modelId) : undefined;
|
|
1216
|
+
const dynamicSources = dynamicAliasIndexes.get(providerId)?.sourcesOf(modelId);
|
|
1062
1217
|
if (!staticSources) return dynamicSources ?? [];
|
|
1063
1218
|
if (!dynamicSources) return staticSources;
|
|
1064
1219
|
return [...new Set([...staticSources, ...dynamicSources])];
|
package/src/compat/resolve.ts
CHANGED
|
@@ -846,7 +846,7 @@ function resolveAnthropicPolicy(
|
|
|
846
846
|
allowAnthropicHeaderOverrides: false,
|
|
847
847
|
supportsEagerToolInputStreaming: official,
|
|
848
848
|
supportsLongCacheRetention: official,
|
|
849
|
-
supportsMidConversationSystem: official && facts.anthropicAdaptiveGenAtLeast("4.8"),
|
|
849
|
+
supportsMidConversationSystem: official && !facts.family("sonnet") && facts.anthropicAdaptiveGenAtLeast("4.8"),
|
|
850
850
|
supportsTurnScopedSystem: false,
|
|
851
851
|
supportsMidConversationToolChanges: false,
|
|
852
852
|
supportsPerMessageEffort: false,
|
|
@@ -116,13 +116,14 @@ collapse {
|
|
|
116
116
|
effort-suffix "-minimal" tier="minimal"
|
|
117
117
|
effort-suffix "-max" tier="max" except-bare-prefix="qwen"
|
|
118
118
|
effort-lane-suffix "-fast" "cursor" bare-prefix="cursor-grok"
|
|
119
|
-
effort-family "
|
|
119
|
+
effort-family "cursor" "gpt-5.6-luna"
|
|
120
120
|
routing-variant-suffix "-wm" "openai-codex" "openai-codex-device"
|
|
121
121
|
variant-family "devin" "claude-opus-5" name="Claude Opus 5" { /* reviewed routing */ }
|
|
122
|
+
variant-family "google-antigravity" "gemini-{rev}-flash" name="Gemini {rev} Flash" revision=">=3.6" { /* per-revision template */ }
|
|
122
123
|
provider-alias "devin" "opus" "claude-opus-5"
|
|
123
124
|
}
|
|
124
125
|
```
|
|
125
|
-
`variant-family` declares one reviewed provider-scoped collapsed family: positional provider and logical id, `name=` display name, and a body of `members "a" "b" …` (wire ids in priority order), `route "<tier>" "<wire-id>"` per effort tier (`off` included), and optional `mode`, `efforts`, `default-level`, `default-member`, `retired-members`, `effort-budget "<tier>" <n>`, `requires-effort`, `suppress-when-off`, `no-thinking`, `preserve-absent-effort-routes`, and `extra-aliases`. `provider-alias` maps one provider-scoped selector spelling onto a logical id without making it a family member.
|
|
126
|
+
`variant-family` declares one reviewed provider-scoped collapsed family: positional provider and logical id, `name=` display name, and a body of `members "a" "b" …` (wire ids in priority order), `route "<tier>" "<wire-id>"` per effort tier (`off` included), and optional `mode`, `efforts`, `default-level`, `default-member`, `retired-members`, `effort-budget "<tier>" <n>`, `requires-effort`, `suppress-when-off`, `no-thinking`, `preserve-absent-effort-routes`, and `extra-aliases`. A `{rev}` placeholder in the logical id makes the node a **template**: it is instantiated once per revision found in live ids (`gemini-{rev}-flash` matches `gemini-3.8-flash-low` → family `gemini-3.8-flash`), every wire id in the body and the `name=` carry the same placeholder, and an optional `revision=` constraint (`">=3.6"`) bounds the generations it applies to. A concrete family with the same instantiated id wins over the template. `provider-alias` maps one provider-scoped selector spelling onto a logical id without making it a family member.
|
|
126
127
|
|
|
127
128
|
`thinking-suffix` accepts one non-empty suffix and no properties. `pair-token` declares bounded (possibly infix) tokens naming the thinking sibling of a live bare twin (`sonar-reasoning-pro` beside `sonar-pro`); it drives thinking-pair derivation only — never identity suffix collapse — and negated `no-`/`non-` forms never match. `effort-suffix` additionally requires `tier` with one of the effort values above, and may have `except-bare-prefix`. `routing-variant-suffix` takes one non-empty suffix followed by one or more provider IDs: a wire identifier carrying the suffix on one of those providers is a **routing variant** of its plain identifier — discovery derives base-model metadata from the plain bundled SKU while keeping the suffixed wire identifier for requests; routing variants never participate in effort collapse. `effort-lane-suffix` takes one non-empty lane suffix followed by one or more provider IDs, plus an optional `bare-prefix` gate: on a declared provider, an identifier ending in the lane suffix collapses the effort suffix wedged before the lane token while keeping the lane on the logical id. `effort-family` takes a provider, the canonical logical id, and zero or more exact aliases that fold onto it.
|
|
128
129
|
|
|
@@ -66,17 +66,32 @@ class "gemini" {
|
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
|
+
// Gemini 3.7 Flash is the one Flash revision whose native thinkingLevel does
|
|
70
|
+
// not accept `minimal` (400 THINKING_LEVEL_MINIMAL). Only the direct
|
|
71
|
+
// google-level transports emit `thinkingLevel` on the wire, so the tier is
|
|
72
|
+
// dropped there; budget (anthropic-messages) and reasoning_effort resellers
|
|
73
|
+
// never send the rejected value and keep the four-tier ladder. The collapsed
|
|
74
|
+
// google-antigravity / google-gemini-cli variants remap minimal -> LOW and
|
|
75
|
+
// carry their own explicit ladder, so they are unaffected.
|
|
76
|
+
// https://ai.google.dev/gemini-api/docs/generate-content/thinking#thinking-levels
|
|
77
|
+
on "google" "google-vertex" "opencode-zen" {
|
|
78
|
+
family "flash" {
|
|
79
|
+
revision ">=3.7 <3.8" {
|
|
80
|
+
thinking-efforts "low" "medium" "high"
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
69
84
|
|
|
70
|
-
// Gemini 3 tool calls require the sentinel signature on unsigned replay.
|
|
71
85
|
revision ">=3" {
|
|
72
86
|
// Replaces the Google multimodal function-response baseline.
|
|
73
87
|
multimodal-function-response #true
|
|
74
|
-
requires-skip-thought-signature #true
|
|
75
88
|
// Replaces the Gemini 3+ mandatory-reasoning fallback.
|
|
76
89
|
thinking-requires-effort #true
|
|
77
90
|
}
|
|
91
|
+
// Only public-Gemini-compatible transports accept the bypass sentinel for unsigned calls.
|
|
78
92
|
on "google" "opencode-zen" {
|
|
79
93
|
revision ">=3" {
|
|
94
|
+
requires-skip-thought-signature #true
|
|
80
95
|
supports-function-part-id #true
|
|
81
96
|
}
|
|
82
97
|
}
|
|
@@ -10,7 +10,10 @@ class "glm" {
|
|
|
10
10
|
// GLM-5.2 exposes the default lower tiers plus a genuine max on generic
|
|
11
11
|
// OpenAI-compatible hosts; the zai-dialect and Anthropic-route hosts
|
|
12
12
|
// (Z.ai, Zhipu, Umans, Ollama Cloud, Baseten) serve only high/max.
|
|
13
|
-
|
|
13
|
+
// priority breaks the intentional overlap with provider-level blanket GLM
|
|
14
|
+
// ladders authored for the pre-5.2 census (e.g. alibaba-coding-plan.kdl),
|
|
15
|
+
// so the 5.2 lineage ladder wins on generic hosts that lack a 5.2 residue.
|
|
16
|
+
revision ">=5.2 <5.3" priority=10 {
|
|
14
17
|
thinking-efforts "minimal" "low" "medium" "high" "max"
|
|
15
18
|
}
|
|
16
19
|
on "zai" "zhipu-coding-plan" {
|