@linxiraos/pi-catalog 1.1.26 → 1.1.27
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 +13 -2
- 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/provider-ids.d.ts +1 -1
- package/dist/types/hosts.d.ts +4 -0
- package/dist/types/model-cache.d.ts +6 -0
- package/dist/types/provider-models/special.d.ts +6 -0
- package/dist/types/types.d.ts +29 -4
- package/dist/types/wire/gemini-headers.d.ts +6 -3
- package/package.json +4 -4
- package/src/compat/auth-ids.ts +2 -0
- package/src/compat/axes.ts +6 -2
- 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/provider-ids.d.ts +1 -1
- package/src/compat/provider-ids.ts +1 -0
- package/src/compat/resolve.ts +20 -23
- package/src/compat/rules/README.md +1 -1
- package/src/compat/rules/auth/_order.kdl +1 -1
- package/src/compat/rules/auth/snowflake.kdl +11 -0
- package/src/compat/rules/classes/anthropic.kdl +102 -7
- package/src/compat/rules/classes/deepseek.kdl +5 -3
- package/src/compat/rules/classes/gemini.kdl +12 -0
- package/src/compat/rules/classes/glm.kdl +1 -1
- package/src/compat/rules/classes/kimi.kdl +7 -0
- package/src/compat/rules/classes/meta.kdl +4 -4
- package/src/compat/rules/classes/openai.kdl +109 -1
- package/src/compat/rules/providers/amazon-bedrock.kdl +0 -7
- package/src/compat/rules/providers/cursor.kdl +6 -8
- package/src/compat/rules/providers/google-antigravity.kdl +8 -1
- package/src/compat/rules/providers/image-tokenization.kdl +42 -0
- package/src/compat/rules/providers/meta.kdl +3 -3
- 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 +1 -0
- package/src/compat/rules/providers/opencode-go.kdl +11 -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/runtime/behavior.kdl +1 -1
- package/src/compat/rules/taxonomy/_collapse.kdl +34 -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/discovery/cursor.ts +26 -3
- 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 +10 -0
- package/src/models.json +1 -1
- package/src/provider-models/cache-provider-id.ts +10 -4
- package/src/provider-models/cline-pass.ts +33 -0
- package/src/provider-models/google.ts +2 -0
- package/src/provider-models/openai-compat.ts +70 -15
- package/src/provider-models/special.d.ts +6 -0
- package/src/provider-models/special.ts +11 -4
- package/src/types.d.ts +29 -4
- package/src/types.ts +34 -3
- package/src/wire/gemini-headers.d.ts +6 -3
- package/src/wire/gemini-headers.ts +39 -21
package/CHANGELOG.md
CHANGED
|
@@ -2,9 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
-
## [1.1.
|
|
5
|
+
## [1.1.27] - 2026-10-07
|
|
6
6
|
|
|
7
|
-
- 版本线推进至 1.1.
|
|
7
|
+
- 版本线推进至 1.1.27;本版无独立用户可见变化。
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Added an optional `statefulResponses` compat field for OpenAI Responses models, kept through OpenRouter's Responses dispatch ([#13686](https://github.com/can1357/oh-my-pi/pull/13686) by [@alphastorm](https://github.com/alphastorm)).
|
|
12
|
+
- Added 15 Snowflake Cortex models with account-specific endpoints, Cortex compatibility rules, and estimated account-billed pricing ([#14507](https://github.com/can1357/oh-my-pi/pull/14507) by [@jorgoose](https://github.com/jorgoose)).
|
|
13
|
+
- Added the `image-tokenization` axis, which declares how GPT-5.2+, Claude and Gemini 3 lines bill input images on every host, with wire-API fallback rules for other models, plus `imageTokens()` to price one image ([#14286](https://github.com/can1357/oh-my-pi/pull/14286) by [@will-bogusz](https://github.com/will-bogusz)).
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- Changed OpenAI Responses endpoints other than OpenAI, Azure OpenAI, and Codex (custom and local servers, proxies including `azure`/`openai-codex` providers pointed at a non-Azure/non-Codex `baseUrl`, OpenRouter) to default `supportsImageDetailOriginal` to `false`, so snapcompact frames and computer screenshots go out as `detail: "auto"` instead of failing on servers that reject `original`; set `compat.supportsImageDetailOriginal: true` to opt a host in ([#13687](https://github.com/can1357/oh-my-pi/pull/13687) by [@alphastorm](https://github.com/alphastorm)).
|
|
18
|
+
- Muse Code can now store Responses results on Meta's side (`store-responses`), so a turn whose connection drops can be recovered instead of re-run. Storage is opt-in via the omp setting `providers.muse-code.storeResponses` or `PI_MUSE_STORE_RESPONSES=1` ([#14293](https://github.com/can1357/oh-my-pi/pull/14293) and [#14534](https://github.com/can1357/oh-my-pi/pull/14534) by [@abilliontokens](https://github.com/abilliontokens)).
|
|
8
19
|
|
|
9
20
|
## [1.1.25] - 2026-10-03
|
|
10
21
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
/** Every provider with an `auth/<id>.kdl` policy. */
|
|
2
|
-
export type AuthProviderId = "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" | "exa" | "factory-droid" | "firepass" | "fireworks" | "github-copilot" | "gitlab-duo" | "gitlab-duo-agent" | "gmi-cloud" | "google" | "google-antigravity" | "google-gemini-cli" | "google-vertex" | "groq" | "helmcode" | "huggingface" | "kagi" | "kilo" | "kimi-code" | "litellm" | "llama.cpp" | "lm-studio" | "local" | "meta" | "minimax" | "minimax-code" | "minimax-code-cn" | "mistral" | "moonshot" | "muse-code" | "nanogpt" | "novita" | "nvidia" | "ollama" | "ollama-cloud" | "openai" | "openai-codex" | "openai-codex-device" | "opencode-go" | "opencode-zen" | "openrouter" | "parallel" | "perplexity" | "qianfan" | "qwen-portal" | "sakana" | "siliconflow" | "siliconflow-cn" | "singularityapi-dev" | "singularityapi-tech" | "stencil" | "stepfun" | "synthetic" | "tavily" | "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" | "zai-coding-plan" | "zenmux" | "zhipu-coding-plan";
|
|
2
|
+
export type AuthProviderId = "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" | "exa" | "factory-droid" | "firepass" | "fireworks" | "github-copilot" | "gitlab-duo" | "gitlab-duo-agent" | "gmi-cloud" | "google" | "google-antigravity" | "google-gemini-cli" | "google-vertex" | "groq" | "helmcode" | "huggingface" | "kagi" | "kilo" | "kimi-code" | "litellm" | "llama.cpp" | "lm-studio" | "local" | "meta" | "minimax" | "minimax-code" | "minimax-code-cn" | "mistral" | "moonshot" | "muse-code" | "nanogpt" | "novita" | "nvidia" | "ollama" | "ollama-cloud" | "openai" | "openai-codex" | "openai-codex-device" | "opencode-go" | "opencode-zen" | "openrouter" | "parallel" | "perplexity" | "qianfan" | "qwen-portal" | "sakana" | "siliconflow" | "siliconflow-cn" | "singularityapi-dev" | "singularityapi-tech" | "snowflake" | "stencil" | "stepfun" | "synthetic" | "tavily" | "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" | "zai-coding-plan" | "zenmux" | "zhipu-coding-plan";
|
|
3
3
|
/** Providers whose policy declares a `login` flow (the `/login` roster). */
|
|
4
|
-
export type LoginProviderId = "abliteration" | "aiand" | "alibaba-coding-plan" | "alibaba-token-plan" | "anthropic" | "baseten" | "cerebras" | "charm-hyper" | "cline-pass" | "cloudflare-ai-gateway" | "commandcode" | "coreweave" | "cursor" | "deepinfra" | "deepseek" | "devin" | "exa" | "factory-droid" | "firepass" | "fireworks" | "github-copilot" | "gitlab-duo" | "gitlab-duo-agent" | "gmi-cloud" | "google-antigravity" | "google-gemini-cli" | "helmcode" | "huggingface" | "kagi" | "kilo" | "kimi-code" | "litellm" | "llama.cpp" | "lm-studio" | "meta" | "minimax-code" | "minimax-code-cn" | "moonshot" | "muse-code" | "nanogpt" | "novita" | "nvidia" | "ollama" | "ollama-cloud" | "openai-codex" | "openai-codex-device" | "opencode-go" | "opencode-zen" | "openrouter" | "parallel" | "perplexity" | "qianfan" | "qwen-portal" | "sakana" | "siliconflow" | "siliconflow-cn" | "singularityapi-dev" | "singularityapi-tech" | "stencil" | "stepfun" | "synthetic" | "tavily" | "together" | "typesafe" | "umans" | "venice" | "vercel-ai-gateway" | "vllm" | "wafer-serverless" | "xai" | "xai-oauth" | "xiaomi" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-cn" | "xiaomi-token-plan-sgp" | "yolo-auto" | "zai" | "zai-coding-plan" | "zenmux" | "zhipu-coding-plan";
|
|
4
|
+
export type LoginProviderId = "abliteration" | "aiand" | "alibaba-coding-plan" | "alibaba-token-plan" | "anthropic" | "baseten" | "cerebras" | "charm-hyper" | "cline-pass" | "cloudflare-ai-gateway" | "commandcode" | "coreweave" | "cursor" | "deepinfra" | "deepseek" | "devin" | "exa" | "factory-droid" | "firepass" | "fireworks" | "github-copilot" | "gitlab-duo" | "gitlab-duo-agent" | "gmi-cloud" | "google-antigravity" | "google-gemini-cli" | "helmcode" | "huggingface" | "kagi" | "kilo" | "kimi-code" | "litellm" | "llama.cpp" | "lm-studio" | "meta" | "minimax-code" | "minimax-code-cn" | "moonshot" | "muse-code" | "nanogpt" | "novita" | "nvidia" | "ollama" | "ollama-cloud" | "openai-codex" | "openai-codex-device" | "opencode-go" | "opencode-zen" | "openrouter" | "parallel" | "perplexity" | "qianfan" | "qwen-portal" | "sakana" | "siliconflow" | "siliconflow-cn" | "singularityapi-dev" | "singularityapi-tech" | "snowflake" | "stencil" | "stepfun" | "synthetic" | "tavily" | "together" | "typesafe" | "umans" | "venice" | "vercel-ai-gateway" | "vllm" | "wafer-serverless" | "xai" | "xai-oauth" | "xiaomi" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-cn" | "xiaomi-token-plan-sgp" | "yolo-auto" | "zai" | "zai-coding-plan" | "zenmux" | "zhipu-coding-plan";
|
|
@@ -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:
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { ModelIdentity } from "./types.js";
|
|
2
|
+
/** OpenAI image `detail` levels; `undefined` means the provider default (`auto`). */
|
|
3
|
+
export type ImageDetail = "auto" | "low" | "high" | "original";
|
|
4
|
+
export interface ImageSize {
|
|
5
|
+
width: number;
|
|
6
|
+
height: number;
|
|
7
|
+
}
|
|
8
|
+
/** One OpenAI `detail` level's sizing: a pixel-dimension limit, then an optional patch budget. */
|
|
9
|
+
export interface PatchSizing {
|
|
10
|
+
/** Longest side after the pixel-dimension fit. */
|
|
11
|
+
maxEdge: number;
|
|
12
|
+
/** Resizing patch budget; absent when the level keeps the image's size. */
|
|
13
|
+
patchBudget?: number;
|
|
14
|
+
}
|
|
15
|
+
/** OpenAI 32px patches × a per-model multiplier, sized per `detail` level. */
|
|
16
|
+
export interface OpenAiPatchTokenization {
|
|
17
|
+
regime: "openai-patch";
|
|
18
|
+
multiplier: number;
|
|
19
|
+
low: PatchSizing;
|
|
20
|
+
high: PatchSizing;
|
|
21
|
+
original: PatchSizing;
|
|
22
|
+
/** The level `auto` (and an omitted `detail`) sizes like. */
|
|
23
|
+
auto: "high" | "original";
|
|
24
|
+
}
|
|
25
|
+
/** One model line's image billing rule: the vendor formula and its per-model numbers. */
|
|
26
|
+
export type ImageTokenization = OpenAiPatchTokenization
|
|
27
|
+
/** Anthropic 28px patches, resized to fit a padded-edge limit and a visual-token budget. */
|
|
28
|
+
| {
|
|
29
|
+
regime: "anthropic-patch";
|
|
30
|
+
maxEdge: number;
|
|
31
|
+
maxTokens: number;
|
|
32
|
+
}
|
|
33
|
+
/** A fixed per-image budget regardless of pixels (Gemini 3 `media_resolution`). */
|
|
34
|
+
| {
|
|
35
|
+
regime: "fixed";
|
|
36
|
+
tokens: number;
|
|
37
|
+
};
|
|
38
|
+
/** What reads the image: a built model, any `{ id }` with optional host and identity, or just a wire `{ api }`. */
|
|
39
|
+
export interface ImageTokenizationTarget {
|
|
40
|
+
id?: string;
|
|
41
|
+
provider?: string;
|
|
42
|
+
api?: string;
|
|
43
|
+
identity?: Pick<ModelIdentity, "class" | "family" | "revision">;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The reader's image billing rule: its lineage rule, else its wire API's
|
|
47
|
+
* fallback; undefined when neither covers it (an unclassified model on an
|
|
48
|
+
* unregistered API).
|
|
49
|
+
*/
|
|
50
|
+
export declare function resolveImageTokenization(target: ImageTokenizationTarget): ImageTokenization | undefined;
|
|
51
|
+
/** Billed input tokens for one image of `size` under `rule`; only `openai-patch` reads `detail`. */
|
|
52
|
+
export declare function imageTokens(rule: ImageTokenization, size: ImageSize, detail?: ImageDetail): number;
|
|
@@ -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" | "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" | "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";
|
package/dist/types/hosts.d.ts
CHANGED
|
@@ -15,6 +15,10 @@ export declare const KNOWN_HOSTS: {
|
|
|
15
15
|
readonly providers: readonly ["openai"];
|
|
16
16
|
readonly urlMarkers: readonly ["api.openai.com"];
|
|
17
17
|
};
|
|
18
|
+
/** URL-only: a Codex provider rerouted through a proxy does not imply the subscription backend's capabilities. */
|
|
19
|
+
readonly openaiCodex: {
|
|
20
|
+
readonly urlMarkers: readonly ["chatgpt.com/backend-api", "chat.openai.com/backend-api"];
|
|
21
|
+
};
|
|
18
22
|
readonly azureOpenAI: {
|
|
19
23
|
readonly providers: readonly ["azure"];
|
|
20
24
|
readonly urlMarkers: readonly [".openai.azure.com", "azure.com/openai", "models.inference.ai.azure.com"];
|
|
@@ -35,6 +35,12 @@ export interface CacheEntry<TApi extends Api = Api> {
|
|
|
35
35
|
*/
|
|
36
36
|
staticFingerprint: string;
|
|
37
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Closes the shared handle to the default `<agent-dir>/models.db`; the next
|
|
40
|
+
* default-path access reopens it. Call before deleting an agent directory the
|
|
41
|
+
* cache was opened under — Windows cannot remove a database that is still open.
|
|
42
|
+
*/
|
|
43
|
+
export declare function closeModelCache(): void;
|
|
38
44
|
export declare function readModelCache<TApi extends Api>(providerId: string, ttlMs: number, now: () => number, dbPath?: string): CacheEntry<TApi> | null;
|
|
39
45
|
/**
|
|
40
46
|
* Persist a provider snapshot.
|
|
@@ -24,6 +24,12 @@ export interface OpenAICodexModelManagerConfig {
|
|
|
24
24
|
* keeps the previous/bundled catalog instead.
|
|
25
25
|
*/
|
|
26
26
|
resolveAccounts?: () => Promise<readonly OpenAICodexAccount[] | null>;
|
|
27
|
+
/**
|
|
28
|
+
* Codex backend base URL (e.g. a Codex-compatible gateway from `models.yml`).
|
|
29
|
+
* Defaults to the official ChatGPT backend. Also scopes the discovery cache,
|
|
30
|
+
* so a gateway roster never serves the official endpoint and vice versa.
|
|
31
|
+
*/
|
|
32
|
+
baseUrl?: string;
|
|
27
33
|
clientVersion?: string;
|
|
28
34
|
fetch?: FetchImpl;
|
|
29
35
|
}
|
package/dist/types/types.d.ts
CHANGED
|
@@ -378,6 +378,8 @@ export interface OpenAICompat {
|
|
|
378
378
|
supportsReasoningParams?: boolean;
|
|
379
379
|
/** Whether Responses requests may include `reasoning.summary`. Default: true except on known incompatible hosts. */
|
|
380
380
|
supportsReasoningSummary?: boolean;
|
|
381
|
+
/** Whether to chain OpenAI Responses turns using stored response ids. Unset uses the official-endpoint default; env and call options take precedence. */
|
|
382
|
+
statefulResponses?: boolean;
|
|
381
383
|
/**
|
|
382
384
|
* Whether the endpoint accepts explicit sampling parameters (`temperature`,
|
|
383
385
|
* `top_p`, `top_k`, `min_p`, penalties). OpenAI proprietary reasoning models
|
|
@@ -398,7 +400,7 @@ export interface OpenAICompat {
|
|
|
398
400
|
alwaysSendMaxTokens?: boolean;
|
|
399
401
|
/** Whether Responses-API tool-call/result history must be strictly paired. Default: auto-detected (Azure OpenAI, GitHub Copilot). */
|
|
400
402
|
strictResponsesPairing?: boolean;
|
|
401
|
-
/** Whether the Responses API accepts the `detail: "original"` image hint. Default:
|
|
403
|
+
/** Whether the Responses API accepts the `detail: "original"` image hint. Default: true for OpenAI, Azure OpenAI, and Codex; false for other hosts. Explicit overrides win. */
|
|
402
404
|
supportsImageDetailOriginal?: boolean;
|
|
403
405
|
/**
|
|
404
406
|
* Whether the Responses endpoint accepts `configuration_update` input items
|
|
@@ -651,6 +653,12 @@ export interface BedrockCompat {
|
|
|
651
653
|
* `PI_OPENAI_STREAM_IDLE_TIMEOUT_MS` alias, then the 300s default.
|
|
652
654
|
*/
|
|
653
655
|
streamIdleTimeoutMs?: number;
|
|
656
|
+
/**
|
|
657
|
+
* Whether the served model accepts explicit sampling parameters
|
|
658
|
+
* (`temperature`, `topP`, …). Class rules set `false` for model lines that
|
|
659
|
+
* reject them on every host. Unassigned: accepted.
|
|
660
|
+
*/
|
|
661
|
+
supportsSamplingParams?: boolean;
|
|
654
662
|
/**
|
|
655
663
|
* Whether the model accepts a forced `toolChoice` (`any` / `tool`). Claude
|
|
656
664
|
* Opus/Sonnet 5.5 reject it outright; the request builder downgrades forced
|
|
@@ -666,6 +674,8 @@ export interface ResolvedBedrockCompat {
|
|
|
666
674
|
supportsLongPromptCacheRetention: boolean;
|
|
667
675
|
promptCacheMinimumTokens: number;
|
|
668
676
|
promptCacheMaximumCheckpoints: number;
|
|
677
|
+
/** See {@link BedrockCompat.supportsSamplingParams}. */
|
|
678
|
+
supportsSamplingParams?: boolean;
|
|
669
679
|
supportsForcedToolChoice: boolean;
|
|
670
680
|
/**
|
|
671
681
|
* Stream-watchdog idle-timeout fallback in ms for hosts with no keepalive
|
|
@@ -788,7 +798,7 @@ export interface ResolvedOpenAISharedCompat {
|
|
|
788
798
|
* `buildModel`; request handlers read fields and never detect, resolve, or
|
|
789
799
|
* allocate.
|
|
790
800
|
*/
|
|
791
|
-
export type ResolvedOpenAICompat = ResolvedOpenAISharedCompat & Required<Omit<OpenAICompat, "supportsDeveloperRole" | "supportsReasoningEffort" | "reasoningEffortMap" | "supportsReasoningParams" | "supportsReasoningSummary" | "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" | "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">> & {
|
|
801
|
+
export type ResolvedOpenAICompat = ResolvedOpenAISharedCompat & Required<Omit<OpenAICompat, "supportsDeveloperRole" | "supportsReasoningEffort" | "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" | "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">> & {
|
|
792
802
|
vercelGatewayRouting?: OpenAICompat["vercelGatewayRouting"];
|
|
793
803
|
extraBody?: OpenAICompat["extraBody"];
|
|
794
804
|
cacheControlFormat?: OpenAICompat["cacheControlFormat"];
|
|
@@ -835,6 +845,16 @@ export interface ResolvedOpenAIResponsesCompat extends ResolvedOpenAISharedCompa
|
|
|
835
845
|
* filling `"auto"`.
|
|
836
846
|
*/
|
|
837
847
|
supportsReasoningSummary: boolean;
|
|
848
|
+
/** Optional chaining override; unset falls back to officialEndpoint at request time. */
|
|
849
|
+
statefulResponses?: boolean;
|
|
850
|
+
/**
|
|
851
|
+
* Whether the host can store Responses results server-side (`store: true`).
|
|
852
|
+
* Rule-owned: hosts that keep generating after a client disconnect so a
|
|
853
|
+
* dropped stream can resume via `GET /responses/{id}`. Storage is opt-in
|
|
854
|
+
* per request (`storeResponses` stream option, else
|
|
855
|
+
* `PI_MUSE_STORE_RESPONSES`); stored runs retain prompts and outputs on the provider.
|
|
856
|
+
*/
|
|
857
|
+
storeResponses: boolean;
|
|
838
858
|
streamIdleTimeoutMs?: number;
|
|
839
859
|
vercelGatewayRouting?: OpenAICompat["vercelGatewayRouting"];
|
|
840
860
|
/** The model sits behind Vercel AI Gateway's Responses endpoint. */
|
|
@@ -918,9 +938,11 @@ export interface DevinCompat {
|
|
|
918
938
|
modelRouter?: boolean;
|
|
919
939
|
/** Whether the upstream model supports native parallel tool calls. */
|
|
920
940
|
supportsParallelToolCalls?: boolean;
|
|
941
|
+
/** See {@link BedrockCompat.supportsSamplingParams}. */
|
|
942
|
+
supportsSamplingParams?: boolean;
|
|
921
943
|
}
|
|
922
944
|
/** Fully-resolved devin-agent compat view. */
|
|
923
|
-
export type ResolvedDevinCompat = Required<DevinCompat>;
|
|
945
|
+
export type ResolvedDevinCompat = Required<Omit<DevinCompat, "supportsSamplingParams">> & Pick<DevinCompat, "supportsSamplingParams">;
|
|
924
946
|
/**
|
|
925
947
|
* Compatibility settings for the Google API family (google-generative-ai,
|
|
926
948
|
* google-vertex, google-gemini-cli). Class-driven defaults come from the
|
|
@@ -955,13 +977,16 @@ export interface GoogleCompat {
|
|
|
955
977
|
stripImageInput?: boolean;
|
|
956
978
|
/** Thinking-loop watchdog guard family applied to streamed reasoning. */
|
|
957
979
|
thinkingLoopGuard?: "gemini" | "deepseek" | "xai";
|
|
980
|
+
/** See {@link BedrockCompat.supportsSamplingParams}. */
|
|
981
|
+
supportsSamplingParams?: boolean;
|
|
958
982
|
}
|
|
959
983
|
/** Fully-resolved google-API compat view, materialized once by `buildModel`. */
|
|
960
|
-
export type ResolvedGoogleCompat = Required<Omit<GoogleCompat, "streamFirstEventTimeoutMs" | "streamIdleTimeoutMs" | "thinkingLoopGuard" | "antigravityUsageLabel">> & {
|
|
984
|
+
export type ResolvedGoogleCompat = Required<Omit<GoogleCompat, "streamFirstEventTimeoutMs" | "streamIdleTimeoutMs" | "thinkingLoopGuard" | "antigravityUsageLabel" | "supportsSamplingParams">> & {
|
|
961
985
|
streamFirstEventTimeoutMs?: number;
|
|
962
986
|
streamIdleTimeoutMs?: number;
|
|
963
987
|
thinkingLoopGuard?: GoogleCompat["thinkingLoopGuard"];
|
|
964
988
|
antigravityUsageLabel?: string;
|
|
989
|
+
supportsSamplingParams?: boolean;
|
|
965
990
|
};
|
|
966
991
|
/** Sparse, user-authored compat overrides for a given API (models.json / config vocabulary). */
|
|
967
992
|
export type CompatConfigOf<TApi extends Api> = TApi extends "openai-completions" | "openrouter" | "openai-responses" | "azure-openai-responses" | "openai-codex-responses" ? OpenAICompat : TApi extends "anthropic-messages" ? AnthropicCompat : TApi extends "bedrock-converse-stream" ? BedrockCompat : TApi extends "devin-agent" ? DevinCompat : TApi extends "google-generative-ai" | "google-vertex" | "google-gemini-cli" ? GoogleCompat : undefined;
|
|
@@ -24,7 +24,7 @@ export declare const getGeminiCliHeaders: (modelId?: string) => {
|
|
|
24
24
|
* client the version and manifest are captured from, independent of the host
|
|
25
25
|
* platform. Overrides: PI_AI_ANTIGRAVITY_VERSION / _CL / _OS / _ARCH.
|
|
26
26
|
*/
|
|
27
|
-
export declare const DEFAULT_ANTIGRAVITY_VERSION = "2.
|
|
27
|
+
export declare const DEFAULT_ANTIGRAVITY_VERSION = "2.19.1";
|
|
28
28
|
/** Current Antigravity client version: env override → manifest-discovered → pinned fallback. */
|
|
29
29
|
export declare function getAntigravityVersion(): string;
|
|
30
30
|
/**
|
|
@@ -35,8 +35,11 @@ export declare function parseAntigravityManifestVersion(yamlText: string): strin
|
|
|
35
35
|
/**
|
|
36
36
|
* Resolves the latest Antigravity release from the official update manifest.
|
|
37
37
|
* Success is cached for the process lifetime; failures are silent (the pinned
|
|
38
|
-
* fallback stays valid) and
|
|
39
|
-
* Skipped entirely when PI_AI_ANTIGRAVITY_VERSION is set.
|
|
38
|
+
* fallback stays valid) and suppress further lookups for
|
|
39
|
+
* `ANTIGRAVITY_VERSION_RETRY_MS`. Skipped entirely when PI_AI_ANTIGRAVITY_VERSION is set.
|
|
40
|
+
*
|
|
41
|
+
* The lookup is shared by concurrent callers, so it is bounded only by its own
|
|
42
|
+
* timeout; `signal` ends this caller's wait without cancelling the lookup.
|
|
40
43
|
*/
|
|
41
44
|
export declare function ensureAntigravityVersion(fetcher?: FetchImpl, signal?: AbortSignal): Promise<void>;
|
|
42
45
|
/** Antigravity `User-Agent` header value; rebuilt when the discovered version changes. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linxiraos/pi-catalog",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.27",
|
|
4
4
|
"description": "Model catalog for omp: bundled model database, provider discovery descriptors, model identity, classification, and equivalence",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -90,12 +90,12 @@
|
|
|
90
90
|
"gen:proto": "bun scripts/generate-protocols.ts"
|
|
91
91
|
},
|
|
92
92
|
"dependencies": {
|
|
93
|
-
"@linxiraos/pi-omptype": "1.1.
|
|
94
|
-
"@linxiraos/pi-utils": "1.1.
|
|
93
|
+
"@linxiraos/pi-omptype": "1.1.27",
|
|
94
|
+
"@linxiraos/pi-utils": "1.1.27"
|
|
95
95
|
},
|
|
96
96
|
"devDependencies": {
|
|
97
97
|
"@bgotink/kdl": "0.4.0",
|
|
98
|
-
"@linxiraos/pi-ai": "1.1.
|
|
98
|
+
"@linxiraos/pi-ai": "1.1.27",
|
|
99
99
|
"@types/bun": "^1.3.14"
|
|
100
100
|
},
|
|
101
101
|
"engines": {
|
package/src/compat/auth-ids.ts
CHANGED
|
@@ -72,6 +72,7 @@ export type AuthProviderId =
|
|
|
72
72
|
| "siliconflow-cn"
|
|
73
73
|
| "singularityapi-dev"
|
|
74
74
|
| "singularityapi-tech"
|
|
75
|
+
| "snowflake"
|
|
75
76
|
| "stencil"
|
|
76
77
|
| "stepfun"
|
|
77
78
|
| "synthetic"
|
|
@@ -156,6 +157,7 @@ export type LoginProviderId =
|
|
|
156
157
|
| "siliconflow-cn"
|
|
157
158
|
| "singularityapi-dev"
|
|
158
159
|
| "singularityapi-tech"
|
|
160
|
+
| "snowflake"
|
|
159
161
|
| "stencil"
|
|
160
162
|
| "stepfun"
|
|
161
163
|
| "synthetic"
|
package/src/compat/axes.ts
CHANGED
|
@@ -169,6 +169,7 @@ export const AXES: Readonly<Record<string, AxisDef>> = {
|
|
|
169
169
|
"requires-thinking-as-text": wire("requiresThinkingAsText", ["openai"]),
|
|
170
170
|
"requires-tool-result-name": wire("requiresToolResultName", ["openai"]),
|
|
171
171
|
"strict-responses-pairing": wire("strictResponsesPairing", ["openai-responses"]),
|
|
172
|
+
"stateful-responses": wire("statefulResponses", ["openai-responses"]),
|
|
172
173
|
"requires-reasoning-off-juice-instruction": wire("requiresReasoningOffJuiceInstruction", ["openai-responses"]),
|
|
173
174
|
"supports-all-turns-reasoning-context": wire("supportsAllTurnsReasoningContext", ["openai-responses"]),
|
|
174
175
|
"supports-configuration-update": wire("supportsConfigurationUpdate", ["openai-responses"]),
|
|
@@ -194,6 +195,7 @@ export const AXES: Readonly<Record<string, AxisDef>> = {
|
|
|
194
195
|
"supports-reasoning-effort": wire("supportsReasoningEffort", OAI),
|
|
195
196
|
"supports-reasoning-params": wire("supportsReasoningParams", OAI),
|
|
196
197
|
"supports-reasoning-summary": wire("supportsReasoningSummary", ["openai-responses"]),
|
|
198
|
+
"store-responses": wire("storeResponses", ["openai-responses"]),
|
|
197
199
|
"supports-store": wire("supportsStore", ["openai"]),
|
|
198
200
|
"supports-strict-mode": wire("supportsStrictMode", OAI),
|
|
199
201
|
"supports-tool-choice": wire("supportsToolChoice", OAI),
|
|
@@ -293,7 +295,7 @@ export const AXES: Readonly<Record<string, AxisDef>> = {
|
|
|
293
295
|
"stream-idle-timeout-ms": wire("streamIdleTimeoutMs", [...OAI, "anthropic", "bedrock", "google"]),
|
|
294
296
|
"strip-image-input": wire("stripImageInput", [...OAI, "anthropic", "google"]),
|
|
295
297
|
"supports-forced-tool-choice": wire("supportsForcedToolChoice", [...OAI, "anthropic", "bedrock"]),
|
|
296
|
-
"supports-sampling-params": wire("supportsSamplingParams", [...OAI, "anthropic"]),
|
|
298
|
+
"supports-sampling-params": wire("supportsSamplingParams", [...OAI, "anthropic", "bedrock", "devin", "google"]),
|
|
297
299
|
"thinking-loop-guard": wire("thinkingLoopGuard", [...OAI, "anthropic", "google"], "scalar", [
|
|
298
300
|
"gemini",
|
|
299
301
|
"deepseek",
|
|
@@ -304,7 +306,7 @@ export const AXES: Readonly<Record<string, AxisDef>> = {
|
|
|
304
306
|
"thinking-default-level": { key: "defaultLevel", set: "thinking", shape: "scalar", values: EFFORTS },
|
|
305
307
|
"thinking-effort-budgets": { key: "effortBudgets", set: "thinking", shape: "object" },
|
|
306
308
|
"thinking-effort-map": { key: "effortMap", set: "thinking", shape: "object" },
|
|
307
|
-
"thinking-efforts": { key: "efforts", set: "thinking", shape: "array", values: EFFORTS },
|
|
309
|
+
"thinking-efforts": { key: "efforts", set: "thinking", shape: "array", values: EFFORTS, emptyArray: true },
|
|
308
310
|
"thinking-mode": {
|
|
309
311
|
key: "mode",
|
|
310
312
|
set: "thinking",
|
|
@@ -390,6 +392,8 @@ export const AXES: Readonly<Record<string, AxisDef>> = {
|
|
|
390
392
|
},
|
|
391
393
|
"web-search-model": { key: "webSearchModel", set: "catalog", shape: "scalar" },
|
|
392
394
|
"hosted-image": { key: "hostedImage", set: "catalog", shape: "scalar", values: [true, false] },
|
|
395
|
+
/** How the model line bills an input image; shape and formulas in `./image-tokenization`. */
|
|
396
|
+
"image-tokenization": { key: "imageTokenization", set: "catalog", shape: "object" },
|
|
393
397
|
"image-model": { key: "imageModel", set: "catalog", shape: "scalar" },
|
|
394
398
|
"limits-patch": { key: "limitsPatch", set: "catalog", shape: "object" },
|
|
395
399
|
"long-context-cost": { key: "longContext", set: "catalog", shape: "object" },
|
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
|
+
}
|
|
@@ -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" | "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" | "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";
|