@oh-my-pi/pi-catalog 17.2.9 → 17.2.10

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,16 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [17.2.10] - 2026-08-06
6
+
7
+ ### Changed
8
+
9
+ - Removed the zod dependency by migrating GitLab Duo Workflow discovery schemas to omptype.
10
+
11
+ ### Fixed
12
+
13
+ - Corrected thinking-effort tiers for deepseek-v4-flash to include the low tier alongside high and max.
14
+
5
15
  ## [17.2.9] - 2026-08-05
6
16
 
7
17
  ### Fixed
@@ -34,6 +34,13 @@ export declare const isQwenModelId: (modelId: string) => boolean;
34
34
  export declare const isGemmaModelId: (modelId: string) => boolean;
35
35
  /** DeepSeek family by id or display name (proxies often rename the id but keep the name). */
36
36
  export declare const isDeepseekModelIdOrName: (modelId: string) => boolean;
37
+ /**
38
+ * DeepSeek V4 Flash SKU in any host/namespace form (`deepseek-v4-flash`, dated
39
+ * `deepseek-v4-flash-0731`, `deepseek-ai/DeepSeek-V4-Flash`). Flash is the only
40
+ * V4 model whose `reasoning_effort` accepts the `low` tier; V4 Pro tops out at
41
+ * `high`/`max`. See https://api-docs.deepseek.com/api/create-chat-completion.
42
+ */
43
+ export declare const isDeepseekV4FlashModelId: (modelId: string) => boolean;
37
44
  /** Xiaomi MiMo family by id or display name. */
38
45
  export declare const isMimoModelIdOrName: (modelId: string) => boolean;
39
46
  /**
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.2.9",
4
+ "version": "17.2.10",
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",
@@ -35,12 +35,11 @@
35
35
  },
36
36
  "dependencies": {
37
37
  "@bufbuild/protobuf": "^2.12.1",
38
- "@oh-my-pi/omptype": "17.2.9",
39
- "@oh-my-pi/pi-utils": "17.2.9",
40
- "zod": "^4"
38
+ "@oh-my-pi/omptype": "17.2.10",
39
+ "@oh-my-pi/pi-utils": "17.2.10"
41
40
  },
42
41
  "devDependencies": {
43
- "@oh-my-pi/pi-ai": "17.2.9",
42
+ "@oh-my-pi/pi-ai": "17.2.10",
44
43
  "@types/bun": "^1.3.14"
45
44
  },
46
45
  "engines": {
@@ -1,6 +1,6 @@
1
1
  import * as fs from "node:fs/promises";
2
2
  import * as path from "node:path";
3
- import { z } from "zod/v4";
3
+ import { type } from "@oh-my-pi/omptype";
4
4
  import type { FetchImpl, ModelSpec } from "../types";
5
5
  import { discoveryFetch, isRecord } from "../utils";
6
6
 
@@ -54,20 +54,28 @@ const ProjectRootNamespaceQuery = `query omp_gitlabDuoWorkflowProjectRootNamespa
54
54
  }
55
55
  }`;
56
56
 
57
- const modelRefSchema = z
58
- .object({
59
- name: z.string().optional().catch(undefined),
60
- ref: z.string().optional().catch(undefined),
61
- })
62
- .loose();
63
-
64
- const aiChatAvailableModelsSchema = z
65
- .object({
66
- defaultModel: z.unknown().nullable().optional(),
67
- selectableModels: z.array(z.unknown()).nullable().optional().catch([]),
68
- pinnedModel: z.unknown().nullable().optional(),
69
- })
70
- .loose();
57
+ const resilientString = type("unknown").pipe(value => {
58
+ if (value === undefined) return undefined;
59
+ const parsed = type("string")(value);
60
+ return parsed instanceof type.errors ? undefined : parsed;
61
+ });
62
+
63
+ const resilientUnknownArray = type("unknown").pipe(value => {
64
+ if (value === undefined || value === null) return value;
65
+ const parsed = type("unknown[]")(value);
66
+ return parsed instanceof type.errors ? [] : parsed;
67
+ });
68
+
69
+ const modelRefSchema = type({
70
+ "name?": resilientString,
71
+ "ref?": resilientString,
72
+ });
73
+
74
+ const aiChatAvailableModelsSchema = type({
75
+ "defaultModel?": "unknown",
76
+ "selectableModels?": resilientUnknownArray,
77
+ "pinnedModel?": "unknown",
78
+ });
71
79
 
72
80
  type GitLabDuoWorkflowCandidateSource = "override" | "project" | "remote" | "group";
73
81
 
@@ -540,17 +548,15 @@ async function postGraphQL(
540
548
  }
541
549
 
542
550
  function parseAvailability(value: unknown): GitLabDuoWorkflowAvailability | null {
543
- const parsed = aiChatAvailableModelsSchema.safeParse(value);
544
- if (!parsed.success) {
545
- return null;
546
- }
551
+ const parsed = aiChatAvailableModelsSchema(value);
552
+ if (parsed instanceof type.errors) return null;
547
553
  return {
548
- defaultModel: parseModelRef(parsed.data.defaultModel),
549
- selectableModels: (parsed.data.selectableModels ?? []).flatMap(model => {
554
+ defaultModel: parseModelRef(parsed.defaultModel),
555
+ selectableModels: (parsed.selectableModels ?? []).flatMap(model => {
550
556
  const parsedModel = parseModelRef(model);
551
557
  return parsedModel ? [parsedModel] : [];
552
558
  }),
553
- pinnedModel: parseModelRef(parsed.data.pinnedModel),
559
+ pinnedModel: parseModelRef(parsed.pinnedModel),
554
560
  };
555
561
  }
556
562
 
@@ -558,15 +564,13 @@ function parseModelRef(value: unknown): GitLabDuoWorkflowModelRef | null {
558
564
  if (value === null || value === undefined) {
559
565
  return null;
560
566
  }
561
- const parsed = modelRefSchema.safeParse(value);
562
- if (!parsed.success) {
563
- return null;
564
- }
565
- const ref = normalizeIdentifier(parsed.data.ref);
567
+ const parsed = modelRefSchema(value);
568
+ if (parsed instanceof type.errors) return null;
569
+ const ref = normalizeIdentifier(parsed.ref);
566
570
  if (!ref) {
567
571
  return null;
568
572
  }
569
- const name = normalizeIdentifier(parsed.data.name) ?? ref;
573
+ const name = normalizeIdentifier(parsed.name) ?? ref;
570
574
  return { name, ref };
571
575
  }
572
576
 
@@ -83,6 +83,16 @@ export const isDeepseekModelIdOrName = memo((value: string): boolean => {
83
83
  return value.toLowerCase().includes("deepseek");
84
84
  });
85
85
 
86
+ /**
87
+ * DeepSeek V4 Flash SKU in any host/namespace form (`deepseek-v4-flash`, dated
88
+ * `deepseek-v4-flash-0731`, `deepseek-ai/DeepSeek-V4-Flash`). Flash is the only
89
+ * V4 model whose `reasoning_effort` accepts the `low` tier; V4 Pro tops out at
90
+ * `high`/`max`. See https://api-docs.deepseek.com/api/create-chat-completion.
91
+ */
92
+ export const isDeepseekV4FlashModelId = memo((modelId: string): boolean => {
93
+ return bareModelId(modelId).toLowerCase().includes("deepseek-v4-flash");
94
+ });
95
+
86
96
  /** Xiaomi MiMo family by id or display name. */
87
97
  export const isMimoModelIdOrName = memo((value: string): boolean => {
88
98
  return value.toLowerCase().includes("mimo");
@@ -24,6 +24,7 @@ import {
24
24
  import {
25
25
  findThinkingVariantToken,
26
26
  isDeepseekModelIdOrName,
27
+ isDeepseekV4FlashModelId,
27
28
  isGlm52ReasoningEffortModelId,
28
29
  isKimiK3ModelId,
29
30
  isMimoModelIdOrName,
@@ -62,9 +63,9 @@ const GEMINI_3_FLASH_EFFORTS: readonly Effort[] = [Effort.Minimal, Effort.Low, E
62
63
  const GPT_5_2_PLUS_EFFORTS: readonly Effort[] = [Effort.Low, Effort.Medium, Effort.High, Effort.XHigh];
63
64
  const GPT_5_1_CODEX_MINI_EFFORTS: readonly Effort[] = [Effort.Medium, Effort.High];
64
65
  const LOW_MEDIUM_HIGH_REASONING_EFFORTS: readonly Effort[] = [Effort.Low, Effort.Medium, Effort.High];
65
- /** Wire-exact `low`/`high`/`max` scale used by Kimi K3 and OpenRouter DeepSeek V4 Flash 0731. */
66
+ /** Wire-exact `low`/`high`/`max` scale used by Kimi K3 and DeepSeek V4 Flash (direct API and aggregators). */
66
67
  const LOW_HIGH_MAX_REASONING_EFFORTS: readonly Effort[] = [Effort.Low, Effort.High, Effort.Max];
67
- /** Wire-exact two-tier scale (`high`/`max`): GLM-5.2 on Z.ai/Umans/Ollama Cloud/Baseten, Sakana Fugu, DeepSeek. */
68
+ /** Wire-exact two-tier scale (`high`/`max`): GLM-5.2 on Z.ai/Umans/Ollama Cloud/Baseten, Sakana Fugu, DeepSeek V4 Pro. */
68
69
  const HIGH_MAX_REASONING_EFFORTS: readonly Effort[] = [Effort.High, Effort.Max];
69
70
  /** OpenRouter's DeepSeek route accepts only `high`. */
70
71
  const HIGH_ONLY_REASONING_EFFORTS: readonly Effort[] = [Effort.High];
@@ -366,14 +367,14 @@ function getModelDefinedEfforts<TApi extends Api>(
366
367
  return OLLAMA_REASONING_EFFORTS;
367
368
  }
368
369
  if (isOpenAICompatReasoningApi(spec.api) && isDeepseekReasoningModel(spec)) {
369
- // OpenRouter generally exposes only high for DeepSeek, but V4 Flash 0731
370
- // advertises and accepts the wire-exact low/high/max ladder.
371
- if (isOpenRouterThinkingFormat(compat)) {
372
- return bareModelId(spec.id) === "deepseek-v4-flash-0731"
373
- ? LOW_HIGH_MAX_REASONING_EFFORTS
374
- : HIGH_ONLY_REASONING_EFFORTS;
370
+ // DeepSeek V4 Flash accepts the wire-exact low/high/max ladder on every
371
+ // host — the direct API and aggregators alike (medium/xhigh map to
372
+ // high). V4 Pro and the older reasoners top out at high/max, and
373
+ // OpenRouter's non-flash DeepSeek route exposes only high.
374
+ if (isDeepseekV4FlashModelId(spec.id)) {
375
+ return LOW_HIGH_MAX_REASONING_EFFORTS;
375
376
  }
376
- return HIGH_MAX_REASONING_EFFORTS;
377
+ return isOpenRouterThinkingFormat(compat) ? HIGH_ONLY_REASONING_EFFORTS : HIGH_MAX_REASONING_EFFORTS;
377
378
  }
378
379
  if (spec.provider === "baseten" && isOpenAIGptOssModelId(spec.id)) {
379
380
  // Baseten's gpt-oss router mirrors its GLM route: high/max only.