@oh-my-pi/pi-catalog 15.12.2 → 15.12.4

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
+ ## [15.12.4] - 2026-06-13
6
+
7
+ ### Added
8
+
9
+ - Added bundled Fireworks models `deepseek-v4-flash`, `kimi-k2.7-code`, `minimax-m2.5`, `minimax-m3`, `nemotron-3-ultra-nvfp4`, `qwen3.6-plus`, and `qwen3.7-plus`
10
+ - Changed
11
+
12
+ ### Changed
13
+
14
+ - Model `contextWindow`/`maxTokens` are now `number | null`; discovery emits `null` when a provider reports no limit, replacing the `222222`/`8888` (`UNK_CONTEXT_WINDOW`/`UNK_MAX_TOKENS`) sentinels (now removed). Bundled `models.json` unknown limits are `null`.
15
+ - Changed the `github-copilot` model context window to `524288` tokens
16
+ - Changed Fireworks model discovery to source the control-plane `List Models` API (`GET /v1/accounts/fireworks/models?filter=supports_serverless=true`) instead of the OpenAI-compatible `/v1/models` inference listing. The inference endpoint returns a sparse, account-specific subset that omits on-demand serverless models (e.g. `kimi-k2.7-code`), so newly published serverless models stayed invisible in the picker until hand-added to the bundled catalog. The control-plane catalog enumerates every serverless model with capability metadata (`supportsServerless`/`supportsTools`/`supportsImageInput`/`contextLength`/`displayName`), paginated and filtered to tool-capable `READY` entries, then merged with bundled/models.dev references — the Kimi K2 max-output clamp and DeepSeek V4 thinking-toggle strip are preserved, and unbundled models default to reasoning so `buildModel` derives the Fireworks effort map. New serverless releases now surface automatically with no catalog edits.
17
+
18
+ ### Fixed
19
+
20
+ - Filled missing `contextWindow` and `maxTokens` in generated `models.json` for proxy/reseller variants by inheriting limits from canonical-family and segment-reference models
21
+ - Ignored zero-cost `x-ai` subscription entries as reference sources when backfilling limits so inflated values are not propagated
22
+ - Fixed the model cache opening with `PRAGMA journal_mode=WAL` before `PRAGMA busy_timeout`, so concurrent omp startups could crash inside `getDb()` on `SQLITE_BUSY` during WAL recovery instead of waiting through the transient lock. The busy handler is now installed before the first lock-taking statement ([#2421](https://github.com/can1357/oh-my-pi/issues/2421)).
23
+
5
24
  ## [15.11.8] - 2026-06-12
6
25
 
7
26
  ### Fixed
@@ -165,6 +165,7 @@ export declare function isFireworksKimiK2ModelId(modelId: string): boolean;
165
165
  * on Fireworks-backed providers, leaving every other model untouched.
166
166
  */
167
167
  export declare function clampFireworksKimiMaxTokens(modelId: string, candidate: number): number;
168
+ export declare function clampFireworksKimiMaxTokens(modelId: string, candidate: number | null): number | null;
168
169
  /**
169
170
  * Fireworks DeepSeek V4 accepts effort via `reasoning_effort` but rejects the
170
171
  * DeepSeek-native binary `thinking` toggle when both are present.
@@ -343,7 +344,6 @@ export interface AnthropicModelManagerConfig {
343
344
  fetch?: FetchImpl;
344
345
  }
345
346
  export declare function anthropicModelManagerOptions(config?: AnthropicModelManagerConfig): ModelManagerOptions<"anthropic-messages">;
346
- export { UNK_CONTEXT_WINDOW, UNK_MAX_TOKENS } from "./discovery-constants";
347
347
  /** Describes how to map models.dev API data for a single provider. */
348
348
  export interface ModelsDevProviderDescriptor {
349
349
  /** Key in the models.dev API response JSON (e.g., "anthropic", "amazon-bedrock") */
@@ -386,3 +386,4 @@ export interface ModelsDevProviderDescriptor {
386
386
  export declare function mapModelsDevToModels(data: Record<string, unknown>, descriptors: readonly ModelsDevProviderDescriptor[]): ModelSpec<Api>[];
387
387
  /** All provider descriptors for models.dev data mapping in generate-models.ts. */
388
388
  export declare const MODELS_DEV_PROVIDER_DESCRIPTORS: readonly ModelsDevProviderDescriptor[];
389
+ export {};
@@ -373,8 +373,8 @@ export interface Model<TApi extends Api = Api> {
373
373
  };
374
374
  /** Premium Copilot requests charged per user-initiated request (defaults to 1). */
375
375
  premiumMultiplier?: number;
376
- contextWindow: number;
377
- maxTokens: number;
376
+ contextWindow: number | null;
377
+ maxTokens: number | null;
378
378
  /**
379
379
  * When `true`, providers MUST omit `max_output_tokens` (Responses) /
380
380
  * `max_tokens` / `max_completion_tokens` (Completions) from the outbound
@@ -1,6 +1,9 @@
1
1
  export { isRecord } from "@oh-my-pi/pi-utils";
2
2
  export declare function toNumber(value: unknown): number | undefined;
3
3
  export declare function toPositiveNumber(value: unknown, fallback: number): number;
4
+ export declare function toPositiveNumber(value: unknown, fallback: number | null): number | null;
5
+ /** Positive finite number, or `null` when the value is missing/non-positive. */
6
+ export declare function toPositiveNumberOrNull(value: unknown): number | null;
4
7
  export declare function toBoolean(value: unknown): boolean | undefined;
5
8
  export declare function isAnthropicOAuthToken(key: string): boolean;
6
9
  /**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-catalog",
4
- "version": "15.12.2",
4
+ "version": "15.12.4",
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": "Can Boluk",
@@ -34,11 +34,11 @@
34
34
  },
35
35
  "dependencies": {
36
36
  "@bufbuild/protobuf": "^2.12.0",
37
- "@oh-my-pi/pi-utils": "15.12.2",
38
- "zod": "4.4.3"
37
+ "@oh-my-pi/pi-utils": "15.12.4",
38
+ "zod": "^4"
39
39
  },
40
40
  "devDependencies": {
41
- "@oh-my-pi/pi-ai": "15.12.2",
41
+ "@oh-my-pi/pi-ai": "15.12.4",
42
42
  "@types/bun": "^1.3.14"
43
43
  },
44
44
  "engines": {
@@ -1,4 +1,4 @@
1
- import * as z from "zod/v4";
1
+ import { z } from "zod/v4";
2
2
  import type { ModelSpec } from "../types";
3
3
  import { toPositiveNumber } from "../utils";
4
4
  import { ANTIGRAVITY_VARIANT_COLLAPSE_TABLE, collapseEffortVariants } from "../variant-collapse";
@@ -1,4 +1,4 @@
1
- import * as z from "zod/v4";
1
+ import { z } from "zod/v4";
2
2
  import type { ModelSpec } from "../types";
3
3
  import { isRecord } from "../utils";
4
4
  import { CODEX_BASE_URL, OPENAI_HEADER_VALUES, OPENAI_HEADERS } from "../wire/codex";
@@ -1,6 +1,6 @@
1
1
  import * as http2 from "node:http2";
2
2
  import { create, fromBinary, toBinary } from "@bufbuild/protobuf";
3
- import * as z from "zod/v4";
3
+ import { z } from "zod/v4";
4
4
  import { getBundledModels } from "../models";
5
5
  import { toModelSpec } from "../provider-models/bundled-references";
6
6
  import type { Model, ModelSpec } from "../types";
@@ -1,7 +1,6 @@
1
- import * as z from "zod/v4";
1
+ import { z } from "zod/v4";
2
2
  import { getBundledModels } from "../models";
3
3
  import { toModelSpec } from "../provider-models/bundled-references";
4
- import { UNK_CONTEXT_WINDOW, UNK_MAX_TOKENS } from "../provider-models/discovery-constants";
5
4
  import type { FetchImpl, Model, ModelSpec } from "../types";
6
5
 
7
6
  const GOOGLE_GENERATIVE_AI_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
@@ -148,7 +147,9 @@ function normalizeBaseUrl(baseUrl?: string): string {
148
147
  return value.replace(/\/+$/, "");
149
148
  }
150
149
 
151
- function normalizePositiveInt(value: number | undefined, fallback: number): number {
150
+ function normalizePositiveInt(value: number | undefined, fallback: number): number;
151
+ function normalizePositiveInt(value: number | undefined, fallback: number | null): number | null;
152
+ function normalizePositiveInt(value: number | undefined, fallback: number | null): number | null {
152
153
  if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
153
154
  return fallback;
154
155
  }
@@ -178,8 +179,8 @@ function normalizeModel(
178
179
  }
179
180
 
180
181
  const reference = bundledById.get(id);
181
- const contextWindow = normalizePositiveInt(item.inputTokenLimit, reference?.contextWindow ?? UNK_CONTEXT_WINDOW);
182
- const maxTokens = normalizePositiveInt(item.outputTokenLimit, reference?.maxTokens ?? UNK_MAX_TOKENS);
182
+ const contextWindow = normalizePositiveInt(item.inputTokenLimit, reference?.contextWindow ?? null);
183
+ const maxTokens = normalizePositiveInt(item.outputTokenLimit, reference?.maxTokens ?? null);
183
184
  const name = normalizeModelName(item.displayName, reference?.name ?? id);
184
185
 
185
186
  if (reference) {
@@ -1,5 +1,4 @@
1
- import * as z from "zod/v4";
2
- import { UNK_CONTEXT_WINDOW, UNK_MAX_TOKENS } from "../provider-models/discovery-constants";
1
+ import { z } from "zod/v4";
3
2
  import type { Api, FetchImpl, ModelSpec, Provider } from "../types";
4
3
 
5
4
  const MODELS_PATH = "/models";
@@ -165,8 +164,8 @@ export async function fetchOpenAICompatibleModels<TApi extends Api>(
165
164
  reasoning: false,
166
165
  input: ["text"],
167
166
  cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
168
- contextWindow: UNK_CONTEXT_WINDOW,
169
- maxTokens: UNK_MAX_TOKENS,
167
+ contextWindow: null,
168
+ maxTokens: null,
170
169
  };
171
170
 
172
171
  // `mapModel` returning null skips the entry (documented contract); only a
@@ -61,10 +61,10 @@ const FAMILY_EXTRACTION_PATTERNS = [
61
61
  function shouldReplaceReference(existing: Model<Api> | undefined, candidate: Model<Api>): boolean {
62
62
  if (!existing) return true;
63
63
  if (candidate.contextWindow !== existing.contextWindow) {
64
- return candidate.contextWindow > existing.contextWindow;
64
+ return (candidate.contextWindow ?? 0) > (existing.contextWindow ?? 0);
65
65
  }
66
66
  if (candidate.maxTokens !== existing.maxTokens) {
67
- return candidate.maxTokens > existing.maxTokens;
67
+ return (candidate.maxTokens ?? 0) > (existing.maxTokens ?? 0);
68
68
  }
69
69
  return existing.provider !== "openai" && candidate.provider === "openai";
70
70
  }
@@ -34,10 +34,10 @@ export function isZeroCostXaiOAuthReference(candidate: Model<Api>): boolean {
34
34
  function shouldReplaceReference(existing: Model<Api> | undefined, candidate: Model<Api>): boolean {
35
35
  if (!existing) return true;
36
36
  if (candidate.contextWindow !== existing.contextWindow) {
37
- return candidate.contextWindow > existing.contextWindow;
37
+ return (candidate.contextWindow ?? 0) > (existing.contextWindow ?? 0);
38
38
  }
39
39
  if (candidate.maxTokens !== existing.maxTokens) {
40
- return candidate.maxTokens > existing.maxTokens;
40
+ return (candidate.maxTokens ?? 0) > (existing.maxTokens ?? 0);
41
41
  }
42
42
  const existingHasCachePricing = existing.cost.cacheRead > 0 || existing.cost.cacheWrite > 0;
43
43
  const candidateHasCachePricing = candidate.cost.cacheRead > 0 || candidate.cost.cacheWrite > 0;
@@ -7,10 +7,12 @@ import { getModelDbPath } from "@oh-my-pi/pi-utils";
7
7
  import type { Api, Model, ModelSpec } from "./types";
8
8
 
9
9
  // Rows persist ModelSpec JSON (sparse `compat`, never the resolved record);
10
- // the model manager rebuilds via `buildModel` on load. v5 invalidates rows
11
- // predating effort-tier variant collapsing (raw `-low`/`-high`/`-thinking`
12
- // member ids); v4 dropped the pre-efforts ThinkingConfig shape.
13
- const CACHE_SCHEMA_VERSION = 5;
10
+ // the model manager rebuilds via `buildModel` on load. v6 invalidates rows
11
+ // that may contain the retired unknown-limit sentinels (222222/8888); v5
12
+ // invalidated rows predating effort-tier variant collapsing (raw
13
+ // `-low`/`-high`/`-thinking` member ids); v4 dropped the pre-efforts
14
+ // ThinkingConfig shape.
15
+ const CACHE_SCHEMA_VERSION = 6;
14
16
 
15
17
  interface CacheRow {
16
18
  provider_id: string;
@@ -51,8 +53,10 @@ function getDb(dbPath?: string): Database {
51
53
  sharedDb.close();
52
54
  }
53
55
  const db = new Database(resolvedPath, { create: true });
54
- db.run("PRAGMA journal_mode = WAL");
56
+ // Install the busy handler BEFORE any lock-taking statement. See
57
+ // https://github.com/can1357/oh-my-pi/issues/2421.
55
58
  db.run("PRAGMA busy_timeout = 3000");
59
+ db.run("PRAGMA journal_mode = WAL");
56
60
  db.run(`
57
61
  CREATE TABLE IF NOT EXISTS model_cache (
58
62
  provider_id TEXT PRIMARY KEY,
@@ -363,11 +363,13 @@ function preferDiscoveryName(discoveryName: string, fallbackName: string, modelI
363
363
  return normalizedDiscoveryName;
364
364
  }
365
365
 
366
- function preferDiscoveryLimit(discoveryLimit: number, fallbackLimit: number): number {
367
- if (!Number.isFinite(discoveryLimit) || discoveryLimit <= 0) {
366
+ function preferDiscoveryLimit(discoveryLimit: number, fallbackLimit: number): number;
367
+ function preferDiscoveryLimit(discoveryLimit: number | null, fallbackLimit: number | null): number | null;
368
+ function preferDiscoveryLimit(discoveryLimit: number | null, fallbackLimit: number | null): number | null {
369
+ if (discoveryLimit === null || !Number.isFinite(discoveryLimit) || discoveryLimit <= 0) {
368
370
  return fallbackLimit;
369
371
  }
370
- if (discoveryLimit === 4096 && fallbackLimit > discoveryLimit) {
372
+ if (discoveryLimit === 4096 && fallbackLimit !== null && fallbackLimit > discoveryLimit) {
371
373
  return fallbackLimit;
372
374
  }
373
375
  return discoveryLimit;
@@ -428,11 +430,11 @@ function isModelLike(value: unknown): value is ModelSpec<Api> {
428
430
  }
429
431
  // Finite positive: NaN > 0 is false, +Infinity < Infinity is false.
430
432
  const cw = v.contextWindow;
431
- if (typeof cw !== "number" || !(cw > 0 && cw < Infinity)) {
433
+ if (cw !== null && (typeof cw !== "number" || !(cw > 0 && cw < Infinity))) {
432
434
  return false;
433
435
  }
434
436
  const mt = v.maxTokens;
435
- if (typeof mt !== "number" || !(mt > 0 && mt < Infinity)) {
437
+ if (mt !== null && (typeof mt !== "number" || !(mt > 0 && mt < Infinity))) {
436
438
  return false;
437
439
  }
438
440
  return true;