@oh-my-pi/pi-catalog 17.0.7 → 17.0.9

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,43 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [17.0.9] - 2026-07-23
6
+
7
+ ### Changed
8
+
9
+ - Renamed `codex-auto-review` model to `GPT-5.3 Codex Spark` with updated pricing and capabilities
10
+ - Removed image input support from GPT-5.3 Codex Spark (text-only)
11
+ - Reduced GPT-5.3 Codex Spark context window from 272K to 128K tokens
12
+ - Changed GPT-5.3 Codex Spark thinking efforts from `["minimal", "low", "medium", "high", "xhigh"]` to `["low", "medium", "high", "xhigh"]`
13
+ - Updated pricing for multiple AI models across providers (costs adjusted in models.json)
14
+ - Reduced max output tokens for an unspecified model from 16384 to 8192
15
+ - Added image input support to Venice AI text model
16
+
17
+ ## [17.0.8] - 2026-07-22
18
+
19
+ ### Added
20
+
21
+ - Added support for several new models across multiple providers, including MiniMax M3, Gemini 3.5 Flash Lite, Gemini 3.6 Flash (with thinking support), Hy3, Doubao-Seed-Character, LongCat 2.0, Laguna S 2.1 (free and paid tiers), Qwen 3.6 35B A3B, SWE-1.6 Slow (devin agent catalog), and XiaomiMiMo/MiMo-V2.5.
22
+
23
+ ### Changed
24
+
25
+ - Updated Grok 4.5 API type to "openai-responses" and updated "o3-mini" to support thinking capabilities with the "kimi" thinking format.
26
+ - Renamed OpenRouter-specific models and routers to include "OpenRouter" in their names (e.g., "OpenRouter Auto Router (Beta)", "OpenRouter Body Builder (beta)", and "OpenRouter Pareto Code Router").
27
+ - Updated context window sizes, costs, and token limits for numerous models.
28
+
29
+ ### Fixed
30
+
31
+ - Fixed an issue where GPT-5.6 Codex SKUs lost usable context window capacity due to dynamic discovery values overwriting bundled limits.
32
+ - Fixed OpenAI Codex discovery dropping account-listed ChatGPT-only models (such as GPT-5.3 Codex Spark) when they are unavailable through the public API.
33
+ - Fixed Codex catalog discovery hiding models when multiple OAuth accounts are configured by independently fetching and merging catalogs from all accounts.
34
+ - Fixed cached models reusing a bundled request model (such as GitHub Copilot long-context variants) being incorrectly flagged as unrestorable and dropped after a restart.
35
+ - Fixed LM Studio discovery reporting a model's theoretical maximum context length instead of the actual loaded context window size of the running instance.
36
+
37
+ ### Removed
38
+
39
+ - Removed several deprecated model families from the devin catalog, including Claude Fable 5, Claude Opus 4.6/4.7, Claude Sonnet 4.6/5, DeepSeek V4 Pro, Gemini 3.1 Pro, Gemini 3.5 Flash, GLM-5.2, SWE-1.6, and Nemotron 3 Ultra.
40
+ - Removed GPT-5 through GPT-5.3 Codex variants and GPT-5.4 nano from the openai-codex catalog.
41
+
5
42
  ## [17.0.6] - 2026-07-20
6
43
 
7
44
  ### Added
@@ -8,6 +8,8 @@ interface CacheEntry<TApi extends Api = Api> {
8
8
  headerOmittedModelIds: readonly string[];
9
9
  /** Header-bearing model ids that cannot be rebuilt from the static source. */
10
10
  unrestorableHeaderModelIds: readonly string[];
11
+ /** Whether unrestorable markers predate request-model header matching. */
12
+ legacyHeaderRestoreMarkers: boolean;
11
13
  /**
12
14
  * Hash of the static catalog slice that was merged into `models` when this
13
15
  * row was written. `resolveProviderModels` compares against the current
@@ -1,9 +1,28 @@
1
1
  import type { DevinModelDiscoveryOptions } from "../discovery/devin.js";
2
2
  import type { ModelManagerOptions } from "../model-manager.js";
3
3
  import type { FetchImpl } from "../types.js";
4
- export interface OpenAICodexModelManagerConfig {
5
- accessToken?: string;
4
+ /** One Codex OAuth account to fetch a catalog for. */
5
+ export interface OpenAICodexAccount {
6
+ /** OAuth access token used for `Authorization: Bearer ...`. */
7
+ accessToken: string;
8
+ /** ChatGPT account id sent as the `chatgpt-account-id` header. */
6
9
  accountId?: string;
10
+ }
11
+ export interface OpenAICodexModelManagerConfig {
12
+ /**
13
+ * Resolves every configured Codex OAuth account at discovery time. Codex
14
+ * discovery is account-scoped — a model can be available to one account and
15
+ * absent from another — so each account's `/models` endpoint is fetched
16
+ * independently and the results unioned by id. Without this, discovery would
17
+ * surface only the account it happened to resolve and, being authoritative,
18
+ * prune every model the other accounts expose (#6265).
19
+ *
20
+ * Returns `null` to abort discovery entirely (e.g. an account's credential
21
+ * failed to refresh): a partial account set would be cached as the complete
22
+ * authoritative catalog and hide the missing account's models, so the caller
23
+ * keeps the previous/bundled catalog instead.
24
+ */
25
+ resolveAccounts?: () => Promise<readonly OpenAICodexAccount[] | null>;
7
26
  clientVersion?: string;
8
27
  fetch?: FetchImpl;
9
28
  }
@@ -53,9 +53,9 @@ export interface EffortVariantFamily {
53
53
  export interface VariantCollapseTable {
54
54
  families: readonly EffortVariantFamily[];
55
55
  }
56
- /** `google-antigravity` (daily-cloudcode-pa): Gemini 3.x on the budget transport. */
56
+ /** `google-antigravity` Gemini families, using each generation's native transport. */
57
57
  export declare const ANTIGRAVITY_VARIANT_COLLAPSE_TABLE: VariantCollapseTable;
58
- /** `google-gemini-cli` (cloudcode-pa): Gemini 3.x on the level transport (official CLI parity). */
58
+ /** `google-gemini-cli` Gemini families on the official CLI's level transport. */
59
59
  export declare const GEMINI_CLI_VARIANT_COLLAPSE_TABLE: VariantCollapseTable;
60
60
  export declare const DEVIN_VARIANT_COLLAPSE_TABLE: VariantCollapseTable;
61
61
  /** Provider id → hand collapse table. The CCA providers diverge on thinking transport. */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-catalog",
4
- "version": "17.0.7",
4
+ "version": "17.0.9",
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,12 +34,12 @@
34
34
  },
35
35
  "dependencies": {
36
36
  "@bufbuild/protobuf": "^2.12.1",
37
- "@oh-my-pi/pi-utils": "17.0.7",
37
+ "@oh-my-pi/pi-utils": "17.0.9",
38
38
  "arktype": "2.2.3",
39
39
  "zod": "^4"
40
40
  },
41
41
  "devDependencies": {
42
- "@oh-my-pi/pi-ai": "17.0.7",
42
+ "@oh-my-pi/pi-ai": "17.0.9",
43
43
  "@types/bun": "^1.3.14"
44
44
  },
45
45
  "engines": {
@@ -8,11 +8,11 @@ const DEFAULT_MODEL_LIST_PATHS = ["/codex/models", "/models"] as const;
8
8
  const DEFAULT_CONTEXT_WINDOW = 272_000;
9
9
  const DEFAULT_MAX_TOKENS = 128_000;
10
10
  /**
11
- * GPT-5.6 luna/sol/terra hard context capacity. Codex discovery omits
12
- * `context_window` for these SKUs, so the generic {@link DEFAULT_CONTEXT_WINDOW}
13
- * (272000) would understate the real window OpenAI's Codex model registry
14
- * declares context_window = max_context_window = 372000 (#5705). Used as the
15
- * fallback only when upstream reports no value.
11
+ * GPT-5.6 luna/sol/terra hard context capacity. OpenAI's Codex model registry
12
+ * declares context_window = max_context_window = 372000 (#5705), but Codex
13
+ * discovery under-reports it omitting the field for some accounts and
14
+ * actively returning 272000 for others (#6259). Applied as a floor for these
15
+ * SKUs so the reported/absent value never regresses the real window.
16
16
  */
17
17
  const GPT_5_6_CONTEXT_WINDOW = 372_000;
18
18
  const CODEX_REMOTE_COMPACTION = {
@@ -33,7 +33,7 @@ const codexModelEntrySchema = type({
33
33
  "default_reasoning_level?": "unknown",
34
34
  "supported_reasoning_levels?": "unknown",
35
35
  "input_modalities?": "unknown",
36
- "supported_in_api?": "unknown",
36
+ "visibility?": "unknown",
37
37
  "priority?": "unknown",
38
38
  "prefer_websockets?": "unknown",
39
39
  "use_responses_lite?": "unknown",
@@ -217,20 +217,24 @@ function normalizeCodexModelEntry(entry: unknown, baseUrl: string): NormalizedCo
217
217
  return null;
218
218
  }
219
219
 
220
- const supportedInApi = toBoolean(payload.supported_in_api);
221
- if (supportedInApi === false) {
220
+ const visibility = toNonEmptyString(payload.visibility)?.toLowerCase();
221
+ if (visibility === "hide" || visibility === "hidden") {
222
222
  return null;
223
223
  }
224
224
 
225
225
  const name = toNonEmptyString(payload.display_name) ?? slug;
226
- // Codex discovery omits `context_window` for GPT-5.6 luna/sol/terra; the
227
- // generic 272000 fallback understates their real 372000 window (#5705).
226
+ // GPT-5.6 luna/sol/terra have a 372000 hard window, but Codex discovery
227
+ // under-reports it: for some accounts the field is omitted, for others it is
228
+ // actively returned as 272000 (#6259). Treat GPT_5_6_CONTEXT_WINDOW as a
229
+ // floor for these SKUs so neither the omission nor the active under-report
230
+ // regresses the real capacity; other models honor the reported value with
231
+ // the generic 272000 fallback.
228
232
  const parsed = parseKnownModel(slug);
229
- const fallbackContextWindow =
230
- parsed.family === "openai" && semverEqual(parsed.version, "5.6")
231
- ? GPT_5_6_CONTEXT_WINDOW
232
- : DEFAULT_CONTEXT_WINDOW;
233
- const contextWindow = toPositiveInt(payload.context_window) ?? fallbackContextWindow;
233
+ const isGpt56 = parsed.family === "openai" && semverEqual(parsed.version, "5.6");
234
+ const reportedContextWindow = toPositiveInt(payload.context_window);
235
+ const contextWindow = isGpt56
236
+ ? Math.max(GPT_5_6_CONTEXT_WINDOW, reportedContextWindow ?? 0)
237
+ : (reportedContextWindow ?? DEFAULT_CONTEXT_WINDOW);
234
238
  const maxTokens = Math.min(DEFAULT_MAX_TOKENS, contextWindow);
235
239
  const reasoning = supportsReasoning(payload.default_reasoning_level, payload.supported_reasoning_levels);
236
240
  const input = normalizeInputModalities(payload.input_modalities);
@@ -21,6 +21,7 @@ import type { Api, Model, ModelSpec } from "./types";
21
21
  // effort-tier variant collapsing (raw `-low`/`-high`/`-thinking` member ids);
22
22
  // v4 dropped the pre-efforts ThinkingConfig shape.
23
23
  const CACHE_SCHEMA_VERSION = 10;
24
+ const HEADER_RESTORE_VERSION = 1;
24
25
 
25
26
  interface CacheRow {
26
27
  provider_id: string;
@@ -31,6 +32,7 @@ interface CacheRow {
31
32
  models: string;
32
33
  header_omitted_model_ids: string;
33
34
  unrestorable_header_model_ids: string;
35
+ header_restore_version: number;
34
36
  }
35
37
 
36
38
  interface TableInfoRow {
@@ -46,6 +48,8 @@ interface CacheEntry<TApi extends Api = Api> {
46
48
  headerOmittedModelIds: readonly string[];
47
49
  /** Header-bearing model ids that cannot be rebuilt from the static source. */
48
50
  unrestorableHeaderModelIds: readonly string[];
51
+ /** Whether unrestorable markers predate request-model header matching. */
52
+ legacyHeaderRestoreMarkers: boolean;
49
53
  /**
50
54
  * Hash of the static catalog slice that was merged into `models` when this
51
55
  * row was written. `resolveProviderModels` compares against the current
@@ -77,6 +81,7 @@ function openDb(resolvedPath: string): Database {
77
81
  static_fingerprint TEXT NOT NULL DEFAULT '',
78
82
  header_omitted_model_ids TEXT NOT NULL DEFAULT '[]',
79
83
  unrestorable_header_model_ids TEXT NOT NULL DEFAULT '[]',
84
+ header_restore_version INTEGER NOT NULL DEFAULT 0,
80
85
  models TEXT NOT NULL
81
86
  )
82
87
  `);
@@ -121,6 +126,12 @@ function migrateCacheSchema(db: Database): void {
121
126
  if (!columns.some(column => column.name === "unrestorable_header_model_ids")) {
122
127
  db.run("ALTER TABLE model_cache ADD COLUMN unrestorable_header_model_ids TEXT NOT NULL DEFAULT '[]'");
123
128
  }
129
+ if (!columns.some(column => column.name === "header_restore_version")) {
130
+ // Existing v10 rows get 0, distinguishing markers produced by the
131
+ // old id-only header matcher from rows written after request-model
132
+ // header matching was introduced.
133
+ db.run("ALTER TABLE model_cache ADD COLUMN header_restore_version INTEGER NOT NULL DEFAULT 0");
134
+ }
124
135
  } finally {
125
136
  stmt.finalize();
126
137
  }
@@ -164,6 +175,7 @@ export function readModelCache<TApi extends Api>(
164
175
  updatedAt: row.updated_at,
165
176
  headerOmittedModelIds,
166
177
  unrestorableHeaderModelIds,
178
+ legacyHeaderRestoreMarkers: row.header_restore_version < HEADER_RESTORE_VERSION,
167
179
  staticFingerprint: row.static_fingerprint ?? "",
168
180
  };
169
181
  } finally {
@@ -226,7 +238,13 @@ export function writeModelCache<TApi extends Api>(
226
238
  for (const model of models) {
227
239
  if (hasModelHeaders(model)) {
228
240
  headerOmittedModelIds.push(model.id);
229
- if (!headersEqual(model.headers, staticById.get(model.id)?.headers)) {
241
+ // Synthesized variants (e.g. Copilot `-1m`) have no same-id static
242
+ // entry; their headers come from the `requestModelId` base. Match
243
+ // against that source too, else they are wrongly flagged
244
+ // unrestorable and dropped on the next offline read (#6037, #6284).
245
+ const staticHeaderSource =
246
+ staticById.get(model.id) ?? (model.requestModelId ? staticById.get(model.requestModelId) : undefined);
247
+ if (!headersEqual(model.headers, staticHeaderSource?.headers)) {
230
248
  unrestorableHeaderModelIds.push(model.id);
231
249
  }
232
250
  }
@@ -235,8 +253,9 @@ export function writeModelCache<TApi extends Api>(
235
253
  db.run(
236
254
  `INSERT OR REPLACE INTO model_cache (
237
255
  provider_id, version, updated_at, authoritative, static_fingerprint,
238
- header_omitted_model_ids, unrestorable_header_model_ids, models
239
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
256
+ header_omitted_model_ids, unrestorable_header_model_ids,
257
+ header_restore_version, models
258
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
240
259
  [
241
260
  providerId,
242
261
  CACHE_SCHEMA_VERSION,
@@ -245,6 +264,7 @@ export function writeModelCache<TApi extends Api>(
245
264
  staticFingerprint,
246
265
  JSON.stringify(headerOmittedModelIds),
247
266
  JSON.stringify(unrestorableHeaderModelIds),
267
+ HEADER_RESTORE_VERSION,
248
268
  JSON.stringify(cachedModels),
249
269
  ],
250
270
  );
@@ -108,15 +108,21 @@ interface CachedHeaderRestoreResult<TApi extends Api> {
108
108
  /**
109
109
  * Restore cache-omitted headers from the current static source.
110
110
  *
111
- * Dynamic-only header-bearing models cannot be reconstructed safely without
112
- * persisting arbitrary credential values; callers must refetch them online or
113
- * omit them from an offline result rather than return a broken model.
111
+ * A same-id static match is trusted only when the row did not flag the model
112
+ * unrestorable (its live headers matched static when cached). Request-model
113
+ * fallback also honors that marker for current rows. Only legacy rows written
114
+ * before request-model header matching may bypass it: their id-only writer
115
+ * necessarily marked every synthesized variant unrestorable (#6037, #6284).
116
+ * Header-bearing models without a trusted source cannot be reconstructed
117
+ * safely without persisting arbitrary credential values; callers must refetch
118
+ * them online or omit them rather than return a broken model.
114
119
  */
115
120
  function restoreCachedModelHeaders<TApi extends Api>(
116
121
  cachedModels: readonly ModelSpec<TApi>[],
117
122
  staticModels: readonly Model<TApi>[],
118
123
  headerOmittedModelIds: readonly string[],
119
124
  unrestorableHeaderModelIds: readonly string[],
125
+ legacyHeaderRestoreMarkers: boolean,
120
126
  ): CachedHeaderRestoreResult<TApi> {
121
127
  const models = passModelList<TApi>(cachedModels);
122
128
  if (headerOmittedModelIds.length === 0) {
@@ -128,11 +134,15 @@ function restoreCachedModelHeaders<TApi extends Api>(
128
134
  const unresolvedModelIds = new Set<string>();
129
135
  const restored = models.map(model => {
130
136
  if (!omittedIds.has(model.id)) return model;
131
- if (unrestorableIds.has(model.id)) {
132
- unresolvedModelIds.add(model.id);
133
- return model;
134
- }
135
- const staticModel = staticById.get(model.id);
137
+ const unrestorable = unrestorableIds.has(model.id);
138
+ // Current unrestorable markers prove that neither same-id nor request-model
139
+ // static headers matched the live model. Only the old id-only writer's
140
+ // markers may recover a synthesized variant through `requestModelId`.
141
+ const staticModel = unrestorable
142
+ ? legacyHeaderRestoreMarkers && model.requestModelId
143
+ ? staticById.get(model.requestModelId)
144
+ : undefined
145
+ : (staticById.get(model.id) ?? (model.requestModelId ? staticById.get(model.requestModelId) : undefined));
136
146
  if (!staticModel?.headers) {
137
147
  unresolvedModelIds.add(model.id);
138
148
  return model;
@@ -165,6 +175,7 @@ export async function resolveProviderModels<TApi extends Api = Api, TModelsDevPa
165
175
  staticModels,
166
176
  cache?.headerOmittedModelIds ?? [],
167
177
  cache?.unrestorableHeaderModelIds ?? [],
178
+ cache?.legacyHeaderRestoreMarkers ?? false,
168
179
  );
169
180
  const usableCachedModels = restoredCache.models.filter(model => !restoredCache.unresolvedModelIds.has(model.id));
170
181
  const cacheHasUnresolvedHeaders = restoredCache.unresolvedModelIds.size > 0;
@@ -245,6 +256,7 @@ export async function resolveProviderModels<TApi extends Api = Api, TModelsDevPa
245
256
  staticModels,
246
257
  latestCache?.headerOmittedModelIds ?? cache?.headerOmittedModelIds ?? [],
247
258
  latestCache?.unrestorableHeaderModelIds ?? cache?.unrestorableHeaderModelIds ?? [],
259
+ latestCache?.legacyHeaderRestoreMarkers ?? cache?.legacyHeaderRestoreMarkers ?? false,
248
260
  );
249
261
  const latestUsableCacheModels = latestRestoredCache.models.filter(
250
262
  model => !latestRestoredCache.unresolvedModelIds.has(model.id),