@oh-my-pi/pi-catalog 18.6.0 → 18.6.3
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 +22 -0
- package/dist/types/compat/auth-ids.d.ts +2 -2
- 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/types.d.ts +14 -2
- package/package.json +4 -4
- package/src/compat/auth-ids.ts +2 -0
- package/src/compat/axes.ts +4 -0
- package/src/compat/image-tokenization.ts +166 -0
- package/src/compat/provider-ids.ts +1 -0
- package/src/compat/resolve.ts +11 -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 +72 -7
- package/src/compat/rules/classes/gemini.kdl +12 -0
- package/src/compat/rules/classes/openai.kdl +102 -0
- package/src/compat/rules/providers/google-antigravity.kdl +3 -0
- package/src/compat/rules/providers/image-tokenization.kdl +42 -0
- package/src/compat/rules/providers/muse-code.kdl +7 -0
- package/src/compat/rules/providers/openai-codex.kdl +1 -0
- package/src/compat/rules/providers/snowflake.kdl +135 -0
- package/src/compat/rules.json +1 -1
- package/src/hosts.ts +2 -0
- package/src/models.json +1 -1
- package/src/provider-models/google.ts +2 -0
- package/src/provider-models/special.ts +2 -2
- package/src/types.ts +14 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [18.6.3] - 2026-10-06
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- 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)).
|
|
10
|
+
- 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)).
|
|
11
|
+
- 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)).
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- 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)).
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- 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)).
|
|
20
|
+
|
|
21
|
+
## [18.6.2] - 2026-10-04
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- Fixed Google Antigravity listing models the signed-in account cannot use, such as Claude Opus 5.5 and Sonnet 5.5 on plans without them, which failed every request with `404 Requested entity was not found`. After a successful model refresh and on subsequent restarts, only models in the account's own Antigravity model list are offered ([#14328](https://github.com/can1357/oh-my-pi/issues/14328)).
|
|
26
|
+
|
|
5
27
|
## [18.6.0] - 2026-10-03
|
|
6
28
|
|
|
7
29
|
### Fixed
|
|
@@ -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";
|
|
@@ -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"];
|
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
|
|
@@ -796,7 +798,7 @@ export interface ResolvedOpenAISharedCompat {
|
|
|
796
798
|
* `buildModel`; request handlers read fields and never detect, resolve, or
|
|
797
799
|
* allocate.
|
|
798
800
|
*/
|
|
799
|
-
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">> & {
|
|
800
802
|
vercelGatewayRouting?: OpenAICompat["vercelGatewayRouting"];
|
|
801
803
|
extraBody?: OpenAICompat["extraBody"];
|
|
802
804
|
cacheControlFormat?: OpenAICompat["cacheControlFormat"];
|
|
@@ -843,6 +845,16 @@ export interface ResolvedOpenAIResponsesCompat extends ResolvedOpenAISharedCompa
|
|
|
843
845
|
* filling `"auto"`.
|
|
844
846
|
*/
|
|
845
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;
|
|
846
858
|
streamIdleTimeoutMs?: number;
|
|
847
859
|
vercelGatewayRouting?: OpenAICompat["vercelGatewayRouting"];
|
|
848
860
|
/** The model sits behind Vercel AI Gateway's Responses endpoint. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-catalog",
|
|
3
|
-
"version": "18.6.
|
|
3
|
+
"version": "18.6.3",
|
|
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.6.
|
|
97
|
-
"@oh-my-pi/pi-utils": "18.6.
|
|
96
|
+
"@oh-my-pi/omptype": "18.6.3",
|
|
97
|
+
"@oh-my-pi/pi-utils": "18.6.3"
|
|
98
98
|
},
|
|
99
99
|
"devDependencies": {
|
|
100
100
|
"@bgotink/kdl": "0.4.0",
|
|
101
|
-
"@oh-my-pi/pi-ai": "18.6.
|
|
101
|
+
"@oh-my-pi/pi-ai": "18.6.3",
|
|
102
102
|
"@types/bun": "^1.3.14"
|
|
103
103
|
},
|
|
104
104
|
"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),
|
|
@@ -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" },
|
|
@@ -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 "@oh-my-pi/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
|
+
}
|
package/src/compat/resolve.ts
CHANGED
|
@@ -735,8 +735,15 @@ function resolveOpenAIResponsesPolicy(
|
|
|
735
735
|
supportsPromptCacheBreakpoints,
|
|
736
736
|
promptCacheBreakpointTtl: supportsPromptCacheBreakpoints ? "30m" : undefined,
|
|
737
737
|
strictResponsesPairing: isAzure || provider === "github-copilot",
|
|
738
|
-
|
|
738
|
+
// Azure's provider id alone only implies support while its endpoint is
|
|
739
|
+
// resolved at runtime; an explicit non-Azure baseUrl is a proxy, like Codex.
|
|
740
|
+
supportsImageDetailOriginal:
|
|
741
|
+
isOpenAIUrl ||
|
|
742
|
+
hostMatchesUrl(baseUrl, "azureOpenAI") ||
|
|
743
|
+
(isAzure && !baseUrl) ||
|
|
744
|
+
hostMatchesUrl(baseUrl, "openaiCodex"),
|
|
739
745
|
supportsReasoningSummary: !isXaiHost,
|
|
746
|
+
statefulResponses: undefined,
|
|
740
747
|
supportsAllTurnsReasoningContext: false,
|
|
741
748
|
supportsConfigurationUpdate: false,
|
|
742
749
|
supportsSteering: false,
|
|
@@ -784,6 +791,7 @@ function resolveOpenAIResponsesPolicy(
|
|
|
784
791
|
PROXY_OPENAI_COMPAT_PROVIDERS[backendProvider] !== true &&
|
|
785
792
|
(LOCAL_OPENAI_COMPAT_PROVIDERS[backendProvider] === true || hasLocalLoopbackBaseUrl(baseUrl)),
|
|
786
793
|
supportsObfuscationOptOut: isOpenAIUrl || provider === "openai",
|
|
794
|
+
storeResponses: false,
|
|
787
795
|
officialEndpoint: isOfficialOpenAIEndpoint(provider, baseUrl),
|
|
788
796
|
harmonyLeakMitigation: false,
|
|
789
797
|
rejectRootObjectUnion: false,
|
|
@@ -845,6 +853,7 @@ function pickResponsesOnly(compat: ResolvedOpenAIResponsesCompat): ResponsesOnly
|
|
|
845
853
|
strictResponsesPairing: compat.strictResponsesPairing,
|
|
846
854
|
supportsImageDetailOriginal: compat.supportsImageDetailOriginal,
|
|
847
855
|
supportsObfuscationOptOut: compat.supportsObfuscationOptOut,
|
|
856
|
+
storeResponses: compat.storeResponses,
|
|
848
857
|
supportsAllTurnsReasoningContext: compat.supportsAllTurnsReasoningContext,
|
|
849
858
|
supportsConfigurationUpdate: compat.supportsConfigurationUpdate,
|
|
850
859
|
supportsSteering: compat.supportsSteering,
|
|
@@ -853,6 +862,7 @@ function pickResponsesOnly(compat: ResolvedOpenAIResponsesCompat): ResponsesOnly
|
|
|
853
862
|
cacheControlFormat: compat.cacheControlFormat,
|
|
854
863
|
requiresReasoningOffJuiceInstruction: compat.requiresReasoningOffJuiceInstruction,
|
|
855
864
|
supportsReasoningSummary: compat.supportsReasoningSummary,
|
|
865
|
+
statefulResponses: compat.statefulResponses,
|
|
856
866
|
isVercelGatewayHost: compat.isVercelGatewayHost,
|
|
857
867
|
} satisfies ResponsesOnlyCompat;
|
|
858
868
|
}
|
|
@@ -8,6 +8,6 @@ login-order "openai-codex" "anthropic" "zai" "zai-coding-plan" "kimi-code" "open
|
|
|
8
8
|
"xiaomi-token-plan-ams" "xiaomi-token-plan-cn" "firepass" "cline-pass" "factory-droid" "commandcode" "charm-hyper" "deepseek" "stepfun" "muse-code" "meta" "moonshot" \
|
|
9
9
|
"cerebras" "baseten" "helmcode" "fireworks" "together" "nvidia" "novita" "deepinfra" "huggingface" "perplexity" \
|
|
10
10
|
"qianfan" "venice" "siliconflow" "siliconflow-cn" "synthetic" "nanogpt" "wafer-serverless" "coreweave" \
|
|
11
|
-
"vercel-ai-gateway" "cloudflare-ai-gateway" "litellm" "kilo" "zenmux" "opencode-zen" "opencode-go" \
|
|
11
|
+
"vercel-ai-gateway" "cloudflare-ai-gateway" "snowflake" "litellm" "kilo" "zenmux" "opencode-zen" "opencode-go" \
|
|
12
12
|
"yolo-auto" "tavily" "kagi" "exa" "parallel" "typesafe" "ollama" "ollama-cloud" "lm-studio" "llama.cpp" "vllm" \
|
|
13
13
|
"gmi-cloud" "stencil" "singularityapi-dev" "singularityapi-tech"
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Snowflake Cortex REST API. Login runs Snowflake OAuth for local applications
|
|
2
|
+
// (built-in SNOWFLAKE$LOCAL_APPLICATION client, PKCE, loopback); the account
|
|
3
|
+
// host is prompted first, so the flow is a custom hook.
|
|
4
|
+
auth "snowflake" {
|
|
5
|
+
name "Snowflake Cortex"
|
|
6
|
+
login "custom" hook="snowflake"
|
|
7
|
+
refresh hook="snowflake"
|
|
8
|
+
api-key-format "structured"
|
|
9
|
+
callback-port 54551
|
|
10
|
+
paste-code #true
|
|
11
|
+
}
|
|
@@ -1,5 +1,70 @@
|
|
|
1
1
|
// Model-lineage compat for "anthropic"; regenerated from the frozen census using class taxonomy selectors.
|
|
2
2
|
|
|
3
|
+
// Images bill ⌈w/28⌉·⌈h/28⌉ visual tokens on every host. Claude 4.7+ and
|
|
4
|
+
// Fable/Mythos read up to 2576px and 4,784 tokens; older lines fit 1568px and
|
|
5
|
+
// cap at 1,568 tokens, downscaling past it.
|
|
6
|
+
// https://platform.claude.com/docs/en/build-with-claude/vision#evaluate-image-size
|
|
7
|
+
// The class default is the high-res tier, so ids without a revision
|
|
8
|
+
// (`claude-opus-latest`, `~anthropic/claude-sonnet-latest`) and future lines
|
|
9
|
+
// are never undercounted; only Opus/Sonnet/Haiku revisions known to predate
|
|
10
|
+
// 4.7 take the standard tier. Separator-collapsed revisions parse as two
|
|
11
|
+
// digits (`claude-opus-45` is Opus 4.5 → 45), so 10–46 is standard too.
|
|
12
|
+
class "anthropic" {
|
|
13
|
+
image-tokenization {
|
|
14
|
+
regime "anthropic-patch"
|
|
15
|
+
max-edge 2576
|
|
16
|
+
max-tokens 4784
|
|
17
|
+
}
|
|
18
|
+
family "opus" {
|
|
19
|
+
revision "<4.7" {
|
|
20
|
+
image-tokenization {
|
|
21
|
+
regime "anthropic-patch"
|
|
22
|
+
max-edge 1568
|
|
23
|
+
max-tokens 1568
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
revision ">=10 <47" {
|
|
27
|
+
image-tokenization {
|
|
28
|
+
regime "anthropic-patch"
|
|
29
|
+
max-edge 1568
|
|
30
|
+
max-tokens 1568
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
family "sonnet" {
|
|
35
|
+
revision "<4.7" {
|
|
36
|
+
image-tokenization {
|
|
37
|
+
regime "anthropic-patch"
|
|
38
|
+
max-edge 1568
|
|
39
|
+
max-tokens 1568
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
revision ">=10 <47" {
|
|
43
|
+
image-tokenization {
|
|
44
|
+
regime "anthropic-patch"
|
|
45
|
+
max-edge 1568
|
|
46
|
+
max-tokens 1568
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
family "haiku" {
|
|
51
|
+
revision "<4.7" {
|
|
52
|
+
image-tokenization {
|
|
53
|
+
regime "anthropic-patch"
|
|
54
|
+
max-edge 1568
|
|
55
|
+
max-tokens 1568
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
revision ">=10 <47" {
|
|
59
|
+
image-tokenization {
|
|
60
|
+
regime "anthropic-patch"
|
|
61
|
+
max-edge 1568
|
|
62
|
+
max-tokens 1568
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
3
68
|
class "anthropic" {
|
|
4
69
|
family "fable" {
|
|
5
70
|
revision ">=5.1" {
|
|
@@ -181,7 +246,7 @@ class "anthropic" {
|
|
|
181
246
|
thinking-efforts "minimal" "low" "medium" "high" "xhigh"
|
|
182
247
|
}
|
|
183
248
|
on "amazon-bedrock" "anthropic" "cloudflare-ai-gateway" "github-copilot" "gitlab-duo" \
|
|
184
|
-
"google-antigravity" "google-vertex" "opencode-zen" "vercel-ai-gateway" "zenmux" {
|
|
249
|
+
"google-antigravity" "google-vertex" "opencode-zen" "snowflake" "vercel-ai-gateway" "zenmux" {
|
|
185
250
|
family "sonnet" {
|
|
186
251
|
revision ">=3.7 <4.6" {
|
|
187
252
|
thinking-mode "budget"
|
|
@@ -189,13 +254,13 @@ class "anthropic" {
|
|
|
189
254
|
}
|
|
190
255
|
}
|
|
191
256
|
on "amazon-bedrock" "anthropic" "cloudflare-ai-gateway" "github-copilot" "gitlab-duo" \
|
|
192
|
-
"google-vertex" "opencode-zen" "vercel-ai-gateway" {
|
|
257
|
+
"google-vertex" "opencode-zen" "snowflake" "vercel-ai-gateway" {
|
|
193
258
|
family "haiku" {
|
|
194
259
|
thinking-mode "budget"
|
|
195
260
|
}
|
|
196
261
|
}
|
|
197
262
|
on "amazon-bedrock" "anthropic" "cloudflare-ai-gateway" "github-copilot" "gitlab-duo" \
|
|
198
|
-
"google-vertex" "opencode-zen" "vercel-ai-gateway" "zenmux" {
|
|
263
|
+
"google-vertex" "opencode-zen" "snowflake" "vercel-ai-gateway" "zenmux" {
|
|
199
264
|
family "opus" {
|
|
200
265
|
revision ">=4.5 <4.6" {
|
|
201
266
|
thinking-mode "anthropic-budget-effort"
|
|
@@ -203,7 +268,7 @@ class "anthropic" {
|
|
|
203
268
|
}
|
|
204
269
|
}
|
|
205
270
|
on "amazon-bedrock" "anthropic" "cloudflare-ai-gateway" "github-copilot" "google-vertex" \
|
|
206
|
-
"opencode-zen" "openrouter" "vercel-ai-gateway" "zenmux" {
|
|
271
|
+
"opencode-zen" "openrouter" "snowflake" "vercel-ai-gateway" "zenmux" {
|
|
207
272
|
family "opus" {
|
|
208
273
|
revision ">=4.6 <4.7" {
|
|
209
274
|
thinking-efforts "low" "medium" "high" "max"
|
|
@@ -211,7 +276,7 @@ class "anthropic" {
|
|
|
211
276
|
}
|
|
212
277
|
}
|
|
213
278
|
on "amazon-bedrock" "anthropic" "cloudflare-ai-gateway" "github-copilot" "google-vertex" \
|
|
214
|
-
"opencode-zen" "vercel-ai-gateway" "zenmux" {
|
|
279
|
+
"opencode-zen" "snowflake" "vercel-ai-gateway" "zenmux" {
|
|
215
280
|
revision ">=4.7 <45.1" {
|
|
216
281
|
thinking-mode "anthropic-adaptive"
|
|
217
282
|
}
|
|
@@ -242,13 +307,13 @@ class "anthropic" {
|
|
|
242
307
|
}
|
|
243
308
|
}
|
|
244
309
|
on "anthropic" "cloudflare-ai-gateway" "github-copilot" "google-vertex" "opencode-zen" \
|
|
245
|
-
"openrouter" "vercel-ai-gateway" "zenmux" {
|
|
310
|
+
"openrouter" "snowflake" "vercel-ai-gateway" "zenmux" {
|
|
246
311
|
revision ">=4.7 <45.1" {
|
|
247
312
|
thinking-efforts "low" "medium" "high" "xhigh" "max"
|
|
248
313
|
}
|
|
249
314
|
}
|
|
250
315
|
on "anthropic" "cloudflare-ai-gateway" "github-copilot" "google-vertex" "opencode-zen" \
|
|
251
|
-
"vercel-ai-gateway" "zenmux" {
|
|
316
|
+
"snowflake" "vercel-ai-gateway" "zenmux" {
|
|
252
317
|
family "sonnet" {
|
|
253
318
|
revision ">=4.6 <5" {
|
|
254
319
|
thinking-efforts "low" "medium" "high"
|
|
@@ -118,3 +118,15 @@ class "gemini" {
|
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
|
+
|
|
122
|
+
// Gemini 3+ bills a fixed `media_resolution` budget per image regardless of
|
|
123
|
+
// pixels; the default for images is HIGH, 1,120 tokens.
|
|
124
|
+
// https://ai.google.dev/gemini-api/docs/media-resolution
|
|
125
|
+
class "gemini" {
|
|
126
|
+
revision ">=3" {
|
|
127
|
+
image-tokenization {
|
|
128
|
+
regime "fixed"
|
|
129
|
+
tokens 1120
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|