@oh-my-pi/pi-catalog 16.1.10 → 16.1.12

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,12 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [16.1.11] - 2026-06-21
6
+
7
+ ### Fixed
8
+
9
+ - Fixed Umans `umans-glm-5.1` / `umans-glm-5.2` advertising native image input. The `models/info` endpoint reports `supports_vision: "via-handoff"` for the GLM models, meaning vision routes through a separate handoff pre-analysis step instead of accepting raw image blocks; `umansSupportsVision` treated any non-empty string as native vision support, so image prompts went directly to GLM and were rejected with `400 This model does not support image inputs`. The helper now requires `supports_vision === true`, the bundled GLM 5.1/5.2 rows are corrected to text-only, and stale mismatched Umans cache rows for those ids are dropped so the vision-handoff path runs even before a successful refresh. ([#3184](https://github.com/can1357/oh-my-pi/issues/3184))
10
+
5
11
  ## [16.1.9] - 2026-06-21
6
12
 
7
13
  ### Fixed
@@ -28,6 +28,8 @@ export interface ModelManagerOptions<TApi extends Api = Api, TModelsDevPayload =
28
28
  cacheTtlMs?: number;
29
29
  /** When true, a successful dynamic fetch is the complete provider catalog and prunes static-only models. */
30
30
  dynamicModelsAuthoritative?: boolean;
31
+ /** Cached model ids to ignore when the cache was written against a different static catalog fingerprint. */
32
+ dropCachedModelIdsOnStaticMismatch?: readonly string[];
31
33
  /** Optional dynamic endpoint fetcher. */
32
34
  fetchDynamicModels?: () => Promise<readonly ModelSpec<TApi>[] | null>;
33
35
  /** Optional models.dev fallback hook. */
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.1.10",
4
+ "version": "16.1.12",
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.0",
37
- "@oh-my-pi/pi-utils": "16.1.10",
37
+ "@oh-my-pi/pi-utils": "16.1.12",
38
38
  "arktype": "^2.2.0",
39
39
  "zod": "^4"
40
40
  },
41
41
  "devDependencies": {
42
- "@oh-my-pi/pi-ai": "16.1.10",
42
+ "@oh-my-pi/pi-ai": "16.1.12",
43
43
  "@types/bun": "^1.3.14"
44
44
  },
45
45
  "engines": {
@@ -39,6 +39,10 @@ const GLM_CODING_PLAN_STREAM_IDLE_TIMEOUT_MS = 600_000;
39
39
  const DEEPSEEK_REASONING_STREAM_IDLE_TIMEOUT_MS = 300_000;
40
40
  /** Kimi K2.6 can spend several minutes reasoning before the first visible token. */
41
41
  const KIMI_K26_REASONING_STREAM_IDLE_TIMEOUT_MS = 300_000;
42
+ /** Xiaomi MiMo Pro on api.xiaomimimo.com can stall ~2min before the first event (issue #1770). */
43
+ const XIAOMI_MIMO_STREAM_IDLE_TIMEOUT_MS = 300_000;
44
+ /** Alibaba Coding Plan (coding-intl.dashscope) qwen models idle before the first event (issue #1770). */
45
+ const ALIBABA_CODING_PLAN_STREAM_IDLE_TIMEOUT_MS = 600_000;
42
46
  const MINIMAX_PROVIDER_OR_ID_PATTERN = /minimax/i;
43
47
  const DSML_HEALING_PROVIDERS = new Set([
44
48
  "ollama",
@@ -287,11 +291,15 @@ export function buildOpenAICompat(spec: ModelSpec<"openai-completions">): Resolv
287
291
  const streamIdleTimeoutMs =
288
292
  GLM_CODING_PLAN_MODEL_PATTERN.test(spec.id) && (isZai || isZhipu)
289
293
  ? GLM_CODING_PLAN_STREAM_IDLE_TIMEOUT_MS
290
- : spec.reasoning && isKimiK26ModelId(spec.id)
291
- ? KIMI_K26_REASONING_STREAM_IDLE_TIMEOUT_MS
292
- : spec.reasoning && isDirectDeepseekApi
293
- ? DEEPSEEK_REASONING_STREAM_IDLE_TIMEOUT_MS
294
- : undefined;
294
+ : provider === "alibaba-coding-plan"
295
+ ? ALIBABA_CODING_PLAN_STREAM_IDLE_TIMEOUT_MS
296
+ : isXiaomiMimo
297
+ ? XIAOMI_MIMO_STREAM_IDLE_TIMEOUT_MS
298
+ : spec.reasoning && isKimiK26ModelId(spec.id)
299
+ ? KIMI_K26_REASONING_STREAM_IDLE_TIMEOUT_MS
300
+ : spec.reasoning && isDirectDeepseekApi
301
+ ? DEEPSEEK_REASONING_STREAM_IDLE_TIMEOUT_MS
302
+ : undefined;
295
303
 
296
304
  // Fireworks "Fast" variants (`<id>-fast`) are served from the router
297
305
  // namespace (`accounts/fireworks/routers/<id>-fast`), like Fire Pass, rather
@@ -39,6 +39,8 @@ export interface ModelManagerOptions<TApi extends Api = Api, TModelsDevPayload =
39
39
  cacheTtlMs?: number;
40
40
  /** When true, a successful dynamic fetch is the complete provider catalog and prunes static-only models. */
41
41
  dynamicModelsAuthoritative?: boolean;
42
+ /** Cached model ids to ignore when the cache was written against a different static catalog fingerprint. */
43
+ dropCachedModelIdsOnStaticMismatch?: readonly string[];
42
44
  /** Optional dynamic endpoint fetcher. */
43
45
  fetchDynamicModels?: () => Promise<readonly ModelSpec<TApi>[] | null>;
44
46
  /** Optional models.dev fallback hook. */
@@ -148,7 +150,13 @@ export async function resolveProviderModels<TApi extends Api = Api, TModelsDevPa
148
150
  const shouldUseFreshCacheAsAuthoritative =
149
151
  strategy === "online-if-uncached" && hasUsableFreshCache && hasAuthoritativeCache;
150
152
  const dynamicFetchSucceeded = fetchedDynamicModels !== null;
151
- const cacheModels = dynamicFetchSucceeded ? [] : normalizeModelList<TApi>(cache?.models ?? []);
153
+ const cacheModels = dynamicFetchSucceeded
154
+ ? []
155
+ : dropCachedModelIdsOnStaticMismatch(
156
+ normalizeModelList<TApi>(cache?.models ?? []),
157
+ cacheFingerprintMatches,
158
+ options.dropCachedModelIdsOnStaticMismatch,
159
+ );
152
160
  const dynamicModels = fetchedDynamicModels ?? [];
153
161
  const mergedWithCache = mergeDynamicModels(mergeModelSources(staticModels, modelsDevModels), cacheModels);
154
162
  const mergedModels = mergeDynamicModels(mergedWithCache, dynamicModels);
@@ -180,7 +188,11 @@ export async function resolveProviderModels<TApi extends Api = Api, TModelsDevPa
180
188
  collapseBuiltModelVariants(
181
189
  mergeDynamicModels(
182
190
  mergeModelSources(staticModels, modelsDevModels),
183
- normalizeModelList<TApi>(latestCache?.models ?? cache?.models ?? []),
191
+ dropCachedModelIdsOnStaticMismatch(
192
+ normalizeModelList<TApi>(latestCache?.models ?? cache?.models ?? []),
193
+ cacheFingerprintMatches,
194
+ options.dropCachedModelIdsOnStaticMismatch,
195
+ ),
184
196
  ),
185
197
  ),
186
198
  false,
@@ -248,6 +260,18 @@ function shouldFetchRemoteSources(
248
260
  return false;
249
261
  }
250
262
 
263
+ function dropCachedModelIdsOnStaticMismatch<TApi extends Api>(
264
+ models: readonly Model<TApi>[],
265
+ cacheFingerprintMatches: boolean,
266
+ ids: readonly string[] | undefined,
267
+ ): Model<TApi>[] {
268
+ if (cacheFingerprintMatches || ids === undefined || ids.length === 0 || models.length === 0) {
269
+ return models.length === 0 ? [] : [...models];
270
+ }
271
+ const droppedIds = new Set(ids);
272
+ return models.filter(model => !droppedIds.has(model.id));
273
+ }
274
+
251
275
  function mergeModelSources<TApi extends Api>(...sources: readonly (readonly Model<TApi>[])[]): Model<TApi>[] {
252
276
  // Strip out empty/missing sources up front. The hot path is `(static, [])`
253
277
  // (modelsDev disabled / failed) — a single non-empty source means we can
package/src/models.json CHANGED
@@ -69284,8 +69284,7 @@
69284
69284
  "baseUrl": "https://api.code.umans.ai",
69285
69285
  "reasoning": true,
69286
69286
  "input": [
69287
- "text",
69288
- "image"
69287
+ "text"
69289
69288
  ],
69290
69289
  "cost": {
69291
69290
  "input": 0,
@@ -69327,8 +69326,7 @@
69327
69326
  ]
69328
69327
  },
69329
69328
  "input": [
69330
- "text",
69331
- "image"
69329
+ "text"
69332
69330
  ],
69333
69331
  "cost": {
69334
69332
  "input": 0,
@@ -568,6 +568,7 @@ const UMANS_REASONING_EFFORT_BY_LEVEL: Record<string, Effort> = {
568
568
  xhigh: Effort.XHigh,
569
569
  };
570
570
  const UMANS_DEFAULT_REASONING_EFFORTS = [Effort.Minimal, Effort.Low, Effort.Medium, Effort.High, Effort.XHigh] as const;
571
+ const UMANS_VIA_HANDOFF_MODEL_IDS = ["umans-glm-5.1", "umans-glm-5.2"] as const;
571
572
 
572
573
  export interface UmansModelManagerConfig {
573
574
  apiKey?: string;
@@ -586,8 +587,18 @@ function normalizeUmansBaseUrl(baseUrl: string | undefined): string {
586
587
  return normalized.endsWith("/v1") ? normalized.slice(0, -3) : normalized;
587
588
  }
588
589
 
590
+ /**
591
+ * Umans `models/info` reports `supports_vision: true` for natively
592
+ * vision-capable models and a non-empty string sentinel (e.g.
593
+ * `"via-handoff"`) for models that route image inputs through a vision
594
+ * handoff pre-analysis step instead of accepting raw image blocks. Only
595
+ * `true` means the model accepts image content directly; sentinel values
596
+ * MUST map to text-only so the agent's vision-handoff path runs instead
597
+ * of triggering an upstream HTTP 400 (`This model does not support image
598
+ * inputs`).
599
+ */
589
600
  function umansSupportsVision(value: unknown): boolean {
590
- return value === true || (typeof value === "string" && value.length > 0);
601
+ return value === true;
591
602
  }
592
603
 
593
604
  function umansReasoningSupported(value: unknown): boolean {
@@ -704,6 +715,7 @@ export function umansModelManagerOptions(config?: UmansModelManagerConfig): Mode
704
715
  return {
705
716
  providerId: "umans",
706
717
  dynamicModelsAuthoritative: true,
718
+ dropCachedModelIdsOnStaticMismatch: UMANS_VIA_HANDOFF_MODEL_IDS,
707
719
  fetchDynamicModels: () => fetchUmansModelsInfo({ baseUrl, apiKey, fetch: config?.fetch, references }),
708
720
  };
709
721
  }