@oh-my-pi/pi-catalog 18.2.1 → 18.2.2
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 +12 -0
- package/dist/types/compat/cascade.d.ts +10 -0
- package/dist/types/compat/collapse.d.ts +8 -0
- package/dist/types/compat/resolve.d.ts +10 -0
- package/dist/types/types.d.ts +10 -0
- package/package.json +4 -4
- package/src/build.ts +59 -0
- package/src/compat/axes.ts +1 -0
- package/src/compat/cascade.ts +24 -0
- package/src/compat/collapse.ts +100 -8
- package/src/compat/resolve.ts +14 -1
- package/src/compat/rules/providers/devin.kdl +46 -0
- package/src/compat/rules/providers/kimi-code.kdl +48 -0
- package/src/compat/rules.json +1 -1
- package/src/discovery/devin.ts +22 -4
- package/src/model-cache.ts +40 -84
- package/src/provider-models/openai-compat.ts +203 -13
- package/src/types.ts +10 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [18.2.2] - 2026-09-16
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- OpenAI-compatible model discovery now fills in reasoning-effort tiers for unrecognized models using the shared catalog’s published reasoning options, while preserving explicit discovery metadata and reviewed model rules.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Fixed recovery of corrupted model caches so private backups are preserved and concurrent recovery cannot overwrite a cache that has already been restored.
|
|
14
|
+
- Fixed pricing for Devin (SWE-2, SWE-1.7, and GLM-5.2 High) and Kimi Code models when upstream discovery omits cost information. SWE-2 now reflects its promotional pricing through December 31, 2026, then switches to list pricing on January 1, 2027.
|
|
15
|
+
- Fixed pricing and chat routing for Devin Fusion models so composite models use their own headline rates and supported Fusion lanes connect directly instead of failing through an incompatible routing path.
|
|
16
|
+
|
|
5
17
|
## [18.2.1] - 2026-09-15
|
|
6
18
|
|
|
7
19
|
### Added
|
|
@@ -30,3 +30,13 @@ export declare function resolveCascade(target: ResolveTarget): ResolvedAxes;
|
|
|
30
30
|
* mutable rule data. Bundled target lookups use {@link resolveCascade}.
|
|
31
31
|
*/
|
|
32
32
|
export declare function resolveCascadeRules(cascade: CompiledCascade, target: ResolveTarget): ResolvedAxes;
|
|
33
|
+
/**
|
|
34
|
+
* Whether the effort ladder this target resolves to comes from a rule scoped
|
|
35
|
+
* to the model's identity (a recognized class, family, revision, or an explicit
|
|
36
|
+
* model selector), rather than a provider/api-wide or fallback unknown-class
|
|
37
|
+
* rule that any unrecognized id at that provider inherits.
|
|
38
|
+
*
|
|
39
|
+
* Discovery reads this to tell reviewed tiers apart from a blanket default, so
|
|
40
|
+
* catalog-published tiers can correct the latter and never the former.
|
|
41
|
+
*/
|
|
42
|
+
export declare function hasModelScopedEffortsRule(target: ResolveTarget): boolean;
|
|
@@ -124,6 +124,14 @@ export declare function deriveThinkingPairFamilies<TSpec extends VariantSpecLike
|
|
|
124
124
|
* unrelated carriers (GitHub Copilot `-1m` context variants) never match.
|
|
125
125
|
*/
|
|
126
126
|
export declare function isCollapsedVariantSpec(spec: VariantSpecLike): boolean;
|
|
127
|
+
/**
|
|
128
|
+
* Whether a Cursor wire id names an extended tier that upstream serves only in
|
|
129
|
+
* max mode. The compiled taxonomy identifies `xhigh`/`extra-high`/`max`
|
|
130
|
+
* efforts and their optional service lanes. This is an inference, not an
|
|
131
|
+
* upstream marker: it is the only per-tier signal available for bundled rows
|
|
132
|
+
* and for routes live discovery never advertised.
|
|
133
|
+
*/
|
|
134
|
+
export declare function isCursorMaxModeWireId(wireModelId: string): boolean;
|
|
127
135
|
/**
|
|
128
136
|
* Collapse a full mixed-provider list: per provider, the compiled reviewed table, Cursor's
|
|
129
137
|
* conservative live effort-sibling rule, and the automatic `X`/`X-thinking`
|
|
@@ -13,3 +13,13 @@ export interface ResolvedModelPolicy<TApi extends Api = Api> {
|
|
|
13
13
|
* complete compat record, thinking metadata, and catalog-data corrections.
|
|
14
14
|
*/
|
|
15
15
|
export declare function resolveModelPolicy<TApi extends Api>(spec: ModelSpec<TApi>): ResolvedModelPolicy<TApi>;
|
|
16
|
+
/**
|
|
17
|
+
* Whether reviewed rules know THIS model's effort ladder, as opposed to it
|
|
18
|
+
* inheriting a provider-wide default or {@link resolveThinkingPolicy} falling
|
|
19
|
+
* through to the neutral wire ladder.
|
|
20
|
+
*
|
|
21
|
+
* Discovery uses this to tell "omp knows this model's tiers" apart from "omp
|
|
22
|
+
* is guessing them", so catalog-published tiers can correct the guess without
|
|
23
|
+
* ever overriding reviewed knowledge.
|
|
24
|
+
*/
|
|
25
|
+
export declare function hasModelScopedEffortLadder<TApi extends Api>(spec: ModelSpec<TApi>): boolean;
|
package/dist/types/types.d.ts
CHANGED
|
@@ -1013,6 +1013,16 @@ export interface Model<TApi extends Api = Api> {
|
|
|
1013
1013
|
gitlabDuoWorkflowRootNamespaceId?: string;
|
|
1014
1014
|
/** Cursor `max_mode` request flag returned by `GetUsableModels` for premium models that require max mode. */
|
|
1015
1015
|
cursorMaxMode?: boolean;
|
|
1016
|
+
/**
|
|
1017
|
+
* Per-wire-id `max_mode` markers for the members a collapsed Cursor row
|
|
1018
|
+
* routes to, recorded by `collapseVariants` from live `GetUsableModels`
|
|
1019
|
+
* rows. {@link cursorMaxMode} on a collapsed row is an OR across members,
|
|
1020
|
+
* so it cannot tell a `-low` route that needs no max mode from an Opus
|
|
1021
|
+
* `-fast` route that does; transports look the routed wire id up here
|
|
1022
|
+
* first. Absent on raw rows (their own `cursorMaxMode` already describes
|
|
1023
|
+
* their single wire id) and on bundled snapshots that predate discovery.
|
|
1024
|
+
*/
|
|
1025
|
+
cursorMaxModeRoutes?: Readonly<Record<string, boolean>>;
|
|
1016
1026
|
cost: ModelCost;
|
|
1017
1027
|
/** Premium Copilot requests charged per user-initiated request (defaults to 1). */
|
|
1018
1028
|
premiumMultiplier?: number;
|
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.2.
|
|
4
|
+
"version": "18.2.2",
|
|
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.2.
|
|
39
|
-
"@oh-my-pi/pi-utils": "18.2.
|
|
38
|
+
"@oh-my-pi/omptype": "18.2.2",
|
|
39
|
+
"@oh-my-pi/pi-utils": "18.2.2"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@bgotink/kdl": "0.4.0",
|
|
43
|
-
"@oh-my-pi/pi-ai": "18.2.
|
|
43
|
+
"@oh-my-pi/pi-ai": "18.2.2",
|
|
44
44
|
"@types/bun": "^1.3.14"
|
|
45
45
|
},
|
|
46
46
|
"engines": {
|
package/src/build.ts
CHANGED
|
@@ -24,6 +24,41 @@ function objectPayload(value: unknown): object | undefined {
|
|
|
24
24
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Overwrite seeded fallback rates with the latest dated card whose
|
|
29
|
+
* `effectiveFrom` is already due. Leaves the seed unchanged when no card
|
|
30
|
+
* has started, and never attaches a `timeBased` tariff.
|
|
31
|
+
*/
|
|
32
|
+
function applyEffectiveFallbackRates(
|
|
33
|
+
cost: { input: number; output: number; cacheRead: number; cacheWrite: number },
|
|
34
|
+
effectiveRates: unknown,
|
|
35
|
+
now = Date.now(),
|
|
36
|
+
): void {
|
|
37
|
+
const rates = objectPayload(effectiveRates);
|
|
38
|
+
if (rates === undefined) return;
|
|
39
|
+
let latestFrom = Number.NEGATIVE_INFINITY;
|
|
40
|
+
let latest: object | undefined;
|
|
41
|
+
for (const entry of Object.values(rates)) {
|
|
42
|
+
const payload = objectPayload(entry);
|
|
43
|
+
if (payload === undefined) continue;
|
|
44
|
+
const date = Reflect.get(payload, "effectiveFrom");
|
|
45
|
+
if (typeof date !== "string") continue;
|
|
46
|
+
const from = Date.parse(date);
|
|
47
|
+
if (!Number.isFinite(from) || from > now || from < latestFrom) continue;
|
|
48
|
+
latestFrom = from;
|
|
49
|
+
latest = payload;
|
|
50
|
+
}
|
|
51
|
+
if (latest === undefined) return;
|
|
52
|
+
const input = numberField(latest, "input");
|
|
53
|
+
if (input !== undefined) cost.input = input;
|
|
54
|
+
const output = numberField(latest, "output");
|
|
55
|
+
if (output !== undefined) cost.output = output;
|
|
56
|
+
const cacheRead = numberField(latest, "cacheRead");
|
|
57
|
+
if (cacheRead !== undefined) cost.cacheRead = cacheRead;
|
|
58
|
+
const cacheWrite = numberField(latest, "cacheWrite");
|
|
59
|
+
if (cacheWrite !== undefined) cost.cacheWrite = cacheWrite;
|
|
60
|
+
}
|
|
61
|
+
|
|
27
62
|
/** Narrow a compiled `input-modalities` axis value to the model input union. */
|
|
28
63
|
function isInputModalities(value: unknown): value is ("text" | "image")[] {
|
|
29
64
|
return Array.isArray(value) && value.every(entry => entry === "text" || entry === "image");
|
|
@@ -139,6 +174,30 @@ export function applyCatalogCorrections(
|
|
|
139
174
|
const cacheWrite = numberField(patch, "cacheWrite");
|
|
140
175
|
if (cacheWrite !== undefined) model.cost.cacheWrite = cacheWrite;
|
|
141
176
|
}
|
|
177
|
+
const fallback = objectPayload(catalog.costFallback);
|
|
178
|
+
if (fallback !== undefined) {
|
|
179
|
+
const base = model.cost;
|
|
180
|
+
const hasTokenPrice = base.input !== 0 || base.output !== 0 || base.cacheRead !== 0 || base.cacheWrite !== 0;
|
|
181
|
+
if (!hasTokenPrice) {
|
|
182
|
+
// Upstream reported no token price (plan-included or promo-free
|
|
183
|
+
// rows): seed the reviewed list price instead of overwriting real
|
|
184
|
+
// discovery data the way `cost-patch` would.
|
|
185
|
+
model.cost = { ...model.cost };
|
|
186
|
+
const input = numberField(fallback, "input");
|
|
187
|
+
if (input !== undefined) model.cost.input = input;
|
|
188
|
+
const output = numberField(fallback, "output");
|
|
189
|
+
if (output !== undefined) model.cost.output = output;
|
|
190
|
+
const cacheRead = numberField(fallback, "cacheRead");
|
|
191
|
+
if (cacheRead !== undefined) model.cost.cacheRead = cacheRead;
|
|
192
|
+
const cacheWrite = numberField(fallback, "cacheWrite");
|
|
193
|
+
if (cacheWrite !== undefined) model.cost.cacheWrite = cacheWrite;
|
|
194
|
+
// Dated fallback rates overwrite the seeded numbers when they have
|
|
195
|
+
// already taken effect. They are not a recurring tariff: wrapping
|
|
196
|
+
// them in `timeBased` with empty peak windows would report
|
|
197
|
+
// permanent off-peak and never wake at the dated boundary.
|
|
198
|
+
applyEffectiveFallbackRates(model.cost, Reflect.get(fallback, "effectiveRates"));
|
|
199
|
+
}
|
|
200
|
+
}
|
|
142
201
|
if (catalog.timeBased !== undefined) {
|
|
143
202
|
model.cost = { ...model.cost, timeBased: materializeTimeBasedCost(catalog.timeBased) };
|
|
144
203
|
}
|
package/src/compat/axes.ts
CHANGED
|
@@ -288,6 +288,7 @@ export const AXES: Readonly<Record<string, AxisDef>> = {
|
|
|
288
288
|
"context-promotion-target": { key: "contextPromotionTarget", set: "catalog", shape: "scalar" },
|
|
289
289
|
"context-window-floor": { key: "contextWindowFloor", set: "catalog", shape: "scalar" },
|
|
290
290
|
"cost-patch": { key: "costPatch", set: "catalog", shape: "object" },
|
|
291
|
+
"cost-fallback": { key: "costFallback", set: "catalog", shape: "object" },
|
|
291
292
|
"delegation-bias": { key: "delegationBias", set: "catalog", shape: "scalar", values: DELEGATION_BIASES },
|
|
292
293
|
"edit-prompt-variant": { key: "editPromptVariant", set: "catalog", shape: "scalar", values: ["full", "compact"] },
|
|
293
294
|
"edit-revision": { key: "editRevision", set: "catalog", shape: "scalar" },
|
package/src/compat/cascade.ts
CHANGED
|
@@ -325,6 +325,30 @@ export function resolveCascadeRules(cascade: CompiledCascade, target: ResolveTar
|
|
|
325
325
|
return cloneAxes(resolveOverIndex(buildRuleIndex(cascade), target));
|
|
326
326
|
}
|
|
327
327
|
|
|
328
|
+
/**
|
|
329
|
+
* Whether the effort ladder this target resolves to comes from a rule scoped
|
|
330
|
+
* to the model's identity (a recognized class, family, revision, or an explicit
|
|
331
|
+
* model selector), rather than a provider/api-wide or fallback unknown-class
|
|
332
|
+
* rule that any unrecognized id at that provider inherits.
|
|
333
|
+
*
|
|
334
|
+
* Discovery reads this to tell reviewed tiers apart from a blanket default, so
|
|
335
|
+
* catalog-published tiers can correct the latter and never the former.
|
|
336
|
+
*/
|
|
337
|
+
export function hasModelScopedEffortsRule(target: ResolveTarget): boolean {
|
|
338
|
+
const winners: WinnerTable = {};
|
|
339
|
+
for (const { rule, rank } of rankRelevantRules(getRuleIndex(), prepareTarget(target))) {
|
|
340
|
+
contest(winners, rule.compiled.thinking, rank, rule, target);
|
|
341
|
+
}
|
|
342
|
+
const winner = winners.efforts?.rule.compiled;
|
|
343
|
+
if (winner === undefined) return false;
|
|
344
|
+
return (
|
|
345
|
+
(winner.class !== undefined && winner.class !== "unknown") ||
|
|
346
|
+
winner.family !== undefined ||
|
|
347
|
+
winner.revision !== undefined ||
|
|
348
|
+
winner.models !== undefined
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
|
|
328
352
|
function resolveOverIndex(index: RuleIndex, target: ResolveTarget): ResolvedAxes {
|
|
329
353
|
const ranked = rankRelevantRules(index, prepareTarget(target));
|
|
330
354
|
let reasoning = target.reasoning === true;
|
package/src/compat/collapse.ts
CHANGED
|
@@ -734,6 +734,80 @@ function reconcileDefaultMember<TSpec extends VariantSpecLike>(
|
|
|
734
734
|
return spec;
|
|
735
735
|
}
|
|
736
736
|
|
|
737
|
+
/**
|
|
738
|
+
* Whether a Cursor wire id names an extended tier that upstream serves only in
|
|
739
|
+
* max mode. The compiled taxonomy identifies `xhigh`/`extra-high`/`max`
|
|
740
|
+
* efforts and their optional service lanes. This is an inference, not an
|
|
741
|
+
* upstream marker: it is the only per-tier signal available for bundled rows
|
|
742
|
+
* and for routes live discovery never advertised.
|
|
743
|
+
*/
|
|
744
|
+
export function isCursorMaxModeWireId(wireModelId: string): boolean {
|
|
745
|
+
const effort = collapseVariantId("cursor", wireModelId).effort;
|
|
746
|
+
return effort === Effort.XHigh || effort === Effort.Max;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
/**
|
|
750
|
+
* Recover Cursor's max-mode marker for a bundled collapsed row. The bundled
|
|
751
|
+
* snapshot may contain only the logical row, but its effort routing still
|
|
752
|
+
* records the wire ids, so {@link isCursorMaxModeWireId} preserves the
|
|
753
|
+
* transport invariant even when live discovery contributes no raw members.
|
|
754
|
+
*/
|
|
755
|
+
function reconcileCursorMaxModeFromRouting<TSpec extends VariantSpecLike>(spec: TSpec): TSpec {
|
|
756
|
+
if (spec.provider !== "cursor" || spec.cursorMaxMode === true) return spec;
|
|
757
|
+
const routing = spec.thinking?.effortRouting;
|
|
758
|
+
if (routing === undefined) return spec;
|
|
759
|
+
const hasMaxModeRoute = Object.values(routing).some(
|
|
760
|
+
(target): target is string => typeof target === "string" && isCursorMaxModeWireId(target),
|
|
761
|
+
);
|
|
762
|
+
return hasMaxModeRoute ? { ...spec, cursorMaxMode: true } : spec;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
/**
|
|
766
|
+
* Index the discovered `max_mode` marker of every live Cursor member by its own
|
|
767
|
+
* wire id. The collapsed row's `cursorMaxMode` is an OR across members, so it
|
|
768
|
+
* says nothing per tier; the transport needs the marker addressable by the wire
|
|
769
|
+
* id it actually sends, because upstream marks tiers the slug cannot identify
|
|
770
|
+
* (the whole Opus `-fast` lane is max-mode, `-low-fast` included). Returns
|
|
771
|
+
* `undefined` when no member carries a marker, so unmarked rosters add no field.
|
|
772
|
+
*/
|
|
773
|
+
function cursorMaxModeRoutesOf<TSpec extends VariantSpecLike>(
|
|
774
|
+
provider: string,
|
|
775
|
+
memberSpecs: readonly TSpec[],
|
|
776
|
+
): Record<string, boolean> | undefined {
|
|
777
|
+
if (provider !== "cursor") return undefined;
|
|
778
|
+
let routes: Record<string, boolean> | undefined;
|
|
779
|
+
for (const member of memberSpecs) {
|
|
780
|
+
if (member.cursorMaxMode === undefined) continue;
|
|
781
|
+
routes ??= {};
|
|
782
|
+
routes[member.id] = member.cursorMaxMode;
|
|
783
|
+
}
|
|
784
|
+
return routes;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
/**
|
|
788
|
+
* Lift Cursor's max-mode markers from live member rows onto an already-collapsed
|
|
789
|
+
* snapshot. Bundled catalog and cache rows froze the flag from `memberSpecs[0]`
|
|
790
|
+
* — the `-none`/`-low` tier — so the committed `gpt-5.6-*` / `cursor-grok-*`
|
|
791
|
+
* rows carry `cursorMaxMode: false`. The existing-collapsed pass-through keeps
|
|
792
|
+
* the snapshot verbatim, so a live `GetUsableModels` roster that marks the
|
|
793
|
+
* `-xhigh`/`-max` tiers would be discarded and max-tier requests would keep
|
|
794
|
+
* sending `max_mode: false` on a max-mode-only wire id. The row-level flag
|
|
795
|
+
* mirrors the fresh-collapse aggregation: only the positive case is lifted, so
|
|
796
|
+
* a roster that marks nothing leaves the snapshot alone. The per-wire-id
|
|
797
|
+
* markers are merged on top of the snapshot's own so live rows win per route.
|
|
798
|
+
* Returns `spec` by reference when unchanged.
|
|
799
|
+
*/
|
|
800
|
+
function reconcileCursorMaxMode<TSpec extends VariantSpecLike>(spec: TSpec, memberSpecs: readonly TSpec[]): TSpec {
|
|
801
|
+
const routes = cursorMaxModeRoutesOf(spec.provider, memberSpecs);
|
|
802
|
+
const lifts = spec.cursorMaxMode !== true && memberSpecs.some(member => member.cursorMaxMode === true);
|
|
803
|
+
if (routes === undefined && !lifts) return spec;
|
|
804
|
+
return {
|
|
805
|
+
...spec,
|
|
806
|
+
...(lifts ? { cursorMaxMode: true } : {}),
|
|
807
|
+
...(routes === undefined ? {} : { cursorMaxModeRoutes: { ...spec.cursorMaxModeRoutes, ...routes } }),
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
|
|
737
811
|
/**
|
|
738
812
|
* Collapse every family in `table` found in `specs`. Non-member specs pass
|
|
739
813
|
* through verbatim (by reference), order preserved; the collapsed spec
|
|
@@ -776,7 +850,9 @@ function collapseWithTable<TSpec extends VariantSpecLike>(
|
|
|
776
850
|
// Recycled extraAliases rows are healed in a later pass.
|
|
777
851
|
const refreshed =
|
|
778
852
|
existing !== undefined && existingCollapsed
|
|
779
|
-
?
|
|
853
|
+
? reconcileCursorMaxModeFromRouting(
|
|
854
|
+
reconcileDefaultMember(refreshCollapsedThinking(reconciled ?? existing, family, retired), family),
|
|
855
|
+
)
|
|
780
856
|
: reconciled;
|
|
781
857
|
if (refreshed !== undefined && refreshed !== existing) {
|
|
782
858
|
familyIdBySpecId.set(family.id, family.id);
|
|
@@ -788,20 +864,26 @@ function collapseWithTable<TSpec extends VariantSpecLike>(
|
|
|
788
864
|
for (const id of rawPresent) familyIdBySpecId.set(id, family.id);
|
|
789
865
|
if (existing) familyIdBySpecId.set(family.id, family.id);
|
|
790
866
|
|
|
867
|
+
const memberSpecs: TSpec[] = [];
|
|
868
|
+
for (const id of rawPresent) {
|
|
869
|
+
const member = byId.get(id);
|
|
870
|
+
if (member !== undefined) memberSpecs.push(member);
|
|
871
|
+
}
|
|
872
|
+
|
|
791
873
|
if (existingCollapsed && reconciled !== undefined) {
|
|
792
874
|
// Mixed input: the collapsed entry wins; stale raw members are deduped
|
|
793
875
|
// away. Retired targets are re-pointed first, then the default wire id
|
|
794
876
|
// prefers the family's declared member when live and otherwise falls
|
|
795
|
-
// back to the first member the account actually advertised.
|
|
796
|
-
|
|
877
|
+
// back to the first member the account actually advertised. The live
|
|
878
|
+
// members still own `cursorMaxMode`: the snapshot froze it from the
|
|
879
|
+
// lowest tier.
|
|
880
|
+
replacement.set(
|
|
881
|
+
family.id,
|
|
882
|
+
reconcileCursorMaxMode(reconcileDefaultMember(reconciled, family, new Set(rawPresent)), memberSpecs),
|
|
883
|
+
);
|
|
797
884
|
continue;
|
|
798
885
|
}
|
|
799
886
|
|
|
800
|
-
const memberSpecs: TSpec[] = [];
|
|
801
|
-
for (const id of rawPresent) {
|
|
802
|
-
const member = byId.get(id);
|
|
803
|
-
if (member !== undefined) memberSpecs.push(member);
|
|
804
|
-
}
|
|
805
887
|
const firstMember = memberSpecs[0];
|
|
806
888
|
if (firstMember === undefined) continue;
|
|
807
889
|
const presentSet = new Set(rawPresent);
|
|
@@ -836,6 +918,14 @@ function collapseWithTable<TSpec extends VariantSpecLike>(
|
|
|
836
918
|
if (memberSpecs.some(spec => spec.input.includes("text"))) input.push("text");
|
|
837
919
|
if (memberSpecs.some(spec => spec.input.includes("image"))) input.push("image");
|
|
838
920
|
|
|
921
|
+
// `cursorMaxMode` gates the `max_mode` request flag. The collapsed row
|
|
922
|
+
// otherwise inherits `memberSpecs[0]`, so a family whose max-mode member
|
|
923
|
+
// is not the first one would advertise `false` and send `max_mode: false`
|
|
924
|
+
// on a max-mode wire id. Only the positive case is aggregated — an
|
|
925
|
+
// unmarked family keeps whatever the first member carried. The OR loses
|
|
926
|
+
// which tier needed it, so the members' own markers are kept per wire id.
|
|
927
|
+
const cursorMaxMode = memberSpecs.some(spec => spec.cursorMaxMode === true) ? true : undefined;
|
|
928
|
+
const cursorMaxModeRoutes = cursorMaxModeRoutesOf(firstMember.provider, memberSpecs);
|
|
839
929
|
const collapsed: TSpec = {
|
|
840
930
|
...firstMember,
|
|
841
931
|
id: family.id,
|
|
@@ -844,6 +934,8 @@ function collapseWithTable<TSpec extends VariantSpecLike>(
|
|
|
844
934
|
input,
|
|
845
935
|
contextWindow: maxOrNull(memberSpecs.map(spec => spec.contextWindow)),
|
|
846
936
|
maxTokens: maxOrNull(memberSpecs.map(spec => spec.maxTokens)),
|
|
937
|
+
...(cursorMaxMode === undefined ? {} : { cursorMaxMode }),
|
|
938
|
+
...(cursorMaxModeRoutes === undefined ? {} : { cursorMaxModeRoutes }),
|
|
847
939
|
};
|
|
848
940
|
// The default wire id is the family's declared `defaultMember` when live,
|
|
849
941
|
// else the highest-priority live member. Omitted when it equals the
|
package/src/compat/resolve.ts
CHANGED
|
@@ -34,7 +34,7 @@ import type {
|
|
|
34
34
|
import { isAnthropicSigningProxyUrl, isAzureAnthropicRoute, isOfficialAnthropicApiUrl } from "./anthropic";
|
|
35
35
|
import { applyCompatOverrides } from "./apply";
|
|
36
36
|
import { API_COMPAT_RECORDS, AXES, type CompatRecordName } from "./axes";
|
|
37
|
-
import { resolveCascade } from "./cascade";
|
|
37
|
+
import { hasModelScopedEffortsRule, resolveCascade } from "./cascade";
|
|
38
38
|
import { compareRevision, parseRevision, type Revision } from "./revision";
|
|
39
39
|
import { classifyModel, stripThinkingVariantSuffix } from "./taxonomy";
|
|
40
40
|
import type { ModelIdentity, ResolvedAxes, ResolveTarget } from "./types";
|
|
@@ -1271,3 +1271,16 @@ export function resolveModelPolicy(spec: ModelSpec<Api>): ResolvedModelPolicy<Ap
|
|
|
1271
1271
|
catalog: axes.catalog,
|
|
1272
1272
|
};
|
|
1273
1273
|
}
|
|
1274
|
+
|
|
1275
|
+
/**
|
|
1276
|
+
* Whether reviewed rules know THIS model's effort ladder, as opposed to it
|
|
1277
|
+
* inheriting a provider-wide default or {@link resolveThinkingPolicy} falling
|
|
1278
|
+
* through to the neutral wire ladder.
|
|
1279
|
+
*
|
|
1280
|
+
* Discovery uses this to tell "omp knows this model's tiers" apart from "omp
|
|
1281
|
+
* is guessing them", so catalog-published tiers can correct the guess without
|
|
1282
|
+
* ever overriding reviewed knowledge.
|
|
1283
|
+
*/
|
|
1284
|
+
export function hasModelScopedEffortLadder<TApi extends Api>(spec: ModelSpec<TApi>): boolean {
|
|
1285
|
+
return hasModelScopedEffortsRule(buildResolveTarget(spec, resolveIdentity(spec)));
|
|
1286
|
+
}
|
|
@@ -39,4 +39,50 @@ provider "devin" {
|
|
|
39
39
|
supports-parallel-tool-calls #true
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
// Devin omits cost dimensions for plan-included models on lower tiers
|
|
45
|
+
// (PRO reports $0 for SWE-2, SWE-1.7, and GLM-5.2 High). `cost-fallback`
|
|
46
|
+
// seeds the enterprise-tier list price only when upstream reports no
|
|
47
|
+
// token price, so accounts whose discovery does carry dimensions keep
|
|
48
|
+
// their real rates.
|
|
49
|
+
|
|
50
|
+
// SWE-2: enterprise promo is 75% off list through 2026-12-31; list
|
|
51
|
+
// pricing ($3/$15, $0.30 cache read) applies from 2027-01-01.
|
|
52
|
+
models "swe-2*" {
|
|
53
|
+
cost-fallback {
|
|
54
|
+
input 0.75
|
|
55
|
+
output 3.75
|
|
56
|
+
cache-read 0.075
|
|
57
|
+
cache-write 0.75
|
|
58
|
+
effective-rates {
|
|
59
|
+
list-price {
|
|
60
|
+
effective-from "2027-01-01T00:00:00Z"
|
|
61
|
+
input 3
|
|
62
|
+
output 15
|
|
63
|
+
cache-read 0.3
|
|
64
|
+
cache-write 3
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// SWE-1.7 and GLM-5.2 High: plan-included on PRO, no published promo
|
|
71
|
+
// schedule — enterprise list is the only published per-token price.
|
|
72
|
+
models "swe-1-7" "swe-1-7-medium" {
|
|
73
|
+
cost-fallback {
|
|
74
|
+
input 0.5
|
|
75
|
+
output 2.5
|
|
76
|
+
cache-read 0.2
|
|
77
|
+
cache-write 0.5
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
models "glm-5-2" {
|
|
81
|
+
cost-fallback {
|
|
82
|
+
input 1.4
|
|
83
|
+
output 4.4
|
|
84
|
+
cache-read 0.26
|
|
85
|
+
cache-write 1.4
|
|
86
|
+
}
|
|
87
|
+
}
|
|
42
88
|
}
|
|
@@ -39,4 +39,52 @@ provider "kimi-code" {
|
|
|
39
39
|
thinking-efforts "minimal" "low" "medium" "high"
|
|
40
40
|
thinking-format "zai"
|
|
41
41
|
}
|
|
42
|
+
|
|
43
|
+
// /coding/v1/models carries no pricing. `cost-fallback` seeds the public
|
|
44
|
+
// Moonshot list price only when discovery reports $0, so a future priced
|
|
45
|
+
// envelope keeps its real rates. `kimi-for-coding` is K2.8 Preview — no
|
|
46
|
+
// public API SKU exists yet, so it uses the K2.7 Code line rate.
|
|
47
|
+
models "k3" "k3-256k" {
|
|
48
|
+
cost-fallback {
|
|
49
|
+
input 3.0
|
|
50
|
+
output 15.0
|
|
51
|
+
cache-read 0.3
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
models "kimi-for-coding" {
|
|
55
|
+
cost-fallback {
|
|
56
|
+
input 0.95
|
|
57
|
+
output 4.0
|
|
58
|
+
cache-read 0.19
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
models "kimi-for-coding-highspeed" {
|
|
62
|
+
cost-fallback {
|
|
63
|
+
input 1.9
|
|
64
|
+
output 8.0
|
|
65
|
+
cache-read 0.38
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Moonshot K2 list (`kimi-k2-0711-preview` / `kimi-k2-0905-preview`), not K2.6.
|
|
69
|
+
models "kimi-k2" {
|
|
70
|
+
cost-fallback {
|
|
71
|
+
input 0.6
|
|
72
|
+
output 2.5
|
|
73
|
+
cache-read 0.15
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
models "kimi-k2.5" {
|
|
77
|
+
cost-fallback {
|
|
78
|
+
input 0.6
|
|
79
|
+
output 3.0
|
|
80
|
+
cache-read 0.1
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
models "kimi-k2-turbo-preview" {
|
|
84
|
+
cost-fallback {
|
|
85
|
+
input 2.4
|
|
86
|
+
output 10.0
|
|
87
|
+
cache-read 0.6
|
|
88
|
+
}
|
|
89
|
+
}
|
|
42
90
|
}
|