@linxiraos/pi-catalog 1.1.26 → 1.2.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 +4 -6
- package/dist/types/compat/auth-ids.d.ts +2 -2
- package/dist/types/compat/collapse.d.ts +7 -0
- package/dist/types/compat/image-tokenization.d.ts +52 -0
- package/dist/types/compat/judgment-questions.d.ts +7 -0
- package/dist/types/compat/prompt-cache-lookback.d.ts +7 -0
- package/dist/types/compat/provider-ids.d.ts +1 -1
- package/dist/types/compat/reasoning-carry.d.ts +21 -0
- package/dist/types/compat/request-size.d.ts +7 -0
- package/dist/types/compat/taxonomy.d.ts +0 -10
- package/dist/types/compat/types.d.ts +4 -0
- package/dist/types/discovery/antigravity.d.ts +13 -3
- package/dist/types/hosts.d.ts +4 -0
- package/dist/types/model-cache.d.ts +6 -0
- package/dist/types/model-manager.d.ts +16 -0
- package/dist/types/provider-models/account-access.d.ts +11 -0
- package/dist/types/provider-models/google.d.ts +23 -1
- package/dist/types/provider-models/openai-compat.d.ts +19 -1
- package/dist/types/provider-models/special.d.ts +8 -0
- package/dist/types/types.d.ts +134 -23
- package/dist/types/wire/cloudflare-ai-gateway.d.ts +0 -10
- package/dist/types/wire/cloudflare.d.ts +10 -0
- package/dist/types/wire/factory-droid.d.ts +1 -1
- package/dist/types/wire/gemini-headers.d.ts +6 -3
- package/package.json +4 -4
- package/src/build.ts +39 -35
- package/src/compat/auth-ids.ts +6 -0
- package/src/compat/axes.ts +43 -5
- package/src/compat/cascade.ts +6 -1
- package/src/compat/collapse.d.ts +7 -0
- package/src/compat/collapse.ts +11 -0
- package/src/compat/image-tokenization.ts +166 -0
- package/src/compat/judgment-questions.ts +12 -0
- package/src/compat/prompt-cache-lookback.ts +23 -0
- package/src/compat/provider-ids.d.ts +1 -1
- package/src/compat/provider-ids.ts +3 -0
- package/src/compat/reasoning-carry.ts +64 -0
- package/src/compat/request-size.ts +25 -0
- package/src/compat/resolve.ts +36 -25
- package/src/compat/rules/README.md +13 -3
- package/src/compat/rules/auth/_order.kdl +2 -2
- package/src/compat/rules/auth/cloudflare-workers-ai.kdl +9 -0
- package/src/compat/rules/auth/coralbricks.kdl +13 -0
- package/src/compat/rules/auth/minimax-code-cn.kdl +1 -1
- package/src/compat/rules/auth/minimax-code.kdl +1 -1
- package/src/compat/rules/auth/openai-codex.kdl +1 -1
- package/src/compat/rules/auth/snowflake.kdl +11 -0
- package/src/compat/rules/auth/stencil.kdl +1 -1
- package/src/compat/rules/classes/anthropic.kdl +188 -7
- package/src/compat/rules/classes/deepseek.kdl +11 -3
- package/src/compat/rules/classes/gemini.kdl +12 -0
- package/src/compat/rules/classes/glm.kdl +17 -8
- package/src/compat/rules/classes/kimi.kdl +15 -0
- package/src/compat/rules/classes/meta.kdl +4 -4
- package/src/compat/rules/classes/minimax.kdl +31 -1
- package/src/compat/rules/classes/openai.kdl +114 -4
- package/src/compat/rules/classes/qwen.kdl +2 -1
- package/src/compat/rules/providers/abliteration.kdl +7 -0
- package/src/compat/rules/providers/amazon-bedrock.kdl +0 -7
- package/src/compat/rules/providers/anthropic.kdl +13 -0
- package/src/compat/rules/providers/apple.kdl +1 -0
- package/src/compat/rules/providers/cloudflare-workers-ai.kdl +34 -0
- package/src/compat/rules/providers/coralbricks.kdl +62 -0
- package/src/compat/rules/providers/cursor.kdl +28 -8
- package/src/compat/rules/providers/devin.kdl +2 -2
- package/src/compat/rules/providers/factory-droid.kdl +77 -13
- package/src/compat/rules/providers/github-copilot.kdl +29 -0
- package/src/compat/rules/providers/google-antigravity.kdl +8 -1
- package/src/compat/rules/providers/groq.kdl +21 -0
- package/src/compat/rules/providers/image-tokenization.kdl +42 -0
- package/src/compat/rules/providers/judgment-questions.kdl +53 -0
- package/src/compat/rules/providers/litellm.kdl +4 -1
- package/src/compat/rules/providers/meta.kdl +3 -3
- package/src/compat/rules/providers/minimax-code-cn.kdl +10 -5
- package/src/compat/rules/providers/minimax-code.kdl +10 -5
- package/src/compat/rules/providers/mistral.kdl +25 -1
- package/src/compat/rules/providers/moonshot.kdl +11 -0
- package/src/compat/rules/providers/muse-code.kdl +7 -0
- package/src/compat/rules/providers/ollama.kdl +3 -0
- package/src/compat/rules/providers/openai-codex.kdl +18 -13
- package/src/compat/rules/providers/openai.kdl +22 -0
- package/src/compat/rules/providers/opencode-go.kdl +11 -0
- package/src/compat/rules/providers/opencode-zen.kdl +20 -0
- package/src/compat/rules/providers/openrouter.kdl +6 -0
- package/src/compat/rules/providers/snowflake.kdl +135 -0
- package/src/compat/rules/providers/tool-result-images.kdl +16 -0
- package/src/compat/rules/providers/zhipu-coding-plan.kdl +10 -0
- package/src/compat/rules/runtime/behavior.kdl +8 -5
- package/src/compat/rules/taxonomy/_collapse.kdl +52 -2
- package/src/compat/rules/taxonomy/deepseek.kdl +4 -0
- package/src/compat/rules/taxonomy/kimi.kdl +5 -0
- package/src/compat/rules.json +1 -1
- package/src/compat/taxonomy.d.ts +0 -10
- package/src/compat/taxonomy.ts +3 -3
- package/src/compat/types.d.ts +4 -0
- package/src/compat/types.ts +4 -0
- package/src/discovery/antigravity.d.ts +13 -3
- package/src/discovery/antigravity.ts +33 -17
- package/src/discovery/cursor.ts +28 -4
- package/src/discovery/devin.ts +20 -2
- package/src/hosts.d.ts +4 -0
- package/src/hosts.ts +2 -0
- package/src/model-cache.d.ts +6 -0
- package/src/model-cache.ts +123 -47
- package/src/model-manager.d.ts +16 -0
- package/src/model-manager.ts +107 -41
- package/src/model-tokenizer.ts +3 -0
- package/src/models.json +1 -1
- package/src/provider-models/account-access.d.ts +11 -0
- package/src/provider-models/account-access.ts +30 -0
- package/src/provider-models/cache-provider-id.ts +13 -6
- package/src/provider-models/cline-pass.ts +33 -0
- package/src/provider-models/descriptors.ts +4 -0
- package/src/provider-models/google.d.ts +23 -1
- package/src/provider-models/google.ts +72 -14
- package/src/provider-models/openai-compat.d.ts +19 -1
- package/src/provider-models/openai-compat.ts +281 -42
- package/src/provider-models/special.d.ts +8 -0
- package/src/provider-models/special.ts +26 -22
- package/src/types.d.ts +134 -23
- package/src/types.ts +183 -20
- package/src/wire/cloudflare-ai-gateway.d.ts +0 -10
- package/src/wire/cloudflare-ai-gateway.ts +0 -36
- package/src/wire/cloudflare.ts +40 -0
- package/src/wire/factory-droid.d.ts +1 -1
- package/src/wire/factory-droid.ts +1 -1
- package/src/wire/gemini-headers.d.ts +6 -3
- package/src/wire/gemini-headers.ts +39 -21
package/src/compat/axes.ts
CHANGED
|
@@ -140,6 +140,7 @@ export const AXES: Readonly<Record<string, AxisDef>> = {
|
|
|
140
140
|
"qwen-preserve-thinking": wire("qwenPreserveThinking", ["openai"]),
|
|
141
141
|
"reject-root-object-union": wire("rejectRootObjectUnion", OAI),
|
|
142
142
|
"retry-without-strict-on-grammar-error": wire("retryWithoutStrictOnGrammarError", OAI),
|
|
143
|
+
"session-header": wire("sessionHeader", OAI, "scalar", ["x-litellm-session-id"]),
|
|
143
144
|
"reasoning-content-field": wire("reasoningContentField", OAI, "scalar", [
|
|
144
145
|
"reasoning_content",
|
|
145
146
|
"reasoning",
|
|
@@ -169,6 +170,8 @@ export const AXES: Readonly<Record<string, AxisDef>> = {
|
|
|
169
170
|
"requires-thinking-as-text": wire("requiresThinkingAsText", ["openai"]),
|
|
170
171
|
"requires-tool-result-name": wire("requiresToolResultName", ["openai"]),
|
|
171
172
|
"strict-responses-pairing": wire("strictResponsesPairing", ["openai-responses"]),
|
|
173
|
+
"connection-bound-native-history": wire("connectionBoundNativeHistory", ["openai-responses"]),
|
|
174
|
+
"stateful-responses": wire("statefulResponses", ["openai-responses"]),
|
|
172
175
|
"requires-reasoning-off-juice-instruction": wire("requiresReasoningOffJuiceInstruction", ["openai-responses"]),
|
|
173
176
|
"supports-all-turns-reasoning-context": wire("supportsAllTurnsReasoningContext", ["openai-responses"]),
|
|
174
177
|
"supports-configuration-update": wire("supportsConfigurationUpdate", ["openai-responses"]),
|
|
@@ -194,6 +197,7 @@ export const AXES: Readonly<Record<string, AxisDef>> = {
|
|
|
194
197
|
"supports-reasoning-effort": wire("supportsReasoningEffort", OAI),
|
|
195
198
|
"supports-reasoning-params": wire("supportsReasoningParams", OAI),
|
|
196
199
|
"supports-reasoning-summary": wire("supportsReasoningSummary", ["openai-responses"]),
|
|
200
|
+
"store-responses": wire("storeResponses", ["openai-responses"]),
|
|
197
201
|
"supports-store": wire("supportsStore", ["openai"]),
|
|
198
202
|
"supports-strict-mode": wire("supportsStrictMode", OAI),
|
|
199
203
|
"supports-tool-choice": wire("supportsToolChoice", OAI),
|
|
@@ -227,12 +231,20 @@ export const AXES: Readonly<Record<string, AxisDef>> = {
|
|
|
227
231
|
"allow-anthropic-header-overrides": wire("allowAnthropicHeaderOverrides", ["anthropic"]),
|
|
228
232
|
"disable-adaptive-thinking": wire("disableAdaptiveThinking", ["anthropic"]),
|
|
229
233
|
"disable-strict-tools": wire("disableStrictTools", ["anthropic"]),
|
|
230
|
-
"
|
|
234
|
+
"between-tools-effort": wire("betweenToolsEffort", ["anthropic"], "scalar", ["low", "medium", "high"]),
|
|
235
|
+
"disabled-thinking": wire("disabledThinking", ["anthropic", "bedrock"], "scalar", [
|
|
236
|
+
"omit",
|
|
237
|
+
"disabled",
|
|
238
|
+
"adaptive",
|
|
239
|
+
"between-tools",
|
|
240
|
+
]),
|
|
231
241
|
"effort-beta": wire("effortBeta", ["anthropic"]),
|
|
232
242
|
"escape-builtin-tool-names": wire("escapeBuiltinToolNames", ["anthropic"]),
|
|
233
243
|
"fast-mode": wire("fastMode", ["anthropic"]),
|
|
234
244
|
"first-party-provider": wire("firstPartyProvider", ["anthropic"]),
|
|
235
245
|
"inject-claude-code-instruction": wire("injectClaudeCodeInstruction", ["anthropic"]),
|
|
246
|
+
"max-image-dimension": wire("maxImageDimension", ["anthropic"]),
|
|
247
|
+
"max-image-payload-bytes": wire("maxImagePayloadBytes", ["anthropic"]),
|
|
236
248
|
"official-endpoint": wire("officialEndpoint", ["anthropic", "openai-responses"]),
|
|
237
249
|
"replay-unsigned-thinking": wire("replayUnsignedThinking", ["anthropic"]),
|
|
238
250
|
"requires-thinking-enabled": wire("requiresThinkingEnabled", ["anthropic"]),
|
|
@@ -248,7 +260,7 @@ export const AXES: Readonly<Record<string, AxisDef>> = {
|
|
|
248
260
|
"supports-per-message-effort": wire("supportsPerMessageEffort", ["anthropic"]),
|
|
249
261
|
"supports-server-compaction": wire("supportsServerCompaction", ["anthropic"]),
|
|
250
262
|
"supports-between-tools-thinking": wire("supportsBetweenToolsThinking", ["anthropic"]),
|
|
251
|
-
"supports-thinking-binding-controls": wire("supportsThinkingBindingControls", ["anthropic"]),
|
|
263
|
+
"supports-thinking-binding-controls": wire("supportsThinkingBindingControls", ["anthropic", "bedrock"]),
|
|
252
264
|
"supports-turn-scoped-system": wire("supportsTurnScopedSystem", ["anthropic"]),
|
|
253
265
|
|
|
254
266
|
// ── wire: bedrock-converse-stream ──
|
|
@@ -259,7 +271,7 @@ export const AXES: Readonly<Record<string, AxisDef>> = {
|
|
|
259
271
|
// ── wire: devin-agent ──
|
|
260
272
|
"model-router": wire("modelRouter", ["devin"]),
|
|
261
273
|
"supports-parallel-tool-calls": wire("supportsParallelToolCalls", ["devin"]),
|
|
262
|
-
"trust-explicit-thinking-only": wire("trustExplicitThinkingOnly", ["devin"]),
|
|
274
|
+
"trust-explicit-thinking-only": wire("trustExplicitThinkingOnly", [...OAI, "devin"]),
|
|
263
275
|
|
|
264
276
|
// ── wire: google APIs ──
|
|
265
277
|
"antigravity-claude-tool-mode": wire("antigravityClaudeToolMode", ["google"]),
|
|
@@ -293,7 +305,7 @@ export const AXES: Readonly<Record<string, AxisDef>> = {
|
|
|
293
305
|
"stream-idle-timeout-ms": wire("streamIdleTimeoutMs", [...OAI, "anthropic", "bedrock", "google"]),
|
|
294
306
|
"strip-image-input": wire("stripImageInput", [...OAI, "anthropic", "google"]),
|
|
295
307
|
"supports-forced-tool-choice": wire("supportsForcedToolChoice", [...OAI, "anthropic", "bedrock"]),
|
|
296
|
-
"supports-sampling-params": wire("supportsSamplingParams", [...OAI, "anthropic"]),
|
|
308
|
+
"supports-sampling-params": wire("supportsSamplingParams", [...OAI, "anthropic", "bedrock", "devin", "google"]),
|
|
297
309
|
"thinking-loop-guard": wire("thinkingLoopGuard", [...OAI, "anthropic", "google"], "scalar", [
|
|
298
310
|
"gemini",
|
|
299
311
|
"deepseek",
|
|
@@ -304,7 +316,7 @@ export const AXES: Readonly<Record<string, AxisDef>> = {
|
|
|
304
316
|
"thinking-default-level": { key: "defaultLevel", set: "thinking", shape: "scalar", values: EFFORTS },
|
|
305
317
|
"thinking-effort-budgets": { key: "effortBudgets", set: "thinking", shape: "object" },
|
|
306
318
|
"thinking-effort-map": { key: "effortMap", set: "thinking", shape: "object" },
|
|
307
|
-
"thinking-efforts": { key: "efforts", set: "thinking", shape: "array", values: EFFORTS },
|
|
319
|
+
"thinking-efforts": { key: "efforts", set: "thinking", shape: "array", values: EFFORTS, emptyArray: true },
|
|
308
320
|
"thinking-mode": {
|
|
309
321
|
key: "mode",
|
|
310
322
|
set: "thinking",
|
|
@@ -390,12 +402,38 @@ export const AXES: Readonly<Record<string, AxisDef>> = {
|
|
|
390
402
|
},
|
|
391
403
|
"web-search-model": { key: "webSearchModel", set: "catalog", shape: "scalar" },
|
|
392
404
|
"hosted-image": { key: "hostedImage", set: "catalog", shape: "scalar", values: [true, false] },
|
|
405
|
+
/** How the model line bills an input image; shape and formulas in `./image-tokenization`. */
|
|
406
|
+
"image-tokenization": { key: "imageTokenization", set: "catalog", shape: "object" },
|
|
393
407
|
"image-model": { key: "imageModel", set: "catalog", shape: "scalar" },
|
|
408
|
+
"inline-image-byte-budget": { key: "inlineImageByteBudget", set: "catalog", shape: "scalar" },
|
|
409
|
+
/**
|
|
410
|
+
* Most questions one System One judgment request may carry; the judge
|
|
411
|
+
* splits larger requests. Unassigned: the endpoint takes any batch size.
|
|
412
|
+
*/
|
|
413
|
+
"judgment-questions-max": { key: "judgmentQuestionsMax", set: "catalog", shape: "scalar" },
|
|
394
414
|
"limits-patch": { key: "limitsPatch", set: "catalog", shape: "object" },
|
|
395
415
|
"long-context-cost": { key: "longContext", set: "catalog", shape: "object" },
|
|
396
416
|
"prompt-cache": { key: "promptCache", set: "catalog", shape: "object" },
|
|
417
|
+
/**
|
|
418
|
+
* Prompt-cache lookback in block positions: how far back from a cache
|
|
419
|
+
* breakpoint the provider looks for an earlier request's cache entry.
|
|
420
|
+
* Unassigned: no known lookback bound.
|
|
421
|
+
*/
|
|
422
|
+
"prompt-cache-lookback": { key: "promptCacheLookback", set: "catalog", shape: "scalar" },
|
|
397
423
|
"long-usage-limit-fallback": { key: "longUsageLimitFallback", set: "catalog", shape: "scalar" },
|
|
398
424
|
"max-context-window": { key: "maxContextWindow", set: "catalog", shape: "scalar" },
|
|
425
|
+
/**
|
|
426
|
+
* Every host of the model returns its full plaintext trace wherever the
|
|
427
|
+
* wire does not mark reasoning as a summary (`ThinkingContent.summary ===
|
|
428
|
+
* false` vouches for nothing more), and its identity (class + family +
|
|
429
|
+
* revision) pins one set of weights, so a turn's reasoning may replay
|
|
430
|
+
* natively on another host serving the same model. Marked summaries never
|
|
431
|
+
* carry; do not declare it where a host serves summaries unmarked.
|
|
432
|
+
* Declared per family in `classes/*.kdl`; a host whose reasoning must not be
|
|
433
|
+
* carried in or out sets `#false` in its `providers/*.kdl`. Read through
|
|
434
|
+
* `compat/reasoning-carry.ts`.
|
|
435
|
+
*/
|
|
436
|
+
"portable-reasoning": { key: "portableReasoning", set: "catalog", shape: "scalar", values: [true, false] },
|
|
399
437
|
"pricing-status": {
|
|
400
438
|
key: "pricingStatus",
|
|
401
439
|
set: "catalog",
|
package/src/compat/cascade.ts
CHANGED
|
@@ -288,7 +288,12 @@ function collect(winners: WinnerTable, pick: (rule: CompiledRule) => Record<stri
|
|
|
288
288
|
return out;
|
|
289
289
|
}
|
|
290
290
|
|
|
291
|
-
|
|
291
|
+
/**
|
|
292
|
+
* Sized above the bundled catalog (~5.6k targets) plus discovered rows so a
|
|
293
|
+
* catalog-wide buildModel pass (discovery, then merge rebuilding the same ids)
|
|
294
|
+
* hits instead of thrashing. Entries share rule values: a few hundred bytes each.
|
|
295
|
+
*/
|
|
296
|
+
const resolveCache = new LRUCache<string, ResolvedAxes>({ max: 16384 });
|
|
292
297
|
|
|
293
298
|
function keyPart(value: string | undefined): string {
|
|
294
299
|
return value === undefined ? "-1:" : `${value.length}:${value}`;
|
package/src/compat/collapse.d.ts
CHANGED
|
@@ -102,6 +102,13 @@ export interface VariantCollapseTable {
|
|
|
102
102
|
* Discovery uses this accessor to preserve injectable table seams.
|
|
103
103
|
*/
|
|
104
104
|
export declare function reviewedCollapseTable(provider: string): VariantCollapseTable | undefined;
|
|
105
|
+
/**
|
|
106
|
+
* Logical id of the reviewed `provider` family claiming `memberId` as a member
|
|
107
|
+
* or extra alias, or `undefined` when no reviewed family owns it. Cursor's rich
|
|
108
|
+
* discovery names lanes with it so they keep the id the legacy-slug collapse
|
|
109
|
+
* (and the bundled catalog) assigns.
|
|
110
|
+
*/
|
|
111
|
+
export declare function reviewedVariantFamilyId(provider: string, memberId: string): string | undefined;
|
|
105
112
|
/**
|
|
106
113
|
* The global automatic rule: derive an `X` + `X-thinking` family for every
|
|
107
114
|
* pair where both ids are live in `specs` (trailing or infix token). Gates:
|
package/src/compat/collapse.ts
CHANGED
|
@@ -492,6 +492,17 @@ export function reviewedCollapseTable(provider: string): VariantCollapseTable |
|
|
|
492
492
|
return REVIEWED_COLLAPSE_TABLES[provider] ?? REVIEWED_COLLAPSE_TABLES[provider.toLowerCase()];
|
|
493
493
|
}
|
|
494
494
|
|
|
495
|
+
/**
|
|
496
|
+
* Logical id of the reviewed `provider` family claiming `memberId` as a member
|
|
497
|
+
* or extra alias, or `undefined` when no reviewed family owns it. Cursor's rich
|
|
498
|
+
* discovery names lanes with it so they keep the id the legacy-slug collapse
|
|
499
|
+
* (and the bundled catalog) assigns.
|
|
500
|
+
*/
|
|
501
|
+
export function reviewedVariantFamilyId(provider: string, memberId: string): string | undefined {
|
|
502
|
+
const table = reviewedCollapseTable(provider);
|
|
503
|
+
return table ? getAliasIndex(table).resolve(memberId.trim()) : undefined;
|
|
504
|
+
}
|
|
505
|
+
|
|
495
506
|
/**
|
|
496
507
|
* The global automatic rule: derive an `X` + `X-thinking` family for every
|
|
497
508
|
* pair where both ids are live in `specs` (trailing or infix token). Gates:
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Billed input tokens per image, from the `image-tokenization` rules in the
|
|
3
|
+
* compat cascade: a model line's lineage rule (`rules/classes/*.kdl`) applies
|
|
4
|
+
* on every host, and readers without one fall back to the rule for the wire
|
|
5
|
+
* API carrying the request (`rules/providers/image-tokenization.kdl`).
|
|
6
|
+
*/
|
|
7
|
+
import { isRecord } from "@linxiraos/pi-utils/type-guards";
|
|
8
|
+
import { classifyModel } from "../identity";
|
|
9
|
+
import { resolveCascade } from "./cascade";
|
|
10
|
+
import type { ModelIdentity } from "./types";
|
|
11
|
+
|
|
12
|
+
/** OpenAI image `detail` levels; `undefined` means the provider default (`auto`). */
|
|
13
|
+
export type ImageDetail = "auto" | "low" | "high" | "original";
|
|
14
|
+
|
|
15
|
+
export interface ImageSize {
|
|
16
|
+
width: number;
|
|
17
|
+
height: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** One OpenAI `detail` level's sizing: a pixel-dimension limit, then an optional patch budget. */
|
|
21
|
+
export interface PatchSizing {
|
|
22
|
+
/** Longest side after the pixel-dimension fit. */
|
|
23
|
+
maxEdge: number;
|
|
24
|
+
/** Resizing patch budget; absent when the level keeps the image's size. */
|
|
25
|
+
patchBudget?: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** OpenAI 32px patches × a per-model multiplier, sized per `detail` level. */
|
|
29
|
+
export interface OpenAiPatchTokenization {
|
|
30
|
+
regime: "openai-patch";
|
|
31
|
+
multiplier: number;
|
|
32
|
+
low: PatchSizing;
|
|
33
|
+
high: PatchSizing;
|
|
34
|
+
original: PatchSizing;
|
|
35
|
+
/** The level `auto` (and an omitted `detail`) sizes like. */
|
|
36
|
+
auto: "high" | "original";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** One model line's image billing rule: the vendor formula and its per-model numbers. */
|
|
40
|
+
export type ImageTokenization =
|
|
41
|
+
| OpenAiPatchTokenization
|
|
42
|
+
/** Anthropic 28px patches, resized to fit a padded-edge limit and a visual-token budget. */
|
|
43
|
+
| { regime: "anthropic-patch"; maxEdge: number; maxTokens: number }
|
|
44
|
+
/** A fixed per-image budget regardless of pixels (Gemini 3 `media_resolution`). */
|
|
45
|
+
| { regime: "fixed"; tokens: number };
|
|
46
|
+
|
|
47
|
+
/** A `class: "unknown"` identity for targets resolved by wire API alone. */
|
|
48
|
+
const UNCLASSIFIED: Pick<ModelIdentity, "class"> = { class: "unknown" };
|
|
49
|
+
|
|
50
|
+
function isImageTokenization(value: unknown): value is ImageTokenization {
|
|
51
|
+
return (
|
|
52
|
+
isRecord(value) &&
|
|
53
|
+
(value.regime === "openai-patch" || value.regime === "anthropic-patch" || value.regime === "fixed")
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** What reads the image: a built model, any `{ id }` with optional host and identity, or just a wire `{ api }`. */
|
|
58
|
+
export interface ImageTokenizationTarget {
|
|
59
|
+
id?: string;
|
|
60
|
+
provider?: string;
|
|
61
|
+
api?: string;
|
|
62
|
+
identity?: Pick<ModelIdentity, "class" | "family" | "revision">;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The reader's image billing rule: its lineage rule, else its wire API's
|
|
67
|
+
* fallback; undefined when neither covers it (an unclassified model on an
|
|
68
|
+
* unregistered API).
|
|
69
|
+
*/
|
|
70
|
+
export function resolveImageTokenization(target: ImageTokenizationTarget): ImageTokenization | undefined {
|
|
71
|
+
const provider = target.provider ?? "";
|
|
72
|
+
const model = target.id ?? "";
|
|
73
|
+
const identity: Pick<ModelIdentity, "class" | "family" | "revision"> =
|
|
74
|
+
target.identity ?? (model ? classifyModel(provider, model, { lenient: true }) : UNCLASSIFIED);
|
|
75
|
+
const rule = resolveCascade({
|
|
76
|
+
provider,
|
|
77
|
+
api: target.api ?? "",
|
|
78
|
+
class: identity.class,
|
|
79
|
+
model,
|
|
80
|
+
reasoning: false,
|
|
81
|
+
...(identity.family !== undefined && { family: identity.family }),
|
|
82
|
+
...(identity.revision !== undefined && { revision: identity.revision }),
|
|
83
|
+
}).catalog.imageTokenization;
|
|
84
|
+
return isImageTokenization(rule) ? rule : undefined;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Scale `size` down (never up) so its longest side is at most `maxEdge`. */
|
|
88
|
+
function fitLongEdge(size: ImageSize, maxEdge: number): ImageSize {
|
|
89
|
+
const longest = Math.max(size.width, size.height);
|
|
90
|
+
if (longest <= maxEdge) return size;
|
|
91
|
+
const scale = maxEdge / longest;
|
|
92
|
+
return {
|
|
93
|
+
width: Math.max(1, Math.round(size.width * scale)),
|
|
94
|
+
height: Math.max(1, Math.round(size.height * scale)),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const OPENAI_PATCH_PX = 32;
|
|
99
|
+
|
|
100
|
+
/** <https://developers.openai.com/api/docs/guides/images-vision#patch-based-image-tokenization> */
|
|
101
|
+
function openAiPatches(size: ImageSize, sizing: PatchSizing): number {
|
|
102
|
+
const { width, height } = fitLongEdge(size, sizing.maxEdge);
|
|
103
|
+
const patches = Math.ceil(width / OPENAI_PATCH_PX) * Math.ceil(height / OPENAI_PATCH_PX);
|
|
104
|
+
const budget = sizing.patchBudget;
|
|
105
|
+
if (budget === undefined || patches <= budget) return patches;
|
|
106
|
+
|
|
107
|
+
const shrink = Math.sqrt((OPENAI_PATCH_PX * OPENAI_PATCH_PX * budget) / (width * height));
|
|
108
|
+
const scaledW = (width * shrink) / OPENAI_PATCH_PX;
|
|
109
|
+
const scaledH = (height * shrink) / OPENAI_PATCH_PX;
|
|
110
|
+
const adjusted = shrink * Math.min(Math.floor(scaledW) / scaledW, Math.floor(scaledH) / scaledH);
|
|
111
|
+
const resizedW = Math.floor(width * adjusted);
|
|
112
|
+
const resizedH = Math.floor(height * adjusted);
|
|
113
|
+
if (resizedW <= 0 || resizedH <= 0) return budget;
|
|
114
|
+
return Math.min(budget, Math.ceil(resizedW / OPENAI_PATCH_PX) * Math.ceil(resizedH / OPENAI_PATCH_PX));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const ANTHROPIC_PATCH_PX = 28;
|
|
118
|
+
|
|
119
|
+
/** Round half to even, like the API's resize (and Python's `round`). */
|
|
120
|
+
function roundTiesToEven(value: number): number {
|
|
121
|
+
const floor = Math.floor(value);
|
|
122
|
+
if (value - floor !== 0.5) return Math.round(value);
|
|
123
|
+
return floor % 2 === 0 ? floor : floor + 1;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Anthropic visual tokens after the API's resize: the largest
|
|
128
|
+
* aspect-preserving size whose 28px-padded edges fit `maxEdge` and whose
|
|
129
|
+
* patch count fits `maxTokens`, found by bisecting the long edge. Port of the
|
|
130
|
+
* reference implementation:
|
|
131
|
+
* <https://platform.claude.com/docs/en/build-with-claude/vision-coordinates#resize-your-image-before-uploading>
|
|
132
|
+
*/
|
|
133
|
+
function anthropicTokens(size: ImageSize, maxEdge: number, maxTokens: number): number {
|
|
134
|
+
const long = Math.max(size.width, size.height);
|
|
135
|
+
const shortSide = Math.min(size.width, size.height);
|
|
136
|
+
const aspect = long / shortSide;
|
|
137
|
+
const patches = (l: number, s: number) => Math.ceil(l / ANTHROPIC_PATCH_PX) * Math.ceil(s / ANTHROPIC_PATCH_PX);
|
|
138
|
+
const fits = (l: number, s: number) =>
|
|
139
|
+
Math.ceil(l / ANTHROPIC_PATCH_PX) * ANTHROPIC_PATCH_PX <= maxEdge &&
|
|
140
|
+
Math.ceil(s / ANTHROPIC_PATCH_PX) * ANTHROPIC_PATCH_PX <= maxEdge &&
|
|
141
|
+
patches(l, s) <= maxTokens;
|
|
142
|
+
const short = (l: number) => Math.max(roundTiesToEven(l / aspect), 1);
|
|
143
|
+
if (fits(long, shortSide)) return patches(long, shortSide);
|
|
144
|
+
let lo = 1; // always fits
|
|
145
|
+
let hi = long; // never fits
|
|
146
|
+
while (lo + 1 < hi) {
|
|
147
|
+
const mid = Math.floor((lo + hi) / 2);
|
|
148
|
+
if (fits(mid, short(mid))) lo = mid;
|
|
149
|
+
else hi = mid;
|
|
150
|
+
}
|
|
151
|
+
return patches(lo, short(lo));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Billed input tokens for one image of `size` under `rule`; only `openai-patch` reads `detail`. */
|
|
155
|
+
export function imageTokens(rule: ImageTokenization, size: ImageSize, detail?: ImageDetail): number {
|
|
156
|
+
switch (rule.regime) {
|
|
157
|
+
case "fixed":
|
|
158
|
+
return rule.tokens;
|
|
159
|
+
case "openai-patch": {
|
|
160
|
+
const level = detail === "low" || detail === "high" || detail === "original" ? detail : rule.auto;
|
|
161
|
+
return Math.ceil(openAiPatches(size, rule[level]) * rule.multiplier);
|
|
162
|
+
}
|
|
163
|
+
case "anthropic-patch":
|
|
164
|
+
return anthropicTokens(size, rule.maxEdge, rule.maxTokens);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Model } from "../types";
|
|
2
|
+
import { resolveCatalogPolicy } from "./catalog-policy";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Most questions one System One judgment request to this model may carry
|
|
6
|
+
* (`judgment-questions-max` axis), or `undefined` when the endpoint takes any
|
|
7
|
+
* batch size.
|
|
8
|
+
*/
|
|
9
|
+
export function resolveJudgmentQuestionsMax(model: Model): number | undefined {
|
|
10
|
+
const value = resolveCatalogPolicy(model).judgmentQuestionsMax;
|
|
11
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : undefined;
|
|
12
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { toModelSpec } from "../provider-models/bundled-references";
|
|
2
|
+
import type { Model } from "../types";
|
|
3
|
+
import { resolveCatalogAxes } from "./resolve";
|
|
4
|
+
|
|
5
|
+
const lookbackCache = new Map<string, number | null>();
|
|
6
|
+
const LOOKBACK_CACHE_MAX = 8192;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Block positions the provider's prompt cache checks back from a breakpoint for
|
|
10
|
+
* an earlier request's entry (`prompt-cache-lookback` axis), or `undefined` when
|
|
11
|
+
* the model has no known lookback bound.
|
|
12
|
+
*/
|
|
13
|
+
export function resolvePromptCacheLookback(model: Model): number | undefined {
|
|
14
|
+
const key = `${model.provider} ${model.id} ${model.api}`;
|
|
15
|
+
let lookback = lookbackCache.get(key);
|
|
16
|
+
if (lookback === undefined) {
|
|
17
|
+
const value = resolveCatalogAxes(toModelSpec(model)).promptCacheLookback;
|
|
18
|
+
lookback = typeof value === "number" && Number.isInteger(value) && value > 0 ? value : null;
|
|
19
|
+
if (lookbackCache.size >= LOOKBACK_CACHE_MAX) lookbackCache.clear();
|
|
20
|
+
lookbackCache.set(key, lookback);
|
|
21
|
+
}
|
|
22
|
+
return lookback ?? undefined;
|
|
23
|
+
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Every chat-model provider with a catalog entry (`default-model` in its provider rules). */
|
|
2
|
-
export type KnownProvider = "abliteration" | "aiand" | "aimlapi" | "alibaba-coding-plan" | "alibaba-token-plan" | "amazon-bedrock" | "anthropic" | "apple" | "azure" | "baseten" | "bedrock-mantle" | "cerebras" | "charm-hyper" | "cline-pass" | "cloudflare-ai-gateway" | "commandcode" | "coreweave" | "cursor" | "deepinfra" | "deepseek" | "devin" | "factory-droid" | "firepass" | "fireworks" | "github-copilot" | "gitlab-duo" | "gitlab-duo-agent" | "gmi-cloud" | "google" | "google-antigravity" | "google-gemini-cli" | "google-vertex" | "groq" | "helmcode" | "huggingface" | "kilo" | "kimi-code" | "litellm" | "lm-studio" | "local" | "meta" | "minimax" | "minimax-code" | "minimax-code-cn" | "mistral" | "moonshot" | "muse-code" | "nanogpt" | "novita" | "nvidia" | "ollama" | "ollama-cloud" | "openai" | "openai-codex" | "opencode-go" | "opencode-zen" | "openrouter" | "qianfan" | "qwen-portal" | "sakana" | "siliconflow" | "siliconflow-cn" | "singularityapi-dev" | "singularityapi-tech" | "stepfun" | "synthetic" | "together" | "typesafe" | "umans" | "venice" | "vercel-ai-gateway" | "vllm" | "wafer-serverless" | "web" | "xai" | "xai-oauth" | "xiaomi" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-cn" | "xiaomi-token-plan-sgp" | "yolo-auto" | "zai" | "zenmux" | "zhipu-coding-plan";
|
|
2
|
+
export type KnownProvider = "abliteration" | "aiand" | "aimlapi" | "alibaba-coding-plan" | "alibaba-token-plan" | "amazon-bedrock" | "anthropic" | "apple" | "azure" | "baseten" | "bedrock-mantle" | "cerebras" | "charm-hyper" | "cline-pass" | "cloudflare-ai-gateway" | "cloudflare-workers-ai" | "commandcode" | "coralbricks" | "coreweave" | "cursor" | "deepinfra" | "deepseek" | "devin" | "factory-droid" | "firepass" | "fireworks" | "github-copilot" | "gitlab-duo" | "gitlab-duo-agent" | "gmi-cloud" | "google" | "google-antigravity" | "google-gemini-cli" | "google-vertex" | "groq" | "helmcode" | "huggingface" | "kilo" | "kimi-code" | "litellm" | "lm-studio" | "local" | "meta" | "minimax" | "minimax-code" | "minimax-code-cn" | "mistral" | "moonshot" | "muse-code" | "nanogpt" | "novita" | "nvidia" | "ollama" | "ollama-cloud" | "openai" | "openai-codex" | "opencode-go" | "opencode-zen" | "openrouter" | "qianfan" | "qwen-portal" | "sakana" | "siliconflow" | "siliconflow-cn" | "singularityapi-dev" | "singularityapi-tech" | "snowflake" | "stepfun" | "synthetic" | "together" | "typesafe" | "umans" | "venice" | "vercel-ai-gateway" | "vllm" | "wafer-serverless" | "web" | "xai" | "xai-oauth" | "xiaomi" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-cn" | "xiaomi-token-plan-sgp" | "yolo-auto" | "zai" | "zenmux" | "zhipu-coding-plan";
|
|
@@ -17,7 +17,9 @@ export type KnownProvider =
|
|
|
17
17
|
| "charm-hyper"
|
|
18
18
|
| "cline-pass"
|
|
19
19
|
| "cloudflare-ai-gateway"
|
|
20
|
+
| "cloudflare-workers-ai"
|
|
20
21
|
| "commandcode"
|
|
22
|
+
| "coralbricks"
|
|
21
23
|
| "coreweave"
|
|
22
24
|
| "cursor"
|
|
23
25
|
| "deepinfra"
|
|
@@ -66,6 +68,7 @@ export type KnownProvider =
|
|
|
66
68
|
| "siliconflow-cn"
|
|
67
69
|
| "singularityapi-dev"
|
|
68
70
|
| "singularityapi-tech"
|
|
71
|
+
| "snowflake"
|
|
69
72
|
| "stepfun"
|
|
70
73
|
| "synthetic"
|
|
71
74
|
| "together"
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reasoning carry: whether reasoning a model produced on one host may replay
|
|
3
|
+
* natively on another host serving the same model.
|
|
4
|
+
*
|
|
5
|
+
* The model-side half is declared with the `portable-reasoning` catalog axis
|
|
6
|
+
* per family in `rules/classes/*.kdl`: the family's stored reasoning is its own
|
|
7
|
+
* full plaintext trace, and its identity pins one set of weights. A host whose
|
|
8
|
+
* reasoning must neither be carried in nor out overrides it to `#false` in its
|
|
9
|
+
* `rules/providers/*.kdl`. Whether the target host has a native slot for
|
|
10
|
+
* earlier reasoning is derived by the request encoders in `@linxiraos/pi-ai`,
|
|
11
|
+
* not declared here.
|
|
12
|
+
*
|
|
13
|
+
* Resolved through the cascade on demand (like delegation bias) because
|
|
14
|
+
* bundled rows are frozen by the generator; results are memoized per
|
|
15
|
+
* deployment.
|
|
16
|
+
*/
|
|
17
|
+
import { resolveCascade } from "./cascade";
|
|
18
|
+
import { classifyModel } from "./taxonomy";
|
|
19
|
+
import type { ModelIdentity } from "./types";
|
|
20
|
+
|
|
21
|
+
/** `null` records a deployment that does not carry reasoning. */
|
|
22
|
+
const portableIdentityMemo = new Map<string, ModelIdentity | null>();
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The identity a deployment's reasoning is carried under, or `undefined` when
|
|
26
|
+
* the deployment's model family is not declared portable there. Class-only
|
|
27
|
+
* identities never qualify: they name a lineage, not a set of weights.
|
|
28
|
+
*/
|
|
29
|
+
export function portableReasoningIdentity(provider: string, api: string, modelId: string): ModelIdentity | undefined {
|
|
30
|
+
const key = `${provider}\0${api}\0${modelId}`;
|
|
31
|
+
const memo = portableIdentityMemo.get(key);
|
|
32
|
+
if (memo !== undefined) return memo ?? undefined;
|
|
33
|
+
const identity = classifyModel(provider, modelId, { lenient: true });
|
|
34
|
+
let portable: ModelIdentity | null = null;
|
|
35
|
+
if (identity.family !== undefined || identity.revision !== undefined) {
|
|
36
|
+
const declared = resolveCascade({
|
|
37
|
+
provider,
|
|
38
|
+
api,
|
|
39
|
+
class: identity.class,
|
|
40
|
+
model: modelId,
|
|
41
|
+
reasoning: true,
|
|
42
|
+
...(identity.family !== undefined && { family: identity.family }),
|
|
43
|
+
...(identity.revision !== undefined && { revision: identity.revision }),
|
|
44
|
+
}).catalog.portableReasoning;
|
|
45
|
+
if (declared === true) portable = identity;
|
|
46
|
+
}
|
|
47
|
+
portableIdentityMemo.set(key, portable);
|
|
48
|
+
return portable ?? undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Whether reasoning recorded on `source` may replay natively on `target`:
|
|
53
|
+
* both deployments declare the family portable and classify to the same
|
|
54
|
+
* class, family, and revision.
|
|
55
|
+
*/
|
|
56
|
+
export function carriesReasoning(
|
|
57
|
+
source: { provider: string; api: string; model: string },
|
|
58
|
+
target: { provider: string; api: string; id: string },
|
|
59
|
+
): boolean {
|
|
60
|
+
const to = portableReasoningIdentity(target.provider, target.api, target.id);
|
|
61
|
+
if (!to) return false;
|
|
62
|
+
const from = portableReasoningIdentity(source.provider, source.api, source.model);
|
|
63
|
+
return from !== undefined && from.class === to.class && from.family === to.family && from.revision === to.revision;
|
|
64
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { toModelSpec } from "../provider-models/bundled-references";
|
|
2
|
+
import type { Model } from "../types";
|
|
3
|
+
import { resolveCatalogAxes } from "./resolve";
|
|
4
|
+
|
|
5
|
+
const imageBudgetCache = new Map<string, number | null>();
|
|
6
|
+
const IMAGE_BUDGET_CACHE_MAX = 8192;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Maximum encoded inline-image bytes retained per request for a deployment with a documented body-size limit.
|
|
10
|
+
* The axis is a host contract: a rerouted `baseUrl` (proxy, gateway) imposes its own limit, so the budget
|
|
11
|
+
* applies only while the model targets its official endpoint.
|
|
12
|
+
*/
|
|
13
|
+
export function resolveInlineImageByteBudget(model: Model): number | undefined {
|
|
14
|
+
const compat = model.compat;
|
|
15
|
+
if (!compat || !("officialEndpoint" in compat) || !compat.officialEndpoint) return undefined;
|
|
16
|
+
const key = `${model.provider} ${model.id} ${model.api}`;
|
|
17
|
+
let budget = imageBudgetCache.get(key);
|
|
18
|
+
if (budget === undefined) {
|
|
19
|
+
const value = resolveCatalogAxes(toModelSpec(model)).inlineImageByteBudget;
|
|
20
|
+
budget = typeof value === "number" && Number.isFinite(value) && value > 0 ? value : null;
|
|
21
|
+
if (imageBudgetCache.size >= IMAGE_BUDGET_CACHE_MAX) imageBudgetCache.clear();
|
|
22
|
+
imageBudgetCache.set(key, budget);
|
|
23
|
+
}
|
|
24
|
+
return budget ?? undefined;
|
|
25
|
+
}
|