@oh-my-pi/pi-catalog 18.10.3 → 18.10.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,35 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [18.10.4] - 2026-10-13
6
+
7
+ ### Added
8
+
9
+ - Added Muse Code's subscription-only Muse Wonder Full and Wonder contributor tiers with the `max` effort, recording API-equivalent cost instead of $0: Wonder Full at Muse Wonder's $4 input / $0.20 cached / $20 output per 1M tokens, contributor tiers at the measured $1.60 / $0.08 / $8 ([#15522](https://github.com/can1357/oh-my-pi/pull/15522) by [@eggpeat](https://github.com/eggpeat))
10
+ - Added the `classifier-cot-extraction` rule axis, set for Claude, marking models whose classifier refuses earlier reasoning quoted back into their input ([#15436](https://github.com/can1357/oh-my-pi/pull/15436) by [@will-bogusz](https://github.com/will-bogusz))
11
+ - Added Gemini 4 Argon pricing on Cursor ($2 input / $0.10 cached / $10 output per 1M tokens) and Google Antigravity, which now uses Google's first-party rate instead of showing $0 ([#15620](https://github.com/can1357/oh-my-pi/pull/15620) by [@eggpeat](https://github.com/eggpeat))
12
+ - Added scheduled list-price changes to `cost-patch`, as `cost-fallback` already allowed: a dated `effective-rates` card sets the price from its date, and each request is priced at the card in force when it was made, even on models loaded before the change, on rebuilt models, and on Cursor lanes priced from a sibling's card. A malformed dated card now fails the model build instead of silently keeping the old price. `getEffectiveTokenCost` returns that card for price displays, and `hasRecurringTariff` tells these dated-only schedules apart from peak/off-peak tariffs, which alone show a status-line arrow ([#15620](https://github.com/can1357/oh-my-pi/pull/15620) by [@eggpeat](https://github.com/eggpeat))
13
+
14
+ ### Changed
15
+
16
+ - Devin models with a long-context price tier (GPT-5.4 through 5.6 standard lanes, GPT-6 through 6.1, Gemini Pro, and Grok 4.5 through 4.7) now default to a 272K or 200K context window, where that pricing starts; run `/extended-context on` to use the full window at long-context rates ([#14891](https://github.com/can1357/oh-my-pi/pull/14891) by [@eggpeat](https://github.com/eggpeat)).
17
+ - Devin SWE-2, SWE-1.7, and GLM-5.2 fallback pricing no longer charges cache writes at the input rate, since Devin publishes no cache-write rate for them ([#14891](https://github.com/can1357/oh-my-pi/pull/14891) by [@eggpeat](https://github.com/eggpeat)).
18
+ - Google Antigravity now lists Gemini 4 Argon once, with selectable low, medium, and high thinking levels, instead of a separate entry per thinking tier ([#15620](https://github.com/can1357/oh-my-pi/pull/15620) by [@eggpeat](https://github.com/eggpeat))
19
+ - Gemini 4 Argon on Google, Vertex, Google Antigravity, and Cursor moves to Google's $4 input / $0.20 cached / $20 output list price on 2026-12-01 (midnight US Pacific), including requests on models loaded before then, without a catalog update ([#15620](https://github.com/can1357/oh-my-pi/pull/15620) by [@eggpeat](https://github.com/eggpeat))
20
+ - Devin SWE-2's promotional fallback price now ends on 2027-01-01 for requests on models loaded before then too ([#15620](https://github.com/can1357/oh-my-pi/pull/15620) by [@eggpeat](https://github.com/eggpeat))
21
+
22
+ ### Fixed
23
+
24
+ - Fixed Devin Claude and GPT-5.6 through GPT-6 models pricing prompt-cache writes at $0; they now use Devin's published rate of 1.25x input, and Adaptive uses its input rate ([#14891](https://github.com/can1357/oh-my-pi/pull/14891) by [@eggpeat](https://github.com/eggpeat)).
25
+ - Fixed Devin GPT-5.4 through GPT-6.1, Gemini Pro, and Grok 4.5 through 4.7 models missing the long-context price tiers Devin publishes above 272K and 200K prompt tokens ([#14891](https://github.com/can1357/oh-my-pi/pull/14891) by [@eggpeat](https://github.com/eggpeat)).
26
+ - Fixed Devin Fusion pairings ignoring their lead model's rules, including its cache-write rate and long-context tier ([#14891](https://github.com/can1357/oh-my-pi/pull/14891) by [@eggpeat](https://github.com/eggpeat)).
27
+ - Fixed Kilo and NanoGPT models showing stale or missing prices (for example Gemini 4 Argon at $4/$20 instead of $2/$10, and many models at $0); prices now come from each gateway's live model list ([#15623](https://github.com/can1357/oh-my-pi/pull/15623) by [@eggpeat](https://github.com/eggpeat))
28
+ - Fixed NanoGPT models showing their raw id as the name and no context window (disabling auto-compaction) when the bundled catalog lacked them; names, context windows and output limits now come from NanoGPT's live model list ([#15623](https://github.com/can1357/oh-my-pi/pull/15623) by [@eggpeat](https://github.com/eggpeat))
29
+
30
+ ### Removed
31
+
32
+ - Removed the Codex `toolMode` model field (`code_mode_only`); Codex discovery no longer maps `tool_mode`
33
+
5
34
  ## [18.10.3] - 2026-10-12
6
35
 
7
36
  ### Breaking Changes
package/README.md CHANGED
@@ -47,12 +47,14 @@ The `models` subpath (also exported from the root) provides timestamp-aware pric
47
47
  | `calculateCost(model, usage, timestamp?)` | Updates and returns `usage.cost` using `model.cost`. |
48
48
  | `calculateUsageCost(cost, usage, timestamp?)` | Updates and returns `usage.cost` using a `ModelCost`. |
49
49
  | `calculateUncachedInputCost(cost, promptInputTokens, timestamp?)` | Returns the cost of a fully uncached prompt. |
50
- | `getTimeBasedPricingPeriod(cost, timestamp?)` | Returns `"peak"`, `"off-peak"`, or `undefined` without a schedule. |
50
+ | `getEffectiveTokenCost(cost, timestamp?)` | Returns the base card in force: the latest due effective rate, else the row's own legs. |
51
+ | `getTimeBasedPricingPeriod(cost, timestamp?)` | Returns `"peak"`, `"off-peak"`, or `undefined` without a recurring tariff. |
52
+ | `hasRecurringTariff(schedule)` | Whether a `TimeBasedCost` carries a peak/off-peak tariff rather than only dated rate cards. |
51
53
  | `getNextTimeBasedPricingTransition(cost, timestamp?)` | Returns the next actual peak/off-peak change strictly after the timestamp, or `undefined` if none exists. |
52
54
 
53
55
  Timestamps are Unix milliseconds; omitted timestamps use the current time for scheduled pricing. Flat token prices are unaffected. Pricing selects the latest applicable effective rate card, then its long-context tier, then the peak/off-peak multiplier. A transition query concerns the recurring tariff, not dated rate-card changes.
54
56
 
55
- `ModelCost.timeBased` is optional typed metadata (`TimeBasedCost`): `offPeakMultiplier`, `peakWindows` (UTC `weekdays`, Sunday = 0, and start-inclusive/end-exclusive `startMinute`/`endMinute`), and optional `effectiveRates`. Each effective rate is a complete `TokenCost` with an `effectiveFrom` Unix-millisecond timestamp and optional `longContext` tier, replacing the base card from that instant.
57
+ `ModelCost.timeBased` is optional typed metadata (`TimeBasedCost`): `offPeakMultiplier`, `peakWindows` (UTC `weekdays`, Sunday = 0, and start-inclusive/end-exclusive `startMinute`/`endMinute`), and optional `effectiveRates`. Each effective rate is a complete `TokenCost` with an `effectiveFrom` Unix-millisecond timestamp and optional `longContext` tier, replacing the base card from that instant. A schedule with no peak windows at a 1x off-peak multiplier has no recurring tariff, only dated rate cards: `cost-patch`/`cost-fallback` `effective-rates` compile to one, so a row built before a scheduled price change still prices later requests at the new card.
56
58
 
57
59
  Pass the request-start timestamp when estimating request usage, then preserve the resulting monetary amounts rather than repricing history at display time. OMP does this using the assistant message timestamp; it is an estimation convention, not a claim about server billing across boundaries. Prefer monetary costs reported by a provider when available.
58
60
 
@@ -9,8 +9,9 @@
9
9
  import { type Api, type Model, type ModelSpec } from "./types.js";
10
10
  /**
11
11
  * Applies reviewed catalog-data value corrections (`cost-patch`,
12
- * `cache-read-at-input-rate`, `limits-patch`, `long-context-cost`,
13
- * `context-window-floor`, `input-modalities`) onto an upstream-sourced spec. Applied by
12
+ * `cache-read-at-input-rate`, `cache-write-rate`, `limits-patch`,
13
+ * `long-context-cost`, `context-window-floor`, `input-modalities`) onto an
14
+ * upstream-sourced spec. Applied by
14
15
  * `buildModel` to every upstream-sourced spec; user-authored overrides are
15
16
  * recomposed after building by the override applicators, so explicit user
16
17
  * limits and pricing still win.
@@ -1,5 +1,5 @@
1
1
  import MODELS from "./models.json";
2
- import type { Api, KnownProvider, Model, ModelCost, ModelPricingStatus, Usage } from "./types.js";
2
+ import type { Api, KnownProvider, Model, ModelCost, ModelPricingStatus, TimeBasedCost, TokenCost, Usage } from "./types.js";
3
3
  export type GeneratedProvider = keyof typeof MODELS;
4
4
  /** Whether `provider` has bundled rows in models.json (e.g. a provider id authored in KDL). */
5
5
  export declare function isGeneratedProvider(provider: string): provider is GeneratedProvider;
@@ -8,6 +8,21 @@ export declare function getBundledProviders(): KnownProvider[];
8
8
  export declare function getBundledModels(provider: GeneratedProvider): Model<Api>[];
9
9
  /** Resolve display semantics without mistaking an absent rate card for a free model. */
10
10
  export declare function getModelPricingStatus(model: Pick<Model<Api>, "cost" | "pricingStatus">): ModelPricingStatus;
11
+ /**
12
+ * The base token card in force at `timestamp` (Unix ms, default now): the
13
+ * latest due `timeBased` effective rate, else the row's own legs. Price
14
+ * displays read this rather than the scalar legs, which keep the card current
15
+ * when the row was built while bundles, caches, and long sessions outlive it.
16
+ * Applies no long-context tier or peak/off-peak multiplier.
17
+ */
18
+ export declare function getEffectiveTokenCost(cost: ModelCost, timestamp?: number): TokenCost;
19
+ /**
20
+ * Whether a schedule carries a recurring peak/off-peak tariff. A schedule with
21
+ * no peak windows at a 1x off-peak multiplier only carries dated rate cards
22
+ * (`cost-patch`/`cost-fallback` `effective-rates`): its price changes on a
23
+ * date, never by time of day.
24
+ */
25
+ export declare function hasRecurringTariff(schedule: TimeBasedCost): boolean;
11
26
  /** Return the recurring UTC tariff period, independently of its monetary multiplier. */
12
27
  export declare function getTimeBasedPricingPeriod(cost: ModelCost, timestamp?: number): "peak" | "off-peak" | undefined;
13
28
  /** Return the next actual peak/off-peak change strictly after the Unix-ms timestamp. */
@@ -1,5 +1,41 @@
1
- import type { TimeBasedCost } from "./types.js";
1
+ import type { EffectiveTokenCost, ModelCost, TimeBasedCost, TokenCost } from "./types.js";
2
2
  /** Validate serialized schedules before admitting cached model rows. */
3
3
  export declare function isTimeBasedCost(value: unknown): value is TimeBasedCost;
4
4
  /** KDL object children are named records; normalize them once while constructing the model. */
5
5
  export declare function materializeTimeBasedCost(value: unknown): TimeBasedCost;
6
+ /**
7
+ * The latest `effectiveRates` card of a materialized schedule due at
8
+ * `timestamp`, or undefined before the first one (or without a schedule).
9
+ */
10
+ export declare function dueEffectiveRate(schedule: TimeBasedCost | undefined, timestamp: number): EffectiveTokenCost | undefined;
11
+ /** A `cost-patch`/`cost-fallback` dated card: the legs it overwrites from `effectiveFrom` (Unix ms). */
12
+ export interface DatedCostCard extends Partial<TokenCost> {
13
+ effectiveFrom: number;
14
+ }
15
+ /**
16
+ * Parse the compiled `effective-rates` block of a `cost-patch` or
17
+ * `cost-fallback` payload into dated cards, ascending by date. Throws on a
18
+ * malformed card, so a typo in a rule fails the build instead of silently
19
+ * leaving the old price in force.
20
+ */
21
+ export declare function parseDatedCostCards(costAxis: unknown, axis: "cost-patch" | "cost-fallback"): DatedCostCard[];
22
+ /**
23
+ * Earliest instant a rate card can name (the `Date` range minimum). A leading
24
+ * card from here holds the price in force before the first dated change.
25
+ */
26
+ export declare const EARLIEST_EFFECTIVE_FROM = -8640000000000000;
27
+ /** A schedule of dated rate cards alone: no peak windows, no off-peak discount. */
28
+ export declare const NO_RECURRING_TARIFF: TimeBasedCost;
29
+ /**
30
+ * A flat card plus its dated overrides as a request-time `ModelCost`: the
31
+ * card due at `now` in the scalar legs, and every period (a leading card for
32
+ * the base price, then each dated card over the previous one) in a
33
+ * `timeBased` schedule without a recurring tariff. The plain card when no
34
+ * dated card exists.
35
+ */
36
+ export declare function datedCost(base: TokenCost, cards: readonly DatedCostCard[], now?: number): ModelCost;
37
+ /**
38
+ * Overwrite `cost` legs with the latest dated card due at `now`. Leaves
39
+ * `cost` unchanged before the first card.
40
+ */
41
+ export declare function applyDueDatedCostCard(cost: TokenCost, cards: readonly DatedCostCard[], now?: number): void;
@@ -1401,8 +1401,6 @@ export interface Model<TApi extends Api = Api> {
1401
1401
  preferWebsockets?: boolean;
1402
1402
  /** Codex Responses Lite transport: send the lite marker and carry instructions/tools as input items (mirrors codex-rs `use_responses_lite`). */
1403
1403
  useResponsesLite?: boolean;
1404
- /** Codex Code Mode restriction: model expects tools routed through a programmatic exec surface (mirrors codex-rs `tool_mode`). */
1405
- toolMode?: "code_mode_only";
1406
1404
  /**
1407
1405
  * Service-tier ids the provider advertises for this model (Codex discovery
1408
1406
  * `service_tiers[].id`, e.g. `priority`, `ultrafast`). Absent when the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oh-my-pi/pi-catalog",
3
- "version": "18.10.3",
3
+ "version": "18.10.4",
4
4
  "description": "Model catalog for omp: bundled model database, provider discovery descriptors, model identity, classification, and equivalence",
5
5
  "keywords": [
6
6
  "ai",
@@ -93,12 +93,12 @@
93
93
  "gen:proto": "bun scripts/generate-protocols.ts"
94
94
  },
95
95
  "dependencies": {
96
- "@oh-my-pi/omptype": "18.10.3",
97
- "@oh-my-pi/pi-utils": "18.10.3"
96
+ "@oh-my-pi/omptype": "18.10.4",
97
+ "@oh-my-pi/pi-utils": "18.10.4"
98
98
  },
99
99
  "devDependencies": {
100
100
  "@bgotink/kdl": "0.4.0",
101
- "@oh-my-pi/pi-ai": "18.10.3",
101
+ "@oh-my-pi/pi-ai": "18.10.4",
102
102
  "@types/bun": "^1.3.14"
103
103
  },
104
104
  "engines": {
package/src/build.ts CHANGED
@@ -10,8 +10,16 @@
10
10
  import { resolveDiscoveryApi, resolveModelPolicy } from "./compat/resolve";
11
11
  import type { ModelIdentity } from "./compat/types";
12
12
  import { resolveModelTokenizer } from "./model-tokenizer";
13
- import { materializeTimeBasedCost } from "./pricing";
14
- import { type Api, MODEL_KINDS, type Model, type ModelSpec } from "./types";
13
+ import {
14
+ applyDueDatedCostCard,
15
+ type DatedCostCard,
16
+ dueEffectiveRate,
17
+ EARLIEST_EFFECTIVE_FROM,
18
+ materializeTimeBasedCost,
19
+ NO_RECURRING_TARIFF,
20
+ parseDatedCostCards,
21
+ } from "./pricing";
22
+ import { type Api, type EffectiveTokenCost, MODEL_KINDS, type Model, type ModelCost, type ModelSpec } from "./types";
15
23
  import { cleanModelName } from "./utils";
16
24
 
17
25
  function numberField(source: object, key: string): number | undefined {
@@ -24,41 +32,6 @@ function objectPayload(value: unknown): object | undefined {
24
32
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
25
33
  }
26
34
 
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
-
62
35
  /** Narrow a compiled `input-modalities` axis value to the model input union. */
63
36
  function isInputModalities(value: unknown): value is ("text" | "image")[] {
64
37
  return Array.isArray(value) && value.every(entry => entry === "text" || entry === "image");
@@ -164,52 +137,65 @@ function applyCatalogAssignments<TApi extends Api>(model: Model<TApi>, catalog:
164
137
  }
165
138
  }
166
139
 
140
+ /** The parsed dated cards of a row's `cost-patch` and `cost-fallback`. */
141
+ interface DatedCostCards {
142
+ patch: readonly DatedCostCard[];
143
+ fallback: readonly DatedCostCard[];
144
+ }
145
+
167
146
  /**
168
- * Applies reviewed catalog-data value corrections (`cost-patch`,
169
- * `cache-read-at-input-rate`, `limits-patch`, `long-context-cost`,
170
- * `context-window-floor`, `input-modalities`) onto an upstream-sourced spec. Applied by
171
- * `buildModel` to every upstream-sourced spec; user-authored overrides are
172
- * recomposed after building by the override applicators, so explicit user
173
- * limits and pricing still win.
147
+ * The row's corrected price card at `now`: `cost-patch`, then `cost-fallback`
148
+ * (each with its dated card due at `now`), then the long-context tier and
149
+ * cache-rate derivations, all from the upstream card.
174
150
  */
175
- export function applyCatalogCorrections(
176
- model: Pick<ModelSpec<Api>, "cost" | "contextWindow" | "maxTokens" | "input">,
151
+ function correctedCost(
152
+ upstream: ModelCost,
177
153
  catalog: Record<string, unknown>,
178
- ): void {
154
+ cards: DatedCostCards,
155
+ now: number,
156
+ ): ModelCost {
157
+ let cost: ModelCost = { ...upstream };
158
+ const upstreamCard =
159
+ upstream.timeBased?.effectiveRates?.[0]?.effectiveFrom === EARLIEST_EFFECTIVE_FROM
160
+ ? dueEffectiveRate(upstream.timeBased, now)
161
+ : undefined;
162
+ if (upstreamCard !== undefined) {
163
+ // A row rebuilt from an already built one (dynamic merges, overrides)
164
+ // carries the dated schedule compiled below. Its leading card covers all
165
+ // history, so the scalar legs only ever held the card due at the first
166
+ // build; start from the card due now instead.
167
+ const { effectiveFrom: _effectiveFrom, ...card } = upstreamCard;
168
+ const { longContext: _longContext, ...base } = cost;
169
+ cost = { ...base, ...card };
170
+ }
179
171
  const patch = objectPayload(catalog.costPatch);
180
172
  if (patch !== undefined) {
181
- model.cost = { ...model.cost };
182
173
  const input = numberField(patch, "input");
183
- if (input !== undefined) model.cost.input = input;
174
+ if (input !== undefined) cost.input = input;
184
175
  const output = numberField(patch, "output");
185
- if (output !== undefined) model.cost.output = output;
176
+ if (output !== undefined) cost.output = output;
186
177
  const cacheRead = numberField(patch, "cacheRead");
187
- if (cacheRead !== undefined) model.cost.cacheRead = cacheRead;
178
+ if (cacheRead !== undefined) cost.cacheRead = cacheRead;
188
179
  const cacheWrite = numberField(patch, "cacheWrite");
189
- if (cacheWrite !== undefined) model.cost.cacheWrite = cacheWrite;
180
+ if (cacheWrite !== undefined) cost.cacheWrite = cacheWrite;
181
+ applyDueDatedCostCard(cost, cards.patch, now);
190
182
  }
191
183
  const fallback = objectPayload(catalog.costFallback);
192
184
  if (fallback !== undefined) {
193
- const base = model.cost;
194
- const hasTokenPrice = base.input !== 0 || base.output !== 0 || base.cacheRead !== 0 || base.cacheWrite !== 0;
185
+ const hasTokenPrice = cost.input !== 0 || cost.output !== 0 || cost.cacheRead !== 0 || cost.cacheWrite !== 0;
195
186
  if (!hasTokenPrice) {
196
187
  // Upstream reported no token price (plan-included or promo-free
197
188
  // rows): seed the reviewed list price instead of overwriting real
198
189
  // discovery data the way `cost-patch` would.
199
- model.cost = { ...model.cost };
200
190
  const input = numberField(fallback, "input");
201
- if (input !== undefined) model.cost.input = input;
191
+ if (input !== undefined) cost.input = input;
202
192
  const output = numberField(fallback, "output");
203
- if (output !== undefined) model.cost.output = output;
193
+ if (output !== undefined) cost.output = output;
204
194
  const cacheRead = numberField(fallback, "cacheRead");
205
- if (cacheRead !== undefined) model.cost.cacheRead = cacheRead;
195
+ if (cacheRead !== undefined) cost.cacheRead = cacheRead;
206
196
  const cacheWrite = numberField(fallback, "cacheWrite");
207
- if (cacheWrite !== undefined) model.cost.cacheWrite = cacheWrite;
208
- // Dated fallback rates overwrite the seeded numbers when they have
209
- // already taken effect. They are not a recurring tariff: wrapping
210
- // them in `timeBased` with empty peak windows would report
211
- // permanent off-peak and never wake at the dated boundary.
212
- applyEffectiveFallbackRates(model.cost, Reflect.get(fallback, "effectiveRates"));
197
+ if (cacheWrite !== undefined) cost.cacheWrite = cacheWrite;
198
+ applyDueDatedCostCard(cost, cards.fallback, now);
213
199
  }
214
200
  }
215
201
  const longContext = objectPayload(catalog.longContext);
@@ -221,20 +207,19 @@ export function applyCatalogCorrections(
221
207
  const output = numberField(longContext, "output");
222
208
  const cacheRead = numberField(longContext, "cacheRead");
223
209
  const cacheWrite = numberField(longContext, "cacheWrite");
224
- const base = model.cost;
225
- const hasTokenPrice = base.input !== 0 || base.output !== 0 || base.cacheRead !== 0 || base.cacheWrite !== 0;
210
+ const hasTokenPrice = cost.input !== 0 || cost.output !== 0 || cost.cacheRead !== 0 || cost.cacheWrite !== 0;
226
211
  if (inputThreshold !== undefined && multiplier !== undefined && hasTokenPrice) {
227
212
  // Multiplier form: tier rates derive from the row's final base card,
228
213
  // after `cost-patch`/`cost-fallback` corrected the live list price.
229
- model.cost = {
230
- ...base,
214
+ cost = {
215
+ ...cost,
231
216
  longContext: {
232
217
  inputThreshold,
233
218
  ...(inclusive && { inputThresholdInclusive: true }),
234
- input: base.input * multiplier,
235
- output: base.output * multiplier,
236
- cacheRead: base.cacheRead * multiplier,
237
- cacheWrite: base.cacheWrite * multiplier,
219
+ input: cost.input * multiplier,
220
+ output: cost.output * (numberField(longContext, "outputMultiplier") ?? multiplier),
221
+ cacheRead: cost.cacheRead * multiplier,
222
+ cacheWrite: cost.cacheWrite * multiplier,
238
223
  },
239
224
  };
240
225
  } else if (
@@ -244,19 +229,94 @@ export function applyCatalogCorrections(
244
229
  cacheRead !== undefined &&
245
230
  cacheWrite !== undefined
246
231
  ) {
247
- model.cost = { ...model.cost, longContext: { inputThreshold, input, output, cacheRead, cacheWrite } };
232
+ cost = { ...cost, longContext: { inputThreshold, input, output, cacheRead, cacheWrite } };
248
233
  }
249
234
  }
250
235
  if (catalog.cacheReadAtInputRate === true) {
251
- const { longContext } = model.cost;
252
- model.cost = {
253
- ...model.cost,
254
- cacheRead: model.cost.input,
236
+ const { longContext } = cost;
237
+ cost = {
238
+ ...cost,
239
+ cacheRead: cost.input,
255
240
  ...(longContext && { longContext: { ...longContext, cacheRead: longContext.input } }),
256
241
  };
257
242
  }
258
- if (catalog.timeBased !== undefined) {
259
- model.cost = { ...model.cost, timeBased: materializeTimeBasedCost(catalog.timeBased) };
243
+ const cacheWriteRate = catalog.cacheWriteRate;
244
+ if (typeof cacheWriteRate === "number") {
245
+ const { longContext } = cost;
246
+ cost = {
247
+ ...cost,
248
+ cacheWrite: cost.input * cacheWriteRate,
249
+ ...(longContext && { longContext: { ...longContext, cacheWrite: longContext.input * cacheWriteRate } }),
250
+ };
251
+ }
252
+ return cost;
253
+ }
254
+
255
+ /**
256
+ * Every price period the dated `cost-patch`/`cost-fallback` cards define, for
257
+ * request-time selection (`calculateUsageCost`): a row built before a change
258
+ * still bills requests after it at the new price, and a leading card keeps
259
+ * earlier requests at the old one on rows built after it. Undefined when no
260
+ * card changes the price (no cards, or a fallback the upstream price outranks).
261
+ */
262
+ function scheduledRates(
263
+ upstream: ModelCost,
264
+ catalog: Record<string, unknown>,
265
+ cards: DatedCostCards,
266
+ ): EffectiveTokenCost[] | undefined {
267
+ const dates = [...cards.patch, ...cards.fallback].map(card => card.effectiveFrom).sort((a, b) => a - b);
268
+ if (dates.length === 0) return undefined;
269
+ const periods: EffectiveTokenCost[] = [];
270
+ let previousPrice: string | undefined;
271
+ for (const effectiveFrom of [EARLIEST_EFFECTIVE_FROM, ...dates]) {
272
+ const { input, output, cacheRead, cacheWrite, longContext } = correctedCost(
273
+ upstream,
274
+ catalog,
275
+ cards,
276
+ effectiveFrom,
277
+ );
278
+ const price = { input, output, cacheRead, cacheWrite, ...(longContext && { longContext }) };
279
+ const key = JSON.stringify(price);
280
+ if (key === previousPrice) continue;
281
+ previousPrice = key;
282
+ periods.push({ effectiveFrom, ...price });
283
+ }
284
+ return periods.length > 1 ? periods : undefined;
285
+ }
286
+
287
+ /**
288
+ * Applies reviewed catalog-data value corrections (`cost-patch`,
289
+ * `cache-read-at-input-rate`, `cache-write-rate`, `limits-patch`,
290
+ * `long-context-cost`, `context-window-floor`, `input-modalities`) onto an
291
+ * upstream-sourced spec. Applied by
292
+ * `buildModel` to every upstream-sourced spec; user-authored overrides are
293
+ * recomposed after building by the override applicators, so explicit user
294
+ * limits and pricing still win.
295
+ */
296
+ export function applyCatalogCorrections(
297
+ model: Pick<ModelSpec<Api>, "cost" | "contextWindow" | "maxTokens" | "input">,
298
+ catalog: Record<string, unknown>,
299
+ ): void {
300
+ const upstream = model.cost;
301
+ const cards: DatedCostCards = {
302
+ patch: parseDatedCostCards(catalog.costPatch, "cost-patch"),
303
+ fallback: parseDatedCostCards(catalog.costFallback, "cost-fallback"),
304
+ };
305
+ // The card due now is the row's displayed price; the dated schedule rides
306
+ // along so rows reused past a change (bundles, caches, long sessions) still
307
+ // bill each request at the card in force when it was made.
308
+ model.cost = correctedCost(upstream, catalog, cards, Date.now());
309
+ const recurring = catalog.timeBased === undefined ? undefined : materializeTimeBasedCost(catalog.timeBased);
310
+ const dated = scheduledRates(upstream, catalog, cards);
311
+ if (dated !== undefined) {
312
+ if (recurring?.effectiveRates !== undefined) {
313
+ throw new Error(
314
+ "Dated cost-patch/cost-fallback effective-rates cannot combine with time-based-cost effective-rates",
315
+ );
316
+ }
317
+ model.cost = { ...model.cost, timeBased: { ...(recurring ?? NO_RECURRING_TARIFF), effectiveRates: dated } };
318
+ } else if (recurring !== undefined) {
319
+ model.cost = { ...model.cost, timeBased: recurring };
260
320
  }
261
321
  const limitsPatch = objectPayload(catalog.limitsPatch);
262
322
  if (limitsPatch !== undefined) {
@@ -370,6 +370,19 @@ export const AXES: Readonly<Record<string, AxisDef>> = {
370
370
  shape: "scalar",
371
371
  values: [true, false],
372
372
  },
373
+ /**
374
+ * The model's input classifier refuses requests that reproduce assistant
375
+ * reasoning as quoted text (`reasoning_extraction`), so reasoning must not
376
+ * be replayed as text into its input: compaction archives, summarizer
377
+ * transcripts and demoted interrupted turns leave it out. Declared per class
378
+ * in `classes/*.kdl`.
379
+ */
380
+ "classifier-cot-extraction": {
381
+ key: "classifierCotExtraction",
382
+ set: "catalog",
383
+ shape: "scalar",
384
+ values: [true, false],
385
+ },
373
386
  "clamp-context-override": { key: "clampContextOverride", set: "catalog", shape: "scalar" },
374
387
  "context-promotion-target": { key: "contextPromotionTarget", set: "catalog", shape: "scalar" },
375
388
  "context-window-floor": { key: "contextWindowFloor", set: "catalog", shape: "scalar" },
@@ -391,6 +404,14 @@ export const AXES: Readonly<Record<string, AxisDef>> = {
391
404
  shape: "scalar",
392
405
  values: [true, false],
393
406
  },
407
+ /**
408
+ * The host bills cache writes at this multiple of the row's live `input`
409
+ * price, for hosts whose discovery reports cache-write tokens but no
410
+ * cache-write rate. It replaces any `cacheWrite` the row carries, including
411
+ * a `cost-patch`/`cost-fallback` value and an absolute long-context tier's
412
+ * `cacheWrite` (rebased on the tier's `input`).
413
+ */
414
+ "cache-write-rate": { key: "cacheWriteRate", set: "catalog", shape: "scalar" },
394
415
  /**
395
416
  * How the model line bills a replayed signed thinking block as input:
396
417
  * `output` bills the reasoning its signature carries, at the size the turn
@@ -119,11 +119,24 @@ class IdentityFacts {
119
119
  }
120
120
  }
121
121
 
122
+ /**
123
+ * The id a spec resolves identity and rules under: its own, unless that names
124
+ * no model and the row sends a model that does on the wire. A Devin Fusion
125
+ * pairing runs only its lead, so the lead's reviewed rules (cache-write rate,
126
+ * long-context tier) apply to it at build time.
127
+ */
128
+ function policyModelId<TApi extends Api>(spec: ModelSpec<TApi>): string {
129
+ const wireId = spec.requestModelId;
130
+ if (wireId === undefined || wireId === spec.id) return spec.id;
131
+ if (classifyModel(spec.provider, spec.id).class !== "unknown") return spec.id;
132
+ return classifyModel(spec.provider, wireId).class === "unknown" ? spec.id : wireId;
133
+ }
134
+
122
135
  function resolveIdentity<TApi extends Api>(spec: ModelSpec<TApi>): ModelIdentity {
123
136
  // Strict on purpose: ambiguous identity is a rule-authoring defect surfaced
124
137
  // at build/CI time, never silently degraded to `unknown`. Discovery
125
138
  // normalization opts into leniency through `classifyModel` directly.
126
- return classifyModel(spec.provider, spec.id);
139
+ return classifyModel(spec.provider, policyModelId(spec));
127
140
  }
128
141
 
129
142
  // ---------------------------------------------------------------------------
@@ -1305,7 +1318,7 @@ function buildResolveTarget<TApi extends Api>(
1305
1318
  provider: providerType,
1306
1319
  api: spec.api,
1307
1320
  class: identity.class,
1308
- model: spec.id,
1321
+ model: policyModelId(spec),
1309
1322
  reasoning: Boolean(spec.reasoning),
1310
1323
  };
1311
1324
  if (identity.family !== undefined) target.family = identity.family;
@@ -196,6 +196,8 @@ An `on-api` nested inside `on` under a class requires both the selected provider
196
196
 
197
197
  A `models` string without `*` is an exact, case-sensitive match against the provider-relative model identifier. A string containing `*` is an anchored, ASCII-case-insensitive wildcard match. Prefer taxonomy ranks; retain exact/glob lists only when they isolate the census member set exactly, and keep a `// residue:` comment explaining why ranks do not.
198
198
 
199
+ A row whose own id classifies as `unknown` but whose `requestModelId` does not resolves under the wire id: classification and `models` matching both use it, so the model the row sends gets its own rules. Devin Fusion pairings (`fusion-<lead>-sidekick-*`) resolve as their lead this way.
200
+
199
201
  `priority=N` is an optional signed integer property on the block that owns axis assignments. Its default is zero. Use it only to resolve an intentional equal-specificity overlap; do not use it to encode declaration order.
200
202
 
201
203
  `buildDiscoveredModel(spec, providerType)` resolves the catalog `discovery-api` axis before materializing compatibility. It preserves the credential-bearing provider ID and records `providerType` as the backend used for provider selectors on subsequent rebuilds. Ordinary `buildModel` preserves its input API. This lets custom-named llama.cpp deployments reuse the same rules without model-specific discovery code.
@@ -213,7 +215,9 @@ The three value shapes are:
213
215
  - **Object**: no arguments and a child block, including an empty block. Child names are kebab-case: an axis-directive spelling compiles to its resolved axis key (`template-reasoning-effort` → `qwenTemplateReasoningEffort`), anything else converts mechanically (`input-threshold` → `inputThreshold`); camelCase names are a compile error. `extra-body` payloads (top-level or nested) are the exception — their child names are literal wire JSON keys copied verbatim (`enable_thinking`). Each child is either one scalar or another object; arrays are not representable inside an object payload.
214
216
 
215
217
  A rule cannot assign the same resolved axis twice in one block.
216
- One object axis carries a computed form: `long-context-cost` accepts either the absolute rates (`input-threshold` + `input`/`output`/`cache-read`/`cache-write`) or `input-threshold` + `multiplier` (with optional `input-threshold-inclusive`), which derives the tier from the row's base price at build time, after `cost-patch` and `cost-fallback`, so the rule tracks upstream list-price updates (xAI's SuperGrok 200K tier). Rows without a token price carry no tier. An empty `long-context-cost {}` adds no tier; a more specific rule uses it to opt a host out of a broader rule's band (GitHub Copilot's Haiku 5.5 rows, whose long tier is the `-1m` sibling).
218
+ One object axis carries a computed form: `long-context-cost` accepts either the absolute rates (`input-threshold` + `input`/`output`/`cache-read`/`cache-write`) or `input-threshold` + `multiplier` (with optional `input-threshold-inclusive`, and `output-multiplier` when the tier raises output by a different factor than the other legs), which derives the tier from the row's base price at build time, after `cost-patch` and `cost-fallback`, so the rule tracks upstream list-price updates (xAI's SuperGrok 200K tier). Rows without a token price carry no tier. An empty `long-context-cost {}` adds no tier; a more specific rule uses it to opt a host out of a broader rule's band (GitHub Copilot's Haiku 5.5 rows, whose long tier is the `-1m` sibling).
219
+
220
+ `cost-patch` and `cost-fallback` accept an optional `effective-rates` block of named dated cards, each with a distinct ISO UTC `effective-from` (`YYYY-MM-DDTHH:mm:ssZ` or with three fractional-second digits) and any of `input`/`output`/`cache-read`/`cache-write` as nonnegative rates; a malformed card fails `buildModel` rather than leaving the old price in force. A card overwrites those legs from its date: for `cost-patch` over whatever upstream reports, for `cost-fallback` only when it seeded the row. A patch may carry the dated cards alone, leaving the upstream price in place until the first one starts; pin the current price beside them when upstream may later report the new one, or a row built after that loses the earlier period (Gemini 4 Argon's 2026-12-01 list-price increase in [`classes/gemini.kdl`](classes/gemini.kdl) pins both). `buildModel` sets the row's legs to the card due when it is built, and compiles every price period into `ModelCost.timeBased.effectiveRates` (complete cards, long-context tier and cache-rate derivations included, with a leading card for the price before the first change) under a schedule with no peak windows and a 1x off-peak multiplier. Requests are therefore priced at the card in force when they were made, even on rows built before a change (bundles, caches, long sessions); price displays read `getEffectiveTokenCost`, which selects the same card, and such a schedule reports no peak/off-peak period. Rebuilding a row that already carries such a schedule (dynamic merges, overrides) starts from the card due at the rebuild, and Cursor lanes priced from a sibling slug's card keep that card's schedule, scaled by the lane's multiplier. Cards that leave the price unchanged (a fallback the upstream price outranks) add no schedule. A rule cannot combine these cards with `time-based-cost` `effective-rates`; it may add them to a recurring `time-based-cost` tariff without its own.
217
221
 
218
222
  `context-window-authoritative #true` preserves a host's supplied context window through runtime model selection instead of applying inferred expansion or reference-price-tier caps. Explicit user context overrides still apply afterward. It applies to rows materialized through `buildModel` (discovery and regenerated bundles). See [`providers/factory-droid.kdl`](providers/factory-droid.kdl).
219
223
 
@@ -25,6 +25,9 @@
25
25
  // https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#switching-models
26
26
  class "anthropic" {
27
27
  prompt-cache-lookback 20
28
+ // Claude's input classifier refuses requests that quote earlier reasoning
29
+ // back as text (`reasoning_extraction`, issue #6093).
30
+ classifier-cot-extraction #true
28
31
  thinking-replay-tokens "output"
29
32
  // verified: billing matched this formula on Opus 4.7+ and Fable 5.1.
30
33
  image-tokenization {
@@ -81,6 +81,34 @@ class "gemini" {
81
81
  }
82
82
  }
83
83
  }
84
+ // Gemini 4 Argon's introductory list price ($2/$10, $0.10 cached) ends
85
+ // 2026-11-30; from 2026-12-01 Google bills $4/$20, $0.20 cached, on the
86
+ // Gemini API and Vertex global endpoint alike. Cloud Billing days start at
87
+ // midnight US Pacific. Both cards are pinned over whatever stencil.so or
88
+ // the bundled pricing peer reports: the switch needs no regeneration, and
89
+ // a row built after upstream catches up still prices earlier requests at
90
+ // the introductory rate.
91
+ // https://ai.google.dev/gemini-api/docs/pricing
92
+ // https://cloud.google.com/vertex-ai/generative-ai/pricing
93
+ on "google" "google-vertex" "google-antigravity" {
94
+ models "gemini-4-argon" {
95
+ cost-patch {
96
+ input 2
97
+ output 10
98
+ cache-read 0.1
99
+ cache-write 0
100
+ effective-rates {
101
+ list-price {
102
+ effective-from "2026-12-01T08:00:00Z"
103
+ input 4
104
+ output 20
105
+ cache-read 0.2
106
+ cache-write 0
107
+ }
108
+ }
109
+ }
110
+ }
111
+ }
84
112
 
85
113
  revision ">=3" {
86
114
  // Replaces the Google multimodal function-response baseline.
@@ -9,8 +9,10 @@ class "meta" {
9
9
  thinking-efforts "minimal" "low" "medium" "high" "xhigh" "max"
10
10
  thinking-mode "effort"
11
11
  }
12
- // Meta documents `max` for Muse Wonder (dev.meta.ai/docs/reasoning).
13
- models "muse-wonder-1.0" {
12
+ // Meta documents `max` for Muse Wonder (dev.meta.ai/docs/reasoning); the
13
+ // Muse Code-only Wonder Full and contributor tiers accept it too (verified
14
+ // 2026-10-12).
15
+ models "muse-wonder-1.0" "muse-wonder-1.0-contributor" "muse-wonder-full-1.0" "muse-wonder-full-1.0-contributor" {
14
16
  thinking-efforts "minimal" "low" "medium" "high" "xhigh" "max"
15
17
  thinking-mode "effort"
16
18
  }