@oh-my-pi/pi-catalog 18.0.3 → 18.0.5

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 CHANGED
@@ -2,6 +2,25 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [18.0.5] - 2026-08-25
6
+
7
+ ### Added
8
+
9
+ - Added built-in DeepInfra provider support (`deepinfra`, `DEEPINFRA_API_KEY`) with live model discovery, including chat models, context windows, pricing, cache-read pricing, vision input, and reasoning-effort capabilities.
10
+ - Added built-in Yolo-Auto provider support with the flat-rate `deepseek-flash-v4` model and live model discovery.
11
+
12
+ ### Fixed
13
+
14
+ - Fixed the Synthetic provider’s default model to use `hf:zai-org/GLM-5.2` instead of the retired `hf:zai-org/GLM-5.1`.
15
+
16
+ ## [18.0.4] - 2026-08-24
17
+
18
+ ### Fixed
19
+
20
+ - Fixed default reasoning effort for `cursor/cursor-grok-4.5` and `cursor/cursor-grok-4.6` so requests without an explicit effort setting default to `-medium` instead of `-low`, preventing rate limit rejections on Cursor's Start plan ([#9478](https://github.com/can1357/oh-my-pi/issues/9478)).
21
+ - Fixed aliased OpenCode Zen Ox Alpha models exposing incorrect effort levels, ensuring the gateway's native `low`, `high`, and `max` tiers are correctly mapped and reachable ([#9349](https://github.com/can1357/oh-my-pi/issues/9349)).
22
+ - Fixed missing rate card tier for public xAI and SuperGrok models with prompt lengths exceeding 200K tokens ([#9512](https://github.com/can1357/oh-my-pi/issues/9512)).
23
+
5
24
  ## [18.0.2] - 2026-08-23
6
25
 
7
26
  ### Fixed
@@ -67,4 +67,15 @@ export declare function resolveWireModelId<TApi extends Api>(model: ApiModel<TAp
67
67
  * thinking-off requests on `thinking.requiresEffort` models.
68
68
  */
69
69
  export declare function minimumSupportedEffort<TApi extends Api>(model: ApiModel<TApi>): Effort | undefined;
70
+ /**
71
+ * Clamp target for effort-less requests on `thinking.requiresEffort` models:
72
+ * the effort whose wire route equals the model's default wire id
73
+ * (`requestModelId`), so a collapsed row clamps to the tier it already
74
+ * advertises as its default rather than the numerically lowest supported tier
75
+ * (e.g. Cursor Grok 4.5/4.6 default to `medium`, the only tier the Start plan
76
+ * serves). Falls back to {@link minimumSupportedEffort} when no route matches
77
+ * the default id — families whose default already is the minimum, or that
78
+ * expose no routing, are unaffected.
79
+ */
80
+ export declare function defaultSupportedEffort<TApi extends Api>(model: ApiModel<TApi>): Effort | undefined;
70
81
  export {};
@@ -1,11 +1,14 @@
1
1
  import MODELS from "./models.json";
2
- import type { Api, KnownProvider, Model, Usage } from "./types.js";
2
+ import type { Api, KnownProvider, Model, ModelCost, Usage } from "./types.js";
3
3
  export type GeneratedProvider = keyof typeof MODELS;
4
4
  export declare function getBundledModel<TApi extends Api = Api>(provider: GeneratedProvider, modelId: string): Model<TApi>;
5
5
  export declare function getBundledProviders(): KnownProvider[];
6
6
  export declare function getBundledModels(provider: GeneratedProvider): Model<Api>[];
7
7
  /** Price a prompt as fully uncached input under its active context-length tier. */
8
- export declare function calculateUncachedInputCost(cost: Model["cost"], promptInputTokens: number): number;
8
+ export declare function calculateUncachedInputCost(cost: ModelCost, promptInputTokens: number): number;
9
+ /** Price one usage record from a token rate card, including active context tiers. */
10
+ export declare function calculateUsageCost(cost: ModelCost, usage: Usage): Usage["cost"];
11
+ /** Price one usage record from its model's token rate card. */
9
12
  export declare function calculateCost<TApi extends Api>(model: Model<TApi>, usage: Usage): Usage["cost"];
10
13
  /**
11
14
  * Check if two models are equal by comparing both their id and provider.
@@ -97,6 +97,16 @@ export declare const CATALOG_PROVIDERS: readonly [{
97
97
  readonly envVars: readonly ["CURSOR_API_KEY"];
98
98
  readonly oauthProvider: "cursor";
99
99
  };
100
+ }, {
101
+ readonly id: "deepinfra";
102
+ readonly defaultModel: "deepseek-ai/DeepSeek-V4-Flash-0731";
103
+ readonly envVars: readonly ["DEEPINFRA_API_KEY"];
104
+ readonly createModelManagerOptions: (config: ModelManagerConfig) => import("../index.js").ModelManagerOptions<"openai-completions", unknown>;
105
+ readonly dynamicModelsAuthoritative: true;
106
+ readonly catalogDiscovery: {
107
+ readonly label: "DeepInfra";
108
+ readonly allowUnauthenticated: true;
109
+ };
100
110
  }, {
101
111
  readonly id: "deepseek";
102
112
  readonly defaultModel: "deepseek-v4-pro";
@@ -361,7 +371,7 @@ export declare const CATALOG_PROVIDERS: readonly [{
361
371
  readonly dynamicModelsAuthoritative: true;
362
372
  }, {
363
373
  readonly id: "synthetic";
364
- readonly defaultModel: "hf:zai-org/GLM-5.1";
374
+ readonly defaultModel: "hf:zai-org/GLM-5.2";
365
375
  readonly envVars: readonly ["SYNTHETIC_API_KEY"];
366
376
  readonly createModelManagerOptions: (config: ModelManagerConfig) => import("../index.js").ModelManagerOptions<"openai-completions", unknown>;
367
377
  readonly dynamicModelsAuthoritative: true;
@@ -469,6 +479,15 @@ export declare const CATALOG_PROVIDERS: readonly [{
469
479
  readonly defaultModel: "mimo-v2.5";
470
480
  readonly envVars: readonly ["XIAOMI_TOKEN_PLAN_SGP_API_KEY"];
471
481
  readonly createModelManagerOptions: (config: ModelManagerConfig) => import("../index.js").ModelManagerOptions<"openai-completions", unknown>;
482
+ }, {
483
+ readonly id: "yolo-auto";
484
+ readonly defaultModel: "deepseek-flash-v4";
485
+ readonly envVars: readonly ["YOLO_AUTO_API_KEY"];
486
+ readonly createModelManagerOptions: (config: ModelManagerConfig) => import("../index.js").ModelManagerOptions<"openai-completions", unknown>;
487
+ readonly dynamicModelsAuthoritative: true;
488
+ readonly catalogDiscovery: {
489
+ readonly label: "Yolo-Auto";
490
+ };
472
491
  }, {
473
492
  readonly id: "zai";
474
493
  readonly defaultModel: "glm-5.3";
@@ -161,11 +161,29 @@ export interface NovitaModelManagerConfig {
161
161
  }
162
162
  /** Builds Novita's public model-discovery manager. */
163
163
  export declare function novitaModelManagerOptions(config?: NovitaModelManagerConfig): ModelManagerOptions<"openai-completions">;
164
+ export declare const DEEPINFRA_BASE_URL = "https://api.deepinfra.com/v1/openai";
165
+ /** DeepInfra OpenAI-compatible discovery configuration. */
166
+ export interface DeepinfraModelManagerConfig {
167
+ apiKey?: string;
168
+ baseUrl?: string;
169
+ fetch?: FetchImpl;
170
+ }
171
+ /**
172
+ * Builds DeepInfra's model-discovery manager. The catalog endpoint is public,
173
+ * so discovery (and keyless `gen:models` generation) works without an API key;
174
+ * model availability at runtime is still gated on credentials by the registry.
175
+ */
176
+ export declare function deepinfraModelManagerOptions(config?: DeepinfraModelManagerConfig): ModelManagerOptions<"openai-completions">;
164
177
  export interface XaiModelManagerConfig {
165
178
  apiKey?: string;
166
179
  baseUrl?: string;
167
180
  fetch?: FetchImpl;
168
181
  }
182
+ /**
183
+ * Applies xAI's long-context rate card and mirrors exact public-model prices
184
+ * onto matching SuperGrok catalog rows.
185
+ */
186
+ export declare function applyXaiCatalogPricing(models: readonly ModelSpec[]): ModelSpec[];
169
187
  export declare function xaiModelManagerOptions(config?: XaiModelManagerConfig): ModelManagerOptions<"openai-responses">;
170
188
  export interface XaiOAuthModelManagerConfig {
171
189
  apiKey?: string;
@@ -523,6 +541,29 @@ export interface AiandModelManagerConfig {
523
541
  * pricing metadata, so discovery is authoritative over the bundled seed.
524
542
  */
525
543
  export declare function aiandModelManagerOptions(config?: AiandModelManagerConfig): ModelManagerOptions<"openai-completions">;
544
+ /**
545
+ * Documented Yolo-Auto catalog (yolo-auto.com/docs, 2026-08) bundled so the
546
+ * provider is usable when generation and first boot have no live key. The
547
+ * flat-rate `/v1/models` response is authoritative once discovery runs.
548
+ * The compat block mirrors the provider's documented wire surface: the API
549
+ * speaks the generic chat template with `reasoning_effort` support and rejects
550
+ * the `developer` role and `store` param.
551
+ */
552
+ export declare const YOLO_AUTO_STATIC_MODELS: readonly ModelSpec<"openai-completions">[];
553
+ export interface YoloAutoModelManagerConfig {
554
+ apiKey?: string;
555
+ baseUrl?: string;
556
+ fetch?: FetchImpl;
557
+ }
558
+ /**
559
+ * Yolo-Auto model manager: OpenAI-compatible chat completions at the
560
+ * flat-rate `deepseek-flash-v4` endpoint. Live `/v1/models` discovery replaces the
561
+ * bundled seed once a key is stored or present in `YOLO_AUTO_API_KEY`. Models
562
+ * the provider adds later inherit metadata (reasoning, thinking, context)
563
+ * from the global bundled reference index, so new ids work without a per-id
564
+ * code change.
565
+ */
566
+ export declare function yoloAutoModelManagerOptions(config?: YoloAutoModelManagerConfig): ModelManagerOptions<"openai-completions">;
526
567
  export interface QwenPortalModelManagerConfig {
527
568
  apiKey?: string;
528
569
  baseUrl?: string;
@@ -121,8 +121,8 @@ export interface Usage {
121
121
  total: number;
122
122
  };
123
123
  }
124
- export type OpenAIReasoningFormat = "openai" | "openrouter" | "zai" | "kimi" | "qwen" | "qwen-chat-template";
125
- export type OpenAIReasoningDisableMode = "omit" | "lowest-effort" | "none-effort" | "openrouter-enabled-false" | "venice-disable-thinking" | "zai-thinking-disabled" | "qwen-enable-thinking-false" | "qwen-template-false";
124
+ export type OpenAIReasoningFormat = "openai" | "openrouter" | "zai" | "kimi" | "qwen" | "qwen-chat-template" | "chat-template";
125
+ export type OpenAIReasoningDisableMode = "omit" | "lowest-effort" | "none-effort" | "openrouter-enabled-false" | "venice-disable-thinking" | "zai-thinking-disabled" | "qwen-enable-thinking-false" | "qwen-template-false" | "chat-template-thinking-false";
126
126
  export type OpenAIStreamMarkupHealingPattern = "kimi" | "dsml" | "qwen" | "thinking";
127
127
  /**
128
128
  * Compatibility settings for openai-completions API.
@@ -705,12 +705,15 @@ export interface TokenCost {
705
705
  cacheWrite: number;
706
706
  }
707
707
  /**
708
- * Rates applied to the full request when its prompt exceeds `inputThreshold`.
709
- * Prompt input is the sum of uncached, cached-read, cache-write, and
710
- * provider-orchestration input tokens.
708
+ * Rates applied to the full request when its prompt exceeds `inputThreshold`,
709
+ * or reaches it when `inputThresholdInclusive` is true. Prompt input is the
710
+ * sum of uncached, cached-read, cache-write, and provider-orchestration input
711
+ * tokens.
711
712
  */
712
713
  export interface LongContextTokenCost extends TokenCost {
713
714
  inputThreshold: number;
715
+ /** Whether the long-context tier starts exactly at `inputThreshold`. */
716
+ inputThresholdInclusive?: boolean;
714
717
  }
715
718
  /** Base token rates plus an optional long-context tier. */
716
719
  export interface ModelCost extends TokenCost {
@@ -21,6 +21,15 @@ export interface EffortVariantFamily {
21
21
  * when it equals the logical id).
22
22
  */
23
23
  members: readonly string[];
24
+ /**
25
+ * Preferred default wire id: overrides the member-order default for the
26
+ * collapsed spec's `requestModelId` when this member is live. Lets a
27
+ * mandatory-reasoning family advertise a canonical tier that is not its
28
+ * numeric floor (Cursor Grok 4.5/4.6 default to `-medium`, the only tier the
29
+ * Start plan serves; the `-low` floor is refused). Ignored when absent from
30
+ * the input or retired.
31
+ */
32
+ defaultMember?: string;
24
33
  /**
25
34
  * Wire ids upstream no longer serves (e.g. a deployment killed while
26
35
  * discovery still advertises it). Fresh collapsing never routes to them,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-catalog",
4
- "version": "18.0.3",
4
+ "version": "18.0.5",
5
5
  "description": "Model catalog for omp: bundled model database, provider discovery descriptors, model identity, classification, and equivalence",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Stencil Labs, Inc.",
@@ -34,11 +34,11 @@
34
34
  "gen:proto": "bun scripts/generate-protocols.ts"
35
35
  },
36
36
  "dependencies": {
37
- "@oh-my-pi/omptype": "18.0.3",
38
- "@oh-my-pi/pi-utils": "18.0.3"
37
+ "@oh-my-pi/omptype": "18.0.5",
38
+ "@oh-my-pi/pi-utils": "18.0.5"
39
39
  },
40
40
  "devDependencies": {
41
- "@oh-my-pi/pi-ai": "18.0.3",
41
+ "@oh-my-pi/pi-ai": "18.0.5",
42
42
  "@types/bun": "^1.3.14"
43
43
  },
44
44
  "engines": {
@@ -94,6 +94,8 @@ function resolveReasoningDisableMode(
94
94
  return "qwen-enable-thinking-false";
95
95
  case "qwen-chat-template":
96
96
  return "qwen-template-false";
97
+ case "chat-template":
98
+ return "chat-template-thinking-false";
97
99
  default:
98
100
  return "lowest-effort";
99
101
  }
@@ -22,6 +22,7 @@ const DEFAULT_MODEL_PROVIDER_ORDER = [
22
22
  "fireworks",
23
23
  "cerebras",
24
24
  "baseten",
25
+ "deepinfra",
25
26
  "openrouter",
26
27
  "aimlapi",
27
28
  "together",
@@ -492,9 +492,17 @@ function mergeDynamicModel<TApi extends Api>(existingModel: Model<TApi>, dynamic
492
492
  // dynamic discovery also pre-applies the correct image fallback for omitted
493
493
  // `supports.vision`, so its explicit `false` must not be OR-upgraded by the
494
494
  // canonical bundled model.
495
+ // DeepInfra's discovery is authoritative (`dynamicModelsAuthoritative`) and
496
+ // its `vision`/`vlm` tags are the catalog's whole truth for modality. Every
497
+ // row shares the single DeepInfra endpoint, so `endpointChanged` never fires
498
+ // there: without this carve-out a model that dropped those tags would keep
499
+ // the bundled reference's image support and the agent would go on sending
500
+ // images to a now text-only route.
495
501
  const endpointChanged = existingModel.baseUrl !== dynamicModel.baseUrl;
496
502
  const dynamicInputAuthoritative =
497
- endpointChanged || (existingModel.provider === "github-copilot" && dynamicModel.provider === "github-copilot");
503
+ endpointChanged ||
504
+ (existingModel.provider === "github-copilot" && dynamicModel.provider === "github-copilot") ||
505
+ (existingModel.provider === "deepinfra" && dynamicModel.provider === "deepinfra");
498
506
  const supportsImage = dynamicInputAuthoritative
499
507
  ? dynamicModel.input.includes("image")
500
508
  : existingModel.input.includes("image") || dynamicModel.input.includes("image");
@@ -329,6 +329,14 @@ function getModelDefinedEfforts<TApi extends Api>(
329
329
  spec: ModelSpec<TApi>,
330
330
  compat: CompatOf<TApi>,
331
331
  ): readonly Effort[] | undefined {
332
+ if (
333
+ isOpenAICompatReasoningApi(spec.api) &&
334
+ isChatTemplateThinkingFormat(compat) &&
335
+ spec.thinking?.mode === "effort" &&
336
+ spec.thinking.efforts.length > 0
337
+ ) {
338
+ return spec.thinking.efforts;
339
+ }
332
340
  if (isGlm53ReasoningEffortModelId(spec.id)) {
333
341
  // GLM-5.3+ exposes a uniform wire-exact low/high/max ladder on every
334
342
  // host — unlike GLM-5.2, whose reasoning_effort dialect is
@@ -519,6 +527,9 @@ function isQwenTemplateReasoningEffortCompat(compat: CompatOf<Api>): boolean {
519
527
  compat !== undefined && "qwenTemplateReasoningEffort" in compat && compat.qwenTemplateReasoningEffort === true
520
528
  );
521
529
  }
530
+ function isChatTemplateThinkingFormat(compat: CompatOf<Api>): boolean {
531
+ return compat !== undefined && "thinkingFormat" in compat && compat.thinkingFormat === "chat-template";
532
+ }
522
533
 
523
534
  function inferDetectedEffortMap<TApi extends Api>(
524
535
  spec: ModelSpec<TApi>,
@@ -559,15 +570,19 @@ function isSakanaFuguReasoningModel<TApi extends Api>(spec: ModelSpec<TApi>): bo
559
570
  * `opencode-zen`) reason through the wire-exact `low`/`high`/`max` ladder with
560
571
  * mandatory thinking: the gateway rejects `minimal`/`medium`/`xhigh`
561
572
  * (`[1210] ... please use low, high, or max`), the same dialect it already
562
- * serves for GLM-5.3 and Kimi K3. Other hosts proxying an `ox-alpha` SKU
563
- * (Kilo, NanoGPT, Venice, OpenRouter) expose their own vocabularies and are
564
- * left untouched. See issue #9349.
573
+ * serves for GLM-5.3 and Kimi K3. The SKU also ships under unrelated aliased
574
+ * ids (`opencode-zen/x-preview-f-free`), so the stencil display name is
575
+ * matched as well. Other hosts proxying an `ox-alpha` SKU (Kilo, NanoGPT,
576
+ * Venice, OpenRouter) expose their own vocabularies and are left untouched.
577
+ * See issue #9349.
565
578
  */
566
579
  function isOpenCodeGatewayOxAlphaModel<TApi extends Api>(spec: ModelSpec<TApi>): boolean {
567
- return (
568
- (spec.provider === "opencode-go" || spec.provider === "opencode-zen") &&
569
- /(?:^|\/)ox-alpha(?:-|$)/i.test(bareModelId(spec.id))
570
- );
580
+ if (spec.provider !== "opencode-go" && spec.provider !== "opencode-zen") return false;
581
+ if (/(?:^|\/)ox-alpha(?:-|$)/i.test(bareModelId(spec.id))) return true;
582
+ // Aliased gateway ids surface the same SKU under an unrelated id; the
583
+ // display name ("Ox Alpha …") is the stable cross-id signal. Anchored on
584
+ // both sides so "Box Alpha"/"Ox Alphabet" cannot false-positive.
585
+ return /\box[ _-]?alpha\b/i.test(spec.name ?? "");
571
586
  }
572
587
 
573
588
  function isDeepseekReasoningModel<TApi extends Api>(spec: ModelSpec<TApi>): boolean {
@@ -923,3 +938,25 @@ export function minimumSupportedEffort<TApi extends Api>(model: ApiModel<TApi>):
923
938
  }
924
939
  return efforts[0];
925
940
  }
941
+
942
+ /**
943
+ * Clamp target for effort-less requests on `thinking.requiresEffort` models:
944
+ * the effort whose wire route equals the model's default wire id
945
+ * (`requestModelId`), so a collapsed row clamps to the tier it already
946
+ * advertises as its default rather than the numerically lowest supported tier
947
+ * (e.g. Cursor Grok 4.5/4.6 default to `medium`, the only tier the Start plan
948
+ * serves). Falls back to {@link minimumSupportedEffort} when no route matches
949
+ * the default id — families whose default already is the minimum, or that
950
+ * expose no routing, are unaffected.
951
+ */
952
+ export function defaultSupportedEffort<TApi extends Api>(model: ApiModel<TApi>): Effort | undefined {
953
+ const routing = model.thinking?.effortRouting;
954
+ const defaultWireId = model.requestModelId;
955
+ if (routing !== undefined && defaultWireId !== undefined) {
956
+ const efforts = model.thinking?.efforts;
957
+ for (const effort of THINKING_EFFORTS) {
958
+ if (efforts?.includes(effort) && routing[effort] === defaultWireId) return effort;
959
+ }
960
+ }
961
+ return minimumSupportedEffort(model);
962
+ }