@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.
Files changed (43) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/dist/types/compat/auth-ids.d.ts +2 -2
  3. package/dist/types/compat/image-tokenization.d.ts +52 -0
  4. package/dist/types/compat/provider-ids.d.ts +1 -1
  5. package/dist/types/compat/types.d.ts +2 -0
  6. package/dist/types/hosts.d.ts +4 -0
  7. package/dist/types/types.d.ts +38 -4
  8. package/package.json +4 -4
  9. package/src/build.ts +4 -1
  10. package/src/compat/auth-ids.ts +2 -0
  11. package/src/compat/axes.ts +4 -0
  12. package/src/compat/image-tokenization.ts +166 -0
  13. package/src/compat/provider-ids.ts +1 -0
  14. package/src/compat/resolve.ts +11 -1
  15. package/src/compat/rules/README.md +4 -0
  16. package/src/compat/rules/auth/_order.kdl +1 -1
  17. package/src/compat/rules/auth/minimax-code-cn.kdl +1 -1
  18. package/src/compat/rules/auth/minimax-code.kdl +1 -1
  19. package/src/compat/rules/auth/snowflake.kdl +11 -0
  20. package/src/compat/rules/classes/anthropic.kdl +72 -7
  21. package/src/compat/rules/classes/gemini.kdl +12 -0
  22. package/src/compat/rules/classes/minimax.kdl +21 -1
  23. package/src/compat/rules/classes/openai.kdl +102 -0
  24. package/src/compat/rules/providers/apple.kdl +1 -0
  25. package/src/compat/rules/providers/google-antigravity.kdl +3 -0
  26. package/src/compat/rules/providers/image-tokenization.kdl +42 -0
  27. package/src/compat/rules/providers/minimax-code-cn.kdl +10 -5
  28. package/src/compat/rules/providers/minimax-code.kdl +10 -5
  29. package/src/compat/rules/providers/mistral.kdl +20 -0
  30. package/src/compat/rules/providers/muse-code.kdl +7 -0
  31. package/src/compat/rules/providers/openai-codex.kdl +7 -0
  32. package/src/compat/rules/providers/openai.kdl +5 -0
  33. package/src/compat/rules/providers/snowflake.kdl +135 -0
  34. package/src/compat/rules/runtime/behavior.kdl +2 -0
  35. package/src/compat/rules/taxonomy/_collapse.kdl +18 -0
  36. package/src/compat/rules.json +1 -1
  37. package/src/compat/types.ts +2 -0
  38. package/src/hosts.ts +2 -0
  39. package/src/models.json +1 -1
  40. package/src/provider-models/google.ts +2 -0
  41. package/src/provider-models/openai-compat.ts +6 -17
  42. package/src/provider-models/special.ts +2 -2
  43. 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. */
@@ -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"];
@@ -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: auto-detected (false for GitHub Copilot, which rejects it with a 400). */
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.6.1",
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.6.1",
97
- "@oh-my-pi/pi-utils": "18.6.1"
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.6.1",
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 lifetimes rather than merging with them.
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
  }
@@ -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"
@@ -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
+ }
@@ -66,6 +66,7 @@ export type KnownProvider =
66
66
  | "siliconflow-cn"
67
67
  | "singularityapi-dev"
68
68
  | "singularityapi-tech"
69
+ | "snowflake"
69
70
  | "stepfun"
70
71
  | "synthetic"
71
72
  | "together"
@@ -735,8 +735,15 @@ function resolveOpenAIResponsesPolicy(
735
735
  supportsPromptCacheBreakpoints,
736
736
  promptCacheBreakpointTtl: supportsPromptCacheBreakpoints ? "30m" : undefined,
737
737
  strictResponsesPairing: isAzure || provider === "github-copilot",
738
- supportsImageDetailOriginal: !isXaiHost && !modelMatchesHost(hostModel, "githubCopilot"),
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 "chat-completions" base-url="https://api.minimaxi.com/v1" model="MiniMax-M3"
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 "chat-completions" label="MiniMax Token Plan" base-url="https://api.minimax.io/v1" model="MiniMax-M3"
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
+ }