@oh-my-pi/pi-catalog 18.11.3 → 18.12.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 +18 -0
- package/dist/types/compat/behavior.d.ts +5 -0
- package/dist/types/compat/catalog-endpoint.d.ts +8 -0
- package/dist/types/compat/judgment-images.d.ts +25 -0
- package/dist/types/compat/request-compression.d.ts +21 -0
- package/dist/types/compat/types.d.ts +2 -0
- package/dist/types/model-cache.d.ts +7 -0
- package/dist/types/provider-models/openai-compat.d.ts +0 -46
- package/dist/types/types.d.ts +8 -1
- package/package.json +4 -4
- package/src/build.ts +13 -0
- package/src/compat/axes.ts +51 -1
- package/src/compat/behavior.ts +9 -1
- package/src/compat/catalog-endpoint.ts +43 -0
- package/src/compat/judgment-images.ts +83 -0
- package/src/compat/request-compression.ts +41 -0
- package/src/compat/resolve.ts +34 -49
- package/src/compat/rules/README.md +17 -1
- package/src/compat/rules/classes/anthropic.kdl +2 -3
- package/src/compat/rules/classes/deepseek.kdl +4 -0
- package/src/compat/rules/classes/minimax.kdl +10 -0
- package/src/compat/rules/classes/openai.kdl +8 -0
- package/src/compat/rules/providers/anthropic.kdl +4 -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/deepinfra.kdl +3 -1
- package/src/compat/rules/providers/devin.kdl +7 -1
- 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 +11 -0
- package/src/compat/rules/providers/moonshot.kdl +2 -0
- package/src/compat/rules/providers/ollama-cloud.kdl +3 -0
- package/src/compat/rules/providers/openai-codex.kdl +9 -0
- package/src/compat/rules/providers/opencode-go.kdl +4 -0
- package/src/compat/rules/providers/opencode-zen.kdl +4 -0
- package/src/compat/rules/providers/openrouter.kdl +8 -0
- package/src/compat/rules/providers/tool-call-names.kdl +37 -0
- package/src/compat/rules/providers/typesafe.kdl +8 -0
- package/src/compat/rules/providers/umans.kdl +6 -0
- package/src/compat/rules/providers/venice.kdl +7 -0
- package/src/compat/rules/providers/xai-oauth.kdl +4 -0
- package/src/compat/rules/providers/zai.kdl +5 -0
- package/src/compat/rules/runtime/behavior.kdl +110 -0
- package/src/compat/rules.json +1 -1
- package/src/compat/types.ts +2 -0
- package/src/model-cache.ts +8 -0
- package/src/model-manager.ts +1 -1
- package/src/models.json +1 -1
- package/src/provider-models/openai-compat.ts +8 -90
- package/src/types.ts +8 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [18.12.0] - 2026-10-17
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added the `request-encodings`, `request-encoding-rejection`, and `endpoint-hosts` rule axes, `resolveRequestCompression(model, url)`, and `isCatalogEndpointUrl(model, url)`, declaring which providers' own endpoints decode compressed request bodies: official Anthropic API (gzip), Codex backend (zstd), and GitHub Copilot's API hosts (zstd, then gzip); Moonshot's China host counts as its own endpoint ([#16086](https://github.com/can1357/oh-my-pi/pull/16086) by [@H4vC](https://github.com/H4vC))
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Bundled GPT-5.4 rows on OpenAI, Azure, OpenCode Zen, and Command Code report a 1M context window, the window the coding agent already applied at runtime; GitHub Copilot and Factory Droid keep their own windows ([#16094](https://github.com/can1357/oh-my-pi/pull/16094) by [@H4vC](https://github.com/H4vC))
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- Fixed custom providers that declare a built-in `providerType` (Azure, GitHub Copilot, OpenCode, ClinePass, DeepSeek, NVIDIA) missing that provider's wire quirks, such as strict Responses pairing and the ClinePass reasoning field: they now follow the same catalog rules as the built-in provider ([#16093](https://github.com/can1357/oh-my-pi/pull/16093) by [@H4vC](https://github.com/H4vC))
|
|
18
|
+
- Fixed OpenCode Zen bringing back its retired bundled models after a failed refresh: the retry-backoff write no longer drops the cached snapshot's retention, so the pruned catalog survives the retry window and offline launches. ([#16054](https://github.com/can1357/oh-my-pi/pull/16054) by [@adi-IL](https://github.com/adi-IL))
|
|
19
|
+
- Fixed `providers.cacheWarming` never arming for Claude on Google Antigravity: Claude models there now declare Vertex Anthropic's five-minute prompt-cache lifetime; Gemini models stay unwarmed because Google's implicit cache has no fixed lifetime ([#15764](https://github.com/can1357/oh-my-pi/issues/15764), [#16025](https://github.com/can1357/oh-my-pi/pull/16025) by [@harshaygadekar](https://github.com/harshaygadekar))
|
|
20
|
+
- Scoped Jev image bounds to the selected models instead of applying them to unrelated native judges ([#16002](https://github.com/can1357/oh-my-pi/pull/16002) by [@anatoli-tsinovoy](https://github.com/anatoli-tsinovoy))
|
|
21
|
+
- Fixed Bedrock sessions rejecting replayed tool names over 64 characters after a model switch ([#15839](https://github.com/can1357/oh-my-pi/issues/15839))
|
|
22
|
+
|
|
5
23
|
## [18.11.3] - 2026-10-16
|
|
6
24
|
|
|
7
25
|
### 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[]>>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Model } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Whether {@link url} is HTTPS to {@link model}'s provider's own endpoint: the
|
|
4
|
+
* host of its bundled base URL or one of its `endpoint-hosts`. A custom model
|
|
5
|
+
* or provider override that keeps a catalog provider id but points `baseUrl`
|
|
6
|
+
* at a gateway, proxy, or local server is not.
|
|
7
|
+
*/
|
|
8
|
+
export declare function isCatalogEndpointUrl(model: Model, url: string | undefined): boolean;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Model } from "../types.js";
|
|
2
|
+
/** Bounds a judgment model applies to image inputs. */
|
|
3
|
+
export interface JudgmentImageInput {
|
|
4
|
+
/** Accepted lowercase MIME types; unset adds no model-specific format restriction. */
|
|
5
|
+
mimeTypes?: readonly string[];
|
|
6
|
+
/** Images per request. */
|
|
7
|
+
maxCount?: number;
|
|
8
|
+
/** Decoded bytes per image. */
|
|
9
|
+
maxBytes?: number;
|
|
10
|
+
/** Decoded bytes across all images. */
|
|
11
|
+
maxTotalBytes?: number;
|
|
12
|
+
/** Pixels per image, when the image header reports dimensions. */
|
|
13
|
+
maxPixels?: number;
|
|
14
|
+
/** Minimum pixels per image, when the image header reports dimensions. */
|
|
15
|
+
minPixels?: number;
|
|
16
|
+
/** Inclusive bounds on each side, when the image header reports dimensions. */
|
|
17
|
+
minSide?: number;
|
|
18
|
+
maxSide?: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Resolve image bounds assigned to this model by the catalog cascade. KDL
|
|
22
|
+
* object payloads have scalar children, so `mime-types` is authored as a
|
|
23
|
+
* comma-separated string and normalized here to the public array type.
|
|
24
|
+
*/
|
|
25
|
+
export declare function resolveJudgmentImageInput(model: Model): JudgmentImageInput | undefined;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Model } from "../types.js";
|
|
2
|
+
/** `Content-Encoding` values a host may accept on request bodies. */
|
|
3
|
+
export type RequestBodyEncoding = "zstd" | "gzip";
|
|
4
|
+
/** Request-body compression contract for one request. */
|
|
5
|
+
export interface RequestCompressionPolicy {
|
|
6
|
+
/** Accepted encodings, most preferred first. */
|
|
7
|
+
readonly encodings: readonly RequestBodyEncoding[];
|
|
8
|
+
/**
|
|
9
|
+
* Error-body pattern of a 400/415 that rejects the encoding; `undefined`
|
|
10
|
+
* treats every 400/415 as a possible rejection.
|
|
11
|
+
*/
|
|
12
|
+
readonly rejection: RegExp | undefined;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Compression contract for a request from {@link model} to {@link url}, or
|
|
16
|
+
* `undefined` when the body must go uncompressed. The `request-encodings` axis
|
|
17
|
+
* applies only while the URL reaches the provider's own endpoint
|
|
18
|
+
* ({@link isCatalogEndpointUrl}): a proxy, relay, or rerouted base URL never
|
|
19
|
+
* receives bytes it cannot read.
|
|
20
|
+
*/
|
|
21
|
+
export declare function resolveRequestCompression(model: Model, url: string): RequestCompressionPolicy | undefined;
|
|
@@ -328,6 +328,8 @@ export interface CompiledBehavior {
|
|
|
328
328
|
retryResetTimezones: CompiledRetryResetTimezone[];
|
|
329
329
|
retiredProviders: string[];
|
|
330
330
|
referenceIsolatedProviders: string[];
|
|
331
|
+
/** Built-in model selector chains per agent role, keyed by role in declaration order. */
|
|
332
|
+
roleDefaults: Record<string, string[]>;
|
|
331
333
|
}
|
|
332
334
|
/**
|
|
333
335
|
* A string setting from `auth/*.kdl` that may be overridden by environment
|
|
@@ -20,6 +20,13 @@ export interface CacheEntry<TApi extends Api = Api> {
|
|
|
20
20
|
models: Model<TApi>[];
|
|
21
21
|
fresh: boolean;
|
|
22
22
|
authoritative: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* `authoritative` of the payload row itself, ignoring the
|
|
25
|
+
* `model_cache_refresh` side row. A failed refresh flips only the side row
|
|
26
|
+
* (freshness plus retry backoff), so this keeps answering "were `models`
|
|
27
|
+
* written by an authoritative discovery result?" after such a write.
|
|
28
|
+
*/
|
|
29
|
+
payloadAuthoritative: boolean;
|
|
23
30
|
updatedAt: number;
|
|
24
31
|
/** Model ids whose live headers were intentionally omitted from disk. */
|
|
25
32
|
headerOmittedModelIds: readonly string[];
|
|
@@ -245,52 +245,6 @@ export interface ZhipuCodingPlanModelManagerConfig {
|
|
|
245
245
|
fetch?: FetchImpl;
|
|
246
246
|
}
|
|
247
247
|
export declare function zhipuCodingPlanModelManagerOptions(config?: ZhipuCodingPlanModelManagerConfig): ModelManagerOptions<"openai-completions">;
|
|
248
|
-
/**
|
|
249
|
-
* Fireworks-published cap for the Kimi K2 family. Fireworks' `/v1/models`
|
|
250
|
-
* envelope generically reports `max_completion_tokens: 65536` for every Kimi
|
|
251
|
-
* deployment, but Kimi K2 (instruct / thinking / turbo) on Fireworks is
|
|
252
|
-
* documented to ship long reasoning traces that should be bounded — capping
|
|
253
|
-
* at 32,768 prevents handing callers a budget the router cannot honor.
|
|
254
|
-
* See https://github.com/can1357/oh-my-pi/issues/1849.
|
|
255
|
-
*/
|
|
256
|
-
export declare const FIREWORKS_KIMI_MAX_TOKENS = 32768;
|
|
257
|
-
/**
|
|
258
|
-
* Fireworks' output ceiling for Kimi K2.7-Code specifically. Its `/v1/models`
|
|
259
|
-
* generic `max_completion_tokens` is 65,536 and Fireworks serves it in full —
|
|
260
|
-
* verified with a single completion emitting 58,971 output tokens and
|
|
261
|
-
* `max_tokens: 200000` accepted without error. Unlike the older K2.5/K2.6
|
|
262
|
-
* family (see {@link FIREWORKS_KIMI_MAX_TOKENS}), K2.7-Code is not clamped to
|
|
263
|
-
* 32,768; that ceiling only truncated it.
|
|
264
|
-
*/
|
|
265
|
-
export declare const FIREWORKS_KIMI_K27_CODE_MAX_TOKENS = 65536;
|
|
266
|
-
/**
|
|
267
|
-
* Returns true for the Kimi K2.5 / K2.6 family served by Fireworks-backed
|
|
268
|
-
* providers (`fireworks` direct, `firepass` router) that share the 32,768
|
|
269
|
-
* `maxTokens` ceiling. Matches both the public catalog id (`kimi-k2.5`,
|
|
270
|
-
* `kimi-k2.6`, `kimi-k2.6-turbo`) and the canonical Fireworks wire id
|
|
271
|
-
* (`accounts/fireworks/{models,routers}/kimi-k2…`).
|
|
272
|
-
*
|
|
273
|
-
* K2.7-Code (incl. `-fast` / `-highspeed`) is deliberately excluded: unlike the
|
|
274
|
-
* earlier K2 family it serves its full context on Fireworks — verified with a
|
|
275
|
-
* single completion emitting 58,971 output tokens and `max_tokens: 200000`
|
|
276
|
-
* accepted without error — so the 32,768 cap would only truncate it. It inherits
|
|
277
|
-
* Fireworks' reported `max_completion_tokens` (65,536) instead.
|
|
278
|
-
*/
|
|
279
|
-
export declare function isFireworksKimiK2ModelId(modelId: string): boolean;
|
|
280
|
-
/**
|
|
281
|
-
* Clamp the Kimi K2 family's `maxTokens` to {@link FIREWORKS_KIMI_MAX_TOKENS}
|
|
282
|
-
* on Fireworks-backed providers, leaving every other model untouched.
|
|
283
|
-
*/
|
|
284
|
-
export declare function clampFireworksKimiMaxTokens(modelId: string, candidate: number): number;
|
|
285
|
-
export declare function clampFireworksKimiMaxTokens(modelId: string, candidate: number | null): number | null;
|
|
286
|
-
/**
|
|
287
|
-
* Kimi K2.7 Code's documented recommended output budget. Some provider
|
|
288
|
-
* discovery rows report the context-sized `max_completion_tokens` instead.
|
|
289
|
-
*/
|
|
290
|
-
export declare const KIMI_K27_CODE_RECOMMENDED_MAX_TOKENS = 32768;
|
|
291
|
-
export declare function isKimiK27CodeModelId(modelId: string): boolean;
|
|
292
|
-
export declare function clampKimiK27CodeMaxTokens(modelId: string, candidate: number): number;
|
|
293
|
-
export declare function clampKimiK27CodeMaxTokens(modelId: string, candidate: number | null): number | null;
|
|
294
248
|
/**
|
|
295
249
|
* Build the Fireworks Fast seed by projecting each base bundled spec into a
|
|
296
250
|
* `<id>-fast` variant. Pushed into the generated catalog (Fast routers never
|
package/dist/types/types.d.ts
CHANGED
|
@@ -451,6 +451,8 @@ export interface OpenAICompat {
|
|
|
451
451
|
reasoningDeltasMayBeCumulative?: boolean;
|
|
452
452
|
/** Strip leaked DeepSeek chat-template special tokens from visible content deltas. Default: auto-detected. */
|
|
453
453
|
stripDeepseekSpecialTokens?: boolean;
|
|
454
|
+
/** Drop assistant thinking blocks from replayed chat-completions history. Default: false. */
|
|
455
|
+
stripThinkingHistory?: boolean;
|
|
454
456
|
/** Heal leaked chat-template/tool-call/thinking markup from visible content deltas. Default: auto-detected. */
|
|
455
457
|
streamMarkupHealingPattern?: OpenAIStreamMarkupHealingPattern;
|
|
456
458
|
/** Whether this wire may revise already-streamed text (`stream-revision` axis). Unassigned: append-only. */
|
|
@@ -874,12 +876,15 @@ export interface ResolvedOpenAISharedCompat {
|
|
|
874
876
|
* `buildModel`; request handlers read fields and never detect, resolve, or
|
|
875
877
|
* allocate.
|
|
876
878
|
*/
|
|
877
|
-
export type ResolvedOpenAICompat = ResolvedOpenAISharedCompat & Required<Omit<OpenAICompat, "supportsDeveloperRole" | "supportsReasoningEffort" | "trustExplicitThinkingOnly" | "reasoningEffortMap" | "supportsReasoningParams" | "supportsReasoningSummary" | "statefulResponses" | "supportsSamplingParams" | "supportsPenaltyAndStopParams" | "thinkingFormat" | "kimiApiFormat" | "reasoningDisableMode" | "omitReasoningEffort" | "includeEncryptedReasoning" | "filterReasoningHistory" | "disableReasoningOnForcedToolChoice" | "disableReasoningOnToolChoice" | "disableReasoningWithTools" | "supportsToolChoice" | "supportsForcedToolChoice" | "supportsNamedToolChoice" | "reasoningContentField" | "mistralReasoningContentParts" | "requiresReasoningContentForToolCalls" | "requiresReasoningContentForAllAssistantTurns" | "allowsSyntheticReasoningContentForToolCalls" | "syntheticReasoningContentFallback" | "replayReasoningContent" | "qwenPreserveThinking" | "qwenTemplateReasoningEffort" | "requiresThinkingAsText" | "requiresMistralToolIds" | "requiresToolResultName" | "requiresAssistantAfterToolResult" | "requiresAssistantContentForToolCalls" | "stripDeepseekSpecialTokens" | "streamMarkupHealingPattern" | "reasoningDeltasMayBeCumulative" | "emptyLengthFinishIsContextError" | "usesOpenAIToolCallIdLimit" | "promptCacheSessionHeader" | "sessionHeader" | "supportsPromptCacheBreakpoints" | "promptCacheBreakpointTtl" | "openRouterRouting" | "isOpenRouterHost" | "supportsStrictMode" | "supportsLongPromptCacheRetention" | "alwaysSendMaxTokens" | "clampOutputToModelMax" | "wireModelIdMode" | "vercelGatewayRouting" | "extraBody" | "toolStrictMode" | "toolSchemaFlavor" | "streamFirstEventTimeoutMs" | "streamIdleTimeoutMs" | "streamRevision" | "cacheControlFormat" | "thinkingKeep" | "strictResponsesPairing" | "supportsImageDetailOriginal" | "supportsConfigurationUpdate" | "supportsSteering" | "stripImageInput" | "thinkingLoopGuard" | "whenThinking">> & {
|
|
879
|
+
export type ResolvedOpenAICompat = ResolvedOpenAISharedCompat & Required<Omit<OpenAICompat, "supportsDeveloperRole" | "supportsReasoningEffort" | "trustExplicitThinkingOnly" | "reasoningEffortMap" | "supportsReasoningParams" | "supportsReasoningSummary" | "statefulResponses" | "supportsSamplingParams" | "supportsPenaltyAndStopParams" | "thinkingFormat" | "kimiApiFormat" | "reasoningDisableMode" | "omitReasoningEffort" | "includeEncryptedReasoning" | "filterReasoningHistory" | "disableReasoningOnForcedToolChoice" | "disableReasoningOnToolChoice" | "disableReasoningWithTools" | "supportsToolChoice" | "supportsForcedToolChoice" | "supportsNamedToolChoice" | "reasoningContentField" | "mistralReasoningContentParts" | "requiresReasoningContentForToolCalls" | "requiresReasoningContentForAllAssistantTurns" | "allowsSyntheticReasoningContentForToolCalls" | "syntheticReasoningContentFallback" | "replayReasoningContent" | "qwenPreserveThinking" | "qwenTemplateReasoningEffort" | "requiresThinkingAsText" | "requiresMistralToolIds" | "requiresToolResultName" | "requiresAssistantAfterToolResult" | "requiresAssistantContentForToolCalls" | "stripDeepseekSpecialTokens" | "stripThinkingHistory" | "streamMarkupHealingPattern" | "reasoningDeltasMayBeCumulative" | "emptyLengthFinishIsContextError" | "usesOpenAIToolCallIdLimit" | "promptCacheSessionHeader" | "sessionHeader" | "supportsPromptCacheBreakpoints" | "promptCacheBreakpointTtl" | "openRouterRouting" | "isOpenRouterHost" | "supportsStrictMode" | "supportsLongPromptCacheRetention" | "alwaysSendMaxTokens" | "clampOutputToModelMax" | "wireModelIdMode" | "vercelGatewayRouting" | "extraBody" | "toolStrictMode" | "toolSchemaFlavor" | "streamFirstEventTimeoutMs" | "streamIdleTimeoutMs" | "streamRevision" | "cacheControlFormat" | "thinkingKeep" | "strictResponsesPairing" | "supportsImageDetailOriginal" | "supportsConfigurationUpdate" | "supportsSteering" | "stripImageInput" | "thinkingLoopGuard" | "whenThinking">> & {
|
|
878
880
|
vercelGatewayRouting?: OpenAICompat["vercelGatewayRouting"];
|
|
879
881
|
extraBody?: OpenAICompat["extraBody"];
|
|
880
882
|
cacheControlFormat?: OpenAICompat["cacheControlFormat"];
|
|
881
883
|
thinkingKeep?: OpenAICompat["thinkingKeep"];
|
|
882
884
|
streamIdleTimeoutMs?: number;
|
|
885
|
+
stripThinkingHistory?: boolean;
|
|
886
|
+
/** The configured `baseUrl` is the official OpenAI API (provider `openai`); unset otherwise. */
|
|
887
|
+
officialEndpoint?: boolean;
|
|
883
888
|
toolStrictMode: ResolvedToolStrictMode;
|
|
884
889
|
/** The model sits behind Vercel AI Gateway. */
|
|
885
890
|
isVercelGatewayHost: boolean;
|
|
@@ -1374,6 +1379,8 @@ export interface Model<TApi extends Api = Api> {
|
|
|
1374
1379
|
contextWindow: number | null;
|
|
1375
1380
|
/** Preserve the host's supplied window instead of applying inferred expansion or reference-price caps. */
|
|
1376
1381
|
contextWindowAuthoritative?: boolean;
|
|
1382
|
+
/** The long-context price tier does not cap the runtime context window when extended context is off. */
|
|
1383
|
+
longContextWindowUncapped?: boolean;
|
|
1377
1384
|
/** Optional larger prompt window available when extended context is enabled. */
|
|
1378
1385
|
maxContextWindow?: number;
|
|
1379
1386
|
maxTokens: number | null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-catalog",
|
|
3
|
-
"version": "18.
|
|
3
|
+
"version": "18.12.0",
|
|
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.
|
|
97
|
-
"@oh-my-pi/pi-utils": "18.
|
|
96
|
+
"@oh-my-pi/omptype": "18.12.0",
|
|
97
|
+
"@oh-my-pi/pi-utils": "18.12.0"
|
|
98
98
|
},
|
|
99
99
|
"devDependencies": {
|
|
100
100
|
"@bgotink/kdl": "0.4.0",
|
|
101
|
-
"@oh-my-pi/pi-ai": "18.
|
|
101
|
+
"@oh-my-pi/pi-ai": "18.12.0",
|
|
102
102
|
"@types/bun": "^1.3.14"
|
|
103
103
|
},
|
|
104
104
|
"engines": {
|
package/src/build.ts
CHANGED
|
@@ -54,6 +54,11 @@ function applyCatalogAssignments<TApi extends Api>(model: Model<TApi>, catalog:
|
|
|
54
54
|
} else {
|
|
55
55
|
delete model.contextWindowAuthoritative;
|
|
56
56
|
}
|
|
57
|
+
if (catalog.longContextWindowUncapped === true) {
|
|
58
|
+
model.longContextWindowUncapped = true;
|
|
59
|
+
} else {
|
|
60
|
+
delete model.longContextWindowUncapped;
|
|
61
|
+
}
|
|
57
62
|
const webSearch = catalog.webSearch;
|
|
58
63
|
if (
|
|
59
64
|
webSearch === "gemini" ||
|
|
@@ -330,6 +335,14 @@ export function applyCatalogCorrections(
|
|
|
330
335
|
const maxTokens = numberField(limitsPatch, "maxTokens");
|
|
331
336
|
if (maxTokens !== undefined) model.maxTokens = maxTokens;
|
|
332
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
|
+
}
|
|
333
346
|
const contextWindowFloor = catalog.contextWindowFloor;
|
|
334
347
|
if (typeof contextWindowFloor === "number") {
|
|
335
348
|
model.contextWindow = Math.max(model.contextWindow ?? 0, contextWindowFloor);
|
package/src/compat/axes.ts
CHANGED
|
@@ -259,7 +259,7 @@ export const AXES: Readonly<Record<string, AxisDef>> = {
|
|
|
259
259
|
"requires-thinking-enabled": wire("requiresThinkingEnabled", ["anthropic"]),
|
|
260
260
|
"requires-tool-result-id": wire("requiresToolResultId", ["anthropic"]),
|
|
261
261
|
"signing-endpoint": wire("signingEndpoint", ["anthropic"]),
|
|
262
|
-
"strip-thinking-history": wire("stripThinkingHistory", ["anthropic", "ollama"]),
|
|
262
|
+
"strip-thinking-history": wire("stripThinkingHistory", ["openai", "anthropic", "ollama"]),
|
|
263
263
|
"supports-context-management": wire("supportsContextManagement", ["anthropic"]),
|
|
264
264
|
"supports-output-effort": wire("supportsOutputEffort", ["anthropic"]),
|
|
265
265
|
"supports-eager-tool-input-streaming": wire("supportsEagerToolInputStreaming", ["anthropic"]),
|
|
@@ -355,6 +355,14 @@ export const AXES: Readonly<Record<string, AxisDef>> = {
|
|
|
355
355
|
shape: "scalar",
|
|
356
356
|
values: [true, false],
|
|
357
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" },
|
|
358
366
|
"preserves-max-output-tokens": {
|
|
359
367
|
key: "preservesMaxOutputTokens",
|
|
360
368
|
set: "catalog",
|
|
@@ -401,6 +409,19 @@ export const AXES: Readonly<Record<string, AxisDef>> = {
|
|
|
401
409
|
shape: "scalar",
|
|
402
410
|
values: [true, false],
|
|
403
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
|
+
},
|
|
404
425
|
"cost-patch": { key: "costPatch", set: "catalog", shape: "object" },
|
|
405
426
|
"cost-fallback": { key: "costFallback", set: "catalog", shape: "object" },
|
|
406
427
|
/**
|
|
@@ -472,12 +493,41 @@ export const AXES: Readonly<Record<string, AxisDef>> = {
|
|
|
472
493
|
/** Longest image edge the host passes to the model: it downscales larger images to fit first. */
|
|
473
494
|
"image-max-edge": { key: "imageMaxEdge", set: "catalog", shape: "scalar" },
|
|
474
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 400/415 error body the host returns when it cannot decode
|
|
517
|
+
* a compressed request body. Unassigned: every 400/415 is a possible
|
|
518
|
+
* rejection and is re-checked with an uncompressed resend.
|
|
519
|
+
*/
|
|
520
|
+
"request-encoding-rejection": { key: "requestEncodingRejection", set: "catalog", shape: "scalar" },
|
|
475
521
|
/**
|
|
476
522
|
* Most questions one System One judgment request may carry; the judge
|
|
477
523
|
* splits larger requests. Unassigned: the endpoint takes any batch size.
|
|
478
524
|
*/
|
|
479
525
|
"judgment-questions-max": { key: "judgmentQuestionsMax", set: "catalog", shape: "scalar" },
|
|
480
526
|
"limits-patch": { key: "limitsPatch", set: "catalog", shape: "object" },
|
|
527
|
+
/** Seeds `maxTokens` only when the row reports none; applied after `limits-patch`. */
|
|
528
|
+
"max-tokens-fallback": { key: "maxTokensFallback", set: "catalog", shape: "scalar" },
|
|
529
|
+
/** Most output tokens the host serves: a larger reported `maxTokens` is lowered to it; an unknown one stays unknown. */
|
|
530
|
+
"max-tokens-ceiling": { key: "maxTokensCeiling", set: "catalog", shape: "scalar" },
|
|
481
531
|
"long-context-cost": { key: "longContext", set: "catalog", shape: "object" },
|
|
482
532
|
"prompt-cache": { key: "promptCache", set: "catalog", shape: "object" },
|
|
483
533
|
"task-sibling-prompt-cache": {
|
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
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { getBundledModels, isGeneratedProvider } from "../models";
|
|
2
|
+
import type { Model } from "../types";
|
|
3
|
+
import { resolveCatalogPolicy } from "./catalog-policy";
|
|
4
|
+
|
|
5
|
+
const bundledHosts = new Map<string, string | null>();
|
|
6
|
+
|
|
7
|
+
/** Hostname of the provider's bundled base URL; every provider bundles one origin. */
|
|
8
|
+
function bundledHost(provider: string): string | null {
|
|
9
|
+
let host = bundledHosts.get(provider);
|
|
10
|
+
if (host !== undefined) return host;
|
|
11
|
+
host = null;
|
|
12
|
+
if (isGeneratedProvider(provider)) {
|
|
13
|
+
for (const row of getBundledModels(provider)) {
|
|
14
|
+
try {
|
|
15
|
+
host = new URL(row.baseUrl).hostname;
|
|
16
|
+
break;
|
|
17
|
+
} catch {}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
bundledHosts.set(provider, host);
|
|
21
|
+
return host;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Whether {@link url} is HTTPS to {@link model}'s provider's own endpoint: the
|
|
26
|
+
* host of its bundled base URL or one of its `endpoint-hosts`. A custom model
|
|
27
|
+
* or provider override that keeps a catalog provider id but points `baseUrl`
|
|
28
|
+
* at a gateway, proxy, or local server is not.
|
|
29
|
+
*/
|
|
30
|
+
export function isCatalogEndpointUrl(model: Model, url: string | undefined): boolean {
|
|
31
|
+
if (!url) return false;
|
|
32
|
+
let parsed: URL;
|
|
33
|
+
try {
|
|
34
|
+
parsed = new URL(url);
|
|
35
|
+
} catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
if (parsed.protocol !== "https:") return false;
|
|
39
|
+
if (parsed.hostname === bundledHost(model.provider)) return true;
|
|
40
|
+
// The rule compiler validates `endpoint-hosts` as an array of strings.
|
|
41
|
+
const hosts = resolveCatalogPolicy(model).endpointHosts as readonly string[] | undefined;
|
|
42
|
+
return hosts?.includes(parsed.hostname) === true;
|
|
43
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { isRecord } from "../utils";
|
|
2
|
+
import type { Model } from "../types";
|
|
3
|
+
import { resolveCatalogPolicy } from "./catalog-policy";
|
|
4
|
+
|
|
5
|
+
/** Bounds a judgment model applies to image inputs. */
|
|
6
|
+
export interface JudgmentImageInput {
|
|
7
|
+
/** Accepted lowercase MIME types; unset adds no model-specific format restriction. */
|
|
8
|
+
mimeTypes?: readonly string[];
|
|
9
|
+
/** Images per request. */
|
|
10
|
+
maxCount?: number;
|
|
11
|
+
/** Decoded bytes per image. */
|
|
12
|
+
maxBytes?: number;
|
|
13
|
+
/** Decoded bytes across all images. */
|
|
14
|
+
maxTotalBytes?: number;
|
|
15
|
+
/** Pixels per image, when the image header reports dimensions. */
|
|
16
|
+
maxPixels?: number;
|
|
17
|
+
/** Minimum pixels per image, when the image header reports dimensions. */
|
|
18
|
+
minPixels?: number;
|
|
19
|
+
/** Inclusive bounds on each side, when the image header reports dimensions. */
|
|
20
|
+
minSide?: number;
|
|
21
|
+
maxSide?: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
type NumericField = "maxCount" | "maxBytes" | "maxTotalBytes" | "maxPixels" | "minPixels" | "minSide" | "maxSide";
|
|
25
|
+
|
|
26
|
+
const NUMERIC_FIELDS: readonly NumericField[] = [
|
|
27
|
+
"maxCount",
|
|
28
|
+
"maxBytes",
|
|
29
|
+
"maxTotalBytes",
|
|
30
|
+
"maxPixels",
|
|
31
|
+
"minPixels",
|
|
32
|
+
"minSide",
|
|
33
|
+
"maxSide",
|
|
34
|
+
];
|
|
35
|
+
const POLICY_FIELDS: Record<string, true> = {
|
|
36
|
+
mimeTypes: true,
|
|
37
|
+
maxCount: true,
|
|
38
|
+
maxBytes: true,
|
|
39
|
+
maxTotalBytes: true,
|
|
40
|
+
maxPixels: true,
|
|
41
|
+
minPixels: true,
|
|
42
|
+
minSide: true,
|
|
43
|
+
maxSide: true,
|
|
44
|
+
};
|
|
45
|
+
const MIME_TYPE = /^[A-Za-z0-9!#$&^_.+-]+\/[A-Za-z0-9!#$&^_.+-]+$/;
|
|
46
|
+
|
|
47
|
+
function positiveInteger(value: unknown): value is number {
|
|
48
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Resolve image bounds assigned to this model by the catalog cascade. KDL
|
|
53
|
+
* object payloads have scalar children, so `mime-types` is authored as a
|
|
54
|
+
* comma-separated string and normalized here to the public array type.
|
|
55
|
+
*/
|
|
56
|
+
export function resolveJudgmentImageInput(model: Model): JudgmentImageInput | undefined {
|
|
57
|
+
const value = resolveCatalogPolicy(model).judgmentImageInput;
|
|
58
|
+
if (!isRecord(value) || Object.keys(value).some(key => !Object.hasOwn(POLICY_FIELDS, key))) return undefined;
|
|
59
|
+
|
|
60
|
+
let mimeTypes: readonly string[] | undefined;
|
|
61
|
+
if (value.mimeTypes !== undefined) {
|
|
62
|
+
if (typeof value.mimeTypes !== "string") return undefined;
|
|
63
|
+
const parsed = value.mimeTypes.split(",");
|
|
64
|
+
if (parsed.some(mimeType => !MIME_TYPE.test(mimeType))) return undefined;
|
|
65
|
+
mimeTypes = Object.freeze(parsed);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const numeric: Partial<Record<NumericField, number>> = {};
|
|
69
|
+
for (const field of NUMERIC_FIELDS) {
|
|
70
|
+
const bound = value[field];
|
|
71
|
+
if (bound === undefined) continue;
|
|
72
|
+
if (!positiveInteger(bound)) return undefined;
|
|
73
|
+
numeric[field] = bound;
|
|
74
|
+
}
|
|
75
|
+
if (
|
|
76
|
+
(numeric.minPixels !== undefined && numeric.maxPixels !== undefined && numeric.minPixels > numeric.maxPixels) ||
|
|
77
|
+
(numeric.minSide !== undefined && numeric.maxSide !== undefined && numeric.minSide > numeric.maxSide)
|
|
78
|
+
) {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return { ...(mimeTypes !== undefined && { mimeTypes }), ...numeric };
|
|
83
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Model } from "../types";
|
|
2
|
+
import { isCatalogEndpointUrl } from "./catalog-endpoint";
|
|
3
|
+
import { resolveCatalogPolicy } from "./catalog-policy";
|
|
4
|
+
|
|
5
|
+
/** `Content-Encoding` values a host may accept on request bodies. */
|
|
6
|
+
export type RequestBodyEncoding = "zstd" | "gzip";
|
|
7
|
+
|
|
8
|
+
/** Request-body compression contract for one request. */
|
|
9
|
+
export interface RequestCompressionPolicy {
|
|
10
|
+
/** Accepted encodings, most preferred first. */
|
|
11
|
+
readonly encodings: readonly RequestBodyEncoding[];
|
|
12
|
+
/**
|
|
13
|
+
* Error-body pattern of a 400/415 that rejects the encoding; `undefined`
|
|
14
|
+
* treats every 400/415 as a possible rejection.
|
|
15
|
+
*/
|
|
16
|
+
readonly rejection: RegExp | undefined;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const rejectionPatterns = new Map<string, RegExp>();
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Compression contract for a request from {@link model} to {@link url}, or
|
|
23
|
+
* `undefined` when the body must go uncompressed. The `request-encodings` axis
|
|
24
|
+
* applies only while the URL reaches the provider's own endpoint
|
|
25
|
+
* ({@link isCatalogEndpointUrl}): a proxy, relay, or rerouted base URL never
|
|
26
|
+
* receives bytes it cannot read.
|
|
27
|
+
*/
|
|
28
|
+
export function resolveRequestCompression(model: Model, url: string): RequestCompressionPolicy | undefined {
|
|
29
|
+
const policy = resolveCatalogPolicy(model);
|
|
30
|
+
// The rule compiler validates encodings against the axis vocabulary.
|
|
31
|
+
const encodings = policy.requestEncodings as readonly RequestBodyEncoding[] | undefined;
|
|
32
|
+
if (!encodings?.length || !isCatalogEndpointUrl(model, url)) return undefined;
|
|
33
|
+
const source = policy.requestEncodingRejection;
|
|
34
|
+
if (typeof source !== "string") return { encodings, rejection: undefined };
|
|
35
|
+
let rejection = rejectionPatterns.get(source);
|
|
36
|
+
if (!rejection) {
|
|
37
|
+
rejection = new RegExp(source);
|
|
38
|
+
rejectionPatterns.set(source, rejection);
|
|
39
|
+
}
|
|
40
|
+
return { encodings, rejection };
|
|
41
|
+
}
|