@oh-my-pi/pi-catalog 18.6.1 → 18.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -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/compat/types.d.ts +2 -0
- package/dist/types/hosts.d.ts +4 -0
- package/dist/types/types.d.ts +38 -4
- package/package.json +4 -4
- package/src/build.ts +4 -1
- 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/README.md +4 -0
- package/src/compat/rules/auth/_order.kdl +1 -1
- package/src/compat/rules/auth/minimax-code-cn.kdl +1 -1
- package/src/compat/rules/auth/minimax-code.kdl +1 -1
- package/src/compat/rules/auth/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/minimax.kdl +21 -1
- package/src/compat/rules/classes/openai.kdl +102 -0
- package/src/compat/rules/providers/apple.kdl +1 -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/minimax-code-cn.kdl +10 -5
- package/src/compat/rules/providers/minimax-code.kdl +10 -5
- package/src/compat/rules/providers/mistral.kdl +20 -0
- package/src/compat/rules/providers/muse-code.kdl +7 -0
- package/src/compat/rules/providers/openai-codex.kdl +7 -0
- package/src/compat/rules/providers/openai.kdl +5 -0
- package/src/compat/rules/providers/snowflake.kdl +135 -0
- package/src/compat/rules/runtime/behavior.kdl +2 -0
- package/src/compat/rules/taxonomy/_collapse.kdl +18 -0
- package/src/compat/rules.json +1 -1
- package/src/compat/types.ts +2 -0
- package/src/hosts.ts +2 -0
- package/src/models.json +1 -1
- package/src/provider-models/google.ts +2 -0
- package/src/provider-models/openai-compat.ts +6 -17
- package/src/provider-models/special.ts +2 -2
- package/src/types.ts +75 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,45 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [18.7.0] - 2026-10-06
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added Mistral Large 4 with reasoning support, image input, a 1M-token context window, and preview pricing.
|
|
10
|
+
- Added configurable thinking levels from low through maximum for MiniMax-M3.1-Flash-Preview; because the model always reasons, requests that disable thinking use the low level.
|
|
11
|
+
- Added Google Antigravity pricing and model support for Claude Opus 5.5 and Sonnet 5.5.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- MiniMax Token Plan providers (`minimax-code` and `minimax-code-cn`) now use MiniMax's recommended Anthropic-compatible API for model requests and login key validation.
|
|
16
|
+
- Google Antigravity now exposes Claude Opus 5.5 and Sonnet 5.5 once each, with selectable low, medium, and high thinking levels.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- Added the correct published pricing for GPT-6 Astra's Ultrafast service tier: a 6× multiplier on the OpenAI API and an 8× multiplier on the Codex card.
|
|
21
|
+
|
|
22
|
+
## [18.6.3] - 2026-10-06
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- 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)).
|
|
27
|
+
- 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)).
|
|
28
|
+
- 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)).
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
- 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)).
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
|
|
36
|
+
- 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)).
|
|
37
|
+
|
|
38
|
+
## [18.6.2] - 2026-10-04
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
|
|
42
|
+
- 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)).
|
|
43
|
+
|
|
5
44
|
## [18.6.0] - 2026-10-03
|
|
6
45
|
|
|
7
46
|
### 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";
|
|
@@ -622,6 +622,8 @@ export interface CompiledProvider {
|
|
|
622
622
|
id: string;
|
|
623
623
|
/** Preferred model id when no explicit selection is made. */
|
|
624
624
|
defaultModel: string;
|
|
625
|
+
/** Whether the provider participates in automatic default selection (defaults to true). */
|
|
626
|
+
automaticDefault?: boolean;
|
|
625
627
|
/** Env vars consulted, in order, for the runtime API-key fallback. */
|
|
626
628
|
envVars?: string[];
|
|
627
629
|
/** The runtime creates a model manager even without a valid API key. */
|
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
|
@@ -15,6 +15,22 @@ export type KindApiKind = (typeof KIND_API_KINDS)[number];
|
|
|
15
15
|
export type WebSearchGrounding = "gemini" | "anthropic" | "codex" | "xai" | "openrouter" | "openai";
|
|
16
16
|
/** Non-chat runner protocols accepted by catalog seeds, outside the chat dispatch union. */
|
|
17
17
|
export declare const RUNNER_APIS: readonly ["local-inference", "web-search", "typesafe", "openrouter-decisions", "openai-images", "openrouter-images", "xai-tts", "openai-speech", "openai-embeddings", "openrouter-rerank", "openrouter-video", "openai-transcriptions"];
|
|
18
|
+
/** Kind each single-purpose runner API serves; `local-inference` hosts several kinds. */
|
|
19
|
+
export declare const RUNNER_API_KINDS: Record<Exclude<(typeof RUNNER_APIS)[number], "local-inference">, ModelKind>;
|
|
20
|
+
/** Kind a runner API serves; `undefined` for chat transports and multi-kind `local-inference`. */
|
|
21
|
+
export declare function runnerApiKind(api: Api): ModelKind | undefined;
|
|
22
|
+
/** Catalog APIs `generate_image` runs through a pi-ai image client; the hosted Responses pair needs a carrier model. */
|
|
23
|
+
export declare const IMAGE_GENERATION_APIS: readonly ["openai-images", "openrouter-images", "google-generative-ai", "google-gemini-cli", "openai-responses", "openai-codex-responses"];
|
|
24
|
+
export type ImageGenerationApi = (typeof IMAGE_GENERATION_APIS)[number];
|
|
25
|
+
/**
|
|
26
|
+
* Kinds a model on `api` may declare: a runner api serves its own kind; a chat
|
|
27
|
+
* transport serves `chat` and `tiny`, plus `image` when `generate_image` runs it
|
|
28
|
+
* (hosted Responses image tool, Gemini image models). `undefined` for
|
|
29
|
+
* `local-inference`, which hosts several kinds chosen by the model itself.
|
|
30
|
+
*/
|
|
31
|
+
export declare function servedKinds(api: Api): readonly ModelKind[] | undefined;
|
|
32
|
+
/** Whether a model of `kind` can run on `api`. */
|
|
33
|
+
export declare function apiServesKind(api: Api, kind: ModelKind): boolean;
|
|
18
34
|
/** Resolve a model's kind while preserving chat semantics for existing catalog rows. */
|
|
19
35
|
export declare function modelKind(model: Pick<Model, "kind">): ModelKind;
|
|
20
36
|
/** Canonical thinking transport used by a model. */
|
|
@@ -378,6 +394,8 @@ export interface OpenAICompat {
|
|
|
378
394
|
supportsReasoningParams?: boolean;
|
|
379
395
|
/** Whether Responses requests may include `reasoning.summary`. Default: true except on known incompatible hosts. */
|
|
380
396
|
supportsReasoningSummary?: boolean;
|
|
397
|
+
/** Whether to chain OpenAI Responses turns using stored response ids. Unset uses the official-endpoint default; env and call options take precedence. */
|
|
398
|
+
statefulResponses?: boolean;
|
|
381
399
|
/**
|
|
382
400
|
* Whether the endpoint accepts explicit sampling parameters (`temperature`,
|
|
383
401
|
* `top_p`, `top_k`, `min_p`, penalties). OpenAI proprietary reasoning models
|
|
@@ -398,7 +416,7 @@ export interface OpenAICompat {
|
|
|
398
416
|
alwaysSendMaxTokens?: boolean;
|
|
399
417
|
/** Whether Responses-API tool-call/result history must be strictly paired. Default: auto-detected (Azure OpenAI, GitHub Copilot). */
|
|
400
418
|
strictResponsesPairing?: boolean;
|
|
401
|
-
/** Whether the Responses API accepts the `detail: "original"` image hint. Default:
|
|
419
|
+
/** 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
420
|
supportsImageDetailOriginal?: boolean;
|
|
403
421
|
/**
|
|
404
422
|
* Whether the Responses endpoint accepts `configuration_update` input items
|
|
@@ -796,7 +814,7 @@ export interface ResolvedOpenAISharedCompat {
|
|
|
796
814
|
* `buildModel`; request handlers read fields and never detect, resolve, or
|
|
797
815
|
* allocate.
|
|
798
816
|
*/
|
|
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">> & {
|
|
817
|
+
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
818
|
vercelGatewayRouting?: OpenAICompat["vercelGatewayRouting"];
|
|
801
819
|
extraBody?: OpenAICompat["extraBody"];
|
|
802
820
|
cacheControlFormat?: OpenAICompat["cacheControlFormat"];
|
|
@@ -843,6 +861,16 @@ export interface ResolvedOpenAIResponsesCompat extends ResolvedOpenAISharedCompa
|
|
|
843
861
|
* filling `"auto"`.
|
|
844
862
|
*/
|
|
845
863
|
supportsReasoningSummary: boolean;
|
|
864
|
+
/** Optional chaining override; unset falls back to officialEndpoint at request time. */
|
|
865
|
+
statefulResponses?: boolean;
|
|
866
|
+
/**
|
|
867
|
+
* Whether the host can store Responses results server-side (`store: true`).
|
|
868
|
+
* Rule-owned: hosts that keep generating after a client disconnect so a
|
|
869
|
+
* dropped stream can resume via `GET /responses/{id}`. Storage is opt-in
|
|
870
|
+
* per request (`storeResponses` stream option, else
|
|
871
|
+
* `PI_MUSE_STORE_RESPONSES`); stored runs retain prompts and outputs on the provider.
|
|
872
|
+
*/
|
|
873
|
+
storeResponses: boolean;
|
|
846
874
|
streamIdleTimeoutMs?: number;
|
|
847
875
|
vercelGatewayRouting?: OpenAICompat["vercelGatewayRouting"];
|
|
848
876
|
/** The model sits behind Vercel AI Gateway's Responses endpoint. */
|
|
@@ -1078,6 +1106,12 @@ export interface Model<TApi extends Api = Api> {
|
|
|
1078
1106
|
id: string;
|
|
1079
1107
|
/** Role-specific runner capability; omitted for ordinary chat models. */
|
|
1080
1108
|
kind?: ModelKind;
|
|
1109
|
+
/**
|
|
1110
|
+
* Verbatim configured kind (models.yml, `modelOverrides`, runtime
|
|
1111
|
+
* registrations). `buildModel` applies it over catalog `kind` rules on every
|
|
1112
|
+
* rebuild, so a configured runner keeps its role.
|
|
1113
|
+
*/
|
|
1114
|
+
kindConfig?: ModelKind;
|
|
1081
1115
|
/** Grounding transport supported by this chat model. */
|
|
1082
1116
|
webSearch?: WebSearchGrounding;
|
|
1083
1117
|
/** Cheaper same-provider model to run hosted web search in this model's place (model id or provider/id). */
|
|
@@ -1290,9 +1324,9 @@ export interface Model<TApi extends Api = Api> {
|
|
|
1290
1324
|
/**
|
|
1291
1325
|
* Per-service-tier cost multipliers baked from the `service-tier-cost`
|
|
1292
1326
|
* catalog axis (e.g. `{ priority: 2.5 }`). Absent tiers use the API-generic
|
|
1293
|
-
* defaults.
|
|
1327
|
+
* defaults, and a tier with no published price stays at 1x.
|
|
1294
1328
|
*/
|
|
1295
|
-
serviceTierCost?: Readonly<Partial<Record<"flex" | "priority", number>>>;
|
|
1329
|
+
serviceTierCost?: Readonly<Partial<Record<"flex" | "priority" | "ultrafast", number>>>;
|
|
1296
1330
|
/**
|
|
1297
1331
|
* Provider-supplied one-line blurb for this model. Set only when an upstream
|
|
1298
1332
|
* ships one (Devin's `GetCliModelConfigs`); never synthesized locally.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-catalog",
|
|
3
|
-
"version": "18.
|
|
3
|
+
"version": "18.7.0",
|
|
4
4
|
"description": "Model catalog for omp: bundled model database, provider discovery descriptors, model identity, classification, and equivalence",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -93,12 +93,12 @@
|
|
|
93
93
|
"gen:proto": "bun scripts/generate-protocols.ts"
|
|
94
94
|
},
|
|
95
95
|
"dependencies": {
|
|
96
|
-
"@oh-my-pi/omptype": "18.
|
|
97
|
-
"@oh-my-pi/pi-utils": "18.
|
|
96
|
+
"@oh-my-pi/omptype": "18.7.0",
|
|
97
|
+
"@oh-my-pi/pi-utils": "18.7.0"
|
|
98
98
|
},
|
|
99
99
|
"devDependencies": {
|
|
100
100
|
"@bgotink/kdl": "0.4.0",
|
|
101
|
-
"@oh-my-pi/pi-ai": "18.
|
|
101
|
+
"@oh-my-pi/pi-ai": "18.7.0",
|
|
102
102
|
"@types/bun": "^1.3.14"
|
|
103
103
|
},
|
|
104
104
|
"engines": {
|
package/src/build.ts
CHANGED
|
@@ -101,9 +101,11 @@ function applyCatalogAssignments<TApi extends Api>(model: Model<TApi>, catalog:
|
|
|
101
101
|
if (serviceTierCost !== undefined) {
|
|
102
102
|
const flex = numberField(serviceTierCost, "flex");
|
|
103
103
|
const priorityTier = numberField(serviceTierCost, "priority");
|
|
104
|
+
const ultrafast = numberField(serviceTierCost, "ultrafast");
|
|
104
105
|
model.serviceTierCost = {
|
|
105
106
|
...(flex !== undefined && { flex }),
|
|
106
107
|
...(priorityTier !== undefined && { priority: priorityTier }),
|
|
108
|
+
...(ultrafast !== undefined && { ultrafast }),
|
|
107
109
|
};
|
|
108
110
|
}
|
|
109
111
|
const priority = catalog.priority;
|
|
@@ -363,7 +365,8 @@ export function buildModel<TApi extends Api>(spec: ModelSpec<TApi>): Model<TApi>
|
|
|
363
365
|
};
|
|
364
366
|
applyCatalogAssignments(model, policy.catalog);
|
|
365
367
|
applyCatalogCorrections(model, policy.catalog);
|
|
366
|
-
// Configured lifetimes replace catalog
|
|
368
|
+
// Configured kinds and lifetimes replace catalog values rather than merging with them.
|
|
369
|
+
if (spec.kindConfig !== undefined) model.kind = spec.kindConfig;
|
|
367
370
|
if (spec.promptCacheConfig !== undefined) model.promptCache = { ...spec.promptCacheConfig };
|
|
368
371
|
return model;
|
|
369
372
|
}
|
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
|
}
|
|
@@ -413,6 +413,10 @@ provider "muse-code" {
|
|
|
413
413
|
|
|
414
414
|
Only `discovery` enrolls a provider in `generate-models.ts`; providers without it are never fetched at generation time (see the `charm-hyper` entry for why a live gateway deliberately omits it).
|
|
415
415
|
|
|
416
|
+
`automatic-default #false` keeps a provider available for explicit selection but excludes it from startup fallback and automatic model presets. The default is `#true`; `apple` opts out because its on-device context cannot accommodate the standard coding-agent prompt in many projects.
|
|
417
|
+
|
|
418
|
+
`kind-apis { <kind> "<api>" }` maps each non-chat kind (`image`, `tts`, `stt`, `embedding`, `rerank`, `video`) to the API discovery assigns rows of that kind. A runner API must sit under the kind it serves (`RUNNER_API_KINDS` in `src/types.ts`); chat APIs, which serve hosted image generation, and multi-kind `local-inference` may back any kind.
|
|
419
|
+
|
|
416
420
|
### Seed rows
|
|
417
421
|
|
|
418
422
|
A `seed` _defines_ bundled rows for providers whose catalog cannot be discovered at generation time — credential-scoped rosters, unauthenticated regens, or models ahead of upstream catalogs. Every other stratum patches rows; this one authors them. Runtime model managers hand the rows to `staticModels` through `seedModels(provider)`; the generator bundles them per the seed's `bundle` policy. Values are literal — a seed never derives from another provider's row, and pricing is never borrowed.
|
|
@@ -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"
|
|
@@ -4,6 +4,6 @@ auth "minimax-code-cn" {
|
|
|
4
4
|
auth-url "https://platform.minimaxi.com/subscribe/token-plan"
|
|
5
5
|
instructions "Subscribe to Token Plan and copy your API key"
|
|
6
6
|
prompt "Paste your MiniMax Token Plan API key" placeholder="sk-..."
|
|
7
|
-
validate "
|
|
7
|
+
validate "anthropic-messages" base-url="https://api.minimaxi.com/anthropic" model="MiniMax-M3"
|
|
8
8
|
}
|
|
9
9
|
}
|
|
@@ -4,6 +4,6 @@ auth "minimax-code" {
|
|
|
4
4
|
auth-url "https://platform.minimax.io/subscribe/token-plan"
|
|
5
5
|
instructions "Subscribe to Token Plan and copy your API key"
|
|
6
6
|
prompt "Paste your MiniMax Token Plan API key" placeholder="sk-..."
|
|
7
|
-
validate "
|
|
7
|
+
validate "anthropic-messages" label="MiniMax Token Plan" base-url="https://api.minimax.io/anthropic" model="MiniMax-M3"
|
|
8
8
|
}
|
|
9
9
|
}
|
|
@@ -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
|
+
}
|