@oh-my-pi/pi-catalog 18.17.0 → 18.18.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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [18.18.0] - 2026-11-07
6
+
7
+ ### Fixed
8
+
9
+ - Fixed Mistral discovery listing embedding, OCR, moderation, transcription, and speech models as chat models; only rows with `capabilities.completion_chat` are offered, with context, image input, and reasoning read from the row ([#18244](https://github.com/can1357/oh-my-pi/pull/18244) by [@VeigaPunk](https://github.com/VeigaPunk))
10
+ - A failed refresh no longer replaces a cached catalog whose models carry request headers with an empty snapshot; the next launch refetches it instead ([#18227](https://github.com/can1357/oh-my-pi/issues/18227))
11
+
5
12
  ## [18.17.0] - 2026-11-06
6
13
 
7
14
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oh-my-pi/pi-catalog",
3
- "version": "18.17.0",
3
+ "version": "18.18.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",
@@ -88,12 +88,12 @@
88
88
  "gen:proto": "bun scripts/generate-protocols.ts"
89
89
  },
90
90
  "dependencies": {
91
- "@oh-my-pi/omptype": "18.17.0",
92
- "@oh-my-pi/pi-utils": "18.17.0"
91
+ "@oh-my-pi/omptype": "18.18.0",
92
+ "@oh-my-pi/pi-utils": "18.18.0"
93
93
  },
94
94
  "devDependencies": {
95
95
  "@bgotink/kdl": "0.4.0",
96
- "@oh-my-pi/pi-ai": "18.17.0",
96
+ "@oh-my-pi/pi-ai": "18.18.0",
97
97
  "@types/bun": "^1.3.14"
98
98
  },
99
99
  "engines": {
@@ -432,7 +432,10 @@ export async function resolveProviderModels<TApi extends Api = Api, TModelsDevPa
432
432
  // non-authoritative row reads back as a fresh catalog and suppresses
433
433
  // refetch for NON_AUTHORITATIVE_RETRY_MS, hiding every discovery-only
434
434
  // model until it ages out. Writing nothing lets the next launch retry
435
- // immediately (#10964).
435
+ // immediately (#10964). Likewise keep a prior row whose header-bearing
436
+ // models cannot be restored offline: rewriting it would drop them and
437
+ // mark the remainder fresh, while the untouched row keeps forcing a
438
+ // refetch that recovers them (#18227).
436
439
  const latestCache = readModelCache<TApi>(cacheProviderId, ttlMs, now, dbPath);
437
440
  const latestRestoredCache = restoreCachedModelHeaders(
438
441
  latestCache?.models ?? cache?.models ?? [],
@@ -468,7 +471,10 @@ export async function resolveProviderModels<TApi extends Api = Api, TModelsDevPa
468
471
  ? retainModelIds(mergedFallbackModels, latestUsableCacheModels)
469
472
  : mergedFallbackModels,
470
473
  );
471
- if (fallbackSnapshotModels.length > 0 || latestCache !== null || cache !== null) {
474
+ if (
475
+ latestRestoredCache.unresolvedModelIds.size === 0 &&
476
+ (fallbackSnapshotModels.length > 0 || latestCache !== null || cache !== null)
477
+ ) {
472
478
  writeModelCache(
473
479
  cacheProviderId,
474
480
  now(),
@@ -3222,10 +3222,47 @@ export interface MistralModelManagerConfig {
3222
3222
  fetch?: FetchImpl;
3223
3223
  }
3224
3224
 
3225
+ /**
3226
+ * Mistral's `/v1/models` lists every SKU the key can call, including
3227
+ * embedding, OCR, moderation, transcription, and speech models, and flags chat
3228
+ * support per row in `capabilities.completion_chat`. Rows without it reject
3229
+ * `/chat/completions` with `invalid_model`, so they stay out of the chat
3230
+ * roster. A row with no `capabilities` object (a compatible gateway on a
3231
+ * custom `baseUrl`) is kept.
3232
+ *
3233
+ * The context window comes from `max_context_length`. `capabilities.vision`
3234
+ * and `capabilities.reasoning` can add image input or reasoning to a bundled
3235
+ * reference, never remove them.
3236
+ */
3237
+ function mapMistralModel(
3238
+ entry: OpenAICompatibleModelRecord,
3239
+ defaults: ModelSpec<"openai-completions">,
3240
+ reference: ModelSpec<"openai-completions"> | undefined,
3241
+ ): ModelSpec<"openai-completions"> | null {
3242
+ const capabilities = isRecord(entry.capabilities) ? entry.capabilities : undefined;
3243
+ if (capabilities && capabilities.completion_chat !== true) return null;
3244
+ const model = mapWithBundledReference(entry, defaults, reference);
3245
+ const input: ModelSpec<"openai-completions">["input"] =
3246
+ capabilities?.vision === true && !model.input.includes("image") ? [...model.input, "image"] : model.input;
3247
+ return {
3248
+ ...model,
3249
+ reasoning: model.reasoning || capabilities?.reasoning === true,
3250
+ input,
3251
+ contextWindow: toPositiveNumber(entry.max_context_length, model.contextWindow),
3252
+ };
3253
+ }
3254
+
3225
3255
  export function mistralModelManagerOptions(
3226
3256
  config?: MistralModelManagerConfig,
3227
3257
  ): ModelManagerOptions<"openai-completions"> {
3228
- return createSimpleOpenAICompletionsOptions("mistral", "https://api.mistral.ai/v1", config);
3258
+ return createOpenAICompatibleModelManagerOptions({
3259
+ api: "openai-completions",
3260
+ providerId: "mistral",
3261
+ defaultBaseUrl: "https://api.mistral.ai/v1",
3262
+ config,
3263
+ requireApiKey: true,
3264
+ mapModel: mapMistralModel,
3265
+ });
3229
3266
  }
3230
3267
 
3231
3268
  // ---------------------------------------------------------------------------