@oh-my-pi/pi-catalog 16.5.0 → 16.5.2

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,20 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [16.5.2] - 2026-07-14
6
+
7
+ ### Fixed
8
+
9
+ - Fixed OpenCode Zen and Go discovery to replace stale bundled models with each provider's live model catalog.
10
+
11
+ ## [16.5.1] - 2026-07-14
12
+
13
+ ### Fixed
14
+
15
+ - Fixed reasoning effort mapping for Z.ai GLM-5.2 on the Anthropic messages endpoint to correctly use the two-tier scale (high, max) and emit output_config.effort.
16
+ - Fixed an issue where stale cached model limits would override updated static catalog limits after a catalog fingerprint mismatch.
17
+ - Fixed Cursor discovery to correctly preserve GetUsableModels max-mode metadata for premium models and invalidate stale cache entries.
18
+
5
19
  ## [16.4.3] - 2026-07-11
6
20
 
7
21
  ### Fixed
@@ -264,11 +264,13 @@ export declare const CATALOG_PROVIDERS: readonly [{
264
264
  readonly defaultModel: "kimi-k2.7-code";
265
265
  readonly envVars: readonly ["OPENCODE_API_KEY"];
266
266
  readonly createModelManagerOptions: (config: ModelManagerConfig) => import("../index.js").ModelManagerOptions<import("../index.js").Api, unknown>;
267
+ readonly dynamicModelsAuthoritative: true;
267
268
  }, {
268
269
  readonly id: "opencode-zen";
269
270
  readonly defaultModel: "claude-opus-4-8";
270
271
  readonly envVars: readonly ["OPENCODE_API_KEY"];
271
272
  readonly createModelManagerOptions: (config: ModelManagerConfig) => import("../index.js").ModelManagerOptions<import("../index.js").Api, unknown>;
273
+ readonly dynamicModelsAuthoritative: true;
272
274
  }, {
273
275
  readonly id: "openrouter";
274
276
  readonly defaultModel: "openai/gpt-5.5";
@@ -590,6 +590,8 @@ export interface Model<TApi extends Api = Api> {
590
590
  supportsTools?: boolean;
591
591
  /** GitLab Duo Workflow root namespace selected during catalog discovery. */
592
592
  gitlabDuoWorkflowRootNamespaceId?: string;
593
+ /** Cursor `max_mode` request flag returned by `GetUsableModels` for premium models that require max mode. */
594
+ cursorMaxMode?: boolean;
593
595
  cost: {
594
596
  input: number;
595
597
  output: number;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-catalog",
4
- "version": "16.5.0",
4
+ "version": "16.5.2",
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": "16.5.0",
37
+ "@oh-my-pi/pi-utils": "16.5.2",
38
38
  "arktype": "2.2.3",
39
39
  "zod": "^4"
40
40
  },
41
41
  "devDependencies": {
42
- "@oh-my-pi/pi-ai": "16.5.0",
42
+ "@oh-my-pi/pi-ai": "16.5.2",
43
43
  "@types/bun": "^1.3.14"
44
44
  },
45
45
  "engines": {
@@ -35,6 +35,7 @@ const CursorModelDetailsSchema = type({
35
35
  displayModelId: OptionalDisplayNameSchema.default(undefined),
36
36
  aliases: CursorAliasesSchema.default(() => []),
37
37
  "thinkingDetails?": "unknown",
38
+ maxMode: "boolean = false",
38
39
  });
39
40
 
40
41
  const CursorModelsInnerSchema = type("unknown[]");
@@ -290,6 +291,7 @@ function normalizeCursorModel(
290
291
  name,
291
292
  baseUrl: baseUrlOverride ?? reference.baseUrl,
292
293
  reasoning,
294
+ cursorMaxMode: details.maxMode,
293
295
  };
294
296
  }
295
297
  return {
@@ -303,6 +305,7 @@ function normalizeCursorModel(
303
305
  cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
304
306
  contextWindow: DEFAULT_CONTEXT_WINDOW,
305
307
  maxTokens: DEFAULT_MAX_TOKENS,
308
+ cursorMaxMode: details.maxMode,
306
309
  };
307
310
  }
308
311
 
@@ -152,8 +152,9 @@ export async function resolveProviderModels<TApi extends Api = Api, TModelsDevPa
152
152
  const dynamicFetchSucceeded = fetchedDynamicModels !== null;
153
153
  const cacheModels = dynamicFetchSucceeded
154
154
  ? []
155
- : dropCachedModelIdsOnStaticMismatch(
155
+ : prepareCacheModelsForStaticMismatch(
156
156
  normalizeModelList<TApi>(cache?.models ?? []),
157
+ staticModels,
157
158
  cacheFingerprintMatches,
158
159
  options.dropCachedModelIdsOnStaticMismatch,
159
160
  );
@@ -188,8 +189,9 @@ export async function resolveProviderModels<TApi extends Api = Api, TModelsDevPa
188
189
  collapseBuiltModelVariants(
189
190
  mergeDynamicModels(
190
191
  mergeModelSources(staticModels, modelsDevModels),
191
- dropCachedModelIdsOnStaticMismatch(
192
+ prepareCacheModelsForStaticMismatch(
192
193
  normalizeModelList<TApi>(latestCache?.models ?? cache?.models ?? []),
194
+ staticModels,
193
195
  cacheFingerprintMatches,
194
196
  options.dropCachedModelIdsOnStaticMismatch,
195
197
  ),
@@ -260,16 +262,29 @@ function shouldFetchRemoteSources(
260
262
  return false;
261
263
  }
262
264
 
263
- function dropCachedModelIdsOnStaticMismatch<TApi extends Api>(
265
+ function prepareCacheModelsForStaticMismatch<TApi extends Api>(
264
266
  models: readonly Model<TApi>[],
267
+ staticModels: readonly Model<TApi>[],
265
268
  cacheFingerprintMatches: boolean,
266
269
  ids: readonly string[] | undefined,
267
270
  ): Model<TApi>[] {
268
- if (cacheFingerprintMatches || ids === undefined || ids.length === 0 || models.length === 0) {
269
- return models.length === 0 ? [] : [...models];
271
+ if (models.length === 0) {
272
+ return [];
273
+ }
274
+ if (cacheFingerprintMatches) {
275
+ return [...models];
276
+ }
277
+
278
+ const droppedIds = ids && ids.length > 0 ? new Set(ids) : undefined;
279
+ const staticIds = staticModels.length > 0 ? new Set(staticModels.map(model => model.id)) : undefined;
280
+ const sanitizedModels: Model<TApi>[] = [];
281
+ for (const model of models) {
282
+ if (droppedIds?.has(model.id)) {
283
+ continue;
284
+ }
285
+ sanitizedModels.push(staticIds?.has(model.id) ? { ...model, contextWindow: null, maxTokens: null } : model);
270
286
  }
271
- const droppedIds = new Set(ids);
272
- return models.filter(model => !droppedIds.has(model.id));
287
+ return sanitizedModels;
273
288
  }
274
289
 
275
290
  function mergeModelSources<TApi extends Api>(...sources: readonly (readonly Model<TApi>[])[]): Model<TApi>[] {
@@ -318,7 +318,7 @@ function getModelDefinedEfforts<TApi extends Api>(
318
318
  }
319
319
  if (
320
320
  isZaiThinkingFormat(compat) ||
321
- isUmansGlm52ReasoningEffortModel(spec) ||
321
+ isAnthropicMessagesGlm52ReasoningEffortModel(spec) ||
322
322
  isOllamaCloudGlm52ReasoningEffortModel(spec) ||
323
323
  spec.provider === "baseten"
324
324
  ) {
@@ -394,8 +394,12 @@ function isOllamaCloudGlm52ReasoningEffortModel<TApi extends Api>(spec: ModelSpe
394
394
  return spec.api === "ollama-chat" && spec.provider === "ollama-cloud" && isGlm52ReasoningEffortModelId(spec.id);
395
395
  }
396
396
 
397
- function isUmansGlm52ReasoningEffortModel<TApi extends Api>(spec: ModelSpec<TApi>): boolean {
398
- return spec.api === "anthropic-messages" && spec.provider === "umans" && isGlm52ReasoningEffortModelId(spec.id);
397
+ function isAnthropicMessagesGlm52ReasoningEffortModel<TApi extends Api>(spec: ModelSpec<TApi>): boolean {
398
+ return (
399
+ spec.api === "anthropic-messages" &&
400
+ (spec.provider === "umans" || spec.provider === "zai") &&
401
+ isGlm52ReasoningEffortModelId(spec.id)
402
+ );
399
403
  }
400
404
 
401
405
  function isMinimaxReasoningModelOnAnthropicEndpoint<TApi extends Api>(spec: ModelSpec<TApi>): boolean {
@@ -617,7 +621,7 @@ function inferThinkingControlMode<TApi extends Api>(
617
621
  if (isMinimaxReasoningModelOnAnthropicEndpoint(spec)) {
618
622
  return "anthropic-adaptive";
619
623
  }
620
- if (isUmansGlm52ReasoningEffortModel(spec)) {
624
+ if (isAnthropicMessagesGlm52ReasoningEffortModel(spec)) {
621
625
  return "anthropic-budget-effort";
622
626
  }
623
627
  if (parsedModel.family === "anthropic") {
package/src/models.json CHANGED
@@ -88484,13 +88484,10 @@
88484
88484
  "contextWindow": 1000000,
88485
88485
  "maxTokens": 131072,
88486
88486
  "thinking": {
88487
- "mode": "budget",
88487
+ "mode": "anthropic-budget-effort",
88488
88488
  "efforts": [
88489
- "minimal",
88490
- "low",
88491
- "medium",
88492
88489
  "high",
88493
- "xhigh"
88490
+ "max"
88494
88491
  ]
88495
88492
  }
88496
88493
  },
@@ -311,12 +311,14 @@ export const CATALOG_PROVIDERS = [
311
311
  defaultModel: "kimi-k2.7-code",
312
312
  envVars: ["OPENCODE_API_KEY"],
313
313
  createModelManagerOptions: (config: ModelManagerConfig) => opencodeGoModelManagerOptions(config),
314
+ dynamicModelsAuthoritative: true,
314
315
  },
315
316
  {
316
317
  id: "opencode-zen",
317
318
  defaultModel: "claude-opus-4-8",
318
319
  envVars: ["OPENCODE_API_KEY"],
319
320
  createModelManagerOptions: (config: ModelManagerConfig) => opencodeZenModelManagerOptions(config),
321
+ dynamicModelsAuthoritative: true,
320
322
  },
321
323
  {
322
324
  id: "openrouter",
@@ -2036,6 +2036,16 @@ function openCodeBaseUrlForApi(api: Api, basePath: string): string {
2036
2036
  return api === "anthropic-messages" ? basePath : `${basePath}/v1`;
2037
2037
  }
2038
2038
 
2039
+ function openCodeModelCacheProviderId(
2040
+ providerId: "opencode-go" | "opencode-zen",
2041
+ apiKey: string | undefined,
2042
+ discoveryBaseUrl: string,
2043
+ ): string {
2044
+ // OpenCode catalogs are entitlement-scoped; isolate authoritative rows by credential and endpoint.
2045
+ const scope = `${apiKey ?? ""}\u0000${discoveryBaseUrl}`;
2046
+ return `${providerId}:models-v1:${Bun.hash(scope).toString(36)}`;
2047
+ }
2048
+
2039
2049
  function openCodeModelManagerOptions(
2040
2050
  providerId: "opencode-go" | "opencode-zen",
2041
2051
  defaultBasePath: string,
@@ -2047,6 +2057,8 @@ function openCodeModelManagerOptions(
2047
2057
  const references = createBundledReferenceMap<Api>(providerId);
2048
2058
  return {
2049
2059
  providerId,
2060
+ cacheProviderId: openCodeModelCacheProviderId(providerId, apiKey, discoveryBaseUrl),
2061
+ dynamicModelsAuthoritative: true,
2050
2062
  ...(apiKey && {
2051
2063
  fetchDynamicModels: () =>
2052
2064
  fetchOpenAICompatibleModels<Api>({
@@ -42,10 +42,13 @@ export interface CursorModelManagerConfig {
42
42
  clientVersion?: string;
43
43
  }
44
44
 
45
+ const CURSOR_CACHE_PROVIDER_ID = "cursor:max-mode-v2";
46
+
45
47
  export function cursorModelManagerOptions(config: CursorModelManagerConfig = {}): ModelManagerOptions<"cursor-agent"> {
46
48
  const { apiKey, baseUrl, clientVersion } = config;
47
49
  return {
48
50
  providerId: "cursor",
51
+ cacheProviderId: CURSOR_CACHE_PROVIDER_ID,
49
52
  ...(apiKey
50
53
  ? {
51
54
  fetchDynamicModels: async () => {
package/src/types.ts CHANGED
@@ -718,6 +718,8 @@ export interface Model<TApi extends Api = Api> {
718
718
  supportsTools?: boolean;
719
719
  /** GitLab Duo Workflow root namespace selected during catalog discovery. */
720
720
  gitlabDuoWorkflowRootNamespaceId?: string;
721
+ /** Cursor `max_mode` request flag returned by `GetUsableModels` for premium models that require max mode. */
722
+ cursorMaxMode?: boolean;
721
723
  cost: {
722
724
  input: number; // $/million tokens
723
725
  output: number; // $/million tokens