@linxiraos/pi-catalog 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +79 -0
- package/README.md +4 -2
- package/THIRD-PARTY-NOTICES.txt +55 -5
- package/dist/types/build.d.ts +3 -2
- package/dist/types/compat/auth-ids.d.ts +2 -2
- package/dist/types/compat/axes.d.ts +1 -1
- package/dist/types/compat/behavior.d.ts +5 -0
- package/dist/types/compat/catalog-endpoint.d.ts +8 -0
- package/dist/types/compat/image-tokenization.d.ts +9 -1
- package/dist/types/compat/judgment-images.d.ts +25 -0
- package/dist/types/compat/provider-ids.d.ts +1 -1
- package/dist/types/compat/request-compression.d.ts +23 -0
- package/dist/types/compat/request-size.d.ts +12 -3
- package/dist/types/compat/thinking-replay.d.ts +9 -0
- package/dist/types/compat/types.d.ts +4 -0
- package/dist/types/discovery/antigravity.d.ts +7 -0
- package/dist/types/model-cache.d.ts +7 -0
- package/dist/types/model-manager.d.ts +6 -0
- package/dist/types/model-thinking.d.ts +11 -1
- package/dist/types/models.d.ts +16 -1
- package/dist/types/pricing.d.ts +37 -1
- package/dist/types/provider-models/openai-compat.d.ts +18 -46
- package/dist/types/types.d.ts +63 -12
- package/dist/types/wire/codex.d.ts +2 -0
- package/package.json +6 -6
- package/src/build.d.ts +3 -2
- package/src/build.ts +159 -80
- package/src/compat/auth-ids.ts +2 -0
- package/src/compat/axes.d.ts +1 -1
- package/src/compat/axes.ts +129 -4
- package/src/compat/behavior.d.ts +5 -0
- package/src/compat/behavior.ts +9 -1
- package/src/compat/cascade.ts +4 -1
- package/src/compat/catalog-endpoint.ts +43 -0
- package/src/compat/image-tokenization.ts +28 -10
- package/src/compat/judgment-images.ts +83 -0
- package/src/compat/provider-ids.d.ts +1 -1
- package/src/compat/provider-ids.ts +1 -0
- package/src/compat/request-compression.ts +43 -0
- package/src/compat/request-size.ts +20 -5
- package/src/compat/resolve.ts +90 -53
- package/src/compat/rules/README.md +32 -3
- package/src/compat/rules/auth/_order.kdl +1 -1
- package/src/compat/rules/auth/google-antigravity.kdl +1 -1
- package/src/compat/rules/auth/sambanova.kdl +9 -0
- package/src/compat/rules/auth/stencil.kdl +1 -1
- package/src/compat/rules/classes/anthropic.kdl +42 -2
- package/src/compat/rules/classes/deepseek.kdl +4 -0
- package/src/compat/rules/classes/gemini.kdl +46 -2
- package/src/compat/rules/classes/meta.kdl +7 -0
- package/src/compat/rules/classes/minimax.kdl +11 -1
- package/src/compat/rules/classes/openai.kdl +34 -3
- package/src/compat/rules/classes/xai.kdl +26 -0
- package/src/compat/rules/providers/amazon-bedrock.kdl +1 -0
- package/src/compat/rules/providers/anthropic.kdl +23 -0
- package/src/compat/rules/providers/azure.kdl +2 -0
- package/src/compat/rules/providers/cerebras.kdl +3 -0
- package/src/compat/rules/providers/cline-pass.kdl +16 -0
- package/src/compat/rules/providers/commandcode.kdl +10 -0
- package/src/compat/rules/providers/cursor.kdl +50 -1
- package/src/compat/rules/providers/deepinfra.kdl +3 -1
- package/src/compat/rules/providers/devin.kdl +117 -6
- package/src/compat/rules/providers/factory-droid.kdl +6 -0
- package/src/compat/rules/providers/firepass.kdl +15 -0
- package/src/compat/rules/providers/fireworks.kdl +26 -0
- package/src/compat/rules/providers/github-copilot.kdl +24 -0
- package/src/compat/rules/providers/google-antigravity.kdl +37 -0
- package/src/compat/rules/providers/google-gemini-cli.kdl +1 -0
- package/src/compat/rules/providers/google-vertex.kdl +5 -0
- package/src/compat/rules/providers/google.kdl +1 -0
- package/src/compat/rules/providers/image-tokenization.kdl +2 -1
- package/src/compat/rules/providers/local.kdl +8 -1
- package/src/compat/rules/providers/meta.kdl +16 -0
- package/src/compat/rules/providers/moonshot.kdl +2 -0
- package/src/compat/rules/providers/muse-code.kdl +49 -0
- package/src/compat/rules/providers/native-tools.kdl +3 -0
- package/src/compat/rules/providers/ollama-cloud.kdl +10 -0
- package/src/compat/rules/providers/openai-codex.kdl +29 -5
- package/src/compat/rules/providers/openai.kdl +1 -0
- package/src/compat/rules/providers/opencode-go.kdl +35 -0
- package/src/compat/rules/providers/opencode-zen.kdl +4 -0
- package/src/compat/rules/providers/openrouter.kdl +19 -0
- package/src/compat/rules/providers/sambanova.kdl +60 -0
- package/src/compat/rules/providers/tool-call-names.kdl +37 -0
- package/src/compat/rules/providers/typesafe.kdl +11 -0
- package/src/compat/rules/providers/umans.kdl +7 -0
- package/src/compat/rules/providers/venice.kdl +7 -0
- package/src/compat/rules/providers/xai-oauth.kdl +16 -6
- package/src/compat/rules/providers/xai.kdl +3 -0
- package/src/compat/rules/providers/zai.kdl +5 -0
- package/src/compat/rules/runtime/behavior.kdl +128 -8
- package/src/compat/rules/taxonomy/_collapse.kdl +11 -0
- package/src/compat/rules/taxonomy/gemma.kdl +1 -0
- package/src/compat/rules/taxonomy/meta.kdl +7 -0
- package/src/compat/rules/taxonomy/openai.kdl +2 -0
- package/src/compat/rules.json +1 -1
- package/src/compat/thinking-replay.ts +33 -0
- package/src/compat/types.d.ts +4 -0
- package/src/compat/types.ts +4 -0
- package/src/discovery/antigravity.d.ts +7 -0
- package/src/discovery/antigravity.ts +13 -0
- package/src/discovery/codex.ts +0 -4
- package/src/discovery/cursor.ts +28 -20
- package/src/discovery/devin.ts +7 -8
- package/src/model-cache.d.ts +7 -0
- package/src/model-cache.ts +8 -0
- package/src/model-manager.d.ts +6 -0
- package/src/model-manager.ts +41 -21
- package/src/model-thinking.d.ts +80 -0
- package/src/model-thinking.ts +13 -1
- package/src/model-tokenizer.ts +7 -1
- package/src/models.d.ts +16 -1
- package/src/models.json +1 -1
- package/src/models.ts +26 -12
- package/src/pricing.d.ts +37 -1
- package/src/pricing.ts +123 -12
- package/src/provider-models/bundled-references.ts +5 -2
- package/src/provider-models/cache-provider-id.ts +1 -1
- package/src/provider-models/descriptors.ts +2 -0
- package/src/provider-models/google.ts +7 -1
- package/src/provider-models/openai-compat.d.ts +18 -46
- package/src/provider-models/openai-compat.ts +188 -104
- package/src/provider-models/special.ts +1 -0
- package/src/types.d.ts +63 -12
- package/src/types.ts +71 -9
- package/src/wire/codex.d.ts +2 -0
- package/src/wire/codex.ts +17 -0
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 {
|
|
14
|
-
|
|
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");
|
|
@@ -81,6 +54,11 @@ function applyCatalogAssignments<TApi extends Api>(model: Model<TApi>, catalog:
|
|
|
81
54
|
} else {
|
|
82
55
|
delete model.contextWindowAuthoritative;
|
|
83
56
|
}
|
|
57
|
+
if (catalog.longContextWindowUncapped === true) {
|
|
58
|
+
model.longContextWindowUncapped = true;
|
|
59
|
+
} else {
|
|
60
|
+
delete model.longContextWindowUncapped;
|
|
61
|
+
}
|
|
84
62
|
const webSearch = catalog.webSearch;
|
|
85
63
|
if (
|
|
86
64
|
webSearch === "gemini" ||
|
|
@@ -142,6 +120,11 @@ function applyCatalogAssignments<TApi extends Api>(model: Model<TApi>, catalog:
|
|
|
142
120
|
} else {
|
|
143
121
|
delete model.requiresCursorToolSchemaProjection;
|
|
144
122
|
}
|
|
123
|
+
if (catalog.toolSchemasInContext === false) {
|
|
124
|
+
model.toolSchemasInContext = false;
|
|
125
|
+
} else {
|
|
126
|
+
delete model.toolSchemasInContext;
|
|
127
|
+
}
|
|
145
128
|
const requiresToolResultImageHoisting = catalog.requiresToolResultImageHoisting;
|
|
146
129
|
if (requiresToolResultImageHoisting === true) {
|
|
147
130
|
model.requiresToolResultImageHoisting = true;
|
|
@@ -164,52 +147,65 @@ function applyCatalogAssignments<TApi extends Api>(model: Model<TApi>, catalog:
|
|
|
164
147
|
}
|
|
165
148
|
}
|
|
166
149
|
|
|
150
|
+
/** The parsed dated cards of a row's `cost-patch` and `cost-fallback`. */
|
|
151
|
+
interface DatedCostCards {
|
|
152
|
+
patch: readonly DatedCostCard[];
|
|
153
|
+
fallback: readonly DatedCostCard[];
|
|
154
|
+
}
|
|
155
|
+
|
|
167
156
|
/**
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
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.
|
|
157
|
+
* The row's corrected price card at `now`: `cost-patch`, then `cost-fallback`
|
|
158
|
+
* (each with its dated card due at `now`), then the long-context tier and
|
|
159
|
+
* cache-rate derivations, all from the upstream card.
|
|
174
160
|
*/
|
|
175
|
-
|
|
176
|
-
|
|
161
|
+
function correctedCost(
|
|
162
|
+
upstream: ModelCost,
|
|
177
163
|
catalog: Record<string, unknown>,
|
|
178
|
-
|
|
164
|
+
cards: DatedCostCards,
|
|
165
|
+
now: number,
|
|
166
|
+
): ModelCost {
|
|
167
|
+
let cost: ModelCost = { ...upstream };
|
|
168
|
+
const upstreamCard =
|
|
169
|
+
upstream.timeBased?.effectiveRates?.[0]?.effectiveFrom === EARLIEST_EFFECTIVE_FROM
|
|
170
|
+
? dueEffectiveRate(upstream.timeBased, now)
|
|
171
|
+
: undefined;
|
|
172
|
+
if (upstreamCard !== undefined) {
|
|
173
|
+
// A row rebuilt from an already built one (dynamic merges, overrides)
|
|
174
|
+
// carries the dated schedule compiled below. Its leading card covers all
|
|
175
|
+
// history, so the scalar legs only ever held the card due at the first
|
|
176
|
+
// build; start from the card due now instead.
|
|
177
|
+
const { effectiveFrom: _effectiveFrom, ...card } = upstreamCard;
|
|
178
|
+
const { longContext: _longContext, ...base } = cost;
|
|
179
|
+
cost = { ...base, ...card };
|
|
180
|
+
}
|
|
179
181
|
const patch = objectPayload(catalog.costPatch);
|
|
180
182
|
if (patch !== undefined) {
|
|
181
|
-
model.cost = { ...model.cost };
|
|
182
183
|
const input = numberField(patch, "input");
|
|
183
|
-
if (input !== undefined)
|
|
184
|
+
if (input !== undefined) cost.input = input;
|
|
184
185
|
const output = numberField(patch, "output");
|
|
185
|
-
if (output !== undefined)
|
|
186
|
+
if (output !== undefined) cost.output = output;
|
|
186
187
|
const cacheRead = numberField(patch, "cacheRead");
|
|
187
|
-
if (cacheRead !== undefined)
|
|
188
|
+
if (cacheRead !== undefined) cost.cacheRead = cacheRead;
|
|
188
189
|
const cacheWrite = numberField(patch, "cacheWrite");
|
|
189
|
-
if (cacheWrite !== undefined)
|
|
190
|
+
if (cacheWrite !== undefined) cost.cacheWrite = cacheWrite;
|
|
191
|
+
applyDueDatedCostCard(cost, cards.patch, now);
|
|
190
192
|
}
|
|
191
193
|
const fallback = objectPayload(catalog.costFallback);
|
|
192
194
|
if (fallback !== undefined) {
|
|
193
|
-
const
|
|
194
|
-
const hasTokenPrice = base.input !== 0 || base.output !== 0 || base.cacheRead !== 0 || base.cacheWrite !== 0;
|
|
195
|
+
const hasTokenPrice = cost.input !== 0 || cost.output !== 0 || cost.cacheRead !== 0 || cost.cacheWrite !== 0;
|
|
195
196
|
if (!hasTokenPrice) {
|
|
196
197
|
// Upstream reported no token price (plan-included or promo-free
|
|
197
198
|
// rows): seed the reviewed list price instead of overwriting real
|
|
198
199
|
// discovery data the way `cost-patch` would.
|
|
199
|
-
model.cost = { ...model.cost };
|
|
200
200
|
const input = numberField(fallback, "input");
|
|
201
|
-
if (input !== undefined)
|
|
201
|
+
if (input !== undefined) cost.input = input;
|
|
202
202
|
const output = numberField(fallback, "output");
|
|
203
|
-
if (output !== undefined)
|
|
203
|
+
if (output !== undefined) cost.output = output;
|
|
204
204
|
const cacheRead = numberField(fallback, "cacheRead");
|
|
205
|
-
if (cacheRead !== undefined)
|
|
205
|
+
if (cacheRead !== undefined) cost.cacheRead = cacheRead;
|
|
206
206
|
const cacheWrite = numberField(fallback, "cacheWrite");
|
|
207
|
-
if (cacheWrite !== undefined)
|
|
208
|
-
|
|
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"));
|
|
207
|
+
if (cacheWrite !== undefined) cost.cacheWrite = cacheWrite;
|
|
208
|
+
applyDueDatedCostCard(cost, cards.fallback, now);
|
|
213
209
|
}
|
|
214
210
|
}
|
|
215
211
|
const longContext = objectPayload(catalog.longContext);
|
|
@@ -221,20 +217,19 @@ export function applyCatalogCorrections(
|
|
|
221
217
|
const output = numberField(longContext, "output");
|
|
222
218
|
const cacheRead = numberField(longContext, "cacheRead");
|
|
223
219
|
const cacheWrite = numberField(longContext, "cacheWrite");
|
|
224
|
-
const
|
|
225
|
-
const hasTokenPrice = base.input !== 0 || base.output !== 0 || base.cacheRead !== 0 || base.cacheWrite !== 0;
|
|
220
|
+
const hasTokenPrice = cost.input !== 0 || cost.output !== 0 || cost.cacheRead !== 0 || cost.cacheWrite !== 0;
|
|
226
221
|
if (inputThreshold !== undefined && multiplier !== undefined && hasTokenPrice) {
|
|
227
222
|
// Multiplier form: tier rates derive from the row's final base card,
|
|
228
223
|
// after `cost-patch`/`cost-fallback` corrected the live list price.
|
|
229
|
-
|
|
230
|
-
...
|
|
224
|
+
cost = {
|
|
225
|
+
...cost,
|
|
231
226
|
longContext: {
|
|
232
227
|
inputThreshold,
|
|
233
228
|
...(inclusive && { inputThresholdInclusive: true }),
|
|
234
|
-
input:
|
|
235
|
-
output:
|
|
236
|
-
cacheRead:
|
|
237
|
-
cacheWrite:
|
|
229
|
+
input: cost.input * multiplier,
|
|
230
|
+
output: cost.output * (numberField(longContext, "outputMultiplier") ?? multiplier),
|
|
231
|
+
cacheRead: cost.cacheRead * multiplier,
|
|
232
|
+
cacheWrite: cost.cacheWrite * multiplier,
|
|
238
233
|
},
|
|
239
234
|
};
|
|
240
235
|
} else if (
|
|
@@ -244,19 +239,94 @@ export function applyCatalogCorrections(
|
|
|
244
239
|
cacheRead !== undefined &&
|
|
245
240
|
cacheWrite !== undefined
|
|
246
241
|
) {
|
|
247
|
-
|
|
242
|
+
cost = { ...cost, longContext: { inputThreshold, input, output, cacheRead, cacheWrite } };
|
|
248
243
|
}
|
|
249
244
|
}
|
|
250
245
|
if (catalog.cacheReadAtInputRate === true) {
|
|
251
|
-
const { longContext } =
|
|
252
|
-
|
|
253
|
-
...
|
|
254
|
-
cacheRead:
|
|
246
|
+
const { longContext } = cost;
|
|
247
|
+
cost = {
|
|
248
|
+
...cost,
|
|
249
|
+
cacheRead: cost.input,
|
|
255
250
|
...(longContext && { longContext: { ...longContext, cacheRead: longContext.input } }),
|
|
256
251
|
};
|
|
257
252
|
}
|
|
258
|
-
|
|
259
|
-
|
|
253
|
+
const cacheWriteRate = catalog.cacheWriteRate;
|
|
254
|
+
if (typeof cacheWriteRate === "number") {
|
|
255
|
+
const { longContext } = cost;
|
|
256
|
+
cost = {
|
|
257
|
+
...cost,
|
|
258
|
+
cacheWrite: cost.input * cacheWriteRate,
|
|
259
|
+
...(longContext && { longContext: { ...longContext, cacheWrite: longContext.input * cacheWriteRate } }),
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
return cost;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Every price period the dated `cost-patch`/`cost-fallback` cards define, for
|
|
267
|
+
* request-time selection (`calculateUsageCost`): a row built before a change
|
|
268
|
+
* still bills requests after it at the new price, and a leading card keeps
|
|
269
|
+
* earlier requests at the old one on rows built after it. Undefined when no
|
|
270
|
+
* card changes the price (no cards, or a fallback the upstream price outranks).
|
|
271
|
+
*/
|
|
272
|
+
function scheduledRates(
|
|
273
|
+
upstream: ModelCost,
|
|
274
|
+
catalog: Record<string, unknown>,
|
|
275
|
+
cards: DatedCostCards,
|
|
276
|
+
): EffectiveTokenCost[] | undefined {
|
|
277
|
+
const dates = [...cards.patch, ...cards.fallback].map(card => card.effectiveFrom).sort((a, b) => a - b);
|
|
278
|
+
if (dates.length === 0) return undefined;
|
|
279
|
+
const periods: EffectiveTokenCost[] = [];
|
|
280
|
+
let previousPrice: string | undefined;
|
|
281
|
+
for (const effectiveFrom of [EARLIEST_EFFECTIVE_FROM, ...dates]) {
|
|
282
|
+
const { input, output, cacheRead, cacheWrite, longContext } = correctedCost(
|
|
283
|
+
upstream,
|
|
284
|
+
catalog,
|
|
285
|
+
cards,
|
|
286
|
+
effectiveFrom,
|
|
287
|
+
);
|
|
288
|
+
const price = { input, output, cacheRead, cacheWrite, ...(longContext && { longContext }) };
|
|
289
|
+
const key = JSON.stringify(price);
|
|
290
|
+
if (key === previousPrice) continue;
|
|
291
|
+
previousPrice = key;
|
|
292
|
+
periods.push({ effectiveFrom, ...price });
|
|
293
|
+
}
|
|
294
|
+
return periods.length > 1 ? periods : undefined;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Applies reviewed catalog-data value corrections (`cost-patch`,
|
|
299
|
+
* `cache-read-at-input-rate`, `cache-write-rate`, `limits-patch`,
|
|
300
|
+
* `long-context-cost`, `context-window-floor`, `input-modalities`) onto an
|
|
301
|
+
* upstream-sourced spec. Applied by
|
|
302
|
+
* `buildModel` to every upstream-sourced spec; user-authored overrides are
|
|
303
|
+
* recomposed after building by the override applicators, so explicit user
|
|
304
|
+
* limits and pricing still win.
|
|
305
|
+
*/
|
|
306
|
+
export function applyCatalogCorrections(
|
|
307
|
+
model: Pick<ModelSpec<Api>, "cost" | "contextWindow" | "maxTokens" | "input">,
|
|
308
|
+
catalog: Record<string, unknown>,
|
|
309
|
+
): void {
|
|
310
|
+
const upstream = model.cost;
|
|
311
|
+
const cards: DatedCostCards = {
|
|
312
|
+
patch: parseDatedCostCards(catalog.costPatch, "cost-patch"),
|
|
313
|
+
fallback: parseDatedCostCards(catalog.costFallback, "cost-fallback"),
|
|
314
|
+
};
|
|
315
|
+
// The card due now is the row's displayed price; the dated schedule rides
|
|
316
|
+
// along so rows reused past a change (bundles, caches, long sessions) still
|
|
317
|
+
// bill each request at the card in force when it was made.
|
|
318
|
+
model.cost = correctedCost(upstream, catalog, cards, Date.now());
|
|
319
|
+
const recurring = catalog.timeBased === undefined ? undefined : materializeTimeBasedCost(catalog.timeBased);
|
|
320
|
+
const dated = scheduledRates(upstream, catalog, cards);
|
|
321
|
+
if (dated !== undefined) {
|
|
322
|
+
if (recurring?.effectiveRates !== undefined) {
|
|
323
|
+
throw new Error(
|
|
324
|
+
"Dated cost-patch/cost-fallback effective-rates cannot combine with time-based-cost effective-rates",
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
model.cost = { ...model.cost, timeBased: { ...(recurring ?? NO_RECURRING_TARIFF), effectiveRates: dated } };
|
|
328
|
+
} else if (recurring !== undefined) {
|
|
329
|
+
model.cost = { ...model.cost, timeBased: recurring };
|
|
260
330
|
}
|
|
261
331
|
const limitsPatch = objectPayload(catalog.limitsPatch);
|
|
262
332
|
if (limitsPatch !== undefined) {
|
|
@@ -265,6 +335,14 @@ export function applyCatalogCorrections(
|
|
|
265
335
|
const maxTokens = numberField(limitsPatch, "maxTokens");
|
|
266
336
|
if (maxTokens !== undefined) model.maxTokens = maxTokens;
|
|
267
337
|
}
|
|
338
|
+
const maxTokensFallback = catalog.maxTokensFallback;
|
|
339
|
+
if (typeof maxTokensFallback === "number" && model.maxTokens === null) {
|
|
340
|
+
model.maxTokens = maxTokensFallback;
|
|
341
|
+
}
|
|
342
|
+
const maxTokensCeiling = catalog.maxTokensCeiling;
|
|
343
|
+
if (typeof maxTokensCeiling === "number" && model.maxTokens !== null && model.maxTokens > maxTokensCeiling) {
|
|
344
|
+
model.maxTokens = maxTokensCeiling;
|
|
345
|
+
}
|
|
268
346
|
const contextWindowFloor = catalog.contextWindowFloor;
|
|
269
347
|
if (typeof contextWindowFloor === "number") {
|
|
270
348
|
model.contextWindow = Math.max(model.contextWindow ?? 0, contextWindowFloor);
|
|
@@ -351,8 +429,9 @@ export function buildModel<TApi extends Api>(spec: ModelSpec<TApi>): Model<TApi>
|
|
|
351
429
|
const model: Model<TApi> = {
|
|
352
430
|
...spec,
|
|
353
431
|
// A reviewed `thinking-upgrade-neutral` policy can repair a stale
|
|
354
|
-
// `reasoning: false` discovery default
|
|
355
|
-
//
|
|
432
|
+
// `reasoning: false` discovery default, and an unclassified neutral spec
|
|
433
|
+
// gets an unverified ladder (see `resolveThinkingPolicy`); materialize
|
|
434
|
+
// either for transports and the picker.
|
|
356
435
|
reasoning: spec.reasoning || policy.thinking !== undefined,
|
|
357
436
|
name: cleanModelName(spec.name),
|
|
358
437
|
identity: policy.identity,
|
package/src/compat/auth-ids.ts
CHANGED
|
@@ -70,6 +70,7 @@ export type AuthProviderId =
|
|
|
70
70
|
| "qianfan"
|
|
71
71
|
| "qwen-portal"
|
|
72
72
|
| "sakana"
|
|
73
|
+
| "sambanova"
|
|
73
74
|
| "siliconflow"
|
|
74
75
|
| "siliconflow-cn"
|
|
75
76
|
| "singularityapi-dev"
|
|
@@ -157,6 +158,7 @@ export type LoginProviderId =
|
|
|
157
158
|
| "qianfan"
|
|
158
159
|
| "qwen-portal"
|
|
159
160
|
| "sakana"
|
|
161
|
+
| "sambanova"
|
|
160
162
|
| "siliconflow"
|
|
161
163
|
| "siliconflow-cn"
|
|
162
164
|
| "singularityapi-dev"
|
package/src/compat/axes.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ export type AxisShape = "scalar" | "array" | "object";
|
|
|
16
16
|
/** Axis namespace: request-wire compat, thinking control surface, or catalog metadata. */
|
|
17
17
|
export type AxisSet = "wire" | "thinking" | "catalog";
|
|
18
18
|
/** Resolved compat record families a wire axis may be assigned onto. */
|
|
19
|
-
export type CompatRecordName = "openai" | "openai-responses" | "anthropic" | "bedrock" | "devin" | "google" | "request";
|
|
19
|
+
export type CompatRecordName = "openai" | "openai-responses" | "anthropic" | "bedrock" | "devin" | "google" | "request" | "ollama";
|
|
20
20
|
/** One axis definition: resolved key, namespace, shape, and applicability. */
|
|
21
21
|
export interface AxisDef {
|
|
22
22
|
/** Resolved camelCase field the directive assigns. */
|
package/src/compat/axes.ts
CHANGED
|
@@ -20,7 +20,15 @@ export type AxisShape = "scalar" | "array" | "object";
|
|
|
20
20
|
export type AxisSet = "wire" | "thinking" | "catalog";
|
|
21
21
|
|
|
22
22
|
/** Resolved compat record families a wire axis may be assigned onto. */
|
|
23
|
-
export type CompatRecordName =
|
|
23
|
+
export type CompatRecordName =
|
|
24
|
+
| "openai"
|
|
25
|
+
| "openai-responses"
|
|
26
|
+
| "anthropic"
|
|
27
|
+
| "bedrock"
|
|
28
|
+
| "devin"
|
|
29
|
+
| "google"
|
|
30
|
+
| "request"
|
|
31
|
+
| "ollama";
|
|
24
32
|
|
|
25
33
|
/** One axis definition: resolved key, namespace, shape, and applicability. */
|
|
26
34
|
export interface AxisDef {
|
|
@@ -242,15 +250,16 @@ export const AXES: Readonly<Record<string, AxisDef>> = {
|
|
|
242
250
|
"escape-builtin-tool-names": wire("escapeBuiltinToolNames", ["anthropic"]),
|
|
243
251
|
"fast-mode": wire("fastMode", ["anthropic"]),
|
|
244
252
|
"first-party-provider": wire("firstPartyProvider", ["anthropic"]),
|
|
253
|
+
"honors-claude-code-beta-kill-switch": wire("honorsClaudeCodeBetaKillSwitch", ["anthropic"]),
|
|
245
254
|
"inject-claude-code-instruction": wire("injectClaudeCodeInstruction", ["anthropic"]),
|
|
246
|
-
"max-image-dimension": wire("maxImageDimension", ["anthropic"]),
|
|
247
|
-
"max-image-payload-bytes": wire("maxImagePayloadBytes", ["anthropic"]),
|
|
255
|
+
"max-image-dimension": wire("maxImageDimension", ["anthropic", "google"]),
|
|
256
|
+
"max-image-payload-bytes": wire("maxImagePayloadBytes", ["anthropic", "google"]),
|
|
248
257
|
"official-endpoint": wire("officialEndpoint", ["anthropic", "openai-responses"]),
|
|
249
258
|
"replay-unsigned-thinking": wire("replayUnsignedThinking", ["anthropic"]),
|
|
250
259
|
"requires-thinking-enabled": wire("requiresThinkingEnabled", ["anthropic"]),
|
|
251
260
|
"requires-tool-result-id": wire("requiresToolResultId", ["anthropic"]),
|
|
252
261
|
"signing-endpoint": wire("signingEndpoint", ["anthropic"]),
|
|
253
|
-
"strip-thinking-history": wire("stripThinkingHistory", ["anthropic"]),
|
|
262
|
+
"strip-thinking-history": wire("stripThinkingHistory", ["openai", "anthropic", "ollama"]),
|
|
254
263
|
"supports-context-management": wire("supportsContextManagement", ["anthropic"]),
|
|
255
264
|
"supports-output-effort": wire("supportsOutputEffort", ["anthropic"]),
|
|
256
265
|
"supports-eager-tool-input-streaming": wire("supportsEagerToolInputStreaming", ["anthropic"]),
|
|
@@ -328,6 +337,8 @@ export const AXES: Readonly<Record<string, AxisDef>> = {
|
|
|
328
337
|
"thinking-suppress-when-off": { key: "suppressWhenOff", set: "thinking", shape: "scalar" },
|
|
329
338
|
"thinking-supports-display": { key: "supportsDisplay", set: "thinking", shape: "scalar" },
|
|
330
339
|
"thinking-upgrade-neutral": { key: "upgradeNeutral", set: "thinking", shape: "scalar" },
|
|
340
|
+
/** Unclassified neutral ids here are capability-unknown: offer the ladder as opt-in. */
|
|
341
|
+
"thinking-unverified": { key: "unverified", set: "thinking", shape: "scalar" },
|
|
331
342
|
|
|
332
343
|
// ── catalog metadata ──
|
|
333
344
|
"apply-patch-tool-type": {
|
|
@@ -337,12 +348,21 @@ export const AXES: Readonly<Record<string, AxisDef>> = {
|
|
|
337
348
|
values: ["freeform", "function"],
|
|
338
349
|
},
|
|
339
350
|
"requires-native-tools": { key: "requiresNativeTools", set: "catalog", shape: "scalar", values: [true, false] },
|
|
351
|
+
"tool-schemas-in-context": { key: "toolSchemasInContext", set: "catalog", shape: "scalar", values: [true, false] },
|
|
340
352
|
"requires-tool-free-history-for-tool-opt-out": {
|
|
341
353
|
key: "requiresToolFreeHistoryForToolOptOut",
|
|
342
354
|
set: "catalog",
|
|
343
355
|
shape: "scalar",
|
|
344
356
|
values: [true, false],
|
|
345
357
|
},
|
|
358
|
+
/** Replayed tool calls must satisfy the target wire's declaration charset and length. */
|
|
359
|
+
"tool-call-name-charset": {
|
|
360
|
+
key: "toolCallNameCharset",
|
|
361
|
+
set: "catalog",
|
|
362
|
+
shape: "scalar",
|
|
363
|
+
values: ["ascii", "google"],
|
|
364
|
+
},
|
|
365
|
+
"tool-call-name-max-length": { key: "toolCallNameMaxLength", set: "catalog", shape: "scalar" },
|
|
346
366
|
"preserves-max-output-tokens": {
|
|
347
367
|
key: "preservesMaxOutputTokens",
|
|
348
368
|
set: "catalog",
|
|
@@ -367,6 +387,19 @@ export const AXES: Readonly<Record<string, AxisDef>> = {
|
|
|
367
387
|
shape: "scalar",
|
|
368
388
|
values: [true, false],
|
|
369
389
|
},
|
|
390
|
+
/**
|
|
391
|
+
* The model's input classifier refuses requests that reproduce assistant
|
|
392
|
+
* reasoning as quoted text (`reasoning_extraction`), so reasoning must not
|
|
393
|
+
* be replayed as text into its input: compaction archives, summarizer
|
|
394
|
+
* transcripts and demoted interrupted turns leave it out. Declared per class
|
|
395
|
+
* in `classes/*.kdl`.
|
|
396
|
+
*/
|
|
397
|
+
"classifier-cot-extraction": {
|
|
398
|
+
key: "classifierCotExtraction",
|
|
399
|
+
set: "catalog",
|
|
400
|
+
shape: "scalar",
|
|
401
|
+
values: [true, false],
|
|
402
|
+
},
|
|
370
403
|
"clamp-context-override": { key: "clampContextOverride", set: "catalog", shape: "scalar" },
|
|
371
404
|
"context-promotion-target": { key: "contextPromotionTarget", set: "catalog", shape: "scalar" },
|
|
372
405
|
"context-window-floor": { key: "contextWindowFloor", set: "catalog", shape: "scalar" },
|
|
@@ -376,6 +409,19 @@ export const AXES: Readonly<Record<string, AxisDef>> = {
|
|
|
376
409
|
shape: "scalar",
|
|
377
410
|
values: [true, false],
|
|
378
411
|
},
|
|
412
|
+
/**
|
|
413
|
+
* The model's long-context price tier does not bound its runtime context
|
|
414
|
+
* window: with extended context off, the window is not capped at the
|
|
415
|
+
* tier's input threshold (subscription hosts carrying API-equivalent tier
|
|
416
|
+
* prices, or a model served at its full window). Explicit user context
|
|
417
|
+
* overrides still apply.
|
|
418
|
+
*/
|
|
419
|
+
"long-context-window-uncapped": {
|
|
420
|
+
key: "longContextWindowUncapped",
|
|
421
|
+
set: "catalog",
|
|
422
|
+
shape: "scalar",
|
|
423
|
+
values: [true, false],
|
|
424
|
+
},
|
|
379
425
|
"cost-patch": { key: "costPatch", set: "catalog", shape: "object" },
|
|
380
426
|
"cost-fallback": { key: "costFallback", set: "catalog", shape: "object" },
|
|
381
427
|
/**
|
|
@@ -388,6 +434,26 @@ export const AXES: Readonly<Record<string, AxisDef>> = {
|
|
|
388
434
|
shape: "scalar",
|
|
389
435
|
values: [true, false],
|
|
390
436
|
},
|
|
437
|
+
/**
|
|
438
|
+
* The host bills cache writes at this multiple of the row's live `input`
|
|
439
|
+
* price, for hosts whose discovery reports cache-write tokens but no
|
|
440
|
+
* cache-write rate. It replaces any `cacheWrite` the row carries, including
|
|
441
|
+
* a `cost-patch`/`cost-fallback` value and an absolute long-context tier's
|
|
442
|
+
* `cacheWrite` (rebased on the tier's `input`).
|
|
443
|
+
*/
|
|
444
|
+
"cache-write-rate": { key: "cacheWriteRate", set: "catalog", shape: "scalar" },
|
|
445
|
+
/**
|
|
446
|
+
* How the model line bills a replayed signed thinking block as input:
|
|
447
|
+
* `output` bills the reasoning its signature carries, at the size the turn
|
|
448
|
+
* billed as output, and not the signature's bytes. Unassigned: estimated
|
|
449
|
+
* from the signature's bytes. Read through `./thinking-replay`.
|
|
450
|
+
*/
|
|
451
|
+
"thinking-replay-tokens": {
|
|
452
|
+
key: "thinkingReplayTokens",
|
|
453
|
+
set: "catalog",
|
|
454
|
+
shape: "scalar",
|
|
455
|
+
values: ["output", "signature"],
|
|
456
|
+
},
|
|
391
457
|
"delegation-bias": { key: "delegationBias", set: "catalog", shape: "scalar", values: DELEGATION_BIASES },
|
|
392
458
|
"discovery-api": { key: "discoveryApi", set: "catalog", shape: "scalar" },
|
|
393
459
|
"edit-prompt-variant": { key: "editPromptVariant", set: "catalog", shape: "scalar", values: ["full", "compact"] },
|
|
@@ -404,16 +470,74 @@ export const AXES: Readonly<Record<string, AxisDef>> = {
|
|
|
404
470
|
"hosted-image": { key: "hostedImage", set: "catalog", shape: "scalar", values: [true, false] },
|
|
405
471
|
/** How the model line bills an input image; shape and formulas in `./image-tokenization`. */
|
|
406
472
|
"image-tokenization": { key: "imageTokenization", set: "catalog", shape: "object" },
|
|
473
|
+
/**
|
|
474
|
+
* Most images one request may carry, documents included where the host
|
|
475
|
+
* counts them. Unassigned: unknown host, held to a conservative floor.
|
|
476
|
+
*/
|
|
477
|
+
"image-count-budget": { key: "imageCountBudget", set: "catalog", shape: "scalar" },
|
|
478
|
+
/**
|
|
479
|
+
* Replaces `image-count-budget` while the request reaches the official
|
|
480
|
+
* endpoint (pi-ai `reachesOfficialEndpoint`): a limit measured or documented
|
|
481
|
+
* for the host's own endpoint, which a custom `baseUrl` or an
|
|
482
|
+
* `ANTHROPIC_BASE_URL`/`FOUNDRY_BASE_URL` reroute (proxy, gateway) need not share.
|
|
483
|
+
*/
|
|
484
|
+
"official-image-count-budget": { key: "officialImageCountBudget", set: "catalog", shape: "scalar" },
|
|
485
|
+
/**
|
|
486
|
+
* Opt-in: the reader may get a fresh `read` result as snapcompact frames
|
|
487
|
+
* (`snapcompact.toolResults`), because it edits as exactly from frames as
|
|
488
|
+
* from text. Unassigned: `read` results stay text, since a reader that
|
|
489
|
+
* misreads indentation in frames makes inexact edits.
|
|
490
|
+
*/
|
|
491
|
+
"snapcompact-image-reads": { key: "snapcompactImageReads", set: "catalog", shape: "scalar", values: [true, false] },
|
|
407
492
|
"image-model": { key: "imageModel", set: "catalog", shape: "scalar" },
|
|
493
|
+
/** Longest image edge the host passes to the model: it downscales larger images to fit first. */
|
|
494
|
+
"image-max-edge": { key: "imageMaxEdge", set: "catalog", shape: "scalar" },
|
|
408
495
|
"inline-image-byte-budget": { key: "inlineImageByteBudget", set: "catalog", shape: "scalar" },
|
|
496
|
+
/**
|
|
497
|
+
* Supported formats and size bounds for judgment-request images; `mime-types`
|
|
498
|
+
* is comma-separated because object payloads have no array shape.
|
|
499
|
+
*/
|
|
500
|
+
"judgment-image-input": { key: "judgmentImageInput", set: "catalog", shape: "object" },
|
|
501
|
+
/**
|
|
502
|
+
* `Content-Encoding`s the host decodes on request bodies, most preferred
|
|
503
|
+
* first. Applied only while the request reaches the provider's own endpoint
|
|
504
|
+
* (see `endpoint-hosts`), so a proxy or rerouted endpoint never receives
|
|
505
|
+
* bytes it cannot read. Unassigned: bodies are sent uncompressed.
|
|
506
|
+
*/
|
|
507
|
+
"request-encodings": { key: "requestEncodings", set: "catalog", shape: "array", values: ["zstd", "gzip"] },
|
|
508
|
+
/**
|
|
509
|
+
* HTTPS hostnames the provider itself serves besides its bundled base URL's
|
|
510
|
+
* host — per-account endpoints such as Copilot's plan hosts. Together with
|
|
511
|
+
* that host they define the provider's own endpoint, which tells first-party
|
|
512
|
+
* traffic from a custom gateway that reuses the provider id.
|
|
513
|
+
*/
|
|
514
|
+
"endpoint-hosts": { key: "endpointHosts", set: "catalog", shape: "array" },
|
|
515
|
+
/**
|
|
516
|
+
* Pattern of the JSON 400/415 error body the host returns when it cannot
|
|
517
|
+
* decode a compressed request body. Unassigned: every JSON 400/415 is a
|
|
518
|
+
* possible rejection and is re-checked with an uncompressed resend. Either
|
|
519
|
+
* way, a 400/403/415 whose body is not JSON (presumed a proxy's answer, not
|
|
520
|
+
* the host's) is re-checked too.
|
|
521
|
+
*/
|
|
522
|
+
"request-encoding-rejection": { key: "requestEncodingRejection", set: "catalog", shape: "scalar" },
|
|
409
523
|
/**
|
|
410
524
|
* Most questions one System One judgment request may carry; the judge
|
|
411
525
|
* splits larger requests. Unassigned: the endpoint takes any batch size.
|
|
412
526
|
*/
|
|
413
527
|
"judgment-questions-max": { key: "judgmentQuestionsMax", set: "catalog", shape: "scalar" },
|
|
414
528
|
"limits-patch": { key: "limitsPatch", set: "catalog", shape: "object" },
|
|
529
|
+
/** Seeds `maxTokens` only when the row reports none; applied after `limits-patch`. */
|
|
530
|
+
"max-tokens-fallback": { key: "maxTokensFallback", set: "catalog", shape: "scalar" },
|
|
531
|
+
/** Most output tokens the host serves: a larger reported `maxTokens` is lowered to it; an unknown one stays unknown. */
|
|
532
|
+
"max-tokens-ceiling": { key: "maxTokensCeiling", set: "catalog", shape: "scalar" },
|
|
415
533
|
"long-context-cost": { key: "longContext", set: "catalog", shape: "object" },
|
|
416
534
|
"prompt-cache": { key: "promptCache", set: "catalog", shape: "object" },
|
|
535
|
+
"task-sibling-prompt-cache": {
|
|
536
|
+
key: "taskSiblingPromptCache",
|
|
537
|
+
set: "catalog",
|
|
538
|
+
shape: "scalar",
|
|
539
|
+
values: [true, false],
|
|
540
|
+
},
|
|
417
541
|
/**
|
|
418
542
|
* Prompt-cache lookback in block positions: how far back from a cache
|
|
419
543
|
* breakpoint the provider looks for an earlier request's cache entry.
|
|
@@ -532,6 +656,7 @@ export const AXES: Readonly<Record<string, AxisDef>> = {
|
|
|
532
656
|
|
|
533
657
|
/** Records applicable to each API family; used by `resolve.ts` when applying wire axes. */
|
|
534
658
|
export const API_COMPAT_RECORDS: Readonly<Record<string, readonly CompatRecordName[]>> = {
|
|
659
|
+
"ollama-chat": ["ollama"],
|
|
535
660
|
"openai-completions": ["openai"],
|
|
536
661
|
openrouter: ["openai", "openai-responses"],
|
|
537
662
|
"openai-responses": ["openai-responses"],
|
package/src/compat/behavior.d.ts
CHANGED
|
@@ -78,3 +78,8 @@ export declare function pricingPeerFor(provider: string, model: string): {
|
|
|
78
78
|
peers: readonly string[];
|
|
79
79
|
peerId: string;
|
|
80
80
|
} | undefined;
|
|
81
|
+
/**
|
|
82
|
+
* Built-in default model selector chains per agent role (`smol`, `slow`,
|
|
83
|
+
* `image`, …), most preferred first, in declaration order.
|
|
84
|
+
*/
|
|
85
|
+
export declare function roleDefaultModels(): Readonly<Record<string, readonly string[]>>;
|
package/src/compat/behavior.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* (`rules/runtime/behavior.kdl`): provider/model heuristics that run before
|
|
4
4
|
* or outside exact bundled-model lookup — responses routing, API routing,
|
|
5
5
|
* quota tiers, plan requirements, model limits, roster and discovery-mode
|
|
6
|
-
* exclusions, hosted defaults, and
|
|
6
|
+
* exclusions, hosted defaults, pricing peers, and per-role default models.
|
|
7
7
|
*/
|
|
8
8
|
import { globMatch } from "./cascade";
|
|
9
9
|
import rules from "./rules.json";
|
|
@@ -225,3 +225,11 @@ export function pricingPeerFor(
|
|
|
225
225
|
const alias = rule.aliases.find(candidate => candidate.model === model);
|
|
226
226
|
return { peers: rule.peers, peerId: alias?.peerId ?? model };
|
|
227
227
|
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Built-in default model selector chains per agent role (`smol`, `slow`,
|
|
231
|
+
* `image`, …), most preferred first, in declaration order.
|
|
232
|
+
*/
|
|
233
|
+
export function roleDefaultModels(): Readonly<Record<string, readonly string[]>> {
|
|
234
|
+
return behavior.roleDefaults;
|
|
235
|
+
}
|
package/src/compat/cascade.ts
CHANGED
|
@@ -409,13 +409,16 @@ function resolveOverIndex(index: RuleIndex, target: ResolveTarget): ResolvedAxes
|
|
|
409
409
|
reasoning = true;
|
|
410
410
|
}
|
|
411
411
|
}
|
|
412
|
+
// Unclassified targets get their provider's ladder too: it is offered as
|
|
413
|
+
// unverified thinking (`resolveThinkingPolicy`), never as reasoning capability.
|
|
414
|
+
const offersThinking = reasoning || target.class === "unknown";
|
|
412
415
|
const wire: WinnerTable = {};
|
|
413
416
|
const thinking: WinnerTable = {};
|
|
414
417
|
const catalog: WinnerTable = {};
|
|
415
418
|
for (const { rule, rank } of ranked) {
|
|
416
419
|
contest(wire, rule.compiled.wire, rank, rule, target);
|
|
417
420
|
contest(catalog, rule.compiled.catalog, rank, rule, target);
|
|
418
|
-
if (
|
|
421
|
+
if (offersThinking) contest(thinking, rule.compiled.thinking, rank, rule, target);
|
|
419
422
|
}
|
|
420
423
|
return {
|
|
421
424
|
wire: collect(wire, rule => rule.wire),
|